Skip to content

Commit f424c0f

Browse files
authored
Remove RequiresDynamicCode from Microsoft.Extensions.DependencyInjection (#79425)
* Remove RequiresDynamicCode from Microsoft.Extensions.DependencyInjection We need a better approach in order to support applications that use DependencyInjection and publish for NativeAOT. DependencyInjection needs to have reliable behavior before and after publishing for NativeAOT. The application can't work successfully at development-time, but then fail after publishing with PublishAot=true. We will resolve the 2 NativeAOT warnings above by adding a runtime check that is behind the new AppContext switch added in #80246 (`System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported`). The runtime check ensures the Types being used with Enumerable and Open Generic services are only Reference Types. If an application tries to create an Enumerable or Closed Generic service of a ValueType, DependencyInjection will throw an exception. The check is enabled by default when PublishAot=true. Fix #79286
1 parent fd6f47c commit f424c0f

25 files changed

+171
-83
lines changed

src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
namespace Microsoft.Extensions.DependencyInjection
88
{
9-
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Using Microsoft.Extensions.DependencyInjection requires generating code dynamically at runtime. For example, when using enumerable and generic ValueType services.")]
109
public partial class DefaultServiceProviderFactory : Microsoft.Extensions.DependencyInjection.IServiceProviderFactory<Microsoft.Extensions.DependencyInjection.IServiceCollection>
1110
{
1211
public DefaultServiceProviderFactory() { }
@@ -16,11 +15,8 @@ public DefaultServiceProviderFactory(Microsoft.Extensions.DependencyInjection.Se
1615
}
1716
public static partial class ServiceCollectionContainerBuilderExtensions
1817
{
19-
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Using Microsoft.Extensions.DependencyInjection requires generating code dynamically at runtime. For example, when using enumerable and generic ValueType services.")]
2018
public static Microsoft.Extensions.DependencyInjection.ServiceProvider BuildServiceProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; }
21-
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Using Microsoft.Extensions.DependencyInjection requires generating code dynamically at runtime. For example, when using enumerable and generic ValueType services.")]
2219
public static Microsoft.Extensions.DependencyInjection.ServiceProvider BuildServiceProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, Microsoft.Extensions.DependencyInjection.ServiceProviderOptions options) { throw null; }
23-
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Using Microsoft.Extensions.DependencyInjection requires generating code dynamically at runtime. For example, when using enumerable and generic ValueType services.")]
2420
public static Microsoft.Extensions.DependencyInjection.ServiceProvider BuildServiceProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, bool validateScopes) { throw null; }
2521
}
2622
public sealed partial class ServiceProvider : System.IAsyncDisposable, System.IDisposable, System.IServiceProvider

src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.csproj

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@
1212
<ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.DependencyInjection.Abstractions\ref\Microsoft.Extensions.DependencyInjection.Abstractions.csproj" />
1313
</ItemGroup>
1414

15-
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">
16-
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\RequiresDynamicCodeAttribute.cs" />
17-
</ItemGroup>
18-
1915
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'netstandard2.1'))">
2016
<PackageReference Include="System.Threading.Tasks.Extensions" Version="$(SystemThreadingTasksExtensionsVersion)" />
2117
<ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Bcl.AsyncInterfaces\ref\Microsoft.Bcl.AsyncInterfaces.csproj" />

src/libraries/Microsoft.Extensions.DependencyInjection/src/DefaultServiceProviderFactory.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5-
using System.Diagnostics.CodeAnalysis;
65

76
namespace Microsoft.Extensions.DependencyInjection
87
{
98
/// <summary>
109
/// Default implementation of <see cref="IServiceProviderFactory{TContainerBuilder}"/>.
1110
/// </summary>
12-
[RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)]
1311
public class DefaultServiceProviderFactory : IServiceProviderFactory<IServiceCollection>
1412
{
1513
private readonly ServiceProviderOptions _options;

src/libraries/Microsoft.Extensions.DependencyInjection/src/Resources/Strings.resx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,4 +180,10 @@
180180
<data name="TrimmingAnnotationsDoNotMatch_NewConstraint" xml:space="preserve">
181181
<value>Generic implementation type '{0}' has a DefaultConstructorConstraint ('new()' constraint), but the generic service type '{1}' doesn't.</value>
182182
</data>
183-
</root>
183+
<data name="AotCannotCreateEnumerableValueType" xml:space="preserve">
184+
<value>Unable to create an Enumerable service of type '{0}' because it is a ValueType. Native code to support creating Enumerable services might not be available with native AOT.</value>
185+
</data>
186+
<data name="AotCannotCreateGenericValueType" xml:space="preserve">
187+
<value>Unable to create a generic service for type '{0}' because '{1}' is a ValueType. Native code to support creating generic services might not be available with native AOT.</value>
188+
</data>
189+
</root>

src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceCollectionContainerBuilderExtensions.cs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5-
using System.Diagnostics.CodeAnalysis;
6-
using System.Runtime.CompilerServices;
7-
using Microsoft.Extensions.DependencyInjection.ServiceLookup;
85

96
namespace Microsoft.Extensions.DependencyInjection
107
{
@@ -18,8 +15,6 @@ public static class ServiceCollectionContainerBuilderExtensions
1815
/// </summary>
1916
/// <param name="services">The <see cref="IServiceCollection"/> containing service descriptors.</param>
2017
/// <returns>The <see cref="ServiceProvider"/>.</returns>
21-
22-
[RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)]
2318
public static ServiceProvider BuildServiceProvider(this IServiceCollection services)
2419
{
2520
return BuildServiceProvider(services, ServiceProviderOptions.Default);
@@ -34,7 +29,6 @@ public static ServiceProvider BuildServiceProvider(this IServiceCollection servi
3429
/// <c>true</c> to perform check verifying that scoped services never gets resolved from root provider; otherwise <c>false</c>.
3530
/// </param>
3631
/// <returns>The <see cref="ServiceProvider"/>.</returns>
37-
[RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)]
3832
public static ServiceProvider BuildServiceProvider(this IServiceCollection services, bool validateScopes)
3933
{
4034
return services.BuildServiceProvider(new ServiceProviderOptions { ValidateScopes = validateScopes });
@@ -49,7 +43,6 @@ public static ServiceProvider BuildServiceProvider(this IServiceCollection servi
4943
/// Configures various service provider behaviors.
5044
/// </param>
5145
/// <returns>The <see cref="ServiceProvider"/>.</returns>
52-
[RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)]
5346
public static ServiceProvider BuildServiceProvider(this IServiceCollection services, ServiceProviderOptions options)
5447
{
5548
if (services is null)

src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
1313
{
14-
[RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)]
1514
internal sealed class CallSiteFactory : IServiceProviderIsService
1615
{
1716
private const int DefaultSlot = 0;
@@ -244,8 +243,14 @@ private static bool AreCompatible(DynamicallyAccessedMemberTypes serviceDynamica
244243
serviceType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
245244
{
246245
Type itemType = serviceType.GenericTypeArguments[0];
247-
CallSiteResultCacheLocation cacheLocation = CallSiteResultCacheLocation.Root;
246+
if (ServiceProvider.VerifyAotCompatibility && itemType.IsValueType)
247+
{
248+
// NativeAOT apps are not able to make Enumerable of ValueType services
249+
// since there is no guarantee the ValueType[] code has been generated.
250+
throw new InvalidOperationException(SR.Format(SR.AotCannotCreateEnumerableValueType, itemType));
251+
}
248252

253+
CallSiteResultCacheLocation cacheLocation = CallSiteResultCacheLocation.Root;
249254
var callSites = new List<ServiceCallSite>();
250255

251256
// If item type is not generic we can safely use descriptor cache
@@ -350,6 +355,9 @@ private static CallSiteResultCacheLocation GetCommonCacheLocation(CallSiteResult
350355
Justification = "MakeGenericType here is used to create a closed generic implementation type given the closed service type. " +
351356
"Trimming annotations on the generic types are verified when 'Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability' is set, which is set by default when PublishTrimmed=true. " +
352357
"That check informs developers when these generic types don't have compatible trimming annotations.")]
358+
[UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
359+
Justification = "When ServiceProvider.VerifyAotCompatibility is true, which it is by default when PublishAot=true, " +
360+
"this method ensures the generic types being created aren't using ValueTypes.")]
353361
private ServiceCallSite? TryCreateOpenGeneric(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, int slot, bool throwOnConstraintViolation)
354362
{
355363
if (serviceType.IsConstructedGenericType &&
@@ -366,7 +374,13 @@ private static CallSiteResultCacheLocation GetCommonCacheLocation(CallSiteResult
366374
Type closedType;
367375
try
368376
{
369-
closedType = descriptor.ImplementationType.MakeGenericType(serviceType.GenericTypeArguments);
377+
Type[] genericTypeArguments = serviceType.GenericTypeArguments;
378+
if (ServiceProvider.VerifyAotCompatibility)
379+
{
380+
VerifyOpenGenericAotCompatibility(serviceType, genericTypeArguments);
381+
}
382+
383+
closedType = descriptor.ImplementationType.MakeGenericType(genericTypeArguments);
370384
}
371385
catch (ArgumentException)
372386
{
@@ -524,6 +538,24 @@ private ServiceCallSite CreateConstructorCallSite(
524538
return parameterCallSites;
525539
}
526540

541+
/// <summary>
542+
/// Verifies none of the generic type arguments are ValueTypes.
543+
/// </summary>
544+
/// <remarks>
545+
/// NativeAOT apps are not guaranteed that the native code for the closed generic of ValueType
546+
/// has been generated. To catch these problems early, this verification is enabled at development-time
547+
/// to inform the developer early that this scenario will not work once AOT'd.
548+
/// </remarks>
549+
private static void VerifyOpenGenericAotCompatibility(Type serviceType, Type[] genericTypeArguments)
550+
{
551+
foreach (Type typeArg in genericTypeArguments)
552+
{
553+
if (typeArg.IsValueType)
554+
{
555+
throw new InvalidOperationException(SR.Format(SR.AotCannotCreateGenericValueType, serviceType, typeArg));
556+
}
557+
}
558+
}
527559

528560
public void Add(Type type, ServiceCallSite serviceCallSite)
529561
{

src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteRuntimeResolver.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Diagnostics;
67
using System.Diagnostics.CodeAnalysis;
78
using System.Reflection;
89
using System.Runtime.ExceptionServices;
910
using System.Threading;
1011

1112
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
1213
{
13-
[RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)]
1414
internal sealed class CallSiteRuntimeResolver : CallSiteVisitor<RuntimeResolverContext, object?>
1515
{
1616
public static CallSiteRuntimeResolver Instance { get; } = new();
@@ -162,7 +162,7 @@ protected override object VisitServiceProvider(ServiceProviderCallSite servicePr
162162

163163
protected override object VisitIEnumerable(IEnumerableCallSite enumerableCallSite, RuntimeResolverContext context)
164164
{
165-
var array = Array.CreateInstance(
165+
Array array = CreateArray(
166166
enumerableCallSite.ItemType,
167167
enumerableCallSite.ServiceCallSites.Length);
168168

@@ -172,6 +172,15 @@ protected override object VisitIEnumerable(IEnumerableCallSite enumerableCallSit
172172
array.SetValue(value, index);
173173
}
174174
return array;
175+
176+
[UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
177+
Justification = "VerifyAotCompatibility ensures elementType is not a ValueType")]
178+
static Array CreateArray(Type elementType, int length)
179+
{
180+
Debug.Assert(!ServiceProvider.VerifyAotCompatibility || !elementType.IsValueType, "VerifyAotCompatibility=true will throw during building the IEnumerableCallSite if elementType is a ValueType.");
181+
182+
return Array.CreateInstance(elementType, length);
183+
}
175184
}
176185

177186
protected override object VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)

src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/DynamicServiceProviderEngine.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88

99
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
1010
{
11-
[RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)]
1211
internal sealed class DynamicServiceProviderEngine : CompiledServiceProviderEngine
1312
{
1413
private readonly ServiceProvider _serviceProvider;
1514

16-
public DynamicServiceProviderEngine(ServiceProvider serviceProvider): base(serviceProvider)
15+
[RequiresDynamicCode("Creates DynamicMethods")]
16+
public DynamicServiceProviderEngine(ServiceProvider serviceProvider) : base(serviceProvider)
1717
{
1818
_serviceProvider = serviceProvider;
1919
}

src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/Expressions/ExpressionResolverBuilder.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
using System;
55
using System.Collections.Concurrent;
66
using System.Collections.Generic;
7+
using System.Diagnostics;
78
using System.Diagnostics.CodeAnalysis;
89
using System.Linq;
910
using System.Linq.Expressions;
1011
using System.Reflection;
1112

1213
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
1314
{
14-
[RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)]
1515
internal sealed class ExpressionResolverBuilder : CallSiteVisitor<object?, Expression>
1616
{
1717
private static readonly ParameterExpression ScopeParameter = Expression.Parameter(typeof(ServiceProviderEngineScope));
@@ -114,10 +114,19 @@ protected override Expression VisitFactory(FactoryCallSite factoryCallSite, obje
114114

115115
protected override Expression VisitIEnumerable(IEnumerableCallSite callSite, object? context)
116116
{
117+
[UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
118+
Justification = "VerifyAotCompatibility ensures elementType is not a ValueType")]
119+
static MethodInfo GetArrayEmptyMethodInfo(Type elementType)
120+
{
121+
Debug.Assert(!ServiceProvider.VerifyAotCompatibility || !elementType.IsValueType, "VerifyAotCompatibility=true will throw during building the IEnumerableCallSite if elementType is a ValueType.");
122+
123+
return ServiceLookupHelpers.GetArrayEmptyMethodInfo(elementType);
124+
}
125+
117126
if (callSite.ServiceCallSites.Length == 0)
118127
{
119128
return Expression.Constant(
120-
ServiceLookupHelpers.GetArrayEmptyMethodInfo(callSite.ItemType)
129+
GetArrayEmptyMethodInfo(callSite.ItemType)
121130
.Invoke(obj: null, parameters: Array.Empty<object>()));
122131
}
123132

src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/Expressions/ExpressionsServiceProviderEngine.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ internal sealed class ExpressionsServiceProviderEngine : ServiceProviderEngine
1010
{
1111
private readonly ExpressionResolverBuilder _expressionResolverBuilder;
1212

13-
[RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)]
1413
public ExpressionsServiceProviderEngine(ServiceProvider serviceProvider)
1514
{
1615
_expressionResolverBuilder = new ExpressionResolverBuilder(serviceProvider);

0 commit comments

Comments
 (0)