/* Copyright (C) 2014-2019 de4dot@gmail.com This file is part of dnSpy dnSpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. dnSpy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with dnSpy. If not, see . */ using System; using System.Collections.ObjectModel; using System.Diagnostics; namespace dnSpy.Debugger.DotNet.Metadata { /// /// Type and member comparer options /// [Flags] public enum DmdSigComparerOptions { /// /// No bit is set /// None = 0, /// /// Don't compare type scope (assembly / module) /// DontCompareTypeScope = 1, /// /// Compare declaring type. It's ignored if it's a nested type and only used if it's a field, constructor, method, property, event, parameter /// CompareDeclaringType = 2, /// /// Don't compare return types /// DontCompareReturnType = 4, /// /// Case insensitive member names /// CaseInsensitiveMemberNames = 8, /// /// Project WinMD references /// ProjectWinMDReferences = 0x10, /// /// Check type equivalence /// CheckTypeEquivalence = 0x20, /// /// Compare optional and required C modifiers /// CompareCustomModifiers = 0x40, /// /// Compare generic type/method parameter's declaring member /// CompareGenericParameterDeclaringMember = 0x80, /// /// When comparing types, don't compare a multi-dimensional array's lower bounds and sizes /// IgnoreMultiDimensionalArrayLowerBoundsAndSizes = 0x100, } /// /// Compares types and members /// public struct DmdSigComparer { const int HASHCODE_MAGIC_TYPE = -674970533; const int HASHCODE_MAGIC_NESTED_TYPE = -1049070942; const int HASHCODE_MAGIC_ET_GENERICINST = -2050514639; const int HASHCODE_MAGIC_ET_VAR = 1288450097; const int HASHCODE_MAGIC_ET_MVAR = -990598495; const int HASHCODE_MAGIC_ET_ARRAY = -96331531; const int HASHCODE_MAGIC_ET_SZARRAY = 871833535; const int HASHCODE_MAGIC_ET_BYREF = -634749586; const int HASHCODE_MAGIC_ET_PTR = 1976400808; const int HASHCODE_MAGIC_ET_FNPTR = 68439620; DmdSigComparerOptions options; const int MAX_RECURSION_COUNT = 100; int recursionCounter; bool DontCompareTypeScope => (options & DmdSigComparerOptions.DontCompareTypeScope) != 0; bool CompareDeclaringType => (options & DmdSigComparerOptions.CompareDeclaringType) != 0; bool DontCompareReturnType => (options & DmdSigComparerOptions.DontCompareReturnType) != 0; bool CaseInsensitiveMemberNames => (options & DmdSigComparerOptions.CaseInsensitiveMemberNames) != 0; //TODO: Use this option bool ProjectWinMDReferences => (options & DmdSigComparerOptions.ProjectWinMDReferences) != 0; bool CheckTypeEquivalence => (options & DmdSigComparerOptions.CheckTypeEquivalence) != 0; bool CompareCustomModifiers => (options & DmdSigComparerOptions.CompareCustomModifiers) != 0; bool CompareGenericParameterDeclaringMember => (options & DmdSigComparerOptions.CompareGenericParameterDeclaringMember) != 0; bool IgnoreMultiDimensionalArrayLowerBoundsAndSizes => (options & DmdSigComparerOptions.IgnoreMultiDimensionalArrayLowerBoundsAndSizes) != 0; /// /// Constructor /// /// Options public DmdSigComparer(DmdSigComparerOptions options) { this.options = options; recursionCounter = 0; } bool IncrementRecursionCounter() { if (recursionCounter >= MAX_RECURSION_COUNT) return false; recursionCounter++; return true; } void DecrementRecursionCounter() => recursionCounter--; bool MemberNameEquals(string? a, string? b) { if (CaseInsensitiveMemberNames) return StringComparer.OrdinalIgnoreCase.Equals(a, b); return StringComparer.Ordinal.Equals(a, b); } int MemberNameGetHashCode(string? a) { if (a is null) return 0; if (CaseInsensitiveMemberNames) return StringComparer.OrdinalIgnoreCase.GetHashCode(a); return StringComparer.Ordinal.GetHashCode(a); } /// /// Compares two members /// /// First member /// Second member /// public bool Equals(DmdMemberInfo? a, DmdMemberInfo? b) { if ((object?)a == b) return true; if (a is null || b is null) return false; switch (a.MemberType) { case DmdMemberTypes.TypeInfo: case DmdMemberTypes.NestedType: return Equals((DmdType)a, b as DmdType); case DmdMemberTypes.Field: return Equals((DmdFieldInfo)a, b as DmdFieldInfo); case DmdMemberTypes.Method: case DmdMemberTypes.Constructor: return Equals((DmdMethodBase)a, b as DmdMethodBase); case DmdMemberTypes.Property: return Equals((DmdPropertyInfo)a, b as DmdPropertyInfo); case DmdMemberTypes.Event: return Equals((DmdEventInfo)a, b as DmdEventInfo); } return false; } /// /// Compares two types /// /// First type /// Second type /// public bool Equals(DmdType? a, DmdType? b) { if ((object?)a == b) return true; if (a is null || b is null) return false; if (!IncrementRecursionCounter()) return false; bool result; var at = a.TypeSignatureKind; if (at != b.TypeSignatureKind) result = false; else if (CompareCustomModifiers && !Equals(a.GetCustomModifiers(), b.GetCustomModifiers())) result = false; else { switch (at) { case DmdTypeSignatureKind.Type: result = MemberNameEquals(a.MetadataName, b.MetadataName) && MemberNameEquals(a.MetadataNamespace, b.MetadataNamespace) && Equals(a.DeclaringType, b.DeclaringType); // Type scope only needs to be checked if it's a non-nested type if (result && !DontCompareTypeScope && a.DeclaringType is null) { result = TypeScopeEquals(a, b); if (!result) { // One or both of the types could be exported types. We need to // resolve them and then compare again. var ra = a.ResolveNoThrow(); var rb = ra is null ? null : b.ResolveNoThrow(); result = ra is not null && rb is not null && TypeScopeEquals(ra, rb); if (!result && CheckTypeEquivalence) result = TIAHelper.Equivalent(ra, rb); } } break; case DmdTypeSignatureKind.Pointer: case DmdTypeSignatureKind.ByRef: case DmdTypeSignatureKind.SZArray: result = Equals(a.GetElementType(), b.GetElementType()); break; case DmdTypeSignatureKind.TypeGenericParameter: result = a.GenericParameterPosition == b.GenericParameterPosition; if (result && CompareGenericParameterDeclaringMember) result = Equals(a.DeclaringType, b.DeclaringType); break; case DmdTypeSignatureKind.MethodGenericParameter: result = a.GenericParameterPosition == b.GenericParameterPosition; if (result && CompareGenericParameterDeclaringMember) { options &= ~DmdSigComparerOptions.CompareGenericParameterDeclaringMember; result = Equals(a.DeclaringMethod, b.DeclaringMethod); options |= DmdSigComparerOptions.CompareGenericParameterDeclaringMember; } break; case DmdTypeSignatureKind.MDArray: result = a.GetArrayRank() == b.GetArrayRank() && (IgnoreMultiDimensionalArrayLowerBoundsAndSizes || (Equals(a.GetArraySizes(), b.GetArraySizes()) && Equals(a.GetArrayLowerBounds(), b.GetArrayLowerBounds()))) && Equals(a.GetElementType(), b.GetElementType()); break; case DmdTypeSignatureKind.GenericInstance: result = Equals(a.GetGenericTypeDefinition(), b.GetGenericTypeDefinition()) && Equals(a.GetGenericArguments(), b.GetGenericArguments()); break; case DmdTypeSignatureKind.FunctionPointer: result = Equals(a.GetFunctionPointerMethodSignature(), b.GetFunctionPointerMethodSignature()); break; default: throw new InvalidOperationException(); } } DecrementRecursionCounter(); return result; } /// /// Compares two fields /// /// First field /// Second field /// public bool Equals(DmdFieldInfo? a, DmdFieldInfo? b) { if ((object?)a == b) return true; if (a is null || b is null) return false; return MemberNameEquals(a.Name, b.Name) && Equals(a.FieldType, b.FieldType) && (!CompareDeclaringType || Equals(a.DeclaringType, b.DeclaringType)); } /// /// Compares two methods or constructors /// /// First method or constructor /// Second method or constructor /// public bool Equals(DmdMethodBase? a, DmdMethodBase? b) { if ((object?)a == b) return true; if (a is null || b is null) return false; return MemberNameEquals(a.Name, b.Name) && Equals(a.GetMethodSignature(), b.GetMethodSignature()) && (!CompareDeclaringType || Equals(a.DeclaringType, b.DeclaringType)); } /// /// Compares two properties /// /// First property /// Second property /// public bool Equals(DmdPropertyInfo? a, DmdPropertyInfo? b) { if ((object?)a == b) return true; if (a is null || b is null) return false; return MemberNameEquals(a.Name, b.Name) && Equals(a.GetMethodSignature(), b.GetMethodSignature()) && (!CompareDeclaringType || Equals(a.DeclaringType, b.DeclaringType)); } /// /// Compares two events /// /// First event /// Second event /// public bool Equals(DmdEventInfo? a, DmdEventInfo? b) { if ((object?)a == b) return true; if (a is null || b is null) return false; return MemberNameEquals(a.Name, b.Name) && Equals(a.EventHandlerType, b.EventHandlerType) && (!CompareDeclaringType || Equals(a.DeclaringType, b.DeclaringType)); } /// /// Compares two method parameters /// /// First method parameter /// Second method parameter /// public bool Equals(DmdParameterInfo? a, DmdParameterInfo? b) { if ((object?)a == b) return true; if (a is null || b is null) return false; return a.Position == b.Position && Equals(a.ParameterType, b.ParameterType) && (!CompareDeclaringType || Equals(a.Member, b.Member)); } /// /// Compares two assembly names /// /// First assembly name /// Second assembly name /// public bool Equals(IDmdAssemblyName? a, IDmdAssemblyName? b) { if ((object?)a == b) return true; if (a is null || b is null) return false; // We do not compare the version number. The runtime can redirect an assembly // reference from a requested version to any other version. // The public key token is also ignored. Only .NET Framwork checks it (.NET // and Unity ignore it). We could add a new option to ignore the PKT but it would // require too many changes to the code (they access singleton comparers) and isn't // worth it. It's also being replaced by .NET. It's not common for two // assemblies loaded in the same process to have the same assembly name but a // different public key token. const DmdAssemblyNameFlags flagsMask = DmdAssemblyNameFlags.ContentType_Mask; return (a.RawFlags & flagsMask) == (b.RawFlags & flagsMask) && StringComparer.OrdinalIgnoreCase.Equals(a.Name, b.Name) && StringComparer.OrdinalIgnoreCase.Equals(a.CultureName ?? string.Empty, b.CultureName ?? string.Empty); } /// /// Compares two method signatures /// /// First method signature /// Second method signature /// public bool Equals(DmdMethodSignature? a, DmdMethodSignature? b) { if ((object?)a == b) return true; if (a is null || b is null) return false; return a.Flags == b.Flags && a.GenericParameterCount == b.GenericParameterCount && (DontCompareReturnType || Equals(a.ReturnType, b.ReturnType)) && Equals(a.GetParameterTypes(), b.GetParameterTypes()) && Equals(a.GetVarArgsParameterTypes(), b.GetVarArgsParameterTypes()); } /// /// Compares two custom modifiers /// /// First custom modifier /// Second custom modifier /// public bool Equals(DmdCustomModifier a, DmdCustomModifier b) => a.IsRequired == b.IsRequired && Equals(a.Type, b.Type); bool Equals(ReadOnlyCollection? a, ReadOnlyCollection? b) { if (a == b) return true; if (a is null || b is null) return false; if (a.Count != b.Count) return false; for (int i = 0; i < a.Count; i++) { if (!Equals(a[i], b[i])) return false; } return true; } bool Equals(ReadOnlyCollection? a, ReadOnlyCollection? b) { if (a == b) return true; if (a is null || b is null) return false; if (a.Count != b.Count) return false; for (int i = 0; i < a.Count; i++) { if (!Equals(a[i], b[i])) return false; } return true; } bool Equals(ReadOnlyCollection? a, ReadOnlyCollection? b) { if (a == b) return true; if (a is null || b is null) return false; if (a.Count != b.Count) return false; for (int i = 0; i < a.Count; i++) { if (a[i] != b[i]) return false; } return true; } bool TypeScopeEquals(DmdType a, DmdType b) { Debug2.Assert(a is not null && b is not null && !a.HasElementType && !b.HasElementType); if (DontCompareTypeScope) return true; if ((object?)a == b) return true; var at = a.TypeScope; var bt = b.TypeScope; switch (at.Kind) { case DmdTypeScopeKind.Invalid: return false; case DmdTypeScopeKind.Module: switch (bt.Kind) { case DmdTypeScopeKind.Invalid: return false; case DmdTypeScopeKind.Module: return at.Data == bt.Data; case DmdTypeScopeKind.ModuleRef: return StringComparer.OrdinalIgnoreCase.Equals(((DmdModule)at.Data!).ScopeName, (string)bt.Data!) && Equals(((DmdModule)at.Data).Assembly.GetName(), (IDmdAssemblyName)bt.Data2!); case DmdTypeScopeKind.AssemblyRef: return Equals(((DmdModule)at.Data!).Assembly.GetName(), (IDmdAssemblyName)bt.Data!); default: throw new InvalidOperationException(); } case DmdTypeScopeKind.ModuleRef: switch (bt.Kind) { case DmdTypeScopeKind.Invalid: return false; case DmdTypeScopeKind.Module: return StringComparer.OrdinalIgnoreCase.Equals((string)at.Data!, ((DmdModule)bt.Data!).ScopeName) && Equals((IDmdAssemblyName)at.Data2!, ((DmdModule)bt.Data).Assembly.GetName()); case DmdTypeScopeKind.ModuleRef: return StringComparer.OrdinalIgnoreCase.Equals((string)at.Data!, (string)bt.Data!) && Equals((IDmdAssemblyName)at.Data2!, (IDmdAssemblyName)bt.Data2!); case DmdTypeScopeKind.AssemblyRef: return Equals((IDmdAssemblyName)at.Data2!, (IDmdAssemblyName)bt.Data!); default: throw new InvalidOperationException(); } case DmdTypeScopeKind.AssemblyRef: switch (bt.Kind) { case DmdTypeScopeKind.Invalid: return false; case DmdTypeScopeKind.Module: return Equals((IDmdAssemblyName)at.Data!, ((DmdModule)bt.Data!).Assembly.GetName()); case DmdTypeScopeKind.ModuleRef: return Equals((IDmdAssemblyName)at.Data!, (IDmdAssemblyName)bt.Data2!); case DmdTypeScopeKind.AssemblyRef: return Equals((IDmdAssemblyName)at.Data!, (IDmdAssemblyName)bt.Data!); default: throw new InvalidOperationException(); } default: throw new InvalidOperationException(); } } /// /// Gets the hash code of a member /// /// Member /// public int GetHashCode(DmdMemberInfo? a) { if (a is null) return 0; switch (a.MemberType) { case DmdMemberTypes.TypeInfo: case DmdMemberTypes.NestedType: return GetHashCode((DmdType)a); case DmdMemberTypes.Field: return GetHashCode((DmdFieldInfo)a); case DmdMemberTypes.Method: case DmdMemberTypes.Constructor: return GetHashCode((DmdMethodBase)a); case DmdMemberTypes.Property: return GetHashCode((DmdPropertyInfo)a); case DmdMemberTypes.Event: return GetHashCode((DmdEventInfo)a); } Debug.Fail($"Unknown type: {a.GetType()}"); return 0; } /// /// Gets the hash code of a type /// /// Type /// public int GetHashCode(DmdType? a) { if (a is null) return 0; if (!IncrementRecursionCounter()) return 0; int hc = CompareCustomModifiers ? GetHashCode(a.GetCustomModifiers()) : 0; switch (a.TypeSignatureKind) { case DmdTypeSignatureKind.Type: hc ^= a.DeclaringType is null ? HASHCODE_MAGIC_TYPE : HASHCODE_MAGIC_NESTED_TYPE; hc ^= MemberNameGetHashCode(a.MetadataName); hc ^= MemberNameGetHashCode(a.MetadataNamespace); hc ^= GetHashCode(a.DeclaringType); // Don't include the type scope in the hash since it can be one of Module, ModuleRef, AssemblyRef // and we could only hash the common denominator which isn't much at all. break; case DmdTypeSignatureKind.Pointer: hc ^= HASHCODE_MAGIC_ET_PTR ^ GetHashCode(a.GetElementType()); break; case DmdTypeSignatureKind.ByRef: hc ^= HASHCODE_MAGIC_ET_BYREF ^ GetHashCode(a.GetElementType()); break; case DmdTypeSignatureKind.SZArray: hc ^= HASHCODE_MAGIC_ET_SZARRAY ^ GetHashCode(a.GetElementType()); break; case DmdTypeSignatureKind.TypeGenericParameter: hc ^= HASHCODE_MAGIC_ET_VAR ^ a.GenericParameterPosition; if (CompareGenericParameterDeclaringMember) hc ^= GetHashCode(a.DeclaringType); break; case DmdTypeSignatureKind.MethodGenericParameter: hc ^= HASHCODE_MAGIC_ET_MVAR ^ a.GenericParameterPosition; if (CompareGenericParameterDeclaringMember) { options &= ~DmdSigComparerOptions.CompareGenericParameterDeclaringMember; hc ^= GetHashCode(a.DeclaringMethod); options |= DmdSigComparerOptions.CompareGenericParameterDeclaringMember; } break; case DmdTypeSignatureKind.MDArray: hc ^= HASHCODE_MAGIC_ET_ARRAY; hc ^= a.GetArrayRank(); if (!IgnoreMultiDimensionalArrayLowerBoundsAndSizes) { hc ^= GetHashCode(a.GetArraySizes()); hc ^= GetHashCode(a.GetArrayLowerBounds()); } hc ^= GetHashCode(a.GetElementType()); break; case DmdTypeSignatureKind.GenericInstance: hc ^= HASHCODE_MAGIC_ET_GENERICINST; hc ^= GetHashCode(a.GetGenericTypeDefinition()); hc ^= GetHashCode(a.GetGenericArguments()); break; case DmdTypeSignatureKind.FunctionPointer: hc ^= HASHCODE_MAGIC_ET_FNPTR; hc ^= GetHashCode(a.GetFunctionPointerMethodSignature()); break; default: throw new InvalidOperationException(); } DecrementRecursionCounter(); return hc; } /// /// Gets the hash code of a field /// /// Field /// public int GetHashCode(DmdFieldInfo? a) { if (a is null) return 0; int hc = MemberNameGetHashCode(a.Name); hc ^= GetHashCode(a.FieldType); if (CompareDeclaringType) hc ^= GetHashCode(a.DeclaringType); return hc; } /// /// Gets the hash code of a method or constructor /// /// Method or constructor /// public int GetHashCode(DmdMethodBase? a) { if (a is null) return 0; int hc = MemberNameGetHashCode(a.Name); hc ^= GetHashCode(a.GetMethodSignature()); if (CompareDeclaringType) hc ^= GetHashCode(a.DeclaringType); return hc; } /// /// Gets the hash code of a property /// /// Property /// public int GetHashCode(DmdPropertyInfo? a) { if (a is null) return 0; int hc = MemberNameGetHashCode(a.Name); hc ^= GetHashCode(a.GetMethodSignature()); if (CompareDeclaringType) hc ^= GetHashCode(a.DeclaringType); return hc; } /// /// Gets the hash code of an event /// /// Event /// public int GetHashCode(DmdEventInfo? a) { if (a is null) return 0; int hc = MemberNameGetHashCode(a.Name); hc ^= GetHashCode(a.EventHandlerType); if (CompareDeclaringType) hc ^= GetHashCode(a.DeclaringType); return hc; } /// /// Gets the hash code of a method parameter /// /// Method parameter /// public int GetHashCode(DmdParameterInfo? a) { if (a is null) return 0; int hc = a.Position; hc ^= GetHashCode(a.ParameterType); if (CompareDeclaringType) hc ^= GetHashCode(a.Member); return hc; } /// /// Gets the hash code of an assembly name /// /// Assembly name /// public int GetHashCode(IDmdAssemblyName? a) { if (a is null) return 0; return StringComparer.OrdinalIgnoreCase.GetHashCode(a.Name ?? string.Empty); } /// /// Gets the hash code of a method signature /// /// Method signature /// public int GetHashCode(DmdMethodSignature? a) { if (a is null) return 0; int hc = (int)a.Flags; hc ^= a.GenericParameterCount; if (!DontCompareReturnType) hc ^= GetHashCode(a.ReturnType); hc ^= GetHashCode(a.GetParameterTypes()); hc ^= GetHashCode(a.GetVarArgsParameterTypes()); return hc; } /// /// Gets the hash code of a custom modifier /// /// Custom modifier /// public int GetHashCode(DmdCustomModifier a) => (a.IsRequired ? -1 : 0) ^ GetHashCode(a.Type); int GetHashCode(ReadOnlyCollection? a) { if (a is null) return 0; int hc = a.Count; for (int i = 0; i < a.Count; i++) hc ^= GetHashCode(a[i]); return hc; } int GetHashCode(ReadOnlyCollection? a) { if (a is null) return 0; int hc = a.Count; for (int i = 0; i < a.Count; i++) hc ^= GetHashCode(a[i]); return hc; } int GetHashCode(ReadOnlyCollection? a) { if (a is null) return 0; int hc = a.Count; for (int i = 0; i < a.Count; i++) hc ^= a[i]; return hc; } } }