57

I have a component which handles errors using return values as opposed to standard exception handling. In addition to the error code, it also returns a stack trace of where the error has occurred. A wrapper I use to invoke the component will interpret return codes and throw an exception.

I'd like to have the wrapper throw an exception that includes the captured stack trace information from the component. I'd like it to appear as if the exception had been thrown from the original site of the error even though it was thrown elsewhere. More specifically, I'd like the stack trace displayed by the Visual Studio test runner to reflect the proper location.

Is there any way to do this? It would also be nice if I could avoid low-level reflection tricks accessing private members but I will take what I can get.

I'm not concerned with how to capture a stack trace, I'm concerned with attaching a stack trace that's already been captured, to an Exception.

I tried overriding the StackTrace property, but Visual Studio is pulling the stack trace data from somewhere else, and seems to ignore the overridden property completely

CustomException GenerateExcpetion()
{
    return new CustomException();
}

void ThrowException(Exception ex)
{
    Trace.WriteLine("Displaying Exception");
    Trace.WriteLine(ex.ToString());
    var edi = ExceptionDispatchInfo.Capture(ex);
    edi.Throw();
}

[TestMethod]
public void Test006()
{
    var ex = GenerateExcpetion();
    ThrowException(ex);
}

public class CustomException : Exception
{
    string _stackTrace;
    public CustomException()
    {
        _stackTrace = Environment.StackTrace;
    }

    public override string StackTrace
    {
        get
        {
            return base.StackTrace;
        }
    }
}

The Exception.ToString() method pulls stack trace data from a private property, and so the stack trace coming from the override doesn't get shown.

CustomException: Exception of type 'CustomException' was thrown.

The ExceptionDispatchInfo looks for the stack trace data from a private property as well, and so it can't find any of that data, and when you throw this custom exception a new stack trace is attached to the exception with the location that it is being thrown from. If you use throw directly, the private stack information is set to the place where the throw happened.

4
  • Why don't you just abandon error codes all together? Commented May 7, 2016 at 20:22
  • @EmperorAiman I guess it's a third party component? Commented May 7, 2016 at 20:23
  • @adjan, that must be from 90s then. Commented May 7, 2016 at 20:24
  • Possible duplicate of How to print the current Stack Trace in .NET without any exception? Commented May 8, 2016 at 0:33

9 Answers 9

49

Just create your own Exception type and override the StackTrace property:

class MyException : Exception
{
    private string oldStackTrace;

    public MyException(string message, string stackTrace) : base(message)
    {
        this.oldStackTrace = stackTrace;
    }


    public override string StackTrace
    {
        get
        {
            return this.oldStackTrace;
        }
    }
}
Sign up to request clarification or add additional context in comments.

5 Comments

Problem with this approach is that when exception is passed to loggers the StackTrace is still the old one and not the overridden one. The only way this will work is through reflection.
It's actually not necessary to create oldStackTrace. By overriding StackTrace it becomes settable in the constructor.
This doesn't work well when you call .ToString() on exception object since underlying ToString() method uses private field of the object instead of property.
@Mayank just override the ToString()?
@adjan that doesn't work properly as if some part of code explicitly uses stack then it'll have invalid info.
15

You can use: Environment.StackTrace to capture the stacktrace at the occurence of the error in the component and then return it together with the other error informations or rethrow.

You can manually build stackframes to create a full trace. See StackFrame/StackTrace for more information.

Comments

12

Well with nothing elegant available, here is my reflection based approach.

public static class ExceptionUtilities
{
    private static readonly FieldInfo STACK_TRACE_STRING_FI = typeof(Exception).GetField("_stackTraceString", BindingFlags.NonPublic | BindingFlags.Instance);
    private static readonly Type TRACE_FORMAT_TI = Type.GetType("System.Diagnostics.StackTrace").GetNestedType("TraceFormat", BindingFlags.NonPublic);
    private static readonly MethodInfo TRACE_TO_STRING_MI = typeof(StackTrace).GetMethod("ToString", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { TRACE_FORMAT_TI }, null);

    public static Exception SetStackTrace(this Exception target, StackTrace stack)
    {
        var getStackTraceString = TRACE_TO_STRING_MI.Invoke(stack, new object[] { Enum.GetValues(TRACE_FORMAT_TI).GetValue(0) });
        STACK_TRACE_STRING_FI.SetValue(target, getStackTraceString);
        return target;
    }
}

Writing a formatted StackTrace string to the _stackTraceString property seems to be enough to fool visual studio test runner and the Exception.ToString() methods into believing the stack was generated by a throw (without actually throwing anything).

See below for usage:

    StackTrace GetDeeperStackTrace(int depth)
    {
        if (depth > 0)
        {
            return GetDeeperStackTrace(depth - 1);
        }
        else
        {
            return new StackTrace(0, true);
        }
    }

    [TestMethod]
    public void Test007()
    {
        Exception needStackTrace = new Exception("Some exception");
        var st = GetDeeperStackTrace(3);

        needStackTrace.SetStackTrace(st);

        Trace.Write(needStackTrace.ToString());

        throw new Exception("Nested has custom stack trace", needStackTrace);
    }

2 Comments

A little more information for inquiring minds: StackTrace.TraceFormat is an enum (as apparent by Enum.GetValues, and the first value is TraceFormat.Normal. You can see the reference source for StackTrace.TraceFormat here and the invoked ToString method here
this works. unit test is passing now. one question if anyone has an answer. even though the stacktrace has the value I assigned using this function, if I open the exception using a breakpoint I still the old value on the stacktrace. It's like VS didn't refresh.
9

Since .NET 5, there is ExceptionDispatchInfo.SetCurrentStackTrace which populates an exception with the current stack trace.

.NET 6 additionally introduced ExceptionDispatchInfo.SetRemoteStackTrace which takes an arbitrary string.

Note, however, that these methods set the _remoteStackTraceString field on the Exception type which is opposed to the other answers here, where the _stackTraceString field is typically set. The resulting behavior may therefore be different but should suffice for the purpose of the original question.

1 Comment

I found that setting _stackTraceString (using reflection), as done elsewhere, does not work properly with ExceptionDispatchInfo.Capture/Throw.
8

There is an even more sophisticated solution that avoids any runtime overhead of the reflection part:

public static class ExceptionExtensions
{
    public static Exception SetStackTrace(this Exception target, StackTrace stack) => _SetStackTrace(target, stack);

    private static readonly Func<Exception, StackTrace, Exception> _SetStackTrace = new Func<Func<Exception, StackTrace, Exception>>(() =>
    {
        ParameterExpression target = Expression.Parameter(typeof(Exception));
        ParameterExpression stack = Expression.Parameter(typeof(StackTrace));
        Type traceFormatType = typeof(StackTrace).GetNestedType("TraceFormat", BindingFlags.NonPublic);
        MethodInfo toString = typeof(StackTrace).GetMethod("ToString", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { traceFormatType }, null);
        object normalTraceFormat = Enum.GetValues(traceFormatType).GetValue(0);
        MethodCallExpression stackTraceString = Expression.Call(stack, toString, Expression.Constant(normalTraceFormat, traceFormatType));
        FieldInfo stackTraceStringField = typeof(Exception).GetField("_stackTraceString", BindingFlags.NonPublic | BindingFlags.Instance);
        BinaryExpression assign = Expression.Assign(Expression.Field(target, stackTraceStringField), stackTraceString);
        return Expression.Lambda<Func<Exception, StackTrace, Exception>>(Expression.Block(assign, target), target, stack).Compile();
    })();
}

4 Comments

net core still has no built-in way?
@Marcel any recommendations how to modify this code if the stacktrace is received as a "string" object, and not StackTrace?
@SR8 You cannot make a cow from minced meat. Only a few (mostly simple) objects allow a turn around ToString() and back. StackTrace is none of them. It does not even have public constructors to create it on your own from the reflection data.
Because Environment.StackTrace returns a string and there doesn't seem to be a way to instantiate a StackTrace from a string, I'm not sure how this could be leveraged.
7

For me following seems to be the best way to do it. This approach works well with loggers since .ToString() for Exception uses private field for stack trace.

public class CustomException : Exception
{
    public CustomException()
    {
        var stackTraceField = typeof(CustomException).BaseType
            .GetField("_stackTraceString", BindingFlags.Instance | BindingFlags.NonPublic);

        stackTraceField.SetValue(this, Environment.StackTrace);
    }
}

3 Comments

This proved perfect for my needs (unit testing to ensure an exception's stack trace was used properly by the method under test).
You could use typeof(Exception)
Didn't work for me in .NET 8, stackTraceField is always null.
1

Here's a modern version of hannasm's answer that leverages the new [UnsafeAccessor] attribute, thus requiring .NET 8.

using System.Runtime.CompilerServices;

namespace System;

internal static class ExceptionExtensions
{
    [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_stackTraceString")]
    private static extern ref string _stackTraceString(Exception exception);

    public static Exception SetStackTrace(this Exception exception, string stackTrace)
    {
        _stackTraceString(exception) = stackTrace;
        return exception;
    }
}

See Accessing private members without reflection in C# by Gérald Barré (a.k.a. Meziantou) for more information.

If you don't mind having an extra line with --- End of stack trace from previous location --- at the end of the provided stack trace string then using ExceptionDispatchInfo.SetRemoteStackTrace(exception, stackTrace) as suggested by Zdeněk Jelínek is probably a better solution since it uses only a public API and doesn't access a private field.

Comments

0

Comments to the top voted solution addressed here.

.NET 9 (and possibly earlier versions, but I haven't checked) does not use the private _stackTraceString field in ToString, but the overridden StackTrace property, so the following solution works:

public class MyCustomException : Exception
{
    public override string StackTrace { get; }
    public MyCustomException() : base()
    {
        StackTrace = string.IsNullOrEmpty(base.StackTrace) ? Environment.StackTrace ?? string.Empty : base.StackTrace;
    }

}

Comments

-3
var error = new Exception(ex.Message);

var STACK_TRACE_STRING_FI = typeof(Exception).GetField("_stackTraceString", BindingFlags.NonPublic | BindingFlags.Instance);

STACK_TRACE_STRING_FI.SetValue(error, ex.StackTrace);

1 Comment

This is what this other answer suggests.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.