Re: Structured Exception Handling (was: Try Finally...)

From: VBDis (vbdis_at_aol.com)
Date: 11/01/04


Date: 01 Nov 2004 01:32:32 GMT

Im Artikel <4184d94b$0$37789$e4fe514c@news.xs4all.nl>, "Maarten Wiltink"
<maarten@kittensandcats.net> schreibt:

>If an exception is raised during execution of statementList1, control
>is [immediately] transferred to statementList2; [only] once
>statementList2 finishes executing, the exception is re-raised."

Okay, a hard nut ;-)

>The parts in square brackets are my additions to disambiguate how I
>read this. To me, it promises that nested try-except and try-finally
>statements will be traversed outwards exactly once, executing each
>in accordance with its semantics, in nesting order.

This is the easy part. What really bite me is the re-raise of the exception
(see below).

The weak point in your interpretation is the assumed "immediate" transfer to
statementList2. We certainly agree that some code has to execute after an
exception is thrown, i.e. that code that forces the exit from statementList1
and the continuation in statementList2. According to my observations that code
includes the first (arbitration) pass of the SEH, so that the continuation
effectively occurs in the second (unwinding) pass.

Short proof:

All but the _HandleFinally subroutines start with code like this (here: D4):
        MOV EAX,[ESP+4]
        TEST [EAX].TExceptionRecord.ExceptionFlags,cUnwindInProgress
        JNE @@exit
This means that during unwinding the code immediately returns, without any
further actions. In _HandleFinally instead the opposite behaviour is encoded,
here nothing happens unless one of the unwind flags (cUnwinding or
cUnwindingForExit) is set.

The ExceptionFlags are updated once, before the RtlUnwind call, to the
unwinding state. Consequently no exception filter nor exception handler code
can execute during the unwinding of the stack - this is possible only in the
first pass.

---
Without appropriate debugging techniques one may miss the invocation of all the
_Handle... procedures during the arbitration pass, because at that time no user
code ever executes. The selected exception handler runs after the stack is
unwound and all Finally blocks have been executed, as indicated in the
arguments of the RtlUnwind call. This effectively is the point where exception
handling stops and the "normal" execution of the program begins.
>To me, it promises that nested try-except and try-finally
>statements will be traversed outwards exactly once
This certainly is true for the user code in the Except and Finally blocks.
Nonetheless I hope to have proven that the hidden preambles of that code, the
_Handle... procedures, are called moreoften, during the arbitration pass. It's
the code in these procedures that guarantees that the according /user/ code is
executed exactly once.
Now for the hard nut:
Similar (non-debugging!) techniques are required to determine whether "once
statementList2 finishes executing, the exception is re-raised." is correct.
>From an inspection of the NotifyExceptFinally code I conclude that a re-raise
only occurs when debugging, otherwise the stack is unwound without
interruption. The re-raise effectively may be required to re-activate the
debugger, which otherwise has no chance to trace the user code in the following
Finally blocks. I admit that here I'm on thin ice :-(
But - have you ever seen the debugger reporting another exception, resulting
from a re-raise when exiting from a Finally block?
Some more arguments for non-believers:
When the Delphi code (in a DLL...) is called from some non-Delphi code, then an
exception filter in that external code has the right to specify the location,
where program operation shall be resumed:
>>
void RtlUnwind(
  PVOID TargetFrame,
  PVOID TargetIp,
  PEXCEPTION_RECORD ExceptionRecord,
  PVOID ReturnValue
);
Parameters:
TargetFrame 
[in, optional] Pointer to the call frame that is the target of the unwind. If
this parameter is NULL, the function performs an exit unwind.
TargetIp 
[in, optional] Continuation address of the unwind. This parameter is ignored if
TargetFrame is NULL.
...
<<
Since it's unknown where the normal program flow shall continue, until an
exception filter has indicated that location by calling RtlUnwind, the chain of
guard blocks has to be traversed, in a first pass, until an exception filter
signals to stop the search. During this pass none but the exception filters are
allowed to do something, in detail no Finally code can become active then.
I'll split my comments to your contribution here, to separate different topics.
DoDi


Relevant Pages

  • Re: Why finally?
    ... I get the same results from executing the code as well. ... This seems to be a definite improvement of V1.x of the framework. ... normal exception paths. ... // Inject an asynchronous ThreadAbortException in the target thread, ...
    (microsoft.public.dotnet.languages.csharp)
  • Re: DirectShow Insanity
    ... The exception happens immediately upon executing the ... > and what exactly is shown in the debug output window? ... > what happens if you wait for completion befor exiting the program? ...
    (microsoft.public.win32.programmer.directx.audio)
  • Re: HasRows() vs. Read() and ERROR suppression
    ... > record found by executing the commandText. ... >> Syntax error converting datetime from character string. ... >> on the Command object, ... >> MUST call .Readon the SqlDataReader, or you don't get the exception. ...
    (microsoft.public.dotnet.framework.adonet)
  • Re: WebRequest problems
    ... > I now close the stream as well as the response, ... And are you always executing that Close? ... could be throwing an exception. ... the problem is that there's a connection pool and if the responses ...
    (microsoft.public.dotnet.framework.compactframework)