Freigeben über


This... is... Dynamic!

Regarding our need to check-and-rethrow exceptions in IronPython, int19h asks "Shouldn't exception filters do the trick without the need to rethrow explicitly?"

That's a good question.  In fact, I had the exact same reaction when I first started looking into this issue.  So I changed IronPython's MSIL generation to use filters, and my updated version was able to pass all of our tests.

Later, during my weekly face-to-face meeting with Shri, I described the work I had done and Shri was immediately able to identify a corner case that was now broken.

Results 1 - 10 of about 163 for "vb.net" "jan brady"

Before looking at some sample Python code that causes the problem, it's worth digging a little into filters to learn something about their operation.  To do so, I'll need to leave my comfort zone and enter the world of VB.NET -- as C# doesn't have any functionality that maps onto MSIL exception filters.

Consider the following code:

     Function CheckException(ByVal pos As Integer) As Boolean
        System.Console.WriteLine("Filter {0}", pos)
        CheckException = (pos = 0)
    End Function
 
    Sub Test(ByVal i As Integer, ByVal max As Integer)
        Try
            If i < max Then
                Test(i + 1, max)
            Else
                Throw New ApplicationException("Exception Occurred")
            End If
        Catch ex As Exception When CheckException(i)
        Finally
            System.Console.WriteLine("Finally {0}", i)
        End Try
    End Sub
 
    Sub Main()
        Try
            Test(1, 4)
        Catch ex As Exception When CheckException(0)
        End Try
    End Sub

This code is designed so that the exception filter in the Test() method will always return false while the one in Main() will return true.  As a side effect of calculating this value, the filter will give us a visual notification that it has run.

The exception handlers form a stack that will be traversed in reverse calling sequence, running the Finally block of each Test() invocation before continuing back up the chain.  Our natural expectation is that the filter will be executed just before the Finally, giving us output that looks like this:

 Filter 4
Finally 4
Filter 3
Finally 3
Filter 2
Finally 2
Filter 1
Finally 1
Filter 0

But what we get when we run the code is output that actually looks like this:

 Filter 4
Filter 3
Filter 2
Filter 1
Filter 0
Finally 4
Finally 3
Finally 2
Finally 1

So what happens instead is that the CLR actually walks the list of exception handlers twice.  The first time, it's looking for a catch block that will handle the exception.  In the course of doing so, it needs to run any filter methods it encounters in order to identify whether or not that particular catch block is "the one".  Only once it finds an exception handler does it go back and run all of the finally and fault blocks between the thrower and the catcher.

You fascinate me; tell me more!

This subtlety matters to us because of the dynamic nature of the Python language.  You can't reliably evaluate any expression in the dynamic world until you reach that particular point in the code -- because the meaning of just about anything is subject to change.

Here's the Python code that demonstrates the problem:

 x = RuntimeError
def test():
    global x
    try:
        raise x
    finally:
        x = SyntaxError
 
try:
    test()
except x:
    print 'Match'
except:
    print 'Mismatch'

In order for this to work correctly, the exception criteria "except x" at the top level must run after x is reassigned in the finally block of the test() method.  But if we use an exception filter to match the thrown exception against x, the filter will run before the value of x is set to SyntaxError and it will incorrectly report that it is the right handler for the job.

And even though this is an edge case, there's nothing that actually distinguishes this code from the more usual example of "except RuntimeError" where we supply the type of the exception.  That's because the meaning of the symbol can't be determined until runtime no matter how familiar it may look to human eyes.  This is in sharp contrast to a statically typed language, where "catch (ArgumentException)" has to be resolvable to a definite type at build time or it's an error.

This is part of what it means to be dynamic. Take the power and use it wisely.

Comments

  • Anonymous
    July 31, 2008
    Personally, I am of the opinion that anyone using this edge case in production code deserves to be drawn and quartered publicly... but yeah, I can understand the need to stay true to the spec.
  • Anonymous
    July 31, 2008
    Even so, wouldn't it be possible to optimize the case where the value used in "except" cannot be changed later on (e.g., it's a name of a .NET class) to a filter block?
  • Anonymous
    August 01, 2008
    The comment has been removed