Incorrect stacktrace by rethrow
Asked Answered
F

12

43

I rethrow an exception with "throw;", but the stacktrace is incorrect:

static void Main(string[] args) {
    try {
        try {
            throw new Exception("Test"); //Line 12
        }
        catch (Exception ex) {
            throw; //Line 15
        }
    }
    catch (Exception ex) {
        System.Diagnostics.Debug.Write(ex.ToString());
    }
    Console.ReadKey();
}

The right stacktrace should be:

System.Exception: Test
   at ConsoleApplication1.Program.Main(String[] args) in Program.cs:Line 12

But I get:

System.Exception: Test
   at ConsoleApplication1.Program.Main(String[] args) in Program.cs:Line 15

But line 15 is the position of the "throw;". I have tested this with .NET 3.5.

Fission answered 18/11, 2010 at 17:15 Comment(13)
I don't think that adding the exception as innerexception and throwing your own exception is uglier or longer. For me, it is best practice.Rugged
This is very interesting topic.Pothole
@Pabuc: Ok, you still have to choose the right exception to throw.Paleozoic
@Al Kepp: ok, then vote it up :DPaleozoic
VS 2010 gives a DivideByZeroException both ways (Console Application). Which version are u using?Ronnironnica
I couldnt believe it, i tried it, you are right. wtf. -_-. I am watching out for this gotcha!Kephart
@acid: That's the same I thought. I had this rule use throw) recorded on stone ... time to change.Paleozoic
The only thing the "exact duplicate" has going for it is the timestamp. This question is better worded, and it has nicer answers. The other question should be closed as an inferior predecessor.Orban
@Andomar: thanks. I think the same but as it's my question I didn't want to say anything.Paleozoic
possible duplicate of Why stacktrace is modified when you use throw in C#?Yarkand
Well, I think that this merge has messed up things but that's up to you. I've the info I was looking for anyway.Paleozoic
No one mentioned this but mono does this correctly (tested in linux however. I dont have a mono windows install)Kephart
To tackle this problem, if one cannot refactor this to a new function, instead of just a 'throw' I throw a newly created exception with the constructor that accepts an innerException which I fill with the original exception thrown. The stack trace will now show the correct line number on top. It is a work around, but it works for me.Kendra
S
27

Throwing twice in the same method is probably a special case - I've not been able to create a stack trace where different lines in the same method follow each other. As the word says, a "stack trace" shows you the stack frames that an exception traversed. And there is only one stack frame per method call!

If you throw from another method, throw; will not remove the entry for Foo(), as expected:

  static void Main(string[] args)
  {
     try
     {
        Rethrower();
     }
     catch (Exception ex)
     {
        Console.Write(ex.ToString());
     }
     Console.ReadKey();
  }

  static void Rethrower()
  {
     try
     {
        Foo();
     }
     catch (Exception ex)
     {
        throw;
     }

  }

  static void Foo()
  {
     throw new Exception("Test"); 
  }

If you modify Rethrower() and replace throw; by throw ex;, the Foo() entry in the stack trace disappears. Again, that's the expected behavior.

Slayton answered 18/11, 2010 at 17:29 Comment(5)
this is an explanation. To rewrite the functions is not every time an option. if you have a WCF-Function like "CreatePerson" and you call a function like "ValidParamLength(string s, uint minlength, uint maxlength)" multiple times and this function throws an exception i must build an internal function so the "CreatePerson"-Function calls the "_internalCreatePerson"-Function to reflect this. if i let this, i get the position of the throw and i downt no with field-validation are failed.Fission
an excampel: void CreatePerson(Person p) { try { ValiedParamLength(p.name, 1, 50); ValiedParamLength(p.vname, 1, 50); ValiedParamLength(p.street, 5, 50); ValiedParamLength(p.zip, 5, 5); ValiedParamLength(p.location, 5, 30); ValiedParamByDb(p.location, "tblLocations", "a_Location"); } catch (Exception ex) { System.Diagnostics.Debug.Write(ex.ToString()); throw; } }Fission
@Floyd: normally you would need different validation rules for each piece of data anyway: ValidateName(name); ValidateZip(zip); etc. Then call ValidParamLength(zip, 5, 5) inside ValidateZip.Slayton
There is an article that explains a way out of this special case: weblogs.asp.net/fmarguerie/archive/2008/01/02/…Headstall
@redtuna: interesting, but the author of the blog post fails to give a warning that calling internal members of framework classes might break for any .NET upgrade. It also won't work if your code runs without reflection permissions.Slayton
O
26

It's something that can be considered as expected. Modifying stack trace is usual case if you specify throw ex;, FxCop will than notify you that stack is modified. In case you make throw;, no warning is generated, but still, the trace will be modified. So unfortunately for now it's the best not to catch the ex or throw it as an inner one. I think it should be considered as a Windows impact or smth like that - edited. Jeff Richter describes this situation in more detail in his "CLR via C#":

The following code throws the same exception object that it caught and causes the CLR to reset its starting point for the exception:

private void SomeMethod() {
  try { ... }
  catch (Exception e) {
    ...
    throw e; // CLR thinks this is where exception originated.
    // FxCop reports this as an error
  }
}

In contrast, if you re-throw an exception object by using the throw keyword by itself, the CLR doesn’t reset the stack’s starting point. The following code re-throws the same exception object that it caught, causing the CLR to not reset its starting point for the exception:

private void SomeMethod() {
  try { ... }
  catch (Exception e) {
    ...
    throw; // This has no effect on where the CLR thinks the exception
    // originated. FxCop does NOT report this as an error
  }
}

In fact, the only difference between these two code fragments is what the CLR thinks is the original location where the exception was thrown. Unfortunately, when you throw or rethrow an exception, Windows does reset the stack’s starting point. So if the exception becomes unhandled, the stack location that gets reported to Windows Error Reporting is the location of the last throw or re-throw, even though the CLR knows the stack location where the original exception was thrown. This is unfortunate because it makes debugging applications that have failed in the field much more difficult. Some developers have found this so intolerable that they have chosen a different way to implement their code to ensure that the stack trace truly reflects the location where an exception was originally thrown:

private void SomeMethod() {
  Boolean trySucceeds = false;
  try {
    ...
    trySucceeds = true;
  }
  finally {
    if (!trySucceeds) { /* catch code goes in here */ }
  }
}
Ogawa answered 19/1, 2011 at 9:16 Comment(6)
Ok, I will take into consideration the method you mention. At least is a different approach. Thanks a lot.Paleozoic
VB.net allows one to act on an exception, and examine the exception object, before finally blocks run, and before having to decide whether to catch an exception. This can be particularly useful when debugging, since the state of objects that caused the exception may still exist when an 'unhandled exception' debugger trap fires. Too bad c# can't do that.Schrock
@supercat: are you talking about the when statement?Paleozoic
@SoMoS: Yep. One needs to use some caution in a "When" statement, since it's possible for a "When" flag to indicate an exception should be caught, but have the exception disappear before the Catch executes (e.g. if a nested Finally clause throws an exception, and that exception gets caught).Schrock
Yeah, I love the when from Vb.Net. Makes the catch handlers much shorter. This should be ported to C# IMHO.Paleozoic
You write in the code "This has no effect on where the CLR thinks the exception originated.". Actually, if the exception was thrown in method a() and you had more than one call to this method, you would know which line in a() threw the exception but you would "not" know which call to a() caused the problem. This is because the stack trace would point to the "throw;" line and not to the calling line. I simply don't use the "throw;" statement anymore and re throw a new exception with the cause specified.Marzi
C
20

This is a well known limitation in the Windows version of the CLR. It uses Windows' built-in support for exception handling (SEH). Problem is, it is stack frame based and a method has only one stack frame. You can easily solve the problem by moving the inner try/catch block into another helper method, thus creating another stack frame. Another consequence of this limitation is that the JIT compiler won't inline any method that contains a try statement.

Chadd answered 18/11, 2010 at 18:50 Comment(0)
H
10

How can I preserve the REAL stacktrace?

You throw a new exception, and include the original exception as the inner exception.

but that's Ugly... Longer... Makes you choice the rigth exception to throw....

You are wrong about the ugly but right about the other two points. The rule of thumb is: don't catch unless you are going to do something with it, like wrap it, modify it, swallow it, or log it. If you decide to catch and then throw again, make sure you are doing something with it, otherwise just let it bubble up.

You may also be tempted to put a catch simply so you can breakpoint within the catch, but the Visual Studio debugger has enough options to make that practice unnecessary, try using first chance exceptions or conditional breakpoints instead.

Houseraising answered 19/1, 2011 at 9:57 Comment(1)
Of course I'm only using the catch when it's needed. What worries me most is that this is a best practice at my work (to use throw) and will be hard to change it ... :\Paleozoic
L
7

Edit/Replace

The behavior is actually different, but subtilely so. As for why the behavior if different, I'll need to defer to a CLR expert.

EDIT: AlexD's answer seems to indicate that this is by design.

Throwing the exception in the same method that catches it confuses the situation a little, so let's throw an exception from another method:

class Program
{
    static void Main(string[] args)
    {
        try
        {
            Throw();
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    public static void Throw()
    {
        int a = 0;
        int b = 10 / a;
    }
}

If throw; is used, the callstack is (line numbers replaced with code):

at Throw():line (int b = 10 / a;)
at Main():line (throw;) // This has been modified

If throw ex; is used, the callstack is:

at Main():line (throw ex;)

If exception is not caught, the callstack is:

at Throw():line (int b = 10 / a;)
at Main():line (Throw())

Tested in .NET 4 / VS 2010

Librate answered 19/1, 2011 at 9:13 Comment(6)
Thats wrong, it does not have its original stracktrace when you use throw. You have the trow line as the point where the exception was thrown, no reference to the int a= 10/0 line at the stacktrace.Paleozoic
@SoMoS - It appears to be related to where the exception is thrown, see my update.Librate
Mmm, I think you're wrong. I have both situations with the same results. Can you check that with a demo?Paleozoic
It appears you are right, but it's not regenerating the stacktrace; it's modifying the line number of the "Main()" line in the stacktrace to be the point of rethrow. The difference is that the call to "Throw()" is still above "Main()" in the stacktrace. If you called throw ex, "Main()" would be the top of the stacktrace.Librate
+1 for the fact that your explanation was able to filter the OP's seen behaviour down to a very specific scenario where the behaviour as mentioned by the OP is seen to occur only if the erroríng line is in the SAME method. However, taking the exact example as mentioned by the OP, the behaviour described by the OP does seem like a bug for that very specific scenarioLamia
@InSane - Actually I think the scenarios are the same, it's just more obvious when the exception is thrown from a called method. AlexD's answer seems to indicate that the frame modification is by design of the rethrow MSIL instruction.Librate
D
5

There is a duplicate question here.

As I understand it - throw; is compiled into 'rethrow' MSIL instruction and it modifies the last frame of the stack-trace.

I would expect it to keep the original stack-trace and add the line where it has been re-thrown, but apparently there can only be one stack frame per method call.

Conclusion: avoid using throw; and wrap your exception in a new one on re-throwing - it's not ugly, it's best practice.

Dilan answered 19/1, 2011 at 10:12 Comment(0)
K
5

You can preserve stack trace using

ExceptionDispatchInfo.Capture(ex);

Here is code sample:

    static void CallAndThrow()
    {
        throw new ApplicationException("Test app ex", new Exception("Test inner ex"));
    }

    static void Main(string[] args)
    {
        try
        {
            try
            {
                try
                {
                    CallAndThrow();
                }
                catch (Exception ex)
                {
                    var dispatchException = ExceptionDispatchInfo.Capture(ex);

                    // rollback tran, etc

                    dispatchException.Throw();
                }
            }
            catch (Exception ex)
            {
                var dispatchException = ExceptionDispatchInfo.Capture(ex);

                // other rollbacks

                dispatchException.Throw();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            Console.WriteLine(ex.InnerException.Message);
            Console.WriteLine(ex.StackTrace);
        }

        Console.ReadLine();
    }

The output will be something like:

Test app ex
Test inner ex
   at TestApp.Program.CallAndThrow() in D:\Projects\TestApp\TestApp\Program.cs:line 19
   at TestApp.Program.Main(String[] args) in D:\Projects\TestApp\TestApp\Program.cs:line 30
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at TestApp.Program.Main(String[] args) in D:\Projects\TestApp\TestApp\Program.cs:line 38
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at TestApp.Program.Main(String[] args) in D:\Projects\TestApp\TestApp\Program.cs:line 47
Klingel answered 12/7, 2016 at 11:43 Comment(2)
ExceptionDispatchInfo is only available from .NET Framework 4 onwardsDunois
last time I checked - .NET 4.5 - learn.microsoft.com/en-us/dotnet/api/…Mall
C
3

OK, there seems to be a bug in the .NET Framework, if you throw an exception, and rethrow it in the same method, the original line number is lost (it will be the last line of the method).

Fortunatelly, a clever guy named Fabrice MARGUERIE found a solution to this bug. Below is my version, which you can test in this .NET Fiddle.

private static void RethrowExceptionButPreserveStackTrace(Exception exception)
{
    System.Reflection.MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace",
      System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
    preserveStackTrace.Invoke(exception, null);
      throw exception;
}

Now catch the exception as usually, but instead of throw; just call this method, and voila, the original line number will be preserved!

Clout answered 5/2, 2014 at 10:43 Comment(1)
Doesn't work for me. I see no significant difference in the stack trace other than the addition of this method to the stack. The original line number of the exception is still not seen in the stack trace.Lemmons
T
2

Not sure whether this is by design, but I think it has always been like that.

If the original throw new Exception is in a separate method, then the result for throw should have the original method name and line number and then the line number in main where the exception is re-thrown.

If you use throw ex, then the result will just be the line in main where the exception is rethrow.

In other words, throw ex loses all the stacktrace, whereas throw preserves the stack trace history (ie details of the lower level methods). But if your exception is generated by the same method as your rethrow, then you can lose some information.

NB. If you write a very simple and small test program, the Framework can sometimes optimise things and change a method to be inline code which means the results may differ from a 'real' program.

Toscano answered 18/11, 2010 at 18:27 Comment(1)
regardless it's still unhappy the line of rethrows and not the one in which the function was called to write to the stack traceFission
E
1

Do you want your right line number? Just use one try/catch per method. In systems, well... just in the UI layer, not in logic or data access, this is very annoying, because if you need database transactions, well, they shouldn't be in the UI layer, and you won't have the right line number, but if you don't need them, don't rethrow with nor without an exception in catch...

5 minutes sample code:

Menu File -> New Project, place three buttons, and call the following code in each one:

private void button1_Click(object sender, EventArgs e)
{
    try
    {
        Class1.testWithoutTC();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine + Environment.NewLine + "In. Ex.: " + ex.InnerException);
    }
}

private void button2_Click(object sender, EventArgs e)
{
    try
    {
        Class1.testWithTC1();
    }
    catch (Exception ex)
    {
            MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine + Environment.NewLine + "In. Ex.: " + ex.InnerException);
    }
}

private void button3_Click(object sender, EventArgs e)
{
    try
    {
        Class1.testWithTC2();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine + Environment.NewLine + "In. Ex.: " + ex.InnerException);
    }
}

Now, create a new Class:

class Class1
{
    public int a;
    public static void testWithoutTC()
    {
        Class1 obj = null;
        obj.a = 1;
    }
    public static void testWithTC1()
    {
        try
        {
            Class1 obj = null;
            obj.a = 1;
        }
        catch
        {
            throw;
        }
    }
    public static void testWithTC2()
    {
        try
        {
            Class1 obj = null;
            obj.a = 1;
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
}

Run... the first button is beautiful!

Ernesternesta answered 25/7, 2012 at 17:40 Comment(0)
S
0

I think this is less a case of stack trace changing and more to do with the way the line number for the stack trace is determined. Trying it out in Visual Studio 2010, the behaviour is similar to what you would expect from the MSDN documentation: "throw ex;" rebuilds the stack trace from the point of this statement, "throw;" leaves the stack trace as it as, except that where ever the exception is rethrown, the line number is the location of the rethrow and not the call the exception came through.

So with "throw;" the method call tree is left unaltered, but the line numbers may change.

I've come across this a few times, and it may be by design and just not documented fully. I can understand why they may have done this as the rethrow location is very useful to know, and if your methods are simple enough the original source would usually be obvious anyway.

As many other people have said, it usually best to not catch the exception unless you really have to, and/or you are going to deal with it at that point.

Interesting side note: Visual Studio 2010 won't even let me build the code as presented in the question as it picks up the divide by zero error at compile time.

Sites answered 19/1, 2011 at 9:59 Comment(2)
If a method with a catch/rethrow calls some other method more than once, and one of those calls generates an exception, it would often be very useful to know which call was involved. I would think that would useful far more often the location of the rethrow.Schrock
It's too bad languages don't make it easier to handle an exception without catching it (for cases where the handler has to take some action based on the exception, but will be able to determine before any nested "finally" blocks have run whether it's going to rethrow). VB.net can combine "Catch When" and "Finally" to achieve this effect, though it's awkward. C# can't even do that.Schrock
A
-2

That is because you catched the Exception from Line 12 and have rethrown it on Line 15, so the Stack Trace takes it as cash, that the Exception was thrown from there.

To better handle exceptions, you should simply use try...finally, and let the unhandled Exception bubble up.

Anaclinal answered 18/11, 2010 at 17:17 Comment(4)
No, that's what throw ex; is supposed to do. throw; should not change the stack trace.Slayton
@Wim -- that is because he has the variable to catch the exception in the catch -- if you use throw; you should also have catch ()Hoofer
@Hogan: try it, it doesn't make any difference how you declare the catch; what matters is what you do inside the catch body. Whether the stack trace is reset normally depends on whether you throw; (adds one entry to stacktrace) or throw ex; (reset, new stacktrace). See geekswithblogs.net/AlsLog/archive/2007/05/10/112386.aspx In this case something special is going on: the compiler probably treats the direct throw-catch-throw within a single method body as a single throw because of an optimization.Slayton
Correction, it doesn't seem to be a compiler optimization, but a special case for two throws in the same method body. Apparently stacktraces never contain two consecutive entries for the same method - at least, I've not been able to create one.Slayton

© 2022 - 2024 — McMap. All rights reserved.