Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Commit

Permalink
Add ExceptionDispatchInfo.SetCurrentStackTrace (#27004)
Browse files Browse the repository at this point in the history
* Add ExceptionDispatchInfo.SetCurrentStackTrace

* Address PR feedback
  • Loading branch information
stephentoub authored Oct 4, 2019
1 parent 66f8bcf commit 3bdc9f6
Show file tree
Hide file tree
Showing 8 changed files with 65 additions and 10 deletions.
2 changes: 1 addition & 1 deletion src/System.Private.CoreLib/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -2228,7 +2228,7 @@
<value>--- End of inner exception stack trace ---</value>
</data>
<data name="Exception_EndStackTraceFromPreviousThrow" xml:space="preserve">
<value>--- End of stack trace from previous location where exception was thrown ---</value>
<value>--- End of stack trace from previous location ---</value>
</data>
<data name="Exception_WasThrown" xml:space="preserve">
<value>Exception of type '{0}' was thrown.</value>
Expand Down
17 changes: 10 additions & 7 deletions src/System.Private.CoreLib/shared/System/Diagnostics/StackTrace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -193,12 +193,17 @@ internal enum TraceFormat
/// the format for backwards compatibility.
/// </summary>
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);
Expand All @@ -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);

Expand Down Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions src/System.Private.CoreLib/shared/System/Exception.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -186,7 +187,9 @@ private void CompleteCallback(ulong packedResult)
}
else
{
TrySetException(Win32Marshal.GetExceptionForWin32Error(errorCode));
Exception e = Win32Marshal.GetExceptionForWin32Error(errorCode);
e.SetCurrentStackTrace();
TrySetException(e);
}
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,23 @@ public void Throw()
// rather than replacing the original stack trace.
[DoesNotReturn]
public static void Throw(Exception source) => Capture(source).Throw();

/// <summary>Stores the current stack trace into the specified <see cref="Exception"/> instance.</summary>
/// <param name="source">The unthrown <see cref="Exception"/> instance.</param>
/// <exception cref="ArgumentNullException">The <paramref name="source"/> argument was null.</exception>
/// <exception cref="InvalidOperationException">The <paramref name="source"/> argument was previously thrown or previously had a stack trace stored into it..</exception>
/// <returns>The <paramref name="source"/> exception instance.</returns>
[StackTraceHidden]
public static Exception SetCurrentStackTrace(Exception source)
{
if (source is null)
{
throw new ArgumentNullException(nameof(source));
}

source.SetCurrentStackTrace();

return source;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 4 additions & 1 deletion src/System.Private.CoreLib/shared/System/Threading/Timer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.Runtime.ExceptionServices;
using System.Threading.Tasks;

namespace System.Threading
Expand Down Expand Up @@ -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
Expand Down
26 changes: 26 additions & 0 deletions src/System.Private.CoreLib/src/System/Exception.CoreCLR.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Text;

namespace System
{
Expand Down Expand Up @@ -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();
}
}
}

0 comments on commit 3bdc9f6

Please sign in to comment.