Scope Form of SEH Clause Declaration
The label form of the EH clause declaration is universal, ubiquitous, and close to the actual representation of the EH clauses in the EH table. The only quality the label form lacks is convenience. In view of that, ILAsm offers an alternative form of EH clause description: a scope form. You’ve already encountered the scope form in Chapter 2, which discussed protecting the code against possible surprises in the unmanaged code being invoked. Just to remind you, here’s what the protected part of the method (from the sample file Simple2.il on the companion CD) looks like:
.try { // Guarded block begins call string [mscorlib]System.Console::ReadLine() // pop // ldnull ldstr "%d" ldsflda int32 Odd.or.Even::val call vararg int32 sscanf(string,string,...,int32*) stloc.0 leave.s DidntBlowUp // Guarded block ends } catch [mscorlib]System.Exception { // Exception handler begins pop ldstr "KABOOM!" call void [mscorlib]System.Console::WriteLine(string) leave.s Return } // Exception handler ends DidntBlowUp:
The scope form can be used only for a limited subset of all possible EH clause configurations: the handler blocks must immediately follow the previous handler block or the guarded block. If the EH clause configuration is different, we must resort to the label form or a mixed form:
br AfterHandler HandlerBegins: // The exception handler code leave KeepGoing AfterHandler: .try { // Guarded code leave KeepGoing } catch [mscorlib]System.Exception handler HandlerBegins to AfterHandler KeepGoing:
The IL Disassembler by default outputs the EH clauses in the scope form—at least those clauses that can be represented in this form. However, we have the option to suppress the scope form and output all EH clauses in their generic label form. But let’s suppose for the sake of convenience that we can shape the code in such a way that the contiguity condition is satisfied, allowing us to use the scope form.
A single guarded block with multiple handlers in scope form will look like this:
.try { // Guarded code leave KeepGoing } catch [mscorlib]System.StackOverflowException { // The exception handler #1 code leave KeepGoing } catch [mscorlib]System.Exception { // The exception handler #2 code leave KeepGoing } KeepGoing:
Much more readable, isn’t it? The nested EH configuration shown earlier in Figure 11-1 is easily understandable when written in scope form:
.try { .try { .try { // Guarded code leave L1 } catch A { // This code works when exception A is thrown leave L2 } } // No need for leave here! finally { // This code works in any case endfinally } } // No need for leave here either! catch B { // This code works when exception B is thrown in guarded code leave L3 }
The filter EH clauses in scope form are subject to the same limitation: the handler block must immediately follow the guarded block. But because in a filter clause the handler block includes first the filter block and then, immediately following it, the actual handler, the scope form of a filter clause looks like this:
.try { // Guarded code leave KeepGoing } filter { // Here we decide whether we should invoke the actual handler ldc.i4.1 // OK, let's invoke the handler endfilter} { // Actual handler code leave KeepGoing }
And, of course, we can easily switch between scope form and label form within a single EH clause declaration. The general ILAsm syntax for an EH clause declaration is as follows:
<EH_clause> ::= .try <guarded_block> <EH_type_specific> <handler_block>Where <guarded_block> ::= <label> to <label> <scope> <EH_type_specific> ::= catch <class_ref> filter <label> filter <scope> finally Fault <handler_block> ::= handler <label> to <label> <scope>
The nonterminals <label> and <class_ref> must be familiar by now, and the meaning of <scope> is obvious: “code enclosed in curly braces.”