From 395872d63123dccc03cf380de9feaf02c636dc7a Mon Sep 17 00:00:00 2001 From: Roman Starkov Date: Mon, 1 Jan 2024 23:25:02 +0000 Subject: [PATCH 01/15] Fix SignatureComparer.GetHashCode(GenericInstanceTypeSignature) --- .../Signatures/SignatureComparer.TypeSignature.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeSignature.cs b/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeSignature.cs index fa11e5a67..73105a2eb 100644 --- a/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeSignature.cs @@ -233,7 +233,7 @@ public int GetHashCode(GenericInstanceTypeSignature obj) unchecked { int hashCode = (int) obj.ElementType << ElementTypeOffset; - hashCode = (hashCode * 397) ^ obj.GenericType.GetHashCode(); + hashCode = (hashCode * 397) ^ GetHashCode(obj.GenericType); hashCode = (hashCode * 397) ^ GetHashCode(obj.TypeArguments); return hashCode; } From 33b6b80650335c3d613a545adaf9416099b7125e Mon Sep 17 00:00:00 2001 From: Roman Starkov Date: Mon, 1 Jan 2024 23:34:37 +0000 Subject: [PATCH 02/15] Fix SignatureComparer.GetHashCode(CustomModifierTypeSignature) --- .../Signatures/SignatureComparer.TypeSignature.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeSignature.cs b/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeSignature.cs index 73105a2eb..edbacbc2b 100644 --- a/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeSignature.cs @@ -208,8 +208,8 @@ public int GetHashCode(CustomModifierTypeSignature obj) unchecked { int hashCode = (int) obj.ElementType << ElementTypeOffset; - hashCode = (hashCode * 397) ^ obj.ModifierType.GetHashCode(); - hashCode = (hashCode * 397) ^ obj.BaseType.GetHashCode(); + hashCode = (hashCode * 397) ^ GetHashCode(obj.ModifierType); + hashCode = (hashCode * 397) ^ GetHashCode(obj.BaseType); return hashCode; } } From 9ab4e94d5e18e6c5fe373a22fcb31d9f2add3554 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 3 Jan 2024 23:55:44 +0100 Subject: [PATCH 03/15] BUGFIX: Optimize ldloca and ldarga to their short forms if possible. --- .../Code/Cil/CilInstructionCollection.cs | 25 +++++++---- .../Code/Cil/CilInstructionCollectionTest.cs | 41 +++++++++++++++++-- 2 files changed, 55 insertions(+), 11 deletions(-) diff --git a/src/AsmResolver.DotNet/Code/Cil/CilInstructionCollection.cs b/src/AsmResolver.DotNet/Code/Cil/CilInstructionCollection.cs index 970bc7b26..60616bc07 100644 --- a/src/AsmResolver.DotNet/Code/Cil/CilInstructionCollection.cs +++ b/src/AsmResolver.DotNet/Code/Cil/CilInstructionCollection.cs @@ -459,7 +459,7 @@ private bool TryOptimizeVariable(CilInstruction instruction) { var variable = instruction.GetLocalVariable(Owner.LocalVariables); - CilOpCode code = instruction.OpCode; + var code = instruction.OpCode; object? operand = instruction.Operand; if (instruction.IsLdloc()) @@ -470,7 +470,7 @@ private bool TryOptimizeVariable(CilInstruction instruction) 1 => (CilOpCodes.Ldloc_1, null), 2 => (CilOpCodes.Ldloc_2, null), 3 => (CilOpCodes.Ldloc_3, null), - {} x when x >= byte.MinValue && x <= byte.MaxValue => (CilOpCodes.Ldloc_S, variable), + <= byte.MaxValue and >= byte.MinValue => (CilOpCodes.Ldloc_S, variable), _ => (CilOpCodes.Ldloc, variable), }; } @@ -482,10 +482,15 @@ private bool TryOptimizeVariable(CilInstruction instruction) 1 => (CilOpCodes.Stloc_1, null), 2 => (CilOpCodes.Stloc_2, null), 3 => (CilOpCodes.Stloc_3, null), - {} x when x >= byte.MinValue && x <= byte.MaxValue => (CilOpCodes.Stloc_S, variable), + <= byte.MaxValue and >= byte.MinValue => (CilOpCodes.Stloc_S, variable), _ => (CilOpCodes.Stloc, variable), }; } + else if (instruction.OpCode.Code == CilCode.Ldloca) + { + if (variable.Index is >= byte.MinValue and <= byte.MaxValue) + code = CilOpCodes.Ldloca_S; + } if (code != instruction.OpCode) { @@ -501,7 +506,7 @@ private bool TryOptimizeArgument(CilInstruction instruction) { var parameter = instruction.GetParameter(Owner.Owner.Parameters); - CilOpCode code = instruction.OpCode; + var code = instruction.OpCode; object? operand = instruction.Operand; if (instruction.IsLdarg()) @@ -512,15 +517,19 @@ private bool TryOptimizeArgument(CilInstruction instruction) 1 => (CilOpCodes.Ldarg_1, null), 2 => (CilOpCodes.Ldarg_2, null), 3 => (CilOpCodes.Ldarg_3, null), - {} x when x >= byte.MinValue && x <= byte.MaxValue => (CilOpCodes.Ldarg_S, parameter), + >= byte.MinValue and <= byte.MaxValue => (CilOpCodes.Ldarg_S, parameter), _ => (CilOpCodes.Ldarg, parameter), }; } else if (instruction.IsStarg()) { - code = parameter.MethodSignatureIndex <= byte.MaxValue - ? CilOpCodes.Starg_S - : CilOpCodes.Starg; + if (parameter.MethodSignatureIndex <= byte.MaxValue) + code = CilOpCodes.Starg_S; + } + else if (instruction.OpCode.Code == CilCode.Ldarga) + { + if (parameter.Index is >= byte.MinValue and <= byte.MaxValue) + code = CilOpCodes.Ldarga_S; } if (code != instruction.OpCode) diff --git a/test/AsmResolver.DotNet.Tests/Code/Cil/CilInstructionCollectionTest.cs b/test/AsmResolver.DotNet.Tests/Code/Cil/CilInstructionCollectionTest.cs index 78eab1163..c3ad3549b 100644 --- a/test/AsmResolver.DotNet.Tests/Code/Cil/CilInstructionCollectionTest.cs +++ b/test/AsmResolver.DotNet.Tests/Code/Cil/CilInstructionCollectionTest.cs @@ -62,7 +62,7 @@ public void OptimizeFirst4ArgumentsToMacros(int index, CilCode expectedMacro) public void OptimizeHiddenThisToLdarg0() { var instructions = CreateDummyMethod(true, 0, 0); - instructions.Add(CilOpCodes.Ldarg, instructions.Owner.Owner.Parameters.ThisParameter); + instructions.Add(CilOpCodes.Ldarg, instructions.Owner.Owner.Parameters.ThisParameter!); instructions.Add(CilOpCodes.Ret); instructions.OptimizeMacros(); @@ -183,6 +183,23 @@ public void OptimizeLoadLocalInstructions(int index, CilCode expected) Assert.Equal(expected, instructions[0].OpCode.Code); } + [Theory] + [InlineData(0, CilCode.Ldloca_S)] + [InlineData(1, CilCode.Ldloca_S)] + [InlineData(255, CilCode.Ldloca_S)] + [InlineData(256, CilCode.Ldloca)] + public void OptimizeLoadLocalAddressInstructions(int index, CilCode expected) + { + var instructions = CreateDummyMethod(false, 0, index + 1); + + instructions.Add(CilOpCodes.Ldloca, instructions.Owner.LocalVariables[index]); + instructions.Add(CilOpCodes.Ret); + + instructions.OptimizeMacros(); + + Assert.Equal(expected, instructions[0].OpCode.Code); + } + [Theory] [InlineData(0, CilCode.Stloc_0)] [InlineData(1, CilCode.Stloc_1)] @@ -225,6 +242,24 @@ public void OptimizeLoadArgInstructions(int index, CilCode expected) Assert.Equal(expected, instructions[0].OpCode.Code); } + [Theory] + [InlineData(0, CilCode.Ldarga_S)] + [InlineData(1, CilCode.Ldarga_S)] + [InlineData(255, CilCode.Ldarga_S)] + [InlineData(256, CilCode.Ldarga)] + public void OptimizeLoadArgAddressInstructions(int index, CilCode expected) + { + var instructions = CreateDummyMethod(false, index + 1, 0); + var method = instructions.Owner.Owner; + + instructions.Add(CilOpCodes.Ldarga, method.Parameters[index]); + instructions.Add(CilOpCodes.Ret); + + instructions.OptimizeMacros(); + + Assert.Equal(expected, instructions[0].OpCode.Code); + } + [Theory] [InlineData(0, CilCode.Starg_S)] [InlineData(255, CilCode.Starg_S)] @@ -374,7 +409,7 @@ public void OptimizeShouldUpdateOffsets() instructions.OptimizeMacros(); int[] offsets = instructions.Select(i => i.Offset).ToArray(); - Assert.NotEqual(offsets, instructions.Select(i => 0)); + Assert.All(offsets.Skip(1), offset => Assert.NotEqual(0, offset)); instructions.CalculateOffsets(); Assert.Equal(offsets, instructions.Select(i => i.Offset)); } @@ -393,7 +428,7 @@ public void ExpandShouldUpdateOffsets() instructions.ExpandMacros(); int[] offsets = instructions.Select(i => i.Offset).ToArray(); - Assert.NotEqual(offsets, instructions.Select(i => 0)); + Assert.All(offsets.Skip(1), offset => Assert.NotEqual(0, offset)); instructions.CalculateOffsets(); Assert.Equal(offsets, instructions.Select(i => i.Offset)); } From d802b9060e7cb980b64b3236b8ebb3feed072042 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 5 Jan 2024 01:56:04 +0100 Subject: [PATCH 04/15] Add duplicated membership tests. --- .../FieldDefinitionTest.cs | 20 +++++++ .../TypeDefinitionTest.cs | 59 ++++++++++++++++++- 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/test/AsmResolver.DotNet.Tests/FieldDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/FieldDefinitionTest.cs index 3e0c4b177..ba53b1598 100644 --- a/test/AsmResolver.DotNet.Tests/FieldDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/FieldDefinitionTest.cs @@ -6,6 +6,7 @@ using AsmResolver.DotNet.TestCases.Fields; using AsmResolver.DotNet.TestCases.Types.Structs; using AsmResolver.PE.DotNet.Cil; +using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using Xunit; namespace AsmResolver.DotNet.Tests @@ -198,6 +199,25 @@ public void PersistentFieldOffset(string name, int offset) Assert.Equal(offset, newField.FieldOffset); } + [Fact] + public void AddSameFieldTwiceToTypeShouldThrow() + { + var module = new ModuleDefinition("SomeModule"); + var field = new FieldDefinition("SomeField", FieldAttributes.Public, module.CorLibTypeFactory.Int32); + var type = new TypeDefinition("SomeNamespace", "SomeType", TypeAttributes.Public); + type.Fields.Add(field); + Assert.Throws(() => type.Fields.Add(field)); + } + [Fact] + public void AddSameFieldToDifferentTypesShouldThrow() + { + var module = new ModuleDefinition("SomeModule"); + var field = new FieldDefinition("SomeField", FieldAttributes.Public, module.CorLibTypeFactory.Int32); + var type1 = new TypeDefinition("SomeNamespace", "SomeType1", TypeAttributes.Public); + var type2 = new TypeDefinition("SomeNamespace", "SomeType2", TypeAttributes.Public); + type1.Fields.Add(field); + Assert.Throws(() => type2.Fields.Add(field)); + } } } diff --git a/test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs index 4e2ebfa28..3c946ef7a 100644 --- a/test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using AsmResolver.DotNet.Builder; +using AsmResolver.DotNet.Code.Cil; using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.Signatures.Types; using AsmResolver.DotNet.TestCases.CustomAttributes; @@ -13,6 +15,7 @@ using AsmResolver.DotNet.TestCases.Properties; using AsmResolver.DotNet.TestCases.Types; using AsmResolver.DotNet.TestCases.Types.Structs; +using AsmResolver.PE.DotNet.Cil; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using Xunit; @@ -198,8 +201,10 @@ public void ReadNestedFullName() public void ReadNestedNestedFullName() { var module = ModuleDefinition.FromFile(typeof(TopLevelClass1).Assembly.Location); - var type = (TypeDefinition) module.LookupMember(typeof(TopLevelClass1.Nested1.Nested1Nested2).MetadataToken); - Assert.Equal("AsmResolver.DotNet.TestCases.NestedClasses.TopLevelClass1+Nested1+Nested1Nested2", type.FullName); + var type = (TypeDefinition) module.LookupMember(typeof(TopLevelClass1.Nested1.Nested1Nested2) + .MetadataToken); + Assert.Equal("AsmResolver.DotNet.TestCases.NestedClasses.TopLevelClass1+Nested1+Nested1Nested2", + type.FullName); } [Fact] @@ -723,5 +728,55 @@ public void GetNonExistingConstructorShouldReturnNull() Assert.Null(type.GetConstructor(factory.String)); Assert.Null(type.GetConstructor(factory.String, factory.String)); } + + [Fact] + public void AddTypeToModuleShouldSetOwner() + { + var module = new ModuleDefinition("Dummy"); + var type = new TypeDefinition("SomeNamespace", "SomeType", TypeAttributes.Public); + module.TopLevelTypes.Add(type); + Assert.Same(module, type.Module); + } + + [Fact] + public void AddNestedTypeToModuleShouldSetOwner() + { + var module = new ModuleDefinition("Dummy"); + var type1 = new TypeDefinition("SomeNamespace", "SomeType", TypeAttributes.Public); + var type2 = new TypeDefinition(null, "NestedType", TypeAttributes.NestedPublic); + module.TopLevelTypes.Add(type1); + type1.NestedTypes.Add(type2); + Assert.Same(type1, type2.DeclaringType); + Assert.Same(module, type2.Module); + } + + [Fact] + public void AddSameTypeTwiceToModuleShouldThrow() + { + var module = new ModuleDefinition("Dummy"); + var type = new TypeDefinition("SomeNamespace", "SomeType", TypeAttributes.Public); + module.TopLevelTypes.Add(type); + Assert.Throws(() => module.TopLevelTypes.Add(type)); + } + + [Fact] + public void AddSameTypeTwiceToNestedTypeShouldThrow() + { + var type = new TypeDefinition("SomeNamespace", "SomeType", TypeAttributes.Public); + var nestedType = new TypeDefinition(null, "SomeType", TypeAttributes.NestedPublic); + type.NestedTypes.Add(nestedType); + Assert.Throws(() => type.NestedTypes.Add(nestedType)); + } + + [Fact] + public void AddSameNestedTypeToDifferentTypesShouldThrow() + { + var type1 = new TypeDefinition("SomeNamespace", "SomeType1", TypeAttributes.Public); + var type2 = new TypeDefinition("SomeNamespace", "SomeType2", TypeAttributes.Public); + var nestedType = new TypeDefinition(null, "SomeType", TypeAttributes.NestedPublic); + type1.NestedTypes.Add(nestedType); + Assert.Throws(() => type2.NestedTypes.Add(nestedType)); + } + } } From a51a50df871bf0d0950fa8239df0733aee234e7b Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 5 Jan 2024 02:20:28 +0100 Subject: [PATCH 05/15] Add MemberCollection that can internally bypass owner checks of members. --- .../Collections/MemberCollection.cs | 31 +++++++++++++++++++ .../Collections/MethodSemanticsCollection.cs | 6 ++-- src/AsmResolver.DotNet/ModuleDefinition.cs | 1 - .../Serialized/SerializedEventDefinition.cs | 2 +- .../Serialized/SerializedGenericParameter.cs | 4 +-- .../Serialized/SerializedMethodDefinition.cs | 8 ++--- .../SerializedModuleDefinition.Metadata.cs | 8 ++--- .../Serialized/SerializedModuleDefinition.cs | 24 +++++++------- .../SerializedPropertyDefinition.cs | 2 +- .../Serialized/SerializedTypeDefinition.cs | 19 +++++------- .../Signatures/Types/CorLibTypeFactory.cs | 5 ++- .../Collections/OwnedCollection.cs | 2 +- 12 files changed, 68 insertions(+), 44 deletions(-) create mode 100644 src/AsmResolver.DotNet/Collections/MemberCollection.cs diff --git a/src/AsmResolver.DotNet/Collections/MemberCollection.cs b/src/AsmResolver.DotNet/Collections/MemberCollection.cs new file mode 100644 index 000000000..1803c3141 --- /dev/null +++ b/src/AsmResolver.DotNet/Collections/MemberCollection.cs @@ -0,0 +1,31 @@ +using AsmResolver.Collections; + +namespace AsmResolver.DotNet.Collections +{ + /// + /// Represents an indexed member collection where each member is owned by some object, and prevents the member from + /// being added to any other instance of the collection. + /// + /// The type of the owner object. + /// The type of elements to store. + public class MemberCollection : OwnedCollection + where TOwner : class + where TMember : class, IOwnedCollectionElement + { + internal MemberCollection(TOwner owner) + : base(owner) + { + } + + internal MemberCollection(TOwner owner, int capacity) + : base(owner, capacity) + { + } + + internal void AddNoOwnerCheck(TMember member) + { + member.Owner = Owner; + Items.Add(member); + } + } +} diff --git a/src/AsmResolver.DotNet/Collections/MethodSemanticsCollection.cs b/src/AsmResolver.DotNet/Collections/MethodSemanticsCollection.cs index 35cfefda3..12f403669 100644 --- a/src/AsmResolver.DotNet/Collections/MethodSemanticsCollection.cs +++ b/src/AsmResolver.DotNet/Collections/MethodSemanticsCollection.cs @@ -1,15 +1,13 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq; -using AsmResolver.Collections; namespace AsmResolver.DotNet.Collections { /// /// Provides an implementation of a list of method semantics that are associated to a property or event. /// - public class MethodSemanticsCollection : OwnedCollection< IHasSemantics, MethodSemantics> + public class MethodSemanticsCollection : MemberCollection { /// /// Creates a new instance of the class. @@ -36,7 +34,7 @@ internal bool ValidateMembership set; } = true; - private void AssertSemanticsValidity([NotNull] MethodSemantics item) + private void AssertSemanticsValidity(MethodSemantics item) { if (!ValidateMembership) return; diff --git a/src/AsmResolver.DotNet/ModuleDefinition.cs b/src/AsmResolver.DotNet/ModuleDefinition.cs index e539a0197..ff49caaf2 100644 --- a/src/AsmResolver.DotNet/ModuleDefinition.cs +++ b/src/AsmResolver.DotNet/ModuleDefinition.cs @@ -302,7 +302,6 @@ public ModuleDefinition(Utf8String? name) Name = name; CorLibTypeFactory = CorLibTypeFactory.CreateMscorlib40TypeFactory(this); - AssemblyReferences.Add((AssemblyReference)CorLibTypeFactory.CorLibScope); MetadataResolver = new DefaultMetadataResolver(new DotNetFrameworkAssemblyResolver()); TopLevelTypes.Add(new TypeDefinition(null, TypeDefinition.ModuleTypeName, 0)); diff --git a/src/AsmResolver.DotNet/Serialized/SerializedEventDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedEventDefinition.cs index 086de5b61..bcbb9aff5 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedEventDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedEventDefinition.cs @@ -69,7 +69,7 @@ protected override IList GetSemantics() foreach (uint rid in module.GetMethodSemantics(MetadataToken)) { var semanticsToken = new MetadataToken(TableIndex.MethodSemantics, rid); - result.Add((MethodSemantics) module.LookupMember(semanticsToken)); + result.AddNoOwnerCheck((MethodSemantics) module.LookupMember(semanticsToken)); } result.ValidateMembership = true; diff --git a/src/AsmResolver.DotNet/Serialized/SerializedGenericParameter.cs b/src/AsmResolver.DotNet/Serialized/SerializedGenericParameter.cs index 592a6ef1a..db448b1db 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedGenericParameter.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedGenericParameter.cs @@ -54,12 +54,12 @@ protected override IList GetConstraints() { var module = _context.ParentModule; var rids = module.GetGenericParameterConstraints(MetadataToken); - var result = new OwnedCollection(this, rids.Count); + var result = new MemberCollection(this, rids.Count); foreach (uint rid in rids) { var constraintToken = new MetadataToken(TableIndex.GenericParamConstraint, rid); - result.Add((GenericParameterConstraint) module.LookupMember(constraintToken)); + result.AddNoOwnerCheck((GenericParameterConstraint) module.LookupMember(constraintToken)); } return result; diff --git a/src/AsmResolver.DotNet/Serialized/SerializedMethodDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedMethodDefinition.cs index 42675f8ab..046b10039 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedMethodDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedMethodDefinition.cs @@ -78,12 +78,12 @@ protected override IList GetSecurityDeclarations() => protected override IList GetParameterDefinitions() { var parameterRange = _context.ParentModule.GetParameterRange(MetadataToken.Rid); - var result = new OwnedCollection(this, parameterRange.Count); + var result = new MemberCollection(this, parameterRange.Count); foreach (var token in parameterRange) { if (_context.ParentModule.TryLookupMember(token, out var member) && member is ParameterDefinition parameter) - result.Add(parameter); + result.AddNoOwnerCheck(parameter); } return result; @@ -106,14 +106,14 @@ protected override IList GetParameterDefinitions() protected override IList GetGenericParameters() { var rids = _context.ParentModule.GetGenericParameters(MetadataToken); - var result = new OwnedCollection(this, rids.Count); + var result = new MemberCollection(this, rids.Count); foreach (uint rid in rids) { if (_context.ParentModule.TryLookupMember(new MetadataToken(TableIndex.GenericParam, rid), out var member) && member is GenericParameter genericParameter) { - result.Add(genericParameter); + result.AddNoOwnerCheck(genericParameter); } } diff --git a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.Metadata.cs b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.Metadata.cs index 243f31ca5..16a1e0133 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.Metadata.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.Metadata.cs @@ -229,12 +229,12 @@ internal IList GetCustomAttributeCollection(IHasCustomAttribute { EnsureCustomAttributesInitialized(); var rids = _customAttributes.GetValues(owner.MetadataToken); - var result = new OwnedCollection(owner, rids.Count); + var result = new MemberCollection(owner, rids.Count); foreach (uint rid in rids) { var attribute = (CustomAttribute) LookupMember(new MetadataToken(TableIndex.CustomAttribute, rid)); - result.Add(attribute); + result.AddNoOwnerCheck(attribute); } return result; @@ -274,12 +274,12 @@ internal IList GetSecurityDeclarationCollection(IHasSecurit { EnsureSecurityDeclarationsInitialized(); var rids = _securityDeclarations.GetValues(owner.MetadataToken); - var result = new OwnedCollection(owner, rids.Count); + var result = new MemberCollection(owner, rids.Count); foreach (uint rid in rids) { var attribute = (SecurityDeclaration) LookupMember(new MetadataToken(TableIndex.DeclSecurity, rid)); - result.Add(attribute); + result.AddNoOwnerCheck(attribute); } return result; diff --git a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs index 464553d67..9b695b802 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs @@ -191,7 +191,7 @@ protected override IList GetTopLevelTypes() var typeDefTable = ReaderContext.TablesStream.GetTable(TableIndex.TypeDef); int nestedTypeCount = ReaderContext.TablesStream.GetTable(TableIndex.NestedClass).Count; - var types = new OwnedCollection(this, + var types = new MemberCollection(this, typeDefTable.Count - nestedTypeCount); for (int i = 0; i < typeDefTable.Count; i++) @@ -200,7 +200,7 @@ protected override IList GetTopLevelTypes() if (_typeDefTree.GetKey(rid) == 0) { var token = new MetadataToken(TableIndex.TypeDef, rid); - types.Add(_memberFactory.LookupTypeDefinition(token)!); + types.AddNoOwnerCheck(_memberFactory.LookupTypeDefinition(token)!); } } @@ -212,13 +212,13 @@ protected override IList GetAssemblyReferences() { var table = ReaderContext.TablesStream.GetTable(TableIndex.AssemblyRef); - var result = new OwnedCollection(this, table.Count); + var result = new MemberCollection(this, table.Count); // Don't use the member factory here, this method may be called before the member factory is initialized. for (int i = 0; i < table.Count; i++) { var token = new MetadataToken(TableIndex.AssemblyRef, (uint) i + 1); - result.Add(new SerializedAssemblyReference(ReaderContext, token, table[i])); + result.AddNoOwnerCheck(new SerializedAssemblyReference(ReaderContext, token, table[i])); } return result; @@ -229,13 +229,13 @@ protected override IList GetModuleReferences() { var table = ReaderContext.TablesStream.GetTable(TableIndex.ModuleRef); - var result = new OwnedCollection(this, table.Count); + var result = new MemberCollection(this, table.Count); for (int i = 0; i < table.Count; i++) { var token = new MetadataToken(TableIndex.ModuleRef, (uint) i + 1); if (_memberFactory.TryLookupMember(token, out var member) && member is ModuleReference module) - result.Add(module); + result.AddNoOwnerCheck(module); } return result; @@ -246,13 +246,13 @@ protected override IList GetFileReferences() { var table = ReaderContext.TablesStream.GetTable(TableIndex.File); - var result = new OwnedCollection(this, table.Count); + var result = new MemberCollection(this, table.Count); for (int i = 0; i < table.Count; i++) { var token = new MetadataToken(TableIndex.File, (uint) i + 1); if (_memberFactory.TryLookupMember(token, out var member) && member is FileReference file) - result.Add(file); + result.AddNoOwnerCheck(file); } return result; @@ -263,13 +263,13 @@ protected override IList GetResources() { var table = ReaderContext.TablesStream.GetTable(TableIndex.ManifestResource); - var result = new OwnedCollection(this, table.Count); + var result = new MemberCollection(this, table.Count); for (int i = 0; i < table.Count; i++) { var token = new MetadataToken(TableIndex.ManifestResource, (uint) i + 1); if (_memberFactory.TryLookupMember(token, out var member) && member is ManifestResource resource) - result.Add(resource); + result.AddNoOwnerCheck(resource); } return result; @@ -280,13 +280,13 @@ protected override IList GetExportedTypes() { var table = ReaderContext.TablesStream.GetTable(TableIndex.ExportedType); - var result = new OwnedCollection(this, table.Count); + var result = new MemberCollection(this, table.Count); for (int i = 0; i < table.Count; i++) { var token = new MetadataToken(TableIndex.ExportedType, (uint) i + 1); if (_memberFactory.TryLookupMember(token, out var member) && member is ExportedType exportedType) - result.Add(exportedType); + result.AddNoOwnerCheck(exportedType); } return result; diff --git a/src/AsmResolver.DotNet/Serialized/SerializedPropertyDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedPropertyDefinition.cs index 1d235e66a..ac71f8512 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedPropertyDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedPropertyDefinition.cs @@ -76,7 +76,7 @@ protected override IList GetSemantics() foreach (uint rid in rids) { var semanticsToken = new MetadataToken(TableIndex.MethodSemantics, rid); - result.Add((MethodSemantics) module.LookupMember(semanticsToken)); + result.AddNoOwnerCheck((MethodSemantics) module.LookupMember(semanticsToken)); } result.ValidateMembership = true; diff --git a/src/AsmResolver.DotNet/Serialized/SerializedTypeDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedTypeDefinition.cs index ba495ba5c..60f1096f5 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedTypeDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedTypeDefinition.cs @@ -1,10 +1,7 @@ using System; using System.Collections.Generic; -using System.Linq; using AsmResolver.Collections; using AsmResolver.DotNet.Collections; -using AsmResolver.PE.DotNet.Metadata; -using AsmResolver.PE.DotNet.Metadata.Strings; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; @@ -58,12 +55,12 @@ public SerializedTypeDefinition(ModuleReaderContext context, MetadataToken token protected override IList GetNestedTypes() { var rids = _context.ParentModule.GetNestedTypeRids(MetadataToken.Rid); - var result = new OwnedCollection(this, rids.Count); + var result = new MemberCollection(this, rids.Count); foreach (uint rid in rids) { var nestedType = (TypeDefinition) _context.ParentModule.LookupMember(new MetadataToken(TableIndex.TypeDef, rid)); - result.Add(nestedType); + result.AddNoOwnerCheck(nestedType); } return result; @@ -97,10 +94,10 @@ protected override IList GetEvents() => private IList CreateMemberCollection(MetadataRange range) where TMember : class, IMetadataMember, IOwnedCollectionElement { - var result = new OwnedCollection(this, range.Count); + var result = new MemberCollection(this, range.Count); foreach (var token in range) - result.Add((TMember) _context.ParentModule.LookupMember(token)); + result.AddNoOwnerCheck((TMember) _context.ParentModule.LookupMember(token)); return result; } @@ -117,14 +114,14 @@ protected override IList GetSecurityDeclarations() => protected override IList GetGenericParameters() { var rids = _context.ParentModule.GetGenericParameters(MetadataToken); - var result = new OwnedCollection(this, rids.Count); + var result = new MemberCollection(this, rids.Count); foreach (uint rid in rids) { if (_context.ParentModule.TryLookupMember(new MetadataToken(TableIndex.GenericParam, rid), out var member) && member is GenericParameter genericParameter) { - result.Add(genericParameter); + result.AddNoOwnerCheck(genericParameter); } } @@ -135,14 +132,14 @@ protected override IList GetGenericParameters() protected override IList GetInterfaces() { var rids = _context.ParentModule.GetInterfaceImplementationRids(MetadataToken); - var result = new OwnedCollection(this, rids.Count); + var result = new MemberCollection(this, rids.Count); foreach (uint rid in rids) { if (_context.ParentModule.TryLookupMember(new MetadataToken(TableIndex.InterfaceImpl, rid), out var member) && member is InterfaceImplementation type) { - result.Add(type); + result.AddNoOwnerCheck(type); } } diff --git a/src/AsmResolver.DotNet/Signatures/Types/CorLibTypeFactory.cs b/src/AsmResolver.DotNet/Signatures/Types/CorLibTypeFactory.cs index 516342867..2d8075a2f 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/CorLibTypeFactory.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/CorLibTypeFactory.cs @@ -136,15 +136,14 @@ public IResolutionScope CorLibScope /// Gets the element type signature for . /// public CorLibTypeSignature Object => GetOrCreateCorLibTypeSignature(ref _object, ElementType.Object, nameof(Object)); - + /// /// Creates a new type factory that references mscorlib 4.0.0.0. /// /// The factory. public static CorLibTypeFactory CreateMscorlib40TypeFactory(ModuleDefinition module) { - var importer = new ReferenceImporter(module); - return new CorLibTypeFactory(importer.ImportScope(KnownCorLibs.MsCorLib_v4_0_0_0)); + return new CorLibTypeFactory(KnownCorLibs.MsCorLib_v4_0_0_0.ImportWith(module.DefaultImporter)); } /// diff --git a/src/AsmResolver/Collections/OwnedCollection.cs b/src/AsmResolver/Collections/OwnedCollection.cs index 6facee7e8..b5617dc90 100644 --- a/src/AsmResolver/Collections/OwnedCollection.cs +++ b/src/AsmResolver/Collections/OwnedCollection.cs @@ -55,7 +55,7 @@ protected void AssertNotNullAndHasNoOwner(TItem? item) { if (item is null) throw new ArgumentNullException(nameof(item)); - if (item.Owner != null && item.Owner != Owner) + if (item.Owner is not null) throw new ArgumentException("Item is already added to another collection."); } From 619c6e15c64359a3e0c73639a44308a970578d33 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 10 Jan 2024 13:16:57 +0100 Subject: [PATCH 06/15] Switch underlying Assembly::Modules collection to MemberCollection. --- .../Serialized/SerializedAssemblyDefinition.cs | 10 +++------- .../Serialized/SerializedModuleDefinition.cs | 4 ---- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/AsmResolver.DotNet/Serialized/SerializedAssemblyDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedAssemblyDefinition.cs index 3af420240..d6e254453 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedAssemblyDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedAssemblyDefinition.cs @@ -2,8 +2,7 @@ using System.Collections.Generic; using System.Runtime.Versioning; using AsmResolver.Collections; -using AsmResolver.PE.DotNet.Metadata.Blob; -using AsmResolver.PE.DotNet.Metadata.Strings; +using AsmResolver.DotNet.Collections; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using FileAttributes = AsmResolver.PE.DotNet.Metadata.Tables.Rows.FileAttributes; @@ -58,11 +57,8 @@ public SerializedAssemblyDefinition( /// protected override IList GetModules() { - _manifestModule.Assembly = null; - var result = new OwnedCollection(this) - { - _manifestModule - }; + var result = new MemberCollection(this); + result.AddNoOwnerCheck(_manifestModule); var moduleResolver = _context.Parameters.ModuleResolver; if (moduleResolver is not null) diff --git a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs index 9b695b802..c5063f1b4 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs @@ -1,17 +1,13 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using AsmResolver.Collections; using AsmResolver.DotNet.Collections; using AsmResolver.DotNet.Signatures.Types; using AsmResolver.PE; using AsmResolver.PE.Debug; using AsmResolver.PE.DotNet; -using AsmResolver.PE.DotNet.Metadata.Guid; -using AsmResolver.PE.DotNet.Metadata.Strings; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; -using AsmResolver.PE.DotNet.Metadata.UserStrings; using AsmResolver.PE.Win32Resources; namespace AsmResolver.DotNet.Serialized From 56e4dc8c0da5e2342b55fad7b86445eb7aa5f455 Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Fri, 23 Feb 2024 22:15:04 -0600 Subject: [PATCH 07/15] Various .NET 8 related changes --- .gitignore | 1 + Directory.Build.props | 1 + appveyor.yml | 4 ++-- src/AsmResolver.DotNet/AsmResolver.DotNet.csproj | 8 ++++---- .../AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj | 2 +- .../AsmResolver.DotNet.Dynamic.Tests.csproj | 2 +- .../AsmResolver.DotNet.Tests.csproj | 4 +++- .../AsmResolver.PE.File.Tests.csproj | 2 +- test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj | 2 +- .../AsmResolver.PE.Win32Resources.Tests.csproj | 2 +- .../AsmResolver.Symbols.Pdb.Tests.csproj | 2 +- test/AsmResolver.Tests/AsmResolver.Tests.csproj | 2 +- test/Directory.Build.props | 9 +++++++++ test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj | 2 +- .../AsmResolver.PE.Exports.OrdinalMapper.csproj | 1 + 15 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 test/Directory.Build.props diff --git a/.gitignore b/.gitignore index 5bf1c1e2b..8b3b163cc 100644 --- a/.gitignore +++ b/.gitignore @@ -60,6 +60,7 @@ x64/ build/ [Bb]in/ [Oo]bj/ +artifacts/ # MSTest test Results [Tt]est[Rr]esult*/ diff --git a/Directory.Build.props b/Directory.Build.props index fd7226b04..447866eaa 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -9,6 +9,7 @@ 10 5.5.0 true + true diff --git a/appveyor.yml b/appveyor.yml index 270821ada..834ddfa99 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -18,7 +18,7 @@ verbosity: minimal artifacts: - - path: '**\*.nupkg' + - path: 'artifacts\package\release\*.nupkg' deploy: provider: NuGet @@ -47,4 +47,4 @@ verbosity: minimal artifacts: - - path: '**\*.nupkg' + - path: 'artifacts\package\release\*.nupkg' diff --git a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj index aaa73fef2..65a6df770 100644 --- a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj +++ b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj @@ -7,7 +7,7 @@ true 1701;1702;NU5105 enable - net6.0;netcoreapp3.1;netstandard2.0;netstandard2.1 + net8.0;net6.0;netcoreapp3.1;netstandard2.0;netstandard2.1 true @@ -24,9 +24,9 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive + + all + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj b/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj index 864745258..0f275278b 100644 --- a/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj +++ b/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 enable 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 aa91f3a42..2cdb79ac5 100644 --- a/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj +++ b/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 disable false diff --git a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj index c9a4eb306..87ffcfcf2 100644 --- a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj +++ b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj @@ -1,13 +1,15 @@ - net6.0 + net8.0 false disable 11 + + true diff --git a/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj b/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj index 99e8aa174..b76ce6699 100644 --- a/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj +++ b/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 false diff --git a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj index 01de3b1e4..bf4aacf9a 100644 --- a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj +++ b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 false true enable diff --git a/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj b/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj index 1c80a65b2..289b06e32 100644 --- a/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj +++ b/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 false AsmResolver.PE.Win32Resources.Tests warnings diff --git a/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj b/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj index 364078a8a..544cd4896 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj +++ b/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 enable false diff --git a/test/AsmResolver.Tests/AsmResolver.Tests.csproj b/test/AsmResolver.Tests/AsmResolver.Tests.csproj index 5a5259bc5..200c2f8a7 100644 --- a/test/AsmResolver.Tests/AsmResolver.Tests.csproj +++ b/test/AsmResolver.Tests/AsmResolver.Tests.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 false true enable diff --git a/test/Directory.Build.props b/test/Directory.Build.props new file mode 100644 index 000000000..171c6ac9d --- /dev/null +++ b/test/Directory.Build.props @@ -0,0 +1,9 @@ + + + + + $(MSBuildThisFileDirectory)..\artifacts\test + false + + + diff --git a/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj b/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj index b2a147cef..90588ca1e 100644 --- a/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj +++ b/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj @@ -2,7 +2,7 @@ Exe - net47;netcoreapp3.1;net6.0 + net47;netcoreapp3.1;net6.0;net8.0 Resources\Icon.ico true diff --git a/tools/AsmResolver.PE.Exports.OrdinalMapper/AsmResolver.PE.Exports.OrdinalMapper.csproj b/tools/AsmResolver.PE.Exports.OrdinalMapper/AsmResolver.PE.Exports.OrdinalMapper.csproj index 399605bab..fb4aa7b71 100644 --- a/tools/AsmResolver.PE.Exports.OrdinalMapper/AsmResolver.PE.Exports.OrdinalMapper.csproj +++ b/tools/AsmResolver.PE.Exports.OrdinalMapper/AsmResolver.PE.Exports.OrdinalMapper.csproj @@ -4,6 +4,7 @@ Exe net472 enable + false From e1e8116741a5a2069636c0cf316df2af30369bf2 Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Fri, 23 Feb 2024 22:36:21 -0600 Subject: [PATCH 08/15] Fix function pointer Type imports --- src/AsmResolver.DotNet/ReferenceImporter.cs | 100 ++++++++++++++++++ .../ReferenceImporterTest.cs | 74 +++++++++++++ 2 files changed, 174 insertions(+) diff --git a/src/AsmResolver.DotNet/ReferenceImporter.cs b/src/AsmResolver.DotNet/ReferenceImporter.cs index c9c2b26c7..b5c0e019f 100644 --- a/src/AsmResolver.DotNet/ReferenceImporter.cs +++ b/src/AsmResolver.DotNet/ReferenceImporter.cs @@ -312,6 +312,41 @@ public virtual TypeSignature ImportTypeSignature(Type type) type.DeclaringMethod != null ? GenericParameterType.Method : GenericParameterType.Type, type.GenericParameterPosition); + if (FnPtrHelper.GetIsFunctionPointer(type)) + { + var returnType = ImportTypeSignature(FnPtrHelper.GetFunctionPointerReturnType(type)); + var callConvs = FnPtrHelper.GetFunctionPointerCallingConventions(type); + var callConv = CallingConventionAttributes.Default; + if (callConvs.Length == 1) + { + var cc = callConvs[0]; + callConv = cc.FullName switch + { + "System.Runtime.CompilerServices.CallConvCdecl" => CallingConventionAttributes.C, + "System.Runtime.CompilerServices.CallConvFastcall" => CallingConventionAttributes.FastCall, + "System.Runtime.CompilerServices.CallConvStdcall" => CallingConventionAttributes.StdCall, + "System.Runtime.CompilerServices.CallConvThiscall" => CallingConventionAttributes.ThisCall, + _ => CallingConventionAttributes.Default + }; + if (callConv == CallingConventionAttributes.Default) + returnType = returnType.MakeModifierType(ImportType(cc), false); + } + else + { + foreach (var cc in callConvs) + returnType = returnType.MakeModifierType(ImportType(cc), false); + } + + if (callConv == default && FnPtrHelper.GetUnmanagedIsFunctionPointer(type)) + callConv = CallingConventionAttributes.Unmanaged; + + return new MethodSignature( + callConv, + returnType, + FnPtrHelper.GetFunctionPointerParameterTypes(type).Select(ImportTypeSignature) + ).MakeFunctionPointerType(); + } + var corlibType = TargetModule.CorLibTypeFactory.FromName(type.Namespace, type.Name); if (corlibType != null) return corlibType; @@ -332,6 +367,71 @@ public virtual TypeSignature ImportTypeSignature(Type type) return new TypeDefOrRefSignature(reference, type.IsValueType); } + // separate class to prevent static cctor checks from leaking into the other ReferenceImporter methods on NAOT + private static class FnPtrHelper + { +#if !NET8_0_OR_GREATER + private static readonly PropertyInfo? IsFunctionPointerProp = typeof(Type).GetProperty("IsFunctionPointer"); + private static readonly PropertyInfo? IsUnmanagedFunctionPointerProp = typeof(Type).GetProperty("IsUnmanagedFunctionPointer"); + private static readonly MethodInfo? GetFunctionPointerReturnTypeMethod = typeof(Type).GetMethod("GetFunctionPointerReturnType"); + private static readonly MethodInfo? GetFunctionPointerCallingConventionsMethod = typeof(Type).GetMethod("GetFunctionPointerCallingConventions"); + private static readonly MethodInfo? GetFunctionPointerParameterTypesMethod = typeof(Type).GetMethod("GetFunctionPointerParameterTypes"); +#endif + + internal static bool GetIsFunctionPointer(Type t) + { +#if NET8_0_OR_GREATER + return t.IsFunctionPointer; +#else + return IsFunctionPointerProp != null && (bool)IsFunctionPointerProp.GetValue(t)!; +#endif + } + + internal static bool GetUnmanagedIsFunctionPointer(Type t) + { +#if NET8_0_OR_GREATER + return t.IsUnmanagedFunctionPointer; +#else + // can only be called if the type was already verified to be a function pointer + // therefore the PropertyInfo is not null + return (bool)IsUnmanagedFunctionPointerProp!.GetValue(t)!; +#endif + } + + internal static Type GetFunctionPointerReturnType(Type t) + { +#if NET8_0_OR_GREATER + return t.GetFunctionPointerReturnType(); +#else + // can only be called if the type was already verified to be a function pointer + // therefore the MethodInfo is not null + return (Type)GetFunctionPointerReturnTypeMethod!.Invoke(t, null)!; +#endif + } + + internal static Type[] GetFunctionPointerCallingConventions(Type t) + { +#if NET8_0_OR_GREATER + return t.GetFunctionPointerCallingConventions(); +#else + // can only be called if the type was already verified to be a function pointer + // therefore the MethodInfo is not null + return (Type[])GetFunctionPointerCallingConventionsMethod!.Invoke(t, null)!; +#endif + } + + internal static Type[] GetFunctionPointerParameterTypes(Type t) + { +#if NET8_0_OR_GREATER + return t.GetFunctionPointerParameterTypes(); +#else + // can only be called if the type was already verified to be a function pointer + // therefore the MethodInfo is not null + return (Type[])GetFunctionPointerParameterTypesMethod!.Invoke(t, null)!; +#endif + } + } + private TypeSignature ImportArrayType(Type type) { var baseType = ImportTypeSignature(type.GetElementType()!); diff --git a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs index 4f745efc9..3de76d56c 100644 --- a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs +++ b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs @@ -546,5 +546,79 @@ public void ImportGenericMethodInstantiationWithReturnTypeViaReflection() var signature = Assert.IsAssignableFrom(imported.Signature.ReturnType); Assert.Equal(2, signature.Index); } + + private static unsafe class DelegatePointerHolder + { + public static delegate*unmanaged[Cdecl, SuppressGCTransition] Complex; + public static delegate*unmanaged[Stdcall] StdcallOnly; + public static delegate*unmanaged[SuppressGCTransition] GcOnly; + } + + + [Fact( +#if !NET8_0_OR_GREATER + Skip = "only works on net8" +#endif + )] + public void ImportComplexFunctionPointerFromReflectedFieldType() + { + var fieldType = typeof(DelegatePointerHolder).GetField("Complex").GetModifiedFieldType(); + var imported = Assert.IsAssignableFrom(_importer.ImportType(fieldType).ToTypeSignature()); + Assert.Equal(CallingConventionAttributes.Unmanaged, imported.Signature.CallingConvention); + var firstModifier = Assert.IsAssignableFrom(imported.Signature.ReturnType); + Assert.True(firstModifier.ModifierType is + { + Namespace.Value: "System.Runtime.CompilerServices", + Name.Value: "CallConvCdecl" or "CallConvSuppressGCTransition" + }); + var secondModifier = Assert.IsAssignableFrom(firstModifier.BaseType); + Assert.True(secondModifier.ModifierType is + { + Namespace.Value: "System.Runtime.CompilerServices", + Name.Value: "CallConvCdecl" or "CallConvSuppressGCTransition" + }); + } + + [Fact( +#if !NET8_0_OR_GREATER + Skip = "only works on net8" +#endif + )] + public void ImportSimpleFunctionPointerFromReflectedFieldType() + { + var fieldType = typeof(DelegatePointerHolder).GetField("StdcallOnly").GetModifiedFieldType(); + var imported = Assert.IsAssignableFrom(_importer.ImportType(fieldType).ToTypeSignature()); + Assert.Equal(CallingConventionAttributes.StdCall, imported.Signature.CallingConvention); + } + + [Fact( +#if !NET8_0_OR_GREATER + Skip = "only works on net8" +#endif + )] + public void ImportModoptOnlyFunctionPointerFromReflectedFieldType() + { + var fieldType = typeof(DelegatePointerHolder).GetField("GcOnly").GetModifiedFieldType(); + var imported = Assert.IsAssignableFrom(_importer.ImportType(fieldType).ToTypeSignature()); + Assert.Equal(CallingConventionAttributes.Unmanaged, imported.Signature.CallingConvention); + var firstModifier = Assert.IsAssignableFrom(imported.Signature.ReturnType); + Assert.True(firstModifier.ModifierType is + { + Namespace.Value: "System.Runtime.CompilerServices", + Name.Value: "CallConvSuppressGCTransition" + }); + } + + [Fact( +#if !NET8_0_OR_GREATER + Skip = "only works on net8" +#endif + )] + public void ImportFunctionPointerFromTypeof() + { + var imported = Assert.IsAssignableFrom(_importer.ImportType(typeof(delegate*)).ToTypeSignature()); + Assert.Collection(imported.Signature.ParameterTypes, t => Assert.Same(_module.CorLibTypeFactory.Int32, t)); + Assert.Same(imported.Signature.ReturnType, _module.CorLibTypeFactory.UInt32); + } } } From 6f170e124941691ebf4a17187242e9234fef0f7f Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Fri, 23 Feb 2024 22:36:26 -0600 Subject: [PATCH 09/15] Add docs about function pointer Type imports --- docs/guides/dotnet/importing.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/guides/dotnet/importing.md b/docs/guides/dotnet/importing.md index b446d9869..55e45c711 100644 --- a/docs/guides/dotnet/importing.md +++ b/docs/guides/dotnet/importing.md @@ -106,6 +106,7 @@ imported through reflection include: is created). - Generic parameters. - Generic type instantiations. +- Function pointer types (!!WARNING!! Function pointer `Type` instances lose their calling conventions unless attained with `GetModified(Field/Property/Parameter)Type`. This includes `typeof`! `typeof(delegate*unmanaged[Cdecl])` is the same as `typeof(delegate*managed)`. `Import(Field/Method)` will also strip the calling conventions from function pointers that are the types of fields or in method signatures. If you need to handle this, manually set the types in the resulting IMethodDefOrRef or MemberReference to the appropriate type attained with `ImportType`.) Instantiations of generic methods are also supported. From 5ff9718512e1238dc22b55f797b904a8bac18e46 Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Fri, 23 Feb 2024 23:10:09 -0600 Subject: [PATCH 10/15] Random DynamicMethod .NET 8 fixes --- src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs | 5 ++++- src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs | 6 ++++-- .../DynamicMethodDefinitionTest.cs | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs b/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs index 4fdc14587..0b6465aa8 100644 --- a/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs +++ b/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs @@ -84,7 +84,10 @@ private static CilMethodBody CreateDynamicMethodBody(MethodDefinition method, ob // 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 (!FieldReader.TryReadField(m, "m_DynamicILInfo", out dynamicILInfo)) + FieldReader.TryReadField(m, "_dynamicILInfo", out dynamicILInfo); + } // Extract all required information to construct the body. byte[]? code; diff --git a/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs b/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs index 27e3d27c3..909b015d6 100644 --- a/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs +++ b/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs @@ -101,7 +101,9 @@ public static object ResolveDynamicResolver(object dynamicMethodObj) if (dynamicMethodObj.GetType().FullName == "System.Reflection.Emit.DynamicMethod") { - object? resolver = FieldReader.ReadField(dynamicMethodObj, "m_resolver"); + object? resolver = + FieldReader.ReadField(dynamicMethodObj, "m_resolver") + ?? FieldReader.ReadField(dynamicMethodObj, "_resolver"); if (resolver != null) dynamicMethodObj = resolver; } @@ -115,7 +117,7 @@ public static object ResolveDynamicResolver(object dynamicMethodObj) object? ilGenerator = dynamicMethodObj .GetType() .GetRuntimeMethods() - .First(q => q.Name == "GetILGenerator") + .First(q => q.Name == "GetILGenerator" && q.GetParameters().Length == 0) .Invoke(dynamicMethodObj, null); //Create instance of dynamicResolver diff --git a/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs b/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs index c649f621d..94a8a4174 100644 --- a/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs @@ -54,7 +54,7 @@ public void RtDynamicMethod() object rtDynamicMethod = generatedDynamicMethod .GetType() .GetField("m_dynMethod", (BindingFlags) (-1))? - .GetValue(generatedDynamicMethod); + .GetValue(generatedDynamicMethod) ?? generatedDynamicMethod; var dynamicMethod = new DynamicMethodDefinition(module, rtDynamicMethod!); Assert.NotNull(dynamicMethod); From 1122da065814e76efd93ffc62da42e6931e710cd Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Sun, 25 Feb 2024 20:23:03 -0600 Subject: [PATCH 11/15] Address PR comments --- docs/guides/dotnet/importing.md | 11 +- src/AsmResolver.DotNet/ModuleDefinition.cs | 11 +- src/AsmResolver.DotNet/ReferenceImporter.cs | 137 +++++------------- src/AsmResolver.DotNet/ReflectionHacks.cs | 89 ++++++++++++ .../ReferenceImporterTest.cs | 33 +---- 5 files changed, 145 insertions(+), 136 deletions(-) create mode 100644 src/AsmResolver.DotNet/ReflectionHacks.cs diff --git a/docs/guides/dotnet/importing.md b/docs/guides/dotnet/importing.md index 55e45c711..b5a937e3a 100644 --- a/docs/guides/dotnet/importing.md +++ b/docs/guides/dotnet/importing.md @@ -106,7 +106,16 @@ imported through reflection include: is created). - Generic parameters. - Generic type instantiations. -- Function pointer types (!!WARNING!! Function pointer `Type` instances lose their calling conventions unless attained with `GetModified(Field/Property/Parameter)Type`. This includes `typeof`! `typeof(delegate*unmanaged[Cdecl])` is the same as `typeof(delegate*managed)`. `Import(Field/Method)` will also strip the calling conventions from function pointers that are the types of fields or in method signatures. If you need to handle this, manually set the types in the resulting IMethodDefOrRef or MemberReference to the appropriate type attained with `ImportType`.) +- Function pointer types (.NET 8.0+ only. TFM doesn't matter for this, the runtime used at runtime is all that matters.) (!!WARNING!! ) + +> [!WARNING] +> Function pointer `Type` instances lose their calling conventions unless attained with +> `GetModified(Field/Property/Parameter)Type`. This includes `typeof`! +> `typeof(delegate*unmanaged[Cdecl])` is the same as `typeof(delegate*managed)`. +> `Import(Field/Method)` will also strip the calling conventions from function pointers +> that are the types of fields or in method signatures. +> If you need to handle this, manually set the types in the resulting +> `IMethodDefOrRef` or `MemberReference` to the appropriate type from `ImportType`. Instantiations of generic methods are also supported. diff --git a/src/AsmResolver.DotNet/ModuleDefinition.cs b/src/AsmResolver.DotNet/ModuleDefinition.cs index ff49caaf2..2ecd38a2e 100644 --- a/src/AsmResolver.DotNet/ModuleDefinition.cs +++ b/src/AsmResolver.DotNet/ModuleDefinition.cs @@ -34,8 +34,6 @@ public class ModuleDefinition : IHasCustomAttribute, IOwnedCollectionElement { - private static MethodInfo? GetHINSTANCEMethod; - private readonly LazyVariable _name; private readonly LazyVariable _mvid; private readonly LazyVariable _encId; @@ -166,14 +164,9 @@ public static ModuleDefinition FromModuleBaseAddress(IntPtr hInstance, PEMapping /// The module. public static ModuleDefinition FromModule(Module module, ModuleReaderParameters readerParameters) { - // We get the base address using GetHINSTANCE, but this method is unfortunately not shipped with - // .NET Standard 2.0, so we need to resort to reflection for this. - GetHINSTANCEMethod ??= typeof(Marshal).GetMethod("GetHINSTANCE", new[] { typeof(Module) }); - - var handle = (IntPtr) GetHINSTANCEMethod?.Invoke(null, new object[] { module })!; - if (handle == IntPtr.Zero) + if (!ReflectionHacks.TryGetHINSTANCE(module, out var handle)) throw new NotSupportedException("The current platform does not support getting the base address of an instance of System.Reflection.Module."); - if (handle == (IntPtr) (-1)) + if (handle == -1) throw new NotSupportedException("Provided module does not have a module base address."); // Dynamically loaded modules are in their unmapped form, as opposed to modules loaded normally by the diff --git a/src/AsmResolver.DotNet/ReferenceImporter.cs b/src/AsmResolver.DotNet/ReferenceImporter.cs index b5c0e019f..f2748e2ef 100644 --- a/src/AsmResolver.DotNet/ReferenceImporter.cs +++ b/src/AsmResolver.DotNet/ReferenceImporter.cs @@ -311,41 +311,8 @@ public virtual TypeSignature ImportTypeSignature(Type type) return new GenericParameterSignature( type.DeclaringMethod != null ? GenericParameterType.Method : GenericParameterType.Type, type.GenericParameterPosition); - - if (FnPtrHelper.GetIsFunctionPointer(type)) - { - var returnType = ImportTypeSignature(FnPtrHelper.GetFunctionPointerReturnType(type)); - var callConvs = FnPtrHelper.GetFunctionPointerCallingConventions(type); - var callConv = CallingConventionAttributes.Default; - if (callConvs.Length == 1) - { - var cc = callConvs[0]; - callConv = cc.FullName switch - { - "System.Runtime.CompilerServices.CallConvCdecl" => CallingConventionAttributes.C, - "System.Runtime.CompilerServices.CallConvFastcall" => CallingConventionAttributes.FastCall, - "System.Runtime.CompilerServices.CallConvStdcall" => CallingConventionAttributes.StdCall, - "System.Runtime.CompilerServices.CallConvThiscall" => CallingConventionAttributes.ThisCall, - _ => CallingConventionAttributes.Default - }; - if (callConv == CallingConventionAttributes.Default) - returnType = returnType.MakeModifierType(ImportType(cc), false); - } - else - { - foreach (var cc in callConvs) - returnType = returnType.MakeModifierType(ImportType(cc), false); - } - - if (callConv == default && FnPtrHelper.GetUnmanagedIsFunctionPointer(type)) - callConv = CallingConventionAttributes.Unmanaged; - - return new MethodSignature( - callConv, - returnType, - FnPtrHelper.GetFunctionPointerParameterTypes(type).Select(ImportTypeSignature) - ).MakeFunctionPointerType(); - } + if (ReflectionHacks.GetIsFunctionPointer(type)) + return ImportFunctionPointerType(type); var corlibType = TargetModule.CorLibTypeFactory.FromName(type.Namespace, type.Name); if (corlibType != null) @@ -367,71 +334,6 @@ public virtual TypeSignature ImportTypeSignature(Type type) return new TypeDefOrRefSignature(reference, type.IsValueType); } - // separate class to prevent static cctor checks from leaking into the other ReferenceImporter methods on NAOT - private static class FnPtrHelper - { -#if !NET8_0_OR_GREATER - private static readonly PropertyInfo? IsFunctionPointerProp = typeof(Type).GetProperty("IsFunctionPointer"); - private static readonly PropertyInfo? IsUnmanagedFunctionPointerProp = typeof(Type).GetProperty("IsUnmanagedFunctionPointer"); - private static readonly MethodInfo? GetFunctionPointerReturnTypeMethod = typeof(Type).GetMethod("GetFunctionPointerReturnType"); - private static readonly MethodInfo? GetFunctionPointerCallingConventionsMethod = typeof(Type).GetMethod("GetFunctionPointerCallingConventions"); - private static readonly MethodInfo? GetFunctionPointerParameterTypesMethod = typeof(Type).GetMethod("GetFunctionPointerParameterTypes"); -#endif - - internal static bool GetIsFunctionPointer(Type t) - { -#if NET8_0_OR_GREATER - return t.IsFunctionPointer; -#else - return IsFunctionPointerProp != null && (bool)IsFunctionPointerProp.GetValue(t)!; -#endif - } - - internal static bool GetUnmanagedIsFunctionPointer(Type t) - { -#if NET8_0_OR_GREATER - return t.IsUnmanagedFunctionPointer; -#else - // can only be called if the type was already verified to be a function pointer - // therefore the PropertyInfo is not null - return (bool)IsUnmanagedFunctionPointerProp!.GetValue(t)!; -#endif - } - - internal static Type GetFunctionPointerReturnType(Type t) - { -#if NET8_0_OR_GREATER - return t.GetFunctionPointerReturnType(); -#else - // can only be called if the type was already verified to be a function pointer - // therefore the MethodInfo is not null - return (Type)GetFunctionPointerReturnTypeMethod!.Invoke(t, null)!; -#endif - } - - internal static Type[] GetFunctionPointerCallingConventions(Type t) - { -#if NET8_0_OR_GREATER - return t.GetFunctionPointerCallingConventions(); -#else - // can only be called if the type was already verified to be a function pointer - // therefore the MethodInfo is not null - return (Type[])GetFunctionPointerCallingConventionsMethod!.Invoke(t, null)!; -#endif - } - - internal static Type[] GetFunctionPointerParameterTypes(Type t) - { -#if NET8_0_OR_GREATER - return t.GetFunctionPointerParameterTypes(); -#else - // can only be called if the type was already verified to be a function pointer - // therefore the MethodInfo is not null - return (Type[])GetFunctionPointerParameterTypesMethod!.Invoke(t, null)!; -#endif - } - } - private TypeSignature ImportArrayType(Type type) { var baseType = ImportTypeSignature(type.GetElementType()!); @@ -455,6 +357,41 @@ private TypeSignature ImportGenericType(Type type) return result; } + private TypeSignature ImportFunctionPointerType(Type type) + { + var returnType = ImportTypeSignature(ReflectionHacks.GetFunctionPointerReturnType(type)); + var callConvs = ReflectionHacks.GetFunctionPointerCallingConventions(type); + var callConv = CallingConventionAttributes.Default; + if (callConvs.Length == 1) + { + var cc = callConvs[0]; + callConv = cc.FullName switch + { + "System.Runtime.CompilerServices.CallConvCdecl" => CallingConventionAttributes.C, + "System.Runtime.CompilerServices.CallConvFastcall" => CallingConventionAttributes.FastCall, + "System.Runtime.CompilerServices.CallConvStdcall" => CallingConventionAttributes.StdCall, + "System.Runtime.CompilerServices.CallConvThiscall" => CallingConventionAttributes.ThisCall, + _ => CallingConventionAttributes.Default + }; + if (callConv == CallingConventionAttributes.Default) + returnType = returnType.MakeModifierType(ImportType(cc), false); + } + else + { + foreach (var cc in callConvs) + returnType = returnType.MakeModifierType(ImportType(cc), false); + } + + if (callConv == default && ReflectionHacks.GetIsUnmanagedFunctionPointer(type)) + callConv = CallingConventionAttributes.Unmanaged; + + return new MethodSignature( + callConv, + returnType, + ReflectionHacks.GetFunctionPointerParameterTypes(type).Select(ImportTypeSignature) + ).MakeFunctionPointerType(); + } + /// /// Imports a reference to- or an instantiation of a method into the module. /// diff --git a/src/AsmResolver.DotNet/ReflectionHacks.cs b/src/AsmResolver.DotNet/ReflectionHacks.cs new file mode 100644 index 000000000..739398c2c --- /dev/null +++ b/src/AsmResolver.DotNet/ReflectionHacks.cs @@ -0,0 +1,89 @@ +using System; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace AsmResolver.DotNet +{ + internal static class ReflectionHacks + { +#if !NET8_0_OR_GREATER + private static readonly PropertyInfo? IsFunctionPointerProp = typeof(Type).GetProperty("IsFunctionPointer"); + private static readonly PropertyInfo? IsUnmanagedFunctionPointerProp = typeof(Type).GetProperty("IsUnmanagedFunctionPointer"); + private static readonly MethodInfo? GetFunctionPointerReturnTypeMethod = typeof(Type).GetMethod("GetFunctionPointerReturnType", Array.Empty()); + private static readonly MethodInfo? GetFunctionPointerCallingConventionsMethod = typeof(Type).GetMethod("GetFunctionPointerCallingConventions", Array.Empty()); + private static readonly MethodInfo? GetFunctionPointerParameterTypesMethod = typeof(Type).GetMethod("GetFunctionPointerParameterTypes", Array.Empty()); +#endif +#if NETSTANDARD2_0 + private static readonly MethodInfo? GetHINSTANCEMethod = typeof(Marshal).GetMethod("GetHINSTANCE", new[] { typeof(Module) }); +#endif + + internal static bool GetIsFunctionPointer(Type t) + { +#if NET8_0_OR_GREATER + return t.IsFunctionPointer; +#else + return IsFunctionPointerProp != null && (bool)IsFunctionPointerProp.GetValue(t)!; +#endif + } + + internal static bool GetIsUnmanagedFunctionPointer(Type t) + { +#if NET8_0_OR_GREATER + return t.IsUnmanagedFunctionPointer; +#else + // can only be called if the type was already verified to be a function pointer + // therefore the PropertyInfo is not null + return (bool)IsUnmanagedFunctionPointerProp!.GetValue(t)!; +#endif + } + + internal static Type GetFunctionPointerReturnType(Type t) + { +#if NET8_0_OR_GREATER + return t.GetFunctionPointerReturnType(); +#else + // can only be called if the type was already verified to be a function pointer + // therefore the MethodInfo is not null + return (Type)GetFunctionPointerReturnTypeMethod!.Invoke(t, null)!; +#endif + } + + internal static Type[] GetFunctionPointerCallingConventions(Type t) + { +#if NET8_0_OR_GREATER + return t.GetFunctionPointerCallingConventions(); +#else + // can only be called if the type was already verified to be a function pointer + // therefore the MethodInfo is not null + return (Type[])GetFunctionPointerCallingConventionsMethod!.Invoke(t, null)!; +#endif + } + + internal static Type[] GetFunctionPointerParameterTypes(Type t) + { +#if NET8_0_OR_GREATER + return t.GetFunctionPointerParameterTypes(); +#else + // can only be called if the type was already verified to be a function pointer + // therefore the MethodInfo is not null + return (Type[])GetFunctionPointerParameterTypesMethod!.Invoke(t, null)!; +#endif + } + + internal static bool TryGetHINSTANCE(Module m, out nint hinstance) + { +#if !NETSTANDARD2_0 + hinstance = Marshal.GetHINSTANCE(m); + return true; +#else + if (GetHINSTANCEMethod == null) + { + hinstance = default; + return false; + } + hinstance = (nint)GetHINSTANCEMethod.Invoke(null, new object[] { m }); + return true; +#endif + } + } +} diff --git a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs index 3de76d56c..a74d24736 100644 --- a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs +++ b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs @@ -547,6 +547,7 @@ public void ImportGenericMethodInstantiationWithReturnTypeViaReflection() Assert.Equal(2, signature.Index); } +#if NET8_0_OR_GREATER private static unsafe class DelegatePointerHolder { public static delegate*unmanaged[Cdecl, SuppressGCTransition] Complex; @@ -554,12 +555,7 @@ private static unsafe class DelegatePointerHolder public static delegate*unmanaged[SuppressGCTransition] GcOnly; } - - [Fact( -#if !NET8_0_OR_GREATER - Skip = "only works on net8" -#endif - )] + [Fact] public void ImportComplexFunctionPointerFromReflectedFieldType() { var fieldType = typeof(DelegatePointerHolder).GetField("Complex").GetModifiedFieldType(); @@ -579,11 +575,7 @@ public void ImportComplexFunctionPointerFromReflectedFieldType() }); } - [Fact( -#if !NET8_0_OR_GREATER - Skip = "only works on net8" -#endif - )] + [Fact] public void ImportSimpleFunctionPointerFromReflectedFieldType() { var fieldType = typeof(DelegatePointerHolder).GetField("StdcallOnly").GetModifiedFieldType(); @@ -591,34 +583,23 @@ public void ImportSimpleFunctionPointerFromReflectedFieldType() Assert.Equal(CallingConventionAttributes.StdCall, imported.Signature.CallingConvention); } - [Fact( -#if !NET8_0_OR_GREATER - Skip = "only works on net8" -#endif - )] + [Fact] public void ImportModoptOnlyFunctionPointerFromReflectedFieldType() { var fieldType = typeof(DelegatePointerHolder).GetField("GcOnly").GetModifiedFieldType(); var imported = Assert.IsAssignableFrom(_importer.ImportType(fieldType).ToTypeSignature()); Assert.Equal(CallingConventionAttributes.Unmanaged, imported.Signature.CallingConvention); var firstModifier = Assert.IsAssignableFrom(imported.Signature.ReturnType); - Assert.True(firstModifier.ModifierType is - { - Namespace.Value: "System.Runtime.CompilerServices", - Name.Value: "CallConvSuppressGCTransition" - }); + Assert.Equal("System.Runtime.CompilerServices.CallConvSuppressGCTransition", firstModifier.ModifierType.FullName); } - [Fact( -#if !NET8_0_OR_GREATER - Skip = "only works on net8" -#endif - )] + [Fact] public void ImportFunctionPointerFromTypeof() { var imported = Assert.IsAssignableFrom(_importer.ImportType(typeof(delegate*)).ToTypeSignature()); Assert.Collection(imported.Signature.ParameterTypes, t => Assert.Same(_module.CorLibTypeFactory.Int32, t)); Assert.Same(imported.Signature.ReturnType, _module.CorLibTypeFactory.UInt32); } +#endif } } From 0175ae273663774cd762cc535fb4e6a38fa3d924 Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 26 Feb 2024 14:56:42 +0100 Subject: [PATCH 12/15] Bump version. --- Directory.Build.props | 4 ++-- appveyor.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 447866eaa..a183e92e4 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,13 +1,13 @@ - Copyright © Washi 2016-2023 + Copyright © Washi 2016-2024 https://github.com/Washi1337/AsmResolver https://github.com/Washi1337/AsmResolver/LICENSE.md https://github.com/Washi1337/AsmResolver git 10 - 5.5.0 + 5.5.1 true true diff --git a/appveyor.yml b/appveyor.yml index 834ddfa99..2006831eb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,7 +4,7 @@ - master image: Visual Studio 2022 - version: 5.5.0-master-build.{build} + version: 5.5.1-master-build.{build} configuration: Release skip_commits: @@ -33,7 +33,7 @@ - development image: Visual Studio 2022 - version: 5.5.0-dev-build.{build} + version: 5.5.1-dev-build.{build} configuration: Release skip_commits: From 2bae91afad666dc7e08736182bf4c5ed4ed52d4a Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 26 Feb 2024 15:49:34 +0100 Subject: [PATCH 13/15] Update deps, address new analyzer warnings, add net8.0 targets to other main packages. --- AsmResolver.sln | 6 ++++++ .../AsmResolver.DotNet.Dynamic.csproj | 9 +++------ .../DynamicCilOperandResolver.cs | 2 +- src/AsmResolver.DotNet/AsmResolver.DotNet.csproj | 5 +---- src/AsmResolver.PE.File/AsmResolver.PE.File.csproj | 9 +++------ .../AsmResolver.PE.Win32Resources.csproj | 13 +++++-------- src/AsmResolver.PE/AsmResolver.PE.csproj | 5 +---- .../AsmResolver.Symbols.Pdb.csproj | 9 +++------ src/AsmResolver/AsmResolver.csproj | 9 +++------ src/Directory.Build.props | 9 +++++++++ .../AsmResolver.DotNet.Dynamic.Tests.csproj | 6 +++--- .../AsmResolver.DotNet.Tests.csproj | 6 +++--- .../ModuleDefinitionTest.cs | 2 +- .../ReferenceImporterTest.cs | 12 ++++++------ test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs | 12 ++++++------ .../AsmResolver.PE.File.Tests.csproj | 6 +++--- .../AsmResolver.PE.Tests.csproj | 6 +++--- .../DotNet/Cil/CilInstructionTest.cs | 2 +- .../AsmResolver.PE.Win32Resources.Tests.csproj | 6 +++--- .../AsmResolver.Symbols.Pdb.Tests.csproj | 10 +++------- test/AsmResolver.Tests/AsmResolver.Tests.csproj | 6 +++--- .../AsmResolver.DotNet.TestCases.Resources.csproj | 2 +- 22 files changed, 71 insertions(+), 81 deletions(-) create mode 100644 src/Directory.Build.props diff --git a/AsmResolver.sln b/AsmResolver.sln index 0cf5601e0..492017f8f 100644 --- a/AsmResolver.sln +++ b/AsmResolver.sln @@ -4,8 +4,14 @@ Microsoft Visual Studio Solution File, Format Version 12.00 VisualStudioVersion = 17.0.31919.166 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{34A95168-A162-4F6A-803B-B6F221FE9EA6}" + ProjectSection(SolutionItems) = preProject + src\Directory.Build.props = src\Directory.Build.props + EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{786C1732-8C96-45DD-97BB-639C9AA7F45B}" + ProjectSection(SolutionItems) = preProject + test\Directory.Build.props = test\Directory.Build.props + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsmResolver", "src\AsmResolver\AsmResolver.csproj", "{4EA3F800-1A1B-4465-931F-4E9949C3373B}" EndProject diff --git a/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj b/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj index f6c65a741..6db621977 100644 --- a/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj +++ b/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj @@ -4,10 +4,7 @@ AsmResolver.DotNet.Dynamic Dynamic method support for the AsmResolver executable file inspection toolsuite. exe pe directories imports exports resources dotnet cil inspection manipulation assembly disassembly dynamic - true 1701;1702;NU5105 - net6.0;netcoreapp3.1;netstandard2.0;netstandard2.1 - enable @@ -23,9 +20,9 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive + + all + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/AsmResolver.DotNet.Dynamic/DynamicCilOperandResolver.cs b/src/AsmResolver.DotNet.Dynamic/DynamicCilOperandResolver.cs index 8f4b202f4..4368460f8 100644 --- a/src/AsmResolver.DotNet.Dynamic/DynamicCilOperandResolver.cs +++ b/src/AsmResolver.DotNet.Dynamic/DynamicCilOperandResolver.cs @@ -37,7 +37,7 @@ public DynamicCilOperandResolver(SerializedModuleDefinition contextModule, CilMe case TableIndex.TypeDef: object? type = _tokens[(int)token.Rid]; if (type is RuntimeTypeHandle runtimeTypeHandle) - return _importer.ImportType(Type.GetTypeFromHandle(runtimeTypeHandle)); + return _importer.ImportType(Type.GetTypeFromHandle(runtimeTypeHandle)!); break; case TableIndex.Field: diff --git a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj index 65a6df770..613b423da 100644 --- a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj +++ b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj @@ -4,10 +4,7 @@ AsmResolver.DotNet High level .NET image models for the AsmResolver executable file inspection toolsuite. exe pe directories imports exports resources dotnet cil inspection manipulation assembly disassembly - true 1701;1702;NU5105 - enable - net8.0;net6.0;netcoreapp3.1;netstandard2.0;netstandard2.1 true @@ -24,7 +21,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj b/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj index aa5d496e7..fb00775e4 100644 --- a/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj +++ b/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj @@ -4,11 +4,8 @@ AsmResolver.PE.File Raw PE file models for the AsmResolver executable file inspection toolsuite. exe pe headers sections inspection manipulation assembly disassembly - true 1701;1702;NU5105 true - enable - net6.0;netcoreapp3.1;netstandard2.0;netstandard2.1 true @@ -25,9 +22,9 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive + + all + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/AsmResolver.PE.Win32Resources/AsmResolver.PE.Win32Resources.csproj b/src/AsmResolver.PE.Win32Resources/AsmResolver.PE.Win32Resources.csproj index b2fc17031..98bce0723 100644 --- a/src/AsmResolver.PE.Win32Resources/AsmResolver.PE.Win32Resources.csproj +++ b/src/AsmResolver.PE.Win32Resources/AsmResolver.PE.Win32Resources.csproj @@ -3,10 +3,7 @@ AsmResolver.PE.Win32Resources exe pe directories imports exports resources dotnet cil inspection manipulation assembly disassembly - true 1701;1702;NU5105 - enable - net6.0;netcoreapp3.1;netstandard2.0;netstandard2.1 true @@ -18,11 +15,11 @@ bin\Release\AsmResolver.PE.Win32Resources.xml - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/AsmResolver.PE/AsmResolver.PE.csproj b/src/AsmResolver.PE/AsmResolver.PE.csproj index 1ec5862cf..699fbbd7a 100644 --- a/src/AsmResolver.PE/AsmResolver.PE.csproj +++ b/src/AsmResolver.PE/AsmResolver.PE.csproj @@ -4,10 +4,7 @@ AsmResolver.PE PE image models for the AsmResolver executable file inspection toolsuite. exe pe directories imports exports resources dotnet cil inspection manipulation assembly disassembly - true 1701;1702;NU5105 - enable - net6.0;netcoreapp3.1;netstandard2.0;netstandard2.1 true @@ -24,7 +21,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj b/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj index 1b0c09029..61bad1f34 100644 --- a/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj +++ b/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj @@ -3,9 +3,6 @@ AsmResolver Windows PDB models for the AsmResolver executable file inspection toolsuite. - windows pdb symbols - enable - net6.0;netcoreapp3.1;netstandard2.0;netstandard2.1 true true @@ -26,9 +23,9 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive + + all + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/AsmResolver/AsmResolver.csproj b/src/AsmResolver/AsmResolver.csproj index d3591d07c..18ba58436 100644 --- a/src/AsmResolver/AsmResolver.csproj +++ b/src/AsmResolver/AsmResolver.csproj @@ -4,10 +4,7 @@ AsmResolver The base library for the AsmResolver executable file inspection toolsuite. exe pe dotnet cil inspection manipulation assembly disassembly - true 1701;1702;NU5105 - enable - net6.0;netcoreapp3.1;netstandard2.0;netstandard2.1 true @@ -22,9 +19,9 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive + + all + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 000000000..95ddd1926 --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,9 @@ + + + + + net8.0;net6.0;netcoreapp3.1;netstandard2.0;netstandard2.1 + true + enable + + 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 2cdb79ac5..8b3a79e64 100644 --- a/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj +++ b/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj @@ -9,9 +9,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj index 87ffcfcf2..bd8c4745e 100644 --- a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj +++ b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj @@ -14,9 +14,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs index 8d992d293..2f917dfb9 100644 --- a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs @@ -167,7 +167,7 @@ public void ReadTypesNoNested() public void ReadTypesNested() { var module = ModuleDefinition.FromFile(typeof(TopLevelClass1).Assembly.Location); - Assert.Equal(new HashSet + Assert.Equal(new Utf8String[] { "", nameof(TopLevelClass1), diff --git a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs index a74d24736..884420334 100644 --- a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs +++ b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs @@ -550,15 +550,15 @@ public void ImportGenericMethodInstantiationWithReturnTypeViaReflection() #if NET8_0_OR_GREATER private static unsafe class DelegatePointerHolder { - public static delegate*unmanaged[Cdecl, SuppressGCTransition] Complex; - public static delegate*unmanaged[Stdcall] StdcallOnly; - public static delegate*unmanaged[SuppressGCTransition] GcOnly; + public static delegate*unmanaged[Cdecl, SuppressGCTransition] Complex = null; + public static delegate*unmanaged[Stdcall] StdcallOnly = null; + public static delegate*unmanaged[SuppressGCTransition] GcOnly = null; } [Fact] public void ImportComplexFunctionPointerFromReflectedFieldType() { - var fieldType = typeof(DelegatePointerHolder).GetField("Complex").GetModifiedFieldType(); + var fieldType = typeof(DelegatePointerHolder).GetField("Complex")!.GetModifiedFieldType(); var imported = Assert.IsAssignableFrom(_importer.ImportType(fieldType).ToTypeSignature()); Assert.Equal(CallingConventionAttributes.Unmanaged, imported.Signature.CallingConvention); var firstModifier = Assert.IsAssignableFrom(imported.Signature.ReturnType); @@ -578,7 +578,7 @@ public void ImportComplexFunctionPointerFromReflectedFieldType() [Fact] public void ImportSimpleFunctionPointerFromReflectedFieldType() { - var fieldType = typeof(DelegatePointerHolder).GetField("StdcallOnly").GetModifiedFieldType(); + var fieldType = typeof(DelegatePointerHolder).GetField("StdcallOnly")!.GetModifiedFieldType(); var imported = Assert.IsAssignableFrom(_importer.ImportType(fieldType).ToTypeSignature()); Assert.Equal(CallingConventionAttributes.StdCall, imported.Signature.CallingConvention); } @@ -586,7 +586,7 @@ public void ImportSimpleFunctionPointerFromReflectedFieldType() [Fact] public void ImportModoptOnlyFunctionPointerFromReflectedFieldType() { - var fieldType = typeof(DelegatePointerHolder).GetField("GcOnly").GetModifiedFieldType(); + var fieldType = typeof(DelegatePointerHolder).GetField("GcOnly")!.GetModifiedFieldType(); var imported = Assert.IsAssignableFrom(_importer.ImportType(fieldType).ToTypeSignature()); Assert.Equal(CallingConventionAttributes.Unmanaged, imported.Signature.CallingConvention); var firstModifier = Assert.IsAssignableFrom(imported.Signature.ReturnType); diff --git a/test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs index 3c946ef7a..fe59cc058 100644 --- a/test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs @@ -138,42 +138,42 @@ public void ReadNestedTypes() var module = ModuleDefinition.FromFile(typeof(TopLevelClass1).Assembly.Location); var class1 = module.TopLevelTypes.First(t => t.Name == nameof(TopLevelClass1)); - Assert.Equal(new HashSet + Assert.Equal(new Utf8String[] { nameof(TopLevelClass1.Nested1), nameof(TopLevelClass1.Nested2) }, class1.NestedTypes.Select(t => t.Name)); var nested1 = class1.NestedTypes.First(t => t.Name == nameof(TopLevelClass1.Nested1)); - Assert.Equal(new HashSet + Assert.Equal(new Utf8String[] { nameof(TopLevelClass1.Nested1.Nested1Nested1), nameof(TopLevelClass1.Nested1.Nested1Nested2) }, nested1.NestedTypes.Select(t => t.Name)); var nested2 = class1.NestedTypes.First(t => t.Name == nameof(TopLevelClass1.Nested2)); - Assert.Equal(new HashSet + Assert.Equal(new Utf8String[] { nameof(TopLevelClass1.Nested2.Nested2Nested1), nameof(TopLevelClass1.Nested2.Nested2Nested2) }, nested2.NestedTypes.Select(t => t.Name)); var class2 = module.TopLevelTypes.First(t => t.Name == nameof(TopLevelClass2)); - Assert.Equal(new HashSet + Assert.Equal(new Utf8String[] { nameof(TopLevelClass2.Nested3), nameof(TopLevelClass2.Nested4) }, class2.NestedTypes.Select(t => t.Name)); var nested3 = class2.NestedTypes.First(t => t.Name == nameof(TopLevelClass2.Nested3)); - Assert.Equal(new HashSet + Assert.Equal(new Utf8String[] { nameof(TopLevelClass2.Nested3.Nested3Nested1), nameof(TopLevelClass2.Nested3.Nested3Nested2) }, nested3.NestedTypes.Select(t => t.Name)); var nested4 = class2.NestedTypes.First(t => t.Name == nameof(TopLevelClass2.Nested4)); - Assert.Equal(new HashSet + Assert.Equal(new Utf8String[] { nameof(TopLevelClass2.Nested4.Nested4Nested1), nameof(TopLevelClass2.Nested4.Nested4Nested2) diff --git a/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj b/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj index b76ce6699..057949751 100644 --- a/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj +++ b/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj @@ -10,9 +10,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj index bf4aacf9a..452bac3e3 100644 --- a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj +++ b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj @@ -17,9 +17,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.PE.Tests/DotNet/Cil/CilInstructionTest.cs b/test/AsmResolver.PE.Tests/DotNet/Cil/CilInstructionTest.cs index 68573748b..80bd69776 100644 --- a/test/AsmResolver.PE.Tests/DotNet/Cil/CilInstructionTest.cs +++ b/test/AsmResolver.PE.Tests/DotNet/Cil/CilInstructionTest.cs @@ -18,7 +18,7 @@ public class CilInstructionTest [InlineData(CilCode.Ldc_I4_7, null, 7)] [InlineData(CilCode.Ldc_I4_8, null, 8)] [InlineData(CilCode.Ldc_I4_S, (sbyte) -10, -10)] - public void GetLdcI4ConstantTest(CilCode code, object operand, int expectedValue) + public void GetLdcI4ConstantTest(CilCode code, object? operand, int expectedValue) { var instruction = new CilInstruction(code.ToOpCode(), operand); Assert.Equal(expectedValue, instruction.GetLdcI4Constant()); diff --git a/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj b/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj index 289b06e32..9cd8c9b8c 100644 --- a/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj +++ b/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj @@ -9,9 +9,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj b/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj index 544cd4896..ed3cc2a91 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj +++ b/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj @@ -9,13 +9,9 @@ - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/test/AsmResolver.Tests/AsmResolver.Tests.csproj b/test/AsmResolver.Tests/AsmResolver.Tests.csproj index 200c2f8a7..2af29423d 100644 --- a/test/AsmResolver.Tests/AsmResolver.Tests.csproj +++ b/test/AsmResolver.Tests/AsmResolver.Tests.csproj @@ -9,9 +9,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Resources/AsmResolver.DotNet.TestCases.Resources.csproj b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Resources/AsmResolver.DotNet.TestCases.Resources.csproj index b4ceea1e3..7a448bff4 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Resources/AsmResolver.DotNet.TestCases.Resources.csproj +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Resources/AsmResolver.DotNet.TestCases.Resources.csproj @@ -24,7 +24,7 @@ - + From af2e49d434a6e6071a3f03f6930858207b992c08 Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 26 Feb 2024 16:55:25 +0100 Subject: [PATCH 14/15] BUGFIX: Support reading/writing unaligned metadata directories. --- src/AsmResolver.PE/DotNet/Metadata/Metadata.cs | 4 +++- .../DotNet/Metadata/MetadataStreamHeader.cs | 2 +- src/AsmResolver/IO/IBinaryStreamWriter.cs | 14 +++++++++++--- .../DotNet/Metadata/MetadataTest.cs | 13 +++++++++++-- .../Properties/Resources.Designer.cs | 7 +++++++ .../Properties/Resources.resx | 3 +++ .../Resources/HelloWorld.UnalignedMetadata.exe | Bin 0 -> 4608 bytes 7 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 test/AsmResolver.PE.Tests/Resources/HelloWorld.UnalignedMetadata.exe diff --git a/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs b/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs index 0066a4ce3..659da6912 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs @@ -188,9 +188,11 @@ protected virtual void WriteStreamHeaders(IBinaryStreamWriter writer, MetadataSt var header = headers[i]; writer.WriteUInt32(header.Offset); writer.WriteUInt32(header.Size); + + ulong nameOffset = writer.Offset; writer.WriteAsciiString(header.Name); writer.WriteByte(0); - writer.Align(4); + writer.AlignRelative(4, nameOffset); } } diff --git a/src/AsmResolver.PE/DotNet/Metadata/MetadataStreamHeader.cs b/src/AsmResolver.PE/DotNet/Metadata/MetadataStreamHeader.cs index da69e89cf..8f76eb69a 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/MetadataStreamHeader.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/MetadataStreamHeader.cs @@ -60,7 +60,7 @@ public static MetadataStreamHeader FromReader(ref BinaryStreamReader reader) uint offset = reader.ReadUInt32(); uint size = reader.ReadUInt32(); string name = reader.ReadAsciiString(); - reader.Align(4); + reader.AlignRelative(4); return new MetadataStreamHeader(offset, size, name); } diff --git a/src/AsmResolver/IO/IBinaryStreamWriter.cs b/src/AsmResolver/IO/IBinaryStreamWriter.cs index fe772a3f8..c5ac930c1 100644 --- a/src/AsmResolver/IO/IBinaryStreamWriter.cs +++ b/src/AsmResolver/IO/IBinaryStreamWriter.cs @@ -201,10 +201,18 @@ public static void WriteAsciiString(this IBinaryStreamWriter writer, string valu /// /// The writer to align. /// The boundary to use. - public static void Align(this IBinaryStreamWriter writer, uint align) + public static void Align(this IBinaryStreamWriter writer, uint align) => writer.AlignRelative(align, 0); + + /// + /// Aligns the writer to a specified boundary, relative to the provided start offset.. + /// + /// The writer to align. + /// The boundary to use. + /// The starting offset to consider the alignment boundaries from. + public static void AlignRelative(this IBinaryStreamWriter writer, uint align, ulong startOffset) { - ulong currentPosition = writer.Offset; - writer.WriteZeroes((int) (currentPosition.Align(align) - writer.Offset)); + ulong currentPosition = writer.Offset - startOffset; + writer.WriteZeroes((int) (currentPosition.Align(align) - currentPosition)); } /// diff --git a/test/AsmResolver.PE.Tests/DotNet/Metadata/MetadataTest.cs b/test/AsmResolver.PE.Tests/DotNet/Metadata/MetadataTest.cs index dc4a0740d..0203c7e3d 100644 --- a/test/AsmResolver.PE.Tests/DotNet/Metadata/MetadataTest.cs +++ b/test/AsmResolver.PE.Tests/DotNet/Metadata/MetadataTest.cs @@ -34,7 +34,17 @@ public void CorrectStreamHeaders() var peImage = PEImage.FromBytes(Properties.Resources.HelloWorld); var metadata = peImage.DotNetDirectory!.Metadata!; - var expectedNames = new[] {"#~", "#Strings", "#US", "#GUID", "#Blob"}; + string[] expectedNames = new[] {"#~", "#Strings", "#US", "#GUID", "#Blob"}; + Assert.Equal(expectedNames, metadata.Streams.Select(s => s.Name)); + } + + [Fact] + public void CorrectStreamHeadersUnalignedMetadataDirectory() + { + var peImage = PEImage.FromBytes(Properties.Resources.HelloWorld_UnalignedMetadata); + var metadata = peImage.DotNetDirectory!.Metadata!; + + string[] expectedNames = new[] {"#~", "#Strings", "#US", "#GUID", "#Blob"}; Assert.Equal(expectedNames, metadata.Streams.Select(s => s.Name)); } @@ -188,6 +198,5 @@ public void SelectFirstUserStringsStreamInEnCMetadata() { AssertCorrectStreamIsSelected(Properties.Resources.HelloWorld_DoubleUserStringsStream_EnC, true); } - } } diff --git a/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs index b006b2bea..a01ff3be2 100644 --- a/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs @@ -171,6 +171,13 @@ public static byte[] HelloWorld_ReadyToRun { } } + public static byte[] HelloWorld_UnalignedMetadata { + get { + object obj = ResourceManager.GetObject("HelloWorld_UnalignedMetadata", resourceCulture); + return ((byte[])(obj)); + } + } + public static byte[] HelloWorldPortablePdb { get { object obj = ResourceManager.GetObject("HelloWorldPortablePdb", resourceCulture); diff --git a/test/AsmResolver.PE.Tests/Properties/Resources.resx b/test/AsmResolver.PE.Tests/Properties/Resources.resx index 00511e896..e588237b0 100644 --- a/test/AsmResolver.PE.Tests/Properties/Resources.resx +++ b/test/AsmResolver.PE.Tests/Properties/Resources.resx @@ -72,6 +72,9 @@ ..\Resources\HelloWorld.ReadyToRun.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\HelloWorld.UnalignedMetadata.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\HelloWorld.pdb;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 diff --git a/test/AsmResolver.PE.Tests/Resources/HelloWorld.UnalignedMetadata.exe b/test/AsmResolver.PE.Tests/Resources/HelloWorld.UnalignedMetadata.exe new file mode 100644 index 0000000000000000000000000000000000000000..85a07954263310a302a150d868c34cc7045b24c2 GIT binary patch literal 4608 zcmeHK-EUmQ6+hRp6UV8YASjL75+>_FLfTw!up5P7C+m3a#Kj+5@7htr0=av4yuRUn zZ13E4yojQxEtQa{N~9to@zAP&07a+*)RyvIm8z8x0tuuD2_ex)^r@=)(C|BR@7<3& zDfFQg51jR!nK|do`I@0Iei+24w(W~egavJ|FJbf?IObODWZA@okj=_orXMBhhT4D4GE^^qrtuHrBln4__^ z{|d|ytLle<1`hA*IRq~T4!0DXE`Iy8p;gC@Zj75*b1XY{^gb5pBNzBbSUU0q(cjQt z<8d(x9lxen;$587ucIGXDbhbM=J-a=tjy)n_rvM8d4AdrD(D$t-8l1QqMbbXr!hvi z$AJyTJe?z$&`IN*@|YXZtP%Iy>3!ppv7Po2dhZM zOA=qOO92Tw2OMHt#Tg|UJMdcO4?7Poy?prh6EFYd!-x6E8+QzC6J+$i@>qFwc>L;9 zy&w(s3d88{H+>h&>7fvkFg_n!@V!__~+y{l#rRQ@OYAfYmHCadgL^(M+ z9(K1pJyM=h-m=8Uxgo=HBvn){0iAYyD|DnP=YwcbM&)*X%0cB~IZ031m(h5(gK5^Q z6{6zVse8Y5j1u#ArsfgiLq9+A^a~q*7Ek^4_`5&2eiw;cm(yQN{*M{x(O0U#D{3#S z7UI`bhkgDaiH_fZ&O(LHP>r#*NkXB@C`}HpWZni#)`f9ZNT*dkj8rpFk#g(6J zHMX}K@|>AS%lh!q?eO$Qc1%caqA?sog*NcakjM_N(3>hSjFdClS}hlg0#x_{43J2(6ta2}_SJB8Rh?5c~X z1Wr_UM%!yRuS6gJ_VtNVo1QCBUip18nZX5;5z@DVDhlvqW~DTHg3?S;J}2Hyodx;_-ZoK2>lbvUYDMwj(Cn81}a#Kj@W^B!cv~OaWo@5%XijL z!PC1?+k-M8TFU0C5^s>G+2s@8a2es_f}G4OG^gry*Rl0$$F%D8%xJ733L7fo2i0fv z=iZE72ir>84ZQuD$sCkWey;&PWwj7G8+gFgr233n$5O2V(AI;y)o$>cVP0-XSGbHP zGnSh3H-blHm=O(U%EsdcX~FD+v&o7)^KRBsTl=4kG^d Q=gP6qyQ5$D_rJ)%Cy9x|_W%F@ literal 0 HcmV?d00001 From 6b12ace2c3fb4ed390b8719de2c7f95e5d7f9ce3 Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 26 Feb 2024 17:18:31 +0100 Subject: [PATCH 15/15] Fix src artifacts path. --- src/Directory.Build.props | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 95ddd1926..93a6227cc 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -5,5 +5,6 @@ net8.0;net6.0;netcoreapp3.1;netstandard2.0;netstandard2.1 true enable + $(MSBuildThisFileDirectory)..\artifacts