Introduction to MSIL – Part 5 – Exception Handling

In this part of the Introduction to MSIL series I will introduce the constructs that the CLI provides for exception handling.

A try block is used to protect a range of instructions. If an exception is thrown by one of the instructions in the protected block, or by any method called directly or indirectly within the protected block, control is transferred to an appropriate exception handler. A try block is declared with the .try directive.

.try
{
    /* protected code */

    leave.s _CONTINUE
}
<exception handler>

_CONTINUE:

The last instruction in the try block is the leave.s instruction which transfers control to the _CONTINUE label. The leave.s instruction ensures that the evaluation stack is emptied and that the appropriate finally blocks are executed. It follows that if control leaves the protected block by some other means, that an exception was thrown. The exception handler must immediately follow the try block.

The CLI offers four different kinds of handlers that you can employ to construct the error handling semantics that you require for your programming language or application. Let’s examine each one individually.

Catch

The catch exception handler, or catch block, has to be the most well-known form of exception handling, since it is provided directly by C++ as well as C#. A catch block is declared with the catch keyword. The catch clause includes the type of exception that the handler is willing to catch as well as the block of code to transfer control to, given a matching exception object. Catch blocks can also be chained together after a single try block for convenience. The first catch clause with a matching exception type will be chosen as the handler. Consider this example.

.try
{
    ldstr "I'm not a number"
    // ldnull
    // ldstr "123"
    call int32 [mscorlib]System.Int32::Parse(string)

    leave.s _CONTINUE
}
catch [mscorlib]System.ArgumentNullException
{
    callvirt instance string [mscorlib]System.Exception::get_Message()
    call void [mscorlib]System.Console::WriteLine(string)

    leave.s _CONTINUE
}
catch [mscorlib]System.FormatException
{
    callvirt instance string [mscorlib]System.Exception::get_Message()
    call void [mscorlib]System.Console::WriteLine(string)

    leave.s _CONTINUE
}

Here we ask the In32::Parse method to parse "I'm not a number", which predictably throws a FormatException. The first catch handler is never given an opportunity to execute. The FormatException handler dutifully writes a message describing the exception to the console and then calls the leave.s instruction to transfer control to the _CONTINUE label. Where was the exception object? The runtime ensure that a reference to the exception is pushed onto the stack before the handler is invoked. You can experiment by commenting out the ldstr instruction in the try block and un-commenting the ldnull instruction. This will push a null reference onto the stack and result in an ArgumentNullException instance being thrown by the Parse method.

Filter

The filter exception handler is a strange construct for a C++ programmer like me. Instead of matching on an exception type, the filter handler provides a scope block where it can evaluate whether it wants to handle the exception. It indicates to the runtime that it wants to handle the exception by pushing a value of 1 onto the stack. If it decides not to handle the exception it pushes a value of 0 onto the stack. Following the “evaluation” block is the handler block that contains the code that will handle the exception.

.try
{
    // ldstr "I'm not a number"
    ldnull
    // ldstr "123"
    call int32 [mscorlib]System.Int32::Parse(string)

    leave.s _CONTINUE
}
filter
{
    ldstr "filter evaluation\n\t"
    call void [mscorlib]System.Console::Write(string)
   
    callvirt instance string [mscorlib]System.Exception::get_Message()
    call void [mscorlib]System.Console::WriteLine(string)

    ldc.i4.1
    endfilter
}
{
    ldstr "filter handler\n\t"
    call void [mscorlib]System.Console::Write(string)
   
    callvirt instance string [mscorlib]System.Exception::get_Message()
    call void [mscorlib]System.Console::WriteLine(string)

    leave.s _CONTINUE
}

The try block will result in an ArgumentNullException exception because a null reference is pushed onto the stack as an argument to the Int32::Parse method.

The filter is then given a chance to determine whether it wants to handle the exception. In this case it simply writes the error message to the console and then pushes the value 1 onto the stack, using the ldc.i4.1 instruction, to indicate that it wants to handle the exception. The endfilter instruction is called to return from the filter clause.

The filter handler block is then called to handle the exception. Notice that both the evaluation and handler blocks can access the exception in flight by popping it off of the stack. Keep in mind that you can throw any reference type so don’t assume that the object reference you pop off of the stack in the filter handler is an instance of System.Exception. This is not a problem for the catch handler since you will know the type of the exception object.

Finally

The finally exception handler should be recognized by C# programmers and C++ programmers familiar with Structured Exception Handling (SEH). A finally block associated with a try block is always executed, regardless of whether control leaves the protected try block through normal means, using the leave.s instruction, or as a result of an exception, using the throw instruction. It provides a reliable mechanism of ensuring a certain block of code is always run for those languages that do not provide destructors, such as C and C#. Although the C programming language does not target the CLI, SEH is used often enough that the Microsoft C/C++ compiler provides the __finally keyword for the same reason.

.try
{
    /* protected code */
   
    leave.s _CONTINUE
}
finally
{
    /* cleanup code */

    endfinally
}

Fault

The fault exception handler is similar to the finally block except that it is invoked only if it’s associated try block is left as a result of an exception. After the fault handler has been given an opportunity to execute, the exception continues on its way in search or a handler that is willing to catch it.

.try
{
    /* protected code */
   
    leave.s _CONTINUE
}
fault
{
    /* cleanup code */

    endfault
}

With that brief introduction to exception handling out of the way, it is time to change gears a bit and talk about how this all relates to popular programming languages like C# and C++/CLI.

Read part 6 now: Common Language Constructs


© 2004 Kenny Kerr

3 Comments

  • Good stuff...



    1. It will be interesting to also include VB in future posts (not just C# and C++) since VB is the only language supporting filtered exceptions. With that rationale, I would be interested in any language that directly supports fault blocks (do you know of any?)



    2. If in C# you declare just a catch with no specific exception, you get in IL the following:

    catch [mscorlib]System.Object

    Any idea why and what the implications are? I would have expected:

    catch [mscorlib]System.Exception

  • What, I need to install VB now!



    :)



    I will be using C# to illustrate how basic constructs like expressions, for loops and if statements map to the CLI. I will then describe how language-specific features are implemented. Things like C# using statements, C++ destructors, VB catch-when clauses, etc. So stay tuned.



    I’m not aware of any language (other than MSIL) that exposes fault handler directly, although certain C++/CLI constructs result in CLI fault handlers being used under the covers. More on that later.



    Although C# only allows you to throw objects of type Exception and types deriving from it, other languages don’t have any such restriction. So C# allows you to handle such cases with what is known as a general catch clause. Keep in mind that the usefulness of this feature is questionable in most cases since it is impossible in C# to access the exception object and it is generally a very bad idea to catch all exceptions blindly. I would not even recommend a catch handler for the Exception type. Try to be more specific. On the other hand, I imagine there are CLR-based environments where this degree of exception handling is required, for example ASP.NET and SQL Server where it is not acceptable for exceptions to propagate up no matter what.



    Originally the designers of the runtime considered that the CLI would be used with different base class libraries (BCL) so they didn’t want to bind the runtime to System.Exception. That is why the CLI supports throwing exceptions of type System.Object. In hindsight this has proven to be a questionable saving since the value of the CLI is closely related to the BCL.

  • Nice explanation of the 'catch object' stuff. Now I have to go find a language that allows throwing non-Exception-derived objects :)



    Agreed about catching specific exceptions... There are scenarios where code would catch blindly though, such as trying to simulate fault handler (catch, do your fault handling stuff, rethrow). We use it simply for logging the exception details and rethrowing in our dll code that is used by 3rd party apps...



    Staying tuned :)

Comments have been disabled for this content.