-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
NativeAOT and DependencyInjection is broken #81779
Comments
Tagging subscribers to this area: @dotnet/area-extensions-options Issue DetailsDescriptionIt looks like #81532 has broken NativeAOT + DependencyInjection. Reproduction Steps
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<OutputType>exe</OutputType>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<PublishAot>true</PublishAot>
<RuntimeFrameworkVersion>8.0.0-preview.2.23107.1</RuntimeFrameworkVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(RuntimeFrameworkVersion)" />
</ItemGroup>
</Project> using Microsoft.Extensions.DependencyInjection;
using System.Diagnostics.CodeAnalysis;
var services = new ServiceCollection();
services.AddTransient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>));
var sp = services.BuildServiceProvider();
var options = sp.GetRequiredService<IOptionsFactory<RouteHandlerOptions>>().Create();
Console.WriteLine(options.ToString());
public sealed class RouteHandlerOptions
{
public RouteHandlerOptions() { }
public bool ThrowOnBadRequest { get; set; }
}
public interface IOptionsFactory<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TOptions>
where TOptions : class
{
TOptions Create();
}
public class OptionsFactory<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TOptions> :
IOptionsFactory<TOptions>
where TOptions : class
{
public TOptions Create()
{
return Activator.CreateInstance<TOptions>();
}
} Expected behaviorThe app should print Actual behaviorThe app fails:
Regression?Yes. This worked in previous builds. Known WorkaroundsNo response ConfigurationNo response Other informationcc @vitek
|
Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas Issue DetailsDescriptionIt looks like #81532 has broken NativeAOT + DependencyInjection. Reproduction Steps
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<OutputType>exe</OutputType>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<PublishAot>true</PublishAot>
<RuntimeFrameworkVersion>8.0.0-preview.2.23107.1</RuntimeFrameworkVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(RuntimeFrameworkVersion)" />
</ItemGroup>
</Project> using Microsoft.Extensions.DependencyInjection;
using System.Diagnostics.CodeAnalysis;
var services = new ServiceCollection();
services.AddTransient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>));
var sp = services.BuildServiceProvider();
var options = sp.GetRequiredService<IOptionsFactory<RouteHandlerOptions>>().Create();
Console.WriteLine(options.ToString());
public sealed class RouteHandlerOptions
{
public RouteHandlerOptions() { }
public bool ThrowOnBadRequest { get; set; }
}
public interface IOptionsFactory<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TOptions>
where TOptions : class
{
TOptions Create();
}
public class OptionsFactory<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TOptions> :
IOptionsFactory<TOptions>
where TOptions : class
{
public TOptions Create()
{
return Activator.CreateInstance<TOptions>();
}
} Expected behaviorThe app should print Actual behaviorThe app fails:
Regression?Yes. This worked in previous builds. Known WorkaroundsNo response ConfigurationNo response Other informationcc @vitek-karas
|
The root cause of the failure is this: GetRequiredService<IOperationsFactory<MyType>>(); Inside The problem is that we only validate generic data flow for the We don't even run the body of the I think the change as it was before revert (so #81532) covers all of the top-level generic instantiations correctly. But it doesn't cover these nested ones. So one possible fix would be to extend the generic argument data flow to apply recursively on the type parameters. It will overmark a little bit in some cases, but I don't see a better way to do this unfortunately. I also have to think what does "recursively" mean actually - do we need to go into the base types and interfaces as well? Currently I don't think so, since the outer type will be marked as constructed which will trigger generic argument data flow on base type and interfaces already? |
We will see a MethodTable being constructed for |
One problem I found with that is warning origin would be messed up in some cases. For example: class TargetType
{
[RequiresUnreferencedCode()]
public void RucMethod();
}
interface INeedsMethods<[DAM(PublicMethods)] T>
{
}
static void GenericMethod<T> { _ = typeof(T); }
static void Test()
{
// This should produce a warning saying that RucMethod as RUC on it
GenericMethod<INeedsMethods<TargetType>>();
} If we implement the behavior inside Ideally the warning would come from the |
I think this is one of those where yes, the annotations are not consistent, but it doesn't pose an actual safety problem. It might be an issue if the app has a wrong suppression only. If this situation doesn't apply to the wrong suppression in di, it should be fine not to warn. |
Digging deeper - doing the recursive data flow is problematic for other reasons. It would require us to run data flow on all method bodies which have any generic instantiations in them anywhere. For example, calling a generic method would be enough to trigger data flow on the method. That would dramatically increase the number of methods which run data flow on, which would in turn slow down the compiler. |
But it does, right? If we don't warn on the RUC method in the above example, we won't warn on the same RUC method if it's done through DI. And yes, there's a wrong suppression in DI, but we're basically trying to workaround that in the compiler. So we're basically saying, we will make it so that the suppression in DI is NOT wrong - and that means that whatever the compiler does, it must not cause additional holes. |
The DI based example would be the one from the issue above, just add RUD onto: [RequiresUnreferencedCode]
public RouteHandlerOptions() { } With that the example above should produce a warning about access to the RUC method (in this case it would actually call the method, so it's not just a potential access). So we can't disable warnings while doing the marking of the inner generic arguments. |
[This is a revert of a revert of #81532 with additional fixes for #81779] This reworks how generic parameter data flow validation is done in the NativeAOT compiler. Previously generic data flow was done from generic dictionary nodes. Problem with that approach is that there's no origin information at that point. The warnings can't point to the place where the problematic instantiation is in the code - we only know that it exists. Aside from it being unfriendly for the users, it means any RUC or suppressions don't work on these warnings the same way they do in linker/analyzer. This change modifies the logic to tag the method as "needs data flow" whenever we spot an instantiation of an annotated generic in it somewhere. Then the actual validation/marking is done from data flow using the trim analysis patterns. The only exception to this is generic data flow for base types and interface implementations, that one is done on the EEType nodes. Note that AOT implements a much more precise version of the generic data flow validation as compared to linker/analyzer. See the big comment at the beginning of `GenericParameterWarningLocation.cs` for how that works. Due to an issue with DependencyInjection, this change also implements a behavior where if a method or field is reflection accessible, the compiler will perform generic argument data flow on all types in the signature of the method/field (which it normally wouldn't do). See #81358 for details about the issue and discussions on the fix approach. Due to the DI behavior described above, there's also the problem with nested generics. If a nested generic applies annotation on a specific type and this whole thing is done from within a DI, the compiler will not apply the annotation, since it doesn't see the type being used anywhere for real. See #81779 for detailed description of the issue. The fix for this is to extend the "needs data flow analysis" logic to look into generic arguments recursively and finding any annotation then triggers the data flow processing of the calling code. Then in that processing when applying generic argument data flow, do so recursively over all generic parameters. Test changes: Adds the two tests from linker which cover this functionality. Change the test infra to use token to compare message origins for expected warnings. Consistently converting generic types/methods into strings across two type systems is just very difficult - the tokens are simple and reliable. Changes the tests to avoid expecting specific generic types/methods formatting in the messages - again, it's too hard to make this consistent without lot of effort. And the tests don't really need it. Adds a test for marking behavior related to generic argument data flow. This is to catch issues like #81779. Adds a smoke test which has a simplified version of the DI problem from #81358. Fixes #77455 Fixes #75898 Fixes #81358 Fixes #81779
Description
It looks like #81532 has broken NativeAOT + DependencyInjection.
Reproduction Steps
dotnet publish
the following app:Expected behavior
The app should print
RouteHandlerOptions
.Actual behavior
The app fails:
Regression?
Yes. This worked in previous builds.
Known Workarounds
No response
Configuration
No response
Other information
cc @vitek-karas
The text was updated successfully, but these errors were encountered: