From 4c05087c41ff61d52d2d023ad0dafc94011fae17 Mon Sep 17 00:00:00 2001 From: wwh1004 Date: Wed, 8 May 2024 13:34:15 +0800 Subject: [PATCH] Allow open generic in dynamic method (experimental, not a legal operation, with unpredictable problems) Fix https://github.com/0xd4d/dnlib/issues/551 --- src/DotNet/Emit/DynamicMethodBodyReader.cs | 10 ++-- src/DotNet/Importer.cs | 61 ++++++++++++++++++---- 2 files changed, 57 insertions(+), 14 deletions(-) diff --git a/src/DotNet/Emit/DynamicMethodBodyReader.cs b/src/DotNet/Emit/DynamicMethodBodyReader.cs index a90ff326c..791abc995 100644 --- a/src/DotNet/Emit/DynamicMethodBodyReader.cs +++ b/src/DotNet/Emit/DynamicMethodBodyReader.cs @@ -420,16 +420,16 @@ public MethodDef GetMethod() { protected override string ReadInlineString(Instruction instr) => ReadToken(reader.ReadUInt32()) as string ?? string.Empty; /// - protected override ITokenOperand ReadInlineTok(Instruction instr) => ReadToken(reader.ReadUInt32()) as ITokenOperand; + protected override ITokenOperand ReadInlineTok(Instruction instr) => ReadToken(reader.ReadUInt32(), false) as ITokenOperand; /// protected override ITypeDefOrRef ReadInlineType(Instruction instr) => ReadToken(reader.ReadUInt32()) as ITypeDefOrRef; - object ReadToken(uint token) { + object ReadToken(uint token, bool? treatAsGenericInst = null) { uint rid = token & 0x00FFFFFF; switch (token >> 24) { case 0x02: - return ImportType(rid); + return ImportType(rid, treatAsGenericInst); case 0x04: return ImportField(rid); @@ -521,10 +521,10 @@ IField ImportField(uint rid) { return null; } - ITypeDefOrRef ImportType(uint rid) { + ITypeDefOrRef ImportType(uint rid, bool? treatAsGenericInst = null) { var obj = Resolve(rid); if (obj is RuntimeTypeHandle) - return importer.Import(Type.GetTypeFromHandle((RuntimeTypeHandle)obj)); + return importer.Import(Type.GetTypeFromHandle((RuntimeTypeHandle)obj), treatAsGenericInst); return null; } diff --git a/src/DotNet/Importer.cs b/src/DotNet/Importer.cs index de37e66b6..0e11d138a 100644 --- a/src/DotNet/Importer.cs +++ b/src/DotNet/Importer.cs @@ -97,6 +97,7 @@ public struct Importer { readonly ModuleDef module; internal readonly GenericParamContext gpContext; readonly ImportMapper mapper; + readonly Type originalDeclaringType; RecursionCounter recursionCounter; ImporterOptions options; @@ -158,12 +159,30 @@ public Importer(ModuleDef module, ImporterOptions options, GenericParamContext g /// Importer options /// Generic parameter context /// Mapper for renamed entities - public Importer(ModuleDef module, ImporterOptions options, GenericParamContext gpContext, ImportMapper mapper) { + public Importer(ModuleDef module, ImporterOptions options, GenericParamContext gpContext, ImportMapper mapper) + : this(module, options, gpContext, mapper, null) { + } + + /// + /// Constructor + /// + /// The module that will own all references + /// Importer options + /// Generic parameter context + /// Mapper for renamed entities + /// + /// WARNING:
+ /// The declaring type of the method when reading a dynamic method.
Pass a non-null value only + /// when you need to allow open generic, which changes some importing behavior to be compatible + /// with illegal open generic dynamic methods. + /// + public Importer(ModuleDef module, ImporterOptions options, GenericParamContext gpContext, ImportMapper mapper, Type originalDeclaringType) { this.module = module; recursionCounter = new RecursionCounter(); this.options = options; this.gpContext = gpContext; this.mapper = mapper; + this.originalDeclaringType = originalDeclaringType; } /// @@ -173,6 +192,18 @@ public Importer(ModuleDef module, ImporterOptions options, GenericParamContext g /// The imported type or null if is invalid public ITypeDefOrRef Import(Type type) => module.UpdateRowId(ImportAsTypeSig(type).ToTypeDefOrRef()); + /// + /// Imports a as a . + /// + /// The type + /// + /// In the .NET metadata (method sig), the parameter is a generic instance type, but the CLR treats it as + /// if it's just a generic type def. This seems to happen only if the parameter type is exactly the same + /// type as the declaring type, eg. a method similar to: MyType<!0> MyType::SomeMethod(). + /// + /// The imported type or null if is invalid + public ITypeDefOrRef Import(Type type, bool? treatAsGenericInst) => module.UpdateRowId(ImportAsTypeSig(type, treatAsGenericInst).ToTypeDefOrRef()); + /// /// Imports a as a . See also /// @@ -196,7 +227,19 @@ public ITypeDefOrRef Import(Type type, IList requiredModifiers, IList /// The type /// The imported type or null if is invalid - public TypeSig ImportAsTypeSig(Type type) => ImportAsTypeSig(type, null, false); + public TypeSig ImportAsTypeSig(Type type) => ImportAsTypeSig(type, false); + + /// + /// Imports a as a + /// + /// The type + /// + /// In the .NET metadata (method sig), the parameter is a generic instance type, but the CLR treats it as + /// if it's just a generic type def. This seems to happen only if the parameter type is exactly the same + /// type as the declaring type, eg. a method similar to: MyType<!0> MyType::SomeMethod(). + /// + /// The imported type or null if is invalid + public TypeSig ImportAsTypeSig(Type type, bool? treatAsGenericInst) => ImportAsTypeSig(type, originalDeclaringType, treatAsGenericInst); TypeSig ImportAsTypeSig(Type type, Type declaringType, bool? treatAsGenericInst = null) { if (type is null) @@ -406,7 +449,7 @@ IResolutionScope CreateScopeReference(Type type) { /// A list of all optional modifiers or null /// The imported type or null if is invalid public TypeSig ImportAsTypeSig(Type type, IList requiredModifiers, IList optionalModifiers) => - ImportAsTypeSig(type, requiredModifiers, optionalModifiers, null); + ImportAsTypeSig(type, requiredModifiers, optionalModifiers, originalDeclaringType); TypeSig ImportAsTypeSig(Type type, IList requiredModifiers, IList optionalModifiers, Type declaringType) { if (type is null) @@ -482,13 +525,13 @@ IMethod ImportInternal(MethodBase methodBase, bool forceFixSignature) { IMethodDefOrRef method; var origMethod = methodBase.Module.ResolveMethod(methodBase.MetadataToken); if (methodBase.DeclaringType.GetElementType2() == ElementType.GenericInst) - method = module.UpdateRowId(new MemberRefUser(module, methodBase.Name, CreateMethodSig(origMethod), Import(methodBase.DeclaringType))); + method = module.UpdateRowId(new MemberRefUser(module, methodBase.Name, CreateMethodSig(origMethod), Import(methodBase.DeclaringType, null))); else method = ImportInternal(origMethod) as IMethodDefOrRef; method = TryResolveMethod(method); - if (methodBase.ContainsGenericParameters) - return method; // Declaring type is instantiated but method itself is not + if (methodBase.ContainsGenericParameters && originalDeclaringType is null) + return method; // Declaring type is instantiated but method itself is not, this is a MemberRef. When importing a dynamic method, treat it as open generic. var gim = CreateGenericInstMethodSig(methodBase); var methodSpec = module.UpdateRowId(new MethodSpecUser(method, gim)); @@ -504,7 +547,7 @@ IMethod ImportInternal(MethodBase methodBase, bool forceFixSignature) { parent = GetModuleParent(methodBase.Module); } else - parent = Import(methodBase.DeclaringType); + parent = Import(methodBase.DeclaringType, null); if (parent is null) return null; @@ -587,7 +630,7 @@ GenericInstMethodSig CreateGenericInstMethodSig(MethodBase mb) { var genMethodArgs = mb.GetGenericArguments(); var gim = new GenericInstMethodSig(CallingConvention.GenericInst, (uint)genMethodArgs.Length); foreach (var gma in genMethodArgs) - gim.GenericArguments.Add(ImportAsTypeSig(gma)); + gim.GenericArguments.Add(ImportAsTypeSig(gma, null)); return gim; } @@ -642,7 +685,7 @@ public IField Import(FieldInfo fieldInfo, bool forceFixSignature) { parent = GetModuleParent(fieldInfo.Module); } else - parent = Import(fieldInfo.DeclaringType); + parent = Import(fieldInfo.DeclaringType, null); if (parent is null) return null;