Subterranean IL: Compiling C# exception handlers

An exception handler in C# combines the IL catch and finally exception handling clauses into a single try statement:

How does this get compiled into IL?

Initial implementation

If you remember from my earlier post, finally clauses must be specified with their own .try clause. So, for the initial implementation, we take the try/catch/finally, and simply split it up into two .try clauses (I have to use label syntax for this):

However, the resulting program isn’t verifiable, and doesn’t run:

Nested try blocks

What’s with the verification error? Well, it’s a condition of IL verification that all exception handling regions (try, catch, filter, finally, fault) of a single .try clause have to be completely contained within any outer exception region, and they can’t overlap with any other exception handling clause. In other words, IL exception handling clauses must to be representable in the scoped syntax, and in this example, we’re overlapping catch and finally clauses.

Not only is this example not verifiable, it isn’t semantically correct. The finally handler is specified round the .try. What happens if you were able to run this code, and an exception was thrown?

  1. Program execution enters top of try block, and exception is thrown within it
  2. CLR searches for an exception handler, finds catch
  3. Because control flow is leaving .try, finally block is run
  4. The catch block is run
  5. leave.s End inside the catch handler branches to End label.

We’re actually running the finally before the catch!

What we do about it

What we actually need to do is put the catch clauses inside the finally clause, as this will ensure the finally gets executed at the correct time (this time using scoped syntax):

Returning from methods

There is a further semantic mismatch that the C# compiler has to deal with; in C#, you are allowed to return from within an exception handling block:

However, you can’t ret inside an exception handling block in IL. So the C# compiler does a leave.s to a ret outside the exception handling area, loading/storing any return value to a local variable along the way (as leave.s clears the stack):

Conclusion

As you can see, the C# compiler has quite a few hoops to jump through to translate C# code into semantically-correct IL, and hides the numerous conditions on IL exception handling blocks from the C# programmer.

Next time: onto fault handlers.