Codegen for On Error Resume Next

VB has a "On Error Resume Next", which tells each line to swallow exceptions and just keep executing to the next line. It's kind of like a try-catch around every single line.

It may be tempting for C++ developers to make fun of VB for this, but this is a lot like programming with HRESULT (or any error handling strategy based on return codes) when you forget to check the return value. And as we look at the codegen below, VB's code could turn out to be even more efficient than the unmanaged error handling equivalent.

 

So in this VB snippet, the exception is is swallowed and "Next" is printed.

 Module Module1

    Sub Main()
        On Error Resume Next
        Console.WriteLine("Hello!")
        Throw New Exception("Bong!")

        Console.WriteLine("Next")
    End Sub

End Module

 

If you're wondering what the codegen is like, you can always compile and then view the IL in ildasm. Or you can view it in Reflector as C#  instead of IL, which is easier to comprehend.

 

 [STAThread]
public static void Main()
{
    // This item is obfuscated and can not be translated.
    int VB$ResumeTarget;
    try
    {
        int VB$CurrentStatement;
    Label_0001:
        ProjectData.ClearProjectError();
        int VB$ActiveHandler = -2;
    Label_0009:
        VB$CurrentStatement = 2;
        Console.WriteLine("Hello!");
    Label_0016:
        VB$CurrentStatement = 3;
        throw new Exception("Bong!");
    Label_0023:
        VB$CurrentStatement = 4;
        Console.WriteLine("Next");
        goto Label_009E;
    Label_0035:
        VB$ResumeTarget = 0;
        switch ((VB$ResumeTarget + 1))
        {
            case 1:
                goto Label_0001;

            case 2:
                goto Label_0009;

            case 3:
                goto Label_0016;

            case 4:
                goto Label_0023;

            case 5:
                goto Label_009E;

            default:
                goto Label_0093;
        }
    Label_0059:
        VB$ResumeTarget = VB$CurrentStatement;
        switch (((VB$ActiveHandler > -2) ? VB$ActiveHandler : 1))
        {
            case 0:
                goto Label_0093;

            case 1:
                goto Label_0035;
        }
    }
    catch (object obj1) when (?)
    {
        ProjectData.SetProjectError((Exception) obj1);
        goto Label_0059;
    }
Label_0093:
    throw ProjectData.CreateProjectError(-2146828237);
Label_009E:
    if (VB$ResumeTarget != 0)
    {
        ProjectData.ClearProjectError();
    }
}

 

 

So you can see it's just putting the entire region in a try/catch block, and then using a switch table to jump back to the appropriate line.  It has a "Current Statement" variable to track the last successful line to execute before an exception may have been thrown, and then switches to the next line on the exception path.

The switch table may seem evil at first, but remember that in native code, all those IfFailGoto() checks to propagate return results also add up to a lot of switching code. In this case, the branches are at least optimized into a single switch table as opposed to scattered branch code.

Comments

  • Anonymous
    April 12, 2008
    "It may be tempting for C++ developers.." Erm, don't you mean: "It may be tempting for <em>Win32</em> developers.." Let's not assume every C++ programmer writes Win32 code. Contrary to popular belief there are other platforms and APIs used by these guys ;)

  • Anonymous
    April 12, 2008
    I didn't think my original statement made the assumption you mention here.   my qualifier "or any error handling strategy based on return codes" was an attempt to make it clear that I recognize there are other APIs then Win32.

  • Anonymous
    April 14, 2008
    By the way, this part of disassembled code demonstrates a bug in .NET Reflector: Label_0035:    VB$ResumeTarget = 0;    switch ((VB$ResumeTarget + 1))