From 8357a77dd4873facb5af59473e12a279dede12f7 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 15 Mar 2023 10:24:19 +0100 Subject: [PATCH 01/10] Add OptionalHeader::SetDataDirectory helper method. --- src/AsmResolver.PE.File/Headers/OptionalHeader.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/AsmResolver.PE.File/Headers/OptionalHeader.cs b/src/AsmResolver.PE.File/Headers/OptionalHeader.cs index a72535fa3..b9055445d 100644 --- a/src/AsmResolver.PE.File/Headers/OptionalHeader.cs +++ b/src/AsmResolver.PE.File/Headers/OptionalHeader.cs @@ -399,6 +399,16 @@ public IList DataDirectories /// The data directory entry. public DataDirectory GetDataDirectory(DataDirectoryIndex index) => DataDirectories[(int) index]; + /// + /// Sets a data directory by its index. + /// + /// The index. + /// The new data directory entry. + public void SetDataDirectory(DataDirectoryIndex index, DataDirectory directory) + { + DataDirectories[(int) index] = directory; + } + /// public override uint GetPhysicalSize() { From a5ec1005b028fb4fbe6342704fe09b0a67e82d07 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 15 Mar 2023 13:17:39 +0100 Subject: [PATCH 02/10] Add TypeSignature::GetReducedType, GetVerificationType, GetIntermediateType and GetDirectBaseClass. --- .../Signatures/Types/ArrayTypeSignature.cs | 6 + .../Types/ByReferenceTypeSignature.cs | 19 ++ .../Signatures/Types/CorLibTypeSignature.cs | 43 ++++ .../Types/CustomModifierTypeSignature.cs | 12 ++ .../Types/GenericInstanceTypeSignature.cs | 30 +++ .../Signatures/Types/PinnedTypeSignature.cs | 12 ++ .../Signatures/Types/SzArrayTypeSignature.cs | 6 + .../Signatures/Types/TypeDefOrRefSignature.cs | 13 ++ .../Signatures/Types/TypeSignature.cs | 43 ++++ .../Signatures/TypeSignatureTest.cs | 190 ++++++++++++++++++ .../GenericDerivedType.cs | 6 + 11 files changed, 380 insertions(+) create mode 100644 test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/GenericDerivedType.cs diff --git a/src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs index 682ec1f4e..a77e9bf97 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs @@ -191,6 +191,12 @@ public bool Validate() return true; } + /// + public override TypeSignature? GetDirectBaseClass() => Module?.CorLibTypeFactory.CorLibScope + .CreateTypeReference("System", "Array") + .ToTypeSignature(false) + .ImportWith(Module.DefaultImporter); + /// public override TResult AcceptVisitor(ITypeSignatureVisitor visitor) => visitor.VisitArrayType(this); diff --git a/src/AsmResolver.DotNet/Signatures/Types/ByReferenceTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/ByReferenceTypeSignature.cs index ab0f38b9e..79f7cc202 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/ByReferenceTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/ByReferenceTypeSignature.cs @@ -1,3 +1,4 @@ +using System; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; namespace AsmResolver.DotNet.Signatures.Types @@ -25,6 +26,24 @@ public ByReferenceTypeSignature(TypeSignature baseType) /// public override bool IsValueType => false; + /// + public override TypeSignature GetVerificationType() + { + if (Module is null) + throw new InvalidOperationException("Cannot determine verification type of a non-imported type."); + + var factory = Module.CorLibTypeFactory; + return BaseType.GetReducedType().ElementType switch + { + ElementType.I1 or ElementType.Boolean => factory.SByte.MakeByReferenceType(), + ElementType.I2 or ElementType.Char => factory.Int16.MakeByReferenceType(), + ElementType.I4 => factory.Int32.MakeByReferenceType(), + ElementType.I8 => factory.Int64.MakeByReferenceType(), + ElementType.I => factory.IntPtr.MakeByReferenceType(), + _ => base.GetVerificationType() + }; + } + /// public override TResult AcceptVisitor(ITypeSignatureVisitor visitor) => visitor.VisitByReferenceType(this); diff --git a/src/AsmResolver.DotNet/Signatures/Types/CorLibTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/CorLibTypeSignature.cs index 9f77eaba4..4e37694c6 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/CorLibTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/CorLibTypeSignature.cs @@ -102,6 +102,49 @@ public override IResolutionScope? Scope /// public override ITypeDefOrRef ToTypeDefOrRef() => Type; + /// + public override TypeSignature GetReducedType() + { + var factory = Module!.CorLibTypeFactory; + return ElementType switch + { + ElementType.I1 or ElementType.U1 => factory.SByte, + ElementType.I2 or ElementType.U2 => factory.Int16, + ElementType.I4 or ElementType.U4 => factory.Int32, + ElementType.I8 or ElementType.U8 => factory.Int64, + ElementType.I or ElementType.U => factory.IntPtr, + _ => base.GetReducedType() + }; + } + + /// + public override TypeSignature GetVerificationType() + { + var factory = Module!.CorLibTypeFactory; + return GetReducedType().ElementType switch + { + ElementType.I1 or ElementType.Boolean => factory.SByte, + ElementType.I2 or ElementType.Char => factory.Int16, + ElementType.I4 => factory.Int32, + ElementType.I8 => factory.Int64, + ElementType.I => factory.IntPtr, + _ => base.GetVerificationType() + }; + } + + /// + public override TypeSignature GetIntermediateType() + { + var factory = Module!.CorLibTypeFactory; + var verificationType = GetVerificationType(); + return verificationType.ElementType switch + { + ElementType.I1 or ElementType.I2 or ElementType.I4 => factory.Int32, + ElementType.R4 or ElementType.R8 => factory.Double, // Technically, this is F. + _ => verificationType + }; + } + /// protected override void WriteContents(in BlobSerializationContext context) => context.Writer.WriteByte((byte) ElementType); diff --git a/src/AsmResolver.DotNet/Signatures/Types/CustomModifierTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/CustomModifierTypeSignature.cs index 12b4554ad..a9ce1e179 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/CustomModifierTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/CustomModifierTypeSignature.cs @@ -66,6 +66,18 @@ public override string Name /// public override bool IsValueType => BaseType.IsValueType; + /// + public override TypeSignature GetReducedType() => BaseType.GetReducedType(); + + /// + public override TypeSignature GetVerificationType() => BaseType.GetVerificationType(); + + /// + public override TypeSignature GetIntermediateType() => BaseType.GetIntermediateType(); + + /// + public override TypeSignature? GetDirectBaseClass() => BaseType.GetDirectBaseClass(); + /// public override TResult AcceptVisitor(ITypeSignatureVisitor visitor) => visitor.VisitCustomModifierType(this); diff --git a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs index 990652f5f..b68537ab6 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs @@ -126,6 +126,36 @@ public override bool IsImportedInModule(ModuleDefinition module) return true; } + /// + public override TypeSignature? GetDirectBaseClass() + { + var genericType = GenericType.Resolve(); + if (genericType is null) + return null; + + // Interfaces have System.Object as direct base class. + if (genericType.IsInterface) + return Module!.CorLibTypeFactory.Object; + + if (genericType.BaseType is not { } baseType) + return null; + + // If the base type is not generic, treat it as a normal TypeDefOrRef. + if (baseType is TypeDefinition or TypeReference) + return baseType.ToTypeSignature(IsValueType); + + // At this point we expect a type specification. + if (baseType is not TypeSpecification { Signature: {} signatureBaseType}) + return null; + + // Ignore any modifiers or pinned signatures. + while (signatureBaseType is PointerTypeSignature or CustomModifierTypeSignature) + signatureBaseType = ((TypeSpecificationSignature) signatureBaseType).BaseType; + + // Substitute any generic type arguments present in the signature. + return signatureBaseType.InstantiateGenericTypes(GenericContext.FromType(this)); + } + /// protected override void WriteContents(in BlobSerializationContext context) { diff --git a/src/AsmResolver.DotNet/Signatures/Types/PinnedTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/PinnedTypeSignature.cs index 8a3cb5f95..1d1f6ed0d 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/PinnedTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/PinnedTypeSignature.cs @@ -26,6 +26,18 @@ public PinnedTypeSignature(TypeSignature baseType) /// public override bool IsValueType => BaseType.IsValueType; + /// + public override TypeSignature GetReducedType() => BaseType.GetReducedType(); + + /// + public override TypeSignature GetVerificationType() => BaseType.GetVerificationType(); + + /// + public override TypeSignature GetIntermediateType() => BaseType.GetIntermediateType(); + + /// + public override TypeSignature? GetDirectBaseClass() => BaseType.GetDirectBaseClass(); + /// public override TResult AcceptVisitor(ITypeSignatureVisitor visitor) => visitor.VisitPinnedType(this); diff --git a/src/AsmResolver.DotNet/Signatures/Types/SzArrayTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/SzArrayTypeSignature.cs index a952b5a0e..aafdef344 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/SzArrayTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/SzArrayTypeSignature.cs @@ -25,6 +25,12 @@ public SzArrayTypeSignature(TypeSignature baseType) /// public override bool IsValueType => false; + /// + public override TypeSignature? GetDirectBaseClass() => Module?.CorLibTypeFactory.CorLibScope + .CreateTypeReference("System", "Array") + .ToTypeSignature(false) + .ImportWith(Module.DefaultImporter); + /// public override TResult AcceptVisitor(ITypeSignatureVisitor visitor) => visitor.VisitSzArrayType(this); diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs index 3fed04061..417f3c5c4 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs @@ -73,6 +73,19 @@ public ITypeDefOrRef Type /// public override ITypeDefOrRef? GetUnderlyingTypeDefOrRef() => Type; + /// + public override TypeSignature? GetDirectBaseClass() + { + var type = Type.Resolve(); + if (type is null) + return null; + + // Interfaces have System.Object as direct base class. + return type.IsInterface + ? Module!.CorLibTypeFactory.Object + : type.BaseType!.ToTypeSignature(false); + } + /// public override TResult AcceptVisitor(ITypeSignatureVisitor visitor) => visitor.VisitTypeDefOrRef(this); diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs index 6bd697c38..101eb8f60 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs @@ -312,6 +312,48 @@ internal static void WriteFieldOrPropType(BlobSerializationContext context, Type /// The base signature. public abstract ITypeDefOrRef? GetUnderlyingTypeDefOrRef(); + /// + /// Obtains the reduced type of the type signature. + /// + /// The reduced type. + /// + /// As per ECMA-335 I.8.7, the reduced type ignores the semantic differences between enumerations and the signed + /// and unsigned integer types; treating these types the same if they have the same number of bits. + /// + public virtual TypeSignature GetReducedType() => this; + + /// + /// Obtains the verification type of the type signature. + /// + /// The verification type. + /// + /// As per ECMA-335 I.8.7, the verification type ignores the semantic differences between enumerations, + /// characters, booleans, the signed and unsigned integer types, and managed pointers to any of these; treating + /// these types the same if they have the same number of bits or point to types with the same number of bits. + /// + public virtual TypeSignature GetVerificationType() => this; + + /// + /// Obtains the intermediate type of the type signature. + /// + /// The intermediate type. + /// + /// As per ECMA-335 I.8.7, intermediate types are a subset of the built-in value types can be represented on the + /// evaluation stack. + /// + public virtual TypeSignature GetIntermediateType() => GetVerificationType(); + + /// + /// Obtains the direct base class of the type signature. + /// + /// The type representing the immediate base class. + /// + /// The direct base class is computed according to the rules defined in ECMA-335 I.8.7, where interfaces + /// will extend , and generic base types will be instantiated with the derived + /// classes type arguments (if any). + /// + public virtual TypeSignature? GetDirectBaseClass() => null; + /// /// Substitutes any generic type parameter in the type signature with the parameters provided by /// the generic context. @@ -338,6 +380,7 @@ public TypeSignature InstantiateGenericTypes(GenericContext context) /// IImportable IImportable.ImportWith(ReferenceImporter importer) => ImportWith(importer); + /// /// Visit the current type signature using the provided visitor. /// diff --git a/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs b/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs index 69f25cbe7..4146cfca9 100644 --- a/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs +++ b/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs @@ -1,5 +1,8 @@ +using System.Linq; using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.Signatures.Types; +using AsmResolver.DotNet.TestCases.Generics; +using AsmResolver.DotNet.TestCases.Types; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using Xunit; @@ -122,5 +125,192 @@ public void GetOptionalModifierTypeFullName() .MakeModifierType(_dummyType, false) .FullName); } + + [Theory] + [InlineData(ElementType.I, ElementType.I)] + [InlineData(ElementType.I1, ElementType.I1)] + [InlineData(ElementType.I2, ElementType.I2)] + [InlineData(ElementType.I4, ElementType.I4)] + [InlineData(ElementType.I8, ElementType.I8)] + [InlineData(ElementType.U, ElementType.I)] + [InlineData(ElementType.U1, ElementType.I1)] + [InlineData(ElementType.U2, ElementType.I2)] + [InlineData(ElementType.U4, ElementType.I4)] + [InlineData(ElementType.U8, ElementType.I8)] + [InlineData(ElementType.String, ElementType.String)] + [InlineData(ElementType.Boolean, ElementType.Boolean)] + [InlineData(ElementType.Char, ElementType.Char)] + public void GetReducedTypeOfPrimitive(ElementType type, ElementType expected) + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + Assert.Equal(expected, module.CorLibTypeFactory.FromElementType(type)!.GetReducedType().ElementType); + } + + [Fact] + public void GetReducedTypeOfNonPrimitive() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var type = module.TopLevelTypes.First(t => t.Name == "Program").ToTypeSignature(false); + Assert.Equal(type, type.GetReducedType()); + } + + [Theory] + [InlineData(ElementType.I, ElementType.I)] + [InlineData(ElementType.I1, ElementType.I1)] + [InlineData(ElementType.I2, ElementType.I2)] + [InlineData(ElementType.I4, ElementType.I4)] + [InlineData(ElementType.I8, ElementType.I8)] + [InlineData(ElementType.U, ElementType.I)] + [InlineData(ElementType.U1, ElementType.I1)] + [InlineData(ElementType.U2, ElementType.I2)] + [InlineData(ElementType.U4, ElementType.I4)] + [InlineData(ElementType.U8, ElementType.I8)] + [InlineData(ElementType.String, ElementType.String)] + [InlineData(ElementType.Boolean, ElementType.I1)] + [InlineData(ElementType.Char, ElementType.I2)] + public void GetVerificationTypeOfPrimitive(ElementType type, ElementType expected) + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + Assert.Equal(expected, module.CorLibTypeFactory.FromElementType(type)!.GetVerificationType().ElementType); + } + + [Theory] + [InlineData(ElementType.I, ElementType.I)] + [InlineData(ElementType.I1, ElementType.I1)] + [InlineData(ElementType.I2, ElementType.I2)] + [InlineData(ElementType.I4, ElementType.I4)] + [InlineData(ElementType.I8, ElementType.I8)] + [InlineData(ElementType.U, ElementType.I)] + [InlineData(ElementType.U1, ElementType.I1)] + [InlineData(ElementType.U2, ElementType.I2)] + [InlineData(ElementType.U4, ElementType.I4)] + [InlineData(ElementType.U8, ElementType.I8)] + [InlineData(ElementType.String, ElementType.String)] + [InlineData(ElementType.Boolean, ElementType.I1)] + [InlineData(ElementType.Char, ElementType.I2)] + public void GetVerificationTypeOfManagedPrimitivePointer(ElementType type, ElementType expected) + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var pointerType = module.CorLibTypeFactory.FromElementType(type)!.MakeByReferenceType(); + var actualType = Assert.IsAssignableFrom(pointerType.GetVerificationType()); + Assert.Equal(expected, actualType.BaseType.ElementType); + } + + [Fact] + public void GetVerificationTypeOfNonPrimitive() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var type = module.TopLevelTypes.First(t => t.Name == "Program").ToTypeSignature(false); + Assert.Equal(type, type.GetVerificationType()); + } + + [Theory] + [InlineData(ElementType.I, ElementType.I)] + [InlineData(ElementType.I1, ElementType.I4)] + [InlineData(ElementType.I2, ElementType.I4)] + [InlineData(ElementType.I4, ElementType.I4)] + [InlineData(ElementType.I8, ElementType.I8)] + [InlineData(ElementType.U, ElementType.I)] + [InlineData(ElementType.U1, ElementType.I4)] + [InlineData(ElementType.U2, ElementType.I4)] + [InlineData(ElementType.U4, ElementType.I4)] + [InlineData(ElementType.U8, ElementType.I8)] + [InlineData(ElementType.String, ElementType.String)] + [InlineData(ElementType.Boolean, ElementType.I4)] + [InlineData(ElementType.Char, ElementType.I4)] + [InlineData(ElementType.R4, ElementType.R8)] // Technically incorrect, as it should be F. + [InlineData(ElementType.R8, ElementType.R8)] + public void GetIntermediateTypeOfPrimitive(ElementType type, ElementType expected) + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + Assert.Equal(expected, module.CorLibTypeFactory.FromElementType(type)!.GetIntermediateType().ElementType); + } + + [Fact] + public void GetIntermediateTypeOfNonPrimitive() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var type = module.TopLevelTypes.First(t => t.Name == "Program").ToTypeSignature(false); + Assert.Equal(type, type.GetIntermediateType()); + } + + [Fact] + public void GetDirectBaseClassOfArrayType() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var signature = module.CorLibTypeFactory.Object.MakeArrayType(3); + Assert.Equal("System.Array", signature.GetDirectBaseClass()!.FullName); + } + + [Fact] + public void GetDirectBaseClassOfSzArrayType() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var signature = module.CorLibTypeFactory.Object.MakeSzArrayType(); + Assert.Equal("System.Array", signature.GetDirectBaseClass()!.FullName); + } + + [Fact] + public void GetDirectBaseClassOfInterfaceType() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var interfaceDefinition = new TypeDefinition("Namespace", "IName", TypeAttributes.Interface); + module.TopLevelTypes.Add(interfaceDefinition); + + var interfaceSignature = interfaceDefinition.ToTypeSignature(false); + + Assert.Equal("System.Object", interfaceSignature.GetDirectBaseClass()!.FullName); + } + + [Fact] + public void GetDirectBaseClassOfNormalType() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var type = module.TopLevelTypes.First(t => t.Name == "Program").ToTypeSignature(); + Assert.Equal("System.Object", type.GetDirectBaseClass()!.FullName); + } + + [Fact] + public void GetDirectBaseClassOfNormalTypeWithBaseType() + { + var module = ModuleDefinition.FromFile(typeof(DerivedClass).Assembly.Location); + var type = module.LookupMember(typeof(DerivedClass).MetadataToken); + Assert.Equal(type.BaseType!.FullName, type.ToTypeSignature().GetDirectBaseClass()!.FullName); + } + + [Fact] + public void GetDirectBaseClassOfGenericTypeInstance() + { + var module = ModuleDefinition.FromFile(typeof(GenericType<,,>).Assembly.Location); + var genericInstanceType = module.LookupMember(typeof(GenericType<,,>).MetadataToken) + .MakeGenericInstanceType( + module.CorLibTypeFactory.Int32, + module.CorLibTypeFactory.String, + module.CorLibTypeFactory.Object); + + Assert.Equal("System.Object", genericInstanceType.GetDirectBaseClass()!.FullName); + } + + [Fact] + public void GetDirectBaseClassOfGenericTypeInstanceWithGenericBaseClass() + { + var module = ModuleDefinition.FromFile(typeof(GenericDerivedType<,>).Assembly.Location); + var genericInstanceType = module.LookupMember(typeof(GenericDerivedType<,>).MetadataToken) + .MakeGenericInstanceType( + module.CorLibTypeFactory.Int32, + module.CorLibTypeFactory.Object); + + var baseClass = Assert.IsAssignableFrom( + genericInstanceType.GetDirectBaseClass()); + + Assert.Equal(typeof(GenericType<,,>).Namespace, baseClass.GenericType.Namespace); + Assert.Equal(typeof(GenericType<,,>).Name, baseClass.GenericType.Name); + Assert.Equal(new[] + { + "System.Int32", + "System.Object", + "System.String" + }, baseClass.TypeArguments.Select(t => t.FullName)); + } } } diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/GenericDerivedType.cs b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/GenericDerivedType.cs new file mode 100644 index 000000000..91d91cc10 --- /dev/null +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/GenericDerivedType.cs @@ -0,0 +1,6 @@ +namespace AsmResolver.DotNet.TestCases.Generics +{ + public class GenericDerivedType : GenericType + { + } +} From 8d27fb49061335d6655e6b092ef21006011332de Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 15 Mar 2023 13:30:02 +0100 Subject: [PATCH 03/10] Add TypeSignature::GetUnderlyingType. --- .../Signatures/Types/TypeDefOrRefSignature.cs | 20 +++++++++++++++++++ .../Signatures/Types/TypeSignature.cs | 16 +++++++++++++++ .../Signatures/TypeSignatureTest.cs | 11 ++++++++++ .../Int32Enum.cs | 9 +++++++++ .../Int64Enum.cs | 9 +++++++++ 5 files changed, 65 insertions(+) create mode 100644 test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Types/Int32Enum.cs create mode 100644 test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Types/Int64Enum.cs diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs index 417f3c5c4..95acc980e 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs @@ -73,6 +73,26 @@ public ITypeDefOrRef Type /// public override ITypeDefOrRef? GetUnderlyingTypeDefOrRef() => Type; + /// + public override TypeSignature GetUnderlyingType() + { + var type = Type.Resolve(); + + if (type is {IsEnum: true}) + return type.GetEnumUnderlyingType() ?? this; + + return this; + } + + /// + public override TypeSignature GetReducedType() + { + var underlyingType = GetUnderlyingType(); + return !ReferenceEquals(underlyingType, this) + ? underlyingType.GetReducedType() + : this; + } + /// public override TypeSignature? GetDirectBaseClass() { diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs index 101eb8f60..bf0b2a9e7 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs @@ -310,8 +310,24 @@ internal static void WriteFieldOrPropType(BlobSerializationContext context, Type /// Gets the underlying base type signature, without any extra adornments. /// /// The base signature. + /// + /// This is not to be confused with , which may resolve enum types to their + /// underlying type representation. + /// public abstract ITypeDefOrRef? GetUnderlyingTypeDefOrRef(); + /// + /// Obtains the underlying type of the type signature. + /// + /// The underlying type. + /// + /// This method computes the underlying type as per ECMA-335 I.8.7, and may therefore attempt to resolve + /// assemblies to determine whether the type is an enum or not. It should not be confused with + /// , which merely obtains the instance + /// behind the type signature. + /// + public virtual TypeSignature GetUnderlyingType() => this; + /// /// Obtains the reduced type of the type signature. /// diff --git a/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs b/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs index 4146cfca9..c0e8fdabc 100644 --- a/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs +++ b/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.Signatures.Types; @@ -146,6 +147,16 @@ public void GetReducedTypeOfPrimitive(ElementType type, ElementType expected) Assert.Equal(expected, module.CorLibTypeFactory.FromElementType(type)!.GetReducedType().ElementType); } + [Theory] + [InlineData(typeof(Int32Enum), ElementType.I4)] + [InlineData(typeof(Int64Enum), ElementType.I8)] + public void GetReducedTypeOfEnum(Type type, ElementType expected) + { + var module = ModuleDefinition.FromFile(type.Assembly.Location); + var signature = module.LookupMember(type.MetadataToken).ToTypeSignature(); + Assert.Equal(expected, signature.GetReducedType().ElementType); + } + [Fact] public void GetReducedTypeOfNonPrimitive() { diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Types/Int32Enum.cs b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Types/Int32Enum.cs new file mode 100644 index 000000000..c405e0c9e --- /dev/null +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Types/Int32Enum.cs @@ -0,0 +1,9 @@ +namespace AsmResolver.DotNet.TestCases.Types +{ + public enum Int32Enum : int + { + Member1 = 1, + Member2 = 2, + Member3 = 3, + } +} diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Types/Int64Enum.cs b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Types/Int64Enum.cs new file mode 100644 index 000000000..0b129abf3 --- /dev/null +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Types/Int64Enum.cs @@ -0,0 +1,9 @@ +namespace AsmResolver.DotNet.TestCases.Types +{ + public enum Int64Enum : long + { + Member1 = 1, + Member2 = 2, + Member3 = 3, + } +} From 5ae06dcd961a440fa1d0bdb8118c428f95e9836a Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 15 Mar 2023 15:00:52 +0100 Subject: [PATCH 04/10] Add ArrayBaseTypeSignature and TypeSignature::IsCompatibleWith and IsAssignableTo. --- .../Types/ArrayBaseTypeSignature.cs | 63 ++++++ .../Signatures/Types/ArrayTypeSignature.cs | 8 +- .../Types/FunctionPointerTypeSignature.cs | 24 ++- .../Types/GenericInstanceTypeSignature.cs | 53 ++++- .../Signatures/Types/PointerTypeSignature.cs | 14 ++ .../Signatures/Types/SzArrayTypeSignature.cs | 12 +- .../Signatures/Types/TypeDefOrRefSignature.cs | 24 +++ .../Signatures/Types/TypeSignature.cs | 74 +++++++ .../Signatures/TypeSignatureTest.cs | 196 ++++++++++++++++++ 9 files changed, 464 insertions(+), 4 deletions(-) create mode 100644 src/AsmResolver.DotNet/Signatures/Types/ArrayBaseTypeSignature.cs diff --git a/src/AsmResolver.DotNet/Signatures/Types/ArrayBaseTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/ArrayBaseTypeSignature.cs new file mode 100644 index 000000000..c5902afe2 --- /dev/null +++ b/src/AsmResolver.DotNet/Signatures/Types/ArrayBaseTypeSignature.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; + +namespace AsmResolver.DotNet.Signatures.Types +{ + /// + /// Represents a type signature representing an array. + /// + public abstract class ArrayBaseTypeSignature : TypeSpecificationSignature + { + /// + /// Initializes an array type signature. + /// + /// The element type of the array. + protected ArrayBaseTypeSignature(TypeSignature baseType) + : base(baseType) + { + } + + /// + /// Gets the number of dimensions this array defines. + /// + public abstract int Rank + { + get; + } + + /// + /// Obtains the dimensions this array defines. + /// + /// The dimensions. + public abstract IEnumerable GetDimensions(); + + /// + protected override bool IsDirectlyCompatibleWith(TypeSignature other) + { + if (base.IsDirectlyCompatibleWith(other)) + return true; + + TypeSignature? elementType = null; + if (other is ArrayBaseTypeSignature otherArrayType && Rank == otherArrayType.Rank) + { + // Arrays are only compatible if they have the same rank. + elementType = otherArrayType.BaseType; + } + else if (Rank == 1 + && other is GenericInstanceTypeSignature genericInstanceType + && genericInstanceType.GenericType.IsTypeOf("System.Collections.Generic", "IList`1")) + { + // Arrays are also compatible with IList if they've only one dimension. + elementType = genericInstanceType.TypeArguments[0]; + } + + if (elementType is null) + return false; + + var v = BaseType.GetUnderlyingType(); + var w = elementType.GetUnderlyingType(); + + return SignatureComparer.Default.Equals(v.GetReducedType(), w.GetReducedType()) + || v.IsCompatibleWith(w); + } + } +} diff --git a/src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs index a77e9bf97..d1b0d5659 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs @@ -13,7 +13,7 @@ namespace AsmResolver.DotNet.Signatures.Types /// /// For simple single-dimension arrays, use instead. /// - public class ArrayTypeSignature : TypeSpecificationSignature + public class ArrayTypeSignature : ArrayBaseTypeSignature { /// /// Creates a new array type signature. @@ -69,6 +69,12 @@ public IList Dimensions get; } + /// + public override int Rank => Dimensions.Count; + + /// + public override IEnumerable GetDimensions() => Dimensions; + internal new static ArrayTypeSignature FromReader(ref BlobReaderContext context, ref BinaryStreamReader reader) { var signature = new ArrayTypeSignature(TypeSignature.FromReader(ref context, ref reader)); diff --git a/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs index b5ebe0c15..251632a3c 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; namespace AsmResolver.DotNet.Signatures.Types @@ -52,6 +51,29 @@ public MethodSignature Signature /// public override bool IsImportedInModule(ModuleDefinition module) => Signature.IsImportedInModule(module); + /// + protected override bool IsDirectlyCompatibleWith(TypeSignature other) + { + if (base.IsDirectlyCompatibleWith(other)) + return true; + + if (other is not FunctionPointerTypeSignature {Signature: { } otherSignature} + || Signature.GenericParameterCount != otherSignature.GenericParameterCount + || Signature.ParameterTypes.Count != otherSignature.ParameterTypes.Count + || !Signature.ReturnType.IsAssignableTo(otherSignature.ReturnType)) + { + return false; + } + + for (int i = 0; i < Signature.ParameterTypes.Count; i++) + { + if (!Signature.ParameterTypes[i].IsAssignableTo(otherSignature.ParameterTypes[i])) + return false; + } + + return true; + } + /// protected override void WriteContents(in BlobSerializationContext context) { diff --git a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs index b68537ab6..7952438f3 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using AsmResolver.IO; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; @@ -156,6 +157,56 @@ public override bool IsImportedInModule(ModuleDefinition module) return signatureBaseType.InstantiateGenericTypes(GenericContext.FromType(this)); } + /// + public override IEnumerable GetDirectlyImplementedInterfaces() + { + var type = GenericType.Resolve(); + if (type is null) + return Enumerable.Empty(); + + var context = GenericContext.FromType(this); + return type.Interfaces.Select(i => i.Interface!.ToTypeSignature(false).InstantiateGenericTypes(context)); + } + + /// + protected override bool IsDirectlyCompatibleWith(TypeSignature other) + { + if (base.IsDirectlyCompatibleWith(other)) + return true; + + if (other is not GenericInstanceTypeSignature otherGenericInstance + || otherGenericInstance.TypeArguments.Count != TypeArguments.Count + || !SignatureComparer.Default.Equals(GenericType, otherGenericInstance.GenericType)) + { + return false; + } + + // If resolution fails, assume no parameter variance. + var genericType = GenericType.Resolve(); + + // Check that every type argument is compatible with each other. + for (int i = 0; i < TypeArguments.Count; i++) + { + var variance = genericType?.GenericParameters[i].Attributes & GenericParameterAttributes.VarianceMask; + + bool argumentIsCompatible = variance switch + { + GenericParameterAttributes.NonVariant => + SignatureComparer.Default.Equals(TypeArguments[i], otherGenericInstance.TypeArguments[i]), + GenericParameterAttributes.Covariant => + TypeArguments[i].IsCompatibleWith(otherGenericInstance.TypeArguments[i]), + GenericParameterAttributes.Contravariant => + otherGenericInstance.TypeArguments[i].IsCompatibleWith(TypeArguments[i]), + _ => throw new ArgumentOutOfRangeException() + }; + + if (!argumentIsCompatible) + return false; + } + + return true; + } + /// protected override void WriteContents(in BlobSerializationContext context) { diff --git a/src/AsmResolver.DotNet/Signatures/Types/PointerTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/PointerTypeSignature.cs index 382c6cfb5..6bbf3e83f 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/PointerTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/PointerTypeSignature.cs @@ -25,6 +25,20 @@ public PointerTypeSignature(TypeSignature baseType) /// public override bool IsValueType => false; + /// + protected override bool IsDirectlyCompatibleWith(TypeSignature other) + { + if (base.IsDirectlyCompatibleWith(other)) + return true; + + if (other is not PointerTypeSignature otherPointer) + return false; + + var v = BaseType.GetVerificationType(); + var w = otherPointer.BaseType.GetVerificationType(); + return SignatureComparer.Default.Equals(v, w); + } + /// public override TResult AcceptVisitor(ITypeSignatureVisitor visitor) => visitor.VisitPointerType(this); diff --git a/src/AsmResolver.DotNet/Signatures/Types/SzArrayTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/SzArrayTypeSignature.cs index aafdef344..1c5fb2950 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/SzArrayTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/SzArrayTypeSignature.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; namespace AsmResolver.DotNet.Signatures.Types @@ -5,8 +6,10 @@ namespace AsmResolver.DotNet.Signatures.Types /// /// Represents a type signature describing a single dimension array with 0 as a lower bound. /// - public class SzArrayTypeSignature : TypeSpecificationSignature + public class SzArrayTypeSignature : ArrayBaseTypeSignature { + private static readonly ArrayDimension[] SzDimensions = { new() }; + /// /// Creates a new single-dimension array signature with 0 as a lower bound. /// @@ -25,12 +28,19 @@ public SzArrayTypeSignature(TypeSignature baseType) /// public override bool IsValueType => false; + /// + public override int Rank => 1; + + /// + public override IEnumerable GetDimensions() => SzDimensions; + /// public override TypeSignature? GetDirectBaseClass() => Module?.CorLibTypeFactory.CorLibScope .CreateTypeReference("System", "Array") .ToTypeSignature(false) .ImportWith(Module.DefaultImporter); + /// public override TResult AcceptVisitor(ITypeSignatureVisitor visitor) => visitor.VisitSzArrayType(this); diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs index 95acc980e..94a55fa9d 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; +using System.Linq; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; namespace AsmResolver.DotNet.Signatures.Types @@ -106,6 +108,28 @@ public override TypeSignature GetReducedType() : type.BaseType!.ToTypeSignature(false); } + /// + public override IEnumerable GetDirectlyImplementedInterfaces() + { + var type = Type.Resolve(); + if (type is null) + return Enumerable.Empty(); + + return type.Interfaces.Select(i => i.Interface!.ToTypeSignature(false)); + } + + /// + protected override bool IsDirectlyCompatibleWith(TypeSignature other) + { + if (base.IsDirectlyCompatibleWith(other)) + return true; + if (IsValueType) + return false; + + return SignatureComparer.Default.Equals(GetDirectBaseClass(), other) + || GetDirectlyImplementedInterfaces().Contains(other, SignatureComparer.Default); + } + /// public override TResult AcceptVisitor(ITypeSignatureVisitor visitor) => visitor.VisitTypeDefOrRef(this); diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs index bf0b2a9e7..1efe3a87a 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Reflection; using AsmResolver.DotNet.Signatures.Types.Parsing; using AsmResolver.IO; @@ -370,6 +372,78 @@ internal static void WriteFieldOrPropType(BlobSerializationContext context, Type /// public virtual TypeSignature? GetDirectBaseClass() => null; + /// + /// Obtains the interfaces that are directly implemented by the type. + /// + /// The interfaces. + /// + /// The result set of types is computed according to the rules defined in ECMA-335 I.8.7, where interfaces + /// will extend , and generic interfaces will be instantiated with the derived + /// classes type arguments (if any). + /// + public virtual IEnumerable GetDirectlyImplementedInterfaces() => Enumerable.Empty(); + + /// + /// Determines whether the current type is directly compatible with the provided type. + /// + /// The other type. + /// true if the types are directly compatible, false otherwise. + /// + /// Type compatibility is determined according to the rules in ECMA-335 I.8.7.1., excluding the transitivity + /// rule. + /// + protected virtual bool IsDirectlyCompatibleWith(TypeSignature other) + { + return SignatureComparer.Default.Equals(this, other); + } + + /// + /// Determines whether the current type is compatible with the provided type. + /// + /// The other type. + /// true if the type is compatible with , false otherwise. + /// + /// Type compatibility is determined according to the rules in ECMA-335 I.8.7.1. + /// + public bool IsCompatibleWith(TypeSignature other) + { + var current = this; + + // Achieve the transitivity rule by moving up the type hierarchy iteratively. + while (current is not null) + { + if (current.IsDirectlyCompatibleWith(other)) + return true; + + current = current.GetDirectBaseClass(); + } + + return false; + } + + /// + /// Determines whether the current type is assignable to the provided type. + /// + /// The other type. + /// true if the type is assignable to , false otherwise. + /// + /// Type compatibility is determined according to the rules in ECMA-335 I.8.7.3. + /// + public bool IsAssignableTo(TypeSignature other) + { + var intermediateType1 = GetIntermediateType(); + var intermediateType2 = other.GetIntermediateType(); + + if (SignatureComparer.Default.Equals(intermediateType1, intermediateType2) + || intermediateType1.ElementType == ElementType.I && intermediateType2.ElementType == ElementType.I4 + || intermediateType1.ElementType == ElementType.I4 && intermediateType2.ElementType == ElementType.I) + { + return true; + } + + return IsCompatibleWith(other); + } + /// /// Substitutes any generic type parameter in the type signature with the parameters provided by /// the generic context. diff --git a/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs b/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs index c0e8fdabc..72c4e8531 100644 --- a/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs +++ b/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs @@ -323,5 +323,201 @@ public void GetDirectBaseClassOfGenericTypeInstanceWithGenericBaseClass() "System.String" }, baseClass.TypeArguments.Select(t => t.FullName)); } + + [Theory] + [InlineData(ElementType.I)] + [InlineData(ElementType.I1)] + [InlineData(ElementType.I2)] + [InlineData(ElementType.I4)] + [InlineData(ElementType.I8)] + [InlineData(ElementType.U)] + [InlineData(ElementType.U1)] + [InlineData(ElementType.U2)] + [InlineData(ElementType.U4)] + [InlineData(ElementType.U8)] + [InlineData(ElementType.R4)] + [InlineData(ElementType.R8)] + [InlineData(ElementType.String)] + [InlineData(ElementType.Boolean)] + [InlineData(ElementType.Char)] + public void IsCompatibleWithIdenticalPrimitiveTypes(ElementType elementType) + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var type = module.CorLibTypeFactory.FromElementType(elementType)!; + Assert.True(type.IsCompatibleWith(type)); + } + + [Theory] + [InlineData(typeof(AbstractClass))] + [InlineData(typeof(DerivedClass))] + public void IsCompatibleWithIdenticalUserTypes(Type type) + { + var module = ModuleDefinition.FromFile(type.Assembly.Location); + var signature = module.LookupMember(type.MetadataToken).ToTypeSignature(); + Assert.True(signature.IsCompatibleWith(signature)); + } + + [Theory] + [InlineData(typeof(DerivedClass), typeof(AbstractClass), true)] + [InlineData(typeof(DerivedDerivedClass), typeof(DerivedClass), true)] + [InlineData(typeof(DerivedDerivedClass), typeof(AbstractClass), true)] + [InlineData(typeof(AbstractClass), typeof(DerivedClass), false)] + [InlineData(typeof(AbstractClass), typeof(DerivedDerivedClass), false)] + public void IsCompatibleWithBaseClass(Type derivedType, Type baseType, bool expected) + { + var module = ModuleDefinition.FromFile(derivedType.Assembly.Location); + var derivedSignature = module.LookupMember(derivedType.MetadataToken).ToTypeSignature(); + var abstractSignature = module.LookupMember(baseType.MetadataToken).ToTypeSignature(); + Assert.Equal(expected, derivedSignature.IsCompatibleWith(abstractSignature)); + } + + [Theory] + [InlineData(typeof(InterfaceImplementations), typeof(IInterface1), true)] + [InlineData(typeof(InterfaceImplementations), typeof(IInterface2), true)] + [InlineData(typeof(InterfaceImplementations), typeof(IInterface3), false)] + [InlineData(typeof(InterfaceImplementations), typeof(IInterface4), false)] + [InlineData(typeof(DerivedInterfaceImplementations), typeof(IInterface1), true)] + [InlineData(typeof(DerivedInterfaceImplementations), typeof(IInterface2), true)] + [InlineData(typeof(DerivedInterfaceImplementations), typeof(IInterface3), true)] + [InlineData(typeof(DerivedInterfaceImplementations), typeof(IInterface4), false)] + [InlineData(typeof(IInterface1), typeof(InterfaceImplementations), false)] + [InlineData(typeof(IInterface2), typeof(InterfaceImplementations), false)] + [InlineData(typeof(IInterface3), typeof(DerivedInterfaceImplementations), false)] + [InlineData(typeof(IInterface4), typeof(DerivedInterfaceImplementations), false)] + public void IsCompatibleWithInterface(Type derivedType, Type interfaceType, bool expected) + { + var module = ModuleDefinition.FromFile(typeof(DerivedClass).Assembly.Location); + var derivedSignature = module.LookupMember(derivedType.MetadataToken).ToTypeSignature(); + var interfaceSignature = module.LookupMember(interfaceType.MetadataToken).ToTypeSignature(); + Assert.Equal(expected, derivedSignature.IsCompatibleWith(interfaceSignature)); + } + + [Theory] + [InlineData(ElementType.I1, ElementType.I1, true)] + [InlineData(ElementType.U1, ElementType.I1, true)] + [InlineData(ElementType.I1, ElementType.U1, true)] + [InlineData(ElementType.U1, ElementType.U1, true)] + [InlineData(ElementType.I1, ElementType.U2, false)] + [InlineData(ElementType.U2, ElementType.U1, false)] + [InlineData(ElementType.I4, ElementType.I4, true)] + [InlineData(ElementType.I4, ElementType.U4, true)] + [InlineData(ElementType.U4, ElementType.I4, true)] + public void IsCompatibleWithArray(ElementType elementType1, ElementType elementType2, bool expected) + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var type1 = module.CorLibTypeFactory.FromElementType(elementType1)!.MakeSzArrayType(); + var type2 = module.CorLibTypeFactory.FromElementType(elementType2)!.MakeSzArrayType(); + Assert.Equal(expected, type1.IsCompatibleWith(type2)); + } + + [Theory] + [InlineData(ElementType.I1, ElementType.I1, true)] + [InlineData(ElementType.U1, ElementType.I1, true)] + [InlineData(ElementType.I1, ElementType.U1, true)] + [InlineData(ElementType.U1, ElementType.U1, true)] + [InlineData(ElementType.I1, ElementType.U2, false)] + [InlineData(ElementType.U2, ElementType.U1, false)] + [InlineData(ElementType.I4, ElementType.I4, true)] + [InlineData(ElementType.I4, ElementType.U4, true)] + [InlineData(ElementType.U4, ElementType.I4, true)] + public void IsCompatibleWithArrayAndIList(ElementType elementType1, ElementType elementType2, bool expected) + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + + var type1 = module.CorLibTypeFactory.FromElementType(elementType1)!.MakeSzArrayType(); + var type2 = module.CorLibTypeFactory.CorLibScope + .CreateTypeReference("System.Collections.Generic", "IList`1") + .ToTypeSignature(false) + .MakeGenericInstanceType(module.CorLibTypeFactory.FromElementType(elementType2)!); + + Assert.Equal(expected, type1.IsCompatibleWith(type2)); + } + + [Fact] + public void IsCompatibleWithGenericInstanceAndObject() + { + var module = ModuleDefinition.FromFile(typeof(GenericType<,,>).Assembly.Location); + + var type1 = module + .LookupMember(typeof(GenericType<,,>).MetadataToken) + .ToTypeSignature() + .MakeGenericInstanceType( + module.CorLibTypeFactory.Int32, + module.CorLibTypeFactory.Object, + module.CorLibTypeFactory.String); + + Assert.True(type1.IsCompatibleWith(type1)); + Assert.True(type1.IsCompatibleWith(module.CorLibTypeFactory.Object)); + } + + [Fact] + public void IsCompatibleWithGenericInstance() + { + var module = ModuleDefinition.FromFile(typeof(GenericDerivedType<,>).Assembly.Location); + + var type1 = module + .LookupMember(typeof(GenericDerivedType<,>).MetadataToken) + .ToTypeSignature() + .MakeGenericInstanceType( + module.CorLibTypeFactory.Int32, + module.CorLibTypeFactory.Object); + + var type2 = module + .LookupMember(typeof(GenericType<,,>).MetadataToken) + .ToTypeSignature() + .MakeGenericInstanceType( + module.CorLibTypeFactory.Int32, + module.CorLibTypeFactory.Object, + module.CorLibTypeFactory.String); + + var type3 = module + .LookupMember(typeof(GenericType<,,>).MetadataToken) + .ToTypeSignature() + .MakeGenericInstanceType( + module.CorLibTypeFactory.Object, + module.CorLibTypeFactory.Int32, + module.CorLibTypeFactory.String); + + Assert.True(type1.IsCompatibleWith(type2)); + Assert.False(type1.IsCompatibleWith(type3)); + } + + [Theory] + [InlineData(ElementType.I1, ElementType.I1, true)] + [InlineData(ElementType.U1, ElementType.I1, true)] + [InlineData(ElementType.I1, ElementType.U1, true)] + [InlineData(ElementType.I1, ElementType.Boolean, true)] + [InlineData(ElementType.I2, ElementType.Char, true)] + [InlineData(ElementType.I4, ElementType.Boolean, false)] + [InlineData(ElementType.I1, ElementType.U2, false)] + public void IsCompatibleWithPointers(ElementType elementType1, ElementType elementType2, bool expected) + { + var module = ModuleDefinition.FromFile(typeof(GenericDerivedType<,>).Assembly.Location); + + var type1 = module.CorLibTypeFactory.FromElementType(elementType1)!.MakePointerType(); + var type2 = module.CorLibTypeFactory.FromElementType(elementType2)!.MakePointerType(); + + Assert.Equal(expected, type1.IsCompatibleWith(type2)); + } + + [Theory] + [InlineData(ElementType.I1, ElementType.I4, true)] + [InlineData(ElementType.I2, ElementType.I4, true)] + [InlineData(ElementType.I4, ElementType.I4, true)] + [InlineData(ElementType.I8, ElementType.I4, false)] + [InlineData(ElementType.I4, ElementType.I1, true)] + [InlineData(ElementType.I4, ElementType.I2, true)] + [InlineData(ElementType.I4, ElementType.I8, false)] + [InlineData(ElementType.I, ElementType.I4, true)] + [InlineData(ElementType.I4, ElementType.I, true)] + public void IsAssignablePrimitives(ElementType elementType1, ElementType elementType2, bool expected) + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + + var type1 = module.CorLibTypeFactory.FromElementType(elementType1)!; + var type2 = module.CorLibTypeFactory.FromElementType(elementType2)!; + + Assert.Equal(expected, type1.IsAssignableTo(type2)); + } } } From e839a83f54833f943e8dcef17e5c3f0640c9368e Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 15 Mar 2023 15:42:56 +0100 Subject: [PATCH 05/10] Add docs for the new type comparison APIs. --- docs/dotnet/type-signatures.rst | 67 +++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/docs/dotnet/type-signatures.rst b/docs/dotnet/type-signatures.rst index 34d5fcff1..219d9555a 100644 --- a/docs/dotnet/type-signatures.rst +++ b/docs/dotnet/type-signatures.rst @@ -219,3 +219,70 @@ Below an overview of all factory shortcut methods: +-------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------+ | ``MakeGenericInstanceType(TypeSignature[] typeArguments)`` | Wraps the type in a new ``GenericInstanceTypeSignature`` with the provided type arguments. | +-------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------+ + + + +Comparing Type Signatures +------------------------- + +Type signatures can be tested for semantic equivalence using the ``SignatureComparer`` class. +Most use-cases of this class will not require any customization. +In these cases, the default ``SignatureComparer`` can be used: + +.. code-block:: csharp + + var comparer = SignatureComparer.Default; + + +However, if you wish to configure the comparer (e.g., for relaxing some of the declaring assembly version comparison rules), it is possible to create a new instance instead: + +.. code-block:: csharp + + var comparer = new SignatureComparer(SignatureComparisonFlags.AllowNewerVersions); + + +Once a comparer is obtained, we can test for type equality using any of the overloaded ``Equals`` methods: + +.. code-block:: csharp + + TypeSignature type1 = ...; + TypeSignature type2 = ...; + + if (comparer.Equals(type1, type2)) + { + // type1 and type2 are semantically equivalent. + } + + +The ``SignatureComparer`` class implements various instances of the ``IEqualityComparer`` interface, and as such, it can be used as a comparer for dictionaries and related types: + +.. code-block:: csharp + + var dictionary = new Dictionary(comparer); + + +.. note:: + + The ``SignatureComparer`` class also implements equality comparers for other kinds of metadata, such as field and method descriptors and their signatures. + + +In some cases, however, exact type equivalence is too strict of a test. +Since .NET facilitates an object oriented environment, many types will inherit or derive from each other, making it difficult to pinpoint exactly which types we would need to compare to test whether two types are compatible with each other. + +Section I.8.7 of the ECMA-335 specification defines a set of rules that dictate whether values of a certain type are compatible with or assignable to variables of another type. +These rules are implemented in AsmResolver using the ``IsCompatibleWith`` and ``IsAssignableTo`` methods: + +.. code-block:: csharp + + if (comparer.IsCompatibleWith(type1, type2)) + { + // type1 can be converted to type2. + } + + +.. code-block:: csharp + + if (comparer.IsAssignableTo(type1, type2)) + { + // Values of type1 can be assigned to variables of type2. + } From ac4129eccc0834208e34e024229c423947444ff5 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 15 Mar 2023 16:57:31 +0100 Subject: [PATCH 06/10] Simplify, fix code typos in docs. --- docs/dotnet/type-signatures.rst | 4 ++-- .../Signatures/Types/ArrayBaseTypeSignature.cs | 3 +++ src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs | 3 --- .../Signatures/Types/SzArrayTypeSignature.cs | 3 --- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/docs/dotnet/type-signatures.rst b/docs/dotnet/type-signatures.rst index 219d9555a..e98940e72 100644 --- a/docs/dotnet/type-signatures.rst +++ b/docs/dotnet/type-signatures.rst @@ -274,7 +274,7 @@ These rules are implemented in AsmResolver using the ``IsCompatibleWith`` and `` .. code-block:: csharp - if (comparer.IsCompatibleWith(type1, type2)) + if (type1.IsCompatibleWith(type2)) { // type1 can be converted to type2. } @@ -282,7 +282,7 @@ These rules are implemented in AsmResolver using the ``IsCompatibleWith`` and `` .. code-block:: csharp - if (comparer.IsAssignableTo(type1, type2)) + if (type1.IsAssignableTo(type2)) { // Values of type1 can be assigned to variables of type2. } diff --git a/src/AsmResolver.DotNet/Signatures/Types/ArrayBaseTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/ArrayBaseTypeSignature.cs index c5902afe2..ee77d817b 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/ArrayBaseTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/ArrayBaseTypeSignature.cs @@ -16,6 +16,9 @@ protected ArrayBaseTypeSignature(TypeSignature baseType) { } + /// + public override bool IsValueType => false; + /// /// Gets the number of dimensions this array defines. /// diff --git a/src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs index d1b0d5659..056f943f6 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs @@ -58,9 +58,6 @@ public ArrayTypeSignature(TypeSignature baseType, params ArrayDimension[] dimens /// public override string Name => $"{BaseType.Name ?? NullTypeToString}{GetDimensionsString()}"; - /// - public override bool IsValueType => false; - /// /// Gets a collection of dimensions. /// diff --git a/src/AsmResolver.DotNet/Signatures/Types/SzArrayTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/SzArrayTypeSignature.cs index 1c5fb2950..c0286ccf9 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/SzArrayTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/SzArrayTypeSignature.cs @@ -25,9 +25,6 @@ public SzArrayTypeSignature(TypeSignature baseType) /// public override string Name => $"{BaseType.Name ?? NullTypeToString}[]"; - /// - public override bool IsValueType => false; - /// public override int Rank => 1; From d6da1dccf0162bb61dc7e85b742b1f429136a5e6 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 18 Mar 2023 10:53:04 +0100 Subject: [PATCH 07/10] Add StripModifiers, ignore modifiers on compatibility checks. --- .../Types/CustomModifierTypeSignature.cs | 3 + .../Types/GenericInstanceTypeSignature.cs | 16 ++-- .../Signatures/Types/PinnedTypeSignature.cs | 3 + .../Signatures/Types/TypeDefOrRefSignature.cs | 2 +- .../Signatures/Types/TypeSignature.cs | 17 ++++- .../Signatures/TypeSignatureTest.cs | 76 +++++++++++++++++++ 6 files changed, 103 insertions(+), 14 deletions(-) diff --git a/src/AsmResolver.DotNet/Signatures/Types/CustomModifierTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/CustomModifierTypeSignature.cs index a9ce1e179..051321466 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/CustomModifierTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/CustomModifierTypeSignature.cs @@ -78,6 +78,9 @@ public override string Name /// public override TypeSignature? GetDirectBaseClass() => BaseType.GetDirectBaseClass(); + /// + public override TypeSignature StripModifiers() => BaseType.StripModifiers(); + /// public override TResult AcceptVisitor(ITypeSignatureVisitor visitor) => visitor.VisitCustomModifierType(this); diff --git a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs index 7952438f3..b5859bad7 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs @@ -145,16 +145,10 @@ public override bool IsImportedInModule(ModuleDefinition module) if (baseType is TypeDefinition or TypeReference) return baseType.ToTypeSignature(IsValueType); - // At this point we expect a type specification. - if (baseType is not TypeSpecification { Signature: {} signatureBaseType}) - return null; - - // Ignore any modifiers or pinned signatures. - while (signatureBaseType is PointerTypeSignature or CustomModifierTypeSignature) - signatureBaseType = ((TypeSpecificationSignature) signatureBaseType).BaseType; - - // Substitute any generic type arguments present in the signature. - return signatureBaseType.InstantiateGenericTypes(GenericContext.FromType(this)); + // At this point we expect a type specification. Substitute any generic type arguments present in it. + return baseType is TypeSpecification { Signature: { } signatureBaseType } + ? signatureBaseType.StripModifiers().InstantiateGenericTypes(GenericContext.FromType(this)) + : null; } /// @@ -192,7 +186,7 @@ protected override bool IsDirectlyCompatibleWith(TypeSignature other) bool argumentIsCompatible = variance switch { GenericParameterAttributes.NonVariant => - SignatureComparer.Default.Equals(TypeArguments[i], otherGenericInstance.TypeArguments[i]), + SignatureComparer.Default.Equals(TypeArguments[i].StripModifiers(), otherGenericInstance.TypeArguments[i].StripModifiers()), GenericParameterAttributes.Covariant => TypeArguments[i].IsCompatibleWith(otherGenericInstance.TypeArguments[i]), GenericParameterAttributes.Contravariant => diff --git a/src/AsmResolver.DotNet/Signatures/Types/PinnedTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/PinnedTypeSignature.cs index 1d1f6ed0d..d558428ea 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/PinnedTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/PinnedTypeSignature.cs @@ -38,6 +38,9 @@ public PinnedTypeSignature(TypeSignature baseType) /// public override TypeSignature? GetDirectBaseClass() => BaseType.GetDirectBaseClass(); + /// + public override TypeSignature StripModifiers() => BaseType.StripModifiers(); + /// public override TResult AcceptVisitor(ITypeSignatureVisitor visitor) => visitor.VisitPinnedType(this); diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs index 94a55fa9d..7a75a917a 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs @@ -105,7 +105,7 @@ public override TypeSignature GetReducedType() // Interfaces have System.Object as direct base class. return type.IsInterface ? Module!.CorLibTypeFactory.Object - : type.BaseType!.ToTypeSignature(false); + : type.BaseType!.ToTypeSignature(false).StripModifiers(); } /// diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs index 1efe3a87a..d86aad63a 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs @@ -383,6 +383,18 @@ internal static void WriteFieldOrPropType(BlobSerializationContext context, Type /// public virtual IEnumerable GetDirectlyImplementedInterfaces() => Enumerable.Empty(); + /// + /// Strips any top-level custom type modifier and pinned type annotations from the signature. + /// + /// The stripped type signature. + /// + /// This method does not necessarily recursively strip away every modifier type from the signature, nor does it + /// allocate new type signatures or change existing ones. It only traverses the type signature until a non-modifier + /// or pinned type is encountered. Annotations that are embedded in the type signature (e.g., as a type argument + /// of a generic instance type), will not be automatically removed. + /// + public virtual TypeSignature StripModifiers() => this; + /// /// Determines whether the current type is directly compatible with the provided type. /// @@ -407,7 +419,8 @@ protected virtual bool IsDirectlyCompatibleWith(TypeSignature other) /// public bool IsCompatibleWith(TypeSignature other) { - var current = this; + var current = StripModifiers(); + other = other.StripModifiers(); // Achieve the transitivity rule by moving up the type hierarchy iteratively. while (current is not null) @@ -415,7 +428,7 @@ public bool IsCompatibleWith(TypeSignature other) if (current.IsDirectlyCompatibleWith(other)) return true; - current = current.GetDirectBaseClass(); + current = current.GetDirectBaseClass()?.StripModifiers(); } return false; diff --git a/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs b/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs index 72c4e8531..19ea59466 100644 --- a/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs +++ b/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs @@ -127,6 +127,33 @@ public void GetOptionalModifierTypeFullName() .FullName); } + [Fact] + public void StripModifiersPinnedType() + { + var type = _dummyType.ToTypeSignature(); + Assert.Equal(type, type.MakePinnedType().StripModifiers(), SignatureComparer.Default); + } + + [Fact] + public void StripModifiersCustomModifierType() + { + var type = _dummyType.ToTypeSignature(); + Assert.Equal(type, type.MakeModifierType(_dummyType, false).StripModifiers(), SignatureComparer.Default); + } + + [Fact] + public void StripMultipleModifiers() + { + var type = _dummyType.ToTypeSignature(); + Assert.Equal(type, + type + .MakeModifierType(_dummyType, false) + .MakeModifierType(_dummyType, true) + .MakePinnedType() + .StripModifiers(), + SignatureComparer.Default); + } + [Theory] [InlineData(ElementType.I, ElementType.I)] [InlineData(ElementType.I1, ElementType.I1)] @@ -519,5 +546,54 @@ public void IsAssignablePrimitives(ElementType elementType1, ElementType element Assert.Equal(expected, type1.IsAssignableTo(type2)); } + + [Fact] + public void IgnoreCustomModifiers() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + + var type1 = module.CorLibTypeFactory.Int32; + var type2 = module.CorLibTypeFactory.Int32.MakeModifierType(module.CorLibTypeFactory.CorLibScope + .CreateTypeReference("System.Runtime.CompilerServices", "IsVolatile") + .ImportWith(module.DefaultImporter), + true); + + Assert.True(type1.IsCompatibleWith(type2)); + Assert.True(type2.IsCompatibleWith(type1)); + } + + [Fact] + public void IgnoreNestedCustomModifiers() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + + var type1 = module.CorLibTypeFactory.Int32; + var type2 = module.CorLibTypeFactory.Int32.MakeModifierType(module.CorLibTypeFactory.CorLibScope + .CreateTypeReference("System.Runtime.CompilerServices", "IsVolatile") + .ImportWith(module.DefaultImporter), + true); + + var genericType = module.CorLibTypeFactory.CorLibScope + .CreateTypeReference("System.Collections.Generic", "List`1") + .ImportWith(module.DefaultImporter); + + var genericType1 = genericType.MakeGenericInstanceType(type1); + var genericType2 = genericType.MakeGenericInstanceType(type2); + + Assert.True(genericType1.IsCompatibleWith(genericType2)); + Assert.True(genericType2.IsCompatibleWith(genericType1)); + } + + [Fact] + public void IgnorePinnedModifiers() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + + var type1 = module.CorLibTypeFactory.Int32; + var type2 = module.CorLibTypeFactory.Int32.MakePinnedType(); + + Assert.True(type1.IsCompatibleWith(type2)); + Assert.True(type2.IsCompatibleWith(type1)); + } } } From e98f1f5061146cdb698da8750eb23682015454e9 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 18 Mar 2023 18:43:21 +0100 Subject: [PATCH 08/10] Add comparer parameter for IsCompatibleWith and related methods. --- .../Types/ArrayBaseTypeSignature.cs | 8 ++-- .../Types/FunctionPointerTypeSignature.cs | 8 ++-- .../Types/GenericInstanceTypeSignature.cs | 12 +++--- .../Signatures/Types/PointerTypeSignature.cs | 6 +-- .../Signatures/Types/TypeDefOrRefSignature.cs | 8 ++-- .../Signatures/Types/TypeSignature.cs | 37 +++++++++++++++---- 6 files changed, 51 insertions(+), 28 deletions(-) diff --git a/src/AsmResolver.DotNet/Signatures/Types/ArrayBaseTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/ArrayBaseTypeSignature.cs index ee77d817b..e073beb19 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/ArrayBaseTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/ArrayBaseTypeSignature.cs @@ -34,9 +34,9 @@ public abstract int Rank public abstract IEnumerable GetDimensions(); /// - protected override bool IsDirectlyCompatibleWith(TypeSignature other) + protected override bool IsDirectlyCompatibleWith(TypeSignature other, SignatureComparer comparer) { - if (base.IsDirectlyCompatibleWith(other)) + if (base.IsDirectlyCompatibleWith(other, comparer)) return true; TypeSignature? elementType = null; @@ -59,8 +59,8 @@ protected override bool IsDirectlyCompatibleWith(TypeSignature other) var v = BaseType.GetUnderlyingType(); var w = elementType.GetUnderlyingType(); - return SignatureComparer.Default.Equals(v.GetReducedType(), w.GetReducedType()) - || v.IsCompatibleWith(w); + return comparer.Equals(v.GetReducedType(), w.GetReducedType()) + || v.IsCompatibleWith(w, comparer); } } } diff --git a/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs index 251632a3c..76beb10c8 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs @@ -52,22 +52,22 @@ public MethodSignature Signature public override bool IsImportedInModule(ModuleDefinition module) => Signature.IsImportedInModule(module); /// - protected override bool IsDirectlyCompatibleWith(TypeSignature other) + protected override bool IsDirectlyCompatibleWith(TypeSignature other, SignatureComparer comparer) { - if (base.IsDirectlyCompatibleWith(other)) + if (base.IsDirectlyCompatibleWith(other, comparer)) return true; if (other is not FunctionPointerTypeSignature {Signature: { } otherSignature} || Signature.GenericParameterCount != otherSignature.GenericParameterCount || Signature.ParameterTypes.Count != otherSignature.ParameterTypes.Count - || !Signature.ReturnType.IsAssignableTo(otherSignature.ReturnType)) + || !Signature.ReturnType.IsAssignableTo(otherSignature.ReturnType, comparer)) { return false; } for (int i = 0; i < Signature.ParameterTypes.Count; i++) { - if (!Signature.ParameterTypes[i].IsAssignableTo(otherSignature.ParameterTypes[i])) + if (!Signature.ParameterTypes[i].IsAssignableTo(otherSignature.ParameterTypes[i], comparer)) return false; } diff --git a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs index b5859bad7..e81d64a86 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs @@ -163,14 +163,14 @@ public override IEnumerable GetDirectlyImplementedInterfaces() } /// - protected override bool IsDirectlyCompatibleWith(TypeSignature other) + protected override bool IsDirectlyCompatibleWith(TypeSignature other, SignatureComparer comparer) { - if (base.IsDirectlyCompatibleWith(other)) + if (base.IsDirectlyCompatibleWith(other, comparer)) return true; if (other is not GenericInstanceTypeSignature otherGenericInstance || otherGenericInstance.TypeArguments.Count != TypeArguments.Count - || !SignatureComparer.Default.Equals(GenericType, otherGenericInstance.GenericType)) + || !comparer.Equals(GenericType, otherGenericInstance.GenericType)) { return false; } @@ -186,11 +186,11 @@ protected override bool IsDirectlyCompatibleWith(TypeSignature other) bool argumentIsCompatible = variance switch { GenericParameterAttributes.NonVariant => - SignatureComparer.Default.Equals(TypeArguments[i].StripModifiers(), otherGenericInstance.TypeArguments[i].StripModifiers()), + comparer.Equals(TypeArguments[i].StripModifiers(), otherGenericInstance.TypeArguments[i].StripModifiers()), GenericParameterAttributes.Covariant => - TypeArguments[i].IsCompatibleWith(otherGenericInstance.TypeArguments[i]), + TypeArguments[i].IsCompatibleWith(otherGenericInstance.TypeArguments[i], comparer), GenericParameterAttributes.Contravariant => - otherGenericInstance.TypeArguments[i].IsCompatibleWith(TypeArguments[i]), + otherGenericInstance.TypeArguments[i].IsCompatibleWith(TypeArguments[i], comparer), _ => throw new ArgumentOutOfRangeException() }; diff --git a/src/AsmResolver.DotNet/Signatures/Types/PointerTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/PointerTypeSignature.cs index 6bbf3e83f..57424eb46 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/PointerTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/PointerTypeSignature.cs @@ -26,9 +26,9 @@ public PointerTypeSignature(TypeSignature baseType) public override bool IsValueType => false; /// - protected override bool IsDirectlyCompatibleWith(TypeSignature other) + protected override bool IsDirectlyCompatibleWith(TypeSignature other, SignatureComparer comparer) { - if (base.IsDirectlyCompatibleWith(other)) + if (base.IsDirectlyCompatibleWith(other, comparer)) return true; if (other is not PointerTypeSignature otherPointer) @@ -36,7 +36,7 @@ protected override bool IsDirectlyCompatibleWith(TypeSignature other) var v = BaseType.GetVerificationType(); var w = otherPointer.BaseType.GetVerificationType(); - return SignatureComparer.Default.Equals(v, w); + return comparer.Equals(v, w); } /// diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs index 7a75a917a..67e592567 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs @@ -119,15 +119,15 @@ public override IEnumerable GetDirectlyImplementedInterfaces() } /// - protected override bool IsDirectlyCompatibleWith(TypeSignature other) + protected override bool IsDirectlyCompatibleWith(TypeSignature other, SignatureComparer comparer) { - if (base.IsDirectlyCompatibleWith(other)) + if (base.IsDirectlyCompatibleWith(other, comparer)) return true; if (IsValueType) return false; - return SignatureComparer.Default.Equals(GetDirectBaseClass(), other) - || GetDirectlyImplementedInterfaces().Contains(other, SignatureComparer.Default); + return comparer.Equals(GetDirectBaseClass(), other) + || GetDirectlyImplementedInterfaces().Contains(other, comparer); } /// diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs index d86aad63a..3e64f20d3 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs @@ -399,14 +399,15 @@ internal static void WriteFieldOrPropType(BlobSerializationContext context, Type /// Determines whether the current type is directly compatible with the provided type. /// /// The other type. + /// The comparer to use for comparing type signatures. /// true if the types are directly compatible, false otherwise. /// /// Type compatibility is determined according to the rules in ECMA-335 I.8.7.1., excluding the transitivity /// rule. /// - protected virtual bool IsDirectlyCompatibleWith(TypeSignature other) + protected virtual bool IsDirectlyCompatibleWith(TypeSignature other, SignatureComparer comparer) { - return SignatureComparer.Default.Equals(this, other); + return comparer.Equals(this, other); } /// @@ -417,7 +418,18 @@ protected virtual bool IsDirectlyCompatibleWith(TypeSignature other) /// /// Type compatibility is determined according to the rules in ECMA-335 I.8.7.1. /// - public bool IsCompatibleWith(TypeSignature other) + public bool IsCompatibleWith(TypeSignature other) => IsCompatibleWith(other, SignatureComparer.Default); + + /// + /// Determines whether the current type is compatible with the provided type. + /// + /// The other type. + /// The comparer to use for comparing type signatures. + /// true if the type is compatible with , false otherwise. + /// + /// Type compatibility is determined according to the rules in ECMA-335 I.8.7.1. + /// + public bool IsCompatibleWith(TypeSignature other, SignatureComparer comparer) { var current = StripModifiers(); other = other.StripModifiers(); @@ -425,7 +437,7 @@ public bool IsCompatibleWith(TypeSignature other) // Achieve the transitivity rule by moving up the type hierarchy iteratively. while (current is not null) { - if (current.IsDirectlyCompatibleWith(other)) + if (current.IsDirectlyCompatibleWith(other, comparer)) return true; current = current.GetDirectBaseClass()?.StripModifiers(); @@ -442,19 +454,30 @@ public bool IsCompatibleWith(TypeSignature other) /// /// Type compatibility is determined according to the rules in ECMA-335 I.8.7.3. /// - public bool IsAssignableTo(TypeSignature other) + public bool IsAssignableTo(TypeSignature other) => IsAssignableTo(other, SignatureComparer.Default); + + /// + /// Determines whether the current type is assignable to the provided type. + /// + /// The other type. + /// The comparer to use for comparing type signatures. + /// true if the type is assignable to , false otherwise. + /// + /// Type compatibility is determined according to the rules in ECMA-335 I.8.7.3. + /// + public bool IsAssignableTo(TypeSignature other, SignatureComparer comparer) { var intermediateType1 = GetIntermediateType(); var intermediateType2 = other.GetIntermediateType(); - if (SignatureComparer.Default.Equals(intermediateType1, intermediateType2) + if (comparer.Equals(intermediateType1, intermediateType2) || intermediateType1.ElementType == ElementType.I && intermediateType2.ElementType == ElementType.I4 || intermediateType1.ElementType == ElementType.I4 && intermediateType2.ElementType == ElementType.I) { return true; } - return IsCompatibleWith(other); + return IsCompatibleWith(other, comparer); } /// From 078a81df8df204086cfc6b27adce91928f84b530 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 18 Mar 2023 18:59:43 +0100 Subject: [PATCH 09/10] Add generic interface implementation compatibility checks. --- .../Types/GenericInstanceTypeSignature.cs | 20 +++++++++++++--- .../Signatures/TypeSignatureTest.cs | 24 +++++++++++++++++++ .../GenericInterfaceImplementation.cs | 6 +++++ .../IGenericInterface.cs | 7 ++++++ 4 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/GenericInterfaceImplementation.cs create mode 100644 test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/IGenericInterface.cs diff --git a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs index e81d64a86..9e5e20309 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs @@ -165,9 +165,13 @@ public override IEnumerable GetDirectlyImplementedInterfaces() /// protected override bool IsDirectlyCompatibleWith(TypeSignature other, SignatureComparer comparer) { - if (base.IsDirectlyCompatibleWith(other, comparer)) - return true; + return base.IsDirectlyCompatibleWith(other, comparer) + || IsDirectlyClassCompatibleWith(other, comparer) + || IsDirectlyInterfaceCompatibleWith(other, comparer); + } + private bool IsDirectlyClassCompatibleWith(TypeSignature other, SignatureComparer comparer) + { if (other is not GenericInstanceTypeSignature otherGenericInstance || otherGenericInstance.TypeArguments.Count != TypeArguments.Count || !comparer.Equals(GenericType, otherGenericInstance.GenericType)) @@ -197,10 +201,20 @@ protected override bool IsDirectlyCompatibleWith(TypeSignature other, SignatureC if (!argumentIsCompatible) return false; } - return true; } + private bool IsDirectlyInterfaceCompatibleWith(TypeSignature other, SignatureComparer comparer) + { + foreach (var @interface in GetDirectlyImplementedInterfaces()) + { + if (@interface.IsCompatibleWith(other, comparer)) + return true; + } + + return false; + } + /// protected override void WriteContents(in BlobSerializationContext context) { diff --git a/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs b/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs index 19ea59466..5c26a3518 100644 --- a/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs +++ b/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs @@ -419,6 +419,30 @@ public void IsCompatibleWithInterface(Type derivedType, Type interfaceType, bool Assert.Equal(expected, derivedSignature.IsCompatibleWith(interfaceSignature)); } + [Theory] + [InlineData(new[] { ElementType.I4, ElementType.I4 }, new[] { ElementType.I4, ElementType.I4, ElementType.String }, true)] + [InlineData(new[] { ElementType.I4, ElementType.I8 }, new[] { ElementType.I4, ElementType.I4, ElementType.String }, false)] + [InlineData(new[] { ElementType.I4, ElementType.I4 }, new[] { ElementType.I4, ElementType.I8, ElementType.String }, false)] + [InlineData(new[] { ElementType.I4, ElementType.I4 }, new[] { ElementType.I4, ElementType.I4, ElementType.Boolean }, false)] + public void IsCompatibleWithGenericInterface(ElementType[] typeArguments1, ElementType[] typeArguments2, bool expected) + { + var module = ModuleDefinition.FromFile(typeof(GenericInterfaceImplementation<,>).Assembly.Location); + + var type1 = module.LookupMember(typeof(GenericInterfaceImplementation<,>).MetadataToken) + .ToTypeSignature(false) + .MakeGenericInstanceType( + typeArguments1.Select(x => (TypeSignature) module.CorLibTypeFactory.FromElementType(x)!).ToArray() + ); + + var type2 = module.LookupMember(typeof(IGenericInterface<,,>).MetadataToken) + .ToTypeSignature(false) + .MakeGenericInstanceType( + typeArguments2.Select(x => (TypeSignature) module.CorLibTypeFactory.FromElementType(x)!).ToArray() + ); + + Assert.Equal(expected, type1.IsCompatibleWith(type2)); + } + [Theory] [InlineData(ElementType.I1, ElementType.I1, true)] [InlineData(ElementType.U1, ElementType.I1, true)] diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/GenericInterfaceImplementation.cs b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/GenericInterfaceImplementation.cs new file mode 100644 index 000000000..7cbee2dd5 --- /dev/null +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/GenericInterfaceImplementation.cs @@ -0,0 +1,6 @@ +namespace AsmResolver.DotNet.TestCases.Generics +{ + public class GenericInterfaceImplementation : IGenericInterface + { + } +} diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/IGenericInterface.cs b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/IGenericInterface.cs new file mode 100644 index 000000000..6ad7d0b37 --- /dev/null +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/IGenericInterface.cs @@ -0,0 +1,7 @@ +namespace AsmResolver.DotNet.TestCases.Generics +{ + public interface IGenericInterface + { + + } +} From bf84256a427d3594cdb10e96a7c8618989887f8b Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 18 Mar 2023 19:15:55 +0100 Subject: [PATCH 10/10] Generalize interface impl traversal. Simplify implementation of IsDirectlyCompatibleWith. --- .../Types/GenericInstanceTypeSignature.cs | 21 ++++--------------- .../Signatures/Types/TypeDefOrRefSignature.cs | 12 ----------- .../Signatures/Types/TypeSignature.cs | 9 ++++++++ 3 files changed, 13 insertions(+), 29 deletions(-) diff --git a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs index 9e5e20309..272ad4ebf 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs @@ -165,13 +165,10 @@ public override IEnumerable GetDirectlyImplementedInterfaces() /// protected override bool IsDirectlyCompatibleWith(TypeSignature other, SignatureComparer comparer) { - return base.IsDirectlyCompatibleWith(other, comparer) - || IsDirectlyClassCompatibleWith(other, comparer) - || IsDirectlyInterfaceCompatibleWith(other, comparer); - } + if (base.IsDirectlyCompatibleWith(other, comparer)) + return true; - private bool IsDirectlyClassCompatibleWith(TypeSignature other, SignatureComparer comparer) - { + // Other type must be a generic instance with the same generic base type and type argument count. if (other is not GenericInstanceTypeSignature otherGenericInstance || otherGenericInstance.TypeArguments.Count != TypeArguments.Count || !comparer.Equals(GenericType, otherGenericInstance.GenericType)) @@ -201,18 +198,8 @@ private bool IsDirectlyClassCompatibleWith(TypeSignature other, SignatureCompare if (!argumentIsCompatible) return false; } - return true; - } - private bool IsDirectlyInterfaceCompatibleWith(TypeSignature other, SignatureComparer comparer) - { - foreach (var @interface in GetDirectlyImplementedInterfaces()) - { - if (@interface.IsCompatibleWith(other, comparer)) - return true; - } - - return false; + return true; } /// diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs index 67e592567..319d76094 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs @@ -118,18 +118,6 @@ public override IEnumerable GetDirectlyImplementedInterfaces() return type.Interfaces.Select(i => i.Interface!.ToTypeSignature(false)); } - /// - protected override bool IsDirectlyCompatibleWith(TypeSignature other, SignatureComparer comparer) - { - if (base.IsDirectlyCompatibleWith(other, comparer)) - return true; - if (IsValueType) - return false; - - return comparer.Equals(GetDirectBaseClass(), other) - || GetDirectlyImplementedInterfaces().Contains(other, comparer); - } - /// public override TResult AcceptVisitor(ITypeSignatureVisitor visitor) => visitor.VisitTypeDefOrRef(this); diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs index 3e64f20d3..01e56d85f 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs @@ -437,9 +437,18 @@ public bool IsCompatibleWith(TypeSignature other, SignatureComparer comparer) // Achieve the transitivity rule by moving up the type hierarchy iteratively. while (current is not null) { + // Is the current type compatible? if (current.IsDirectlyCompatibleWith(other, comparer)) return true; + // Are any of the interfaces compatible instead? + foreach (var @interface in current.GetDirectlyImplementedInterfaces()) + { + if (@interface.IsCompatibleWith(other, comparer)) + return true; + } + + // If neither, move up type hierarchy. current = current.GetDirectBaseClass()?.StripModifiers(); }