What can possibly go wrong in the simple act of modifying the message of an exception to add some additional diagnostic information and then re-raising it?
Quite a lot actually, and all from one simple mistake.
Here’s a simplified version of a fairly common construct that you will encounter in Delphi code:
var
a: Integer;
begin
try
a := 0;
Caption := IntToStr(100 div a);
except
on e: Exception do
begin
e.Message := 'Oops: ' + e.Message;
raise e;
end;
end;
end;
The intention is to add some additional diagnostic information to an exception message to assist in debugging (assuming it is not subsequently handled by some exception handler further up the stack), but without handling the exception itself. i.e. to re-raise the exception.
Fairly innocuous and entirely straightforward - most experienced Delphi developers can probably do this in their sleep.
Which is I think what happened here - sleep-coding I mean - because if you compile and run this code your exception handling will cause an apparent meltdown in your application. After the initial exception is reported - seemingly normally - you will then encounter access violations and even external exceptions.
Some of you may have spotted the problem already, but it took me 20 minutes of trying to figure out why such seemingly simple and harmless code was causing access violations and external exceptions etc etc.
How can raising an exception do that? Perhaps the method was being invoked on an invalid instance or some other similar bad pointer was getting involved in the situation somehow (which was far more complex than this simplified version).
The problem of course lay in the mostly harmless looking:
raise e;
To re-raise an exception you do not specify an exception instance. You only do that when raising a brand new exception. By referencing the “caught” exception instance “e” in the raise statement, the runtime will naively handle that exception (destroying the exception instance) and then goes right ahead and raises that destroyed - dead - instance.
The exception instance gets handled AND re-raised and when the already handled (destroyed) exception arrives at another exception handler, all hell breaks loose.
It strikes me that the compiler/RTL between them should be able to tell when you do this and treat “raise e” as simply “raise” when “e” is a reference to the currently-being handled exception.
But for now, it’s something to be wary of.
Footnote
There’s an entirely clean triple-entendre in this post’s title.
1. The technical aspect covered above - raising a handled (dead) exception
2. Stress related health concerns that have been bothering me for the past 12 months or so and which came to a head at the end of last year, are hopefully if not behind me then at least waving as I sail them by on the freeway. I did not actually die - quite obviously - but believed I was close to it on more than one occasion. and which caused me to take a break from engaging in the Delphi community including this blog. My activity may not be as high as it once was, at least for a while, but I am re-raised at last.
3. It is no exaggeration to say that a factor, albeit a small one, in my recovery has been the undoubted resurgence in the vitality of Delph itself. A raising from the dead (again? some might say) of Delphi.
No comments:
Post a Comment