Catch, Rethrow, and Debuggability

When you catch and rethrow an exception, the stack is unwound by the time the catch clause is executed, and thus you may lose valuable information in the debugger.

Consider the following code snippet:

     static void Main(string[] args)
    {
        Func1a();
    }

    static void Func1a()
    {
        try
        {
            Func2();
        }
        catch
        {
            throw; // rethrown, this goes unhandled
        }
    }

    static void Func2()
    {
        int x = 5;
        throw new Exception("Test"); // original first chance exception
    }

If you stop in the debugger when the exception is first thrown, you get the full callstack right at the throw point :

   random.exe!Program.Func2() C#
  random.exe!Program.Func1b() C#
  random.exe!Program.Main(string[] args = {Dimensions:[0]}) C#

You can then use the full power of the debugger, such as inspecting parameters, locals,  interceptable exceptions, mixed-mode callstacks (if interop-debugging), native assembly windows, etc.

However, if you stop at the catch/rethrow point, the stacktrace in the debugger is:

   random.exe!Program.Func1b() C#
  random.exe!Program.Main(string[] args = {Dimensions:[0]}) C#

Now Func2(), the original throw site, is no longer on the stack in the debugger. 

Other trivia:

  1. What about the Exception.StackTrace property ? Exceptions have a StackTrace property which is a string representation of the stack trace. It is not nearly as rich as the stack trace under the debugger as mentioned above.

  2. 'throw;' vs. 'throw e;' ?  These affect the exception object that's thrown, but don't affect the callstack from which it's thrown. Imagine this scenario:

             catch (Exception e)
            {            
                if (...) 
                    throw;
                else
                    throw e;
            }
    

    The callstack would be determined by the time you entered the catch block, and so would be the same in both cases.

  3. Does it have to be this way? This is a natural implementation resulting from Structured Exception Handling (SEH) because catch handlers are executed in the 2nd-pass. See Matt Pietrek's excellent SEH article for more gory details. You could, of course, imagine alternative implementations.

  4. What about unmanaged code? This is also true for unmanaged code (see Matt's article). However, unmanaged C++ exceptions don't even have stacktrace properties, so that case is even harder to debug.

  5. The example above uses catch-all (which is frowned upon); but this rethrow problem is also true for catching specific exceptions.

  6. [Update 2/12/07]:  Here's a technique using JMC to make catch / rethrow more debuggable.

The practical side:

  1. Some library functions will automatically catch + rethrow. For eg:
    - MethodInfo.Invoke will catch and rethrow exceptions as a TargetInvocationException, with an inner exception of the original exception.
    - Exceptions crossing appdomain boundaries will get caught + marshaled across the appdomain boundary + rethrown  on the other side.
  2. People commonly get burned by this because most people (for good reason) don't stop on first-chance exceptions. So by the time you gets the unhandled exception notification, the useful info has already been lost via catch/rethrow.  
  3. This can be especially tough for unhandled exception bug reports.