From aa5a37b88ba4de4aa70a325214ccce0a9cc7d7d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Thu, 14 Mar 2024 07:27:44 +0100 Subject: [PATCH] Improve native AOT compatibility In the end, someone will need to do the work to enable the AOT/trim/single file safety analyzers and [address all the warnings](https://devblogs.microsoft.com/dotnet/creating-aot-compatible-libraries/), but this is enough to have a golden path that makes `sample/StackTrace` project produce the same results under native AOT as under JIT. Necessary fixes: * `GetMethodBody` under AOT throws. This will not return anything useful under Mono with ILStrip either. The code simply needs to deal with it. * Runtime reflection stack doesn't guarantee referential equality of `MemberInfo`s (except for `TypeInfo`). This is also true on JIT-based runtimes, but there's even less caching on native AOT. Use operator `==`. * The reflection to read `TupleElementNamesAttribute` is trim-unfriendly and cannot be analyzed. Replace with no-reflection. * Some fallout from the previous point because the nullable annotations on the thing returned by reflection were wrong. --- .../EnhancedStackTrace.Frames.cs | 21 ++++++++++++++++--- .../Internal/ReflectionHelper.cs | 8 +++++-- .../ValueTupleResolvedParameter.cs | 4 ++-- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs b/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs index 033dcbe..092106f 100644 --- a/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs +++ b/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs @@ -191,7 +191,7 @@ public static ResolvedMethod GetMethodDisplayString(MethodBase originMethod) var value = field.GetValue(field); if (value is Delegate d && d.Target is not null) { - if (ReferenceEquals(d.Method, originMethod) && + if (d.Method == originMethod && d.Target.ToString() == originMethod.DeclaringType?.ToString()) { methodDisplayInfo.Name = field.Name; @@ -386,7 +386,16 @@ private static bool TryResolveSourceMethod(IEnumerable candidateMeth ordinal = null; foreach (var candidateMethod in candidateMethods) { - if (candidateMethod.GetMethodBody() is not { } methodBody) + MethodBody? methodBody = null; + try + { + methodBody = candidateMethod.GetMethodBody(); + } + catch + { + // Platforms like native AOT don't provide access to IL method bodies + } + if (methodBody == null) { continue; } @@ -605,9 +614,15 @@ private static ResolvedParameter GetParameter(ParameterInfo parameter) { var customAttribs = parameter.GetCustomAttributes(inherit: false); +#if NET45 var tupleNameAttribute = customAttribs.OfType().FirstOrDefault(a => a.IsTupleElementNameAttribute()); var tupleNames = tupleNameAttribute?.GetTransformerNames(); +#else + var tupleNameAttribute = customAttribs.OfType().FirstOrDefault(); + + var tupleNames = tupleNameAttribute?.TransformNames; +#endif if (tupleNames?.Count > 0) { @@ -628,7 +643,7 @@ private static ResolvedParameter GetParameter(ParameterInfo parameter) }; } - private static ResolvedParameter GetValueTupleParameter(IList tupleNames, string prefix, string? name, Type parameterType) + private static ResolvedParameter GetValueTupleParameter(IList tupleNames, string prefix, string? name, Type parameterType) { return new ValueTupleResolvedParameter(parameterType, tupleNames) { diff --git a/src/Ben.Demystifier/Internal/ReflectionHelper.cs b/src/Ben.Demystifier/Internal/ReflectionHelper.cs index 123dc85..21a5a9a 100644 --- a/src/Ben.Demystifier/Internal/ReflectionHelper.cs +++ b/src/Ben.Demystifier/Internal/ReflectionHelper.cs @@ -12,7 +12,9 @@ namespace System.Diagnostics.Internal /// public static class ReflectionHelper { +#if NET45 private static PropertyInfo? transformerNamesLazyPropertyInfo; +#endif /// /// Returns true if the is a value tuple type. @@ -22,6 +24,7 @@ public static bool IsValueTuple(this Type type) return type.Namespace == "System" && type.Name.Contains("ValueTuple`"); } +#if NET45 /// /// Returns true if the given is of type TupleElementNameAttribute. /// @@ -43,12 +46,12 @@ public static bool IsTupleElementNameAttribute(this Attribute attribute) /// To avoid compile-time dependency hell with System.ValueTuple, this method uses reflection /// instead of casting the attribute to a specific type. /// - public static IList? GetTransformerNames(this Attribute attribute) + public static IList? GetTransformerNames(this Attribute attribute) { Debug.Assert(attribute.IsTupleElementNameAttribute()); var propertyInfo = GetTransformNamesPropertyInfo(attribute.GetType()); - return propertyInfo?.GetValue(attribute) as IList; + return propertyInfo?.GetValue(attribute) as IList; } private static PropertyInfo? GetTransformNamesPropertyInfo(Type attributeType) @@ -58,5 +61,6 @@ public static bool IsTupleElementNameAttribute(this Attribute attribute) #pragma warning restore 8634 () => attributeType.GetProperty("TransformNames", BindingFlags.Instance | BindingFlags.Public)!); } +#endif } } diff --git a/src/Ben.Demystifier/ValueTupleResolvedParameter.cs b/src/Ben.Demystifier/ValueTupleResolvedParameter.cs index 7dacf13..e349f4f 100644 --- a/src/Ben.Demystifier/ValueTupleResolvedParameter.cs +++ b/src/Ben.Demystifier/ValueTupleResolvedParameter.cs @@ -9,9 +9,9 @@ namespace System.Diagnostics { public class ValueTupleResolvedParameter : ResolvedParameter { - public IList TupleNames { get; } + public IList TupleNames { get; } - public ValueTupleResolvedParameter(Type resolvedType, IList tupleNames) + public ValueTupleResolvedParameter(Type resolvedType, IList tupleNames) : base(resolvedType) => TupleNames = tupleNames;