Skip to content

Commit

Permalink
Cleanup ActivatorUtilities
Browse files Browse the repository at this point in the history
Noticed a bunch of unused APIs in ActivatorUtilities when attempting to enable trimmability on Http.Abstractions
  • Loading branch information
pranavkm committed Feb 25, 2022
1 parent f23cfb8 commit f75d963
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 291 deletions.
259 changes: 7 additions & 252 deletions src/Shared/ActivatorUtilities/ActivatorUtilities.cs
Original file line number Diff line number Diff line change
@@ -1,40 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable warnings
#nullable enable annotations
#nullable enable

using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.ExceptionServices;

#if ActivatorUtilities_In_DependencyInjection
using Microsoft.Extensions.Internal;

namespace Microsoft.Extensions.DependencyInjection;
#else
namespace Microsoft.Extensions.Internal;
#endif

/// <summary>
/// Helper code for the various activator services.
/// </summary>
#if ActivatorUtilities_In_DependencyInjection
public
#else
// Do not take a dependency on this class unless you are explicitly trying to avoid taking a
// dependency on Microsoft.AspNetCore.DependencyInjection.Abstractions.
internal
#endif
static class ActivatorUtilities
internal static class ActivatorUtilities
{
private const DynamicallyAccessedMemberTypes ActivatorAccessibility = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors;

private static readonly MethodInfo GetServiceInfo =
GetMethodInfo<Func<IServiceProvider, Type, Type, bool, object>>((sp, t, r, c) => GetService(sp, t, r, c));

/// <summary>
/// Instantiate a type with constructor arguments provided directly and/or from an <see cref="IServiceProvider"/>.
/// </summary>
Expand All @@ -47,8 +25,7 @@ public static object CreateInstance(
[DynamicallyAccessedMembers(ActivatorAccessibility)] Type instanceType,
params object[] parameters)
{
int bestLength = -1;
var seenPreferred = false;
var bestLength = -1;

ConstructorMatcher bestMatcher = default;

Expand All @@ -57,29 +34,13 @@ public static object CreateInstance(
foreach (var constructor in instanceType.GetConstructors())
{
var matcher = new ConstructorMatcher(constructor);
var isPreferred = constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute), false);
var length = matcher.Match(parameters);

if (isPreferred)
{
if (seenPreferred)
{
ThrowMultipleCtorsMarkedWithAttributeException();
}

if (length == -1)
{
ThrowMarkedCtorDoesNotTakeAllProvidedArguments();
}
}

if (isPreferred || bestLength < length)
if (bestLength < length)
{
bestLength = length;
bestMatcher = matcher;
}

seenPreferred |= isPreferred;
}
}

Expand All @@ -92,35 +53,6 @@ public static object CreateInstance(
return bestMatcher.CreateInstance(provider);
}

/// <summary>
/// Create a delegate that will instantiate a type with constructor arguments provided directly
/// and/or from an <see cref="IServiceProvider"/>.
/// </summary>
/// <param name="instanceType">The type to activate</param>
/// <param name="argumentTypes">
/// The types of objects, in order, that will be passed to the returned function as its second parameter
/// </param>
/// <returns>
/// A factory that will instantiate instanceType using an <see cref="IServiceProvider"/>
/// and an argument array containing objects matching the types defined in argumentTypes
/// </returns>
public static ObjectFactory CreateFactory(
[DynamicallyAccessedMembers(ActivatorAccessibility)] Type instanceType,
Type[] argumentTypes)
{
FindApplicableConstructor(instanceType, argumentTypes, out ConstructorInfo constructor, out int?[] parameterMap);

var provider = Expression.Parameter(typeof(IServiceProvider), "provider");
var argumentArray = Expression.Parameter(typeof(object[]), "argumentArray");
var factoryExpressionBody = BuildFactoryExpression(constructor, parameterMap, provider, argumentArray);

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

var result = factoryLamda.Compile();
return result.Invoke;
}

/// <summary>
/// Instantiate a type with constructor arguments provided directly and/or from an <see cref="IServiceProvider"/>.
/// </summary>
Expand Down Expand Up @@ -161,184 +93,17 @@ public static object GetServiceOrCreateInstance(
return provider.GetService(type) ?? CreateInstance(provider, type);
}

private static MethodInfo GetMethodInfo<T>(Expression<T> expr)
{
var mc = (MethodCallExpression)expr.Body;
return mc.Method;
}

private static object? GetService(IServiceProvider sp, Type type, Type requiredBy, bool isDefaultParameterRequired)
{
var service = sp.GetService(type);
if (service == null && !isDefaultParameterRequired)
{
var message = $"Unable to resolve service for type '{type}' while attempting to activate '{requiredBy}'.";
throw new InvalidOperationException(message);
}
return service;
}

private static Expression BuildFactoryExpression(
ConstructorInfo constructor,
int?[] parameterMap,
Expression serviceProvider,
Expression factoryArgumentArray)
{
var constructorParameters = constructor.GetParameters();
var constructorArguments = new Expression[constructorParameters.Length];

for (var i = 0; i < constructorParameters.Length; i++)
{
var constructorParameter = constructorParameters[i];
var parameterType = constructorParameter.ParameterType;
var hasDefaultValue = ParameterDefaultValue.TryGetDefaultValue(constructorParameter, out var defaultValue);

if (parameterMap[i] != null)
{
constructorArguments[i] = Expression.ArrayAccess(factoryArgumentArray, Expression.Constant(parameterMap[i]));
}
else
{
var parameterTypeExpression = new Expression[] { serviceProvider,
Expression.Constant(parameterType, typeof(Type)),
Expression.Constant(constructor.DeclaringType, typeof(Type)),
Expression.Constant(hasDefaultValue) };
constructorArguments[i] = Expression.Call(GetServiceInfo, parameterTypeExpression);
}

// Support optional constructor arguments by passing in the default value
// when the argument would otherwise be null.
if (hasDefaultValue)
{
var defaultValueExpression = Expression.Constant(defaultValue);
constructorArguments[i] = Expression.Coalesce(constructorArguments[i], defaultValueExpression);
}

constructorArguments[i] = Expression.Convert(constructorArguments[i], parameterType);
}

return Expression.New(constructor, constructorArguments);
}

private static void FindApplicableConstructor(
[DynamicallyAccessedMembers(ActivatorAccessibility)] Type instanceType,
Type[] argumentTypes,
out ConstructorInfo matchingConstructor,
out int?[] parameterMap)
{
matchingConstructor = null!;
parameterMap = null!;

if (!TryFindPreferredConstructor(instanceType, argumentTypes, ref matchingConstructor!, ref parameterMap!) &&
!TryFindMatchingConstructor(instanceType, argumentTypes, ref matchingConstructor!, ref parameterMap!))
{
var message = $"A suitable constructor for type '{instanceType}' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.";
throw new InvalidOperationException(message);
}
}

// Tries to find constructor based on provided argument types
private static bool TryFindMatchingConstructor(
[DynamicallyAccessedMembers(ActivatorAccessibility)] Type instanceType,
Type[] argumentTypes,
ref ConstructorInfo matchingConstructor,
ref int?[] parameterMap)
{
foreach (var constructor in instanceType.GetConstructors())
{
if (TryCreateParameterMap(constructor.GetParameters(), argumentTypes, out int?[] tempParameterMap))
{
if (matchingConstructor != null)
{
throw new InvalidOperationException($"Multiple constructors accepting all given argument types have been found in type '{instanceType}'. There should only be one applicable constructor.");
}

matchingConstructor = constructor;
parameterMap = tempParameterMap;
}
}

return matchingConstructor != null;
}

// Tries to find constructor marked with ActivatorUtilitiesConstructorAttribute
private static bool TryFindPreferredConstructor(
[DynamicallyAccessedMembers(ActivatorAccessibility)] Type instanceType,
Type[] argumentTypes,
ref ConstructorInfo matchingConstructor,
ref int?[] parameterMap)
{
var seenPreferred = false;
foreach (var constructor in instanceType.GetConstructors())
{
if (constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute), false))
{
if (seenPreferred)
{
ThrowMultipleCtorsMarkedWithAttributeException();
}

if (!TryCreateParameterMap(constructor.GetParameters(), argumentTypes, out int?[] tempParameterMap))
{
ThrowMarkedCtorDoesNotTakeAllProvidedArguments();
}

matchingConstructor = constructor;
parameterMap = tempParameterMap;
seenPreferred = true;
}
}

return matchingConstructor != null;
}

// Creates an injective parameterMap from givenParameterTypes to assignable constructorParameters.
// Returns true if each given parameter type is assignable to a unique; otherwise, false.
private static bool TryCreateParameterMap(ParameterInfo[] constructorParameters, Type[] argumentTypes, out int?[] parameterMap)
{
parameterMap = new int?[constructorParameters.Length];

for (var i = 0; i < argumentTypes.Length; i++)
{
var foundMatch = false;
var givenParameter = argumentTypes[i];

for (var j = 0; j < constructorParameters.Length; j++)
{
if (parameterMap[j] != null)
{
// This ctor parameter has already been matched
continue;
}

if (constructorParameters[j].ParameterType.IsAssignableFrom(givenParameter))
{
foundMatch = true;
parameterMap[j] = i;
break;
}
}

if (!foundMatch)
{
return false;
}
}

return true;
}

private struct ConstructorMatcher
{
private readonly ConstructorInfo _constructor;
private readonly ParameterInfo[] _parameters;
private readonly object[] _parameterValues;
private readonly object?[] _parameterValues;

public ConstructorMatcher(ConstructorInfo constructor)
{
_constructor = constructor;
_parameters = _constructor.GetParameters();
_parameterValues = new object[_parameters.Length];
_parameterValues = new object?[_parameters.Length];
}

public int Match(object[] givenParameters)
Expand Down Expand Up @@ -417,15 +182,5 @@ public object CreateInstance(IServiceProvider provider)
#endif
}
}

private static void ThrowMultipleCtorsMarkedWithAttributeException()
{
throw new InvalidOperationException($"Multiple constructors were marked with {nameof(ActivatorUtilitiesConstructorAttribute)}.");
}

private static void ThrowMarkedCtorDoesNotTakeAllProvidedArguments()
{
throw new InvalidOperationException($"Constructor marked with {nameof(ActivatorUtilitiesConstructorAttribute)} does not accept all given argument types.");
}
}

This file was deleted.

23 changes: 0 additions & 23 deletions src/Shared/ActivatorUtilities/ObjectFactory.cs

This file was deleted.

26 changes: 26 additions & 0 deletions src/Tools/LinkabilityChecker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
## Trimmer baseline verification

This project is used to verify that ASP.NET Core APIs do not result in additional trimmer warnings other than the ones that are already known. It works by running the trimmer in "library mode", rooting all of it's public APIs, using a set of baselined suppressions and ensuring no new trimmer warnings are produced.

### Enabling trimming on a new project

* Update the project file to enable trimming `<Trimmable>true</Trimmable>
* Run `eng/scripts/GenerateProjectList.ps1` to update the list of projects that are known to be trimmable
* Build this project

### Updating the baselines

If a suppressed warning has been resolved, or if new trimmer warnings are to be baselined, run the following command:

```
dotnet build /p:GenerateLinkerWarningSuppressions=true
```

This should update the WarningSuppressions.xml files associated with projects.

⚠️ Note that the generated file sometimes messing up formatting for some compiler generated nested types and you may need to manually touch up these files on regenerating. The generated file uses braces `{...}` instead of angle brackets `<...>`:

```diff
- LegacyRouteTableFactory.&lt;&gt;c.{Create}b__2_1(System.Reflection.Assembly)
+ LegacyRouteTableFactory.&lt;&gt;c.&lt;Create&gt;b__2_1(System.Reflection.Assembly)
```

0 comments on commit f75d963

Please sign in to comment.