runtime: Trimmed InlineArrayAttribute breaks Microsoft.Extensions.DependencyInjection
Description
When enabling trimming and using Microsoft.Extensions.DependencyInjection directly or indirectly (e.g. through M.E.Logging), execution fails when service accessors are created.
Reproduction Steps
Create a piece of code that uses M.E.DependencyInjection, such as this one:
Microsoft.Extensions.Logging.LoggerFactory.Create(b =>{});
and enable the trimming for all types.
Expected behavior
Execution completes.
Actual behavior
System.ArgumentNullException: ArgumentNull_Generic Arg_ParamName_Name, collection
dotnet.native.js:8 at System.Collections.Generic.List`1[[Microsoft.Extensions.Options.IConfigureOptions`1[[Microsoft.Extensions.Logging.LoggerFilterOptions, Microsoft.Extensions.Logging, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]], Microsoft.Extensions.Options, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]]..ctor(IEnumerable`1 collection)
dotnet.native.js:8 at Microsoft.Extensions.Options.OptionsFactory`1[[Microsoft.Extensions.Logging.LoggerFilterOptions, Microsoft.Extensions.Logging, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]]..ctor(IEnumerable`1 setups, IEnumerable`1 postConfigures, IEnumerable`1 validations)
dotnet.native.js:8 at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Constructor(Object obj, IntPtr* args)
dotnet.native.js:8 at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
dotnet.native.js:8 at System.Reflection.MethodBaseInvoker.InvokeWithFewArgs(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
dotnet.native.js:8 at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
dotnet.native.js:8 at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
dotnet.native.js:8 at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext, Microsoft.Extensions.DependencyInjection, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].VisitCallSiteMain(ServiceCallSite callSite, RuntimeResolverContext argument)
dotnet.native.js:8 at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitDisposeCache(ServiceCallSite transientCallSite, RuntimeResolverContext context)
dotnet.native.js:8 at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext, Microsoft.Extensions.DependencyInjection, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].VisitCallSite(ServiceCallSite callSite, RuntimeResolverContext argument)
dotnet.native.js:8 at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
dotnet.native.js:8 at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext, Microsoft.Extensions.DependencyInjection, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].VisitCallSiteMain(ServiceCallSite callSite, RuntimeResolverContext argument)
dotnet.native.js:8 at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
dotnet.native.js:8 at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext, Microsoft.Extensions.DependencyInjection, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].VisitCallSite(ServiceCallSite callSite, RuntimeResolverContext argument)
dotnet.native.js:8 at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
dotnet.native.js:8 at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext, Microsoft.Extensions.DependencyInjection, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].VisitCallSiteMain(ServiceCallSite callSite, RuntimeResolverContext argument)
dotnet.native.js:8 at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
dotnet.native.js:8 at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext, Microsoft.Extensions.DependencyInjection, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].VisitCallSite(ServiceCallSite callSite, RuntimeResolverContext argument)
dotnet.native.js:8 at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
dotnet.native.js:8 at Microsoft.Extensions.DependencyInjection.ServiceProvider.CreateServiceAccessor(Type serviceType)
dotnet.native.js:8 at System.Collections.Concurrent.ConcurrentDictionary`2[[System.Type, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Func`2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope, Microsoft.Extensions.DependencyInjection, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].GetOrAdd(Type key, Func`2 valueFactory)
dotnet.native.js:8 at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
dotnet.native.js:8 at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)
dotnet.native.js:8 at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[ILoggerFactory](IServiceProvider provider)
dotnet.native.js:8 at Microsoft.Extensions.Logging.LoggerFactory.Create(Action`1 configure)
Regression?
yes
Known Workarounds
Add the following to linker directives:
<assembly fullname="System.Private.CoreLib">
<type fullname="System.Runtime.CompilerServices.InlineArrayAttribute" />
</assembly>
Configuration
6d0e3e59dd2f96eb9ca5c5397ff2a9d7c5526cac, using WebAssembly, but likely to repro on any other linker compatible target.
Other information
No response
About this issue
- Original URL
- State: closed
- Created 10 months ago
- Comments: 26 (26 by maintainers)
Commits related to this issue
- fix: Disable unused attributes trimming Related to https://github.com/dotnet/runtime/issues/90745 — committed to unoplatform/Uno.Wasm.Bootstrap by jeromelaban 10 months ago
There was a long discussion on
--used-attrs-onlyat https://github.com/dotnet/linker/issues/952#issuecomment-591975814. It’s a fragile list of attributes to keep track of. Their behaviors can be subtle, up to and including causing MSRC-level (security) issues (e.g. stripping attributes that affect DLL path resolution or generation of stack cookies).We introduced the ability to list attributes that are “safe” to remove and trim those since the linked discussion happened. I think the savings will be very small compared to this new option.
I think we should just delete
--used-attrs-onlyfrom the product so that people don’t use this and file (potentially MSRC-level) bugs on this.I’m a bit lost in this. Linker should not remove any attributes by default - there is a hidden feature which would do that, but it’s not enabled and is VERY experimental (and we don’t really maintain it anymore). If this issue is about the fact that turning on this hidden feature breaks things… it’s basically by design. There would need to be other work on top of the “keep these at all times” list - it’s basically a new feature work.
There is a hardcoded list of attributes which are OK to be removed and there’s an XML file which directs linker to do that. That list is different for some targets (Blazor removes more attributes than normal console apps for example). If that list contains this attribute, than that would be a bug. https://github.com/dotnet/runtime/blob/64243bbf5e9ee53c0c4c5678f2cd8c7f1c9b4f6f/src/mono/System.Private.CoreLib/src/ILLink/ILLink.LinkAttributes.xml#L6 https://github.com/dotnet/runtime/blob/64243bbf5e9ee53c0c4c5678f2cd8c7f1c9b4f6f/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.LinkAttributes.Shared.xml#L188 https://github.com/dotnet/runtime/blob/64243bbf5e9ee53c0c4c5678f2cd8c7f1c9b4f6f/src/coreclr/nativeaot/System.Private.CoreLib/src/ILLink/ILLink.LinkAttributes.xml#L6
The current list of these attributes recognized by the runtime is in https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/wellknownattributes.h. It is not that many attributes and it is not changing frequently. I think it would be simplest to duplicate annotations as attributes than to invent custom encodings of the list.
The problem is that the issue is not actually limited to the attributes recognized by the runtime. Managed code out there can look for arbitrary attributes by name as well and the linker is not able to reason about it. As Vitek pointed out in https://github.com/dotnet/runtime/issues/90745#issuecomment-1683003039, the attribute stripping is very experimental unmaintained hidden feature, it does not work well, and it is not enabled in any workloads supported by .NET team.
Where do you see Blazor using it? I couldn’t find mentions of it in aspnet or sdk. I do see mentions of it in Uno though, which is unfortunate.
I also tried reproducing this in a console app with
--used-attrs-only truebut I couldn’t, so I think we need more information or a repro.Yup, just that this issue is the classic case of, “something happens in one place and it breaks if another place isn’t changed as well.”
Trying to 1) identify the problem, 2) fix the problem, 3) ensure it’s fixed everywhere, and 4) try to prevent similar problems happening in the future.
Ideally I’d like to make mismatches like this impossible, but barring that I’d like to ensure we have appropriate process set up.
The existing attributes recognized by the runtime are hardcoded here: https://github.com/dotnet/runtime/blob/7bc69ad2639a98e5940e4061edd8aacfae50128f/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs#L1154-L1167 . I think
InlineArrayAttributeshould be added to this list.Number of other attributes recognized by the runtime seems to be missing in this list. The list of attributes recognized by the runtime is in https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/wellknownattributes.h . Should all of these attributes be hardcoded in the linker? (It should be ok to omit attributes for scenarios like COM interop that are not trim compatible by design.)