Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeSignature Compatibility and Assignability Rules #421

Merged
merged 10 commits into from
Mar 19, 2023
67 changes: 67 additions & 0 deletions docs/dotnet/type-signatures.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>`` interface, and as such, it can be used as a comparer for dictionaries and related types:

.. code-block:: csharp

var dictionary = new Dictionary<TypeSignature, TValue>(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.
}
66 changes: 66 additions & 0 deletions src/AsmResolver.DotNet/Signatures/Types/ArrayBaseTypeSignature.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System.Collections.Generic;

namespace AsmResolver.DotNet.Signatures.Types
{
/// <summary>
/// Represents a type signature representing an array.
/// </summary>
public abstract class ArrayBaseTypeSignature : TypeSpecificationSignature
{
/// <summary>
/// Initializes an array type signature.
/// </summary>
/// <param name="baseType">The element type of the array.</param>
protected ArrayBaseTypeSignature(TypeSignature baseType)
: base(baseType)
{
}

/// <inheritdoc />
public override bool IsValueType => false;

/// <summary>
/// Gets the number of dimensions this array defines.
/// </summary>
public abstract int Rank
{
get;
}

/// <summary>
/// Obtains the dimensions this array defines.
/// </summary>
/// <returns>The dimensions.</returns>
public abstract IEnumerable<ArrayDimension> GetDimensions();

/// <inheritdoc />
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<T> 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);
}
}
}
17 changes: 13 additions & 4 deletions src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace AsmResolver.DotNet.Signatures.Types
/// <remarks>
/// For simple single-dimension arrays, use <see cref="SzArrayTypeSignature"/> instead.
/// </remarks>
public class ArrayTypeSignature : TypeSpecificationSignature
public class ArrayTypeSignature : ArrayBaseTypeSignature
{
/// <summary>
/// Creates a new array type signature.
Expand Down Expand Up @@ -58,9 +58,6 @@ public ArrayTypeSignature(TypeSignature baseType, params ArrayDimension[] dimens
/// <inheritdoc />
public override string Name => $"{BaseType.Name ?? NullTypeToString}{GetDimensionsString()}";

/// <inheritdoc />
public override bool IsValueType => false;

/// <summary>
/// Gets a collection of dimensions.
/// </summary>
Expand All @@ -69,6 +66,12 @@ public IList<ArrayDimension> Dimensions
get;
}

/// <inheritdoc />
public override int Rank => Dimensions.Count;

/// <inheritdoc />
public override IEnumerable<ArrayDimension> GetDimensions() => Dimensions;

internal new static ArrayTypeSignature FromReader(ref BlobReaderContext context, ref BinaryStreamReader reader)
{
var signature = new ArrayTypeSignature(TypeSignature.FromReader(ref context, ref reader));
Expand Down Expand Up @@ -191,6 +194,12 @@ public bool Validate()
return true;
}

/// <inheritdoc />
public override TypeSignature? GetDirectBaseClass() => Module?.CorLibTypeFactory.CorLibScope
.CreateTypeReference("System", "Array")
.ToTypeSignature(false)
.ImportWith(Module.DefaultImporter);

/// <inheritdoc />
public override TResult AcceptVisitor<TResult>(ITypeSignatureVisitor<TResult> visitor) =>
visitor.VisitArrayType(this);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using AsmResolver.PE.DotNet.Metadata.Tables.Rows;

namespace AsmResolver.DotNet.Signatures.Types
Expand Down Expand Up @@ -25,6 +26,24 @@ public ByReferenceTypeSignature(TypeSignature baseType)
/// <inheritdoc />
public override bool IsValueType => false;

/// <inheritdoc />
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()
};
}

/// <inheritdoc />
public override TResult AcceptVisitor<TResult>(ITypeSignatureVisitor<TResult> visitor) =>
visitor.VisitByReferenceType(this);
Expand Down
43 changes: 43 additions & 0 deletions src/AsmResolver.DotNet/Signatures/Types/CorLibTypeSignature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,49 @@ public override IResolutionScope? Scope
/// <inheritdoc />
public override ITypeDefOrRef ToTypeDefOrRef() => Type;

/// <inheritdoc />
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()
};
}

/// <inheritdoc />
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()
};
}

/// <inheritdoc />
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
};
}

/// <inheritdoc />
protected override void WriteContents(in BlobSerializationContext context) =>
context.Writer.WriteByte((byte) ElementType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,21 @@ public override string Name
/// <inheritdoc />
public override bool IsValueType => BaseType.IsValueType;

/// <inheritdoc />
public override TypeSignature GetReducedType() => BaseType.GetReducedType();

/// <inheritdoc />
public override TypeSignature GetVerificationType() => BaseType.GetVerificationType();

/// <inheritdoc />
public override TypeSignature GetIntermediateType() => BaseType.GetIntermediateType();

/// <inheritdoc />
public override TypeSignature? GetDirectBaseClass() => BaseType.GetDirectBaseClass();

/// <inheritdoc />
public override TypeSignature StripModifiers() => BaseType.StripModifiers();

/// <inheritdoc />
public override TResult AcceptVisitor<TResult>(ITypeSignatureVisitor<TResult> visitor) =>
visitor.VisitCustomModifierType(this);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Linq;
using AsmResolver.PE.DotNet.Metadata.Tables.Rows;

namespace AsmResolver.DotNet.Signatures.Types
Expand Down Expand Up @@ -52,6 +51,29 @@ public MethodSignature Signature
/// <inheritdoc />
public override bool IsImportedInModule(ModuleDefinition module) => Signature.IsImportedInModule(module);

/// <inheritdoc />
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;
}

/// <inheritdoc />
protected override void WriteContents(in BlobSerializationContext context)
{
Expand Down
Loading