10

Possible Duplicate:
incorrect stacktrace by rethrow

It is generally accepted that in .NET throw; does not reset the stack trace but throw ex; does.

However, in this simple program , I get different line numbers:

void Main()
{
    try
    {
        try
        {
            Wrapper(); // line 13
        }
        catch(Exception e)
        {
            Console.WriteLine(e.ToString());
            throw; // line 18
        }
    }
    catch(Exception e)
    {
          Console.WriteLine(e.ToString());
    }
}

public void Wrapper()
{
    Throw(); // line 28
}

public void Throw()
{
    var x = (string)(object)1; // line 33
}

The output is:

System.InvalidCastException: Unable to cast object of type 'System.Int32' to type 'System.String'. at ConsoleApplication2.Program.Main(String[] args) in C:\long-path\Program.cs:line 13

System.InvalidCastException: Unable to cast object of type 'System.Int32' to type 'System.String'. at ConsoleApplication2.Program.Main(String[] args) in C:\long-path\Program.cs:line 18

Note: The first stack trace contains line 13, the second contains line 18. Additionally, neither line 13 nor line 18 are the lines where the cast is actually happening.

My question now is: In what circumstances does throw; change the stack trace and in which circumstance doesn't it change the stack trace?

Please note, that this has already been observed, but not answered in general.


UPDATE:
I ran the code above in Debug mode and it yields this:

System.InvalidCastException: Unable to cast object of type 'System.Int32' to type 'System.String'. at ConsoleApplication2.Program.Throw() in C:\long-path\Program.cs:line 33 at ConsoleApplication2.Program.Wrapper() in C:\long-path\Program.cs:line 28 at ConsoleApplication2.Program.Main(String[] args) in C:\long-path\Program.cs:line 13

System.InvalidCastException: Unable to cast object of type 'System.Int32' to type 'System.String'. at ConsoleApplication2.Program.Throw() in C:\long-path\Program.cs:line 33 at ConsoleApplication2.Program.Wrapper() in C:\long-path\Program.cs:line 28 at ConsoleApplication2.Program.Main(String[] args) in C:\long-path\Program.cs:line 18

Please note: The last line number still changes

Community
  • 1
  • 1
Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443

2 Answers2

5

The reason for this happening is because of method inlining when running in Release mode. If you don't want the Wrapper and Throw methods to be inlined in Release mode you could decorate them with the [MethodImpl] attribute:

[MethodImpl(MethodImplOptions.NoInlining)]
public void Wrapper()
{
    Throw();
}

[MethodImpl(MethodImplOptions.NoInlining)]
public void Throw()
{
    var x = (string)(object)1;
}
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • 1
    OK, this explains why the line number in the first catch is not the line number of the cast. It still doesn't explain why `throw;` changes the line number in the stack trace. – Daniel Hilgarth Sep 11 '12 at 16:03
  • I was about to say something along those lines, so just to add: Running the code in debug mode yields complete call stacks which both originate in the 'Throw' method. – Brian Rasmussen Sep 11 '12 at 16:03
  • @DanielHilgarth The line number you're seeing is the line where the exception was re-thrown. In a release build that's all you get as the rest has been inlined. If you want the origin of the exception, you need the full stack. – Brian Rasmussen Sep 11 '12 at 16:05
  • @BrianRasmussen: Please see update to my question. I mean the last line number that changes. – Daniel Hilgarth Sep 11 '12 at 16:10
  • 1
    If the last line number didn't change, you wouldn't be able to tell that the exception was re-thrown. The difference between `throw e;` and `throw;` is that the first will clear the stack before the point of re-throwing, while the latter will keep it. Re-throwing will add to the stack in both cases, just as it should. – Brian Rasmussen Sep 11 '12 at 16:16
  • 1
    @BrianRasmussen: `throw;` still loses important information. What if the content inside `try` wouldn't be just one call to a method but something like this: `int.Parse(x1); int.Parse(x2);` (on seperate lines of course). After `throw`, you wouldn't know which of the two threw the exception. – Daniel Hilgarth Sep 11 '12 at 16:24
4

As Darin already point out the reduced stack trace is due to method inlining. However there is also the point of the line reference that is available in the stack trace not being equal.

I do not know the explanation behind this, but there is a way for you to keep all stacktrace information when rethrowing an exception. You need to throw a new exception and pass the one you catch as the inner exception. With this approach the merged stacktrace will contain the point of origin of the exception and also the point where the exception is rethrown.

I talked about this and presented full examples on different ways to rethrow exceptions in the following blog post:

.NET Exceptions – throw ex is evil but throw is not that innocent


Your comment motivated me for a quick research but the best I could find was this comment by Jonathan de Halleux on a blog post about catch and rethrow:

It also changes the line number in the stacktrace in the method that rethrows (since the rethrow becomes the site of throw in that method).

This could be elaborated further, but it points to the fact that probably the throw site which will then be used to obtain line information is tracked at each method and the rethrow causes it to be overridden.

So even though the stacktrace is preserved when using throw instead of throw e the original throw site will be lost unless you wrap and throw a new exception.

Other things to try; since SO does not allow direct messages and the above comment was made by Peli you can try to tag this question with pex to get his attention and get him to follow up on that comment. :)

Community
  • 1
  • 1
João Angelo
  • 56,552
  • 12
  • 145
  • 147
  • 1
    Thanks. I know that wrapping it will preserve the complete stack trace. I asked this question to have a reference for future posts where people say to just use `throw;` "as it preserves the stack trace" when in fact it doesn't do that always. – Daniel Hilgarth Sep 11 '12 at 16:13
  • I updated the question with further information form the interwebs, that you may already seen, but may be useful for reference. However, it still does not explain the topic completely. – João Angelo Sep 11 '12 at 17:34