Browse Source

Merge pull request #3686 from icsharpcode/custom-signature-decoder-comparer

Hide compiler-generated accessor methods
pull/3694/head
Christoph Wille 2 weeks ago committed by GitHub
parent
commit
6db9a02180
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 16
      ICSharpCode.Decompiler.Tests/Helpers/Tester.cs
  2. 3
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  3. 365
      ICSharpCode.Decompiler.Tests/Metadata/SignatureBlobComparerTests.cs
  4. 6
      ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
  5. 22
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3684.cs
  6. 18
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3684.dep.cs
  7. 161
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  8. 137
      ICSharpCode.Decompiler/Metadata/SignatureBlobComparer.cs

16
ICSharpCode.Decompiler.Tests/Helpers/Tester.cs

@ -489,6 +489,17 @@ namespace System.Runtime.CompilerServices @@ -489,6 +489,17 @@ namespace System.Runtime.CompilerServices
{
sourceFileNames.Add(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(sourceFileName), match.Groups[1].Value)));
}
List<string> dependencyAssemblies = new List<string>();
string sourceText = File.ReadAllText(sourceFileName);
foreach (Match match in Regex.Matches(sourceText, @"//\s*#dependency\s+([\w\d./\\]+)"))
{
string depSourcePath = Path.GetFullPath(Path.Combine(
Path.GetDirectoryName(sourceFileName), match.Groups[1].Value));
var depResults = await CompileCSharp(depSourcePath, flags | CompilerOptions.Library).ConfigureAwait(false);
dependencyAssemblies.Add(Path.GetFullPath(depResults.PathToAssembly));
}
bool targetNet40 = (flags & CompilerOptions.TargetNet40) != 0;
bool useRoslyn = (flags & CompilerOptions.UseRoslynMask) != 0;
@ -556,6 +567,11 @@ namespace System.Runtime.CompilerServices @@ -556,6 +567,11 @@ namespace System.Runtime.CompilerServices
references = references.Select(r => "-r:\"" + Path.Combine(refAsmPath, r) + "\"");
foreach (var dependency in dependencyAssemblies)
{
references = references.Append($"-r:\"{dependency}\"");
}
string otherOptions = $"-nologo -noconfig " +
$"-langversion:{languageVersion} " +
$"-unsafe -o{(flags.HasFlag(CompilerOptions.Optimize) ? "+ " : "- ")}";

3
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -167,6 +167,9 @@ @@ -167,6 +167,9 @@
<Compile Include="TestCases\Pretty\Issue3610.cs" />
<Compile Include="TestCases\Pretty\Issue3611.cs" />
<Compile Include="TestCases\Pretty\Issue3598.cs" />
<Compile Include="Metadata\SignatureBlobComparerTests.cs" />
<None Include="TestCases\Pretty\Issue3684.dep.cs" />
<None Include="TestCases\Pretty\Issue3684.cs" />
<None Include="TestCases\Ugly\NoLocalFunctions.Expected.cs" />
<None Include="TestCases\ILPretty\Issue3504.cs" />
<Compile Include="TestCases\ILPretty\MonoFixed.cs" />

365
ICSharpCode.Decompiler.Tests/Metadata/SignatureBlobComparerTests.cs

@ -0,0 +1,365 @@ @@ -0,0 +1,365 @@
// Copyright (c) 2026 Siegfried Pammer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata;
using ICSharpCode.Decompiler.Metadata;
using NUnit.Framework;
namespace ICSharpCode.Decompiler.Tests.Metadata
{
[TestFixture]
public class SignatureBlobComparerTests
{
static MetadataReader Metadata => TypeSystem.TypeSystemLoaderTests.TestAssembly.Metadata;
#region Helper methods
/// <summary>
/// Finds all MethodDefinitionHandles for methods with the given name in the given type.
/// </summary>
static List<MethodDefinitionHandle> FindMethods(string typeName, string methodName)
{
var results = new List<MethodDefinitionHandle>();
foreach (var typeHandle in Metadata.TypeDefinitions)
{
var typeDef = Metadata.GetTypeDefinition(typeHandle);
if (!Metadata.StringComparer.Equals(typeDef.Name, typeName))
continue;
foreach (var methodHandle in typeDef.GetMethods())
{
var methodDef = Metadata.GetMethodDefinition(methodHandle);
if (Metadata.StringComparer.Equals(methodDef.Name, methodName))
results.Add(methodHandle);
}
}
return results;
}
/// <summary>
/// Finds a single MethodDefinitionHandle for a method with the given name in the given type.
/// </summary>
static MethodDefinitionHandle FindMethod(string typeName, string methodName)
{
var results = FindMethods(typeName, methodName);
Assert.That(results.Count, Is.EqualTo(1),
$"Expected exactly one method '{methodName}' in '{typeName}', found {results.Count}");
return results[0];
}
/// <summary>
/// Finds a MethodDefinitionHandle matching on name and parameter count.
/// </summary>
static MethodDefinitionHandle FindMethod(string typeName, string methodName, int parameterCount)
{
var results = FindMethods(typeName, methodName);
var filtered = results.Where(h => {
var m = Metadata.GetMethodDefinition(h);
return m.GetParameters().Count == parameterCount;
}).ToList();
Assert.That(filtered.Count, Is.EqualTo(1),
$"Expected exactly one method '{methodName}' with {parameterCount} params in '{typeName}', found {filtered.Count}");
return filtered[0];
}
static BlobReader GetSignatureBlob(MethodDefinitionHandle handle)
{
var method = Metadata.GetMethodDefinition(handle);
return Metadata.GetBlobReader(method.Signature);
}
static bool CompareSignatures(MethodDefinitionHandle a, MethodDefinitionHandle b)
{
return SignatureBlobComparer.EqualsMethodSignature(
GetSignatureBlob(a), GetSignatureBlob(b),
Metadata, Metadata);
}
#endregion
#region Identity / reflexive tests
[Test]
public void SameMethod_IsEqual()
{
// SimplePublicClass.Method() compared to itself
var method = FindMethod("SimplePublicClass", "Method");
Assert.That(CompareSignatures(method, method), Is.True);
}
[Test]
public void Constructor_ComparedToItself_IsEqual()
{
var ctor = FindMethod("SimplePublicClass", ".ctor");
Assert.That(CompareSignatures(ctor, ctor), Is.True);
}
[Test]
public void StaticMethod_ComparedToItself_IsEqual()
{
// StaticClass.Extension(this object) — it's static at the IL level
var method = FindMethod("StaticClass", "Extension");
Assert.That(CompareSignatures(method, method), Is.True);
}
#endregion
#region Same name, different signatures
[Test]
public void DifferentParameterTypes_NotEqual()
{
// MethodWithOptionalParameter(int) vs MethodWithParamsArray(object[])
var intParam = FindMethod("ParameterTests", "MethodWithOptionalParameter");
var arrayParam = FindMethod("ParameterTests", "MethodWithParamsArray");
Assert.That(CompareSignatures(intParam, arrayParam), Is.False);
}
[Test]
public void RefVsOut_SameAtSignatureLevel()
{
// ref and out are both ELEMENT_TYPE_BYREF at the blob level;
// the out modifier is only represented via attributes.
var methodRef = FindMethod("ParameterTests", "MethodWithRefParameter");
var methodOut = FindMethod("ParameterTests", "MethodWithOutParameter");
Assert.That(CompareSignatures(methodRef, methodOut), Is.True);
}
[Test]
public void RefVsIn_SameAtSignatureLevel()
{
// ref and in are both ELEMENT_TYPE_BYREF at the blob level;
// the in modifier is only represented via IsReadOnlyAttribute.
var methodRef = FindMethod("ParameterTests", "MethodWithRefParameter");
var methodIn = FindMethod("ParameterTests", "MethodWithInParameter");
Assert.That(CompareSignatures(methodRef, methodIn), Is.True);
}
[Test]
public void ByRefVsNonByRef_NotEqual()
{
// MethodWithRefParameter(ref int) vs MethodWithOptionalParameter(int)
// byref int vs plain int should differ at the blob level
var byRef = FindMethod("ParameterTests", "MethodWithRefParameter");
var plain = FindMethod("ParameterTests", "MethodWithOptionalParameter");
Assert.That(CompareSignatures(byRef, plain), Is.False);
}
[Test]
public void DifferentParameterCount_NotEqual()
{
// Compare a method with 0 params to one with 1 param
var method0 = FindMethod("SimplePublicClass", "Method"); // 0 params
var method1 = FindMethod("ParameterTests", "MethodWithOutParameter"); // 1 param
Assert.That(CompareSignatures(method0, method1), Is.False);
}
[Test]
public void DifferentReturnType_NotEqual()
{
// SimplePublicClass.Method() returns void
// PropertyTest has a getter that returns string (get_Item)
var voidMethod = FindMethod("SimplePublicClass", "Method");
var stringGetter = FindMethod("PropertyTest", "get_Item");
Assert.That(CompareSignatures(voidMethod, stringGetter), Is.False);
}
#endregion
#region Generic methods
[Test]
public void GenericMethod_ComparedToItself_IsEqual()
{
// GenericClass<A,B>.TestMethod<K,V>(string)
var method = FindMethod("GenericClass`2", "TestMethod");
Assert.That(CompareSignatures(method, method), Is.True);
}
[Test]
public void GenericMethod_DifferentArity_NotEqual()
{
// TestMethod<K,V>(string) has 2 type params
// GetIndex<T>(T) has 1 type param
var testMethod = FindMethod("GenericClass`2", "TestMethod");
var getIndex = FindMethod("GenericClass`2", "GetIndex");
Assert.That(CompareSignatures(testMethod, getIndex), Is.False);
}
[Test]
public void GenericMethodWithOneTypeParam_ComparedToItself_IsEqual()
{
var method = FindMethod("GenericClass`2", "GetIndex");
Assert.That(CompareSignatures(method, method), Is.True);
}
[Test]
public void NonGenericVsGenericMethod_NotEqual()
{
// SimplePublicClass.Method() — non-generic
// GenericClass<A,B>.GetIndex<T>(T) — generic
var nonGeneric = FindMethod("SimplePublicClass", "Method");
var generic = FindMethod("GenericClass`2", "GetIndex");
Assert.That(CompareSignatures(nonGeneric, generic), Is.False);
}
#endregion
#region Instance vs static
[Test]
public void InstanceVsStatic_DifferentCallingConvention_NotEqual()
{
// An instance method vs a static method — calling convention differs in the signature header
// StaticClass.Extension is static; SimplePublicClass.Method is instance
var staticMethod = FindMethod("StaticClass", "Extension");
var instanceMethod = FindMethod("SimplePublicClass", "Method");
Assert.That(CompareSignatures(staticMethod, instanceMethod), Is.False);
}
#endregion
#region Methods with same signature across different types
[Test]
public void SameSignatureInDifferentTypes_IsEqual()
{
// Both SimplePublicClass and ParameterTests have parameterless instance constructors
var ctor1 = FindMethod("SimplePublicClass", ".ctor");
var ctor2 = FindMethod("ParameterTests", ".ctor");
Assert.That(CompareSignatures(ctor1, ctor2), Is.True);
}
#endregion
#region Methods involving complex types
[Test]
public void MethodWithArrayParam_ComparedToItself_IsEqual()
{
// ParameterTests.MethodWithParamsArray(params object[])
var method = FindMethod("ParameterTests", "MethodWithParamsArray");
Assert.That(CompareSignatures(method, method), Is.True);
}
[Test]
public void MethodWithOptionalParam_ComparedToNonOptional_IsEqual()
{
// At the signature blob level, optional vs required doesn't matter —
// the signatures are the same if the types match.
// MethodWithOptionalParameter(int) vs MethodWithEnumOptionalParameter(StringComparison)
// These have different parameter types, so they should NOT be equal.
var optionalInt = FindMethod("ParameterTests", "MethodWithOptionalParameter");
var optionalEnum = FindMethod("ParameterTests", "MethodWithEnumOptionalParameter");
Assert.That(CompareSignatures(optionalInt, optionalEnum), Is.False);
}
[Test]
public void VarArgsMethod_ComparedToItself_IsEqual()
{
var method = FindMethod("ParameterTests", "VarArgsMethod");
Assert.That(CompareSignatures(method, method), Is.True);
}
[Test]
public void VarArgsMethod_VsNormalMethod_NotEqual()
{
// VarArgsMethod uses __arglist calling convention
var varArgs = FindMethod("ParameterTests", "VarArgsMethod");
var normal = FindMethod("SimplePublicClass", "Method");
Assert.That(CompareSignatures(varArgs, normal), Is.False);
}
#endregion
#region Indexer accessors
[Test]
public void IndexerGetter_ComparedToItself_IsEqual()
{
var getter = FindMethod("PropertyTest", "get_Item");
Assert.That(CompareSignatures(getter, getter), Is.True);
}
[Test]
public void IndexerGetter_VsSetter_NotEqual()
{
var getter = FindMethod("PropertyTest", "get_Item");
var setter = FindMethod("PropertyTest", "set_Item");
Assert.That(CompareSignatures(getter, setter), Is.False);
}
#endregion
#region DllImport / extern methods
[Test]
public void DllImportMethod_ComparedToItself_IsEqual()
{
var method = FindMethod("NonCustomAttributes", "DllMethod");
Assert.That(CompareSignatures(method, method), Is.True);
}
#endregion
#region Explicit interface implementation
[Test]
public void ExplicitImpl_VsRegularMethod_SameSignature()
{
// ExplicitImplementationTests has both M(int) and IExplicitImplementationTests.M(int)
// Their signatures should be identical at the blob level
var methods = FindMethods("ExplicitImplementationTests", "M");
Assert.That(methods.Count, Is.EqualTo(1),
"Explicit impl has a mangled name, so only one 'M' should be found");
}
#endregion
#region Symmetric property
[Test]
public void Comparison_IsSymmetric()
{
var method1 = FindMethod("SimplePublicClass", "Method");
var method2 = FindMethod("ParameterTests", "MethodWithOutParameter");
bool forward = CompareSignatures(method1, method2);
bool backward = CompareSignatures(method2, method1);
Assert.That(forward, Is.EqualTo(backward), "Comparison should be symmetric");
}
[Test]
public void Comparison_IsSymmetric_WhenEqual()
{
var ctor1 = FindMethod("SimplePublicClass", ".ctor");
var ctor2 = FindMethod("ParameterTests", ".ctor");
bool forward = CompareSignatures(ctor1, ctor2);
bool backward = CompareSignatures(ctor2, ctor1);
Assert.That(forward, Is.True);
Assert.That(backward, Is.True);
}
#endregion
}
}

6
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

@ -852,6 +852,12 @@ namespace ICSharpCode.Decompiler.Tests @@ -852,6 +852,12 @@ namespace ICSharpCode.Decompiler.Tests
await RunForLibrary(cscOptions: cscOptions);
}
[Test]
public async Task Issue3684([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions)
{
await RunForLibrary(cscOptions: cscOptions);
}
async Task RunForLibrary([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, Action<DecompilerSettings> configureDecompiler = null)
{
await Run(testName, asmOptions | AssemblerOptions.Library, cscOptions | CompilerOptions.Library, configureDecompiler);

22
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3684.cs

@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
// #dependency Issue3684.dep.cs
using CrossAssemblyDep;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
public static class Issue3684
{
public interface IInterface
{
string Name { get; set; }
T Convert<T>(T input);
}
public class DerivedClass : BaseClass, IInterface
{
T IInterface.Convert<T>(T input)
{
return ((BaseClass)this).Convert<T>(input);
}
}
}
}

18
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue3684.dep.cs

@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
namespace CrossAssemblyDep
{
public class BaseClass
{
private string name = "BaseClass";
public string Name {
get {
return name;
}
set {
name = value;
}
}
public T Convert<T>(T input) { return input; }
}
}

161
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -292,6 +292,8 @@ namespace ICSharpCode.Decompiler.CSharp @@ -292,6 +292,8 @@ namespace ICSharpCode.Decompiler.CSharp
name = metadata.GetString(method.Name);
if (name == ".ctor" && method.RelativeVirtualAddress == 0 && metadata.GetTypeDefinition(method.GetDeclaringType()).Attributes.HasFlag(System.Reflection.TypeAttributes.Import))
return true;
if (module is PEFile m && IsAccessorInterfaceImplementationRuntimeHelper(m, methodHandle))
return true;
if (settings.LocalFunctions && LocalFunctionDecompiler.IsLocalFunctionMethod(module, methodHandle))
return true;
if (settings.AnonymousMethods && methodHandle.HasGeneratedName(metadata) && methodHandle.IsCompilerGenerated(metadata))
@ -386,6 +388,165 @@ namespace ICSharpCode.Decompiler.CSharp @@ -386,6 +388,165 @@ namespace ICSharpCode.Decompiler.CSharp
return name.StartsWith("<", StringComparison.Ordinal) && name.EndsWith(">P", StringComparison.Ordinal);
}
static bool IsAccessorInterfaceImplementationRuntimeHelper(PEFile module, MethodDefinitionHandle handle)
{
var metadata = module.Metadata;
var method = metadata.GetMethodDefinition(handle);
if ((method.Attributes & System.Reflection.MethodAttributes.Static) != 0)
return false;
string rawName = metadata.GetString(method.Name);
int dot = rawName.LastIndexOf('.');
if (dot < 0)
return false;
string name = rawName.Substring(dot + 1);
if (handle.GetMethodImplementations(metadata).Length == 0)
return false;
if (method.RelativeVirtualAddress == 0)
return false;
if (!name.StartsWith("get_", StringComparison.Ordinal) &&
!name.StartsWith("set_", StringComparison.Ordinal) &&
!name.StartsWith("add_", StringComparison.Ordinal) &&
!name.StartsWith("remove_", StringComparison.Ordinal) &&
!name.StartsWith("raise_", StringComparison.Ordinal))
{
return false;
}
var signature = metadata.GetBlobReader(method.Signature);
(int genericParameterCount, int parameterCount) = SignatureBlobComparer.ReadParameterCount(ref signature);
if (genericParameterCount == -1 || parameterCount == -1)
return false;
signature.Reset();
int maximumMethodSize = 4 * (parameterCount + 1) + 5 + 1;
var body = module.Reader.GetMethodBody(method.RelativeVirtualAddress);
var reader = body.GetILReader();
if (reader.RemainingBytes > maximumMethodSize)
return false;
for (int i = 0; i < parameterCount + 1; i++)
{
int index;
switch (reader.DecodeOpCode())
{
case ILOpCode.Ldarg:
index = reader.ReadUInt16();
if (index != i)
return false;
break;
case ILOpCode.Ldarg_s:
index = reader.ReadByte();
if (index != i)
return false;
break;
case ILOpCode.Ldarg_0:
if (i != 0)
return false;
break;
case ILOpCode.Ldarg_1:
if (i != 1)
return false;
break;
case ILOpCode.Ldarg_2:
if (i != 2)
return false;
break;
case ILOpCode.Ldarg_3:
if (i != 3)
return false;
break;
default:
return false;
}
}
if (reader.DecodeOpCode() != ILOpCode.Call)
return false;
EntityHandle targetHandle = MetadataTokenHelpers.EntityHandleOrNil(reader.ReadInt32());
if (targetHandle.IsNil)
return false;
if (reader.DecodeOpCode() != ILOpCode.Ret)
return false;
if (reader.RemainingBytes != 0)
return false;
BlobReader signature2;
string otherName;
switch (targetHandle.Kind)
{
case HandleKind.MethodDefinition:
if (genericParameterCount != 0)
return false;
var methodDef = metadata.GetMethodDefinition((MethodDefinitionHandle)targetHandle);
signature2 = metadata.GetBlobReader(methodDef.Signature);
otherName = metadata.GetString(methodDef.Name);
break;
case HandleKind.MethodSpecification:
if (genericParameterCount == 0)
return false;
var methodSpec = metadata.GetMethodSpecification((MethodSpecificationHandle)targetHandle);
var instantiationBlob = metadata.GetBlobReader(methodSpec.Signature);
if (!IsIdentityInstantiation(ref instantiationBlob, genericParameterCount))
return false;
switch (methodSpec.Method.Kind)
{
case HandleKind.MethodDefinition:
var methodSpecDef = metadata.GetMethodDefinition((MethodDefinitionHandle)methodSpec.Method);
signature2 = metadata.GetBlobReader(methodSpecDef.Signature);
otherName = metadata.GetString(methodSpecDef.Name);
break;
case HandleKind.MemberReference:
var methodSpecRef = metadata.GetMemberReference((MemberReferenceHandle)methodSpec.Method);
if (methodSpecRef.GetKind() != MemberReferenceKind.Method)
return false;
signature2 = metadata.GetBlobReader(methodSpecRef.Signature);
otherName = metadata.GetString(methodSpecRef.Name);
break;
default:
return false;
}
break;
case HandleKind.MemberReference:
if (genericParameterCount != 0)
return false;
var methodRef = metadata.GetMemberReference((MemberReferenceHandle)targetHandle);
if (methodRef.GetKind() != MemberReferenceKind.Method)
return false;
signature2 = metadata.GetBlobReader(methodRef.Signature);
otherName = metadata.GetString(methodRef.Name);
break;
default:
return false;
}
if (otherName != name)
return false;
return SignatureBlobComparer.EqualsMethodSignature(signature, signature2, metadata, metadata, skipModifiers: true);
static bool IsIdentityInstantiation(ref BlobReader reader, int expectedCount)
{
// Format: GENRICINST count type1 type2 ...
if (reader.ReadByte() != 0x0A) // GENERICINST
return false;
if (!reader.TryReadCompressedInteger(out int count) || count != expectedCount)
return false;
for (int i = 0; i < count; i++)
{
if (reader.ReadByte() != 0x1E) // ELEMENT_TYPE_MVAR
return false;
if (!reader.TryReadCompressedInteger(out int index) || index != i)
return false;
}
return true;
}
}
static bool IsSwitchOnStringCache(SRM.FieldDefinition field, MetadataReader metadata)
{
return metadata.GetString(field.Name).StartsWith("<>f__switch", StringComparison.Ordinal);

137
ICSharpCode.Decompiler/Metadata/SignatureBlobComparer.cs

@ -16,18 +16,74 @@ @@ -16,18 +16,74 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System.Diagnostics;
using System.Reflection.Metadata;
namespace ICSharpCode.Decompiler.Metadata
{
public static class SignatureBlobComparer
{
public static bool EqualsMethodSignature(BlobReader a, BlobReader b, MetadataReader contextForA, MetadataReader contextForB)
internal static (int GenericParameterCount, int ParameterCount) ReadParameterCount(ref BlobReader reader)
{
return EqualsMethodSignature(ref a, ref b, contextForA, contextForB);
if (reader.RemainingBytes == 0)
return (-1, -1);
var header = reader.ReadSignatureHeader();
int gp;
if (header.IsGeneric)
{
if (!reader.TryReadCompressedInteger(out gp))
gp = -1;
}
else
{
gp = 0;
}
if (!reader.TryReadCompressedInteger(out int p))
p = -1;
return (gp, p);
}
public static bool EqualsMethodSignature(BlobReader a, BlobReader b, MetadataReader contextForA, MetadataReader contextForB, bool skipModifiers = false)
{
return EqualsMethodSignature(ref a, ref b, contextForA, contextForB, skipModifiers);
}
static int SkipCustomModifiers(ref BlobReader reader, int typeCode)
{
while (typeCode is 0x1F or 0x20) // ELEMENT_TYPE_CMOD_REQD or ELEMENT_TYPE_CMOD_OPT
{
reader.ReadTypeHandle(); // discard modifier
if (!reader.TryReadCompressedInteger(out typeCode))
return -1;
}
return typeCode;
}
static bool TypeSlotEquals(ref BlobReader a, ref BlobReader b, MetadataReader contextForA, MetadataReader contextForB, bool skipModifiers)
{
if (!skipModifiers)
{
if (!IsSameCompressedInteger(ref a, ref b, out int typeCode))
return false;
return TypesAreEqual(ref a, ref b, contextForA, contextForB, typeCode, false);
}
if (!a.TryReadCompressedInteger(out int typeCodeA) || !b.TryReadCompressedInteger(out int typeCodeB))
return false;
typeCodeA = SkipCustomModifiers(ref a, typeCodeA);
typeCodeB = SkipCustomModifiers(ref b, typeCodeB);
if (typeCodeA < 0 || typeCodeB < 0)
return false;
if (typeCodeA != typeCodeB)
return false;
return TypesAreEqual(ref a, ref b, contextForA, contextForB, typeCodeA, true);
}
static bool EqualsMethodSignature(ref BlobReader a, ref BlobReader b, MetadataReader contextForA, MetadataReader contextForB)
static bool EqualsMethodSignature(ref BlobReader a, ref BlobReader b,
MetadataReader contextForA, MetadataReader contextForB, bool skipModifiers)
{
SignatureHeader header;
// compare signature headers
@ -42,26 +98,37 @@ namespace ICSharpCode.Decompiler.Metadata @@ -42,26 +98,37 @@ namespace ICSharpCode.Decompiler.Metadata
// read & compare parameter count
if (!IsSameCompressedInteger(ref a, ref b, out int totalParameterCount))
return false;
if (!IsSameCompressedInteger(ref a, ref b, out int typeCode))
return false;
if (!TypesAreEqual(ref a, ref b, contextForA, contextForB, typeCode))
// return type
if (!TypeSlotEquals(ref a, ref b, contextForA, contextForB, skipModifiers))
return false;
int i = 0;
for (; i < totalParameterCount; i++)
{
if (!IsSameCompressedInteger(ref a, ref b, out typeCode))
// peek at both sides for varargs sentinel or type code
if (!a.TryReadCompressedInteger(out int typeCodeA) || !b.TryReadCompressedInteger(out int typeCodeB))
return false;
// varargs sentinel
if (typeCode == 65)
if (typeCodeA == 65 || typeCodeB == 65)
{
if (typeCodeA != typeCodeB)
return false;
break;
if (!TypesAreEqual(ref a, ref b, contextForA, contextForB, typeCode))
}
if (skipModifiers)
{
typeCodeA = SkipCustomModifiers(ref a, typeCodeA);
typeCodeB = SkipCustomModifiers(ref b, typeCodeB);
if (typeCodeA < 0 || typeCodeB < 0)
return false;
}
if (typeCodeA != typeCodeB)
return false;
if (!TypesAreEqual(ref a, ref b, contextForA, contextForB, typeCodeA, skipModifiers))
return false;
}
for (; i < totalParameterCount; i++)
{
if (!IsSameCompressedInteger(ref a, ref b, out typeCode))
return false;
if (!TypesAreEqual(ref a, ref b, contextForA, contextForB, typeCode))
if (!TypeSlotEquals(ref a, ref b, contextForA, contextForB, skipModifiers))
return false;
}
return true;
@ -76,7 +143,7 @@ namespace ICSharpCode.Decompiler.Metadata @@ -76,7 +143,7 @@ namespace ICSharpCode.Decompiler.Metadata
{
if (!IsSameCompressedInteger(ref a, ref b, out int typeCode))
return false;
return TypesAreEqual(ref a, ref b, contextForA, contextForB, typeCode);
return TypesAreEqual(ref a, ref b, contextForA, contextForB, typeCode, false);
}
static bool IsSameCompressedInteger(ref BlobReader a, ref BlobReader b, out int value)
@ -89,14 +156,15 @@ namespace ICSharpCode.Decompiler.Metadata @@ -89,14 +156,15 @@ namespace ICSharpCode.Decompiler.Metadata
return a.TryReadCompressedSignedInteger(out value) && b.TryReadCompressedSignedInteger(out int otherValue) && value == otherValue;
}
static bool TypesAreEqual(ref BlobReader a, ref BlobReader b, MetadataReader contextForA, MetadataReader contextForB, int typeCode)
static bool TypesAreEqual(ref BlobReader a, ref BlobReader b, MetadataReader contextForA, MetadataReader contextForB, int typeCode, bool skipModifiers)
{
Debug.Assert(typeCode != 0x1F && typeCode != 0x20, "Unexpected ELEMENT_TYPE_CMOD_REQD or ELEMENT_TYPE_CMOD_OPT");
switch (typeCode)
{
case 0x1: // ELEMENT_TYPE_VOID
case 0x2: // ELEMENT_TYPE_BOOLEAN
case 0x3: // ELEMENT_TYPE_CHAR
case 0x4: // ELEMENT_TYPE_I1
case 0x2: // ELEMENT_TYPE_BOOLEAN
case 0x3: // ELEMENT_TYPE_CHAR
case 0x4: // ELEMENT_TYPE_I1
case 0x5: // ELEMENT_TYPE_U1
case 0x6: // ELEMENT_TYPE_I2
case 0x7: // ELEMENT_TYPE_U2
@ -112,24 +180,24 @@ namespace ICSharpCode.Decompiler.Metadata @@ -112,24 +180,24 @@ namespace ICSharpCode.Decompiler.Metadata
case 0x19: // ELEMENT_TYPE_U
case 0x1C: // ELEMENT_TYPE_OBJECT
return true;
case 0xF: // ELEMENT_TYPE_PTR
case 0x10: // ELEMENT_TYPE_BYREF
case 0xF: // ELEMENT_TYPE_PTR
case 0x10: // ELEMENT_TYPE_BYREF
case 0x45: // ELEMENT_TYPE_PINNED
case 0x1D: // ELEMENT_TYPE_SZARRAY
if (!IsSameCompressedInteger(ref a, ref b, out typeCode))
return false;
if (!TypesAreEqual(ref a, ref b, contextForA, contextForB, typeCode))
if (!TypesAreEqual(ref a, ref b, contextForA, contextForB, typeCode, skipModifiers))
return false;
return true;
case 0x1B: // ELEMENT_TYPE_FNPTR
if (!EqualsMethodSignature(ref a, ref b, contextForA, contextForB))
case 0x1B: // ELEMENT_TYPE_FNPTR
if (!EqualsMethodSignature(ref a, ref b, contextForA, contextForB, skipModifiers))
return false;
return true;
case 0x14: // ELEMENT_TYPE_ARRAY
case 0x14: // ELEMENT_TYPE_ARRAY
// element type
if (!IsSameCompressedInteger(ref a, ref b, out typeCode))
return false;
if (!TypesAreEqual(ref a, ref b, contextForA, contextForB, typeCode))
if (!TypesAreEqual(ref a, ref b, contextForA, contextForB, typeCode, skipModifiers))
return false;
// rank
if (!IsSameCompressedInteger(ref a, ref b, out _))
@ -151,22 +219,11 @@ namespace ICSharpCode.Decompiler.Metadata @@ -151,22 +219,11 @@ namespace ICSharpCode.Decompiler.Metadata
return false;
}
return true;
case 0x1F: // ELEMENT_TYPE_CMOD_REQD
case 0x20: // ELEMENT_TYPE_CMOD_OPT
// modifier
if (!TypeHandleEquals(ref a, ref b, contextForA, contextForB))
return false;
// unmodified type
if (!IsSameCompressedInteger(ref a, ref b, out typeCode))
return false;
if (!TypesAreEqual(ref a, ref b, contextForA, contextForB, typeCode))
return false;
return true;
case 0x15: // ELEMENT_TYPE_GENERICINST
case 0x15: // ELEMENT_TYPE_GENERICINST
// generic type
if (!IsSameCompressedInteger(ref a, ref b, out typeCode))
return false;
if (!TypesAreEqual(ref a, ref b, contextForA, contextForB, typeCode))
if (!TypesAreEqual(ref a, ref b, contextForA, contextForB, typeCode, skipModifiers))
return false;
if (!IsSameCompressedInteger(ref a, ref b, out int numOfArguments))
return false;
@ -174,12 +231,12 @@ namespace ICSharpCode.Decompiler.Metadata @@ -174,12 +231,12 @@ namespace ICSharpCode.Decompiler.Metadata
{
if (!IsSameCompressedInteger(ref a, ref b, out typeCode))
return false;
if (!TypesAreEqual(ref a, ref b, contextForA, contextForB, typeCode))
if (!TypesAreEqual(ref a, ref b, contextForA, contextForB, typeCode, skipModifiers))
return false;
}
return true;
case 0x13: // ELEMENT_TYPE_VAR
case 0x1E: // ELEMENT_TYPE_MVAR
case 0x1E: // ELEMENT_TYPE_MVAR
// index
if (!IsSameCompressedInteger(ref a, ref b, out _))
return false;
@ -200,6 +257,8 @@ namespace ICSharpCode.Decompiler.Metadata @@ -200,6 +257,8 @@ namespace ICSharpCode.Decompiler.Metadata
var typeB = b.ReadTypeHandle();
if (typeA.IsNil || typeB.IsNil)
return false;
if (contextForA == contextForB)
return typeA.Equals(typeB);
return typeA.GetFullTypeName(contextForA) == typeB.GetFullTypeName(contextForB);
}
}

Loading…
Cancel
Save