From dbc08ff757b36f2200270343abf74dc26ab5acc2 Mon Sep 17 00:00:00 2001 From: CursedLand Date: Thu, 20 Oct 2022 21:37:34 +0200 Subject: [PATCH 1/8] BUGFIX: resolving m_scope field from DynamicILInfo rather than DynamicResolver. --- .../DynamicMethodDefinition.cs | 17 ++++++++-- .../DynamicMethodDefinitionTest.cs | 33 +++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs b/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs index e39ebc5f5..6cbfd1862 100644 --- a/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs +++ b/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs @@ -78,12 +78,14 @@ private static CilMethodBody CreateDynamicMethodBody(MethodDefinition method, ob // Attempt to get the code field. byte[]? code = FieldReader.ReadField(dynamicMethodObj, "m_code"); + object? dynamicILInfo = null; + // If it is still null, it might still be set using DynamicILInfo::SetCode. // Find the code stored in the DynamicILInfo if available. if (code is null && FieldReader.TryReadField(dynamicMethodObj, "m_method", out var methodBase) && methodBase is not null - && FieldReader.TryReadField(methodBase, "m_DynamicILInfo", out object? dynamicILInfo) + && FieldReader.TryReadField(methodBase, "m_DynamicILInfo", out dynamicILInfo) && dynamicILInfo is not null) { code = FieldReader.ReadField(dynamicILInfo, "m_code"); @@ -93,7 +95,18 @@ private static CilMethodBody CreateDynamicMethodBody(MethodDefinition method, ob throw new InvalidOperationException("Dynamic method does not have a CIL code stream."); // Get remaining fields. - object scope = FieldReader.ReadField(dynamicMethodObj, "m_scope")!; + + object scope; + + if (dynamicMethodObj.GetType().FullName != "System.Reflection.Emit.DynamicILInfo" && dynamicILInfo is { }) + { + scope = FieldReader.ReadField(dynamicILInfo, "m_scope")!; + } + else + { + scope = FieldReader.ReadField(dynamicMethodObj, "m_scope")!; + } + var tokenList = FieldReader.ReadField>(scope, "m_tokens")!; byte[] localSig = FieldReader.ReadField(dynamicMethodObj, "m_localSignature")!; byte[] ehHeader = FieldReader.ReadField(dynamicMethodObj, "m_exceptionHeader")!; diff --git a/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs b/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs index c65bba058..933aa2c08 100644 --- a/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs @@ -7,7 +7,9 @@ using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.Signatures.Types; using AsmResolver.DotNet.TestCases.Methods; +using AsmResolver.IO; using AsmResolver.PE.DotNet.Cil; +using AsmResolver.PE.DotNet.Metadata.Tables; using Xunit; using MethodAttributes = AsmResolver.PE.DotNet.Metadata.Tables.Rows.MethodAttributes; @@ -116,6 +118,37 @@ public void ImportNestedType() Assert.Equal(nameof(DynamicMethodDefinitionTest), declaringType.DeclaringType.Name); } + [Fact] + public void ReadDynamicMethodInitializedByDynamicILInfoWithTokens() { + // Create new dynamic method. + var method = new DynamicMethod("Test", typeof(void), Type.EmptyTypes); + var info = method.GetDynamicILInfo(); + info.SetLocalSignature(new byte[] { 0x7, 0x0 }); + + // Write some IL. + using var codeStream = new MemoryStream(); + var assembler = new CilAssembler( + new BinaryStreamWriter(codeStream), + new CilOperandBuilder(new OriginalMetadataTokenProvider(null), EmptyErrorListener.Instance)); + uint token = (uint)info.GetTokenFor(typeof(Console).GetMethod("WriteLine", Type.EmptyTypes).MethodHandle); + assembler.WriteInstruction(new CilInstruction(CilOpCodes.Call, new MetadataToken(token))); + assembler.WriteInstruction(new CilInstruction(CilOpCodes.Ret)); + + // Set code. + info.SetCode(codeStream.ToArray(), 1); + + // Pass into DynamicMethodDefinition + var contextModule = ModuleDefinition.FromFile(typeof(DynamicMethodDefinitionTest).Assembly.Location); + var definition = new DynamicMethodDefinition(contextModule, method); + + // Verify + Assert.NotNull(definition.CilMethodBody); + var instruction = definition.CilMethodBody.Instructions[0]; + Assert.Equal(CilOpCodes.Call, instruction.OpCode); + var reference = Assert.IsAssignableFrom(instruction.Operand); + Assert.Equal("WriteLine", reference.Name); + } + internal static class NestedClass { public static void TestMethod() => Console.WriteLine("TestMethod"); From 2f973a52ec0b004c5a270714072a36890d84e780 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 22 Oct 2022 19:33:52 +0200 Subject: [PATCH 2/8] Add failing locals test. --- .../DynamicMethodDefinitionTest.cs | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs b/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs index 933aa2c08..8372ef3fa 100644 --- a/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs @@ -3,13 +3,16 @@ using System.Linq; using System.Reflection; using System.Reflection.Emit; +using AsmResolver.DotNet.Code; using AsmResolver.DotNet.Code.Cil; +using AsmResolver.DotNet.Code.Native; using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.Signatures.Types; using AsmResolver.DotNet.TestCases.Methods; using AsmResolver.IO; using AsmResolver.PE.DotNet.Cil; using AsmResolver.PE.DotNet.Metadata.Tables; +using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using Xunit; using MethodAttributes = AsmResolver.PE.DotNet.Metadata.Tables.Rows.MethodAttributes; @@ -119,7 +122,8 @@ public void ImportNestedType() } [Fact] - public void ReadDynamicMethodInitializedByDynamicILInfoWithTokens() { + public void ReadDynamicMethodInitializedByDynamicILInfoWithTokens() + { // Create new dynamic method. var method = new DynamicMethod("Test", typeof(void), Type.EmptyTypes); var info = method.GetDynamicILInfo(); @@ -149,6 +153,35 @@ public void ReadDynamicMethodInitializedByDynamicILInfoWithTokens() { Assert.Equal("WriteLine", reference.Name); } + [Fact] + public void ReadDynamicMethodInitializedByDynamicILInfoWithLocals() + { + // Create new dynamic method. + var method = new DynamicMethod("Test", typeof(void), Type.EmptyTypes); + var info = method.GetDynamicILInfo(); + + var helper = SignatureHelper.GetLocalVarSigHelper(); + helper.AddArgument(typeof(int)); + helper.AddArgument(typeof(bool)); + helper.AddArgument(typeof(string)); + info.SetLocalSignature(helper.GetSignature()); + + // Write some IL. + info.SetCode(new byte[] {0x2a}, 1); + + // Pass into DynamicMethodDefinition + var contextModule = ModuleDefinition.FromFile(typeof(DynamicMethodDefinitionTest).Assembly.Location); + var definition = new DynamicMethodDefinition(contextModule, method); + + // Verify + Assert.NotNull(definition.CilMethodBody); + var locals = definition.CilMethodBody.LocalVariables; + Assert.Equal(3, locals.Count); + Assert.Equal("Int32", locals[0].VariableType.Name); + Assert.Equal("Boolean", locals[1].VariableType.Name); + Assert.Equal("String", locals[2].VariableType.Name); + } + internal static class NestedClass { public static void TestMethod() => Console.WriteLine("TestMethod"); From cd8f2d300398cf27db8ce86861d2b94ca6bc54c4 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 22 Oct 2022 20:55:14 +0200 Subject: [PATCH 3/8] Read as much from the dynamic il info object as possible. --- .../DynamicMethodDefinition.cs | 77 ++++++++++--------- .../DynamicMethodHelper.cs | 17 ++-- .../Code/Cil/OriginalMetadataTokenProvider.cs | 2 +- 3 files changed, 52 insertions(+), 44 deletions(-) diff --git a/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs b/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs index 6cbfd1862..69f8806f4 100644 --- a/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs +++ b/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs @@ -25,8 +25,8 @@ public class DynamicMethodDefinition : MethodDefinition public DynamicMethodDefinition(ModuleDefinition module, object dynamicMethodObj) : base(new MetadataToken(TableIndex.Method, 0)) { - dynamicMethodObj = DynamicMethodHelper.ResolveDynamicResolver(dynamicMethodObj); - var methodBase = FieldReader.ReadField(dynamicMethodObj, "m_method"); + object resolver = DynamicMethodHelper.ResolveDynamicResolver(dynamicMethodObj); + var methodBase = FieldReader.ReadField(resolver, "m_method"); if (methodBase is null) { throw new ArgumentException( @@ -37,7 +37,7 @@ public DynamicMethodDefinition(ModuleDefinition module, object dynamicMethodObj) Name = methodBase.Name; Attributes = (MethodAttributes)methodBase.Attributes; Signature = module.DefaultImporter.ImportMethodSignature(ResolveSig(methodBase, module)); - CilMethodBody = CreateDynamicMethodBody(this, dynamicMethodObj); + CilMethodBody = CreateDynamicMethodBody(this, resolver); } private MethodSignature ResolveSig(MethodBase methodBase, ModuleDefinition module) @@ -73,55 +73,58 @@ private static CilMethodBody CreateDynamicMethodBody(MethodDefinition method, ob throw new ArgumentException("Method body should reference a serialized module."); var result = new CilMethodBody(method); - dynamicMethodObj = DynamicMethodHelper.ResolveDynamicResolver(dynamicMethodObj); - - // Attempt to get the code field. - byte[]? code = FieldReader.ReadField(dynamicMethodObj, "m_code"); + object resolver = DynamicMethodHelper.ResolveDynamicResolver(dynamicMethodObj); + // We prefer to extract the information from DynamicILInfo if it is there, as it has more accurate info + // if the DynamicMethod code is not flushed yet into the resolver (e.g., it hasn't been invoked yet). object? dynamicILInfo = null; + if (FieldReader.TryReadField(resolver, "m_method", out var m) && m is not null) + FieldReader.TryReadField(m, "m_DynamicILInfo", out dynamicILInfo); - // If it is still null, it might still be set using DynamicILInfo::SetCode. - // Find the code stored in the DynamicILInfo if available. - if (code is null - && FieldReader.TryReadField(dynamicMethodObj, "m_method", out var methodBase) - && methodBase is not null - && FieldReader.TryReadField(methodBase, "m_DynamicILInfo", out dynamicILInfo) - && dynamicILInfo is not null) - { - code = FieldReader.ReadField(dynamicILInfo, "m_code"); - } - - if (code is null) - throw new InvalidOperationException("Dynamic method does not have a CIL code stream."); - - // Get remaining fields. - + // Extract all required information to construct the body. + byte[]? code; object scope; + List tokenList; + byte[]? localSig; + byte[]? ehHeader; + IList? ehInfos; - if (dynamicMethodObj.GetType().FullName != "System.Reflection.Emit.DynamicILInfo" && dynamicILInfo is { }) + if (resolver.GetType().FullName != "System.Reflection.Emit.DynamicILInfo" && dynamicILInfo is { }) { + code = FieldReader.ReadField(dynamicILInfo, "m_code"); scope = FieldReader.ReadField(dynamicILInfo, "m_scope")!; + tokenList = FieldReader.ReadField>(scope, "m_tokens")!; + localSig = FieldReader.ReadField(dynamicILInfo, "m_localSignature"); + ehHeader = FieldReader.ReadField(dynamicILInfo, "m_exceptions"); + + // DynamicILInfo does not have EH info. Try recover it from the resolver. + ehInfos = FieldReader.ReadField>(resolver, "m_exceptions"); } else { - scope = FieldReader.ReadField(dynamicMethodObj, "m_scope")!; + code = FieldReader.ReadField(resolver, "m_code"); + scope = FieldReader.ReadField(resolver, "m_scope")!; + tokenList = FieldReader.ReadField>(scope, "m_tokens")!; + localSig = FieldReader.ReadField(resolver, "m_localSignature"); + ehHeader = FieldReader.ReadField(resolver, "m_exceptionHeader"); + ehInfos = FieldReader.ReadField>(resolver, "m_exceptions"); } - var tokenList = FieldReader.ReadField>(scope, "m_tokens")!; - byte[] localSig = FieldReader.ReadField(dynamicMethodObj, "m_localSignature")!; - byte[] ehHeader = FieldReader.ReadField(dynamicMethodObj, "m_exceptionHeader")!; - var ehInfos = FieldReader.ReadField>(dynamicMethodObj, "m_exceptions")!; - - //Local Variables - DynamicMethodHelper.ReadLocalVariables(result, method, localSig); + // Interpret local variables signatures. + if (localSig is not null) + DynamicMethodHelper.ReadLocalVariables(result, method, localSig); // Read raw instructions. - var reader = new BinaryStreamReader(code); - var disassembler = new CilDisassembler(reader, new DynamicCilOperandResolver(module, result, tokenList)); - result.Instructions.AddRange(disassembler.ReadInstructions()); + if (code is not null) + { + var reader = new BinaryStreamReader(code); + var disassembler = + new CilDisassembler(reader, new DynamicCilOperandResolver(module, result, tokenList)); + result.Instructions.AddRange(disassembler.ReadInstructions()); + } - //Exception Handlers - DynamicMethodHelper.ReadReflectionExceptionHandlers(result, ehInfos, ehHeader, module.DefaultImporter); + // Interpret exception handler information or header. + DynamicMethodHelper.ReadReflectionExceptionHandlers(result, ehHeader, ehInfos, module.DefaultImporter); return result; } diff --git a/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs b/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs index 9deb9ec5e..a88e2b455 100644 --- a/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs +++ b/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs @@ -89,19 +89,24 @@ private static LocalVariablesSignature ReadLocalVariableSignature( } public static void ReadReflectionExceptionHandlers(CilMethodBody methodBody, - IList? ehInfos, byte[] ehHeader, ReferenceImporter importer) + byte[]? ehHeader, IList? ehInfos, ReferenceImporter importer) { - //Sample needed! - if (ehHeader is { Length: > 4 }) - throw new NotImplementedException("Exception handlers from ehHeader not supported yet."); - - if (ehInfos is { Count: > 0 }) + if (ehHeader is {Length: > 4}) + { + InterpretEHSection(methodBody, importer, ehHeader); + } + else if (ehInfos is { Count: > 0 }) { foreach (var ehInfo in ehInfos) InterpretEHInfo(methodBody, importer, ehInfo); } } + private static void InterpretEHSection(CilMethodBody methodBody, ReferenceImporter importer, byte[] ehHeader) + { + throw new NotImplementedException("Raw exception data is not supported yet."); + } + private static void InterpretEHInfo(CilMethodBody methodBody, ReferenceImporter importer, object ehInfo) { for (int i = 0; i < FieldReader.ReadField(ehInfo, "m_currentCatch"); i++) diff --git a/src/AsmResolver.DotNet/Code/Cil/OriginalMetadataTokenProvider.cs b/src/AsmResolver.DotNet/Code/Cil/OriginalMetadataTokenProvider.cs index db8440077..6d4ac27b1 100644 --- a/src/AsmResolver.DotNet/Code/Cil/OriginalMetadataTokenProvider.cs +++ b/src/AsmResolver.DotNet/Code/Cil/OriginalMetadataTokenProvider.cs @@ -26,7 +26,7 @@ public OriginalMetadataTokenProvider(ModuleDefinition? module) private MetadataToken GetToken(IMetadataMember member) { - if (_module is not null && member is IModuleProvider provider && provider.Module == _module) + if (_module is not null && member is IModuleProvider provider && provider.Module != _module) throw new MemberNotImportedException(member); return member.MetadataToken; From 6445b57cd3c4618ca2b1272f357fee3240688a15 Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 23 Oct 2022 15:03:43 +0200 Subject: [PATCH 4/8] BUGFIX: Regression in COR_ELEMENT_TYPE_INTERNAL type reading. --- .../DynamicMethodDefinition.cs | 17 +++++++++++------ .../DynamicMethodHelper.cs | 15 +++++++++++++-- .../DynamicMethodDefinitionTest.cs | 4 ++-- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs b/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs index 69f8806f4..16aca31c2 100644 --- a/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs +++ b/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs @@ -40,6 +40,14 @@ public DynamicMethodDefinition(ModuleDefinition module, object dynamicMethodObj) CilMethodBody = CreateDynamicMethodBody(this, resolver); } + /// + /// Determines whether dynamic method reading is fully supported in the current host's .NET environment. + /// + public static bool IsSupported => DynamicMethodHelper.IsSupported; + + /// + public override ModuleDefinition Module { get; } + private MethodSignature ResolveSig(MethodBase methodBase, ModuleDefinition module) { var importer = module.DefaultImporter; @@ -58,9 +66,6 @@ private MethodSignature ResolveSig(MethodBase methodBase, ModuleDefinition modul returnType, parameterTypes); } - /// - public override ModuleDefinition Module { get; } - /// /// Creates a CIL method body from a dynamic method. /// @@ -89,7 +94,7 @@ private static CilMethodBody CreateDynamicMethodBody(MethodDefinition method, ob byte[]? ehHeader; IList? ehInfos; - if (resolver.GetType().FullName != "System.Reflection.Emit.DynamicILInfo" && dynamicILInfo is { }) + if (resolver.GetType().FullName != "System.Reflection.Emit.DynamicILInfo" && dynamicILInfo is not null) { code = FieldReader.ReadField(dynamicILInfo, "m_code"); scope = FieldReader.ReadField(dynamicILInfo, "m_scope")!; @@ -118,8 +123,8 @@ private static CilMethodBody CreateDynamicMethodBody(MethodDefinition method, ob if (code is not null) { var reader = new BinaryStreamReader(code); - var disassembler = - new CilDisassembler(reader, new DynamicCilOperandResolver(module, result, tokenList)); + var operandResolver = new DynamicCilOperandResolver(module, result, tokenList); + var disassembler = new CilDisassembler(reader, operandResolver); result.Instructions.AddRange(disassembler.ReadInstructions()); } diff --git a/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs b/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs index a88e2b455..49d6051fa 100644 --- a/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs +++ b/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Reflection.Emit; @@ -15,7 +16,7 @@ namespace AsmResolver.DotNet.Dynamic { internal static class DynamicMethodHelper { - private static readonly MethodInfo GetTypeFromHandleUnsafeMethod; + private static readonly MethodInfo? GetTypeFromHandleUnsafeMethod; static DynamicMethodHelper() { @@ -25,9 +26,12 @@ static DynamicMethodHelper() (BindingFlags) (-1), null, new[] {typeof(IntPtr)}, - null)!; + null); } + [MemberNotNullWhen(true, nameof(GetTypeFromHandleUnsafeMethod))] + public static bool IsSupported => GetTypeFromHandleUnsafeMethod is not null; + public static void ReadLocalVariables(CilMethodBody methodBody, MethodDefinition method, byte[] localSig) { if (method.Module is not SerializedModuleDefinition module) @@ -53,6 +57,13 @@ private static TypeSignature ReadTypeSignature(in BlobReadContext context, ref B private static TypeSignature ReadInternalTypeSignature(in BlobReadContext context, ref BinaryStreamReader reader) { + if (!IsSupported) + throw new PlatformNotSupportedException("The current platform does not support the translation of raw type handles to System.Type instances."); + + // Consume INTERNAL element type. + reader.ReadByte(); + + // Read address. var address = IntPtr.Size switch { 4 => new IntPtr(reader.ReadInt32()), diff --git a/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs b/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs index 8372ef3fa..83098adf6 100644 --- a/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs @@ -163,7 +163,7 @@ public void ReadDynamicMethodInitializedByDynamicILInfoWithLocals() var helper = SignatureHelper.GetLocalVarSigHelper(); helper.AddArgument(typeof(int)); helper.AddArgument(typeof(bool)); - helper.AddArgument(typeof(string)); + helper.AddArgument(typeof(Stream)); info.SetLocalSignature(helper.GetSignature()); // Write some IL. @@ -179,7 +179,7 @@ public void ReadDynamicMethodInitializedByDynamicILInfoWithLocals() Assert.Equal(3, locals.Count); Assert.Equal("Int32", locals[0].VariableType.Name); Assert.Equal("Boolean", locals[1].VariableType.Name); - Assert.Equal("String", locals[2].VariableType.Name); + Assert.Equal("Stream", locals[2].VariableType.Name); } internal static class NestedClass From 0fbe09ec536c875aa131b28ccb9a4cb7ef70b142 Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 23 Oct 2022 16:35:20 +0200 Subject: [PATCH 5/8] BUGFIX: Ensure that complex type signatures are also using the dynamic resolution of mt addresses. --- .../DynamicMethodDefinition.cs | 2 +- .../DynamicMethodHelper.cs | 75 +------------------ .../DynamicTypeSignatureReader.cs | 59 +++++++++++++++ .../Signatures/BlobReadContext.cs | 27 ++++++- .../Types/ITypeSignatureResolver.cs | 27 +++++++ .../Types/PhysicalTypeSignatureResolver.cs | 57 ++++++++++++++ .../Signatures/Types/TypeSignature.cs | 41 ++-------- .../DynamicMethodDefinitionTest.cs | 4 +- 8 files changed, 180 insertions(+), 112 deletions(-) create mode 100644 src/AsmResolver.DotNet.Dynamic/DynamicTypeSignatureReader.cs create mode 100644 src/AsmResolver.DotNet/Signatures/Types/ITypeSignatureResolver.cs create mode 100644 src/AsmResolver.DotNet/Signatures/Types/PhysicalTypeSignatureResolver.cs diff --git a/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs b/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs index 16aca31c2..4fdc14587 100644 --- a/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs +++ b/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs @@ -43,7 +43,7 @@ public DynamicMethodDefinition(ModuleDefinition module, object dynamicMethodObj) /// /// Determines whether dynamic method reading is fully supported in the current host's .NET environment. /// - public static bool IsSupported => DynamicMethodHelper.IsSupported; + public static bool IsSupported => DynamicTypeSignatureResolver.IsSupported; /// public override ModuleDefinition Module { get; } diff --git a/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs b/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs index 49d6051fa..3563096b6 100644 --- a/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs +++ b/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs @@ -1,45 +1,27 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Reflection.Emit; using AsmResolver.DotNet.Code.Cil; using AsmResolver.DotNet.Serialized; using AsmResolver.DotNet.Signatures; -using AsmResolver.DotNet.Signatures.Types; using AsmResolver.IO; using AsmResolver.PE.DotNet.Cil; -using AsmResolver.PE.DotNet.Metadata.Tables.Rows; namespace AsmResolver.DotNet.Dynamic { internal static class DynamicMethodHelper { - private static readonly MethodInfo? GetTypeFromHandleUnsafeMethod; - - static DynamicMethodHelper() - { - // We need to use reflection for this to stay compatible with .netstandard 2.0. - GetTypeFromHandleUnsafeMethod = typeof(Type) - .GetMethod("GetTypeFromHandleUnsafe", - (BindingFlags) (-1), - null, - new[] {typeof(IntPtr)}, - null); - } - - [MemberNotNullWhen(true, nameof(GetTypeFromHandleUnsafeMethod))] - public static bool IsSupported => GetTypeFromHandleUnsafeMethod is not null; - public static void ReadLocalVariables(CilMethodBody methodBody, MethodDefinition method, byte[] localSig) { if (method.Module is not SerializedModuleDefinition module) throw new ArgumentException("Method body should reference a serialized module."); var reader = new BinaryStreamReader(localSig); - if (ReadLocalVariableSignature(new BlobReadContext(module.ReaderContext), ref reader) - is not { } localsSignature) + var context = new BlobReadContext(module.ReaderContext, DynamicTypeSignatureResolver.Instance); + if (CallingConventionSignature.FromReader(context, ref reader) + is not LocalVariablesSignature localsSignature) { throw new ArgumentException("Invalid local variables signature."); } @@ -48,57 +30,6 @@ public static void ReadLocalVariables(CilMethodBody methodBody, MethodDefinition methodBody.LocalVariables.Add(new CilLocalVariable(localsSignature.VariableTypes[i])); } - private static TypeSignature ReadTypeSignature(in BlobReadContext context, ref BinaryStreamReader reader) - { - return (ElementType) reader.PeekByte() == ElementType.Internal - ? ReadInternalTypeSignature(context, ref reader) - : TypeSignature.FromReader(in context, ref reader); - } - - private static TypeSignature ReadInternalTypeSignature(in BlobReadContext context, ref BinaryStreamReader reader) - { - if (!IsSupported) - throw new PlatformNotSupportedException("The current platform does not support the translation of raw type handles to System.Type instances."); - - // Consume INTERNAL element type. - reader.ReadByte(); - - // Read address. - var address = IntPtr.Size switch - { - 4 => new IntPtr(reader.ReadInt32()), - _ => new IntPtr(reader.ReadInt64()) - }; - - // Let the runtime translate the address to a type and import it. - var clrType = (Type?) GetTypeFromHandleUnsafeMethod.Invoke(null, new object[] { address }); - - var type = clrType is not null - ? new ReferenceImporter(context.ReaderContext.ParentModule).ImportType(clrType) - : InvalidTypeDefOrRef.Get(InvalidTypeSignatureError.IllegalTypeSpec); - - return new TypeDefOrRefSignature(type); - } - - private static LocalVariablesSignature ReadLocalVariableSignature( - in BlobReadContext context, - ref BinaryStreamReader reader) - { - var result = new LocalVariablesSignature(); - result.Attributes = (CallingConventionAttributes) reader.ReadByte(); - - if (!reader.TryReadCompressedUInt32(out uint count)) - { - context.ReaderContext.BadImage("Invalid number of local variables in local variable signature."); - return result; - } - - for (int i = 0; i < count; i++) - result.VariableTypes.Add(ReadTypeSignature(context, ref reader)); - - return result; - } - public static void ReadReflectionExceptionHandlers(CilMethodBody methodBody, byte[]? ehHeader, IList? ehInfos, ReferenceImporter importer) { diff --git a/src/AsmResolver.DotNet.Dynamic/DynamicTypeSignatureReader.cs b/src/AsmResolver.DotNet.Dynamic/DynamicTypeSignatureReader.cs new file mode 100644 index 000000000..969ef3f0a --- /dev/null +++ b/src/AsmResolver.DotNet.Dynamic/DynamicTypeSignatureReader.cs @@ -0,0 +1,59 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using AsmResolver.DotNet.Signatures; +using AsmResolver.DotNet.Signatures.Types; + +namespace AsmResolver.DotNet.Dynamic +{ + /// + /// Provides an implementation for the that resolves metadata tokens from + /// the underlying module's tables stream, and is able to transform addresses referencing method tables in the + /// current process to type signatures. + /// + public class DynamicTypeSignatureResolver : PhysicalTypeSignatureResolver + { + private static readonly MethodInfo? GetTypeFromHandleUnsafeMethod; + + static DynamicTypeSignatureResolver() + { + // We need to use reflection for this to stay compatible with .netstandard 2.0. + GetTypeFromHandleUnsafeMethod = typeof(Type) + .GetMethod("GetTypeFromHandleUnsafe", + (BindingFlags) (-1), + null, + new[] {typeof(IntPtr)}, + null); + } + + /// + /// Gets the singleton instance of the class. + /// + public new static DynamicTypeSignatureResolver Instance + { + get; + } = new(); + + /// + /// Gets a value indicating whether dynamic resolution of method tables is supported. + /// + [MemberNotNullWhen(true, nameof(GetTypeFromHandleUnsafeMethod))] + public static bool IsSupported => GetTypeFromHandleUnsafeMethod is not null; + + /// + public override TypeSignature ResolveRuntimeType(in BlobReadContext context, nint address) + { + if (!IsSupported) + throw new PlatformNotSupportedException("The current platform does not support the translation of raw type handles to System.Type instances."); + + // Let the runtime translate the address to a type and import it. + var clrType = (Type?) GetTypeFromHandleUnsafeMethod.Invoke(null, new object[] { address }); + + var type = clrType is not null + ? new ReferenceImporter(context.ReaderContext.ParentModule).ImportType(clrType) + : InvalidTypeDefOrRef.Get(InvalidTypeSignatureError.IllegalTypeSpec); + + return new TypeDefOrRefSignature(type); + } + } +} diff --git a/src/AsmResolver.DotNet/Signatures/BlobReadContext.cs b/src/AsmResolver.DotNet/Signatures/BlobReadContext.cs index 4e1757ed2..1af93d92d 100644 --- a/src/AsmResolver.DotNet/Signatures/BlobReadContext.cs +++ b/src/AsmResolver.DotNet/Signatures/BlobReadContext.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using AsmResolver.DotNet.Serialized; +using AsmResolver.DotNet.Signatures.Types; using AsmResolver.PE.DotNet.Metadata.Tables; namespace AsmResolver.DotNet.Signatures @@ -16,7 +17,7 @@ public readonly struct BlobReadContext /// /// The original read context. public BlobReadContext(ModuleReaderContext readerContext) - : this(readerContext, Enumerable.Empty()) + : this(readerContext, PhysicalTypeSignatureResolver.Instance, Enumerable.Empty()) { } @@ -24,10 +25,22 @@ public BlobReadContext(ModuleReaderContext readerContext) /// Creates a new instance of the structure. /// /// The original read context. + /// The object responsible for resolving raw type metadata tokens and addresses. + public BlobReadContext(ModuleReaderContext readerContext, ITypeSignatureResolver resolver) + : this(readerContext, resolver, Enumerable.Empty()) + { + } + + /// + /// Creates a new instance of the structure. + /// + /// The original read context. + /// The object responsible for resolving raw type metadata tokens and addresses. /// A collection of traversed metadata tokens. - public BlobReadContext(ModuleReaderContext readerContext, IEnumerable traversedTokens) + public BlobReadContext(ModuleReaderContext readerContext, ITypeSignatureResolver resolver, IEnumerable traversedTokens) { ReaderContext = readerContext; + TypeSignatureResolver = resolver; TraversedTokens = new HashSet(traversedTokens); } @@ -39,6 +52,14 @@ public ModuleReaderContext ReaderContext get; } + /// + /// Gets the object responsible for resolving raw type metadata tokens and addresses. + /// + public ITypeSignatureResolver TypeSignatureResolver + { + get; + } + /// /// Gets a collection of metadata tokens that were traversed during the parsing of metadata. /// @@ -47,4 +68,4 @@ public ISet TraversedTokens get; } } -} \ No newline at end of file +} diff --git a/src/AsmResolver.DotNet/Signatures/Types/ITypeSignatureResolver.cs b/src/AsmResolver.DotNet/Signatures/Types/ITypeSignatureResolver.cs new file mode 100644 index 000000000..9b7faec67 --- /dev/null +++ b/src/AsmResolver.DotNet/Signatures/Types/ITypeSignatureResolver.cs @@ -0,0 +1,27 @@ +using AsmResolver.PE.DotNet.Metadata.Tables; + +namespace AsmResolver.DotNet.Signatures.Types +{ + /// + /// Provides members for resolving raw metadata tokens and addresses to types. + /// + public interface ITypeSignatureResolver + { + /// + /// Resolves a metadata token to a type. + /// + /// The blob reading context the type is situated in. + /// The token to resolve. + /// The type. + ITypeDefOrRef ResolveToken(in BlobReadContext context, MetadataToken token); + + /// + /// Resolves an address to a runtime method table to a type signature. + /// + /// The blob reading context the type is situated in. + /// The address to resolve. + /// The type. + TypeSignature ResolveRuntimeType(in BlobReadContext context, nint address); + } + +} diff --git a/src/AsmResolver.DotNet/Signatures/Types/PhysicalTypeSignatureResolver.cs b/src/AsmResolver.DotNet/Signatures/Types/PhysicalTypeSignatureResolver.cs new file mode 100644 index 000000000..b85f6b6aa --- /dev/null +++ b/src/AsmResolver.DotNet/Signatures/Types/PhysicalTypeSignatureResolver.cs @@ -0,0 +1,57 @@ +using System; +using AsmResolver.PE.DotNet.Metadata.Tables; + +namespace AsmResolver.DotNet.Signatures.Types +{ + /// + /// Provides an implementation for the that resolves metadata tokens from + /// the underlying module's tables stream. + /// + public class PhysicalTypeSignatureResolver : ITypeSignatureResolver + { + /// + /// Gets the singleton instance of the class. + /// + public static PhysicalTypeSignatureResolver Instance + { + get; + } = new(); + + /// + public virtual ITypeDefOrRef ResolveToken(in BlobReadContext context, MetadataToken token) + { + switch (token.Table) + { + // Check for infinite recursion. + case TableIndex.TypeSpec when !context.TraversedTokens.Add(token): + context.ReaderContext.BadImage("Infinite metadata loop was detected."); + return InvalidTypeDefOrRef.Get(InvalidTypeSignatureError.MetadataLoop); + + // Any other type is legal. + case TableIndex.TypeSpec: + case TableIndex.TypeDef: + case TableIndex.TypeRef: + if (context.ReaderContext.ParentModule.TryLookupMember(token, out var member) + && member is ITypeDefOrRef typeDefOrRef) + { + return typeDefOrRef; + } + + context.ReaderContext.BadImage($"Metadata token in type signature refers to a non-existing TypeDefOrRef member {token}."); + return InvalidTypeDefOrRef.Get(InvalidTypeSignatureError.InvalidCodedIndex); + + default: + context.ReaderContext.BadImage("Invalid coded index."); + return InvalidTypeDefOrRef.Get(InvalidTypeSignatureError.InvalidCodedIndex); + } + } + + /// + public virtual TypeSignature ResolveRuntimeType(in BlobReadContext context, nint address) + { + throw new NotSupportedException( + "Encountered an COR_ELEMENT_TYPE_INTERNAL type signature which is not supported by this " + + " type signature reader. Use the AsmResolver.DotNet.Dynamic extension package instead."); + } + } +} diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs index 7eb81bfbd..78ce8e2b9 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs @@ -144,9 +144,11 @@ public static TypeSignature FromReader(in BlobReadContext context, ref BinaryStr return new BoxedTypeSignature(FromReader(context, ref reader)); case ElementType.Internal: - throw new NotSupportedException( - "Encountered an COR_ELEMENT_TYPE_INTERNAL type signature which is not supported by this " - + " type signature reader. Use the AsmResolver.DotNet.Dynamic extension package instead."); + return context.TypeSignatureResolver.ResolveRuntimeType(context, IntPtr.Size switch + { + 4 => new IntPtr(reader.ReadInt32()), + _ => new IntPtr(reader.ReadInt64()) + }); default: throw new ArgumentOutOfRangeException($"Invalid or unsupported element type {elementType}."); @@ -177,38 +179,7 @@ protected static ITypeDefOrRef ReadTypeDefOrRef(in BlobReadContext context, ref return InvalidTypeDefOrRef.Get(InvalidTypeSignatureError.IllegalTypeSpec); } - ITypeDefOrRef result; - switch (token.Table) - { - // Check for infinite recursion. - case TableIndex.TypeSpec when !context.TraversedTokens.Add(token): - context.ReaderContext.BadImage("Infinite metadata loop was detected."); - result = InvalidTypeDefOrRef.Get(InvalidTypeSignatureError.MetadataLoop); - break; - - // Any other type is legal. - case TableIndex.TypeSpec: - case TableIndex.TypeDef: - case TableIndex.TypeRef: - if (module.TryLookupMember(token, out var member) && member is ITypeDefOrRef typeDefOrRef) - { - result = typeDefOrRef; - } - else - { - context.ReaderContext.BadImage($"Metadata token in type signature refers to a non-existing TypeDefOrRef member {token}."); - result = InvalidTypeDefOrRef.Get(InvalidTypeSignatureError.InvalidCodedIndex); - } - - break; - - default: - context.ReaderContext.BadImage("Invalid coded index."); - result = InvalidTypeDefOrRef.Get(InvalidTypeSignatureError.InvalidCodedIndex); - break; - } - - return result; + return context.TypeSignatureResolver.ResolveToken(context, token); } /// diff --git a/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs b/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs index 83098adf6..24d3b916f 100644 --- a/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs @@ -164,6 +164,7 @@ public void ReadDynamicMethodInitializedByDynamicILInfoWithLocals() helper.AddArgument(typeof(int)); helper.AddArgument(typeof(bool)); helper.AddArgument(typeof(Stream)); + helper.AddArgument(typeof(Stream[])); info.SetLocalSignature(helper.GetSignature()); // Write some IL. @@ -176,10 +177,11 @@ public void ReadDynamicMethodInitializedByDynamicILInfoWithLocals() // Verify Assert.NotNull(definition.CilMethodBody); var locals = definition.CilMethodBody.LocalVariables; - Assert.Equal(3, locals.Count); + Assert.Equal(4, locals.Count); Assert.Equal("Int32", locals[0].VariableType.Name); Assert.Equal("Boolean", locals[1].VariableType.Name); Assert.Equal("Stream", locals[2].VariableType.Name); + Assert.Equal("Stream", Assert.IsAssignableFrom(locals[3].VariableType).BaseType.Name); } internal static class NestedClass From d3feaca9c4feab347dfb465144245d8128004efc Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 23 Oct 2022 22:31:33 +0200 Subject: [PATCH 6/8] BUGFIX: Set generic parameter count when importing generic methods via reflection. --- src/AsmResolver.DotNet/ReferenceImporter.cs | 9 +++- .../AsmResolver.DotNet.Dynamic.Tests.csproj | 1 + .../DynamicMethodDefinitionTest.cs | 54 +++++++++++++++++-- .../ReferenceImporterTest.cs | 30 +++++++++++ 4 files changed, 88 insertions(+), 6 deletions(-) diff --git a/src/AsmResolver.DotNet/ReferenceImporter.cs b/src/AsmResolver.DotNet/ReferenceImporter.cs index eb27ec74e..1ecc28160 100644 --- a/src/AsmResolver.DotNet/ReferenceImporter.cs +++ b/src/AsmResolver.DotNet/ReferenceImporter.cs @@ -523,7 +523,14 @@ public virtual IMethodDescriptor ImportMethod(MethodBase method) var result = new MethodSignature( method.IsStatic ? 0 : CallingConventionAttributes.HasThis, - returnType, parameterTypes); + returnType, + parameterTypes); + + if (method.IsGenericMethodDefinition) + { + result.IsGeneric = true; + result.GenericParameterCount = method.GetGenericArguments().Length; + } if (method.DeclaringType == null) throw new ArgumentException("Method's declaring type is null."); diff --git a/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj b/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj index 456d762fe..cbb134178 100644 --- a/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj +++ b/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj @@ -20,6 +20,7 @@ + diff --git a/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs b/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs index 24d3b916f..229a27713 100644 --- a/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs @@ -3,18 +3,14 @@ using System.Linq; using System.Reflection; using System.Reflection.Emit; -using AsmResolver.DotNet.Code; using AsmResolver.DotNet.Code.Cil; -using AsmResolver.DotNet.Code.Native; -using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.Signatures.Types; +using AsmResolver.DotNet.TestCases.Generics; using AsmResolver.DotNet.TestCases.Methods; using AsmResolver.IO; using AsmResolver.PE.DotNet.Cil; using AsmResolver.PE.DotNet.Metadata.Tables; -using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using Xunit; -using MethodAttributes = AsmResolver.PE.DotNet.Metadata.Tables.Rows.MethodAttributes; namespace AsmResolver.DotNet.Dynamic.Tests { @@ -121,6 +117,54 @@ public void ImportNestedType() Assert.Equal(nameof(DynamicMethodDefinitionTest), declaringType.DeclaringType.Name); } + [Fact] + public void ImportGenericTypeInstantiation() + { + var method = new DynamicMethod("Test", typeof(void), Type.EmptyTypes); + var cil = method.GetILGenerator(); + cil.Emit(OpCodes.Call, typeof(GenericType) + .GetMethod(nameof(GenericType.NonGenericMethodInGenericType))!); + cil.Emit(OpCodes.Ret); + + var contextModule = ModuleDefinition.FromFile(typeof(DynamicMethodDefinitionTest).Assembly.Location); + var definition = new DynamicMethodDefinition(contextModule, method); + + Assert.NotNull(definition.CilMethodBody); + var instruction = definition.CilMethodBody.Instructions[0]; + Assert.Equal(CilOpCodes.Call, instruction.OpCode); + var operand = Assert.IsAssignableFrom(instruction.Operand); + var type = Assert.IsAssignableFrom(operand.DeclaringType); + var signature = Assert.IsAssignableFrom(type.Signature); + Assert.Equal("Int32", signature.TypeArguments[0].Name); + Assert.Equal("String", signature.TypeArguments[1].Name); + Assert.Equal("Stream", signature.TypeArguments[2].Name); + } + + [Fact] + public void ImportGenericMethodInstantiation() + { + var method = new DynamicMethod("Test", typeof(void), Type.EmptyTypes); + var cil = method.GetILGenerator(); + cil.Emit(OpCodes.Call, typeof(NonGenericType) + .GetMethod(nameof(NonGenericType.GenericMethodInNonGenericType))! + .MakeGenericMethod(typeof(int), typeof(string), typeof(Stream))); + cil.Emit(OpCodes.Ret); + + var contextModule = ModuleDefinition.FromFile(typeof(DynamicMethodDefinitionTest).Assembly.Location); + var definition = new DynamicMethodDefinition(contextModule, method); + + Assert.NotNull(definition.CilMethodBody); + var instruction = definition.CilMethodBody.Instructions[0]; + Assert.Equal(CilOpCodes.Call, instruction.OpCode); + var operand = Assert.IsAssignableFrom(instruction.Operand); + Assert.Equal(nameof(NonGenericType.GenericMethodInNonGenericType), operand.Name); + Assert.NotNull(operand.Signature); + Assert.Equal(3, operand.Method!.Signature!.GenericParameterCount); + Assert.Equal("Int32", operand.Signature.TypeArguments[0].Name); + Assert.Equal("String", operand.Signature.TypeArguments[1].Name); + Assert.Equal("Stream", operand.Signature.TypeArguments[2].Name); + } + [Fact] public void ReadDynamicMethodInitializedByDynamicILInfoWithTokens() { diff --git a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs index 8112872b0..f1808e99d 100644 --- a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs +++ b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs @@ -5,6 +5,7 @@ using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.Signatures.Types; using AsmResolver.DotNet.TestCases.Fields; +using AsmResolver.DotNet.TestCases.Generics; using AsmResolver.DotNet.TestCases.NestedClasses; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using Xunit; @@ -501,5 +502,34 @@ public void ImportInstanceFieldByReflectionShouldConstructValidFieldSignature() Assert.NotNull(resolved); Assert.Equal(field, Assert.IsAssignableFrom(resolved), Comparer); } + + [Fact] + public void ImportGenericTypeInstantiationViaReflection() + { + var type = typeof(GenericType); + + var imported = Assert.IsAssignableFrom(_importer.ImportType(type)); + var signature = Assert.IsAssignableFrom(imported.Signature); + Assert.Equal("Int32", signature.TypeArguments[0].Name); + Assert.Equal("String", signature.TypeArguments[1].Name); + Assert.Equal("Stream", signature.TypeArguments[2].Name); + } + + [Fact] + public void ImportGenericMethodInstantiationViaReflection() + { + var method = typeof(NonGenericType) + .GetMethod(nameof(NonGenericType.GenericMethodInNonGenericType))! + .MakeGenericMethod(typeof(int), typeof(string), typeof(Stream)); + + var imported = Assert.IsAssignableFrom(_importer.ImportMethod(method)); + Assert.Equal(nameof(NonGenericType.GenericMethodInNonGenericType), imported.Name); + Assert.NotNull(imported.Signature); + Assert.True(imported.Method!.Signature!.IsGeneric); + Assert.Equal(3, imported.Method!.Signature!.GenericParameterCount); + Assert.Equal("Int32", imported.Signature.TypeArguments[0].Name); + Assert.Equal("String", imported.Signature.TypeArguments[1].Name); + Assert.Equal("Stream", imported.Signature.TypeArguments[2].Name); + } } } From ced5316e13d381a15e72a02503d9e7abee98a09c Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 24 Oct 2022 17:46:35 +0200 Subject: [PATCH 7/8] BUGFIX: Ensure type arguments are not substituted when importing methods with generic declaring type instantiations. --- src/AsmResolver.DotNet/ReferenceImporter.cs | 19 ++++++++++++++----- .../ReferenceImporterTest.cs | 13 +++++++++++++ .../GenericType.cs | 9 +++++++-- .../NonGenericType.cs | 12 +++++++++--- 4 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/AsmResolver.DotNet/ReferenceImporter.cs b/src/AsmResolver.DotNet/ReferenceImporter.cs index 1ecc28160..1f0821c10 100644 --- a/src/AsmResolver.DotNet/ReferenceImporter.cs +++ b/src/AsmResolver.DotNet/ReferenceImporter.cs @@ -506,14 +506,26 @@ public virtual IMethodDescriptor ImportMethod(MethodBase method) if (method is null) throw new ArgumentNullException(nameof(method)); + // We need to create a method spec if this method is a generic instantiation. if (method.IsGenericMethod && !method.IsGenericMethodDefinition) return ImportGenericMethod((MethodInfo) method); + // Test whether we have a declaring type. + var originalDeclaringType = method.DeclaringType; + if (originalDeclaringType is null) + throw new ArgumentException("Method's declaring type is null."); + + // System.Reflection substitutes all type parameters in the MethodInfo instance with their concrete + // arguments if the declaring type is a generic instantiation. However in metadata, we need the original + // parameter references. Thus, resolve the original method info first if required. + if (originalDeclaringType.IsGenericType && !originalDeclaringType.IsGenericTypeDefinition) + method = method.Module.ResolveMethod(method.MetadataToken)!; + var returnType = method is MethodInfo info ? ImportTypeSignature(info.ReturnType) : TargetModule.CorLibTypeFactory.Void; - var parameters = method.DeclaringType is { IsConstructedGenericType: true } + var parameters = originalDeclaringType is { IsConstructedGenericType: true } ? method.Module.ResolveMethod(method.MetadataToken)!.GetParameters() : method.GetParameters(); @@ -532,10 +544,7 @@ public virtual IMethodDescriptor ImportMethod(MethodBase method) result.GenericParameterCount = method.GetGenericArguments().Length; } - if (method.DeclaringType == null) - throw new ArgumentException("Method's declaring type is null."); - - return new MemberReference(ImportType(method.DeclaringType), method.Name, result); + return new MemberReference(ImportType(originalDeclaringType), method.Name, result); } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls AsmResolver.DotNet.ReferenceImporter.ImportMethod(System.Reflection.MethodBase)")] diff --git a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs index f1808e99d..2bbd52f5b 100644 --- a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs +++ b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs @@ -531,5 +531,18 @@ public void ImportGenericMethodInstantiationViaReflection() Assert.Equal("String", imported.Signature.TypeArguments[1].Name); Assert.Equal("Stream", imported.Signature.TypeArguments[2].Name); } + + [Fact] + public void ImportGenericMethodInstantiationWithReturnTypeViaReflection() + { + var method = typeof(GenericType) + .GetMethod(nameof(GenericType.NonGenericMethodWithReturnType))!; + + var imported = Assert.IsAssignableFrom(_importer.ImportMethod(method)); + Assert.Equal(nameof(GenericType.NonGenericMethodWithReturnType), imported.Name); + Assert.NotNull(imported.Signature); + var signature = Assert.IsAssignableFrom(imported.Signature.ReturnType); + Assert.Equal(2, signature.Index); + } } } diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/GenericType.cs b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/GenericType.cs index 8c0cbb875..bfddea4ec 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/GenericType.cs +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/GenericType.cs @@ -7,9 +7,14 @@ public class GenericType public static void NonGenericMethodInGenericType() { } - + public static void GenericMethodInGenericType() { } + + public static T3 NonGenericMethodWithReturnType() + { + return default; + } } -} \ No newline at end of file +} diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/NonGenericType.cs b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/NonGenericType.cs index 8465cc90c..9b499bfe4 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/NonGenericType.cs +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/NonGenericType.cs @@ -7,7 +7,7 @@ public class NonGenericType public static void NonGenericMethodInNonGenericType() { } - + public static void GenericMethodInNonGenericType() { } @@ -16,8 +16,14 @@ public static void GenericMethodWithConstraints() where T1 : IFoo where T2 : IFoo, IBar { - + + } + + public static T GenericMethodWithReturnType() + { + return default; } + } public interface IFoo @@ -27,4 +33,4 @@ public interface IFoo public interface IBar { } -} \ No newline at end of file +} From 9c2352596d3a0e257c9e3f9ecbd047a34c10f798 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 26 Oct 2022 22:13:50 +0200 Subject: [PATCH 8/8] Bump to 5.0.0.beta.2, remove nullability warnings in tests. --- Directory.Build.props | 2 +- test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index da8160f3a..13a85bb60 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,7 +7,7 @@ https://github.com/Washi1337/AsmResolver git 10 - 5.0.0-beta.1 + 5.0.0-beta.2 diff --git a/test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs b/test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs index b60f1ed9d..0b63631d2 100644 --- a/test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs +++ b/test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs @@ -32,22 +32,24 @@ public void ReadExportNames() public void ReadExportAddresses() { var image = PEImage.FromBytes(Properties.Resources.SimpleDll_Exports); + Assert.NotNull(image.Exports); Assert.Equal(new[] { 0x000111DBu, 0x00011320u, - }, image.Exports?.Entries.Select(e => e.Address.Rva)); + }, image.Exports.Entries.Select(e => e.Address.Rva)); } [Fact] public void ReadOrdinals() { var image = PEImage.FromBytes(Properties.Resources.SimpleDll_Exports); + Assert.NotNull(image.Exports); Assert.Equal(new[] { 1u, 2u, - }, image.Exports?.Entries.Select(e => e.Ordinal)); + }, image.Exports.Entries.Select(e => e.Ordinal)); } [Fact]