Skip to content

Commit

Permalink
Remove RequiresDynamicCode from Microsoft.Extensions.DependencyInject…
Browse files Browse the repository at this point in the history
…ion (#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
  • Loading branch information
eerhardt authored Jan 10, 2023
1 parent fd6f47c commit f424c0f
Show file tree
Hide file tree
Showing 25 changed files with 171 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

namespace Microsoft.Extensions.DependencyInjection
{
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Using Microsoft.Extensions.DependencyInjection requires generating code dynamically at runtime. For example, when using enumerable and generic ValueType services.")]
public partial class DefaultServiceProviderFactory : Microsoft.Extensions.DependencyInjection.IServiceProviderFactory<Microsoft.Extensions.DependencyInjection.IServiceCollection>
{
public DefaultServiceProviderFactory() { }
Expand All @@ -16,11 +15,8 @@ public DefaultServiceProviderFactory(Microsoft.Extensions.DependencyInjection.Se
}
public static partial class ServiceCollectionContainerBuilderExtensions
{
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Using Microsoft.Extensions.DependencyInjection requires generating code dynamically at runtime. For example, when using enumerable and generic ValueType services.")]
public static Microsoft.Extensions.DependencyInjection.ServiceProvider BuildServiceProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; }
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Using Microsoft.Extensions.DependencyInjection requires generating code dynamically at runtime. For example, when using enumerable and generic ValueType services.")]
public static Microsoft.Extensions.DependencyInjection.ServiceProvider BuildServiceProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, Microsoft.Extensions.DependencyInjection.ServiceProviderOptions options) { throw null; }
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Using Microsoft.Extensions.DependencyInjection requires generating code dynamically at runtime. For example, when using enumerable and generic ValueType services.")]
public static Microsoft.Extensions.DependencyInjection.ServiceProvider BuildServiceProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, bool validateScopes) { throw null; }
}
public sealed partial class ServiceProvider : System.IAsyncDisposable, System.IDisposable, System.IServiceProvider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@
<ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.DependencyInjection.Abstractions\ref\Microsoft.Extensions.DependencyInjection.Abstractions.csproj" />
</ItemGroup>

<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\RequiresDynamicCodeAttribute.cs" />
</ItemGroup>

<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'netstandard2.1'))">
<PackageReference Include="System.Threading.Tasks.Extensions" Version="$(SystemThreadingTasksExtensionsVersion)" />
<ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Bcl.AsyncInterfaces\ref\Microsoft.Bcl.AsyncInterfaces.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics.CodeAnalysis;

namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Default implementation of <see cref="IServiceProviderFactory{TContainerBuilder}"/>.
/// </summary>
[RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)]
public class DefaultServiceProviderFactory : IServiceProviderFactory<IServiceCollection>
{
private readonly ServiceProviderOptions _options;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,10 @@
<data name="TrimmingAnnotationsDoNotMatch_NewConstraint" xml:space="preserve">
<value>Generic implementation type '{0}' has a DefaultConstructorConstraint ('new()' constraint), but the generic service type '{1}' doesn't.</value>
</data>
</root>
<data name="AotCannotCreateEnumerableValueType" xml:space="preserve">
<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>
</data>
<data name="AotCannotCreateGenericValueType" xml:space="preserve">
<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>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.DependencyInjection.ServiceLookup;

namespace Microsoft.Extensions.DependencyInjection
{
Expand All @@ -18,8 +15,6 @@ public static class ServiceCollectionContainerBuilderExtensions
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> containing service descriptors.</param>
/// <returns>The <see cref="ServiceProvider"/>.</returns>

[RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)]
public static ServiceProvider BuildServiceProvider(this IServiceCollection services)
{
return BuildServiceProvider(services, ServiceProviderOptions.Default);
Expand All @@ -34,7 +29,6 @@ public static ServiceProvider BuildServiceProvider(this IServiceCollection servi
/// <c>true</c> to perform check verifying that scoped services never gets resolved from root provider; otherwise <c>false</c>.
/// </param>
/// <returns>The <see cref="ServiceProvider"/>.</returns>
[RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)]
public static ServiceProvider BuildServiceProvider(this IServiceCollection services, bool validateScopes)
{
return services.BuildServiceProvider(new ServiceProviderOptions { ValidateScopes = validateScopes });
Expand All @@ -49,7 +43,6 @@ public static ServiceProvider BuildServiceProvider(this IServiceCollection servi
/// Configures various service provider behaviors.
/// </param>
/// <returns>The <see cref="ServiceProvider"/>.</returns>
[RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)]
public static ServiceProvider BuildServiceProvider(this IServiceCollection services, ServiceProviderOptions options)
{
if (services is null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

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

CallSiteResultCacheLocation cacheLocation = CallSiteResultCacheLocation.Root;
var callSites = new List<ServiceCallSite>();

// If item type is not generic we can safely use descriptor cache
Expand Down Expand Up @@ -350,6 +355,9 @@ private static CallSiteResultCacheLocation GetCommonCacheLocation(CallSiteResult
Justification = "MakeGenericType here is used to create a closed generic implementation type given the closed service type. " +
"Trimming annotations on the generic types are verified when 'Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability' is set, which is set by default when PublishTrimmed=true. " +
"That check informs developers when these generic types don't have compatible trimming annotations.")]
[UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
Justification = "When ServiceProvider.VerifyAotCompatibility is true, which it is by default when PublishAot=true, " +
"this method ensures the generic types being created aren't using ValueTypes.")]
private ServiceCallSite? TryCreateOpenGeneric(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, int slot, bool throwOnConstraintViolation)
{
if (serviceType.IsConstructedGenericType &&
Expand All @@ -366,7 +374,13 @@ private static CallSiteResultCacheLocation GetCommonCacheLocation(CallSiteResult
Type closedType;
try
{
closedType = descriptor.ImplementationType.MakeGenericType(serviceType.GenericTypeArguments);
Type[] genericTypeArguments = serviceType.GenericTypeArguments;
if (ServiceProvider.VerifyAotCompatibility)
{
VerifyOpenGenericAotCompatibility(serviceType, genericTypeArguments);
}

closedType = descriptor.ImplementationType.MakeGenericType(genericTypeArguments);
}
catch (ArgumentException)
{
Expand Down Expand Up @@ -524,6 +538,24 @@ private ServiceCallSite CreateConstructorCallSite(
return parameterCallSites;
}

/// <summary>
/// Verifies none of the generic type arguments are ValueTypes.
/// </summary>
/// <remarks>
/// NativeAOT apps are not guaranteed that the native code for the closed generic of ValueType
/// has been generated. To catch these problems early, this verification is enabled at development-time
/// to inform the developer early that this scenario will not work once AOT'd.
/// </remarks>
private static void VerifyOpenGenericAotCompatibility(Type serviceType, Type[] genericTypeArguments)
{
foreach (Type typeArg in genericTypeArguments)
{
if (typeArg.IsValueType)
{
throw new InvalidOperationException(SR.Format(SR.AotCannotCreateGenericValueType, serviceType, typeArg));
}
}
}

public void Add(Type type, ServiceCallSite serviceCallSite)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Threading;

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

protected override object VisitIEnumerable(IEnumerableCallSite enumerableCallSite, RuntimeResolverContext context)
{
var array = Array.CreateInstance(
Array array = CreateArray(
enumerableCallSite.ItemType,
enumerableCallSite.ServiceCallSites.Length);

Expand All @@ -172,6 +172,15 @@ protected override object VisitIEnumerable(IEnumerableCallSite enumerableCallSit
array.SetValue(value, index);
}
return array;

[UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
Justification = "VerifyAotCompatibility ensures elementType is not a ValueType")]
static Array CreateArray(Type elementType, int length)
{
Debug.Assert(!ServiceProvider.VerifyAotCompatibility || !elementType.IsValueType, "VerifyAotCompatibility=true will throw during building the IEnumerableCallSite if elementType is a ValueType.");

return Array.CreateInstance(elementType, length);
}
}

protected override object VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@

namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
{
[RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)]
internal sealed class DynamicServiceProviderEngine : CompiledServiceProviderEngine
{
private readonly ServiceProvider _serviceProvider;

public DynamicServiceProviderEngine(ServiceProvider serviceProvider): base(serviceProvider)
[RequiresDynamicCode("Creates DynamicMethods")]
public DynamicServiceProviderEngine(ServiceProvider serviceProvider) : base(serviceProvider)
{
_serviceProvider = serviceProvider;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

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

protected override Expression VisitIEnumerable(IEnumerableCallSite callSite, object? context)
{
[UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
Justification = "VerifyAotCompatibility ensures elementType is not a ValueType")]
static MethodInfo GetArrayEmptyMethodInfo(Type elementType)
{
Debug.Assert(!ServiceProvider.VerifyAotCompatibility || !elementType.IsValueType, "VerifyAotCompatibility=true will throw during building the IEnumerableCallSite if elementType is a ValueType.");

return ServiceLookupHelpers.GetArrayEmptyMethodInfo(elementType);
}

if (callSite.ServiceCallSites.Length == 0)
{
return Expression.Constant(
ServiceLookupHelpers.GetArrayEmptyMethodInfo(callSite.ItemType)
GetArrayEmptyMethodInfo(callSite.ItemType)
.Invoke(obj: null, parameters: Array.Empty<object>()));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ internal sealed class ExpressionsServiceProviderEngine : ServiceProviderEngine
{
private readonly ExpressionResolverBuilder _expressionResolverBuilder;

[RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)]
public ExpressionsServiceProviderEngine(ServiceProvider serviceProvider)
{
_expressionResolverBuilder = new ExpressionResolverBuilder(serviceProvider);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,34 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;

namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
{
[RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)]
internal sealed class IEnumerableCallSite : ServiceCallSite
{
internal Type ItemType { get; }
internal ServiceCallSite[] ServiceCallSites { get; }

public IEnumerableCallSite(ResultCache cache, Type itemType, ServiceCallSite[] serviceCallSites) : base(cache)
{
Debug.Assert(!ServiceProvider.VerifyAotCompatibility || !itemType.IsValueType, "If VerifyAotCompatibility=true, an IEnumerableCallSite should not be created with a ValueType.");

ItemType = itemType;
ServiceCallSites = serviceCallSites;
}

[UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
Justification = "When ServiceProvider.VerifyAotCompatibility is true, which it is by default when PublishAot=true, " +
"CallSiteFactory ensures ItemType is not a ValueType.")]
public override Type ServiceType => typeof(IEnumerable<>).MakeGenericType(ItemType);
public override Type ImplementationType => ItemType.MakeArrayType();

[UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
Justification = "When ServiceProvider.VerifyAotCompatibility is true, which it is by default when PublishAot=true, " +
"CallSiteFactory ensures ItemType is not a ValueType.")]
public override Type ImplementationType => ItemType.MakeArrayType();

public override CallSiteKind Kind { get; } = CallSiteKind.IEnumerable;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
{
[RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)]
internal sealed class RuntimeServiceProviderEngine : ServiceProviderEngine
{
public static RuntimeServiceProviderEngine Instance { get; } = new RuntimeServiceProviderEngine();
Expand Down
Loading

0 comments on commit f424c0f

Please sign in to comment.