如何抛出反序列化异常?

时间:2021-03-19 11:17:06

标签: c# exception json.net grpc

我正在使用 JsonConvert.SerializeObject 在服务器上序列化一个 Exception,然后编码为 byte[] 并在客户端反序列化使用 JsonConvert.DeserializeObject。到目前为止一切正常......问题是当我抛出 Exception 被替换的堆栈跟踪时,让我演示:

public void HandleException(RpcException exp)
{
    // Get the exception byte[]
    string exceptionString = exp.Trailer.GetBytes("exception-bin");
    
    // Deserialize the exception
    Exception exception = JsonConvert.DeserializeObject<Exception>(exceptionString, new 
    JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });
    
    // Log the Exception: The stacktrace is correct. Ex.: at ServerMethod()
    Console.WriteLine(exception);
    
    // Throw the same Exception: The stacktrace is changed. Ex.: at HandleException()
    ExceptionDispatchInfo.Capture(exception).Throw();
}

2 个答案:

答案 0 :(得分:0)

如果您反序列化一个 Exception 并设置 JsonSerializerSettings.Context = new StreamingContext(StreamingContextStates.CrossAppDomain),那么即使在抛出异常之后,反序列化的堆栈跟踪字符串也会被添加到显示的 StackTrace 之前:

var settings = new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.All,
    Context = new StreamingContext(StreamingContextStates.CrossAppDomain),
};
var exception = JsonConvert.DeserializeObject<Exception>(exceptionString, settings);

注意事项:

  • 这是可行的,因为在 streaming constructor for Exception 中,反序列化的堆栈跟踪字符串被保存到一个 _remoteStackTraceString 中,该字符串随后被添加到常规堆栈跟踪中:

    <块引用>
    if (context.State == StreamingContextStates.CrossAppDomain)
    {
        // ...this new exception may get thrown.  It is logically a re-throw, but 
        //  physically a brand-new exception.  Since the stack trace is cleared 
        //  on a new exception, the "_remoteStackTraceString" is provided to 
        //  effectively import a stack trace from a "remote" exception.  So,
        //  move the _stackTraceString into the _remoteStackTraceString.  Note
        //  that if there is an existing _remoteStackTraceString, it will be 
        //  preserved at the head of the new string, so everything works as 
        //  expected.
        // Even if this exception is NOT thrown, things will still work as expected
        //  because the StackTrace property returns the concatenation of the
        //  _remoteStackTraceString and the _stackTraceString.
        _remoteStackTraceString = _remoteStackTraceString + _stackTraceString;
        _stackTraceString = null;
    }
    
  • 虽然 Exception 的序列化流确实包含堆栈跟踪字符串,但它不会尝试捕获 private Object _stackTrace,运行时使用它来识别正在执行的程序集中异常的位置被抛出。这似乎是 ExceptionDispatchInfo 在抛出异常时无法复制和使用此信息的原因。因此,抛出反序列化异常并从序列化流中恢复其“真实”堆栈跟踪似乎是不可能的。

  • 为了让 Json.NET 使用其流构造函数反序列化一个类型(从而根据需要设置远程跟踪字符串),该类型必须用 [Serializable] 标记并实现 ISerializableSystem.Exception 满足这两个要求,但 Exception 的某些派生类并不总是添加 [Serializable] 属性。如果您的特定序列化异常缺少该属性,请参阅Deserializing custom exceptions in Newtonsoft.Json

  • 使用 TypeNameHandling.All 反序列化异常是不安全的,并且在从不受信任的来源反序列化时可能会导致注入攻击类型。请参阅:External json vulnerable because of Json.Net TypeNameHandling auto? 其回答专门讨论了异常的反序列化。

演示小提琴here

答案 1 :(得分:0)

我想指出的只是一个小案例:我从两个应用程序中调用此序列化/反序列化,一个是 Blazor (.net 5),另一个是 WinForms (.net framework 4.7)。在 blazor one 中,接受答案的方法不起作用。在这种情况下,我所做的是通过反射设置 RemoteStackTrace。

// Convert string para exception
Exception exception = JsonConvert.DeserializeObject<Exception>(exceptionString, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto });

// Set RemoteStackTrace
exception.GetType().GetField("_remoteStackTraceString", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(exception, exception.StackTrace);

// Throw the Exception with original stacktrace
ExceptionDispatchInfo.Capture(exception).Throw();