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

Filed under:

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 handler
and 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 the fault 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