Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are changes to an Exception object lost when re-raising it?

I was sure this used to work for me, and I've seen it out on the net (Jolyon Smith and David Moorhouse). Having just tried it in a simple program both in D2007 and in XE2 trial, it doesn't keep the modified Message. As soon as the "raise" happens, the message reverts back to the original exception.

What blindlingly obvious thing am I missing ? The alternative is to "raise Exception.Create(...)" but I want to just propogate the original exception back up the chain, only with additional information tagged along at each exception block.

var a: Integer;
begin
  try
    a := 0;
    Label1.Caption := IntToStr(100 div a);
  except
    on e: Exception do
    begin
      e.Message := 'Extra Info Plus the original : ' + e.Message;
      raise;
    end;
  end;
end;
like image 350
Paul Avatar asked Sep 08 '11 11:09

Paul


2 Answers

Well blow me! This looked so wrong that I had to try it myself, and you're absolutely right! I've narrowed it down to the fact that this is an OS exception (divide by zero) that is generated by the OS itself and not by Delphi. If you try and raise an EIntError yourself you get the expected behaviour, and not what you see above. Note that expected behaviour occurs whenever you raise an exception yourself.

Update: In the System.pas unit there is the following code called when the exception is re-raised:

{ Destroy any objects created for non-delphi exceptions }  MOV     EAX,[EDX].TRaiseFrame.ExceptionRecord AND     [EAX].TExceptionRecord.ExceptionFlags,NOT cUnwinding CMP     [EAX].TExceptionRecord.ExceptionCode,cDelphiException JE      @@delphiException MOV     EAX,[EDX].TRaiseFrame.ExceptObject CALL    TObject.Free CALL    NotifyReRaise 

So if the exception is not a Delphi exception (in this case an OS exception) then the (modified) "Delphi" exception is freed and the original exception is re-raised, thereby throwing away any changes made to the exception. Case closed!

Update 2: (couldn't help myself). You can reproduce this with the following code:

type   TThreadNameInfo = record     InfoType: LongWord;  // must be $00001000     NamePtr: PAnsiChar;  // pointer to message (in user address space)     ThreadId: LongWord;  // thread id ($ffffffff indicates caller thread)     Flags: LongWord;     // reserved for future use, must be zero   end;  var   lThreadNameInfo: TThreadNameInfo;    with lThreadNameInfo do begin     InfoType := $00001000;     NamePtr := PAnsiChar(AnsiString('Division by zero'));     ThreadId := $ffffffff;     Flags := $00000000;   end;   RaiseException($C0000094, 0, sizeof(lThreadNameInfo) div sizeof(LongWord), @lThreadNameInfo); 

Have fun!

like image 83
Misha Avatar answered Oct 03 '22 04:10

Misha


See Misha's explanation. As a workaround, you can do this:

except
  on E: Exception do
  begin
    E := Exception(ExceptObject);
    E.Message := '(Extra info) ' + E.Message;
    AcquireExceptionObject;
    raise E;
  end;
end;
like image 22
Ondrej Kelle Avatar answered Oct 03 '22 03:10

Ondrej Kelle