From 9bf2c148737f2feda7fce5da015d5b785c66ffc7 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Thu, 3 Oct 2019 21:59:40 -0400 Subject: [PATCH] Add ExceptionDispatchInfo.SetCurrentStackTrace (dotnet/coreclr#27004) * Add ExceptionDispatchInfo.SetCurrentStackTrace * Address PR feedback Commit migrated from https://github.com/dotnet/coreclr/commit/3bdc9f625480f497adfcd2bbfae039fdfd1e73f3 --- .../System.Private.CoreLib/Resources/Strings.resx | 2 +- .../src/System/Exception.CoreCLR.cs | 26 ++++++++++++++++++++++ .../src/System/Diagnostics/StackTrace.cs | 17 ++++++++------ .../System.Private.CoreLib/src/System/Exception.cs | 1 + .../System/IO/FileStreamCompletionSource.Win32.cs | 5 ++++- .../ExceptionServices/ExceptionDispatchInfo.cs | 18 +++++++++++++++ .../src/System/Threading/Tasks/Task.cs | 1 + .../src/System/Threading/Timer.cs | 5 ++++- 8 files changed, 65 insertions(+), 10 deletions(-) diff --git a/src/coreclr/src/System.Private.CoreLib/Resources/Strings.resx b/src/coreclr/src/System.Private.CoreLib/Resources/Strings.resx index e2afd26..f18a55d 100644 --- a/src/coreclr/src/System.Private.CoreLib/Resources/Strings.resx +++ b/src/coreclr/src/System.Private.CoreLib/Resources/Strings.resx @@ -2228,7 +2228,7 @@ --- End of inner exception stack trace --- - --- End of stack trace from previous location where exception was thrown --- + --- End of stack trace from previous location --- Exception of type '{0}' was thrown. diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Exception.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Exception.CoreCLR.cs index 8bffa79..cbffeab 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Exception.CoreCLR.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Exception.CoreCLR.cs @@ -8,6 +8,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; +using System.Text; namespace System { @@ -420,5 +421,30 @@ namespace System return new DispatchState(stackTrace, dynamicMethods, _remoteStackTraceString, _ipForWatsonBuckets, _watsonBuckets); } + + [StackTraceHidden] + internal void SetCurrentStackTrace() + { + // If this is a preallocated singleton exception, silently skip the operation, + // regardless of the value of throwIfHasExistingStack. + if (IsImmutableAgileException(this)) + { + return; + } + + // Check to see if the exception already has a stack set in it. + if (_stackTrace != null || _stackTraceString != null || _remoteStackTraceString != null) + { + ThrowHelper.ThrowInvalidOperationException(); + } + + // Store the current stack trace into the "remote" stack trace, which was originally introduced to support + // remoting of exceptions cross app-domain boundaries, and is thus concatenated into Exception.StackTrace + // when it's retrieved. + var sb = new StringBuilder(256); + new StackTrace(fNeedFileInfo: true).ToString(System.Diagnostics.StackTrace.TraceFormat.TrailingNewLine, sb); + sb.AppendLine(SR.Exception_EndStackTraceFromPreviousThrow); + _remoteStackTraceString = sb.ToString(); + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs index aaabf20..b69dc67 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs @@ -194,11 +194,16 @@ namespace System.Diagnostics /// internal string ToString(TraceFormat traceFormat) { + var sb = new StringBuilder(256); + ToString(traceFormat, sb); + return sb.ToString(); + } + + internal void ToString(TraceFormat traceFormat, StringBuilder sb) + { string word_At = SR.Word_At; string inFileLineNum = SR.StackTrace_InFileLineNumber; - bool fFirstFrame = true; - StringBuilder sb = new StringBuilder(255); for (int iFrameIndex = 0; iFrameIndex < _numOfFrames; iFrameIndex++) { StackFrame? sf = GetFrame(iFrameIndex); @@ -210,7 +215,7 @@ namespace System.Diagnostics if (fFirstFrame) fFirstFrame = false; else - sb.Append(Environment.NewLineConst); + sb.AppendLine(); sb.AppendFormat(CultureInfo.InvariantCulture, " {0} ", word_At); @@ -320,16 +325,14 @@ namespace System.Diagnostics // Skip EDI boundary for async if (sf.IsLastFrameFromForeignExceptionStackTrace && !isAsync) { - sb.Append(Environment.NewLineConst); + sb.AppendLine(); sb.Append(SR.Exception_EndStackTraceFromPreviousThrow); } } } if (traceFormat == TraceFormat.TrailingNewLine) - sb.Append(Environment.NewLineConst); - - return sb.ToString(); + sb.AppendLine(); } #endif // !CORERT diff --git a/src/libraries/System.Private.CoreLib/src/System/Exception.cs b/src/libraries/System.Private.CoreLib/src/System/Exception.cs index 5d38412..a437da1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Exception.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Exception.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections; +using System.Diagnostics; using System.Runtime.Serialization; namespace System diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamCompletionSource.Win32.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamCompletionSource.Win32.cs index 81240d6..7345afe 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamCompletionSource.Win32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamCompletionSource.Win32.cs @@ -4,6 +4,7 @@ using System.Buffers; using System.Diagnostics; +using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -186,7 +187,9 @@ namespace System.IO } else { - TrySetException(Win32Marshal.GetExceptionForWin32Error(errorCode)); + Exception e = Win32Marshal.GetExceptionForWin32Error(errorCode); + e.SetCurrentStackTrace(); + TrySetException(e); } } else diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/ExceptionServices/ExceptionDispatchInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/ExceptionServices/ExceptionDispatchInfo.cs index 60f3bea..9323695 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/ExceptionServices/ExceptionDispatchInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/ExceptionServices/ExceptionDispatchInfo.cs @@ -61,5 +61,23 @@ namespace System.Runtime.ExceptionServices // rather than replacing the original stack trace. [DoesNotReturn] public static void Throw(Exception source) => Capture(source).Throw(); + + /// Stores the current stack trace into the specified instance. + /// The unthrown instance. + /// The argument was null. + /// The argument was previously thrown or previously had a stack trace stored into it.. + /// The exception instance. + [StackTraceHidden] + public static Exception SetCurrentStackTrace(Exception source) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + source.SetCurrentStackTrace(); + + return source; + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs index 55c946a..9d2c4f1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @@ -1802,6 +1802,7 @@ namespace System.Threading.Tasks // may not contain a TCE, but an OCE or any OCE-derived type, which would mean we'd be // propagating an exception of a different type. canceledException = new TaskCanceledException(this); + canceledException.SetCurrentStackTrace(); } if (ExceptionRecorded) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Timer.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Timer.cs index 3574e43..d7c0537 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Timer.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Timer.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Diagnostics.Tracing; +using System.Runtime.ExceptionServices; using System.Threading.Tasks; namespace System.Threading @@ -533,7 +534,9 @@ namespace System.Threading // returning false if you use it multiple times. Since first calling Timer.Dispose(WaitHandle) // and then calling Timer.DisposeAsync is not something anyone is likely to or should do, we // simplify by just failing in that case. - return new ValueTask(Task.FromException(new InvalidOperationException(SR.InvalidOperation_TimerAlreadyClosed))); + var e = new InvalidOperationException(SR.InvalidOperation_TimerAlreadyClosed); + e.SetCurrentStackTrace(); + return new ValueTask(Task.FromException(e)); } } else -- 2.7.4