diff --git a/blocks/MemberReferenceHelper.cs b/blocks/MemberReferenceHelper.cs index 180629c6..6f2b25be 100644 --- a/blocks/MemberReferenceHelper.cs +++ b/blocks/MemberReferenceHelper.cs @@ -117,6 +117,33 @@ namespace de4dot.blocks { } } + public class TypeReferenceSameVersionKey { + readonly TypeReference typeRef; + + public TypeReference TypeReference { + get { return typeRef; } + } + + public TypeReferenceSameVersionKey(TypeReference typeRef) { + this.typeRef = typeRef; + } + + public override int GetHashCode() { + return MemberReferenceHelper.typeReferenceHashCodeSameVersion(typeRef); + } + + public override bool Equals(object obj) { + var other = obj as TypeReferenceSameVersionKey; + if (other == null) + return false; + return MemberReferenceHelper.compareTypeReferenceSameVersion(typeRef, other.typeRef); + } + + public override string ToString() { + return typeRef.ToString(); + } + } + public class FieldReferenceKey { readonly FieldReference fieldRef; @@ -333,17 +360,14 @@ namespace de4dot.blocks { return string.Format("[{0}]{1}", getCanonicalizedScopeName(scope), fullName); } - public static string getCanonicalizedScopeName(IMetadataScope scope) { - AssemblyNameReference asmRef = null; - + public static AssemblyNameReference getAssemblyNameReference(IMetadataScope scope) { switch (scope.MetadataScopeType) { case MetadataScopeType.AssemblyNameReference: - asmRef = (AssemblyNameReference)scope; - break; + return (AssemblyNameReference)scope; case MetadataScopeType.ModuleDefinition: var module = (ModuleDefinition)scope; if (module.Assembly != null) - asmRef = module.Assembly.Name; + return module.Assembly.Name; break; case MetadataScopeType.ModuleReference: break; @@ -351,6 +375,11 @@ namespace de4dot.blocks { throw new ApplicationException(string.Format("Invalid scope type: {0}", scope.GetType())); } + return null; + } + + public static string getCanonicalizedScopeName(IMetadataScope scope) { + var asmRef = getAssemblyNameReference(scope); if (asmRef != null) { // The version number should be ignored. Older code may reference an old version of // the assembly, but if the newer one has been loaded, that one is used. @@ -359,6 +388,13 @@ namespace de4dot.blocks { return string.Format("{0}", scope.ToString().ToLowerInvariant()); } + public static string getCanonicalizedScopeAndVersion(IMetadataScope scope) { + var asmRef = getAssemblyNameReference(scope); + if (asmRef != null) + return string.Format("{0}, Version={1}", asmRef.Name.ToLowerInvariant(), asmRef.Version); + return string.Format("{0}, Version=", scope.ToString().ToLowerInvariant()); + } + public static bool compareScope(IMetadataScope a, IMetadataScope b) { if (ReferenceEquals(a, b)) return true; @@ -373,6 +409,20 @@ namespace de4dot.blocks { return getCanonicalizedScopeName(a).GetHashCode(); } + public static bool compareScopeSameVersion(IMetadataScope a, IMetadataScope b) { + if (ReferenceEquals(a, b)) + return true; + if (a == null || b == null) + return false; + return getCanonicalizedScopeAndVersion(a) == getCanonicalizedScopeAndVersion(b); + } + + public static int scopeHashCodeSameVersion(IMetadataScope a) { + if (a == null) + return 0; + return getCanonicalizedScopeAndVersion(a).GetHashCode(); + } + public static bool compareEventReference(EventReference a, EventReference b) { if (ReferenceEquals(a, b)) return true; @@ -811,6 +861,39 @@ namespace de4dot.blocks { return res; } + public static bool compareTypeReferenceSameVersion(TypeReference a, TypeReference b) { + if (ReferenceEquals(a, b)) + return true; + if (a == null || b == null) + return false; + + if ((a.GetType() != typeof(TypeReference) && a.GetType() != typeof(TypeDefinition)) || + (b.GetType() != typeof(TypeReference) && b.GetType() != typeof(TypeDefinition))) + throw new ApplicationException("arg must be exactly of type TypeReference or TypeDefinition"); + + return a.Name == b.Name && + a.Namespace == b.Namespace && + compareTypeReferenceSameVersion(a.DeclaringType, b.DeclaringType) && + compareScopeSameVersion(a.Scope, b.Scope); + } + + public static int typeReferenceHashCodeSameVersion(TypeReference a) { + if (a == null) + return 0; + + if (a.GetType() != typeof(TypeReference) && a.GetType() != typeof(TypeDefinition)) + throw new ApplicationException("arg must be exactly of type TypeReference or TypeDefinition"); + + int res = 0; + + res += a.Name.GetHashCode(); + res += a.Namespace.GetHashCode(); + res += typeReferenceHashCodeSameVersion(a.DeclaringType); + res += scopeHashCodeSameVersion(a.Scope); + + return res; + } + static bool compareGenericParameterProvider(IGenericParameterProvider a, IGenericParameterProvider b) { if (ReferenceEquals(a, b)) return true; diff --git a/de4dot.code/renamer/asmmodules/Modules.cs b/de4dot.code/renamer/asmmodules/Modules.cs index 6b9efd0a..bc1fb7ef 100644 --- a/de4dot.code/renamer/asmmodules/Modules.cs +++ b/de4dot.code/renamer/asmmodules/Modules.cs @@ -225,7 +225,107 @@ namespace de4dot.renamer.asmmodules { } } - Dictionary otherTypesDict = new Dictionary(); + class AssemblyKeyDictionary where T : class { + Dictionary dict = new Dictionary(); + Dictionary> refs = new Dictionary>(); + + public T this[TypeReference type] { + get { + T value; + if (tryGetValue(type, out value)) + return value; + throw new KeyNotFoundException(); + } + set { + var key = new TypeReferenceSameVersionKey(type); + dict[key] = value; + + if (value != null) { + var key2 = new TypeReferenceKey(type); + List list; + if (!refs.TryGetValue(key2, out list)) + refs[key2] = list = new List(); + list.Add(type); + } + } + } + + public bool tryGetValue(TypeReference type, out T value) { + var key = new TypeReferenceSameVersionKey(type); + if (dict.TryGetValue(key, out value)) + return true; + return false; + } + + public bool tryGetSimilarValue(TypeReference type, out T value) { + var key2 = new TypeReferenceKey(type); + List list; + if (!refs.TryGetValue(key2, out list)) { + value = default(T); + return false; + } + + // Find a type whose version is >= type's version and closest to it. + + TypeReference foundType = null; + var typeAsmName = MemberReferenceHelper.getAssemblyNameReference(type.Scope); + AssemblyNameReference foundAsmName = null; + foreach (var otherRef in list) { + var key = new TypeReferenceSameVersionKey(otherRef); + if (!dict.TryGetValue(key, out value)) + continue; + + if (typeAsmName == null) { + foundType = otherRef; + break; + } + + var otherAsmName = MemberReferenceHelper.getAssemblyNameReference(otherRef.Scope); + if (otherAsmName == null) + continue; + // Check pkt or we could return a type in eg. a SL assembly when it's not a SL app. + if (!same(typeAsmName.PublicKeyToken, otherAsmName.PublicKeyToken)) + continue; + if (typeAsmName.Version > otherAsmName.Version) + continue; // old version + + if (foundType == null) { + foundAsmName = otherAsmName; + foundType = otherRef; + continue; + } + + if (foundAsmName.Version <= otherAsmName.Version) + continue; + foundAsmName = otherAsmName; + foundType = otherRef; + } + + if (foundType != null) { + value = dict[new TypeReferenceSameVersionKey(foundType)]; + return true; + } + + value = default(T); + return false; + } + + bool same(byte[] a, byte[] b) { + if (ReferenceEquals(a, b)) + return true; + if (a == null || b == null) + return false; + if (a.Length != b.Length) + return false; + for (int i = 0; i < a.Length; i++) { + if (a[i] != b[i]) + return false; + } + return true; + } + } + + AssemblyKeyDictionary typeToTypeDefDict = new AssemblyKeyDictionary(); ExternalAssemblies externalAssemblies = new ExternalAssemblies(); TypeDef resolveOther(TypeReference type) { if (type == null) @@ -233,14 +333,23 @@ namespace de4dot.renamer.asmmodules { type = type.GetElementType(); TypeDef typeDef; - var key = new TypeReferenceKey(type); - if (otherTypesDict.TryGetValue(key, out typeDef)) + if (typeToTypeDefDict.tryGetValue(type, out typeDef)) return typeDef; - otherTypesDict[key] = null; // In case of a circular reference - TypeDefinition typeDefinition = externalAssemblies.resolve(type); - if (typeDefinition == null) - return null; + var typeDefinition = externalAssemblies.resolve(type); + if (typeDefinition == null) { + typeToTypeDefDict.tryGetSimilarValue(type, out typeDef); + typeToTypeDefDict[type] = typeDef; + return typeDef; + } + + if (typeToTypeDefDict.tryGetValue(typeDefinition, out typeDef)) { + typeToTypeDefDict[type] = typeDef; + return typeDef; + } + + typeToTypeDefDict[type] = null; // In case of a circular reference + typeToTypeDefDict[typeDefinition] = null; typeDef = new TypeDef(typeDefinition, null, 0); typeDef.addMembers(); @@ -253,7 +362,11 @@ namespace de4dot.renamer.asmmodules { var baseDef = resolveOther(typeDef.TypeDefinition.BaseType); if (baseDef != null) typeDef.addBaseType(baseDef, typeDef.TypeDefinition.BaseType); - return otherTypesDict[key] = typeDef; + + typeToTypeDefDict[type] = typeDef; + if (type != typeDefinition) + typeToTypeDefDict[typeDefinition] = typeDef; + return typeDef; } public MethodNameScopes initializeVirtualMembers() {