Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Native AOT on .NET 7+ #2247

Closed
mattjohnsonpint opened this issue Mar 22, 2023 · 2 comments
Closed

Support Native AOT on .NET 7+ #2247

mattjohnsonpint opened this issue Mar 22, 2023 · 2 comments
Assignees
Labels
Feature New feature or request
Milestone

Comments

@mattjohnsonpint
Copy link
Contributor

mattjohnsonpint commented Mar 22, 2023

Problem Statement

.NET 7 introduced "Native AOT" deployments for Windows and Linux and .NET 8 adds Native AOT support for macOS.

  • This is different than ".NET Native", that is used with UWP
  • It is also different than the Mono AOT compilation used on .NET iOS apps

Initial investigation

Testing on Windows, a .NET console app with Sentry publishing with Native AOT gives these warnings:

C:\Users\mattj\.nuget\packages\sentry\3.29.1\lib\net6.0\Sentry.dll : warning IL2104: Assembly 'Sentry' produced trim warnings. For more information see https://aka.ms/dotnet-illink/libraries [C:\Users\mattj\Code\Temp\AotConsoleApp\AotConsoleApp.csproj]
C:\Users\mattj\.nuget\packages\sentry\3.29.1\lib\net6.0\Sentry.dll : warning IL3053: Assembly 'Sentry' produced AOT analysis warnings. [C:\Users\mattj\Code\Temp\AotConsoleApp\AotConsoleApp.csproj]

Trying to capture an exception shows internal serialization failures related AOT trimming:

Error: Failed to serialize object for property 'Dynamic Code'. Original depth: 2, current depth: 2
System.NotSupportedException: 'System.Text.Json.Serialization.Converters.DictionaryOfTKeyTValueConverter`3[System.Collections.Generic.Dictionary`2[System.String, System.Boolean], System.String, System.Boolean]' is missing native code or metadata. This can happen for code that is not compatible with trimming or AOT. Inspect and fix trimming and AOT related warnings that were generated when the app was published. For more information see https://aka.ms/nativeaot-compatibility
   at System.Reflection.Runtime.General.TypeUnifier.WithVerifiedTypeHandle(RuntimeConstructedGenericTypeInfo, RuntimeTypeInfo[]) + 0x88
   at System.Text.Json.Serialization.Converters.IEnumerableConverterFactory.CreateConverter(Type, JsonSerializerOptions) + 0x6a2
   at System.Text.Json.Serialization.JsonConverterFactory.GetConverterInternal(Type, JsonSerializerOptions) + 0x15
   at System.Text.Json.JsonSerializerOptions.ExpandConverterFactory(JsonConverter, Type) + 0x32
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.GetConverterForType(Type, JsonSerializerOptions, Boolean) + 0x78
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.CreateJsonTypeInfo(Type, JsonSerializerOptions) + 0x21
   at System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver.GetTypeInfo(Type, JsonSerializerOptions) + 0x47
   at System.Text.Json.JsonSerializerOptions.GetTypeInfoNoCaching(Type type) + 0x33
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey, Func`2) + 0x82
   at System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type, Boolean, Boolean) + 0x3f
   at System.Text.Json.JsonSerializer.ResolvePolymorphicTypeInfo[TValue](TValue&, JsonTypeInfo, Boolean&) + 0x82
   at System.Text.Json.JsonSerializer.WriteCore[TValue](Utf8JsonWriter, TValue&, JsonTypeInfo`1) + 0x99
   at System.Text.Json.JsonSerializer.Serialize[TValue](Utf8JsonWriter, TValue, JsonSerializerOptions) + 0x3a
   at Sentry.Internal.Extensions.JsonExtensions.WriteDynamicValue(Utf8JsonWriter, Object, IDiagnosticLogger) + 0x437
   at Sentry.Internal.Extensions.JsonExtensions.WriteDynamic(Utf8JsonWriter, String, Object, IDiagnosticLogger) + 0x4a

The error event does come through to Sentry, but without its stack trace.

Trimming was added in .NET 6 as an option for self-contained deployments, but is required when Native AOT is used. There is guidance for libraries here:

https://learn.microsoft.com/dotnet/core/deploying/trimming/prepare-libraries-for-trimming

Checking for trimming errors by adding <IsTrimmable>true</IsTrimmable> to Sentry.csproj shows three main areas where there are trimming issues in the Sentry SDK:

  • Ben.Demystifier uses reflection heavily. We already learned (in Fix: Disable Ben.Demystifier for UWP by default. #821) that we can't use it by default in UWP because .NET Native also doesn't like it. We'll have to do something similar for Native AOT. That means no StackTraceMode.Enhanced for Native AOT. 😞 (unless we can find an alternative)

    modules\Ben.Demystifier\src\Ben.Demystifier\Internal\ReflectionHelper.cs(59,23): error IL2070: 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicProperties' in call to 'System.Type.GetProperty(String, BindingFlags)'. The parameter 'attributeType' of method 'System.Diagnostics.Internal.ReflectionHelper.GetTransformNamesPropertyInfo(Type)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. [src\Sentry\Sentry.csproj::TargetFramework=net6.0]
    modules\Ben.Demystifier\src\Ben.Demystifier\EnhancedStackTrace.Frames.cs(39,54): error IL2026: Using member 'System.Reflection.Assembly.GetType(String, Boolean)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Types might be removed. [src\Sentry\Sentry.csproj::TargetFramework=net6.0]
    modules\Ben.Demystifier\src\Ben.Demystifier\Internal\ILReader.cs(61,32): error IL2026: Using member 'System.Reflection.Module.ResolveMember(Int32, Type[], Type[])' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Trimming changes metadata tokens. [src\Sentry\Sentry.csproj::TargetFramework=net6.0]
    modules\Ben.Demystifier\src\Ben.Demystifier\EnhancedStackTrace.Frames.cs(389,21): error IL2026: Using member 'System.Reflection.MethodBase.GetMethodBody()' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Trimming may change method bodies. For example it can change some instructions, remove branches or local variables. [src\Sentry\Sentry.csproj::TargetFramework=net6.0]
    modules\Ben.Demystifier\src\Ben.Demystifier\EnhancedStackTrace.Frames.cs(463,52): error IL2075: 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods', 'DynamicallyAccessedMemberTypes.NonPublicMethods' in call to 'System.Type.GetMethods(BindingFlags)'. The return value of method 'System.Reflection.MemberInfo.DeclaringType.get' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. [src\Sentry\Sentry.csproj::TargetFramework=net6.0]
    modules\Ben.Demystifier\src\Ben.Demystifier\EnhancedStackTrace.Frames.cs(348,36): error IL2075: 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods', 'DynamicallyAccessedMemberTypes.NonPublicMethods' in call to 'System.Type.GetMethods(BindingFlags)'. The return value of method 'System.Reflection.MemberInfo.DeclaringType.get' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. [src\Sentry\Sentry.csproj::TargetFramework=net6.0]
    modules\Ben.Demystifier\src\Ben.Demystifier\EnhancedStackTrace.Frames.cs(351,41): error IL2075: 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicConstructors', 'DynamicallyAccessedMemberTypes.NonPublicConstructors' in call to 'System.Type.GetConstructors(BindingFlags)'. The return value of method 'System.Reflection.MemberInfo.DeclaringType.get' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. [src\Sentry\Sentry.csproj::TargetFramework=net6.0]
    modules\Ben.Demystifier\src\Ben.Demystifier\EnhancedStackTrace.Frames.cs(363,36): error IL2075: 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods', 'DynamicallyAccessedMemberTypes.NonPublicMethods' in call to 'System.Type.GetMethods(BindingFlags)'. The return value of method 'System.Type.DeclaringType.get' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. [src\Sentry\Sentry.csproj::TargetFramework=net6.0]
    modules\Ben.Demystifier\src\Ben.Demystifier\EnhancedStackTrace.Frames.cs(366,41): error IL2075: 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicConstructors', 'DynamicallyAccessedMemberTypes.NonPublicConstructors' in call to 'System.Type.GetConstructors(BindingFlags)'. The return value of method 'System.Type.DeclaringType.get' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. [src\Sentry\Sentry.csproj::TargetFramework=net6.0]
    modules\Ben.Demystifier\src\Ben.Demystifier\EnhancedStackTrace.Frames.cs(371,45): error IL2075: 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicConstructors', 'DynamicallyAccessedMemberTypes.NonPublicConstructors' in call to 'System.Type.GetConstructors(BindingFlags)'. The return value of method 'System.Type.DeclaringType.get' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. [src\Sentry\Sentry.csproj::TargetFramework=net6.0]
    modules\Ben.Demystifier\src\Ben.Demystifier\EnhancedStackTrace.Frames.cs(188,42): error IL2075: 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicFields', 'DynamicallyAccessedMemberTypes.NonPublicFields' in call to 'System.Type.GetFields(BindingFlags)'. The return value of method 'System.Reflection.MemberInfo.DeclaringType.get' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. [src\Sentry\Sentry.csproj::TargetFramework=net6.0]
    modules\Ben.Demystifier\src\Ben.Demystifier\EnhancedStackTrace.Frames.cs(867,17): error IL2070: 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods', 'DynamicallyAccessedMemberTypes.NonPublicMethods' in call to 'System.Type.GetMethods(BindingFlags)'. The parameter 'type' of method 'GetDeclaredMethods(Type)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. [src\Sentry\Sentry.csproj::TargetFramework=net6.0]
    
    
  • Our WinUIUnhandledExceptionIntegration uses reflection to attach to Microsoft.UI.Xaml.Application.Current.UnhandledException. This is primarily for .NET MAUI on Windows, but is also for non-MAUI WinUI 3 applications.

    src\Sentry\Integrations\WinUIUnhandledExceptionIntegration.cs(84,35): error IL2026: Using member 'System.Reflection.Assembly.GetType(String)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Types might be removed. [src\Sentry\Sentry.csproj::TargetFramework=net6.0]
    src\Sentry\Integrations\WinUIUnhandledExceptionIntegration.cs(85,31): error IL2075: 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicProperties' in call to 'System.Type.GetProperty(String)'. The return value of method 'System.Reflection.Assembly.GetType(String)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. [src\Sentry\Sentry.csproj::TargetFramework=net6.0]
    src\Sentry\Integrations\WinUIUnhandledExceptionIntegration.cs(86,29): error IL2075: 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicEvents' in call to 'System.Type.GetEvent(String)'. The return value of method 'System.Reflection.Assembly.GetType(String)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. [src\Sentry\Sentry.csproj::TargetFramework=net6.0]
    
  • Our JsonExtensions.WriteDynamicValue method has a long list of types it will serialize, but then ultimately just tries to serialize object. Such usage is invalid in STJ when Native AOT is used.

    src\Sentry\Internal\Extensions\JsonExtensions.cs(488,17): error IL2026: Using member 'System.Text.Json.JsonSerializer.Serialize<TValue>(Utf8JsonWriter, TValue, JsonSerializerOptions)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved. [src\Sentry\Sentry.csproj::TargetFramework=net6.0]
    src\Sentry\Internal\Extensions\JsonExtensions.cs(497,17): error IL2026: Using member 'System.Text.Json.JsonSerializer.Serialize<TValue>(Utf8JsonWriter, TValue, JsonSerializerOptions)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved. [src\Sentry\Sentry.csproj::TargetFramework=net6.0]
    src\Sentry\Internal\Extensions\JsonExtensions.cs(504,17): error IL2026: Using member 'System.Text.Json.JsonSerializer.Serialize<TValue>(Utf8JsonWriter, TValue, JsonSerializerOptions)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved. [src\Sentry\Sentry.csproj::TargetFramework=net6.0]
    

Solution Brainstorm

We should be able to disable Ben.Demystifier and just use StackTraceMode.Original, but we'll need to add various trimmer attributes to the code to prevent the trimming warnings from breaking the build. This will be tricky, as we import Ben.Demystifier as a submodule.

We can either move WinUIUnhandledExceptionIntegration back to the Sentry.Maui package, or to a new package, or we can make a Windows target for the main Sentry package to avoid reflection. Alternatively, we could explore if DynamicDependencyAttribute would let us keep it as-is without it being trimmed out.

The main pain point is going to be JSON serialization. We have lots of places where we serialize object types - some of which are strictly internal, but others are exposed to end users. We could consider an API that would let the end-user provide JsonSerializerContext. See https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-source-generator/

Here are all the public APIs that would be affected by such a change:

  • Contexts
  • Envelope.Header
  • EnvelopeItem.Header
  • Mechanism.Data
  • Mechanism.Meta
  • Request.Data
  • SentryEvent.Extra
  • SentryMessage.Params
  • SentryValues
  • Span.Extra
  • Transaction.Extra

All of these currently flow through to serialization via JsonExtensions.WriteDynamicValue.

After we ensure Sentry works without compile errors/warnings, we should also see what might need to change to support symbolication for .NET Native AOT apps.

@mattjohnsonpint mattjohnsonpint added Feature New feature or request Platform: .NET labels Mar 22, 2023
@mattjohnsonpint mattjohnsonpint added this to the 4.0.0 milestone Mar 22, 2023
@mattjohnsonpint
Copy link
Contributor Author

None of those actually explain the missing stack trace when using StackTraceMode.Original. Digging a bit deeper, we can see one more trim warning if we add a .NET 7 target:

src\Sentry\Internal\DebugStackTrace.cs(157,20): error IL2026: Using member 'System.Diagnostics.StackFrame.GetMethod()' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Metadata for the method might be incomplete or removed. [C:\Users\mattj\Code\getsentry\sentry-dotnet\src\Sentry\Sentry.csproj::TargetFramework=net7.0]
src\Sentry\Internal\DebugStackTrace.cs(183,13): error IL2026: Using member 'System.Diagnostics.StackFrame.GetMethod()' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Metadata for the method might be incomplete or removed. [C:\Users\mattj\Code\getsentry\sentry-dotnet\src\Sentry\Sentry.csproj::TargetFramework=net7.0]

Indeed, StackFrame.GetMethod() returns null when called in a Native AOT app. Interestingly though, StackFrame.ToString() still gives the method name and native offset. For example:

"MyNamespace.MyClass.SomeMethod() + 0x34 at offset 52 in file:line:column <filename unknown>:0:0"

Not sure why they give the same offset in both hex and decimal, and the rest of the string is useless. But it is indeed there.

https://github.com/dotnet/runtime/blob/7500625bd9303a48d0500b6123cec604e49039ae/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Diagnostics/StackFrame.NativeAot.cs#L168

Getting the rest of the info we need (module, assembly, etc.) may be difficult.

@bruno-garcia
Copy link
Member

Seems Native AOT will run also on iOS: dotnet/runtime#80905

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature New feature or request
Projects
Archived in project
Development

No branches or pull requests

5 participants