Skip to content

Commit e1f3f5a

Browse files
committed
Avoid unnecessarily creating TypeReferences on import
When importing a TypeSpecification, an element type or generic argument could be a TypeDefinition within the current module. When this happens, a new TypeReference does not need to be created. This can lead to a TypeReference being added to the typeref table for a type that is already in the assembly. This seems to hold together, although I don't think it's ideal. And really my goal is to avoid failing this logic in the ILLink test framework https://github.com/dotnet/runtime/blob/cec44d6dff9de95421f199f65bbc80b8296da1c0/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs#L56 This fix superceeds #138. I've left that fix in since that API is exposed publically and others could be depending on it. While I was adjusting this logic, I thought I would apply the same logic to a TypeReference where the scope is the current modules assembly. This case is a bit contrived as it shouldn't happen, but if it did, it would result in the same circular reference problem as #138
1 parent 3136847 commit e1f3f5a

File tree

2 files changed

+63
-1
lines changed

2 files changed

+63
-1
lines changed

Mono.Cecil/Import.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,16 @@ TypeReference ImportType (TypeReference type, ImportGenericContext context)
506506
if (type.IsTypeSpecification ())
507507
return ImportTypeSpecification (type, context);
508508

509+
// If a type's scope is the module definition then we don't need to create a new type reference. We can reuse the existing one, which is likely the TypeDefinition.
510+
// Reusing the type avoid creating an unnecessary entry in the type reference table
511+
if (type.Scope == module)
512+
return type;
513+
514+
// This case is more contrived, but it's the same as the above. If the type's scope matches the current modules assembly then we don't need to create a new type reference.
515+
// Nor do we need to import the scope because the scope is the current modules assembly.
516+
if (type.Scope is AssemblyNameReference asmName && Mixin.Equals (asmName, module.assembly.Name))
517+
return type;
518+
509519
var reference = new TypeReference (
510520
type.Namespace,
511521
type.Name,
@@ -535,6 +545,8 @@ protected IMetadataScope ImportScope (IMetadataScope scope)
535545
case MetadataScopeType.AssemblyNameReference:
536546
return ImportReference ((AssemblyNameReference) scope);
537547
case MetadataScopeType.ModuleDefinition:
548+
// This change to avoid self reference has been superceded by the check in ImportType to avoid creating the new TypeReference in the first place.
549+
// However, given that ImportScope is protected people could be relying on this behavior so I'm not going to remove it
538550
if (scope == module) return scope;
539551
return ImportReference (((ModuleDefinition) scope).Assembly.Name);
540552
case MetadataScopeType.ModuleReference:
@@ -809,7 +821,7 @@ static bool Equals<T> (T a, T b) where T : class, IEquatable<T>
809821
return a.Equals (b);
810822
}
811823

812-
static bool Equals (AssemblyNameReference a, AssemblyNameReference b)
824+
public static bool Equals (AssemblyNameReference a, AssemblyNameReference b)
813825
{
814826
if (ReferenceEquals (a, b))
815827
return true;

Test/Mono.Cecil.Tests/ImportCecilTests.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,51 @@ public void ContextGenericTest ()
286286
Assert.AreEqual ("Mono.Cecil.Tests.ImportCecilTests/Generic`1<TS> Mono.Cecil.Tests.ImportCecilTests/Generic`1<System.String>::ComplexGenericMethod<TS>(T,TS)", method.FullName);
287287
}
288288

289+
[Test]
290+
public void ImportGenericTypeWithGenericArgumentInSameAssembly ()
291+
{
292+
using var module = CreateTestModule ();
293+
var generic_type = module.ImportReference (typeof (GenericForSameAssemblyTests<>)).Resolve ();
294+
295+
var type_definition = new TypeDefinition(string.Empty, "Bar", TypeAttributes.Public, module.ImportReference (typeof (object)));
296+
module.Types.Add(type_definition);
297+
298+
var generic_inst = new GenericInstanceType (generic_type);
299+
generic_inst.GenericArguments.Add (type_definition);
300+
301+
var imported = (GenericInstanceType)module.ImportReference (generic_inst);
302+
303+
Assert.That(imported.GenericArguments[0], Is.EqualTo (type_definition));
304+
305+
// Shouldn't be able to find this assert if the assert above passes, but just in case also assert a circular reference isn't created.
306+
Assert.That(module.AssemblyReferences.Select(r => r.Name), Does.Not.Contain (module.Name));
307+
}
308+
309+
[Test]
310+
public void ImportGenericTypeWithGenericArgumentInSameAssemblyTypeReference ()
311+
{
312+
using var module = CreateTestModule ();
313+
var generic_type = module.ImportReference (typeof (GenericForSameAssemblyTests<>)).Resolve ();
314+
315+
var type_definition = new TypeDefinition(string.Empty, "Bar", TypeAttributes.Public, module.ImportReference (typeof (object)));
316+
module.Types.Add(type_definition);
317+
318+
var type_reference = new TypeReference(type_definition.Namespace, type_definition.Name, module, new AssemblyNameReference(module.Assembly.Name.Name, module.Assembly.Name.Version));
319+
320+
var generic_instance = new GenericInstanceType (generic_type);
321+
generic_instance.GenericArguments.Add (type_reference);
322+
323+
var imported = (GenericInstanceType)module.ImportReference (generic_instance);
324+
325+
// By reusing the same type reference we can avoid triggering an ImportReference call which creates a circular reference.
326+
Assert.That(imported.GenericArguments[0], Is.EqualTo (type_reference));
327+
328+
// Shouldn't be able to find this assert if the assert above passes, but just in case also assert a circular reference isn't created.
329+
Assert.That(module.AssemblyReferences.Select(r => r.Name), Does.Not.Contain (module.Name));
330+
}
331+
332+
public class GenericForSameAssemblyTests<T>;
333+
289334
delegate void Emitter (ModuleDefinition module, MethodBody body);
290335

291336
static TDelegate Compile<TDelegate> (Emitter emitter, [CallerMemberName] string testMethodName = null)
@@ -319,6 +364,11 @@ static SR.Assembly LoadTestModule (ModuleDefinition module)
319364
}
320365
}
321366

367+
static ModuleDefinition CreateTestModule ([CallerMemberName] string testMethodName = null)
368+
{
369+
return CreateModule("ImportCecil_" + testMethodName);
370+
}
371+
322372
static ModuleDefinition CreateTestModule<TDelegate> (string name, Emitter emitter)
323373
{
324374
var module = CreateModule (name);

0 commit comments

Comments
 (0)