ASP.NET Hosting

Rethrowing exceptions and preserving the full call stack trace

Did you know that depending on the way you rethrow exceptions you may lose important information? There are already several blog posts that explain and demonstrate the difference between throw and throw ex. I'm realizing only now that none of the two solutions yields the complete call stack trace information!

Let's see what the problem is and I'll show you the real solution.

I'll use the following method to generate an exception:

private static void BadWork()
{
  int i = 0;
  int j = 12 / i; // Line 10: DivideByZeroException
  int k = j + 1;
}

Let's consider what happens if we call BadWork and rethrow the exception with throw ex as follows:

try
{
  BadWork();
}
catch (Exception ex)
{
  // do something
  // ...
  throw ex; // Line 24
}

Here is the call stack trace that we get in this case:

Unhandled Exception: System.DivideByZeroException: Attempted to divide by zero.
   at Program.WithThrowEx() in Program.cs:line 24
   at Program.Main(String[] args) in Program.cs:line 88

Line 24 is where throw ex is, not where the exception was thrown.

Let's now replace throw ex by throw:

try
{
  BadWork();
}
catch
{
  // do something
  // ...
  throw; // Line 38
}


This time, here is the call stack trace:

Unhandled Exception: System.DivideByZeroException: Attempted to divide by zero.
   at Program.BadWork() in Program.cs:line 10
   at Program.WithThrow() in Program.cs:line 38
   at Program.Main(String[] args) in Program.cs:line 89

As you can see, we get one additional stack frame this time. Line 10 is where the exception was thrown, which is important information because this is the only information that identifies where the exception actually happened.

This shows that it's better to use throw rather than throw ex if you want the full stack trace information to be preserved. However, there are cases where throw is not enough. In the following example, throw does not preserve the full stack trace:

try
{
  int i = 0;
  int j = 12 / i; // Line 47
  int k = j + 1;
}
catch
{
  // do something
  // ...
  throw; // Line 54
}

Unhandled Exception: System.DivideByZeroException: Attempted to divide by zero.
   at Program.WithThrowIncomplete() in Program.cs:line 54
   at Program.Main(String[] args) in Program.cs:line 106

This time, you can see that information is lost again. Line 54 is where throw is, not where the exception was thrown.

To preserve the full call stack information, you need to use the following method:

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

This method can be used as follows: 

try
{
  int i = 0;
  int j = 12 / i; // Line 78
  int k = j + 1;
}
catch (Exception ex)
{
  // do something
  // ...
  PreserveStackTrace(ex);
  throw; // Line 86
}

Here is the new call stack information you get with the above code:

Unhandled Exception: System.DivideByZeroException: Attempted to divide by zero.
   at Program.WithThrowAndStackTracePreservation() in Program.cs:line 78
   at Program.WithThrowAndStackTracePreservation() in Program.cs:line 86
   at Program.Main(String[] args) in Program.cs:line 110

Here is the call stack information you get with throw ex and a call to PreserveStackTrace:

Unhandled Exception: System.DivideByZeroException: Attempted to divide by zero.
   at Program.BadWork() in Program.cs:line 10
   at Program.WithThrowExAndStackTracePreservation() in Program.cs:line 62
   at Program.WithThrowExAndStackTracePreservation() in Program.cs:line 69
   at Program.Main(String[] args) in Program.cs:line 109

Here we get the full call stack information. Lines 78 and 10 are where the exceptions were thrown. To my knowledge, this is the only way to get complete call stack information in your logs. Without it, it may be difficult to hunt down some bugs.
It's worth noting that if you call PreserveStackTrace, then you can use throw or throw ex and you'll equally get the full stack trace information.

I found this useful trick on Chris Taylor's blog. If you want to use this with .NET 1, you should refer to Chris' post because it seems that the InternalPreserveStackTrace method didn't exist before .NET 2.0.

The complete source code is attached to this post.

17 Comments

  • Nikola, when you throw a new exception with the inner exception specified, you lose the type of the original exception.
    Throwing a new exception of type Exception or a more specific type can be useful when done on purpose. But if you always throw Exception, you are losing something. The trick I show here is useful when you want to let the original exception flow, without creating a new one, which would mean something different.
    Let's consider the following example:

    try
    {
    ...
    try
    {
    // Code that throws a DivideByZeroException
    }
    catch (Exception x)
    {
    ...
    throw new Exception("message", x);
    }
    }
    catch (DivideByZeroException)
    {
    // NEVER EXECUTED because all exceptions have been encapsulated into new instances of the Exception class
    }

  • Hi, I'm working in vb.net
    If I use:
    Try
    ....
    Catch ex As Exception
    PreserveStackTrace(ex);
    Throw ex // Line 86
    End Try

    The stack is not preserved.
    It works only if I use:

    Try
    ....
    Catch ex As Reflection.TargetInvocationException
    PreserveStackTrace(ex);
    Throw ex // Line 86
    End Try

    The problem is that "Reflection.TargetInvocationException" does not catch all the exceptions, only the ones thrown by reflected methods.

    Does anyone knows a way for this to work catching all the exceptions?

  • To Fabrice Marguerie:

    Fabrice you do not actually lose the type. When you use innerException or rethrow an exception, it still knows what type of exception it is.

    I sometimes wish people would be sure of their facts before posting!

  • John, I guess that the text you refer to is "when you throw a new exception with the inner exception specified, you lose the type of the original exception."

    What I mean by this is that the type of the new exception is indeed different than the original exception. At least in Nikola's example. Of course, the type of the inner exception is unchanged. But when you throw a new exception of a different type, you cannot catch this exception in a catch statement based on the original type. That's why it's better to rethrow the original exception if that's what you want. That's not always what you want and nesting exceptions using innerException is perfectly fine and useful in some cases.

    I'm sure of my facts on this.

  • Hell yah, thanks for the article. I was trying to accomplish something similar by writing to _stackTraceString of innerexception using reflection but that wasn't working. This bit of code is exactly what I needed. (and gives a better result)

  • That's exactly what I was looking for.
    Thanks!

  • I believe the use of inner exception is much safer than any other method but there are some ground rules.

    Only catch an exception in an inner function when you have some useful information to add:

    try{

    }

    catch(exception e){

     //Log some info regarding the state e.g. current

     //data

     throw new exception('my custom message', e)

    }

    That way stack is only increased when you need local information otherwise the exception can just be allowed to pass through to a parent function

  • Supposing that I always catch "Exception" and I'm not terribly concerned about the type, then using Throw("msg", ex) with the inner exception specified, WILL retain the original stack trace, correct?

  • Mr Different John,

    I haven't checked that, but I would say yes, assuming that Throw("msg", ex) is the equivalent of the following C# code:

    throw new Exception("msg", ex);

    Fabrice

  • Dear Fabrice,
    First of all thanks for such a nice article. It really provided me what i was looking for.
    Secondly you are right

    Throw("msg", ex)
    is the equivalent of
    throw new Exception("msg", ex);

  • Thanks farrukh

  • Fabrice,
    Thanks for the post. Would this slight modification be ok?

    public static void RethrowException(Exception ex)
    {
    MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace",
    BindingFlags.Instance | BindingFlags.NonPublic);
    preserveStackTrace.Invoke(ex, null);
    throw ex;
    }

  • GokuDaMaster, why would one want to do this?
    Why would you want to have the stack trace show the RethrowException method as the exception source??

  • It's more for convenience and readability I guess. The stack trace would still show the function that called RethrowException.

    As long as the same function didn't call RethrowException multiple times it would be easy to find the problem line, otherwise using your version would be better.

  • There was an older comment from 'Jose' about having some issues in VB.NET. I took the directly converted C# -> VB.NET code and tested it. It worked as descibed with no issues. I had code with and without the call to the Sub, and my exception log would show the proper line number with stack trace or omit it as excpected. This is a well explained entry on this topic, thank you. The VB.NET code I used is below:

    Private Shared Sub PreserveStackTrace(ByVal exObj As Exception)
    Dim preserveStackTrace As MethodInfo = GetType(Exception).GetMethod("InternalPreserveStackTrace", BindingFlags.Instance Or BindingFlags.NonPublic)
    preserveStackTrace.Invoke(exObj, Nothing)
    End Sub

  • Thank you.
    You make my day.

  • Mikhail, thanks a lot for sharing the results of your study.
    Personally, I consider that performance is not a big issue because exceptions should remain exceptional, as you write at the end of your comment.

    To answer your question, what's expensive is reflection.
    This can be largely improved by caching the results of the call to typeof(Exception).GetMethod(...) (in a static field). This is a common practice to reduce the cost of accessing members via reflection.

    I haven't tested this, but I invite you to include this option in your benchmark.

Comments have been disabled for this content.