Skip to content

DispatchProxy cannot work with types from collectible assemblies. #62050

Description

@teo-tsirpanis

Description

The System.Reflection.DispatchProxy type creates its types in an AssemblyBuilder created with AssemblyBuilderAccess.Run, leading to not just a resource leak when DispatchProxies with collectible types are used, but a total inability, because "[a] non-collectible assembly may not reference a collectible assembly".

Reproduction Steps

I wrote the following demo that tests the unloadability of an ALC:

using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Loader;

public class SimpleProxy<T> : DispatchProxy where T: class
{
    public T Target { get; private set; }

    public static T Decorate(T target = null)
    {
        var proxy = (SimpleProxy<T>) (object) Create<T, SimpleProxy<T>>();
        proxy.Target = target ?? Activator.CreateInstance<T>();
        return proxy as T;
    }

    protected override object Invoke(MethodInfo targetMethod, object[] args)
    {
        var result = targetMethod.Invoke(Target, args);
        return result;
    }
}

public interface ITestObject { }

public class TestObject : ITestObject
{
    public static ITestObject Create() => new TestObject();
    public static ITestObject CreateDecorated() =>
        SimpleProxy<ITestObject>.Decorate(Create());
}

static class Program
{
    [MethodImpl(MethodImplOptions.NoInlining)]
    static WeakReference CreateObjectInUnloadableALC(bool createDecorated)
    {
        var alc = new AssemblyLoadContext(null, true);
        var asm = alc.LoadFromAssemblyPath(typeof(Program).Assembly.Location);
        var typ = asm.GetType(nameof(TestObject));
        var methodName = createDecorated ? nameof(TestObject.CreateDecorated) : nameof(TestObject.Create);
        var method = typ.GetMethod(methodName);
        method.Invoke(null, null);
        return new WeakReference(alc);
    }

    static void CollectUntilWeakReferenceDies(WeakReference wr)
    {
        int i;
        for (i = 0; i < 10 && wr.IsAlive; i++)
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
        if (wr.IsAlive)
            Console.WriteLine("Still alive");
        else
            Console.WriteLine($"Collected after {i} GCs.");
    }

    static void Main()
    {
        Console.WriteLine("Testing with a raw object");
        var wr = CreateObjectInUnloadableALC(false);
        CollectUntilWeakReferenceDies(wr);
        Console.WriteLine("Testing with an object wrapped by DispatchProxy");
        wr = CreateObjectInUnloadableALC(true);
        CollectUntilWeakReferenceDies(wr);
    }
}

Expected behavior

Testing with a raw object
Collected after 1 GCs.
Testing with an object wrapped by DispatchProxy
Collected after 1 GCs.

Actual behavior

Testing with a raw object
Collected after 1 GCs.
Testing with an object wrapped by DispatchProxy
Unhandled exception. System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
 ---> System.NotSupportedException: A non-collectible assembly may not reference a collectible assembly.
   at System.Reflection.Emit.ModuleBuilder.GetTypeRef(QCallModule module, String strFullName, QCallModule refedModule, String strRefedModuleFileName, Int32 tkResolution)
   at System.Reflection.Emit.ModuleBuilder.GetTypeRefNested(Type type, Module refedModule, String strRefedModuleFileName)
   at System.Reflection.Emit.ModuleBuilder.GetTypeTokenWorkerNoLock(Type type, Boolean getGenericDefinition)
   at System.Reflection.Emit.ModuleBuilder.GetTypeTokenInternal(Type type, Boolean getGenericDefinition)
   at System.Reflection.Emit.SignatureHelper.AddOneArgTypeHelperWorker(Type clsArgument, Boolean lastWasGenericInst)
   at System.Reflection.Emit.SignatureHelper.AddOneArgTypeHelperWorker(Type clsArgument, Boolean lastWasGenericInst)
   at System.Reflection.Emit.SignatureHelper.GetTypeSigToken(Module module, Type type)
   at System.Reflection.Emit.ModuleBuilder.GetTypeTokenWorkerNoLock(Type type, Boolean getGenericDefinition)
   at System.Reflection.Emit.ModuleBuilder.GetTypeTokenInternal(Type type, Boolean getGenericDefinition)
   at System.Reflection.Emit.TypeBuilder..ctor(String fullname, TypeAttributes attr, Type parent, Type[] interfaces, ModuleBuilder module, PackingSize iPackingSize, Int32 iTypeSize, TypeBuilder enclosingType)
   at System.Reflection.Emit.ModuleBuilder.DefineType(String name, TypeAttributes attr, Type parent)
   at System.Reflection.DispatchProxyGenerator.ProxyAssembly.CreateProxy(String name, Type proxyBaseType)
   at System.Reflection.DispatchProxyGenerator.GenerateProxyType(Type baseType, Type interfaceType)
   at System.Reflection.DispatchProxyGenerator.GetProxyType(Type baseType, Type interfaceType)
   at System.Reflection.DispatchProxyGenerator.CreateProxyInstance(Type baseType, Type interfaceType)
   at System.Reflection.DispatchProxy.Create[T,TProxy]()
   at SimpleProxy`1.Decorate(T target) in Program.cs:line 12
   at TestObject.CreateDecorated() in Program.cs:line 33
   --- End of inner exception stack trace ---
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
   at Program.CreateObjectInCollectibleALC(Boolean createDecorated) in Program.cs:line 46
   at Program.Main() in Program.cs:line 70

Regression?

No

Known Workarounds

Use non-collectible ALCs.

Configuration

Tested in .NET 5, the relevant code did not change in 6.

Other information

The logic around creating AssemblyBuilders needs to change; I will try to do it in the future.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions