diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml index 0e5e5abf785801..5919536924219a 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml @@ -837,4 +837,10 @@ CP0002 M:System.Threading.Lock.#ctor(System.Boolean) - + + CP0002 + M:System.Diagnostics.DiagnosticMethodInfo.#ctor(System.String,System.String,System.String) + ref/net9.0/System.Private.CoreLib.dll + lib/net9.0/System.Private.CoreLib.dll + + \ No newline at end of file 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 655c460352de3e..ff3ab06b73c550 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 @@ -2,8 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; - -using Internal.Runtime.CompilerServices; +using System.Diagnostics; namespace Internal.Runtime.Augments { @@ -26,5 +25,7 @@ public abstract class StackTraceMetadataCallbacks /// Returns a value indicating whether the method should be hidden in stack traces /// Formatted method name or null if metadata for the method is not available public abstract string TryGetMethodNameFromStartAddress(IntPtr methodStartAddress, out bool isStackTraceHidden); + + public abstract DiagnosticMethodInfo TryGetDiagnosticMethodInfoFromStartAddress(IntPtr methodStartAddress); } } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj index 78a4e56c54976c..bea161cffbaa0c 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -131,6 +131,7 @@ + diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Delegate.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Delegate.cs index bc8c517acb8ea9..05e3859bee90a1 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Delegate.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Delegate.cs @@ -12,6 +12,7 @@ using Internal.Reflection.Augments; using Internal.Runtime; +using Internal.Runtime.Augments; using Internal.Runtime.CompilerServices; namespace System @@ -254,6 +255,8 @@ private IntPtr GetActualTargetFunctionPointer(object thisObject) protected virtual MethodInfo GetMethodImpl() { + // NOTE: this implementation is mirrored in GetDiagnosticMethodInfo below + // Multi-cast delegates return the Method of the last delegate in the list if (_helperObject is Wrapper[] invocationList) { @@ -270,6 +273,52 @@ protected virtual MethodInfo GetMethodImpl() return ReflectionAugments.ReflectionCoreCallbacks.GetDelegateMethod(this); } + internal DiagnosticMethodInfo GetDiagnosticMethodInfo() + { + // NOTE: this implementation is mirrored in GetMethodImpl above + + // Multi-cast delegates return the diagnostic method info of the last delegate in the list + if (_helperObject is Wrapper[] invocationList) + { + int invocationCount = (int)_extraFunctionPointerOrData; + return invocationList[invocationCount - 1].Value.GetDiagnosticMethodInfo(); + } + + // Return the delegate Invoke method for marshalled function pointers and LINQ expressions + if ((_firstParameter is NativeFunctionPointerWrapper) || (_functionPointer == GetThunk(ObjectArrayThunk))) + { + Type t = GetType(); + return new DiagnosticMethodInfo("Invoke", t.FullName, t.Module.Assembly.FullName); + } + + IntPtr ldftnResult = GetDelegateLdFtnResult(out RuntimeTypeHandle _, out bool isOpenResolver); + if (isOpenResolver) + { + MethodInfo mi = ReflectionAugments.ReflectionCoreCallbacks.GetDelegateMethod(this); + Type? declaringType = mi.DeclaringType; + if (declaringType.IsConstructedGenericType) + declaringType = declaringType.GetGenericTypeDefinition(); + return new DiagnosticMethodInfo(mi.Name, declaringType.FullName, mi.Module.Assembly.FullName); + } + else + { + IntPtr functionPointer; + if (FunctionPointerOps.IsGenericMethodPointer(ldftnResult)) + { + unsafe + { + GenericMethodDescriptor* realTargetData = FunctionPointerOps.ConvertToGenericDescriptor(ldftnResult); + functionPointer = RuntimeAugments.GetCodeTarget(realTargetData->MethodFunctionPointer); + } + } + else + { + functionPointer = ldftnResult; + } + return RuntimeAugments.StackTraceCallbacksIfAvailable?.TryGetDiagnosticMethodInfoFromStartAddress(functionPointer); + } + } + public object? Target { get diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Diagnostics/DiagnosticMethodInfo.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Diagnostics/DiagnosticMethodInfo.NativeAot.cs new file mode 100644 index 00000000000000..b67fa05ba2f500 --- /dev/null +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Diagnostics/DiagnosticMethodInfo.NativeAot.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Internal.Runtime.Augments; + +namespace System.Diagnostics +{ + public sealed partial class DiagnosticMethodInfo + { + // Public for System.Private.StackTraceMetadata sake + public DiagnosticMethodInfo(string name, string declaringTypeName, string declaringAssemblyName) + => (Name, DeclaringTypeName, DeclaringAssemblyName) = (name, declaringTypeName, declaringAssemblyName); + + public string Name { get; } + + public string? DeclaringTypeName { get; } + + public string? DeclaringAssemblyName { get; } + + public static DiagnosticMethodInfo? Create(Delegate @delegate) + { + ArgumentNullException.ThrowIfNull(@delegate); + return @delegate.GetDiagnosticMethodInfo(); + } + + public static DiagnosticMethodInfo? Create(StackFrame frame) + { + ArgumentNullException.ThrowIfNull(frame); + return frame.TryGetMethodStartAddress(out IntPtr startAddress) + ? RuntimeAugments.StackTraceCallbacksIfAvailable?.TryGetDiagnosticMethodInfoFromStartAddress(startAddress) + : null; + } + } +} 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 f061cf1d461d71..ed17c7eb72d9ad 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 @@ -46,6 +46,19 @@ public partial class StackFrame return _method; } + internal bool TryGetMethodStartAddress(out IntPtr startAddress) + { + if (_ipAddress == IntPtr.Zero || _ipAddress == Exception.EdiSeparator) + { + startAddress = IntPtr.Zero; + return false; + } + + startAddress = _ipAddress - _nativeOffset; + Debug.Assert(RuntimeImports.RhFindMethodStartAddress(_ipAddress) == startAddress); + return true; + } + private bool TryInitializeMethodBase() { if (_noMethodBaseAvailable || _ipAddress == IntPtr.Zero || _ipAddress == Exception.EdiSeparator) diff --git a/src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/Internal/StackTraceMetadata/MethodNameFormatter.cs b/src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/Internal/StackTraceMetadata/MethodNameFormatter.cs index 5e84594fc62975..4b57c36fa4930a 100644 --- a/src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/Internal/StackTraceMetadata/MethodNameFormatter.cs +++ b/src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/Internal/StackTraceMetadata/MethodNameFormatter.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Diagnostics; using System.Text; @@ -10,6 +11,14 @@ namespace Internal.StackTraceMetadata { internal class MethodNameFormatter { + [Flags] + private enum Flags + { + None = 0, + NamespaceQualify = 1, + ReflectionFormat = 2, + } + /// /// Metadata reader used for the purpose of method name formatting. /// @@ -35,10 +44,17 @@ private MethodNameFormatter(MetadataReader metadataReader, SigTypeContext typeCo _typeContext = typeContext; } + public static string FormatReflectionNotationTypeName(MetadataReader metadataReader, Handle type) + { + MethodNameFormatter formatter = new MethodNameFormatter(metadataReader, default); + formatter.EmitTypeName(type, Flags.NamespaceQualify | Flags.ReflectionFormat); + return formatter._outputBuilder.ToString(); + } + public static string FormatMethodName(MetadataReader metadataReader, Handle owningType, ConstantStringValueHandle name, MethodSignatureHandle signature, ConstantStringArrayHandle genericArguments) { MethodNameFormatter formatter = new MethodNameFormatter(metadataReader, SigTypeContext.FromMethod(metadataReader, owningType, genericArguments)); - formatter.EmitTypeName(owningType, namespaceQualified: true); + formatter.EmitTypeName(owningType, Flags.NamespaceQualify); formatter._outputBuilder.Append('.'); formatter.EmitString(name); @@ -75,7 +91,7 @@ public static string FormatMethodName(MetadataReader metadataReader, TypeDefinit MethodNameFormatter formatter = new MethodNameFormatter(metadataReader, SigTypeContext.FromMethod(metadataReader, enclosingTypeHandle, methodHandle)); Method method = metadataReader.GetMethod(methodHandle); - formatter.EmitTypeName(enclosingTypeHandle, namespaceQualified: true); + formatter.EmitTypeName(enclosingTypeHandle, Flags.NamespaceQualify); formatter._outputBuilder.Append('.'); formatter.EmitString(method.Name); @@ -91,7 +107,7 @@ public static string FormatMethodName(MetadataReader metadataReader, TypeDefinit { formatter._outputBuilder.Append(','); } - formatter.EmitTypeName(handle, namespaceQualified: false); + formatter.EmitTypeName(handle, Flags.None); } if (!first) { @@ -147,7 +163,7 @@ bool TryGetNextParameter(ref ParameterHandleCollection.Enumerator enumerator, ou _outputBuilder.Append(", "); } - EmitTypeName(type, namespaceQualified: false); + EmitTypeName(type, Flags.None); if (++typeIndex == parameter.Sequence && hasParameter) { @@ -182,37 +198,35 @@ private void EmitTypeVector(HandleCollection typeVector) { _outputBuilder.Append(", "); } - EmitTypeName(handle, namespaceQualified: false); + EmitTypeName(handle, Flags.None); } } /// /// Emit the name of a given type to the output string builder. /// - /// Type handle to format - /// When set to true, include namespace information - private void EmitTypeName(Handle typeHandle, bool namespaceQualified) + private void EmitTypeName(Handle typeHandle, Flags flags) { switch (typeHandle.HandleType) { case HandleType.TypeReference: - EmitTypeReferenceName(typeHandle.ToTypeReferenceHandle(_metadataReader), namespaceQualified); + EmitTypeReferenceName(typeHandle.ToTypeReferenceHandle(_metadataReader), flags); break; case HandleType.TypeSpecification: - EmitTypeSpecificationName(typeHandle.ToTypeSpecificationHandle(_metadataReader), namespaceQualified); + EmitTypeSpecificationName(typeHandle.ToTypeSpecificationHandle(_metadataReader), flags); break; case HandleType.TypeInstantiationSignature: - EmitTypeInstantiationName(typeHandle.ToTypeInstantiationSignatureHandle(_metadataReader), namespaceQualified); + EmitTypeInstantiationName(typeHandle.ToTypeInstantiationSignatureHandle(_metadataReader), flags); break; case HandleType.SZArraySignature: - EmitSZArrayTypeName(typeHandle.ToSZArraySignatureHandle(_metadataReader), namespaceQualified); + EmitSZArrayTypeName(typeHandle.ToSZArraySignatureHandle(_metadataReader), flags); break; case HandleType.ArraySignature: - EmitArrayTypeName(typeHandle.ToArraySignatureHandle(_metadataReader), namespaceQualified); + EmitArrayTypeName(typeHandle.ToArraySignatureHandle(_metadataReader), flags); break; case HandleType.PointerSignature: @@ -224,15 +238,15 @@ private void EmitTypeName(Handle typeHandle, bool namespaceQualified) break; case HandleType.TypeDefinition: - EmitTypeDefinitionName(typeHandle.ToTypeDefinitionHandle(_metadataReader), namespaceQualified); + EmitTypeDefinitionName(typeHandle.ToTypeDefinitionHandle(_metadataReader), flags); break; case HandleType.TypeVariableSignature: - EmitTypeName(_typeContext.GetTypeVariable(typeHandle.ToTypeVariableSignatureHandle(_metadataReader).GetTypeVariableSignature(_metadataReader).Number), namespaceQualified); + EmitTypeName(_typeContext.GetTypeVariable(typeHandle.ToTypeVariableSignatureHandle(_metadataReader).GetTypeVariableSignature(_metadataReader).Number), flags); break; case HandleType.MethodTypeVariableSignature: - EmitTypeName(_typeContext.GetMethodVariable(typeHandle.ToMethodTypeVariableSignatureHandle(_metadataReader).GetMethodTypeVariableSignature(_metadataReader).Number), namespaceQualified); + EmitTypeName(_typeContext.GetMethodVariable(typeHandle.ToMethodTypeVariableSignatureHandle(_metadataReader).GetMethodTypeVariableSignature(_metadataReader).Number), flags); break; case HandleType.GenericParameter: @@ -290,9 +304,7 @@ private void EmitNamespaceDefinitionName(NamespaceDefinitionHandle namespaceDefH /// /// Emit type reference. /// - /// Type reference handle - /// When set to true, include namespace information - private void EmitTypeReferenceName(TypeReferenceHandle typeRefHandle, bool namespaceQualified) + private void EmitTypeReferenceName(TypeReferenceHandle typeRefHandle, Flags flags) { TypeReference typeRef = _metadataReader.GetTypeReference(typeRefHandle); if (!typeRef.ParentNamespaceOrType.IsNil) @@ -300,10 +312,13 @@ private void EmitTypeReferenceName(TypeReferenceHandle typeRefHandle, bool names if (typeRef.ParentNamespaceOrType.HandleType != HandleType.NamespaceReference) { // Nested type - EmitTypeName(typeRef.ParentNamespaceOrType, namespaceQualified); - _outputBuilder.Append('.'); + EmitTypeName(typeRef.ParentNamespaceOrType, flags); + if ((flags & Flags.ReflectionFormat) != 0) + _outputBuilder.Append('+'); + else + _outputBuilder.Append('.'); } - else if (namespaceQualified) + else if ((flags & Flags.NamespaceQualify) != 0) { int charsWritten = _outputBuilder.Length; EmitNamespaceReferenceName(typeRef.ParentNamespaceOrType.ToNamespaceReferenceHandle(_metadataReader)); @@ -314,16 +329,19 @@ private void EmitTypeReferenceName(TypeReferenceHandle typeRefHandle, bool names EmitString(typeRef.TypeName); } - private void EmitTypeDefinitionName(TypeDefinitionHandle typeDefHandle, bool namespaceQualified) + private void EmitTypeDefinitionName(TypeDefinitionHandle typeDefHandle, Flags flags) { TypeDefinition typeDef = _metadataReader.GetTypeDefinition(typeDefHandle); if (!typeDef.EnclosingType.IsNil) { // Nested type - EmitTypeName(typeDef.EnclosingType, namespaceQualified); - _outputBuilder.Append('.'); + EmitTypeName(typeDef.EnclosingType, flags); + if ((flags & Flags.ReflectionFormat) != 0) + _outputBuilder.Append('+'); + else + _outputBuilder.Append('.'); } - else if (namespaceQualified) + else if ((flags & Flags.NamespaceQualify) != 0) { int charsWritten = _outputBuilder.Length; EmitNamespaceDefinitionName(typeDef.NamespaceDefinition); @@ -336,47 +354,39 @@ private void EmitTypeDefinitionName(TypeDefinitionHandle typeDefHandle, bool nam /// /// Emit an arbitrary type specification. /// - /// Type specification handle - /// When set to true, include namespace information - private void EmitTypeSpecificationName(TypeSpecificationHandle typeSpecHandle, bool namespaceQualified) + private void EmitTypeSpecificationName(TypeSpecificationHandle typeSpecHandle, Flags flags) { TypeSpecification typeSpec = _metadataReader.GetTypeSpecification(typeSpecHandle); - EmitTypeName(typeSpec.Signature, namespaceQualified); + EmitTypeName(typeSpec.Signature, flags); } /// /// Emit generic instantiation type. /// - /// Instantiated type specification signature handle - /// When set to true, include namespace information - private void EmitTypeInstantiationName(TypeInstantiationSignatureHandle typeInstHandle, bool namespaceQualified) + private void EmitTypeInstantiationName(TypeInstantiationSignatureHandle typeInstHandle, Flags flags) { // Stack trace metadata ignores the instantiation arguments of the type in the CLR TypeInstantiationSignature typeInst = _metadataReader.GetTypeInstantiationSignature(typeInstHandle); - EmitTypeName(typeInst.GenericType, namespaceQualified); + EmitTypeName(typeInst.GenericType, flags); } /// /// Emit SZArray (single-dimensional array with zero lower bound) type. /// - /// SZArray type specification signature handle - /// When set to true, include namespace information - private void EmitSZArrayTypeName(SZArraySignatureHandle szArraySigHandle, bool namespaceQualified) + private void EmitSZArrayTypeName(SZArraySignatureHandle szArraySigHandle, Flags flags) { SZArraySignature szArraySig = _metadataReader.GetSZArraySignature(szArraySigHandle); - EmitTypeName(szArraySig.ElementType, namespaceQualified); + EmitTypeName(szArraySig.ElementType, flags); _outputBuilder.Append("[]"); } /// /// Emit multi-dimensional array type. /// - /// Multi-dimensional array type specification signature handle - /// When set to true, include namespace information - private void EmitArrayTypeName(ArraySignatureHandle arraySigHandle, bool namespaceQualified) + private void EmitArrayTypeName(ArraySignatureHandle arraySigHandle, Flags flags) { ArraySignature arraySig = _metadataReader.GetArraySignature(arraySigHandle); - EmitTypeName(arraySig.ElementType, namespaceQualified); + EmitTypeName(arraySig.ElementType, flags); _outputBuilder.Append('['); if (arraySig.Rank > 1) { @@ -396,7 +406,7 @@ private void EmitArrayTypeName(ArraySignatureHandle arraySigHandle, bool namespa private void EmitPointerTypeName(PointerSignatureHandle pointerSigHandle) { PointerSignature pointerSig = _metadataReader.GetPointerSignature(pointerSigHandle); - EmitTypeName(pointerSig.Type, namespaceQualified: false); + EmitTypeName(pointerSig.Type, Flags.None); _outputBuilder.Append('*'); } @@ -415,7 +425,7 @@ private void EmitFunctionPointerTypeName() private void EmitByRefTypeName(ByReferenceSignatureHandle byRefSigHandle) { ByReferenceSignature byRefSig = _metadataReader.GetByReferenceSignature(byRefSigHandle); - EmitTypeName(byRefSig.Type, namespaceQualified: false); + EmitTypeName(byRefSig.Type, Flags.None); _outputBuilder.Append('&'); } 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 bed368cb664a44..15c65d5e9dd63c 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 @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Reflection.Runtime.General; using Internal.Metadata.NativeFormat; @@ -51,9 +52,18 @@ public static unsafe string GetMethodNameFromStartAddressIfAvailable(IntPtr meth { if (moduleInfo.Handle.OsModuleBase == moduleStartAddress) { - string name = _perModuleMethodNameResolverHashtable.GetOrCreateValue(moduleInfo.Handle.GetIntPtrUNSAFE()).GetMethodNameFromRvaIfAvailable(rva, out isStackTraceHidden); - if (name != null || isStackTraceHidden) - return name; + PerModuleMethodNameResolver resolver = _perModuleMethodNameResolverHashtable.GetOrCreateValue(moduleInfo.Handle.GetIntPtrUNSAFE()); + if (resolver.TryGetStackTraceData(rva, out var data)) + { + isStackTraceHidden = data.IsHidden; + if (data.OwningType.IsNil) + { + Debug.Assert(data.Name.IsNil && data.Signature.IsNil); + Debug.Assert(isStackTraceHidden); + return null; + } + return MethodNameFormatter.FormatMethodName(resolver.Reader, data.OwningType, data.Name, data.Signature, data.GenericArguments); + } } } @@ -89,6 +99,93 @@ public static unsafe string GetMethodNameFromStartAddressIfAvailable(IntPtr meth return null; } + public static unsafe DiagnosticMethodInfo? GetDiagnosticMethodInfoFromStartAddressIfAvailable(IntPtr methodStartAddress) + { + IntPtr moduleStartAddress = RuntimeAugments.GetOSModuleFromPointer(methodStartAddress); + int rva = (int)((byte*)methodStartAddress - (byte*)moduleStartAddress); + foreach (NativeFormatModuleInfo moduleInfo in ModuleList.EnumerateModules()) + { + if (moduleInfo.Handle.OsModuleBase == moduleStartAddress) + { + PerModuleMethodNameResolver resolver = _perModuleMethodNameResolverHashtable.GetOrCreateValue(moduleInfo.Handle.GetIntPtrUNSAFE()); + if (resolver.TryGetStackTraceData(rva, out var data)) + { + if (data.OwningType.IsNil) + { + Debug.Assert(data.Name.IsNil && data.Signature.IsNil); + return null; + } + return new DiagnosticMethodInfo( + resolver.Reader.GetString(data.Name), + MethodNameFormatter.FormatReflectionNotationTypeName(resolver.Reader, data.OwningType), + FormatAssemblyName(resolver.Reader, data.OwningType) + ); + } + } + } + + // We haven't found information in the stack trace metadata tables, but maybe reflection will have this + if (IsReflectionExecutionAvailable() && ReflectionExecution.TryGetMethodMetadataFromStartAddress(methodStartAddress, + out MetadataReader reader, + out TypeDefinitionHandle typeHandle, + out MethodHandle methodHandle)) + { + return new DiagnosticMethodInfo( + reader.GetString(reader.GetMethod(methodHandle).Name), + MethodNameFormatter.FormatReflectionNotationTypeName(reader, typeHandle), + FormatAssemblyName(reader, typeHandle) + ); + } + + return null; + } + + private static string FormatAssemblyName(MetadataReader reader, Handle handle) + { + switch (handle.HandleType) + { + case HandleType.TypeDefinition: + TypeDefinition typeDef = reader.GetTypeDefinition(handle.ToTypeDefinitionHandle(reader)); + TypeDefinitionHandle enclosingTypeDef = typeDef.EnclosingType; + if (!enclosingTypeDef.IsNil) + return FormatAssemblyName(reader, enclosingTypeDef); + return FormatAssemblyName(reader, typeDef.NamespaceDefinition); + case HandleType.TypeReference: + TypeReference typeRef = reader.GetTypeReference(handle.ToTypeReferenceHandle(reader)); + return FormatAssemblyName(reader, typeRef.ParentNamespaceOrType); + case HandleType.TypeSpecification: + TypeSpecification typeSpec = reader.GetTypeSpecification(handle.ToTypeSpecificationHandle(reader)); + return FormatAssemblyName(reader, typeSpec.Signature); + case HandleType.TypeInstantiationSignature: + TypeInstantiationSignature typeInst = reader.GetTypeInstantiationSignature(handle.ToTypeInstantiationSignatureHandle(reader)); + return FormatAssemblyName(reader, typeInst.GenericType); + case HandleType.NamespaceDefinition: + NamespaceDefinition nsDef = reader.GetNamespaceDefinition(handle.ToNamespaceDefinitionHandle(reader)); + return FormatAssemblyName(reader, nsDef.ParentScopeOrNamespace); + case HandleType.NamespaceReference: + NamespaceReference nsRef = reader.GetNamespaceReference(handle.ToNamespaceReferenceHandle(reader)); + return FormatAssemblyName(reader, nsRef.ParentScopeOrNamespace); + case HandleType.ScopeDefinition: + return FormatAssemblyName(reader, handle.ToScopeDefinitionHandle(reader)); + case HandleType.ScopeReference: + return FormatAssemblyName(reader, handle.ToScopeReferenceHandle(reader)); + default: + return ""; + } + } + + private static string FormatAssemblyName(MetadataReader reader, ScopeDefinitionHandle handle) + { + ScopeDefinition scopeDef = reader.GetScopeDefinition(handle); + return $"{reader.GetString(scopeDef.Name)}, Version={scopeDef.MajorVersion}.{scopeDef.MinorVersion}.{scopeDef.BuildNumber}.{scopeDef.RevisionNumber}"; + } + + private static string FormatAssemblyName(MetadataReader reader, ScopeReferenceHandle handle) + { + ScopeReference scopeRef = reader.GetScopeReference(handle); + return $"{reader.GetString(scopeRef.Name)}, Version={scopeRef.MajorVersion}.{scopeRef.MinorVersion}.{scopeRef.BuildNumber}.{scopeRef.RevisionNumber}"; + } + // Can be rewritten to false through a feature switch. private static bool IsReflectionExecutionAvailable() => true; @@ -148,6 +245,11 @@ protected override PerModuleMethodNameResolver CreateValueFromKey(IntPtr key) /// private sealed class StackTraceMetadataCallbacksImpl : StackTraceMetadataCallbacks { + public override DiagnosticMethodInfo TryGetDiagnosticMethodInfoFromStartAddress(nint methodStartAddress) + { + return GetDiagnosticMethodInfoFromStartAddressIfAvailable(methodStartAddress); + } + public override string TryGetMethodNameFromStartAddress(IntPtr methodStartAddress, out bool isStackTraceHidden) { return GetMethodNameFromStartAddressIfAvailable(methodStartAddress, out isStackTraceHidden); @@ -172,7 +274,7 @@ private sealed class PerModuleMethodNameResolver /// /// Metadata reader for the stack trace metadata. /// - private readonly MetadataReader _metadataReader; + public readonly MetadataReader Reader; /// /// Publicly exposed module address property. @@ -216,7 +318,7 @@ public unsafe PerModuleMethodNameResolver(IntPtr moduleAddress) out rvaToTokenMapBlob, out rvaToTokenMapBlobSize)) { - _metadataReader = new MetadataReader(new IntPtr(metadataBlob), (int)metadataBlobSize); + Reader = new MetadataReader(new IntPtr(metadataBlob), (int)metadataBlobSize); int entryCount = *(int*)rvaToTokenMapBlob; _stacktraceDatas = new StackTraceData[entryCount]; @@ -253,19 +355,19 @@ private unsafe void PopulateRvaToTokenMap(TypeManagerHandle handle, byte* pMap, if ((command & StackTraceDataCommand.UpdateName) != 0) { - currentName = new Handle(HandleType.ConstantStringValue, (int)NativePrimitiveDecoder.DecodeUnsigned(ref pCurrent)).ToConstantStringValueHandle(_metadataReader); + currentName = new Handle(HandleType.ConstantStringValue, (int)NativePrimitiveDecoder.DecodeUnsigned(ref pCurrent)).ToConstantStringValueHandle(Reader); } if ((command & StackTraceDataCommand.UpdateSignature) != 0) { - currentSignature = new Handle(HandleType.MethodSignature, (int)NativePrimitiveDecoder.DecodeUnsigned(ref pCurrent)).ToMethodSignatureHandle(_metadataReader); + currentSignature = new Handle(HandleType.MethodSignature, (int)NativePrimitiveDecoder.DecodeUnsigned(ref pCurrent)).ToMethodSignatureHandle(Reader); currentMethodInst = default; } if ((command & StackTraceDataCommand.UpdateGenericSignature) != 0) { - currentSignature = new Handle(HandleType.MethodSignature, (int)NativePrimitiveDecoder.DecodeUnsigned(ref pCurrent)).ToMethodSignatureHandle(_metadataReader); - currentMethodInst = new Handle(HandleType.ConstantStringArray, (int)NativePrimitiveDecoder.DecodeUnsigned(ref pCurrent)).ToConstantStringArrayHandle(_metadataReader); + currentSignature = new Handle(HandleType.MethodSignature, (int)NativePrimitiveDecoder.DecodeUnsigned(ref pCurrent)).ToMethodSignatureHandle(Reader); + currentMethodInst = new Handle(HandleType.ConstantStringArray, (int)NativePrimitiveDecoder.DecodeUnsigned(ref pCurrent)).ToConstantStringArrayHandle(Reader); } void* pMethod = ReadRelPtr32(pCurrent); @@ -296,38 +398,28 @@ private unsafe void PopulateRvaToTokenMap(TypeManagerHandle handle, byte* pMap, /// /// Try to resolve method name based on its address using the stack trace metadata /// - public string GetMethodNameFromRvaIfAvailable(int rva, out bool isStackTraceHidden) + public bool TryGetStackTraceData(int rva, out StackTraceData data) { - isStackTraceHidden = false; - if (_stacktraceDatas == null) { // No stack trace metadata for this module - return null; + data = default; + return false; } int index = Array.BinarySearch(_stacktraceDatas, new StackTraceData() { Rva = rva }); if (index < 0) { // Method RVA not found in the map - return null; - } - - StackTraceData data = _stacktraceDatas[index]; - - isStackTraceHidden = data.IsHidden; - - if (data.OwningType.IsNil) - { - Debug.Assert(data.Name.IsNil && data.Signature.IsNil); - Debug.Assert(isStackTraceHidden); - return null; + data = default; + return false; } - return MethodNameFormatter.FormatMethodName(_metadataReader, data.OwningType, data.Name, data.Signature, data.GenericArguments); + data = _stacktraceDatas[index]; + return true; } - private struct StackTraceData : IComparable + public struct StackTraceData : IComparable { private const int IsHiddenFlag = 0x2; diff --git a/src/libraries/System.Diagnostics.StackTrace/ref/System.Diagnostics.StackTrace.cs b/src/libraries/System.Diagnostics.StackTrace/ref/System.Diagnostics.StackTrace.cs index edfbd2534f4b04..c46c202e7ae146 100644 --- a/src/libraries/System.Diagnostics.StackTrace/ref/System.Diagnostics.StackTrace.cs +++ b/src/libraries/System.Diagnostics.StackTrace/ref/System.Diagnostics.StackTrace.cs @@ -159,6 +159,15 @@ public SymLanguageVendor() { } #endif // !BUILDING_CORELIB_REFERENCE namespace System.Diagnostics { + public sealed partial class DiagnosticMethodInfo + { + private DiagnosticMethodInfo() { } + public string Name { get { throw null; } } + public string DeclaringTypeName { get { throw null; } } + public string DeclaringAssemblyName { get { throw null; } } + public static DiagnosticMethodInfo? Create(System.Delegate @delegate) { throw null; } + public static DiagnosticMethodInfo? Create(System.Diagnostics.StackFrame frame) { throw null; } + } public partial class StackFrame { public const int OFFSET_UNKNOWN = -1; diff --git a/src/libraries/System.Diagnostics.StackTrace/tests/DiagnosticMethodInfoTests.cs b/src/libraries/System.Diagnostics.StackTrace/tests/DiagnosticMethodInfoTests.cs new file mode 100644 index 00000000000000..a25fcc0a8d6f9b --- /dev/null +++ b/src/libraries/System.Diagnostics.StackTrace/tests/DiagnosticMethodInfoTests.cs @@ -0,0 +1,323 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Metadata; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Microsoft.DotNet.RemoteExecutor; +using Xunit; + +namespace System.Diagnostics.Tests +{ + public class DiagnosticMethodInfoTests + { + [Fact] + public void Create_Null() + { + Assert.Throws(() => DiagnosticMethodInfo.Create((Delegate)null)); + Assert.Throws(() => DiagnosticMethodInfo.Create((StackFrame)null)); + } + + public static IEnumerable Create_OpenDelegate_TestData() + { + // Tracked at https://github.com/dotnet/runtime/issues/100748 + bool hasGvmOpenDelegateBug = !PlatformDetection.IsMonoRuntime && !PlatformDetection.IsNativeAot; + + const string TestNamespace = nameof(System) + "." + nameof(System.Diagnostics) + "." + nameof(System.Diagnostics.Tests) + "."; + + yield return new object[] { + typeof(IInterfaceForDiagnosticMethodInfoTests).GetMethod(nameof(IInterfaceForDiagnosticMethodInfoTests.NonGenericMethod)).CreateDelegate>(), + nameof(IInterfaceForDiagnosticMethodInfoTests.NonGenericMethod), + TestNamespace + nameof(IInterfaceForDiagnosticMethodInfoTests) + }; + + if (!hasGvmOpenDelegateBug) + { + yield return new object[] { + typeof(IInterfaceForDiagnosticMethodInfoTests).GetMethod(nameof(IInterfaceForDiagnosticMethodInfoTests.GenericMethod)).MakeGenericMethod(typeof(object)).CreateDelegate>(), + nameof(IInterfaceForDiagnosticMethodInfoTests.GenericMethod), + TestNamespace + nameof(IInterfaceForDiagnosticMethodInfoTests) + }; + } + + yield return new object[] { + typeof(IGenericInterfaceForDiagnosticMethodInfoTests).GetMethod(nameof(IGenericInterfaceForDiagnosticMethodInfoTests.NonGenericMethod)).CreateDelegate>>(), + nameof(IGenericInterfaceForDiagnosticMethodInfoTests.NonGenericMethod), + TestNamespace + nameof(IGenericInterfaceForDiagnosticMethodInfoTests) + "`1" + }; + + if (!hasGvmOpenDelegateBug) + { + yield return new object[] { + typeof(IGenericInterfaceForDiagnosticMethodInfoTests).GetMethod(nameof(IGenericInterfaceForDiagnosticMethodInfoTests.GenericMethod)).MakeGenericMethod(typeof(object)).CreateDelegate>>(), + nameof(IGenericInterfaceForDiagnosticMethodInfoTests.GenericMethod), + TestNamespace + nameof(IGenericInterfaceForDiagnosticMethodInfoTests) + "`1" + }; + } + + yield return new object[] { + typeof(StructForDiagnosticMethodInfoTests).GetMethod(nameof(StructForDiagnosticMethodInfoTests.NonGenericMethod)).CreateDelegate>(), + nameof(StructForDiagnosticMethodInfoTests.NonGenericMethod), + TestNamespace + nameof(StructForDiagnosticMethodInfoTests) + }; + + yield return new object[] { + typeof(StructForDiagnosticMethodInfoTests).GetMethod(nameof(StructForDiagnosticMethodInfoTests.GenericMethod)).MakeGenericMethod(typeof(object)).CreateDelegate>(), + nameof(StructForDiagnosticMethodInfoTests.GenericMethod), + TestNamespace + nameof(StructForDiagnosticMethodInfoTests) + }; + } + + [Theory] + [MemberData(nameof(Create_OpenDelegate_TestData))] + public void Create_OpenDelegate(Delegate del, string expectedName, string expectedTypeName) + { + DiagnosticMethodInfo dmi = DiagnosticMethodInfo.Create(del); + + Assert.Equal(expectedName, dmi.Name); + Assert.Equal(expectedTypeName, dmi.DeclaringTypeName); + AssertEqualAssemblyName(Assembly.GetExecutingAssembly().GetName(), dmi.DeclaringAssemblyName); + } + + public static IEnumerable Create_ClosedDelegate_TestData() + { + const string TestNamespace = nameof(System) + "." + nameof(System.Diagnostics) + "." + nameof(System.Diagnostics.Tests) + "."; + + IInterfaceForDiagnosticMethodInfoTests o = new ClassForDiagnosticMethodInfoTests(); + yield return new object[] { + (Action)o.NonGenericDefaultMethod, + nameof(IInterfaceForDiagnosticMethodInfoTests.NonGenericDefaultMethod), + TestNamespace + nameof(IInterfaceForDiagnosticMethodInfoTests) + }; + yield return new object[] { + (Action)o.GenericDefaultMethod, + nameof(IInterfaceForDiagnosticMethodInfoTests.GenericDefaultMethod), + TestNamespace + nameof(IInterfaceForDiagnosticMethodInfoTests) + }; + yield return new object[] { + (Action)o.NonGenericMethod, + TestNamespace + nameof(IInterfaceForDiagnosticMethodInfoTests) + "." + nameof(IInterfaceForDiagnosticMethodInfoTests.NonGenericMethod), + TestNamespace + nameof(ClassForDiagnosticMethodInfoTests) + }; + yield return new object[] { + (Action)o.GenericMethod, + TestNamespace + nameof(IInterfaceForDiagnosticMethodInfoTests) + "." + nameof(IInterfaceForDiagnosticMethodInfoTests.GenericMethod), + TestNamespace + nameof(ClassForDiagnosticMethodInfoTests) + }; + + IGenericInterfaceForDiagnosticMethodInfoTests og = new GenericClassForDiagnosticMethodInfoTests(); + + // Making this work with CoreCLR tracked in https://github.com/dotnet/runtime/issues/103268 + if (PlatformDetection.IsMonoRuntime || PlatformDetection.IsNativeAot) + { + // Making this work with native AOT tracked in https://github.com/dotnet/runtime/issues/103219 + if (!PlatformDetection.IsNativeAot) + { + yield return new object[] { + (Action)og.NonGenericDefaultMethod, + nameof(IGenericInterfaceForDiagnosticMethodInfoTests.NonGenericDefaultMethod) , + TestNamespace + nameof(IGenericInterfaceForDiagnosticMethodInfoTests) + "`1" + }; + } + yield return new object[] { + (Action)og.GenericDefaultMethod, + nameof(IGenericInterfaceForDiagnosticMethodInfoTests.GenericDefaultMethod), + TestNamespace + nameof(IGenericInterfaceForDiagnosticMethodInfoTests) + "`1" + }; + } + yield return new object[] { + (Action)og.NonGenericMethod, + TestNamespace + nameof(IGenericInterfaceForDiagnosticMethodInfoTests) + "." + nameof(IGenericInterfaceForDiagnosticMethodInfoTests.NonGenericMethod), + TestNamespace + nameof(GenericClassForDiagnosticMethodInfoTests) + "`1" + }; + yield return new object[] { + (Action)og.GenericMethod, + TestNamespace + nameof(IGenericInterfaceForDiagnosticMethodInfoTests) + "." + nameof(IGenericInterfaceForDiagnosticMethodInfoTests.GenericMethod), + TestNamespace + nameof(GenericClassForDiagnosticMethodInfoTests) + "`1" + }; + + StructForDiagnosticMethodInfoTests s = default; + yield return new object[] { + (Action)s.NonGenericMethod, + nameof(StructForDiagnosticMethodInfoTests.NonGenericMethod), + TestNamespace + nameof(StructForDiagnosticMethodInfoTests) + }; + yield return new object[] { + (Action)s.GenericMethod, + nameof(StructForDiagnosticMethodInfoTests.GenericMethod), + TestNamespace + nameof(StructForDiagnosticMethodInfoTests) + }; + } + + [Theory] + [MemberData(nameof(Create_ClosedDelegate_TestData))] + public void Create_ClosedDelegate(Delegate del, string expectedName, string expectedTypeName) + { + DiagnosticMethodInfo dmi = DiagnosticMethodInfo.Create(del); + + Assert.Equal(expectedName, dmi.Name); + Assert.Equal(expectedTypeName, dmi.DeclaringTypeName); + AssertEqualAssemblyName(Assembly.GetExecutingAssembly().GetName(), dmi.DeclaringAssemblyName); + } + + private static void AssertEqualAssemblyName(AssemblyName aname, string s) + { + var ani = AssemblyNameInfo.Parse(s); + Assert.Equal(aname.Name, ani.Name); + Assert.Equal(aname.Version, ani.Version); + } + + [Fact] + public void Create_MulticastDelegate() + { + var c = new ClassForDiagnosticMethodInfoTests(); + Action a1 = c.Method1; + Action a2 = c.Method2; + + Action a = a1 + a2; + + DiagnosticMethodInfo dmi = DiagnosticMethodInfo.Create(a); + Assert.Equal(nameof(ClassForDiagnosticMethodInfoTests.Method2), dmi.Name); + } + + [Fact] + [SkipOnMono("needs triage") /* Same as https://github.com/dotnet/runtime/blob/0686ce61ed1e1cb3cb420281a0154efa5d0d00d5/src/tests/Interop/MarshalAPI/FunctionPointer/FunctionPointer.cs#L9 */] + public unsafe void Create_MarshalledPointer() + { + void* pMem = NativeMemory.Alloc(1); + Action del = Marshal.GetDelegateForFunctionPointer((nint)pMem); + NativeMemory.Free(pMem); + + DiagnosticMethodInfo dmi = DiagnosticMethodInfo.Create(del); + Assert.Equal(nameof(Action.Invoke), dmi.Name); + Assert.Equal(nameof(System) + "." + nameof(Action), dmi.DeclaringTypeName); + } + + [Fact] + public unsafe void Create_StackFrame() + { + StackTrace tr = NonGenericStackTraceClass.TestNonGeneric(); + + Verify(tr.GetFrame(0), "Test", "System.Diagnostics.Tests.GenericStackTraceClass`1+Nested"); + Verify(tr.GetFrame(1), "TestGeneric", "System.Diagnostics.Tests.GenericStackTraceClass`1"); + Verify(tr.GetFrame(2), "TestNonGeneric", "System.Diagnostics.Tests.GenericStackTraceClass`1"); + Verify(tr.GetFrame(3), "TestGeneric", "System.Diagnostics.Tests.NonGenericStackTraceClass"); + Verify(tr.GetFrame(4), "TestNonGeneric", "System.Diagnostics.Tests.NonGenericStackTraceClass"); + + static void Verify(StackFrame fr, string expectedName, string expectedDeclaringName) + { + DiagnosticMethodInfo dmi = DiagnosticMethodInfo.Create(fr); + Assert.Equal(expectedName, dmi.Name); + Assert.Equal(expectedDeclaringName, dmi.DeclaringTypeName); + } + } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void Create_Delegate_StackTraceSupportDisabled() + { + var options = new RemoteInvokeOptions() + { + RuntimeConfigurationOptions = + { + ["System.Diagnostics.StackTrace.IsSupported"] = false + } + }; + + RemoteExecutor.Invoke(static () => + { + var c = new ClassForDiagnosticMethodInfoTests(); + Action a = c.Method1; + + DiagnosticMethodInfo dmi = DiagnosticMethodInfo.Create(a); + Assert.Null(dmi); + }, options).Dispose(); + } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void Create_Frame_StackTraceSupportDisabled() + { + var options = new RemoteInvokeOptions() + { + RuntimeConfigurationOptions = + { + ["System.Diagnostics.StackTrace.IsSupported"] = false + } + }; + + RemoteExecutor.Invoke(static () => + { + StackFrame f = new StackFrame(); + DiagnosticMethodInfo dmi = DiagnosticMethodInfo.Create(f); + Assert.Null(dmi); + }, options).Dispose(); + } + } + + interface IInterfaceForDiagnosticMethodInfoTests + { + void NonGenericMethod(); + void GenericMethod(); + + void NonGenericDefaultMethod() { } + void GenericDefaultMethod() { } + } + + class ClassForDiagnosticMethodInfoTests : IInterfaceForDiagnosticMethodInfoTests + { + void IInterfaceForDiagnosticMethodInfoTests.GenericMethod() => throw new NotImplementedException(); + void IInterfaceForDiagnosticMethodInfoTests.NonGenericMethod() => throw new NotImplementedException(); + public void Method1() { } + public void Method2() { } + } + + interface IGenericInterfaceForDiagnosticMethodInfoTests + { + void NonGenericMethod(); + void GenericMethod(); + + void NonGenericDefaultMethod() { } + void GenericDefaultMethod() { } + } + + class GenericClassForDiagnosticMethodInfoTests : IGenericInterfaceForDiagnosticMethodInfoTests + { + void IGenericInterfaceForDiagnosticMethodInfoTests.GenericMethod() => throw new NotImplementedException(); + void IGenericInterfaceForDiagnosticMethodInfoTests.NonGenericMethod() => throw new NotImplementedException(); + public void Method1() { } + public void Method2() { } + } + + struct StructForDiagnosticMethodInfoTests : IInterfaceForDiagnosticMethodInfoTests + { + public void NonGenericMethod() { } + public void GenericMethod() { } + } + + delegate void RefAction(ref T t); + + class NonGenericStackTraceClass + { + [MethodImpl(MethodImplOptions.NoInlining)] + public static StackTrace TestNonGeneric() => TestGeneric(); + + [MethodImpl(MethodImplOptions.NoInlining)] + public static StackTrace TestGeneric() => GenericStackTraceClass.TestNonGeneric(); + } + + class GenericStackTraceClass + { + [MethodImpl(MethodImplOptions.NoInlining)] + public static StackTrace TestNonGeneric() => TestGeneric(); + + [MethodImpl(MethodImplOptions.NoInlining)] + public static StackTrace TestGeneric() => Nested.Test(); + + public class Nested + { + [MethodImpl(MethodImplOptions.NoInlining)] + public static StackTrace Test() => new StackTrace(); + } + } +} diff --git a/src/libraries/System.Diagnostics.StackTrace/tests/System.Diagnostics.StackTrace.Tests.csproj b/src/libraries/System.Diagnostics.StackTrace/tests/System.Diagnostics.StackTrace.Tests.csproj index d32645ab4b2dda..87f7d5be40d428 100644 --- a/src/libraries/System.Diagnostics.StackTrace/tests/System.Diagnostics.StackTrace.Tests.csproj +++ b/src/libraries/System.Diagnostics.StackTrace/tests/System.Diagnostics.StackTrace.Tests.csproj @@ -9,6 +9,7 @@ true + diff --git a/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.Shared.xml b/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.Shared.xml index 5561632ceaaaa7..4ddeabcb227f11 100644 --- a/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.Shared.xml +++ b/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.Shared.xml @@ -7,6 +7,9 @@ + + + diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 3ec13eb3791f86..90ae446c5b9dc6 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -292,6 +292,7 @@ + @@ -1244,6 +1245,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/DiagnosticMethodInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/DiagnosticMethodInfo.cs new file mode 100644 index 00000000000000..85a9aab53bb204 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/DiagnosticMethodInfo.cs @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using System.Reflection; + +namespace System.Diagnostics +{ + /// + /// Represents diagnostic information about a method. Information provided by this class is similar to information + /// provided by but it's meant for logging and tracing purposes. + /// + public sealed partial class DiagnosticMethodInfo + { +#if !NATIVEAOT + private readonly MethodBase _method; + + private DiagnosticMethodInfo(MethodBase method) => _method = method; + + /// + /// Gets the name of the method. + /// + /// Only the simple name of the method is returned, without information about generic parameters or arity. + public string Name => _method.Name; + + /// + /// Gets the fully qualified name of the type that owns this method, including its namespace but not its assembly. + /// + public string? DeclaringTypeName + { + get + { + Type? declaringType = _method.DeclaringType; + if (declaringType is { IsConstructedGenericType: true }) + declaringType = declaringType.GetGenericTypeDefinition(); + return declaringType?.FullName; + } + } + + /// + /// Gets the display name of the assembly that owns this method. + /// + public string? DeclaringAssemblyName => _method.Module.Assembly.FullName; + + /// + /// Creates a that represents the target of the delegate. + /// + /// This returns the definition of the target method, with stripped instantiation information. + /// The return value might be null if the `StackTraceSupport` feature switch is set to false. + public static DiagnosticMethodInfo? Create(Delegate @delegate) + { + ArgumentNullException.ThrowIfNull(@delegate); + + if (!StackTrace.IsSupported) + return null; + + return new DiagnosticMethodInfo(@delegate.Method); + + } + + /// + /// Creates a that represents the method this stack frame is associtated with. + /// + /// This returns the definition of the target method, with stripped instantiation information. + /// The return value might be null if the `StackTraceSupport` feature switch is set to false. + /// The return value might be null if the target method is unknown. + [UnconditionalSuppressMessage("Trimming", "IL2026", + Justification = "IL-level trimming doesn't remove method name and owning type information; this implementation is not used with native AOT trimming")] + public static DiagnosticMethodInfo? Create(StackFrame frame) + { + ArgumentNullException.ThrowIfNull(frame); + + if (!StackTrace.IsSupported) + return null; + + MethodBase? method = frame.GetMethod(); + if (method != null) + return new DiagnosticMethodInfo(method); + + return null; + } +#endif + } +} 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 e71e88411f04a8..5c5f8d374cc23b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs @@ -17,6 +17,11 @@ namespace System.Diagnostics /// public partial class StackTrace { + internal static bool IsSupported { get; } = InitializeIsSupported(); + + private static bool InitializeIsSupported() => + AppContext.TryGetSwitch("System.Diagnostics.StackTrace.IsSupported", out bool isSupported) ? isSupported : true; + public const int METHODS_TO_SKIP = 0; private int _numOfFrames; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/FutureFactory.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/FutureFactory.cs index 1f6fb5f62337ef..088f359877badb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/FutureFactory.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/FutureFactory.cs @@ -765,7 +765,7 @@ internal static Task FromAsyncImpl(Func promise = new Task(state, creationOptions); if (TplEventSource.Log.IsEnabled()) - TplEventSource.Log.TraceOperationBegin(promise.Id, "TaskFactory.FromAsync: " + beginMethod.Method.Name, 0); + TplEventSource.Log.TraceOperationBegin(promise.Id, "TaskFactory.FromAsync: " + beginMethod.GetMethodName(), 0); if (Task.s_asyncDebuggingEnabled) Task.AddToActiveTasks(promise); @@ -882,7 +882,7 @@ internal static Task FromAsyncImpl(Func promise = new Task(state, creationOptions); if (TplEventSource.Log.IsEnabled()) - TplEventSource.Log.TraceOperationBegin(promise.Id, "TaskFactory.FromAsync: " + beginMethod.Method.Name, 0); + TplEventSource.Log.TraceOperationBegin(promise.Id, "TaskFactory.FromAsync: " + beginMethod.GetMethodName(), 0); if (Task.s_asyncDebuggingEnabled) Task.AddToActiveTasks(promise); @@ -1007,7 +1007,7 @@ internal static Task FromAsyncImpl(Func promise = new Task(state, creationOptions); if (TplEventSource.Log.IsEnabled()) - TplEventSource.Log.TraceOperationBegin(promise.Id, "TaskFactory.FromAsync: " + beginMethod.Method.Name, 0); + TplEventSource.Log.TraceOperationBegin(promise.Id, "TaskFactory.FromAsync: " + beginMethod.GetMethodName(), 0); if (Task.s_asyncDebuggingEnabled) Task.AddToActiveTasks(promise); @@ -1140,7 +1140,7 @@ internal static Task FromAsyncImpl(Func promise = new Task(state, creationOptions); if (TplEventSource.Log.IsEnabled()) - TplEventSource.Log.TraceOperationBegin(promise.Id, "TaskFactory.FromAsync: " + beginMethod.Method.Name, 0); + TplEventSource.Log.TraceOperationBegin(promise.Id, "TaskFactory.FromAsync: " + beginMethod.GetMethodName(), 0); if (Task.s_asyncDebuggingEnabled) Task.AddToActiveTasks(promise); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/LoggingExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/LoggingExtensions.cs new file mode 100644 index 00000000000000..0e30ae67096c2c --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/LoggingExtensions.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; + +namespace System.Threading.Tasks +{ + internal static class LoggingExtensions + { + public static string GetMethodName(this Delegate @delegate) + { + DiagnosticMethodInfo? dmi = DiagnosticMethodInfo.Create(@delegate); + return dmi?.Name ?? ""; + } + } +} 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 4bf84b92a64d44..ccc2e3d021ffc4 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 @@ -1681,7 +1681,7 @@ internal void ScheduleAndStart(bool needsProtection) { // For all other task than TaskContinuations we want to log. TaskContinuations log in their constructor Debug.Assert(m_action != null, "Must have a delegate to be in ScheduleAndStart"); - TplEventSource.Log.TraceOperationBegin(this.Id, "Task: " + m_action.Method.Name, 0); + TplEventSource.Log.TraceOperationBegin(this.Id, "Task: " + m_action.GetMethodName(), 0); } try diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskContinuation.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskContinuation.cs index 277a6e90765eda..fac0bb9d4b25e6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskContinuation.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskContinuation.cs @@ -277,7 +277,7 @@ internal ContinueWithTaskContinuation(Task task, TaskContinuationOptions options m_options = options; m_taskScheduler = scheduler; if (TplEventSource.Log.IsEnabled()) - TplEventSource.Log.TraceOperationBegin(m_task.Id, "Task.ContinueWith: " + task.m_action!.Method.Name, 0); + TplEventSource.Log.TraceOperationBegin(m_task.Id, "Task.ContinueWith: " + task.m_action!.GetMethodName(), 0); if (Task.s_asyncDebuggingEnabled) Task.AddToActiveTasks(m_task); diff --git a/src/tests/nativeaot/SmokeTests/StackTraceMetadata/StackTraceMetadata.cs b/src/tests/nativeaot/SmokeTests/StackTraceMetadata/StackTraceMetadata.cs index 19bff10ee7f489..cbbf114f4dd6fb 100644 --- a/src/tests/nativeaot/SmokeTests/StackTraceMetadata/StackTraceMetadata.cs +++ b/src/tests/nativeaot/SmokeTests/StackTraceMetadata/StackTraceMetadata.cs @@ -2,13 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics; using System.Runtime.CompilerServices; class Program { - [MethodImpl(MethodImplOptions.NoInlining)] static int Main() { + DiagnosticMethodInfoTests.Run(); + string stackTrace = Environment.StackTrace; Console.WriteLine(stackTrace); @@ -22,4 +24,62 @@ static int Main() return expected == actual ? 100 : 1; } + class DiagnosticMethodInfoTests + { + public static void Run() + { +#if STRIPPED + DiagnosticMethodInfo dmi = DiagnosticMethodInfo.Create(new StackFrame()); + if (dmi != null) + throw new Exception("Succeeded in creating DiagnosticMethodInfo despite no expectation"); +#else + DiagnosticMethodInfo dmi = DiagnosticMethodInfo.Create(new StackFrame()); + if (dmi == null) + throw new Exception("No DiagnosticMethodInfo despite no expectation"); + if (dmi.Name != nameof(Run)) + throw new Exception($"Name is {dmi.Name} from {dmi.DeclaringTypeName}"); + + StackTrace tr = NonGenericStackTraceClass.TestNonGeneric(); + + Verify(tr.GetFrame(0), "Test", "GenericStackTraceClass`1+Nested"); + Verify(tr.GetFrame(1), "TestGeneric", "GenericStackTraceClass`1"); + Verify(tr.GetFrame(2), "TestNonGeneric", "GenericStackTraceClass`1"); + Verify(tr.GetFrame(3), "TestGeneric", "NonGenericStackTraceClass"); + Verify(tr.GetFrame(4), "TestNonGeneric", "NonGenericStackTraceClass"); + + static void Verify(StackFrame fr, string expectedName, string expectedDeclaringName) + { + DiagnosticMethodInfo dmi = DiagnosticMethodInfo.Create(fr); + if (expectedName != dmi.Name) + throw new Exception($"{expectedName} != {dmi.Name}"); + if (!dmi.DeclaringTypeName.EndsWith(expectedDeclaringName)) + throw new Exception($"!{dmi.DeclaringTypeName}.EndsWith({expectedDeclaringName})"); + } +#endif + } + + class NonGenericStackTraceClass + { + [MethodImpl(MethodImplOptions.NoInlining)] + public static StackTrace TestNonGeneric() => TestGeneric(); + + [MethodImpl(MethodImplOptions.NoInlining)] + public static StackTrace TestGeneric() => GenericStackTraceClass.TestNonGeneric(); + } + + class GenericStackTraceClass + { + [MethodImpl(MethodImplOptions.NoInlining)] + public static StackTrace TestNonGeneric() => TestGeneric(); + + [MethodImpl(MethodImplOptions.NoInlining)] + public static StackTrace TestGeneric() => Nested.Test(); + + public class Nested + { + [MethodImpl(MethodImplOptions.NoInlining)] + public static StackTrace Test() => new StackTrace(); + } + } + } }