One can use Trace
for this. I stuck in an extra, different error to show it is omitted in the output. It should also be clear how to trace other error messages.
foo = Trace[
Table[
a = 1/0;
b = {2, 3}
c = i;,
{i, 2}],
HoldPattern@Message[Set::write, ___],
TraceAbove -> True]
(* Power::infty, Set::write errors *)

If you would like a function like Mr.Wizard's to apply to a code block, we can wrap it up like this:
traceSet = Function[code,
Trace[
code,
HoldPattern@Message[Set::write, __],
TraceAbove -> True],
HoldAll]
Here is a way to view the list of lists of lists of output, somewhat reminiscent of WReach's traceView. (His function is built on TraceScan
, which I could not get to work in the way above. The option TraceAbove -> True
seems to be ignored. Is that a bug?)
Clear[ov];
ov[{e1_HoldForm, rest___}, open_: False] :=
OpenerView[{e1, Column[ov[#, open] & /@ {rest}, Dividers -> All]}, open];
ov[e_, _] := e
Example:
ov[foo, True]

Now you can see the offending code comes from
a = 1/0; b = {2, 3} c = i;
which leads to the actual error, {2, 3} c = i
. Note that the error message already informs us that the immediate source is c {2,3}
.
If it's a very large chunk of code, you can select the bottom segment of the evaluation chain as follows. The length of the chain is controlled by
Length@First@pos == 5
which can be adjusted to suit:
Cases[foo,
e_List /;
With[{pos = Position[e, HoldPattern@Message[Set::write, ___]]},
Length[pos] == 1 && Length@First@pos == 5], Infinity] //
Column[#, Dividers -> All] &

One could also apply the OpenerView
to each element in the output by adding to the pattern in the second argument of Cases
the code :> ov[e, True]
to make it a transformation rule.
Tag Times ... is Protected
message is in 90% cases the result of a missing semicolon. $\endgroup$