Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wrong line number on stack trace

I have this code

try
{
  //AN EXCEPTION IS GENERATED HERE!!!
}
catch  
{
   SqlService.RollbackTransaction();
   throw;
}

Code above is called in this code

try
{
  //HERE IS CALLED THE METHOD THAT CONTAINS THE CODE ABOVE
}
catch (Exception ex)
{
   HandleException(ex);
}

The exception passed as parameter to the method "HandleException" contains the line number of the "throw" line in the stack trace instead of the real line where the exception was generated. Anyone knows why this could be happening?

EDIT1 Ok, thanks to all for your answers. I changed the inner catch for


catch(Exception ex)
{
    SqlService.RollbackTransaction();
    throw new Exception("Enrollment error", ex);
}

Now I have the correct line on the stack trace, but I had to create a new exception. I was hoping to find a better solution :-(

EDIT2 Maybe (if you have 5 minutes) you could try this scenario in order to check if you get the same result, not very complicated to recreate.

like image 374
Claudio Redi Avatar asked Mar 22 '10 16:03

Claudio Redi


People also ask

What causes stack trace?

Usually, a stack trace is shown when an Exception is not handled correctly in code. (An exception is what a runtime environment uses to tell you that there's an error in your code.) This may be one of the built-in Exception types, or a custom Exception created by a program or a library.

What is error stack?

Stack trace error is a generic term frequently associated with long error messages. The stack trace information identifies where in the program the error occurs and is helpful to programmers. For users, the long stack track information may not be very useful for troubleshooting web errors.

Should stack traces be logged?

Therefore, you should log a stacktrace if, and only if, and always if, the exception indicates a bug in the program. However, that does not always indicate that a method you write should catch and log the exception.


Video Answer


5 Answers

Yes, this is a limitation in the exception handling logic. If a method contains more than one throw statement that throws an exception then you'll get the line number of the last one that threw. This example code reproduces this behavior:

using System;

class Program {
    static void Main(string[] args) {
        try {
            Test();
        }
        catch (Exception ex) {
            Console.WriteLine(ex.ToString());
        }
        Console.ReadLine();
    }
    static void Test() {
        try {
            throw new Exception();  // Line 15
        }
        catch {
            throw;                  // Line 18
        }
    }
}

Output:

System.Exception: Exception of type 'System.Exception' was thrown.
   at Program.Test() in ConsoleApplication1\Program.cs:line 18
   at Program.Main(String[] args) in ConsoleApplication1\Program.cs:line 6

The work-around is simple, just use a helper method to run the code that might throw an exception.

Like this:

static void Test() {
    try {
        Test2();                // Line 15
    }
    catch {
        throw;                  // Line 18
    }
}
static void Test2() {
    throw new Exception();      // Line 22
}

The underlying reason for this awkward behavior is that .NET exception handling is built on top of the operating system support for exceptions. Called SEH, Structured Exception Handling in Windows. Which is stack-frame based, there can only be one active exception per stack frame. A .NET method has one stack frame, regardless of the number of scope blocks inside the method. By using the helper method, you automatically get another stack frame that can track its own exception. The jitter also automatically suppresses the inlining optimization when a method contains a throw statement so there is no need to explicitly use the [MethodImpl] attribute.

like image 142
Hans Passant Avatar answered Oct 02 '22 19:10

Hans Passant


I often get this in production systems if Optimize code is checked. This screws up line numbers even in 2016.

Make sure your configuration is set to 'Release' or whatever configuration you are building and deploying under. The checkbox has a different value per configuration

I never ultimately know how more 'optimized' my code is with this checked - so check it back if you need to - but it has saved my stack trace on many occasions.

enter image description here

like image 41
Simon_Weaver Avatar answered Oct 02 '22 19:10

Simon_Weaver


As of .NET Framework 4.5 you can use the ExceptionDispatchInfo class to do this without the need for another method. For example, borrowing the code from Hans' excellent answer, when you just use throw, like this:

using System;

class Program {
    static void Main(string[] args) {
        try {
            Test();
        }
        catch (Exception ex) {
            Console.WriteLine(ex.ToString());
        }
        Console.ReadLine();
    }
    static void Test() {
        try {
            throw new ArgumentException();  // Line 15
        }
        catch {
            throw;                          // Line 18
        }
    }
}

It outputs this:

System.ArgumentException: Value does not fall within the expected range.
   at Program.Test() in Program.cs:line 18
   at Program.Main(String[] args) in Program.cs:line 6

But, you can use ExceptionDispatchInfo to capture and re-throw the exception, like this:

using System;

class Program {
    static void Main(string[] args) {
        try {
            Test();
        }
        catch (Exception ex) {
            Console.WriteLine(ex.ToString());
        }
        Console.ReadLine();
    }
    static void Test() {
        try {
            throw new ArgumentException();              // Line 15
        }
        catch(Exception ex) {
            ExceptionDispatchInfo.Capture(ex).Throw();  // Line 18
        }
    }
}

Then it will output this:

System.ArgumentException: Value does not fall within the expected range.
   at Program.Test() in Program.cs:line 15
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Program.Test() in Program.cs:line 18
   at Program.Main(String[] args) in Program.cs:line 6

As you can see, ExceptionDispatchInfo.Throw appends additional information to the stack trace of the original exception, adding the fact that it was re-thrown, but it retains the original line number and exception type. See the MSDN documentation for more information.

like image 40
Steven Doggart Avatar answered Oct 02 '22 19:10

Steven Doggart


Does the date/time stamp of your .pdb file match your .exe/.dll file? If not, it could be that the compilation is not in "debug mode" which generates a fresh .pdb file on each build. The pdb file has the accurate line numbers when exceptions occur.

Look into your compile settings to make sure the debug data is generated, or if you're in a test/production environment, check the .pdb file to make sure the timestamps match.

like image 21
Dillie-O Avatar answered Oct 02 '22 17:10

Dillie-O


C# stack traces are generated at throw time, not at exception creation time.

This is different from Java, where the stack traces are filled at exception creation time.

This is apparently by design.

like image 38
Randolpho Avatar answered Oct 02 '22 18:10

Randolpho