diff --git a/docs/dotnet/type-signatures.rst b/docs/dotnet/type-signatures.rst index 34d5fcff1..e98940e72 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 (type1.IsCompatibleWith(type2)) + { + // type1 can be converted to type2. + } + + +.. code-block:: csharp + + 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 new file mode 100644 index 000000000..e073beb19 --- /dev/null +++ b/src/AsmResolver.DotNet/Signatures/Types/ArrayBaseTypeSignature.cs @@ -0,0 +1,66 @@ +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) + { + } + + /// + public override bool IsValueType => false; + + /// + /// 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, SignatureComparer comparer) + { + if (base.IsDirectlyCompatibleWith(other, comparer)) + 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 comparer.Equals(v.GetReducedType(), w.GetReducedType()) + || v.IsCompatibleWith(w, comparer); + } + } +} diff --git a/src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs index 682ec1f4e..056f943f6 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. @@ -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. /// @@ -69,6 +66,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)); @@ -191,6 +194,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..051321466 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/CustomModifierTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/CustomModifierTypeSignature.cs @@ -66,6 +66,21 @@ 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 TypeSignature StripModifiers() => BaseType.StripModifiers(); + /// public override TResult AcceptVisitor(ITypeSignatureVisitor visitor) => visitor.VisitCustomModifierType(this); diff --git a/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs index b5ebe0c15..76beb10c8 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, SignatureComparer comparer) + { + 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, comparer)) + { + return false; + } + + for (int i = 0; i < Signature.ParameterTypes.Count; i++) + { + if (!Signature.ParameterTypes[i].IsAssignableTo(otherSignature.ParameterTypes[i], comparer)) + 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 990652f5f..272ad4ebf 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; @@ -126,6 +127,81 @@ 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. Substitute any generic type arguments present in it. + return baseType is TypeSpecification { Signature: { } signatureBaseType } + ? signatureBaseType.StripModifiers().InstantiateGenericTypes(GenericContext.FromType(this)) + : null; + } + + /// + 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, SignatureComparer comparer) + { + if (base.IsDirectlyCompatibleWith(other, comparer)) + return true; + + // 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)) + { + 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 => + comparer.Equals(TypeArguments[i].StripModifiers(), otherGenericInstance.TypeArguments[i].StripModifiers()), + GenericParameterAttributes.Covariant => + TypeArguments[i].IsCompatibleWith(otherGenericInstance.TypeArguments[i], comparer), + GenericParameterAttributes.Contravariant => + otherGenericInstance.TypeArguments[i].IsCompatibleWith(TypeArguments[i], comparer), + _ => throw new ArgumentOutOfRangeException() + }; + + if (!argumentIsCompatible) + return false; + } + + return true; + } + /// 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..d558428ea 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/PinnedTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/PinnedTypeSignature.cs @@ -26,6 +26,21 @@ 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 TypeSignature StripModifiers() => BaseType.StripModifiers(); + /// public override TResult AcceptVisitor(ITypeSignatureVisitor visitor) => visitor.VisitPinnedType(this); diff --git a/src/AsmResolver.DotNet/Signatures/Types/PointerTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/PointerTypeSignature.cs index 382c6cfb5..57424eb46 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, SignatureComparer comparer) + { + if (base.IsDirectlyCompatibleWith(other, comparer)) + return true; + + if (other is not PointerTypeSignature otherPointer) + return false; + + var v = BaseType.GetVerificationType(); + var w = otherPointer.BaseType.GetVerificationType(); + return comparer.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 a952b5a0e..c0286ccf9 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. /// @@ -23,7 +26,17 @@ public SzArrayTypeSignature(TypeSignature baseType) public override string Name => $"{BaseType.Name ?? NullTypeToString}[]"; /// - 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) => diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs index 3fed04061..319d76094 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 @@ -73,6 +75,49 @@ 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() + { + 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).StripModifiers(); + } + + /// + public override IEnumerable GetDirectlyImplementedInterfaces() + { + var type = Type.Resolve(); + if (type is null) + return Enumerable.Empty(); + + return type.Interfaces.Select(i => i.Interface!.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..01e56d85f 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; @@ -310,8 +312,183 @@ 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. + /// + /// 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; + + /// + /// 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(); + + /// + /// 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. + /// + /// 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, SignatureComparer comparer) + { + return comparer.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) => 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(); + + // 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(); + } + + 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) => 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 (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, comparer); + } + /// /// Substitutes any generic type parameter in the type signature with the parameters provided by /// the generic context. @@ -338,6 +515,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/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() { diff --git a/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs b/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs index 69f25cbe7..5c26a3518 100644 --- a/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs +++ b/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs @@ -1,5 +1,9 @@ +using System; +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 +126,498 @@ public void GetOptionalModifierTypeFullName() .MakeModifierType(_dummyType, false) .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)] + [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); + } + + [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() + { + 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)); + } + + [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(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)] + [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)); + } + + [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)); + } } } 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 + { + } +} 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 + { + + } +} 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, + } +}