diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/StackTraceMetadataCallbacks.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/StackTraceMetadataCallbacks.cs
index 32031ecab7c892..c423404ac4c330 100644
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/StackTraceMetadataCallbacks.cs
+++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/StackTraceMetadataCallbacks.cs
@@ -17,7 +17,7 @@ namespace Internal.Runtime.Augments
[CLSCompliant(false)]
public abstract class StackTraceMetadataCallbacks
{
- public abstract string TryGetMethodStackFrameInfo(IntPtr methodStartAddress, int offset, bool needsFileInfo, out string owningType, out string genericArgs, out string methodSignature, out bool isStackTraceHidden, out string fileName, out int lineNumber);
+ public abstract string TryGetMethodStackFrameInfo(IntPtr methodStartAddress, int offset, bool needsFileInfo, out string owningType, out string genericArgs, out string methodSignature, out bool isStackTraceHidden, out bool isAsync, out string fileName, out int lineNumber);
public abstract DiagnosticMethodInfo TryGetDiagnosticMethodInfoFromStartAddress(IntPtr methodStartAddress);
}
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Diagnostics/StackFrame.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Diagnostics/StackFrame.NativeAot.cs
index 71f3eb24181068..d66c20ea5aec51 100644
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Diagnostics/StackFrame.NativeAot.cs
+++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Diagnostics/StackFrame.NativeAot.cs
@@ -40,6 +40,13 @@ public partial class StackFrame
private bool _isStackTraceHidden;
+ ///
+ /// Will be true if this frame corresponds to a runtime-async method or the MoveNext method of a
+ /// compiler-generated async state machine. Used to suppress the "--- End of stack trace from
+ /// previous location ---" delimiter.
+ ///
+ private bool _isAsync;
+
// If stack trace metadata is available, _methodOwningType is the namespace-qualified name of the owning type,
// _methodName is the name of the method, _methodGenericArgs are generic arguments, and _methodSignature is the list of parameters
// without braces. StackTrace will format this as `{_methodOwningType}.{_methodName}<{_genericArgs}>({_methodSignature}).
@@ -122,7 +129,7 @@ private void InitializeForIpAddress(IntPtr ipAddress, bool needFileInfo)
StackTraceMetadataCallbacks stackTraceCallbacks = RuntimeAugments.StackTraceCallbacksIfAvailable;
if (stackTraceCallbacks != null)
{
- _methodName = stackTraceCallbacks.TryGetMethodStackFrameInfo(methodStartAddress, _nativeOffset, needFileInfo, out _methodOwningType, out _methodGenericArgs, out _methodSignature, out _isStackTraceHidden, out _fileName, out _lineNumber);
+ _methodName = stackTraceCallbacks.TryGetMethodStackFrameInfo(methodStartAddress, _nativeOffset, needFileInfo, out _methodOwningType, out _methodGenericArgs, out _methodSignature, out _isStackTraceHidden, out _isAsync, out _fileName, out _lineNumber);
}
if (_methodName == null)
@@ -230,7 +237,7 @@ internal void AppendToStackTrace(StringBuilder builder)
builder.AppendLine();
}
}
- if (_isLastFrameFromForeignExceptionStackTrace)
+ if (_isLastFrameFromForeignExceptionStackTrace && !_isAsync)
{
// Passing default for Exception_EndStackTraceFromPreviousThrow in case SR.UsingResourceKeys is set.
builder.AppendLine(SR.UsingResourceKeys() ?
diff --git a/src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/Internal/StackTraceMetadata/StackTraceMetadata.cs b/src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/Internal/StackTraceMetadata/StackTraceMetadata.cs
index b845aaac54a3f0..b67e691bee2442 100644
--- a/src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/Internal/StackTraceMetadata/StackTraceMetadata.cs
+++ b/src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/Internal/StackTraceMetadata/StackTraceMetadata.cs
@@ -50,7 +50,7 @@ internal static void Initialize()
///
/// Locate the containing module for a method and try to resolve its name based on start address.
///
- public static unsafe string GetMethodNameFromStartAddressIfAvailable(IntPtr methodStartAddress, out string owningTypeName, out string genericArgs, out string methodSignature, out bool isStackTraceHidden, out int hashCodeForLineInfo)
+ public static unsafe string GetMethodNameFromStartAddressIfAvailable(IntPtr methodStartAddress, out string owningTypeName, out string genericArgs, out string methodSignature, out bool isStackTraceHidden, out bool isAsync, out int hashCodeForLineInfo)
{
IntPtr moduleStartAddress = RuntimeAugments.GetOSModuleFromPointer(methodStartAddress);
int rva = (int)((byte*)methodStartAddress - (byte*)moduleStartAddress);
@@ -62,6 +62,7 @@ public static unsafe string GetMethodNameFromStartAddressIfAvailable(IntPtr meth
if (resolver.TryGetStackTraceData(rva, out var data))
{
isStackTraceHidden = data.IsHidden;
+ isAsync = data.IsAsync;
if (data.OwningType.IsNil)
{
Debug.Assert(data.Name.IsNil && data.Signature.IsNil);
@@ -80,6 +81,7 @@ public static unsafe string GetMethodNameFromStartAddressIfAvailable(IntPtr meth
}
isStackTraceHidden = false;
+ isAsync = false;
// We haven't found information in the stack trace metadata tables, but maybe reflection will have this
if (ReflectionExecution.TryGetMethodMetadataFromStartAddress(methodStartAddress,
@@ -331,9 +333,9 @@ public override DiagnosticMethodInfo TryGetDiagnosticMethodInfoFromStartAddress(
return GetDiagnosticMethodInfoFromStartAddressIfAvailable(methodStartAddress);
}
- public override string TryGetMethodStackFrameInfo(IntPtr methodStartAddress, int offset, bool needsFileInfo, out string owningType, out string genericArgs, out string methodSignature, out bool isStackTraceHidden, out string fileName, out int lineNumber)
+ public override string TryGetMethodStackFrameInfo(IntPtr methodStartAddress, int offset, bool needsFileInfo, out string owningType, out string genericArgs, out string methodSignature, out bool isStackTraceHidden, out bool isAsync, out string fileName, out int lineNumber)
{
- string methodName = GetMethodNameFromStartAddressIfAvailable(methodStartAddress, out owningType, out genericArgs, out methodSignature, out isStackTraceHidden, out int hashCode);
+ string methodName = GetMethodNameFromStartAddressIfAvailable(methodStartAddress, out owningType, out genericArgs, out methodSignature, out isStackTraceHidden, out isAsync, out int hashCode);
if (needsFileInfo)
{
@@ -474,6 +476,7 @@ private unsafe void PopulateRvaToTokenMap(TypeManagerHandle handle, byte* pMap,
{
Rva = methodRva,
IsHidden = (command & StackTraceDataCommand.IsStackTraceHidden) != 0,
+ IsAsync = (command & StackTraceDataCommand.IsAsync) != 0,
OwningType = currentOwningType,
Name = currentName,
Signature = currentSignature,
@@ -516,25 +519,36 @@ public bool TryGetStackTraceData(int rva, out StackTraceData data)
public struct StackTraceData : IComparable
{
private const int IsHiddenFlag = 0x2;
+ private const int IsAsyncFlag = 0x1;
+ private const int FlagsMask = IsHiddenFlag | IsAsyncFlag;
- private readonly int _rvaAndIsHiddenBit;
+ private readonly int _rvaAndFlags;
public int Rva
{
- get => _rvaAndIsHiddenBit & ~IsHiddenFlag;
+ get => _rvaAndFlags & ~FlagsMask;
init
{
- Debug.Assert((value & IsHiddenFlag) == 0);
- _rvaAndIsHiddenBit = value | (_rvaAndIsHiddenBit & IsHiddenFlag);
+ Debug.Assert((value & FlagsMask) == 0);
+ _rvaAndFlags = value | (_rvaAndFlags & FlagsMask);
}
}
public bool IsHidden
{
- get => (_rvaAndIsHiddenBit & IsHiddenFlag) != 0;
+ get => (_rvaAndFlags & IsHiddenFlag) != 0;
init
{
if (value)
- _rvaAndIsHiddenBit |= IsHiddenFlag;
+ _rvaAndFlags |= IsHiddenFlag;
+ }
+ }
+ public bool IsAsync
+ {
+ get => (_rvaAndFlags & IsAsyncFlag) != 0;
+ init
+ {
+ if (value)
+ _rvaAndFlags |= IsAsyncFlag;
}
}
public Handle OwningType { get; init; }
diff --git a/src/coreclr/tools/Common/Internal/Runtime/StackTraceData.cs b/src/coreclr/tools/Common/Internal/Runtime/StackTraceData.cs
index 46fb51450a9d81..ad344da31af07c 100644
--- a/src/coreclr/tools/Common/Internal/Runtime/StackTraceData.cs
+++ b/src/coreclr/tools/Common/Internal/Runtime/StackTraceData.cs
@@ -11,5 +11,6 @@ internal static class StackTraceDataCommand
public const byte UpdateGenericSignature = 0x08; // Just a shortcut - sig metadata has the info
public const byte IsStackTraceHidden = 0x10;
+ public const byte IsAsync = 0x20;
}
}
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/StackTraceMethodMappingNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/StackTraceMethodMappingNode.cs
index 2e94b1cddcf8a8..4b83c3f0cacc04 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/StackTraceMethodMappingNode.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/StackTraceMethodMappingNode.cs
@@ -124,6 +124,11 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
command |= StackTraceDataCommand.IsStackTraceHidden;
}
+ if ((entry.Flags & StackTraceRecordFlags.IsAsync) != 0)
+ {
+ command |= StackTraceDataCommand.IsAsync;
+ }
+
objData.EmitByte(commandReservation, command);
objData.EmitReloc(factory.MethodEntrypoint(entry.Method), RelocType.IMAGE_REL_BASED_RELPTR32);
}
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs
index 6d99ee84ba317c..ac310a8a51e583 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs
@@ -780,6 +780,8 @@ protected void ComputeMetadata(
flags |= StackTraceRecordFlags.IsHidden;
if ((stackVisibility & MethodStackTraceVisibilityFlags.HasLineNumbers) != 0)
flags |= StackTraceRecordFlags.HasLineNumbers;
+ if ((stackVisibility & MethodStackTraceVisibilityFlags.IsAsync) != 0)
+ flags |= StackTraceRecordFlags.IsAsync;
if ((stackVisibility & MethodStackTraceVisibilityFlags.HasMetadata) != 0)
{
@@ -1351,6 +1353,7 @@ public enum StackTraceRecordFlags
None = 0,
IsHidden = 1,
HasLineNumbers = 2,
+ IsAsync = 4,
}
public readonly struct StackTraceRecordData
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/StackTraceEmissionPolicy.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/StackTraceEmissionPolicy.cs
index 67576210ec50ab..78e74b35d9c2bd 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/StackTraceEmissionPolicy.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/StackTraceEmissionPolicy.cs
@@ -29,6 +29,8 @@ public override MethodStackTraceVisibilityFlags GetMethodVisibility(MethodDesc m
public class EcmaMethodStackTraceEmissionPolicy : StackTraceEmissionPolicy
{
private readonly MethodStackTraceVisibilityFlags _flags;
+ private MetadataType _iAsyncStateMachineType;
+ private bool _iAsyncStateMachineTypeComputed;
public EcmaMethodStackTraceEmissionPolicy(bool includeLineNumbers)
{
@@ -46,10 +48,54 @@ public override MethodStackTraceVisibilityFlags GetMethodVisibility(MethodDesc m
result |= MethodStackTraceVisibilityFlags.IsHidden;
}
+ if (IsAsyncFrame(method))
+ {
+ result |= MethodStackTraceVisibilityFlags.IsAsync;
+ }
+
return (method.GetTypicalMethodDefinition() is Internal.TypeSystem.Ecma.EcmaMethod || (method.IsAsync && method.IsAsyncCall()))
? result | MethodStackTraceVisibilityFlags.HasMetadata
: result;
}
+
+ // Determines whether a frame for this method should be treated as "async" when formatting a
+ // stack trace. Async frames suppress the "--- End of stack trace from previous location ---"
+ // delimiter. This covers both runtime-async (V2) methods and the MoveNext method of a
+ // compiler-generated (V1) async state machine.
+ private bool IsAsyncFrame(MethodDesc method)
+ {
+ if (method.IsAsync)
+ {
+ return true;
+ }
+
+ // Async state machines only expose their exception-throwing code through IAsyncStateMachine.MoveNext.
+ if (method.Name != "MoveNext"u8)
+ {
+ return false;
+ }
+
+ if (!_iAsyncStateMachineTypeComputed)
+ {
+ _iAsyncStateMachineType = method.Context.SystemModule.GetType("System.Runtime.CompilerServices"u8, "IAsyncStateMachine"u8, throwIfNotFound: false);
+ _iAsyncStateMachineTypeComputed = true;
+ }
+
+ if (_iAsyncStateMachineType == null)
+ {
+ return false;
+ }
+
+ foreach (DefType interfaceType in method.OwningType.RuntimeInterfaces)
+ {
+ if (interfaceType == _iAsyncStateMachineType)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
}
[Flags]
@@ -58,5 +104,6 @@ public enum MethodStackTraceVisibilityFlags
HasMetadata = 0x1,
IsHidden = 0x2,
HasLineNumbers = 0x4,
+ IsAsync = 0x8,
}
}
diff --git a/src/libraries/System.Diagnostics.StackTrace/tests/StackTraceTests.cs b/src/libraries/System.Diagnostics.StackTrace/tests/StackTraceTests.cs
index 687392cf998a58..08c97fe5e1ca87 100644
--- a/src/libraries/System.Diagnostics.StackTrace/tests/StackTraceTests.cs
+++ b/src/libraries/System.Diagnostics.StackTrace/tests/StackTraceTests.cs
@@ -709,11 +709,7 @@ public async Task ToString_Async(Func asyncMethod, string[] expectedPatter
startIndex = match.Index + match.Length;
}
- // [ActiveIssue("https://github.com/dotnet/runtime/issues/129155", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))]
- if (!PlatformDetection.IsNativeAot)
- {
- Assert.DoesNotContain("--- End of stack trace from previous location ---", exceptionText);
- }
+ Assert.DoesNotContain("--- End of stack trace from previous location ---", exceptionText);
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]