Export (0) Print
Expand All

Exception Handling in J#

Visual Studio .NET 2003
 

Pratap Lakshman
Microsoft Corporation

January 15, 2004

Applies to:
   Microsoft® Visual J#®
   Microsoft Visual J# .NET

Summary: Exception handling (EH) in a language used to be considered the domain of that language's runtime. Today, however, EH is a fundamental feature of the common language runtime (CLR). The CLR provides language-neutral support for EH, allowing exceptions to be raised in one language and caught in another. Microsoft Visual J# integrates the Java-language EH model onto the CLR. This means that J# programmers who are used to the Java language can continue to work with a familiar EH model while reaping the benefits of running on the CLR. (9 printed pages)

Contents

Introduction
The EH Model
Implementation Support for EH
EH in J#
The Cost of EH Processing
Conclusion
References

Introduction

Computer hardware is designed to detect certain exceptional conditions, and transfer control to a specified location when such a condition is detected. A typical cause of "hardware trap" conditions is trying to read/write a misaligned datum in memory or a memory bounds violation. Traps simplify code because one need not include checks for such exceptional conditions in the program itself. Traps provide a convenient mechanism for reporting infrequent events to programs that would otherwise need to make frequent checks. Traps also decrease the probability of such exceptional conditions going undetected.

The Exception Handling (EH) mechanism in modern object-oriented languages can be considered an analogy of this "hardware trap" model.

EH provides a way of transferring control and information from a point in the execution of a program to an "exception handler" associated with a point previously passed by the execution. The transfer of control in the EH model is not simply meant as an alternative "return" mechanism, but specifically as a mechanism for supporting the construction of fault-tolerant systems. Having said that, it is not intended to turn every method into a fault-tolerant entity, but rather is a mechanism by which a subsystem can be given a large measure of fault-tolerance even if its individual methods are written without regard for overall error-handling strategies.

The EH Model

All exception handling models address the following issues:

  • Exception representation. An exception may, or may not, be explicitly represented in a language. In the Java language exceptions are class types. The exception types are organized hierarchically with java.lang.Throwable at the root [1].
  • The domain of an exception handler. Associated with each handler is a domain that specifies the region of computation during which, if an exception occurs, the handler will be activated. Within a program, there may be several handlers for a particular exception. The accuracy with which a domain can be specified determines how precisely the source of the exception can be located. In the Java language the domain is explicitly indicated using the try block [2].
    try {
      // statements which may throw exceptions
    } catch (Exception e) {
      // Exception handler for e
    } finally {
      // code executed under all circumstances.
      // typically for cleaning-up purposes
    }
    
    

    Linguistic support for EH helps achieve lexical separation of normal use, detection, and the correction procedures. Exception handlers are separated, textually from the place at which the exception is raised so the normal behavior of the program is not obscured. This, in turn, leads to code that is easier to understand and maintain.

  • Exception propagation. When an exception is raised and there is no exception handler in the enclosing domain, either the exception can be propagated to the next outer level enclosing domain, or it can be considered a stop condition.

    The Java language supports the notion of "checked" and "runtime" exceptions [2]. The set of possible checked exceptions that may be thrown by a method must be listed as part of the method's declaration. Violation of this exception specification results in a compile-time error.

    When an exception is thrown, control is transferred to the nearest handler with an appropriate type—"nearest" means the handler whose try-block was most recently entered by the thread of control and not yet exited; "appropriate type" means an exact match for the type of the exception thrown, or matching a type higher in the exception hierarchy.

  • Resumption or termination model. This determines the action to be taken after an exception has been handled. Should it be possible for execution to resume at the point the exception was thrown? This is typically called "resumption" semantics. The idea of resumption is that after an exception is thrown the handler may alter the state or supply more information and then resume either at the point where the exception was thrown (resume) or at the beginning of the try block (retry). Languages like Eiffel [3] and Microsoft® Visual Basic® .NET support resumption semantics.

    When an exception is thrown in "termination" semantics, control never returns to the throw point or to the beginning of the try block. Once an exception is thrown, the block in which the exception occurred expires. The exception handler cannot return control to the source of the error by using the return statement; a return issued in this context returns from the function containing the catch block.

    The Java language and Managed C++ support termination semantics.

Implementation Support for EH

The primary implementation task in supporting EH is to discover the appropriate catch clause to handle the thrown exception. This requires an implementation to somehow keep track of the active area of each method on the program stack. Also the implementation must provide some method of querying the exception object as to its actual type. Finally there needs to be some mechanism for management of the lifetime of the object thrown—its creation, storage and destruction. In general the EH mechanism requires a tightly coupled handshake between compiler generated data structures and the runtime. The implementation tradeoff is between program size versus the runtime speed of the program when no exception is thrown.

The common language runtime (CLR) provides support for many different structured exception handling models. It supports the implementation of EH in most languages. The support comprises two parts:

  • IL instructions [4] for the operations of throwing and catching exceptions
  • A base type System.Exception (part of the CTS) from which all exception classes should directly or indirectly derive

The basis of EH in the CLR is IL in the trycatchfinally … structure with semantics similar to C# and the Java language. Instructions within a try block are said to be in a "protected" block. If an exception is raised in this code, or an unhandled exception is raised in code called by the code in this protected block, the execution of the protected block is stopped and the execution engine (EE) looks for a handler.

The EE checks the scope of declared handlers within the current method to see if the protected block to which they apply covers the instruction that raised the exception. If such a handler is found, then it is selected; if not, then the search continues in the calling method.

Handlers may be filtered or unfiltered. An unfiltered handler accepts any exception that is raised, and control enters the code of the handler with the exception object being the sole object on the evaluation stack.

There are two kinds of filtered handlers [5]:

  • One kind is declared to accept a particular class of an exception object—a typed filter. When such a handler is selected, the EE performs a type check on the current exception to see if it is the nominated type or a subtype of that type. In either case, the code of the handler is entered with the exception object on the evaluation stack.
  • A program can declare explicit tests on the exception block with code in a filter block. The code in this block finishes by pushing a boolean on the stack to indicate whether the handler block should be entered. If a filtered handler declines to handle an exception, the search for a handler resumes as before.

In Microsoft Visual J#®, both forms of the filtered handler are used (depending on the kind of exception that is being handled).

EH in J#

J# programmers have to deal with two exception hierarchies [6]:

  • Java-language exceptions, which are derived from java.lang.Throwable (including checked and runtime exceptions)
  • NET Framework exceptions, which are derived from System.Exception

Many of the Java-language runtime exceptions have semantic equivalents in the .NET Framework [7]; for example, java.lang.NullPointerException is similar to System.NullReferenceException. When you catch a Java-language runtime exception in your source code, the compiler emits code to catch its .NET Framework equivalent, as well.

Consider the following code:

public class mapped
{
  public static void main(String [] args)
  {
    try {
      someMethod();
    } catch (java.lang.NullPointerException e) {
      // NullPointerException is a mapped exception.
      // It is mapped to System.NullReferenceException.
      // Compiler emits code to catch both
    } finally {
      // cleanup code
    }
  }

  public static void someMethod() {}
}

In the above example, the exception could have originated in two ways:

  • It was thrown explicitly in someMethod()
  • It arose as a run time exception

The catch (java.lang.NullPointerException e) clause in source code will catch both NullPointerException and NullReferenceException—the J# compiler arranges for this to happen.

This means you can concentrate on exceptions specific to the Java language and ignore their equivalent .NET Framework exceptions.

The compiler emits a filter block for the above code. Here is the relevant IL (key statements highlighted with bold text) generated using v1.1 of the J# compiler:

.method public hidebysig static void  main(string[] args) cil managed
{
  .entrypoint

  // >>>> skipping some instructions <<<<
  .locals init ([0] class [vjslib]java.lang.NullPointerException t,
           [1] class [vjslib]java.lang.Throwable V_1)

  .try
  {    IL_000a:  call       void mapped::someMethod()
    IL_000f:  leave.s    IL_002b
    IL_0011:  call       class [vjslib]java.lang.Throwable
[vjslib]java.lang.NullPointerException::'<exceptFilter>'(object)    IL_0016:  stloc.1
    IL_0017:  ldloc.1
    IL_0018:  brfalse.s  IL_001d
    IL_001a:  ldc.i4.1
    IL_001b:  br.s       IL_001e
    IL_001d:  ldc.i4.0
    IL_001e:  endfilter
    IL_0020:  pop
    IL_0021:  ldloc.1
    IL_0022:  castclass  [vjslib]java.lang.NullPointerException
    IL_0027:  stloc.0
    IL_0028:  leave.s    IL_002b
  }  // end .try
  finally
  {    IL_002a:  endfinally
    .try IL_000a to IL_0011 filter IL_0011 handler IL_0020 to IL_002a
  }  // end handler  IL_002b:  call       void [vjslib]com.ms.vjsharp.util.Utilities::cleanupAfterMainReturns()
  IL_0030:  ret
} // end of method mapped::main

We begin by creating two local variables to hold the Java-language exceptions: one specific to the type we want to handle, and another to hold the return value from the filter block.

At offset IL_000a we make a call to mapped::someMethod(). If the function returns normally we "leave" through offset IL_002b.

If an exception were thrown within the call to someMethod, the filter block <exceptFilter> is entered. The <exceptFilter> method is passed the thrown exception. It will test if the exception is either the type we are specifically looking for, or a mapped .NET Framework exception. If a mapping exists, <exceptFilter> returns an object of type java.lang.Exception; otherwise, it returns null. This returned value is stored in the local variable V_1. If <exceptFilter> returns null, the filter block returns 'false' to the EE (denoting that the catch handler does not handle the exception thrown, and that the search for a handler should continue). However, if the filter block returns a non-null value, the filter block returns 'true' to the EE (denoting that the catch handler will handle the thrown exception), which, in turn, enters the catch handler.

On entering the catch handler, the stack is pop'd (to get rid of the exception actually thrown). The local variable (in this case V_1) is loaded and cast to the desired exception type (java.lang.NullPointerException, in this case) and further processing within the handler continues.

For Java-language exceptions that do not have a mapping to .NET Framework exceptions, the compiler emits IL in the form of the trycatchfinally … structure.

Consider the following code:

public class unMapped
{
  public static void main(String [] args)
  {
    try {
      someMethod();
    } catch (java.io.FileNotFoundException t) {
      // FileNotFoundException is an unmapped exception
    } finally {
      // cleanup code
    }
  }

  public static void someMethod() throws java.io.FileNotFoundException
  {
    // do nothing
  }
}

Here is the relevant IL (key statements highlighted with bold text) generated using v1.1 of the J# compiler:

.method public hidebysig static void main(string[] args) cil managed
{
  .entrypoint

  // >>>> skipping some instructions <<<<

  .try
  {
    .try    {
      IL_000a:  call       void unMapped::someMethod()
      IL_000f:  leave.s    IL_0017
    }  // end .try
    catch [vjslib]java.io.FileNotFoundException 
    {      IL_0011:  dup
      IL_0012:  stloc.1
      IL_0013:  stloc.0
      IL_0014:  leave.s    IL_0017
    }  // end handler
  }  // end .try
  finally
  {    IL_0016:  endfinally
  }  // end handler  IL_0017:  call       void  [vjslib]com.ms.vjsharp.util.Utilities::cleanupAfterMainReturns()
  IL_001c:  ret
} // end of method unMapped::main

The finally handler cannot coexist with other handlers. If a guarded block has a finally handler, it cannot have anything else. To combine a finally handler with other handlers, we need to nest the guarded and handler blocks within other guarded blocks, so that each finally handler has its own personal guarded block. In the present case, the code has a finally handler and the nesting can be seen in the generated IL.

The Cost of EH Processing

The process of handling an exception is done using a two-pass runtime stack unwinding model.

Recall that if an exception is raised in code inside a protected block, or an unhandled exception is raised in code called by the code in this protected block, the execution of the protected block is stopped and the execution engine (EE) looks for a handler.

Exception information is specified to the CLR in the form of an exception clause table (EH table) that accompanies the IL for the method. Each exception clause takes the following form:

Discriminator
Try block offset
Try block length
Handler block offset
Handler block lenqth
Class token or Filter block offset

The discriminator indicates if the handler is an unfiltered or filtered exception handler, or a termination handler (finally handler). Each handler guards a specific code range, as given by the try block offset and length. All offsets and lengths are with respect to the IL instruction stream for the method.

Starting at the top of the EH table for the current method frame, if the EE finds that the exception happened in a guarded block, it checks to see whether the handler specified in this clause will process the exception. If this particular handler can't be engaged then the EE continues traversing the EH table in search of other clauses that have guarded blocks covering the exception locus.

If none of the clauses in the EH table for the current method are suited to handle the exception, the EE steps up the call stack and starts checking the exception against EH tables of the method that called the method where the exception occurred. This process continues from method frame to method frame up the call stack until the EE finds a handler to be engaged or until it exhausts the call stack. The latter case is the end of the story: the EE cannot continue and either aborts the application execution or offers the user a choice between aborting the execution and invoking the debugger, depending on the runtime configuration.

This is the first pass. The stack unwinding during this pass is non-destructive. It is mainly to locate a suitable handler; the finally handlers are ignored during the first pass.

If a suitable handler is found, the EE swings into the second pass. The EE again walks the EH tables it worked with during the first pass and invokes all relevant finally handlers. Once the execution engine reaches the typed handler or filter block handler it found on its first pass, it engages the actual handler.

Conclusion

Handling exceptional conditions is one of the most delicate software construction problems—particularly in object-oriented programming, which relies heavily on the notion of reusable software components.

Until now, EH in a language was considered the domain of that language's runtime—each language had its own way of detecting exceptional conditions and locating a relevant handler—but not anymore. EH is now a fundamental feature of the CLR. The CLR provides language-neutral support for EH, allowing exceptions to be raised in one language and caught in another.

J# integrates the Java-language EH model onto the CLR. This means that J# programmers who are used to the Java language can continue to work with a familiar EH model while reaping the benefits of running on the CLR.

References

[1] Chan, P., Lee, R., Kramer, D., The Java Class Libraries 2e, Volume 1, Addison Wesley, 1998.

[2] Arnold, K., Gosling, J., The Java Programming Language 2e, Addison Wesley, 1998.

[3] Bertrand Meyer, From Structured Programming to Object-Oriented Design: The Road to Eiffel, in Structured Programming, vol. 10, no. 1, January 1989 (available online at http://www.inf.ethz.ch/~meyer/publications/structured/road.pdf)

[4] Common Language Infrastructure (CLI) Partition III: CIL, 2002

[5] Common Language Infrastructure (CLI) Partition II: Metadata Definition and Semantics, 2002

[6] Visual J# Exception Hierarchies, Visual J# .NET product documentation

[7] Visual J# Exceptions with .NET Framework Equivalents, Visual J# .NET product documentation

Show:
© 2014 Microsoft