-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Description
If the same assembly is loaded twice - once in the Default ALC and second time in the custom ALC, and let's say that assembly defines a type BaseType. Creating two new types derived from the BaseType from default and then from custom ALC using TypeBuilder from the same dynamic module will return two types which both derive from the same type (the first BaseType).
This is wrong, since this should basically always return true:
moduleBuilder.DefineType("TestType", parentType).CreateType().IsAssignableTo(parentType);But in the above case it doesn't - the second type will return false from this.
Full repro:
Create a new console app and paste this code:
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Loader;
var typeId = 0;
var moduleBuilder = AssemblyBuilder
.DefineDynamicAssembly(new AssemblyName("assembly"), AssemblyBuilderAccess.Run)
.DefineDynamicModule("module");
Type baseTypeInCustomALC = new AssemblyLoadContext("CustomALC")
.LoadFromAssemblyPath(typeof(BaseType).Assembly.Location)
.GetType(typeof(BaseType).FullName!)!;
// First -----
// This always writes "true"
if (args.Length > 0)
{
Type? firstT = CreateDerivedType(typeof(BaseType));
Console.WriteLine("First: " + firstT?.IsAssignableTo(typeof(BaseType)));
}
else
{
Console.WriteLine("First: <skipped>");
}
// Second ----
// If the First is executed, this writes "false", otherwise it writes "true"
Type? secondT = CreateDerivedType(baseTypeInCustomALC);
Console.WriteLine("Second: " + secondT?.IsAssignableTo(baseTypeInCustomALC));
Type? CreateDerivedType(Type parentType)
{
return moduleBuilder
.DefineType($"type{typeId++}", TypeAttributes.Public, parentType)
.CreateType();
}
public class BaseType
{
} ❯ dotnet run
First: <skipped>
Second: True
❯ dotnet run -- true
First: True
Second: FalseThe "Second" try returns true/false depending if the "First" try was executed or not.
This is a tricky problem - under the hood runtime creates basically a normal assembly for every assembly builder. Such assembly has only one way to refer to types from other assemblies - typeref+assemblyref. Both of these specify just simple names. And then these go through normal assembly resolution process - which will effectively cache the first result (this is simplified, the logic to get the type ref is pretty complex and there might be other tricks to it, but it does eventually create a simple assembly ref).
I don't think TypeBuilder can truly fix this - given the current implementation (where it generates real metadata with tokens and everything), since the underlying IL metadata doesn't have a way to express two assembly references to two different assemblies of the same exact name.
That said TypeBuilder should guard against this - ideally it should basically create the type ref, resolve it and validate that it got back the original Type instance - if not, it should fail as otherwise it creates wrong output.
Note: The "Workaround" for this is to use two different dynamic modules (moduleBuilder) as those can then have separate assembly refs to different assemblies.
Originally found in #60468. See #60468 (comment) for a more detailed discussion of the original problem.