-
Notifications
You must be signed in to change notification settings - Fork 10.3k
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
[AOT] Fix runtime warnings #45604
[AOT] Fix runtime warnings #45604
Conversation
...rotection/DataProtection/src/AuthenticatedEncryption/ManagedAuthenticatedEncryptorFactory.cs
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like an improvement to me. I do wonder if we should have stronger runtime checks for the container and identity stuff. Maybe we should be adding new : class
generic constraints to some APIs.
Should we file a tracking issue to remove the pragmas once dotnet/runtime#79425 is merged?
if (containerType.IsValueType && !RuntimeFeature.IsDynamicCodeSupported) | ||
{ | ||
throw new InvalidOperationException("ValueType startup container isn't supported with AOT."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is a valid approach to avoid the warning. This breaks the principle that "if you don't get any warnings, your app will work the same before and after publishing AOT".
If someone was using this functionality, and published for AOT, they wouldn't get any warnings and their app would work successfully at F5/dotnet run
time. But when they went to publish the app, it would be broken.
Note this is different than the approach taken in dotnet/runtime#79425. The difference is that DI is using a "feature switch" which gets enabled when PublishAot=true
is set in the .csproj. That approach means that the app will still fail at F5/dotnet run
time, if the app was using the AOT-incompatible functionality.
I think the existing hosting APIs should just be marked RequiresDynamicCode
instead of suppressing these warnings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same applies for the StartupLoader code/suppression.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Startup containers are very rarely used. And I doubt anyone has created a struct startup container. I don't want to annotate the hosting APIs as RequiresDynamicCode for this one weird edge case.
Good point about dotnet run
/dotnet publish
difference. Should we add a flag like dotnet/runtime#79425 does that ASP.NET Core can use to validate AOT compatibility? Or should there be one flag that is shared by libraries/applications?
I think the use case might be shared between many libraries: AOT always works except in unusual and rare situations. The pragmatic approach is to have a runtime error instead of always warning with annotations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vitek-karas @MichalStrehovsky @jkotas - thoughts on converting the DI feature switch in dotnet/runtime#79425 to be a general purpose “Verify AOT Compatibility” feature switch that can be used by other libraries? Like for instance, here in aspnet?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another option is to always throw an exception for value types, AOT or not, if we think this functionality isn’t used with value types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's an option. We can publish it as a breaking change.
IMO regardless of whether we use it here or not, I think a generalized runtime verify AOT compatibility flag has some use. I think it provides a pragmatic middle ground in gray situations where something is compatible 99% of the time. Right now we have to choose between suppress and pretend everything is ok, and warn for everyone.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
general purpose “Verify AOT Compatibility” feature switch
This sounds like dotnet/runtime#39806
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having something like dotnet/runtime#39806 would be nice.
You're then left with a choice:
- Just disable the problematic case always (in our case here, always throw and potentially add class constrains in right places) - breaking change
- Rely on Add ability to disable reflection emit for testing runtime#39806 to basically trigger
IsDynamicCodeSupported
based on a feature switch for you. Downside is that such solution is not granular - and so it would not be possible to disable dynamic code everywhere, except this one case. - Introduce a new feature switch - which is what Remove RequiresDynamicCode from Microsoft.Extensions.DependencyInjection runtime#79425 is doing. Similar to the above, but it adds granularity. Downside is the added complexity and "yet another feature switch"
- Mark the API as AOT incompatible via the attributes - and hopefully figure out a new API which can be used instead
I think which one to do is a function of the specific scenario. If it's something super rare, I would go with either outright breaking change or generic feature switch. If it's still rare, but "real", then a dedicated feature switch might be better.
src/Shared/ProblemDetails/HttpValidationProblemDetailsJsonConverter.cs
Outdated
Show resolved
Hide resolved
I've reviewed all the PR comments and changes in this PR. I've simplified some annotations and removed some unnecessary ones. Some items are still in discussion (startup container, attributes on problem details converters) and work todo (fix problem details errors serialization in converter). Everything else is resolved/improved. |
src/Shared/ProblemDetails/HttpValidationProblemDetailsJsonConverter.cs
Outdated
Show resolved
Hide resolved
src/Shared/ProblemDetails/HttpValidationProblemDetailsJsonConverter.cs
Outdated
Show resolved
Hide resolved
src/Shared/ProblemDetails/HttpValidationProblemDetailsJsonConverter.cs
Outdated
Show resolved
Hide resolved
[JsonSerializable(typeof(JsonElement))] | ||
[JsonSerializable(typeof(string))] | ||
[JsonSerializable(typeof(decimal))] | ||
[JsonSerializable(typeof(float))] | ||
[JsonSerializable(typeof(double))] | ||
[JsonSerializable(typeof(int))] | ||
[JsonSerializable(typeof(long))] | ||
[JsonSerializable(typeof(Guid))] | ||
[JsonSerializable(typeof(Uri))] | ||
[JsonSerializable(typeof(TimeSpan))] | ||
[JsonSerializable(typeof(DateTime))] | ||
[JsonSerializable(typeof(DateTimeOffset))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should undo this given our discussion @JamesNK
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is useful because it allows common type values as extension values to just work, e.g. adding a string value to problem details extensions.
Other than a small size cost when problem details aren't trimmed away, this doesn't hurt. Are you sure you want it removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't mind keeping it for now and removing it with @brunolins16's JSON changes, but I don't think this is generally useful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok! I'd like to keep as is in this PR (rather than reverting to what it was, which doesn't work), and we can iterate on it during .NET 8 as we learn more.
@@ -18,6 +18,8 @@ public static partial class HttpResponseJsonExtensions | |||
{ | |||
private const string RequiresUnreferencedCodeMessage = "JSON serialization and deserialization might require types that cannot be statically analyzed. " + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@brunolins16 this will all be undone with your changes right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't believe, the unsafe API will still need to be attributed, however, RDF + MVC should not call them anymore.
@eerhardt @davidfowl All feedback applied. Are there any other changes, or can this get approved and merged 🙏 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a great step forward! Thanks for all the great work here.
I just had a few nits, a question, and a follow-up recommendation for the future.
This LGTM. Let's get this in and make more progress.
"Map methods that configure a RequestDelegate don't use trimmer unsafe features.")] | ||
[UnconditionalSuppressMessage("AOT", "IL3050", | ||
Justification = "We surface a RequiresDynamicCode in the call to the Map methods adding route handlers this EndpointDataSource. Analysis is unable to infer this. " + | ||
"Map methods that configure a RequestDelegate don't use AOT unsafe features.")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there some way we can verify this?
One idea is to add a "Native AOT"-test that uses filter factories and ensures they work correctly.
If it is possible to verify this, we can do it in a follow-up issue/PR. It doesn't need to be addressed in this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll create follow-up issues for areas to test with native AOT enabled. Any code that tests IsDynamicCodeSupported
is a good candidate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The plan is to undo this anyways. This should be suppression free after we execute #45524. There will be new API changes that remove the use of RequestDelegateFactory.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does one add a "native AOT" test?
Edit: as discussed offline, we have yet to develop an approach, but we will probably model it on the one used by dotnet/runtime (i.e. create convenience wrappers for exes published as AOT).
@@ -328,3 +352,10 @@ private Task DisplayRuntimeException(HttpContext context, Exception ex) | |||
return errorPage.ExecuteAsync(context); | |||
} | |||
} | |||
|
|||
internal record ExceptionExtensionData(string Details, IHeaderDictionary Headers, string Path, string? Endpoint, RouteValueDictionary? RouteValues); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For my learning:
In dotnet/runtime we typically don't use record
because of the bloat associated with it - C# generates members you don't use/need. Do we have guidelines in dotnet/aspnetcore when to use record
and when not? Or don't we care as much?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we don't care as much. It's internal, so if we crack down on records in aspnetcore (we have them in other places) then it's simple enough to change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For reference, this is how much IL is generated by this single record
line: https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA0AXEBLANgH1kigBMACAUQTBgAcNsIA7KjGJgZ0aYBEBDDHwAUAAQCMABjI8YgvBzRkAkgAkYfEjCg9sYBsz5QAnmTUatCsuKkAFAQAtF1gPyUmJWhGxMMigEoQAK5sAGp8uIEwOnrchkauAcEwYREwHACUANwAsABQeXkiAExkiaHhkdH6THF5AN55AL4Fud5sUABmfDTKZprautW1uQ25jUA==
This record
will translate to about 5kB of binary footprint in the native aot image. 80% of it is dynamically unreachable code, but it is impossible for the trimmer to prove that it is unreachable because a lot of it is behind potentially reachable interfaces.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Impressive! Looks like we should clean up record usage in aspnetcore.
Co-authored-by: Eric Erhardt <eric.erhardt@microsoft.com>
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Values set on ProblemDetails.Extensions are supported by the default writer.")] | ||
[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Values set on ProblemDetails.Extensions are supported by the default writer.")] | ||
private ProblemDetails CreateProblemDetails(ErrorContext errorContext, HttpContext httpContext) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not let it warn until we have the real fix?
@@ -32,7 +38,14 @@ public IdentityBuilder(Type user, IServiceCollection services) | |||
/// <param name="role">The <see cref="Type"/> to use for the roles.</param> | |||
/// <param name="services">The <see cref="IServiceCollection"/> to attach to.</param> | |||
public IdentityBuilder(Type user, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type role, IServiceCollection services) : this(user, services) | |||
=> RoleType = role; | |||
{ | |||
if (role.IsValueType) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a breaking change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RoleType
and UserType
are later used to generate types, all of which have a where T : class
constraint.
Throwing here means the error is different, and it is thrown in a different place. But a value type as the user or role would always lead to an error.
Although the constructor is public, I imagine the vast majority of people who ever use IdentityBuilder create it via this extension method:
aspnetcore/src/Identity/Extensions.Core/src/IdentityServiceCollectionExtensions.cs
Line 22 in 09b0db0
public static IdentityBuilder AddIdentityCore<TUser>(this IServiceCollection services) where TUser : class |
It enforces a class with its own constraint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to see issues for:
- The developer exception page WRT what we're doing JSON wise there
- ProblemDetails and undoing what's there now
- Undoing the DI changes when those other changes land
- Doing API work in the Map* handlers to remove suppressions in the RouteEndpointDataSource
Follow-up issues: |
Merged! Thanks everyone for your feedback. |
@@ -4,6 +4,7 @@ | |||
<Description>Helpers for launching the SPA CLI proxy automatically when the application starts in ASP.NET MVC Core.</Description> | |||
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework> | |||
<IsTrimmable>true</IsTrimmable> | |||
<EnableAOTAnalyzer>false</EnableAOTAnalyzer> | |||
<!-- This is ok since this assembly is not referenced by any application but it is loaded as a hosting startup |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which line was this comment intended to refer to?
[JsonSerializable(typeof(ProblemDetails))] | ||
[JsonSerializable(typeof(JsonElement))] | ||
[JsonSerializable(typeof(string))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is bool
implied and/or specified elsewhere?
"Map methods that configure a RequestDelegate don't use trimmer unsafe features.")] | ||
[UnconditionalSuppressMessage("AOT", "IL3050", | ||
Justification = "We surface a RequiresDynamicCode in the call to the Map methods adding route handlers this EndpointDataSource. Analysis is unable to infer this. " + | ||
"Map methods that configure a RequestDelegate don't use AOT unsafe features.")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does one add a "native AOT" test?
Edit: as discussed offline, we have yet to develop an approach, but we will probably model it on the one used by dotnet/runtime (i.e. create convenience wrappers for exes published as AOT).
...introduced in dotnet#45604 now that dotnet/runtime#79425 is available.
Addresses #45473
RuntimeFeatures.IsDynamicCodeSupported
is false.