Wednesday, May 16, 2012

How .NET Exception stack traces are broken. How they can be fixed.

Unlike Java, .NET Exception stack traces are Less Than Worthy. There are many ways this is true. Today we will discuss how exception stack traces get lost when rethrown.

If you don't need to be convinced that this is a bug, not a feature, you can skip ahead to the workaround.

The Problem


Imagine a program where Method1 calls Method2 calls Method3 calls Method4 which throws an exception. Imagine that one of these intermediate methods, Method2, needs to catch the exception, do some stuff (not shown), and then rethrow the caught exception.

        public static void MyMain()
        {
            try { Method1(); } catch (Exception e) { Console.WriteLine(e); }
        }

        public static void Method1()
        {
            Method2();
        }

        public static void Method2()
        {
            try { Method3(); } catch (Exception e) { throw e; }
        }

        public static void Method3()
        {
            Method4();
        }

        public static void Method4()
        {
            throw new Exception("From Method4");
        }


The ultimate caller, MyMain, prints the exception, including it's stack trace. Here's what it prints:
System.Exception: From Method4
   at MiscCS4Tests.PreserveExceptionStackTrace2.Method2()
   at MiscCS4Tests.PreserveExceptionStackTrace2.Method1()
   at MiscCS4Tests.PreserveExceptionStackTrace2.MyMain()

 Wait, what happened to Method3 and Method4?

The problem is that, when C# throws an exception, it throws away (no pun intended) all stack trace information and starts over.

That is never what I want. (And if it was, I would more likely create a new exception object to throw for other reasons.)

There are ways around this:
  • Instead of throw e; use throw;  That will preserve the stack trace. But this form of throw is only allowed inside a catch block.
  • Create & throw a new exception object that nests the caught exception as the "inner exception". That will retain the full stack trace, if you chain through all the nested exceptions (which Exception.ToString does).
The problem is that often you are no longer in the catch block when you want to rethrow. And neither do you want to nest the exception merely to preserve the stack trace, since that typically requires changing the Exception type (unless you use reflection to create the exact same Exception sub-type that you caught, assuming you understand it's constructor).

As a typical example, suppose that you catch several different explicit exceptions using different catch blocks, but the body of the catch blocks are identical.

            try {
                AMethod();
            } catch (ArgumentException e) {
                DoSomething();
                throw;
            } catch (IOException e) {
                DoSomething();
                throw;
            }


As a good programmer, you consider duplicated code to be evil, so you refactor it into a shared place. Perhaps a separate method; more often inline like this:

            Exception ex = null;
            try {
                AMethod();
            }
            catch (ArgumentException e) { ex = e; }
            catch (IOException e)       { ex = e; }
            if (ex != null) {
                DoSomething();
                throw ex;
            }


But however you do it, you're no longer in a catch block when you rethrow. So you have to explicitly specify the exception on the throw. And you've lost your stack trace.

The Workaround


It turns out that Microsoft realized that they needed to preserve (remote) stack traces when rethrowing remote exceptions. So they built that functionality into the Exception class. Too bad they didn't realize the rest of us needed it too; so they made it private.

Here's a handy Exception extension method that will use reflection to invoke this private method. 

    public static class ExceptionHelper
    {
        public static void PreserveStackTrace(this Exception e)
        {
            _internalPreserveStackTrace(e);
        }

        private static readonly Action<Exception> _internalPreserveStackTrace =
                (Action<Exception>)Delegate.CreateDelegate(
                    typeof(Action<Exception>),
                    typeof(Exception).GetMethod(
                                "InternalPreserveStackTrace",
                                BindingFlags.Instance | BindingFlags.NonPublic));
    }
 

Here's Method2 (from the above example) rewritten to use this:

        public static void Method2()
        {
            try { Method3(); }
            catch (Exception e) { e.PreserveStackTrace(); throw e; }
        }


And here is the stack trace now:

System.Exception: From Method4
   at MiscCS4Tests.PreserveExceptionStackTrace2.Method4() in xxx.cs:line 170
   at MiscCS4Tests.PreserveExceptionStackTrace2.Method3() in xxx.cs:line 165
   at MiscCS4Tests.PreserveExceptionStackTrace2.Method2() in xxx.cs:line 159
   at MiscCS4Tests.PreserveExceptionStackTrace2.Method2() in xxx.cs:line 161
   at MiscCS4Tests.PreserveExceptionStackTrace2.Method1() in xxx..cs:line 154
   at MiscCS4Tests.PreserveExceptionStackTrace2.MyMain() in xxx..cs:line 149

Note that Method2 appears twice in the trace: once where the exception was caught, and once where it was thrown. This is undesirable but better than losing the rest of the stack trace.

Caveats:
  • There are security restrictions for using reflection. Refer to the Accessing Members That Are Normally Inaccessible section in this article.
  • This depends on internal implementations of the Exception class which could change. Although, if that happens, you can at least rewrite this extension method to use less efficient, but publicly supported, interfaces as described here. This involves serializing & deserializing the exception.
     

2 comments:

  1. Hi Dave,

    Thank you for the information about preserve-stack-trace internal/private method in .NET framework!

    Using exception to control-flow is an anti-pattern, and definitely not a good practice. Then, Where would we realistically use this though? Any thoughts?

    Thanks!

    ReplyDelete
    Replies
    1. Hi Shiva,

      Your question is beyond the scope of my post, but here are my thoughts.

      Exceptions (like any other programming technique) can be used for good or bad. When using it obscures the common, expected control flow, that is bad. When it removes recurring, verbose error-handling for unusual cases, esp. where that handling needs to be split between much higher and much lower callers, with many intervening layers that could/should be oblivious to such cases, it is good; the common expected control flow becomes clearer.

      And there is the pragmatic issue of dealing with third-party APIs (that you have no control over, like .NET) that throw Exceptions.

      Given that, whenever you are handling Exceptions for whatever reasons, there is good and bad practice. Other articles discuss this more generally; e.g., https://today.java.net/article/2006/04/04/exception-handling-antipatterns#antipatterns

      My post is independent of all that. It says that, if you are using Exceptions, the programming framework defining your base Exceptions should give you the capabilities you need. Specifically, it should make it easy for you to retain all the context information (e.g., stack trace) to enable easier debugging. .NET falls down here.

      Thanks for your comment!

      Delete