Skip to content
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

Resolve MakeGenericType ILLink warning in DependencyInjection #55102

Merged
merged 5 commits into from
Jul 31, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/workflow/trimming/feature-switches.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ configurations but their defaults might vary as any SDK can set the defaults dif
| EnableCppCLIHostActivation | System.Runtime.InteropServices.EnableCppCLIHostActivation | C++/CLI host activation code is disabled when set to false and related functionality can be trimmed. |
| MetadataUpdaterSupport | System.Reflection.Metadata.MetadataUpdater.IsSupported | Metadata update related code to be trimmed when set to false |
| _EnableConsumingManagedCodeFromNativeHosting | System.Runtime.InteropServices.EnableConsumingManagedCodeFromNativeHosting | Getting a managed function from native hosting is disabled when set to false and related functionality can be trimmed. |
| VerifyDependencyInjectionOpenGenericServiceTrimmability | Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability | When set to true, DependencyInjection will verify trimming annotations applied to open generic services are correct |

Any feature-switch which defines property can be set in csproj file or
on the command line as any other MSBuild property. Those without predefined property name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,10 @@ public static ObjectFactory CreateFactory(
ParameterExpression? argumentArray = Expression.Parameter(typeof(object[]), "argumentArray");
Expression? factoryExpressionBody = BuildFactoryExpression(constructor, parameterMap, provider, argumentArray);

var factoryLambda = Expression.Lambda<Func<IServiceProvider, object[], object>>(
var factoryLambda = Expression.Lambda<Func<IServiceProvider, object?[]?, object>>(
factoryExpressionBody, provider, argumentArray);

Func<IServiceProvider, object[], object>? result = factoryLambda.Compile();
Func<IServiceProvider, object?[]?, object>? result = factoryLambda.Compile();
return result.Invoke;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.1;netstandard2.0;net461</TargetFrameworks>
<TargetFrameworks>$(NetCoreAppCurrent);netstandard2.1;netstandard2.0;net461</TargetFrameworks>
<EnableDefaultItems>true</EnableDefaultItems>
<Nullable>enable</Nullable>
<PackageDescription>Abstractions for dependency injection.

Commonly Used Types:
Microsoft.Extensions.DependencyInjection.IServiceCollection</PackageDescription>
<!-- Use targeting pack references instead of granular ones in the project file. -->
<DisableImplicitAssemblyReferences>false</DisableImplicitAssemblyReferences>
</PropertyGroup>

<ItemGroup>
<Compile Include="$(CommonPath)Extensions\ParameterDefaultValue\ParameterDefaultValue.cs"
Link="Common\src\Extensions\ParameterDefaultValue\ParameterDefaultValue.cs" />
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\DynamicallyAccessedMembersAttribute.cs" />
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\DynamicallyAccessedMemberTypes.cs" />
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\UnconditionalSuppressMessageAttribute.cs" />
</ItemGroup>

<ItemGroup>
<Compile Include="$(CommonPath)Extensions\ParameterDefaultValue\ParameterDefaultValue.cs"
Link="Common\src\Extensions\ParameterDefaultValue\ParameterDefaultValue.cs" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0' or
$([MSBuild]::GetTargetFrameworkIdentifier('$(TargetFramework)')) == '.NETFramework'">
<PackageReference Include="System.Threading.Tasks.Extensions" Version="$(SystemThreadingTasksExtensionsVersion)" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<linker>
<assembly fullname="Microsoft.Extensions.DependencyInjection">
<type fullname="Microsoft.Extensions.DependencyInjection.ServiceProvider">
<method signature="System.Boolean get_VerifyOpenGenericServiceTrimmability()" body="stub" value="true" feature="Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability" featurevalue="true" />
</type>
</assembly>
</linker>

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.1;netstandard2.0;net461</TargetFrameworks>
<TargetFrameworks>$(NetCoreAppCurrent);netstandard2.1;netstandard2.0;net461</TargetFrameworks>
<!-- Debug IL generation -->
<ILEmitBackendSaveAssemblies>False</ILEmitBackendSaveAssemblies>
<Nullable>Annotations</Nullable>
<!-- Type 'Microsoft.Extensions.DependencyInjection.ServiceCollection' has been forwaded down.-->
<NoWarn>$(NoWarn);CP0001</NoWarn>
<PackageDescription>Default implementation of dependency injection for Microsoft.Extensions.DependencyInjection.</PackageDescription>
<DisableImplicitAssemblyReferences>false</DisableImplicitAssemblyReferences>
</PropertyGroup>

<!-- DesignTimeBuild requires all the TargetFramework Derived Properties to not be present in the first property group. -->
Expand All @@ -17,6 +18,17 @@
<DefineConstants Condition="$(TargetFramework.StartsWith('net4')) and '$(ILEmitBackendSaveAssemblies)' == 'True'">$(DefineConstants);SAVE_ASSEMBLIES</DefineConstants>
</PropertyGroup>

<ItemGroup>
<ILLinkSubstitutionsXmls Include="$(ILLinkDirectory)ILLink.Substitutions.xml" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
<!-- These types weren't available before net5.0 and need to be compiled into the assembly. -->
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\DynamicallyAccessedMembersAttribute.cs" />
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\DynamicallyAccessedMemberTypes.cs" />
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\UnconditionalSuppressMessageAttribute.cs" />
</ItemGroup>

<ItemGroup>
<Compile Include="**\*.cs" />
<Compile Remove="ServiceLookup\ILEmit\**\*.cs" />
Expand All @@ -27,11 +39,6 @@
Link="Common\src\Extensions\ParameterDefaultValue\ParameterDefaultValue.cs" />
<Compile Include="$(CommonPath)Extensions\TypeNameHelper\TypeNameHelper.cs"
Link="Common\src\Extensions\TypeNameHelper\TypeNameHelper.cs" />

<!-- These types weren't available before net5.0 and need to be compiled into the assembly. -->
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\DynamicallyAccessedMembersAttribute.cs" />
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\DynamicallyAccessedMemberTypes.cs" />
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\UnconditionalSuppressMessageAttribute.cs" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,4 +174,10 @@
<data name="CallSiteTypeNotSupported" xml:space="preserve">
<value>Call site type {0} is not supported</value>
</data>
</root>
<data name="TrimmingAnnotationsDoNotMatch" xml:space="preserve">
<value>Generic implementation type '{0}' has a DynamicallyAccessedMembers attribute applied to a generic argument type, but the service type '{1}' doesn't have a matching DynamicallyAccessedMembers attribute on its generic argument type.</value>
</data>
<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>
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,18 @@ private void Populate()
SR.Format(SR.TypeCannotBeActivated, implementationType, serviceType));
}

if (serviceType.GetGenericArguments().Length != implementationType.GetGenericArguments().Length)
Type[] serviceTypeGenericArguments = serviceType.GetGenericArguments();
Type[] implementationTypeGenericArguments = implementationType.GetGenericArguments();
if (serviceTypeGenericArguments.Length != implementationTypeGenericArguments.Length)
{
throw new ArgumentException(
SR.Format(SR.ArityOfOpenGenericServiceNotEqualArityOfOpenGenericImplementation, serviceType, implementationType), "descriptors");
}

if (ServiceProvider.VerifyOpenGenericServiceTrimmability)
{
ValidateTrimmingAnnotations(serviceType, serviceTypeGenericArguments, implementationType, implementationTypeGenericArguments);
}
}
else if (descriptor.ImplementationInstance == null && descriptor.ImplementationFactory == null)
{
Expand All @@ -77,6 +84,68 @@ private void Populate()
}
}

/// <summary>
/// Validates that two generic type definitions have compatible trimming annotations on their generic arguments.
/// </summary>
/// <remarks>
/// When open generic types are used in DI, there is an error when the concrete implementation type
/// has [DynamicallyAccessedMembers] attributes on a generic argument type, but the interface/service type
/// doesn't have matching annotations. The problem is that the trimmer doesn't see the members that need to
/// be preserved on the type being passed to the generic argument. But when the interface/service type also has
/// the annotations, the trimmer will see which members need to be preserved on the closed generic argument type.
/// </remarks>
private static void ValidateTrimmingAnnotations(
Type serviceType,
Type[] serviceTypeGenericArguments,
Type implementationType,
Type[] implementationTypeGenericArguments)
{
Debug.Assert(serviceTypeGenericArguments.Length == implementationTypeGenericArguments.Length);

for (int i = 0; i < serviceTypeGenericArguments.Length; i++)
{
Type serviceGenericType = serviceTypeGenericArguments[i];
Type implementationGenericType = implementationTypeGenericArguments[i];

DynamicallyAccessedMemberTypes serviceDynamicallyAccessedMembers = GetDynamicallyAccessedMemberTypes(serviceGenericType);
DynamicallyAccessedMemberTypes implementationDynamicallyAccessedMembers = GetDynamicallyAccessedMemberTypes(implementationGenericType);

if (!AreCompatible(serviceDynamicallyAccessedMembers, implementationDynamicallyAccessedMembers))
{
throw new ArgumentException(SR.Format(SR.TrimmingAnnotationsDoNotMatch, implementationType.FullName, serviceType.FullName));
}

bool serviceHasNewConstraint = serviceGenericType.GenericParameterAttributes.HasFlag(GenericParameterAttributes.DefaultConstructorConstraint);
bool implementationHasNewConstraint = implementationGenericType.GenericParameterAttributes.HasFlag(GenericParameterAttributes.DefaultConstructorConstraint);
if (implementationHasNewConstraint && !serviceHasNewConstraint)
{
throw new ArgumentException(SR.Format(SR.TrimmingAnnotationsDoNotMatch_NewConstraint, implementationType.FullName, serviceType.FullName));
}
}
}

private static DynamicallyAccessedMemberTypes GetDynamicallyAccessedMemberTypes(Type serviceGenericType)
{
foreach (CustomAttributeData attributeData in serviceGenericType.GetCustomAttributesData())
{
if (attributeData.AttributeType.FullName == "System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute" &&
attributeData.ConstructorArguments.Count == 1 &&
attributeData.ConstructorArguments[0].ArgumentType.FullName == "System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes")
{
return (DynamicallyAccessedMemberTypes)(int)attributeData.ConstructorArguments[0].Value;
}
}

return DynamicallyAccessedMemberTypes.None;
}

private static bool AreCompatible(DynamicallyAccessedMemberTypes serviceDynamicallyAccessedMembers, DynamicallyAccessedMemberTypes implementationDynamicallyAccessedMembers)
{
// The DynamicallyAccessedMemberTypes don't need to exactly match.
// The service type needs to preserve a superset of the members required by the implementation type.
return serviceDynamicallyAccessedMembers.HasFlag(implementationDynamicallyAccessedMembers);
}

// For unit testing
internal int? GetSlot(ServiceDescriptor serviceDescriptor)
{
Expand Down Expand Up @@ -273,6 +342,10 @@ private ServiceCallSite TryCreateExact(ServiceDescriptor descriptor, Type servic
return null;
}

[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2055:MakeGenericType",
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.")]
private ServiceCallSite TryCreateOpenGeneric(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, int slot, bool throwOnConstraintViolation)
{
if (serviceType.IsConstructedGenericType &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ public sealed class ServiceProvider : IServiceProvider, IDisposable, IAsyncDispo

internal ServiceProviderEngineScope Root { get; }

internal static bool VerifyOpenGenericServiceTrimmability { get; } =
AppContext.TryGetSwitch("Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability", out bool verifyOpenGenerics) ? verifyOpenGenerics : false;

internal ServiceProvider(IEnumerable<ServiceDescriptor> serviceDescriptors, ServiceProviderOptions options)
{
// note that Root needs to be set before calling GetEngine(), because the engine may need to access Root
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent);net461</TargetFrameworks>
<EnableDefaultItems>true</EnableDefaultItems>
<IncludeRemoteExecutor>true</IncludeRemoteExecutor>
<!-- CS0436: DynamicallyAccessedMemberTypes conflicts with imported type -->
<NoWarn Condition="'$(TargetFramework)' == 'net461'">$(NoWarn);CS0436</NoWarn>
</PropertyGroup>

<ItemGroup>
Expand All @@ -11,6 +14,12 @@
Link="Shared\SingleThreadedSynchronizationContext.cs" />
</ItemGroup>

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

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonVersion)" />
<ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.DependencyInjection.Abstractions\src\Microsoft.Extensions.DependencyInjection.Abstractions.csproj" SkipUseReferenceAssembly="true" />
Expand Down
Loading