Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wrong line number in stack trace for exception thrown inside switch statement

I have noticed a strange behavior with the line number in an exception's stack trace if the exception is thrown inside a switch statement.

Here is an example (the formatting matters of course because of line numbers):

using System;
class Program {
    static void Main(string[] args) {
        for (int i = 0; i < 3; i++) {
            try {
                ThrowSomeException(i);
            } catch (Exception exc) {
                Console.WriteLine(exc);
            }
        }
    }
    private static void ThrowSomeException(int arg) {
        Console.WriteLine("arg = {0}", arg);
        switch (arg) {
        case 0:
            throw new Exception("Line number = 16");
        case 1:
            throw new Exception("Line number = 18");
        default:
            throw new Exception("Line number = 20");
        }
    }
}

The line reported in the stack trace is the line of the next exception in the switch statement. The above program produces this result (note the mismatch between line number in the exception text and the line number in the stack trace):

arg = 0
System.Exception: Line number = 16
   at Program.ThrowSomeException(Int32 arg) in x:\test\Program.cs:line 18
   at Program.Main(String[] args) in x:\test\Program.cs:line 6
arg = 1
System.Exception: Line number = 18
   at Program.ThrowSomeException(Int32 arg) in x:\test\Program.cs:line 20
   at Program.Main(String[] args) in x:\test\Program.cs:line 6
arg = 2
System.Exception: Line number = 20
   at Program.ThrowSomeException(Int32 arg) in x:\test\Program.cs:line 20
   at Program.Main(String[] args) in x:\test\Program.cs:line 6

Why is this happening?

Note: I tried this both with VS 2012 and 2013, compiling both on .NET 3.5 and 4.5 and got the same results.

Debug vs Release: surprisingly enough, I'm getting this strange behavior in Debug only, in Release the line numbers are correct.

like image 875
Paolo Tedesco Avatar asked Jul 23 '14 13:07

Paolo Tedesco


1 Answers

I have tried decompiling the assembly, and it seems like the assembly does some optimization in either mode. In particular the second method gets changed:

private static void ThrowSomeException(int arg)
{
    Console.WriteLine("arg = {0}", arg);
    switch (arg)
    {
        case 0:
        {
            throw new Exception("Line number = 16");
        }
        case 1:
        {
            throw new Exception("Line number = 18");
        }
    }
    throw new Exception("Line number = 20");
}

This is what Telerik JustDecompile tells me the method looks like, both in Debug and Release mode. It is very possible that if you view the raw assembly, there will be a further explanation as to why this discrepancy exists.

I do not know how to continue this, but I think this is a very interesting problem. I'm going to mark my answer as Community Wiki, in the hopes that a collaborative effort can resolve this.


I have done some more testing. I've moved the ThrowSomeException() function to a separate class and made it non-static, and that didn't change anything. I then rewrote it slightly to first assign the exception to a variable and then throw it separately.

internal class Program
{
    private static void Main()
    {
        Test test = new Test();
        for (int i = 0; i < 3; i++)
        {
            try
            {
                test.ThrowSomeException(i);
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception);
            }
        }
    }


}

public class Test
{
    public void ThrowSomeException(int arg)
    {

        Console.WriteLine("arg = {0}", arg);
        switch (arg)
        {
            case 0:
                {
                    Exception ex = new Exception("Line number = 36");
                    throw ex;
                }
            case 1:
                {
                    Exception ex = new Exception("Line number = 41");
                    throw ex;
                }
            default:
                {
                    Exception ex = new Exception("Line number = 46");
                    throw ex;

                }
        }

    }
}

The above code has the following output in Debug mode:

arg = 0
System.Exception: Line number = 36
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 40
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 14
arg = 1
System.Exception: Line number = 41
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 45
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 14
arg = 2
System.Exception: Line number = 46
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 47
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 14

In Release mode, the second exception is thrown from line 46 instead of 45. This behavior is consistent for all versions of the .NET framework and all versions of VS. I'm next going to try doing this using a VB project to see if it makes a difference.

Edit: using the following VB project in VS 2012:

Module Program
Sub Main()
    Dim test As New Test()
    For i As Integer = 0 To 2
        Try
            test.ThrowSomeException(i)
        Catch exception As Exception
            Console.WriteLine(exception)
        End Try
    Next
End Sub
End Module

Public Class Test
Public Sub ThrowSomeException(arg As Integer)

    Console.WriteLine("arg = {0}", arg)
    Select Case arg
        Case 0
            If True Then
                Dim ex As New Exception("Line number = 22")
                Throw ex
            End If
        Case 1
            If True Then
                Dim ex As New Exception("Line number = 27")
                Throw ex
            End If
        Case Else
            If True Then
                Dim ex As New Exception("Line number = 32")
                Throw ex

            End If
    End Select

End Sub
End Class

The problem does not occur and the line numbers are consistent.

I have also tested the executables generated by the output directly, and found even weirder results. This is the output of the debug exe:

arg = 0
System.Exception: Line number = 36
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 40
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 14
arg = 1
System.Exception: Line number = 41
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 45
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 14
arg = 2
System.Exception: Line number = 46
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 47
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 14

And this is the output from the release mode exe:

arg = 0
System.Exception: Line number = 36
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 41
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 14
arg = 1
System.Exception: Line number = 41
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 42
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 14
arg = 2
System.Exception: Line number = 46
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 37
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 14

This is not the same result as using the debugger to run this.

Quick testing als seems to indicate that extra lines between the exception and throw statement (in this case a console.writeline() also affect the result:

rewritten switch to this:

switch (arg)
    {
        case 0:
            {
                Exception ex = new Exception("Line number = 37");
                Console.WriteLine("case 0");
                throw ex;
            }
        case 1:
            {
                Exception ex = new Exception("Line number = 43");
                Console.WriteLine("case 1");
                throw ex;
            }
        default:
            {
                Exception ex = new Exception("Line number = 49");
                Console.WriteLine("case default");
                throw ex;                    
            }
    }

gives this Release output from VS:

arg = 0
case 0
System.Exception: Line number = 37
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 43
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 15
arg = 1
case 1
System.Exception: Line number = 43
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 49
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 15
arg = 2
case default
System.Exception: Line number = 49
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 51
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 15

and this output from command line:

arg = 0
case 0
System.Exception: Line number = 37
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 43
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 15
arg = 1
case 1
System.Exception: Line number = 43
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 45
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 15
arg = 2
case default
System.Exception: Line number = 49
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 39
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 15

and this Debug output from VS:

arg = 0
case 0
System.Exception: Line number = 37
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 42
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 15
arg = 1
case 1
System.Exception: Line number = 43
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 48
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 15
arg = 2
case default
System.Exception: Line number = 49
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 51
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 15

And this output from the command line:

arg = 0
case 0
System.Exception: Line number = 37
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 42
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 15
arg = 1
case 1
System.Exception: Line number = 43
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 48
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 15
arg = 2
case default
System.Exception: Line number = 49
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 51
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 15
like image 165
3 revs Avatar answered Oct 09 '22 18:10

3 revs