Markus Markus - 1 month ago 18
Vb.net Question

Weird debugger behavior in VB.net

A colleague found a wired debugger behavior in in his VB.net solution. I confess this will be more an academic question as this only effects the sequence of highlighted statements while debugging and not the overall behavior of the code. So for all curious out there:

We stripped down this to the following minimum console application:

Private Sub PlayWithExceptions
Dim a = 2
Try
throw new Exception("1")
Catch ex As Exception
If a = 2 Then
Dim x = New XElement("Dummy")
Else
throw
End If
End Try
End Sub

Sub Main()
Try
PlayWithExceptions()
Catch ex As Exception
End Try
End Sub


As obvious the debugger throws Exception(“1”) and the debugger jumps into the catch clause of PlayWithExceptions method. There, as “a” is always 2, the debugger jumps to some dummy code (New XElement…), from there to the “End If” and finally back into the Else-leaf onto the throw statement. I confess that Visual Studio does not re-throw the exception, but nevertheless it looks very strange.

Changing the condition “If a = 2” into “If True” eliminates this behavior.

Refactoring to conditional catches eliminates this behavior too.

Private Sub PlayWithExceptions
Dim a = 2
Try
throw new Exception("1")
Catch ex As Exception When a = 2
Dim x = New XElement("Dummy")
Catch ex As Exception
throw
End Try
End sub


Translating these few lines into C# does not show this behavior as well.

private static void PlayWithExceptions()
{
var a = 2;
try
{
throw new Exception("1");
}
catch (Exception)
{
if (a == 2)
{
var x = new XElement("Dummy");
}
else
{
throw;
}
}
}

static void Main(string[] args)
{
try
{
PlayWithExceptions();
}
catch (Exception ex)
{
}
}


We tried .Net3.5 and .Net4.6 as well as the targets AnyCPU and x86 without any effect to the above VB-code. The code was executed with the default Debug settings and no further optimizations. We used VS2015 Update 3.

Has anyone an idea why Visual Studio pretends to re-throw the exception in VB (but without really re-throwing it)? It looks confusing while debugging…

Answer

It's related to hidden code which sets/unsets error information for VB.Net's Err object - which doesn't have a real "location" in the source.

In the IL, the code to clear the error is located immediately after the rethrow call, and so that's the closest source line it can show when its about to invoke it. What I cannot answer is why it stops before invoking it when it should just be stepping between (visible) source lines.

But if you inspect the Err object when the debugger is on the Throw line, you'll see that it has a current exception object. Whereas on the step after that, the current exception has been cleared. See IL_0035 below for where the debugger is pausing:

.method private static void  PlayWithExceptions() cil managed
{
  // Code size       62 (0x3e)
  .maxstack  2
  .locals init ([0] int32 a,
           [1] class [mscorlib]System.Exception ex,
           [2] bool V_2,
           [3] class [System.Xml.Linq]System.Xml.Linq.XElement x)
  IL_0000:  nop
  IL_0001:  ldc.i4.2
  IL_0002:  stloc.0
  .try
  {
    IL_0003:  nop
    IL_0004:  ldstr      "1"
    IL_0009:  newobj     instance void [mscorlib]System.Exception::.ctor(string)
    IL_000e:  throw
  }  // end .try
  catch [mscorlib]System.Exception 
  {
    IL_000f:  dup
    IL_0010:  call       void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.ProjectData::SetProjectError(class [mscorlib]System.Exception)
    IL_0015:  stloc.1
    IL_0016:  nop
    IL_0017:  ldloc.0
    IL_0018:  ldc.i4.2
    IL_0019:  ceq
    IL_001b:  stloc.2
    IL_001c:  ldloc.2
    IL_001d:  brfalse.s  IL_0032
    IL_001f:  ldstr      "Dummy"
    IL_0024:  call       class [System.Xml.Linq]System.Xml.Linq.XName [System.Xml.Linq]System.Xml.Linq.XName::op_Implicit(string)
    IL_0029:  newobj     instance void [System.Xml.Linq]System.Xml.Linq.XElement::.ctor(class [System.Xml.Linq]System.Xml.Linq.XName)
    IL_002e:  stloc.3
    IL_002f:  nop
    IL_0030:  br.s       IL_0035
    IL_0032:  nop
    IL_0033:  rethrow
//Debugger is pausing at IL_0035 when the highlight is on Throw
    IL_0035:  call       void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.ProjectData::ClearProjectError()
    IL_003a:  leave.s    IL_003c
  }  // end handler
  IL_003c:  nop
  IL_003d:  ret
} // end of method Module1::PlayWithExceptions

For the If True variant, it doesn't even include the Throw code any more and so it obviously can never believe that it's about to execute it. For the variant with exception filters, each Catch clause independently manages its SetProjectError/ClearProjectError calls and so there's no confusion between the one called for Throw and the one called for New XElement.

Comments