From 1596ccb14371c0a89e62f8e3604c289efa9c703a Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 6 Sep 2023 20:10:51 +0200 Subject: [PATCH 1/4] Add TypeDefinition::GetConstructor. --- src/AsmResolver.DotNet/TypeDefinition.cs | 73 +++++++++++- .../TypeDefinitionTest.cs | 104 +++++++++++++++--- .../Constructors.cs | 26 +++++ .../NoStaticConstructor.cs | 5 + 4 files changed, 190 insertions(+), 18 deletions(-) create mode 100644 test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/Constructors.cs create mode 100644 test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/NoStaticConstructor.cs diff --git a/src/AsmResolver.DotNet/TypeDefinition.cs b/src/AsmResolver.DotNet/TypeDefinition.cs index b3792ad8b..b29ae8ad0 100644 --- a/src/AsmResolver.DotNet/TypeDefinition.cs +++ b/src/AsmResolver.DotNet/TypeDefinition.cs @@ -792,15 +792,18 @@ public TypeReference ToTypeReference() } /// - /// Gets the static constructor that is executed when the CLR loads this type. + /// Finds the static constructor that is executed when the CLR loads this type. /// /// The static constructor, or null if none is present. public MethodDefinition? GetStaticConstructor() { - return Methods.FirstOrDefault(m => - m.IsConstructor - && m.IsStatic - && m.Parameters.Count == 0); + for (int i = 0; i < Methods.Count; i++) + { + if (Methods[i] is {IsConstructor: true, IsStatic: true, Parameters.Count: 0} method) + return method; + } + + return null; } /// @@ -845,6 +848,66 @@ public MethodDefinition GetOrCreateStaticConstructor(ModuleDefinition? module) return cctor; } + /// + /// Finds the instance parameterless constructor this type defines. + /// + /// The constructor, or null if none is present. + public MethodDefinition? GetConstructor() + { + return GetConstructor(SignatureComparer.Default, (IList) Array.Empty()); + } + + /// + /// Finds the instance constructor with the provided parameter types this type defines. + /// + /// An ordered list of types the parameters of the constructor should have. + /// The constructor, or null if none is present. + public MethodDefinition? GetConstructor(params TypeSignature[] arguments) + { + return GetConstructor(SignatureComparer.Default, arguments); + } + + /// + /// Finds the instance constructor with the provided parameter types this type defines. + /// + /// The signature comparer to use when comparing the parameter types. + /// An ordered list of types the parameters of the constructor should have. + /// The constructor, or null if none is present. + public MethodDefinition? GetConstructor(SignatureComparer comparer, params TypeSignature[] arguments) + { + return GetConstructor(comparer, (IList) arguments); + } + + /// + /// Finds the instance constructor with the provided parameter types this type defines. + /// + /// The signature comparer to use when comparing the parameter types. + /// An ordered list of types the parameters of the constructor should have. + /// The constructor, or null if none is present. + public MethodDefinition? GetConstructor(SignatureComparer comparer, IList arguments) + { + for (int i = 0; i < Methods.Count; i++) + { + if (Methods[i] is not {IsConstructor: true, IsStatic: false} method) + continue; + + if (method.Parameters.Count != arguments.Count) + continue; + + bool fullMatch = true; + for (int j = 0; j < method.Parameters.Count && fullMatch; j++) + { + if (!comparer.Equals(method.Parameters[j].ParameterType, arguments[j])) + fullMatch = false; + } + + if (fullMatch) + return method; + } + + return null; + } + /// /// Obtains the namespace of the type definition. /// diff --git a/test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs index 4f321ad7b..ebff55bf9 100644 --- a/test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs @@ -13,7 +13,6 @@ using AsmResolver.DotNet.TestCases.Properties; using AsmResolver.DotNet.TestCases.Types; using AsmResolver.DotNet.TestCases.Types.Structs; -using AsmResolver.PE.DotNet.Metadata.Strings; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using Xunit; @@ -27,7 +26,7 @@ public class TypeDefinitionTest private TypeDefinition RebuildAndLookup(TypeDefinition type) { var stream = new MemoryStream(); - type.Module.Write(stream); + type.Module!.Write(stream); var newModule = ModuleDefinition.FromBytes(stream.ToArray()); return newModule.TopLevelTypes.FirstOrDefault(t => t.FullName == type.FullName); @@ -489,7 +488,7 @@ public void ReadInterfaces() Assert.Equal(new HashSet(new Utf8String[] { nameof(IInterface1), nameof(IInterface2), - }), new HashSet(type.Interfaces.Select(i => i.Interface.Name))); + }), new HashSet(type.Interfaces.Select(i => i.Interface?.Name))); } [Fact] @@ -501,7 +500,7 @@ public void PersistentInterfaces() Assert.Equal(new HashSet(new Utf8String[] { nameof(IInterface1), nameof(IInterface2), - }), new HashSet(newType.Interfaces.Select(i => i.Interface.Name))); + }), new HashSet(newType.Interfaces.Select(i => i.Interface?.Name))); } [Fact] @@ -550,8 +549,8 @@ public void InheritanceMultipleLevels() var module = ModuleDefinition.FromFile(typeof(DerivedDerivedClass).Assembly.Location); var type = module.TopLevelTypes.First(t => t.Name == nameof(DerivedDerivedClass)); - Assert.True(type.InheritsFrom(typeof(AbstractClass).FullName)); - Assert.False(type.InheritsFrom(typeof(Class).FullName)); + Assert.True(type.InheritsFrom(typeof(AbstractClass).FullName!)); + Assert.False(type.InheritsFrom(typeof(Class).FullName!)); } [Fact] @@ -560,17 +559,17 @@ public void InterfaceImplementedFromInheritanceHierarchy() var module = ModuleDefinition.FromFile(typeof(DerivedInterfaceImplementations).Assembly.Location); var type = module.TopLevelTypes.First(t => t.Name == nameof(DerivedInterfaceImplementations)); - Assert.True(type.Implements(typeof(IInterface1).FullName)); - Assert.True(type.Implements(typeof(IInterface2).FullName)); - Assert.True(type.Implements(typeof(IInterface3).FullName)); - Assert.False(type.Implements(typeof(IInterface4).FullName)); + Assert.True(type.Implements(typeof(IInterface1).FullName!)); + Assert.True(type.Implements(typeof(IInterface2).FullName!)); + Assert.True(type.Implements(typeof(IInterface3).FullName!)); + Assert.False(type.Implements(typeof(IInterface4).FullName!)); } [Fact] public void CorLibTypeDefinitionToSignatureShouldResultInCorLibTypeSignature() { var module = new ModuleDefinition("Test"); - var type = module.CorLibTypeFactory.Object.Resolve(); + var type = module.CorLibTypeFactory.Object.Resolve()!; var signature = type.ToTypeSignature(); var corlibType = Assert.IsAssignableFrom(signature); Assert.Equal(ElementType.Object, corlibType.ElementType); @@ -615,13 +614,92 @@ public void AddTypeWithCorLibBaseTypeToAssemblyWithCorLibTypeReferenceInAttribut public void ReadIsByRefLike() { var resolver = new DotNetCoreAssemblyResolver(new Version(5, 0)); - var corLib = resolver.Resolve(KnownCorLibs.SystemPrivateCoreLib_v5_0_0_0); + var corLib = resolver.Resolve(KnownCorLibs.SystemPrivateCoreLib_v5_0_0_0)!; - var intType = corLib.ManifestModule.TopLevelTypes.First(t => t.Name == "Int32"); + var intType = corLib.ManifestModule!.TopLevelTypes.First(t => t.Name == "Int32"); var spanType = corLib.ManifestModule.TopLevelTypes.First(t => t.Name == "Span`1"); Assert.False(intType.IsByRefLike); Assert.True(spanType.IsByRefLike); } + + [Fact] + public void GetStaticConstructor() + { + var module = ModuleDefinition.FromFile(typeof(Constructors).Assembly.Location); + + var type1 = module.LookupMember(typeof(Constructors).MetadataToken); + var cctor = type1.GetStaticConstructor(); + Assert.NotNull(cctor); + Assert.True(cctor.IsStatic); + Assert.True(cctor.IsConstructor); + + var type2 = module.LookupMember(typeof(NoStaticConstructor).MetadataToken); + Assert.Null(type2.GetStaticConstructor()); + } + + [Fact] + public void GetOrCreateStaticConstructor() + { + var module = ModuleDefinition.FromFile(typeof(Constructors).Assembly.Location); + var type1 = module.LookupMember(typeof(Constructors).MetadataToken); + + // If cctor already exists, we expect this to be returned. + var cctor = type1.GetStaticConstructor(); + Assert.NotNull(cctor); + Assert.Same(cctor, type1.GetOrCreateStaticConstructor()); + + var type2 = module.LookupMember(typeof(NoStaticConstructor).MetadataToken); + Assert.Null(type2.GetStaticConstructor()); + + // If cctor doesn't exist yet, it should be added. + cctor = type2.GetOrCreateStaticConstructor(); + Assert.NotNull(cctor); + Assert.Same(type2, cctor.DeclaringType); + Assert.Same(cctor, type2.GetOrCreateStaticConstructor()); + Assert.True(cctor.IsStatic); + Assert.True(cctor.IsConstructor); + } + + [Fact] + public void GetParameterlessConstructor() + { + var module = ModuleDefinition.FromFile(typeof(Constructors).Assembly.Location); + var type = module.LookupMember(typeof(Constructors).MetadataToken); + + var ctor = type.GetConstructor(); + Assert.NotNull(ctor); + Assert.False(ctor.IsStatic); + Assert.True(ctor.IsConstructor); + Assert.Empty(ctor.Parameters); + } + + [Theory] + [InlineData(new[] {ElementType.I4, ElementType.I4})] + [InlineData(new[] {ElementType.I4, ElementType.String})] + [InlineData(new[] {ElementType.I4, ElementType.String, ElementType.R8})] + public void GetParametersConstructor(ElementType[] types) + { + var module = ModuleDefinition.FromFile(typeof(Constructors).Assembly.Location); + var type = module.LookupMember(typeof(Constructors).MetadataToken); + + var signatures = types.Select(x => (TypeSignature) module.CorLibTypeFactory.FromElementType(x)).ToArray(); + var ctor = type.GetConstructor(signatures); + Assert.NotNull(ctor); + Assert.False(ctor.IsStatic); + Assert.True(ctor.IsConstructor); + Assert.Equal(signatures, ctor.Signature!.ParameterTypes); + } + + [Fact] + public void GetNonExistingConstructorShouldReturnNull() + { + var module = ModuleDefinition.FromFile(typeof(Constructors).Assembly.Location); + var type = module.LookupMember(typeof(Constructors).MetadataToken); + var factory = module.CorLibTypeFactory; + + Assert.Null(type.GetConstructor(factory.String)); + Assert.Null(type.GetConstructor(factory.String, factory.String)); + } } } diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/Constructors.cs b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/Constructors.cs new file mode 100644 index 000000000..38abf9a48 --- /dev/null +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/Constructors.cs @@ -0,0 +1,26 @@ +using System; + +namespace AsmResolver.DotNet.TestCases.Methods; + +public class Constructors +{ + static Constructors() + { + } + + public Constructors() + { + } + + public Constructors(int a, int b) + { + } + + public Constructors(int a, string b) + { + } + + public Constructors(int a, string b, double c) + { + } +} diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/NoStaticConstructor.cs b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/NoStaticConstructor.cs new file mode 100644 index 000000000..64985fff3 --- /dev/null +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/NoStaticConstructor.cs @@ -0,0 +1,5 @@ +namespace AsmResolver.DotNet.TestCases.Methods; + +public class NoStaticConstructor +{ +} From 08113c69d67dcc3034ab8e4405fbe9e19ec26ba7 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 6 Sep 2023 20:27:37 +0200 Subject: [PATCH 2/4] Add MethodDefinition::Create[Static]Constructor --- src/AsmResolver.DotNet/MethodDefinition.cs | 52 +++++++++++++++++++ src/AsmResolver.DotNet/TypeDefinition.cs | 31 ++++------- .../MethodDefinitionTest.cs | 23 ++++++++ .../Constructors.cs | 16 ++++++ 4 files changed, 102 insertions(+), 20 deletions(-) diff --git a/src/AsmResolver.DotNet/MethodDefinition.cs b/src/AsmResolver.DotNet/MethodDefinition.cs index 234396cb2..00105ab2d 100644 --- a/src/AsmResolver.DotNet/MethodDefinition.cs +++ b/src/AsmResolver.DotNet/MethodDefinition.cs @@ -8,6 +8,8 @@ using AsmResolver.DotNet.Code.Native; using AsmResolver.DotNet.Collections; using AsmResolver.DotNet.Signatures; +using AsmResolver.DotNet.Signatures.Types; +using AsmResolver.PE.DotNet.Cil; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; @@ -689,6 +691,56 @@ public UnmanagedExportInfo? ExportInfo set => _exportInfo.SetValue(value); } + /// + /// Creates a new private static constructor for a type that is executed when its declaring type is loaded by the CLR. + /// + /// The target module the method will be added to. + /// The constructor. + /// + /// The resulting method's body will consist of a single ret instruction. + /// + public static MethodDefinition CreateStaticConstructor(ModuleDefinition module) + { + var cctor = new MethodDefinition(".cctor", + MethodAttributes.Private + | MethodAttributes.Static + | MethodAttributes.SpecialName + | MethodAttributes.RuntimeSpecialName, + MethodSignature.CreateStatic(module.CorLibTypeFactory.Void)); + + cctor.CilMethodBody = new CilMethodBody(cctor); + cctor.CilMethodBody.Instructions.Add(CilOpCodes.Ret); + + return cctor; + } + + /// + /// Creates a new public constructor for a type that is executed when its declaring type is loaded by the CLR. + /// + /// The target module the method will be added to. + /// An ordered list of types the parameters of the constructor should have. + /// The constructor. + /// + /// The resulting method's body will consist of a single ret instruction, and does not contain a call to + /// any of the declaring type's base classes. For an idiomatic .NET binary, this should be added. + /// + public static MethodDefinition CreateConstructor(ModuleDefinition module, params TypeSignature[] parameterTypes) + { + var ctor = new MethodDefinition(".ctor", + MethodAttributes.Public + | MethodAttributes.SpecialName + | MethodAttributes.RuntimeSpecialName, + MethodSignature.CreateInstance(module.CorLibTypeFactory.Void, parameterTypes)); + + for (int i = 0; i < parameterTypes.Length; i++) + ctor.ParameterDefinitions.Add(new ParameterDefinition(null)); + + ctor.CilMethodBody = new CilMethodBody(ctor); + ctor.CilMethodBody.Instructions.Add(CilOpCodes.Ret); + + return ctor; + } + MethodDefinition IMethodDescriptor.Resolve() => this; /// diff --git a/src/AsmResolver.DotNet/TypeDefinition.cs b/src/AsmResolver.DotNet/TypeDefinition.cs index b29ae8ad0..ba6eaa8c6 100644 --- a/src/AsmResolver.DotNet/TypeDefinition.cs +++ b/src/AsmResolver.DotNet/TypeDefinition.cs @@ -832,16 +832,7 @@ public MethodDefinition GetOrCreateStaticConstructor(ModuleDefinition? module) if (module == null) throw new ArgumentNullException(nameof(module)); - cctor = new MethodDefinition(".cctor", - MethodAttributes.Private - | MethodAttributes.Static - | MethodAttributes.SpecialName - | MethodAttributes.RuntimeSpecialName, - MethodSignature.CreateStatic(module.CorLibTypeFactory.Void)); - - cctor.CilMethodBody = new CilMethodBody(cctor); - cctor.CilMethodBody.Instructions.Add(new CilInstruction(0, CilOpCodes.Ret)); - + cctor = MethodDefinition.CreateStaticConstructor(module); Methods.Insert(0, cctor); } @@ -860,44 +851,44 @@ public MethodDefinition GetOrCreateStaticConstructor(ModuleDefinition? module) /// /// Finds the instance constructor with the provided parameter types this type defines. /// - /// An ordered list of types the parameters of the constructor should have. + /// An ordered list of types the parameters of the constructor should have. /// The constructor, or null if none is present. - public MethodDefinition? GetConstructor(params TypeSignature[] arguments) + public MethodDefinition? GetConstructor(params TypeSignature[] parameterTypes) { - return GetConstructor(SignatureComparer.Default, arguments); + return GetConstructor(SignatureComparer.Default, parameterTypes); } /// /// Finds the instance constructor with the provided parameter types this type defines. /// /// The signature comparer to use when comparing the parameter types. - /// An ordered list of types the parameters of the constructor should have. + /// An ordered list of types the parameters of the constructor should have. /// The constructor, or null if none is present. - public MethodDefinition? GetConstructor(SignatureComparer comparer, params TypeSignature[] arguments) + public MethodDefinition? GetConstructor(SignatureComparer comparer, params TypeSignature[] parameterTypes) { - return GetConstructor(comparer, (IList) arguments); + return GetConstructor(comparer, (IList) parameterTypes); } /// /// Finds the instance constructor with the provided parameter types this type defines. /// /// The signature comparer to use when comparing the parameter types. - /// An ordered list of types the parameters of the constructor should have. + /// An ordered list of types the parameters of the constructor should have. /// The constructor, or null if none is present. - public MethodDefinition? GetConstructor(SignatureComparer comparer, IList arguments) + public MethodDefinition? GetConstructor(SignatureComparer comparer, IList parameterTypes) { for (int i = 0; i < Methods.Count; i++) { if (Methods[i] is not {IsConstructor: true, IsStatic: false} method) continue; - if (method.Parameters.Count != arguments.Count) + if (method.Parameters.Count != parameterTypes.Count) continue; bool fullMatch = true; for (int j = 0; j < method.Parameters.Count && fullMatch; j++) { - if (!comparer.Equals(method.Parameters[j].ParameterType, arguments[j])) + if (!comparer.Equals(method.Parameters[j].ParameterType, parameterTypes[j])) fullMatch = false; } diff --git a/test/AsmResolver.DotNet.Tests/MethodDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/MethodDefinitionTest.cs index ad21af54f..be30f21fc 100644 --- a/test/AsmResolver.DotNet.Tests/MethodDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/MethodDefinitionTest.cs @@ -566,5 +566,28 @@ public void MethodFullNameTests(string methodName, string expectedFullName) Assert.Equal(expectedFullName, method.FullName); } + + [Fact] + public void CreateParameterlessConstructor() + { + var module = ModuleDefinition.FromFile(typeof(Constructors).Assembly.Location); + var ctor = MethodDefinition.CreateConstructor(module); + + Assert.True(ctor.IsConstructor); + Assert.Empty(ctor.Parameters); + Assert.NotNull(ctor.CilMethodBody); + Assert.Equal(CilOpCodes.Ret, Assert.Single(ctor.CilMethodBody.Instructions).OpCode); + } + + [Fact] + public void CreateConstructor() + { + var module = ModuleDefinition.FromFile(typeof(Constructors).Assembly.Location); + var factory = module.CorLibTypeFactory; + var ctor = MethodDefinition.CreateConstructor(module, factory.Int32, factory.Double); + + Assert.True(ctor.IsConstructor); + Assert.Equal(new[] {factory.Int32, factory.Double}, ctor.Parameters.Select(x => x.ParameterType)); + } } } diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/Constructors.cs b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/Constructors.cs index 38abf9a48..2d4651f00 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/Constructors.cs +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/Constructors.cs @@ -23,4 +23,20 @@ public Constructors(int a, string b) public Constructors(int a, string b, double c) { } + + public void NonConstructorMethod() + { + } + + public void NonConstructorMethod(int a, int b) + { + } + + public void NonConstructorMethod(int a, string b) + { + } + + public void NonConstructorMethod(int a, string b, double c) + { + } } From 8b5ea886a90592b8088c63928012c5a773debcdf Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 6 Sep 2023 20:43:18 +0200 Subject: [PATCH 3/4] Add TypeDefinition::InheritsFrom and Implements overloads taking namespace and name separately. --- src/AsmResolver.DotNet/TypeDefinition.cs | 98 +++++++++++++------ .../TypeDefinitionTest.cs | 22 +++++ 2 files changed, 91 insertions(+), 29 deletions(-) diff --git a/src/AsmResolver.DotNet/TypeDefinition.cs b/src/AsmResolver.DotNet/TypeDefinition.cs index ba6eaa8c6..c05c1ee5f 100644 --- a/src/AsmResolver.DotNet/TypeDefinition.cs +++ b/src/AsmResolver.DotNet/TypeDefinition.cs @@ -647,51 +647,91 @@ public ClassLayout? ClassLayout } /// - /// Determines whether the type inherits from a particular type + /// Determines whether the type inherits from a particular type. /// /// The full name of the type - /// Whether the current inherits the type - public bool InheritsFrom(string fullName) - { - var type = this; - do - { - if (type.FullName == fullName) - return true; - - var current = type; - type = type.BaseType?.Resolve(); + /// + /// true whether the current inherits from the type, + /// false otherwise. + /// + public bool InheritsFrom(string fullName) => FindInTypeTree(x => x.FullName == fullName); - // This prevents an issue where the base type is the same as itself - // ... so basically a cyclic dependency - if (current == type) - return false; - } while (type is {}); + /// + /// Determines whether the type inherits from a particular type. + /// + /// The namespace of the type. + /// The name of the type. + /// + /// true whether the current inherits from the type, + /// false otherwise. + /// + public bool InheritsFrom(string? ns, string name) => FindInTypeTree(x => x.IsTypeOf(ns, name)); - return false; - } + /// + /// Determines whether the type inherits from a particular type. + /// + /// The namespace of the type. + /// The name of the type. + /// + /// true whether the current inherits from the type, + /// false otherwise. + /// + public bool InheritsFrom(Utf8String? ns, Utf8String name) => FindInTypeTree(x => x.IsTypeOfUtf8(ns, name)); /// - /// Determines whether the type implements a particular interface + /// Determines whether the type implements a particular interface. /// /// The full name of the interface - /// Whether the type implements the interface + /// + /// true whether the current implements the interface, + /// false otherwise. + /// public bool Implements(string fullName) { + return FindInTypeTree(x => x.Interfaces.Any(@interface => @interface.Interface?.FullName == fullName)); + } + + /// + /// Determines whether the type implements a particular interface. + /// + /// The namespace of the type. + /// The name of the type. + /// + /// true whether the current implements the interface, + /// false otherwise. + /// + public bool Implements(string? ns, string name) => FindInTypeTree( + x => x.Interfaces.Any(@interface => @interface.Interface?.IsTypeOf(ns, name) ?? false)); + + /// + /// Determines whether the type implements a particular interface. + /// + /// The namespace of the type. + /// The name of the type. + /// + /// true whether the current implements the interface, + /// false otherwise. + /// + public bool Implements(Utf8String? ns, Utf8String name) => FindInTypeTree( + x => x.Interfaces.Any(@interface => @interface.Interface?.IsTypeOfUtf8(ns, name) ?? false)); + + private bool FindInTypeTree(Predicate condition) + { + var visited = new List(); + var type = this; do { - if (type.Interfaces.Any(@interface => @interface.Interface?.FullName == fullName)) + // Protect against malicious cyclic dependency graphs. + if (visited.Contains(type)) + return false; + + if (condition(type)) return true; - var current = type; + visited.Add(type); type = type.BaseType?.Resolve(); - - // This prevents an issue where the base type is the same as itself - // ... so basically a cyclic dependency - if (current == type) - return false; - } while (type is {}); + } while (type is not null); return false; } diff --git a/test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs index ebff55bf9..4e2ebfa28 100644 --- a/test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs @@ -553,6 +553,16 @@ public void InheritanceMultipleLevels() Assert.False(type.InheritsFrom(typeof(Class).FullName!)); } + [Fact] + public void InheritanceMultipleLevelsTypeOf() + { + var module = ModuleDefinition.FromFile(typeof(DerivedDerivedClass).Assembly.Location); + var type = module.TopLevelTypes.First(t => t.Name == nameof(DerivedDerivedClass)); + + Assert.True(type.InheritsFrom(typeof(AbstractClass).Namespace, nameof(AbstractClass))); + Assert.False(type.InheritsFrom(typeof(Class).Namespace, nameof(Class))); + } + [Fact] public void InterfaceImplementedFromInheritanceHierarchy() { @@ -565,6 +575,18 @@ public void InterfaceImplementedFromInheritanceHierarchy() Assert.False(type.Implements(typeof(IInterface4).FullName!)); } + [Fact] + public void InterfaceImplementedFromInheritanceHierarchyTypeOf() + { + var module = ModuleDefinition.FromFile(typeof(DerivedInterfaceImplementations).Assembly.Location); + var type = module.TopLevelTypes.First(t => t.Name == nameof(DerivedInterfaceImplementations)); + + Assert.True(type.Implements(typeof(IInterface1).Namespace, nameof(IInterface1))); + Assert.True(type.Implements(typeof(IInterface2).Namespace, nameof(IInterface2))); + Assert.True(type.Implements(typeof(IInterface3).Namespace, nameof(IInterface3))); + Assert.False(type.Implements(typeof(IInterface4).Namespace, nameof(IInterface4))); + } + [Fact] public void CorLibTypeDefinitionToSignatureShouldResultInCorLibTypeSignature() { From a0d9645fa79164d35a6b97e65b5bd3d15c17b91f Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 6 Sep 2023 21:59:20 +0200 Subject: [PATCH 4/4] Expand docs on new APIs and creating metadata in general. --- docs/guides/dotnet/cloning.md | 12 +- docs/guides/dotnet/managed-method-bodies.md | 134 +++++++-- docs/guides/dotnet/member-tree.md | 308 ++++++++++++++++++-- 3 files changed, 391 insertions(+), 63 deletions(-) diff --git a/docs/guides/dotnet/cloning.md b/docs/guides/dotnet/cloning.md index 37b54fd21..40b6fc79a 100644 --- a/docs/guides/dotnet/cloning.md +++ b/docs/guides/dotnet/cloning.md @@ -82,8 +82,8 @@ look up the members by metadata token. ``` csharp var sourceModule = ModuleDefinition.FromFile(typeof(Rectangle).Assembly.Location); -var rectangleType = (TypeDefinition) sourceModule.LookupMember(typeof(Rectangle).MetadataToken); -var vectorType = (TypeDefinition) sourceModule.LookupMember(typeof(Vector2).MetadataToken); +var rectangleType = sourceModule.LookupMember(typeof(Rectangle).MetadataToken); +var vectorType = sourceModule.LookupMember(typeof(Vector2).MetadataToken); ``` We can then use `MemberCloner.Include` to include the types in the @@ -103,7 +103,7 @@ cloner.Include(rectangleType); cloner.Include(vectorType); ``` -`Include` returns the same `MemberCloner` instance, allowing for more fluent +`Include` returns the same `MemberCloner` instance, allowing for more fluent syntax: ``` csharp @@ -305,8 +305,8 @@ foreach (var clonedType in clonedTypes) ``` Injecting the cloned top level types is a very common use-case for the cloner. -AsmResolver defines the `InjectTypeClonerListener` class that implements a -cloner listener that injects all top-level types automatically into +AsmResolver defines the `InjectTypeClonerListener` class that implements a +cloner listener that injects all top-level types automatically into the destination module. In such a case, the code can be reduced to the following: ``` csharp @@ -337,4 +337,4 @@ new MemberCloner(destinationModule) The `AssignTokensClonerListener` uses the target module's `TokenAllocator`. See [Metadata Token Allocation](token-allocation.md) for more information on how this -class operates. \ No newline at end of file +class operates. diff --git a/docs/guides/dotnet/managed-method-bodies.md b/docs/guides/dotnet/managed-method-bodies.md index 6cda54423..69f658699 100644 --- a/docs/guides/dotnet/managed-method-bodies.md +++ b/docs/guides/dotnet/managed-method-bodies.md @@ -1,5 +1,10 @@ # CIL Method Bodies +Most methods defined in a .NET module are implemented using the Common +Intermediate Language (CIL), and AsmResolver provides high-level +disassembler and assembler capabilities for creating, reading and writing +method bodies written in this language. + The relevant models in this document can be found in the following namespaces: @@ -33,7 +38,9 @@ blocks: - `ExceptionHandlers`: A collection of regions protected by an exception handler. -## Basic structure of CIL instructions +## Instructions + +### Basic structure of CIL instructions Instructions that are assembled into the method body are automatically disassembled and put in a mutable collection of `CilInstruction`, @@ -53,27 +60,27 @@ The `CilInstruction` class defines three basic properties: By default, depending on the value of `OpCode.OperandType`, `Operand` contains (and always should contain) one of the following: -|OpCode.OperandType |Type of Operand | -|------------------------------------|----------------------------------------| -|`CilOperandType.InlineNone` |N/A (is always `null`) | -|`CilOperandType.ShortInlineI` |`sbyte` | -|`CilOperandType.InlineI` |`int` | -|`CilOperandType.InlineI8` |`long` | -|`CilOperandType.ShortInlineR` |`float` | -|`CilOperandType.InlineR` |`double` | -|`CilOperandType.InlineString` |`string` or `MetadataToken` | -|`CilOperandType.InlineBrTarget` |`ICilLabel` or `int` | -|`CilOperandType.ShortInlineBrTarget`|`ICilLabel` or `sbyte` | -|`CilOperandType.InlineSwitch` |`IList` | -|`CilOperandType.ShortInlineVar` |`CilLocalVariable` or `byte` | -|`CilOperandType.InlineVar` |`CilLocalVariable` or `ushort` | -|`CilOperandType.ShortInlineArgument`|`Parameter` or `byte` | -|`CilOperandType.InlineArgument` |`Parameter` or `ushort` | -|`CilOperandType.InlineField` |`IFieldDescriptor` or `MetadataToken` | -|`CilOperandType.InlineMethod` |`IMethodDescriptor` or `MetadataToken` | -|`CilOperandType.InlineSig` |`StandAloneSignature` or `MetadataToken`| -|`CilOperandType.InlineTok` |`IMetadataMember` or `MetadataToken` | -|`CilOperandType.InlineType` |`ITypeDefOrRef` or `MetadataToken` | +|OpCode.OperandType |Type of Operand | +|------------------------------------|----------------------------------------| +|`CilOperandType.InlineNone` |N/A (is always `null`) | +|`CilOperandType.ShortInlineI` |`sbyte` | +|`CilOperandType.InlineI` |`int` | +|`CilOperandType.InlineI8` |`long` | +|`CilOperandType.ShortInlineR` |`float` | +|`CilOperandType.InlineR` |`double` | +|`CilOperandType.InlineString` |`string` or `MetadataToken` | +|`CilOperandType.InlineBrTarget` |`ICilLabel` or `int` | +|`CilOperandType.ShortInlineBrTarget`|`ICilLabel` or `sbyte` | +|`CilOperandType.InlineSwitch` |`IList` | +|`CilOperandType.ShortInlineVar` |`CilLocalVariable` or `byte` | +|`CilOperandType.InlineVar` |`CilLocalVariable` or `ushort` | +|`CilOperandType.ShortInlineArgument`|`Parameter` or `byte` | +|`CilOperandType.InlineArgument` |`Parameter` or `ushort` | +|`CilOperandType.InlineField` |`IFieldDescriptor` or `MetadataToken` | +|`CilOperandType.InlineMethod` |`IMethodDescriptor` or `MetadataToken` | +|`CilOperandType.InlineSig` |`StandAloneSignature` or `MetadataToken`| +|`CilOperandType.InlineTok` |`IMetadataMember` or `MetadataToken` | +|`CilOperandType.InlineType` |`ITypeDefOrRef` or `MetadataToken` | > [!WARNING] @@ -103,7 +110,7 @@ instructions.Add(CilOpCodes.Ldstr, "Hello, World!"); instructions.Add(CilOpCodes.Ret); ``` -## Pushing 32-bit integer constants onto the stack +### Pushing 32-bit integer constants onto the stack In CIL, pushing integer constants onto the stack is done using one of the `ldc.i4` instruction variants. @@ -124,7 +131,7 @@ If we want to get the pushed value, we can use the the `ldc.i4` variants, including all the macro opcodes that do not explicitly define an operand such as `ldc.i4.1`. -## Branching Instructions +### Branching Instructions Branch instructions are instructions that (might) transfer control to another part of the method body. To reference the instruction to jump to @@ -160,7 +167,7 @@ The `switch` operation uses a `IList` instead. > code stream. This can be disabled by setting `VerifyLabelsOnBuild` to > `false`. -## Finding instructions by offset +### Finding Instructions by Offset Instructions stored in a method body are indexed not by offset, but by order of occurrence. If it is required to find an instruction by offset, @@ -187,7 +194,7 @@ int index = body.Instructions.GetIndexByOffset(0x0012); instruction1 = body.Instructions[index]; ``` -## Referencing members +### Referencing Members As specified by the table above, operations such as a `call` require a member as operand. @@ -211,7 +218,7 @@ More information on the capabilities and limitations of the `ReferenceImporter` can be found in [Reference Importing](importing.md). -## Expanding and optimising macros +### Expanding and Optimizing Macros CIL defines a couple of macro operations that do the same as their full counterpart, but require less space to be encoded. For example, the @@ -240,7 +247,7 @@ body.Instructions.ExpandMacros(); // instruction is now expanded to "ldc.i4 1". ``` -## Pretty printing CIL instructions +### Pretty printing CIL Instructions Instructions can be formatted using e.g. an instance of the `CilInstructionFormatter`: @@ -251,7 +258,7 @@ foreach (CilInstruction instruction in body.Instructions) Console.WriteLine(formatter.FormatInstruction(instruction)); ``` -## Patching CIL instructions +### Patching CIL Instructions Instructions can be added or removed using the `Add`, `Insert`, `Remove` and `RemoveAt` methods: @@ -297,7 +304,32 @@ helper function: body.Instructions[i].ReplaceWithNop(); ``` -## Exception handlers +## Local Variables + +Most methods will define local variables to temporarily store state +throughout the execution of the method's code. Local variables are +exposed through the `CilMethodBody.LocalVariables` property, and are +represented using the `CilLocalVariable` class. + +```csharp +CilMethodBody body = ...; +foreach (var local in body.LocalVariables) + Console.WriteLine($"{local.Index}: {local.VariableType}"); +``` + +New variables can be created by calling its constructor: + +```csharp +ModuleDefinition module = ...; +var local = new CilLocalVariable(module.CorLibTypeFactory.Int32); +``` + +New variables can be added to the method: +```csharp +body.LocalVariables.Add(local); +``` + +## Exception Handlers Exception handlers are regions in the method body that are protected from exceptions. In AsmResolver, they are represented by the @@ -321,6 +353,45 @@ from exceptions. In AsmResolver, they are represented by the Depending on the value of `HandlerType`, either `FilterStart` or `ExceptionType`, or neither has a value. +```csharp +CilMethodBody body = ...; +foreach (var handler in body.ExceptionHandlers) +{ + Console.WriteLine($"HandlerType: {handler.HandlerType}"); + Console.WriteLine($"TryStart: {handler.TryStart}"); + Console.WriteLine($"TryEnd: {handler.TryEnd}"); + Console.WriteLine($"HandlerStart: {handler.HandlerStart}"); + Console.WriteLine($"HandlerEnd: {handler.HandlerEnd}"); + + if (handler.HandlerType == CilExceptionHandlerType.Exception) + { + // handler is a try-catch with an exception type. + Console.WriteLine($"ExceptionType: {handler.ExceptionType}"); + } + else if if (handler.HandlerType == CilExceptionHandlerType.Filter) + { + // handler is a try-catch with a custom filter: + Console.WriteLine($"FilterStart: {handler.FilterStart}"); + } +} +``` + +New handlers can be added to the method body: + +```csharp +body.ExceptionHandlers.Add(new CilExceptionHandler +{ + HandlerType = CilExceptionHandlerType.Exception, + TryStart = body.Instructions[0].CreateLabel(), + TryEnd = body.Instructions[4].CreateLabel(), + HandlerStart = body.Instructions[4].CreateLabel(), + HandlerEnd = body.Instructions[8].CreateLabel(), + ExceptionType = module.CorLibTypeFactory.CorLibScope + .CreateTypeReference("System", "Exception") + .ImprotWith(module.DefaultImporter) +}); +``` + > [!NOTE] > Similar to branch instructions, when an exception handler contains a > `null` label or a label that references an instruction that is not @@ -328,7 +399,8 @@ Depending on the value of `HandlerType`, either `FilterStart` or > serializing the code stream. This can be disabled by setting > `VerifyLabelsOnBuild` to `false`. -## Maximum stack depth + +## Maximum Stack Depth CIL method bodies work with a stack, and the stack has a pre-defined size. This pre-defined size is defined by the `MaxStack` property. diff --git a/docs/guides/dotnet/member-tree.md b/docs/guides/dotnet/member-tree.md index ec0e7a972..6ebeeba08 100644 --- a/docs/guides/dotnet/member-tree.md +++ b/docs/guides/dotnet/member-tree.md @@ -1,6 +1,6 @@ # The Member Tree -## Assemblies and modules +## Assemblies and Modules The root of every .NET assembly is represented by the `AssemblyDefinition` class. This class exposes basic information such as @@ -20,14 +20,32 @@ Most .NET assemblies only have one module. This main module is also known as the manifest module, and can be accessed directly through the `AssemblyDefinition.ManifestModule` property. -## Obtaining types in a module +Executable modules can have an entry point, which can be obtained using +the `ManagedEntryPoint` property: -Types are represented by the `TypeDefinition` class. To get the types -defined in a module, use the `ModuleDefinition.TopLevelTypes` property. -A top level types is any non-nested type. Nested types are exposed -through the `TypeDefinition.NestedTypes`. Alternatively, to get all -types, including nested types, it is possible to call the -`ModuleDefinition.GetAllTypes` method instead. +```csharp +var entryPoint = module.ManagedEntryPoint; +``` + +Often, modules also contain a static module constructor, which is executed +the moment the module is loaded into memory by the CLR (and thus before the +entry point is executed). AsmResolver provides helper methods to quickly +locate such a constructor: + +```csharp +var cctor = module.GetModuleConstructor(); +``` + +## Types + +Types form logical units or data type defined in a module, and are +represented by the `TypeDefinition` class. + +### Inspecting Types in a Module + +Types defined in a module are exposed through the `ModuleDefinition.TopLevelTypes` +property. A top level types is any non-nested type. Nested types +are exposed through the `TypeDefinition.NestedTypes`. Below is an example program that iterates through all types recursively and prints them: @@ -55,23 +73,164 @@ private static void DumpTypes(IEnumerable types, int indentation } ``` -## Obtaining methods and fields +Alternatively, you can get all the types including nested types using the +`ModuleDefinition.GetAllTypes()` method: + +``` csharp +var module = ModuleDefinition.FromFile(...); +foreach (var type in module.GetAllTypes()) + Console.WriteLine(type.FullName); +``` + +### Creating New Types + +New types can be created by calling one of its constructors: + +```csharp +ModuleDefinition module = ... +var newType = new TypeDefinition( + "Namespace", + "Name", + TypeAttributes.Public, + module.CorLibTypeFactory.Object); +``` + +> [!WARNING] +> For classes, ensure that you specify a non-null base type or the CLR will +> not load the binary properly. + +For structures, make sure that your type inherits from `System.ValueType`: + +```csharp +ModuleDefinition module = ... +var newType = new TypeDefinition( + "Namespace", + "Name", + TypeAttributes.Public, + module.CorLibTypeFactory.CorLibScope + .CreateTypeReference("System", "ValueType") + .ImportWith(module.DefaultImporter)); +``` + +Interfaces in a .NET module do not have a base type, and as such, creating +new interfaces will not require specifying one: + +```csharp +ModuleDefinition module = ... +var newType = new TypeDefinition( + "Namespace", + "IName", + TypeAttributes.Public | TypeAttributes.Interface); +``` + +Once a type has been constructed, it can be added to either a `ModuleDefinition` +as a top-level type, or to another `TypeDefinition` as a nested type: + +```csharp +ModuleDefinition module = ...; +module.TopLevelTypes.Add(newType); +``` +```csharp +TypeDefinition type = ...; +type.NestedTypes.Add(newType); +``` + +## Fields + +Fields comprise all the data a type stores, and form the internal structure +of a class or value type. They are represented using the `FieldDefinition` +class. + +### Inspecting Fields in a Type + +The `TypeDefinition` class exposes a collection of fields that the type +defines: + +``` csharp +foreach (var field in type.Fields) + Console.WriteLine($"{field.Name} : {field.MetadataToken}"); +``` + +Fields have a signature which contains the field's type. + +``` csharp +FieldDefinition field = ... +Console.WriteLine($"Field type: {field.Signature.FieldType}"); +``` + +Fields can also have constants attached, exposed via the `Constant` +property: + +``` csharp +FieldDefinition field = ... +if (field.Constant is { } constant) + Console.WriteLine($"Field Constant Data: {BitConverter.ToString(constant.Value>Data)}"); +``` + +For fields that have an RVA attached (such as fields with an initial +value set), you can access the `FieldRva` property containing the +`ISegment` value with the raw data. This is in particular useful for +inspecting fields containing the initial raw data of an array. + +``` csharp +FieldDefinition field = ... +if (field.FieldRva is { } rva) + Console.WriteLine($"Field Initial Data: {BitConverter.ToString(rva.WriteIntoArray())}"); +``` + +Refer to [Reading and Writing File Segments](../core/segments.md) for more +information on how to use `ISegment`s. + + +### Creating New Fields + +Creating and adding new fields can be done by using one of its constructors. + +``` csharp +ModuleDefinition module = ...; +var field = new FieldDefinition( + "MyField", + FieldAttributes.Public, + module.CorLibTypeFactory.Int32);" +``` + +Fields can be added to a type: + +```csharp +TypeDefinition type = ...; +type.Fields.Add(field); +``` + +Most properties in `FieldDefinition` are mutable, allowing you to configure +however you want your new field to be. + + +## Methods + +Methods are functions defined in a type, and provide a way to define +operations that can be applied to a type. They are represented using +the `MethodDefinition` class. -The `TypeDefinition` class exposes collections of methods and fields -that the type defines: +### Inspecting Methods in a Type + +The `TypeDefinition` class exposes a collection of methods that the type +defines: ``` csharp foreach (var method in type.Methods) Console.WriteLine($"{method.Name} : {method.MetadataToken}"); ``` +AsmResolver provides helper methods to find constructors in a type: + ``` csharp -foreach (var field in type.Fields) - Console.WriteLine($"{field.Name} : {field.MetadataToken}"); +var parameterlessCtor = type.GetConstructor(); +var parameterizedCtor = type.GetConstructor(module.CorLibFactory.Int32); +var cctor = type.GetStaticConstructor(); ``` Methods and fields have a `Signature` property, that contain the return -and parameter types, or the field type respectively. +and parameter types: ``` csharp MethodDefinition method = ... @@ -79,11 +238,6 @@ Console.WriteLine($"Return type: {method.Signature.ReturnType}"); Console.WriteLine($"Parameter types: {string.Join(", ", method.Signature.ParameterTypes)}"); ``` -``` csharp -FieldDefinition field = ... -Console.WriteLine($"Field type: {field.Signature.FieldType}"); -``` - However, for reading parameters from a method definition, it is preferred to use the `Parameters` property instead of the `ParameterTypes` property stored in the signature. This is because the @@ -96,27 +250,129 @@ foreach (var parameter in method.Parameters) Console.WriteLine($"{parameter.Name} : {parameter.ParameterType}"); ``` -## Obtaining properties and events +Methods may or may not be assigned a method body. This can be verified +using `HasMethodBody`: -Obtaining properties and events is similar to obtaining methods and -fields; `TypeDefinition` exposes them in a list as well: +```csharp +if (method.HasMethodBody) +{ + // ... +} +``` + + +Typically, a method body implemented using the Common Intermediate +Language (CIL), the bytecode used by .NET. This method body can be +inspected using the `CilMethodBody` property: + +```csharp +if (method.CilMethodBody is { } body) +{ + foreach (var instruction in body.Instructions) + Console.WriteLine(instruction); +} +``` + +For more information on CIL method bodies, refer to +[CIL Method Bodies](managed-method-bodies.md). + + +### Creating New Methods + +Creating new methods can be done either through one of its constructors, +taking a name, attributes, and a method signature. + +For static methods, use the `MethodSignature.CreateStatic` to create +the signature: ``` csharp -foreach (var @event in type.Events) - Console.WriteLine($"{@event.Name} : {@event.MetadataToken}"); +ModuleDefinition module = ...; +var method = new MethodDefinition( + "MyMethod", + MethodAttributes.Public | MethodAttributes.Static, + MethodSignature.CreateStatic( + module.CorLibTypeFactory.Void, // Return type + module.CorLibTypeFactory.Int32, // Parameter 1 + module.CorLibTypeFactory.String // Parameter 2 + )); ``` +Similarly, for instance methods, use the `MethodSignature.CreateInstance` +to create the signature: + +``` csharp +ModuleDefinition module = ...; +var method = new MethodDefinition( + "MyMethod", + MethodAttributes.Public, + MethodSignature.CreateInstance( + module.CorLibTypeFactory.Void, // Return type + module.CorLibTypeFactory.Int32, // Parameter 1 + module.CorLibTypeFactory.String // Parameter 2 + )); +``` + +AsmResolver provides helper methods to create special methods such as +constructors that automatically set the right attributes and initialize +it with a default method body. + +```csharp +ModuleDefinition module = ...; +var ctor = MethodDefinition.CreateConstructor(module.CorLibTypeFactory.Int32); +var cctor = MethodDefinition.CreateStaticConstructor(); +``` + +After creating methods, they can be added to a type: + +```csharp +TypeDefinition type = ...; +type.Methods.Add(method); +``` + +Most properties in `MethodDefinition` are mutable, allowing you to configure +however you want your new method to be. + + +## Properties and Events + +Properties and Events add special semantics to groups of methods, and are +represented using the `PropertyDefinition` and `EventDefinition` classes +respectively. + +Obtaining properties and events is similar to obtaining methods and +fields; `TypeDefinition` exposes them in a list as well: + ``` csharp foreach (var property in type.Properties) Console.WriteLine($"{property.Name} : {property.MetadataToken}"); ``` +``` csharp +foreach (var @event in type.Events) + Console.WriteLine($"{@event.Name} : {@event.MetadataToken}"); +``` + Properties and events have methods associated to them. These are accessible through the `Semantics` property: ``` csharp foreach (MethodSemantics semantic in property.Semantics) -{ Console.WriteLine($"{semantic.Attributes} {semantic.Method.Name} : {semantic.MetadataToken}"); -} +``` + +For properties, there are helpers defined to quickly access the getter +or setter methods of the property: + +```csharp +MethodDefinition getter = property.GetMethod; +MethodDefinition setter = property.SetMethod; +``` + +Similarly, for events, there exists helpers for obtaining the add, +remove, and fire methods: + +```csharp +MethodDefinition adder = property.AddMethod; +MethodDefinition remover = property.RemoveMethod; +MethodDefinition fire = property.FireMethod; ```