diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/MemberTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/MemberTests.cs index 1995ec1c5..e32cb1c2f 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/MemberTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/MemberTests.cs @@ -24,19 +24,57 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { internal class MemberTests { +#if CS72 + public interface IC + { + int MMM(in int x); + } + + public class C : IC + { + public int MMM(in int x) + { + return x; + } + } + + public interface IC2 + { + object MMM(in object x); + int MMM2(ref int x); + } + + public class C2 : IC2 + { + public object MMM(in dynamic x) + { + return x; + } + + public int MMM2(in int x) + { + return x; + } + + int IC2.MMM2(ref int x) + { + return MMM2(in x); + } + } +#endif public class IndexerNonDefaultName { [IndexerName("Foo")] #if ROSLYN public int this[int index] => 0; #else - #pragma warning disable format +#pragma warning disable format public int this[int index] { get { return 0; } } - #pragma warning restore format +#pragma warning restore format #endif } diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 6a00c714d..2ceaf206a 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -22,6 +22,7 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; using System.Reflection.PortableExecutable; using System.Text.RegularExpressions; using System.Threading; @@ -284,6 +285,8 @@ namespace ICSharpCode.Decompiler.CSharp var methodSemantics = module.MethodSemanticsLookup.GetSemantics(methodHandle).Item2; if (methodSemantics != 0 && methodSemantics != System.Reflection.MethodSemanticsAttributes.Other) return true; + if (IsInterfaceImplementationRuntimeHelper(module, methodHandle, methodSemantics)) + return true; if (settings.LocalFunctions && LocalFunctionDecompiler.IsLocalFunctionMethod(module, methodHandle)) return true; if (settings.AnonymousMethods && methodHandle.HasGeneratedName(metadata) && methodHandle.IsCompilerGenerated(metadata)) @@ -376,6 +379,135 @@ namespace ICSharpCode.Decompiler.CSharp return false; } + static bool IsInterfaceImplementationRuntimeHelper(PEFile module, MethodDefinitionHandle handle, System.Reflection.MethodSemanticsAttributes methodSemantics) + { + 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; + 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 instantiation = methodSpec.DecodeSignature(MetadataExtensions.MinimalSignatureTypeProvider, default); + 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: + 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); + } + static bool IsSwitchOnStringCache(SRM.FieldDefinition field, MetadataReader metadata) { return metadata.GetString(field.Name).StartsWith("<>f__switch", StringComparison.Ordinal); diff --git a/ICSharpCode.Decompiler/Metadata/SignatureBlobComparer.cs b/ICSharpCode.Decompiler/Metadata/SignatureBlobComparer.cs index 22f2c7629..66d9591ca 100644 --- a/ICSharpCode.Decompiler/Metadata/SignatureBlobComparer.cs +++ b/ICSharpCode.Decompiler/Metadata/SignatureBlobComparer.cs @@ -22,12 +22,41 @@ namespace ICSharpCode.Decompiler.Metadata { public static class SignatureBlobComparer { + internal static (int GenericParameterCount, int ParameterCount) ReadParameterCount(ref BlobReader reader) + { + 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) { - return EqualsMethodSignature(ref a, ref b, contextForA, contextForB); + return EqualsMethodSignature(ref a, ref b, contextForA, contextForB, false); } - static bool EqualsMethodSignature(ref BlobReader a, ref BlobReader b, MetadataReader contextForA, MetadataReader contextForB) + public static bool EqualsMethodSignature(BlobReader a, BlobReader b, MetadataReader contextForA, MetadataReader contextForB, bool skipModifiers) + { + return EqualsMethodSignature(ref a, ref b, contextForA, contextForB, skipModifiers); + } + + static bool EqualsMethodSignature(ref BlobReader a, ref BlobReader b, + MetadataReader contextForA, MetadataReader contextForB, bool skipModifiers) { SignatureHeader header; // compare signature headers @@ -44,7 +73,7 @@ namespace ICSharpCode.Decompiler.Metadata return false; if (!IsSameCompressedInteger(ref a, ref b, out int typeCode)) return false; - if (!TypesAreEqual(ref a, ref b, contextForA, contextForB, typeCode)) + if (!TypesAreEqual(ref a, ref b, contextForA, contextForB, typeCode, skipModifiers)) return false; int i = 0; for (; i < totalParameterCount; i++) @@ -54,14 +83,14 @@ namespace ICSharpCode.Decompiler.Metadata // varargs sentinel if (typeCode == 65) break; - if (!TypesAreEqual(ref a, ref b, contextForA, contextForB, typeCode)) + if (!TypesAreEqual(ref a, ref b, contextForA, contextForB, typeCode, 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 (!TypesAreEqual(ref a, ref b, contextForA, contextForB, typeCode, skipModifiers)) return false; } return true; @@ -76,7 +105,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,7 +118,7 @@ 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) { switch (typeCode) { @@ -118,18 +147,18 @@ namespace ICSharpCode.Decompiler.Metadata 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)) + if (!EqualsMethodSignature(ref a, ref b, contextForA, contextForB, skipModifiers)) return false; return true; 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 _)) @@ -154,19 +183,19 @@ namespace ICSharpCode.Decompiler.Metadata case 0x1F: // ELEMENT_TYPE_CMOD_REQD case 0x20: // ELEMENT_TYPE_CMOD_OPT // modifier - if (!TypeHandleEquals(ref a, ref b, contextForA, contextForB)) + if (!TypeHandleEquals(ref a, ref b, contextForA, contextForB) && !skipModifiers) return false; // unmodified 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; return true; 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,7 +203,7 @@ 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; @@ -200,6 +229,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); } }