diff --git a/src/System.Private.CoreLib/Resources/Strings.resx b/src/System.Private.CoreLib/Resources/Strings.resx index e2afd2659910..f18a55d7c72f 100644 --- a/src/System.Private.CoreLib/Resources/Strings.resx +++ b/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/System.Private.CoreLib/shared/System/Diagnostics/StackTrace.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/StackTrace.cs index aaabf2076e2a..b69dc670755e 100644 --- a/src/System.Private.CoreLib/shared/System/Diagnostics/StackTrace.cs +++ b/src/System.Private.CoreLib/shared/System/Diagnostics/StackTrace.cs @@ -193,12 +193,17 @@ internal enum TraceFormat /// the format for backwards compatibility. /// 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 @@ internal string ToString(TraceFormat traceFormat) if (fFirstFrame) fFirstFrame = false; else - sb.Append(Environment.NewLineConst); + sb.AppendLine(); sb.AppendFormat(CultureInfo.InvariantCulture, " {0} ", word_At); @@ -320,16 +325,14 @@ internal string ToString(TraceFormat traceFormat) // 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/System.Private.CoreLib/shared/System/Exception.cs b/src/System.Private.CoreLib/shared/System/Exception.cs index 5d38412538f6..a437da1c33f2 100644 --- a/src/System.Private.CoreLib/shared/System/Exception.cs +++ b/src/System.Private.CoreLib/shared/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/System.Private.CoreLib/shared/System/IO/FileStreamCompletionSource.Win32.cs b/src/System.Private.CoreLib/shared/System/IO/FileStreamCompletionSource.Win32.cs index 81240d6ba9f9..7345afec3f1f 100644 --- a/src/System.Private.CoreLib/shared/System/IO/FileStreamCompletionSource.Win32.cs +++ b/src/System.Private.CoreLib/shared/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 @@ private void CompleteCallback(ulong packedResult) } else { - TrySetException(Win32Marshal.GetExceptionForWin32Error(errorCode)); + Exception e = Win32Marshal.GetExceptionForWin32Error(errorCode); + e.SetCurrentStackTrace(); + TrySetException(e); } } else diff --git a/src/System.Private.CoreLib/shared/System/Runtime/ExceptionServices/ExceptionDispatchInfo.cs b/src/System.Private.CoreLib/shared/System/Runtime/ExceptionServices/ExceptionDispatchInfo.cs index 60f3beaf6057..93236955f2a1 100644 --- a/src/System.Private.CoreLib/shared/System/Runtime/ExceptionServices/ExceptionDispatchInfo.cs +++ b/src/System.Private.CoreLib/shared/System/Runtime/ExceptionServices/ExceptionDispatchInfo.cs @@ -61,5 +61,23 @@ public void Throw() // 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/System.Private.CoreLib/shared/System/Threading/Tasks/Task.cs b/src/System.Private.CoreLib/shared/System/Threading/Tasks/Task.cs index 55c946add0d0..9d2c4f172379 100644 --- a/src/System.Private.CoreLib/shared/System/Threading/Tasks/Task.cs +++ b/src/System.Private.CoreLib/shared/System/Threading/Tasks/Task.cs @@ -1802,6 +1802,7 @@ internal void AddException(object exceptionObject, bool representsCancellation) // 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/System.Private.CoreLib/shared/System/Threading/Timer.cs b/src/System.Private.CoreLib/shared/System/Threading/Timer.cs index 3574e4344e2f..d7c0537f559c 100644 --- a/src/System.Private.CoreLib/shared/System/Threading/Timer.cs +++ b/src/System.Private.CoreLib/shared/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 @@ public ValueTask CloseAsync() // 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 diff --git a/src/System.Private.CoreLib/src/System/Exception.CoreCLR.cs b/src/System.Private.CoreLib/src/System/Exception.CoreCLR.cs index 8bffa790a862..cbffeabd4bfd 100644 --- a/src/System.Private.CoreLib/src/System/Exception.CoreCLR.cs +++ b/src/System.Private.CoreLib/src/System/Exception.CoreCLR.cs @@ -8,6 +8,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; +using System.Text; namespace System { @@ -420,5 +421,30 @@ internal DispatchState CaptureDispatchState() 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(); + } } }