Subterranean IL: Exception handler semantics
Posted
by Simon Cooper
on Simple Talk
See other posts from Simple Talk
or by Simon Cooper
Published on Mon, 21 Feb 2011 12:00:00 GMT
Indexed on
2011/02/21
23:31 UTC
Read the original article
Hit count: 408
In my blog posts on fault and filter exception handlers, I said that the same behaviour could be replicated using normal catch blocks. Well, that isn't entirely true...
Changing the handler semantics
Consider the following:
.try { .try { .try { newobj instance void [mscorlib]System.Exception::.ctor() // IL for: // e.Data.Add("DictKey", true) throw } fault { ldstr "1: Fault handler" call void [mscorlib]System.Console::WriteLine(string) endfault } } filter { ldstr "2a: Filter logic" call void [mscorlib]System.Console::WriteLine(string) // IL for: // (bool)((Exception)e).Data["DictKey"] endfilter }{ ldstr "2b: Filter handler" call void [mscorlib]System.Console::WriteLine(string) leave.s Return } } catch object { ldstr "3: Catch handler" call void [mscorlib]System.Console::WriteLine(string) leave.s Return } Return: // rest of method
If the filter handler is engaged (true
is inserted into the exception dictionary) then the filter handler gets engaged, and the following gets printed to the console:
2a: Filter logic 1: Fault handler 2b: Filter handlerand if the filter handler isn't engaged, then the following is printed:
2a:Filter logic 1: Fault handler 3: Catch handler
Filter handler execution
The filter handler is executed first. Hmm, ok. Well, what happens if we replaced thefault
block with the C# equivalent (with the exception dictionary value set to false
)?
.try { // throw exception } catch object { ldstr "1: Fault handler" call void [mscorlib]System.Console::WriteLine(string) rethrow }we get this:
1: Fault handler 2a: Filter logic 3: Catch handler
The fault handler is executed first, instead of the filter block.
Eh?
This change in behaviour is due to the way the CLR searches for exception handlers. When an exception is thrown, the CLR stops execution of the thread, and searches up the stack for an exception handler that can handle the exception and stop it propagating further - catch
or filter
handlers. It checks the type clause of catch
clauses, and executes the code in filter blocks to see if the filter can handle the exception.
When the CLR finds a valid handler, it saves the handler's location, then goes back to where the exception was thrown and executes fault
and finally
blocks between there and the handler location, discarding stack frames in the process, until it reaches the handler.
So?
By replacing a fault
with a catch
, we have changed the semantics of when the filter code is executed; by using a rethrow
instruction, we've split up the exception handler search into two - one search to find the first catch
, then a second when the rethrow
instruction is encountered.
This is only really obvious when mixing C# exception handlers with fault
or filter
handlers, so this doesn't affect code written only in C#. However it could cause some subtle and hard-to-debug effects with object initialization and ordering when using and calling code written in a language that can compile fault
and filter
handlers.
© Simple Talk or respective owner