MSIL: try..filter..fault
My colleague, Mike, recently pointed out to me that there
is certain structured exception handling (SEH) clauses available in Microsoft's Intermediate Language (MSIL)
that is not neccessarily available in all the CLI languages.
As an example, one of theses clauses, the filter clause, is available in
Visual Basic.NET (When) and C++/CLI but not available in the C# language.
Basically what a filter allows you is to execute some code and based on the
outcome of that code decide whether to engage the exception handler clause.
0x0001 A dedicated block of the IL code,
called filter, will process the exception and define whether the handler
should be engaged. The offset of the filter block is specified in the
FilterOffset entry. Since we cannot specify the filter block length—the EH
clause structure contains no entry for it—a positioning limitation is
associated with the filter block: the respective handler block must
immediately follow the filter block, allowing the length of the filter block
to be inferred from the offset of the handler. The filter block must end with
the endfilter instruction, described in Chapter 10, "IL Instructions." At the
moment endfilter is invoked, the evaluation stack must hold a single int32
value, equal to 1 if the handler is to be engaged and equal to 0 otherwise.
This EH clause type is called a filter type. Branching into or out of the
filter block is illegal. [From Inside Microsoft.NET IL Assembler by Serge Liden (No
Blog)]
I've been looking for a scenario where this might come in useful and after
reading Abhinaba Basu's blog on C#:try and retry I found a possible scenario where a filter
block might be useful.
In the sample below the very secure
password checking code is throwing an exception when the password
entered doesn't match the hard-coded password "secret". The execution
engine of the runtime then determines whether the exception happened in
a guarded clause, which indeed it has. It then checks whether the
handler specified for that clause will process the exception* by
putting the exception object on the stack and invokes the filter block.
If the exception is within the allowed three attempts the exception is
handled and execution is returned to the top. *(This can also be
decided based on type but this example is using the user defined filter)
Come the third failed attempt the filter clause puts a 0 back on the
evaluation stack and the exception handler clause is skipped and the
exception is passed to the next available handler in the call stack.
There is none in this case so the exception is unhandled and the
application exits. [The sample code can be downloaded from here and compiled using the ILASM tool that ships with the .NET SDK. Compiled executable here.]
[Note: A very cool IDE add-in for Visual Studio 2005 is Visual IL by available from GotDotNet by Craig Skibo].
.assembly extern mscorlib { }
.assembly FilterRetry { }
.module FilterRetry.exe
.namespace Acme {
.class public auto ansi PasswordChecker extends [mscorlib]System.Object {
.method public static void check( ) cil managed
{
.locals init (int32 counter, string secret)
.entrypoint
ldstr "secret"
stloc.1
AskForPassword:
ldstr "Enter your password:"
call void [mscorlib]System.Console::WriteLine(string)
.try // Try in scope form
{ // Guarded code
call string [mscorlib]System.Console::ReadLine()
ldloc.1
call bool [mscorlib]System.String::op_Equality(string,string)
brfalse.s Incorrect
leave.s AllowAccess
Incorrect:
// increment counter
ldloc.0
ldc.i4.1
add
stloc.0
// throw exception
ldstr "You have been kicked out!"
newobj instance void [mscorlib]System.Exception::.ctor(string)
throw
}
filter
{ // Here we decided whether we should invoke the handler
pop
ldc.i4.3
ldloc.0
cgt
endfilter
}
{ // Actual handler code starts here
ldstr "You have been granted another pop by the filter!"
call void [mscorlib]System.Console::WriteLine(string)
leave.s AskForPassword
}
AllowAccess:
ldstr "Welcome to the AllowAccess block"
call void [mscorlib]System.Console::WriteLine(string)
ret
} // end of method
} // end of class
} // end of namespace
In Visual Basic.NET something similar
can be achieved by doing something like the following (Just an arbitary
sample, not related to the one above]:
Sub Exception1()
Try
Console.WriteLine("Exception1")
Dim i As Integer = 10 / z
Catch e As OverflowException When DoIFeelLikeIt(e)
Console.WriteLine("Caught DivideByZero")
Catch e As Exception
Console.WriteLine("Some exception")
End Try
End Sub
Function DoIFeelLikeIt(ByRef e As Exception) As Boolean
Console.WriteLine("Filtering {0}", e.Message)
If DateTime.Now.Hour > 8 And DateTime.Now.Hour < 19 Then
Return True
End If
Return False
End Function
Another example of a SEH clause not
available in the C# language is the fault clause. It is similar to the
finally clause except that it's only invoked when an exception has been
thrown in the guarded block.
0x0004 The handler will be engaged if any exception occurs. This type of EH clause is called a fault type. A fault handler is similar to a finally handler in all aspects except one: the fault handler is not engaged if no exception has been thrown within the guarded block and everything is nice and quiet. The fault handler must also end with the endfinally instruction, which for this specific purpose has been given the synonym endfault.
Another interesting bit of
information I found out about the way managed exceptions is handled is
that the finally clause is always invoked before the first found
exception handler clause is invoked. Which makes sense if you think
about it afterwards. I highly recommend reading the SEH chapter in
Inside Microsoft IL Assembler to better understand exception handling
in the runtime. [Sample chapter here which happens to be the chapter on SEH :)]
[Update. I see the code formatting didn't come out as planned on this theme. ]
powered by IMHO 1.3