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();
+ }
}
}