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

Support unloadability in DispatchProxy. #62095

Merged
merged 8 commits into from
Dec 3, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<Reference Include="System.Reflection.Primitives" />
<Reference Include="System.Resources.ResourceManager" />
<Reference Include="System.Runtime" />
<Reference Include="System.Runtime.Loader" />
<Reference Include="System.Threading" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection.Emit;
using System.Runtime.ExceptionServices;
using System.Runtime.CompilerServices;
using System.Runtime.Loader;
using System.Threading;

namespace System.Reflection
Expand Down Expand Up @@ -44,32 +45,18 @@ internal static class DispatchProxyGenerator
// It is the first field in the class and the first ctor parameter.
private const int MethodInfosFieldAndCtorParameterIndex = 0;

// Proxies are requested for a pair of types: base type and interface type.
// The generated proxy will subclass the given base type and implement the interface type.
// We maintain a cache keyed by 'base type' containing a dictionary keyed by interface type,
// containing the generated proxy type for that pair. There are likely to be few (maybe only 1)
// base type in use for many interface types.
// Note: this differs from Silverlight's RealProxy implementation which keys strictly off the
// interface type. But this does not allow the same interface type to be used with more than a
// single base type. The implementation here permits multiple interface types to be used with
// multiple base types, and the generated proxy types will be unique.
// This cache of generated types grows unbounded, one element per unique T/ProxyT pair.
// This approach is used to prevent regenerating identical proxy types for identical T/Proxy pairs,
// which would ultimately be a more expensive leak.
// Proxy instances are not cached. Their lifetime is entirely owned by the caller of DispatchProxy.Create.
private static readonly Dictionary<Type, Dictionary<Type, GeneratedTypeInfo>> s_baseTypeAndInterfaceToGeneratedProxyType = new Dictionary<Type, Dictionary<Type, GeneratedTypeInfo>>();
private static readonly ProxyAssembly s_proxyAssembly = new ProxyAssembly();
// We group AssemblyBuilders by the ALC of the base type's assembly.
// This allows us to granularly unload generated proxy types.
private static readonly ConditionalWeakTable<AssemblyLoadContext, ProxyAssembly> s_alcProxyAssemblyMap = new();
private static readonly MethodInfo s_dispatchProxyInvokeMethod = typeof(DispatchProxy).GetMethod("Invoke", BindingFlags.NonPublic | BindingFlags.Instance)!;
private static readonly MethodInfo s_getTypeFromHandleMethod = typeof(Type).GetRuntimeMethod("GetTypeFromHandle", new Type[] { typeof(RuntimeTypeHandle) })!;
private static readonly MethodInfo s_getTypeFromHandleMethod = typeof(Type).GetMethod("GetTypeFromHandle", new Type[] { typeof(RuntimeTypeHandle) })!;
private static readonly MethodInfo s_makeGenericMethodMethod = GetGenericMethodMethodInfo();

[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "MakeGenericMethod is safe here because the user code invoking the generic method will reference " +
"the GenericTypes being used, which will guarantee the requirements of the generic method.")]
private static MethodInfo GetGenericMethodMethodInfo()
{
return typeof(MethodInfo).GetMethod("MakeGenericMethod", new Type[] { typeof(Type[]) })!;
}
private static MethodInfo GetGenericMethodMethodInfo() =>
typeof(MethodInfo).GetMethod("MakeGenericMethod", new Type[] { typeof(Type[]) })!;

// Returns a new instance of a proxy the derives from 'baseType' and implements 'interfaceType'
internal static object CreateProxyInstance(
Expand All @@ -79,78 +66,12 @@ internal static object CreateProxyInstance(
Debug.Assert(baseType != null);
Debug.Assert(interfaceType != null);

GeneratedTypeInfo proxiedType = GetProxyType(baseType, interfaceType);
return Activator.CreateInstance(proxiedType.GeneratedType, new object[] { proxiedType.MethodInfos })!;
}

private static GeneratedTypeInfo GetProxyType(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type baseType,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type interfaceType)
{
lock (s_baseTypeAndInterfaceToGeneratedProxyType)
{
if (!s_baseTypeAndInterfaceToGeneratedProxyType.TryGetValue(baseType, out Dictionary<Type, GeneratedTypeInfo>? interfaceToProxy))
{
interfaceToProxy = new Dictionary<Type, GeneratedTypeInfo>();
s_baseTypeAndInterfaceToGeneratedProxyType[baseType] = interfaceToProxy;
}

if (!interfaceToProxy.TryGetValue(interfaceType, out GeneratedTypeInfo? generatedProxy))
{
generatedProxy = GenerateProxyType(baseType, interfaceType);
interfaceToProxy[interfaceType] = generatedProxy;
}

return generatedProxy;
}
}

// Unconditionally generates a new proxy type derived from 'baseType' and implements 'interfaceType'
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2062:UnrecognizedReflectionPattern",
Justification = "interfaceType is annotated as preserve All members, so any Types returned from GetInterfaces should be preserved as well once https://github.com/mono/linker/issues/1731 is fixed.")]
private static GeneratedTypeInfo GenerateProxyType(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type baseType,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type interfaceType)
{
// Parameter validation is deferred until the point we need to create the proxy.
// This prevents unnecessary overhead revalidating cached proxy types.

// The interface type must be an interface, not a class
if (!interfaceType.IsInterface)
{
// "T" is the generic parameter seen via the public contract
throw new ArgumentException(SR.Format(SR.InterfaceType_Must_Be_Interface, interfaceType.FullName), "T");
}

// The base type cannot be sealed because the proxy needs to subclass it.
if (baseType.IsSealed)
{
// "TProxy" is the generic parameter seen via the public contract
throw new ArgumentException(SR.Format(SR.BaseType_Cannot_Be_Sealed, baseType.FullName), "TProxy");
}

// The base type cannot be abstract
if (baseType.IsAbstract)
{
throw new ArgumentException(SR.Format(SR.BaseType_Cannot_Be_Abstract, baseType.FullName), "TProxy");
}

// The base type must have a public default ctor
if (baseType.GetConstructor(Type.EmptyTypes) == null)
{
throw new ArgumentException(SR.Format(SR.BaseType_Must_Have_Default_Ctor, baseType.FullName), "TProxy");
}

// Create a type that derives from 'baseType' provided by caller
ProxyBuilder pb = s_proxyAssembly.CreateProxy("generatedProxy", baseType);

foreach (Type t in interfaceType.GetInterfaces())
pb.AddInterfaceImpl(t);
AssemblyLoadContext? alc = AssemblyLoadContext.GetLoadContext(baseType.Assembly);
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved
Debug.Assert(alc != null);

pb.AddInterfaceImpl(interfaceType);

GeneratedTypeInfo generatedProxyType = pb.CreateType();
return generatedProxyType;
ProxyAssembly proxyAssembly = s_alcProxyAssemblyMap.GetValue(alc, static x => new ProxyAssembly(x));
GeneratedTypeInfo proxiedType = proxyAssembly.GetProxyType(baseType, interfaceType);
return Activator.CreateInstance(proxiedType.GeneratedType, new object[] { proxiedType.MethodInfos })!;
}

private sealed class GeneratedTypeInfo
Expand All @@ -170,16 +91,43 @@ public GeneratedTypeInfo(

private sealed class ProxyAssembly
{
// Proxies are requested for a pair of types: base type and interface type.
// The generated proxy will subclass the given base type and implement the interface type.
// We maintain a cache keyed by 'base type' containing a dictionary keyed by interface type,
// containing the generated proxy type for that pair. There are likely to be few (maybe only 1)
// base type in use for many interface types.
// Note: this differs from Silverlight's RealProxy implementation which keys strictly off the
// interface type. But this does not allow the same interface type to be used with more than a
// single base type. The implementation here permits multiple interface types to be used with
// multiple base types, and the generated proxy types will be unique.
// This cache of generated types grows unbounded, one element per unique T/ProxyT pair.
// This approach is used to prevent regenerating identical proxy types for identical T/Proxy pairs,
// which would ultimately be a more expensive leak.
// Proxy instances are not cached. Their lifetime is entirely owned by the caller of DispatchProxy.Create.
private readonly Dictionary<Type, Dictionary<Type, GeneratedTypeInfo>> _baseTypeAndInterfaceToGeneratedProxyType = new Dictionary<Type, Dictionary<Type, GeneratedTypeInfo>>();

private readonly AssemblyBuilder _ab;
private readonly ModuleBuilder _mb;
private int _typeId;

private readonly HashSet<string?> _ignoresAccessAssemblyNames = new HashSet<string?>();
private readonly HashSet<string> _ignoresAccessAssemblyNames = new HashSet<string>();
private ConstructorInfo? _ignoresAccessChecksToAttributeConstructor;

public ProxyAssembly()
public ProxyAssembly(AssemblyLoadContext alc)
{
_ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("ProxyBuilder"), AssemblyBuilderAccess.Run);
string name;
if (alc == AssemblyLoadContext.Default)
{
name = "ProxyBuilder";
}
else
{
string? alcName = alc.Name;
name = string.IsNullOrEmpty(alcName) ? $"DispatchProxyTypes.{alc.GetHashCode()}" : $"DispatchProxyTypes.{alcName}";
}
AssemblyBuilderAccess builderAccess =
alc.IsCollectible ? AssemblyBuilderAccess.RunAndCollect : AssemblyBuilderAccess.Run;
_ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(name), builderAccess);
_mb = _ab.DefineDynamicModule("testmod");
}

Expand All @@ -199,9 +147,79 @@ internal ConstructorInfo IgnoresAccessChecksAttributeConstructor
}
}

public GeneratedTypeInfo GetProxyType(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type baseType,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type interfaceType)
{
lock (_baseTypeAndInterfaceToGeneratedProxyType)
{
if (!_baseTypeAndInterfaceToGeneratedProxyType.TryGetValue(baseType, out Dictionary<Type, GeneratedTypeInfo>? interfaceToProxy))
{
interfaceToProxy = new Dictionary<Type, GeneratedTypeInfo>();
_baseTypeAndInterfaceToGeneratedProxyType[baseType] = interfaceToProxy;
}

if (!interfaceToProxy.TryGetValue(interfaceType, out GeneratedTypeInfo? generatedProxy))
{
generatedProxy = GenerateProxyType(baseType, interfaceType);
interfaceToProxy[interfaceType] = generatedProxy;
}

return generatedProxy;
}
}

// Unconditionally generates a new proxy type derived from 'baseType' and implements 'interfaceType'
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2062:UnrecognizedReflectionPattern",
Justification = "interfaceType is annotated as preserve All members, so any Types returned from GetInterfaces should be preserved as well once https://github.com/mono/linker/issues/1731 is fixed.")]
private GeneratedTypeInfo GenerateProxyType(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type baseType,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type interfaceType)
{
// Parameter validation is deferred until the point we need to create the proxy.
// This prevents unnecessary overhead revalidating cached proxy types.

// The interface type must be an interface, not a class
if (!interfaceType.IsInterface)
{
// "T" is the generic parameter seen via the public contract
throw new ArgumentException(SR.Format(SR.InterfaceType_Must_Be_Interface, interfaceType.FullName), "T");
}

// The base type cannot be sealed because the proxy needs to subclass it.
if (baseType.IsSealed)
{
// "TProxy" is the generic parameter seen via the public contract
throw new ArgumentException(SR.Format(SR.BaseType_Cannot_Be_Sealed, baseType.FullName), "TProxy");
}

// The base type cannot be abstract
if (baseType.IsAbstract)
{
throw new ArgumentException(SR.Format(SR.BaseType_Cannot_Be_Abstract, baseType.FullName), "TProxy");
}

// The base type must have a public default ctor
if (baseType.GetConstructor(Type.EmptyTypes) == null)
{
throw new ArgumentException(SR.Format(SR.BaseType_Must_Have_Default_Ctor, baseType.FullName), "TProxy");
}

// Create a type that derives from 'baseType' provided by caller
ProxyBuilder pb = CreateProxy("generatedProxy", baseType);

foreach (Type t in interfaceType.GetInterfaces())
pb.AddInterfaceImpl(t);

pb.AddInterfaceImpl(interfaceType);

GeneratedTypeInfo generatedProxyType = pb.CreateType();
return generatedProxyType;
}

[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:UnrecognizedReflectionPattern",
Justification = "Only the parameterless ctor is referenced on proxyBaseType. Other members can be trimmed if unused.")]
public ProxyBuilder CreateProxy(
private ProxyBuilder CreateProxy(
string name,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type proxyBaseType)
{
Expand Down Expand Up @@ -231,10 +249,9 @@ internal void EnsureTypeIsVisible(Type type)
if (!type.IsVisible)
{
string assemblyName = type.Assembly.GetName().Name!;
if (!_ignoresAccessAssemblyNames.Contains(assemblyName))
if (_ignoresAccessAssemblyNames.Add(assemblyName))
{
GenerateInstanceOfIgnoresAccessChecksToAttribute(assemblyName);
_ignoresAccessAssemblyNames.Add(assemblyName);
}
}
}
Expand Down Expand Up @@ -278,11 +295,11 @@ private void Complete()
ILGenerator il = cb.GetILGenerator();

// chained ctor call
ConstructorInfo? baseCtor = _proxyBaseType.GetConstructor(Type.EmptyTypes);
ConstructorInfo baseCtor = _proxyBaseType.GetConstructor(Type.EmptyTypes)!;
Debug.Assert(baseCtor != null);

il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, baseCtor!);
il.Emit(OpCodes.Call, baseCtor);

// store all the fields
for (int i = 0; i < args.Length; i++)
Expand Down Expand Up @@ -520,64 +537,7 @@ private MethodBuilder AddMethodImpl(MethodInfo mi, int methodInfoIndex)
return mdb;
}

// TypeCode does not exist in ProjectK or ProjectN.
// This lookup method was copied from PortableLibraryThunks\Internal\PortableLibraryThunks\System\TypeThunks.cs
// but returns the integer value equivalent to its TypeCode enum.
private static int GetTypeCode(Type? type)
{
if (type == null)
return 0; // TypeCode.Empty;

if (type == typeof(bool))
return 3; // TypeCode.Boolean;

if (type == typeof(char))
return 4; // TypeCode.Char;

if (type == typeof(sbyte))
return 5; // TypeCode.SByte;

if (type == typeof(byte))
return 6; // TypeCode.Byte;

if (type == typeof(short))
return 7; // TypeCode.Int16;

if (type == typeof(ushort))
return 8; // TypeCode.UInt16;

if (type == typeof(int))
return 9; // TypeCode.Int32;

if (type == typeof(uint))
return 10; // TypeCode.UInt32;

if (type == typeof(long))
return 11; // TypeCode.Int64;

if (type == typeof(ulong))
return 12; // TypeCode.UInt64;

if (type == typeof(float))
return 13; // TypeCode.Single;

if (type == typeof(double))
return 14; // TypeCode.Double;

if (type == typeof(decimal))
return 15; // TypeCode.Decimal;

if (type == typeof(DateTime))
return 16; // TypeCode.DateTime;

if (type == typeof(string))
return 18; // TypeCode.String;

if (type.IsEnum)
return GetTypeCode(Enum.GetUnderlyingType(type));

return 1; // TypeCode.Object;
}
private static int GetTypeCode(Type type) => (int)Type.GetTypeCode(type);

private static readonly OpCode[] s_convOpCodes = new OpCode[] {
OpCodes.Nop, //Empty = 0,
Expand Down
Loading