From 00fcd0550fcdda621c2e4662f6ddd55ce673b931 Mon Sep 17 00:00:00 2001 From: Gegy Date: Thu, 24 Jan 2019 14:48:32 +0200 Subject: [WIP] Mapping rework (#91) * Move packages * Mapping & entry refactor: first pass * Fix deobf -> obf tree remapping * Resolve various issues * Give all entries the potential for parents and treat inner classes as children * Deobf UI tree elements * Tests pass * Sort mapping output * Fix delta tracking * Index separation and first pass for #97 * Keep track of remapped jar index * Fix child entries not being remapped * Drop non-root entries * Track dropped mappings * Fix enigma mapping ordering * EntryTreeNode interface * Small tweaks * Naive full index remap on rename * Entries can resolve to more than one root entry * Support alternative resolution strategies * Bridge method resolution * Tests pass * Fix mappings being used where there are none * Fix methods with different descriptors being considered unique. closes #89 --- src/main/java/cuchaz/enigma/analysis/JarIndex.java | 583 --------------------- 1 file changed, 583 deletions(-) delete mode 100644 src/main/java/cuchaz/enigma/analysis/JarIndex.java (limited to 'src/main/java/cuchaz/enigma/analysis/JarIndex.java') diff --git a/src/main/java/cuchaz/enigma/analysis/JarIndex.java b/src/main/java/cuchaz/enigma/analysis/JarIndex.java deleted file mode 100644 index 361c8e7..0000000 --- a/src/main/java/cuchaz/enigma/analysis/JarIndex.java +++ /dev/null @@ -1,583 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.analysis; - -import com.google.common.collect.*; -import cuchaz.enigma.bytecode.AccessFlags; -import cuchaz.enigma.mapping.*; -import cuchaz.enigma.mapping.entry.*; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.Opcodes; - -import java.util.*; - -public class JarIndex { - - private final ReferencedEntryPool entryPool; - - private Set obfClassEntries; - private TranslationIndex translationIndex; - private Map access; - private Multimap fields; - private Multimap methods; - private Multimap methodImplementations; - private Multimap> methodsReferencing; - private Multimap> methodsReferencingClasses; - private Multimap methodReferences; - private Multimap> fieldReferences; - private Multimap innerClassesByOuter; - private Map outerClassesByInner; - private Map bridgedMethods; - private Set syntheticMethods; - - public JarIndex(ReferencedEntryPool entryPool) { - this.entryPool = entryPool; - this.obfClassEntries = Sets.newHashSet(); - this.translationIndex = new TranslationIndex(entryPool); - this.access = Maps.newHashMap(); - this.fields = HashMultimap.create(); - this.methods = HashMultimap.create(); - this.methodImplementations = HashMultimap.create(); - this.methodsReferencingClasses = HashMultimap.create(); - this.methodsReferencing = HashMultimap.create(); - this.methodReferences = HashMultimap.create(); - this.fieldReferences = HashMultimap.create(); - this.innerClassesByOuter = HashMultimap.create(); - this.outerClassesByInner = Maps.newHashMap(); - this.bridgedMethods = Maps.newHashMap(); - this.syntheticMethods = Sets.newHashSet(); - } - - public void indexJar(ParsedJar jar, boolean buildInnerClasses) { - - // step 1: read the class names - obfClassEntries.addAll(jar.getClassEntries()); - - // step 2: index classes, fields, methods, interfaces - if (buildInnerClasses) { - // + step 5: index inner classes - jar.visitReader(name -> new IndexClassVisitor(this, Opcodes.ASM5, new IndexInnerClassVisitor(this, Opcodes.ASM5)), ClassReader.SKIP_CODE); - } else { - jar.visitReader(name -> new IndexClassVisitor(this, Opcodes.ASM5), ClassReader.SKIP_CODE); - } - - // step 3: index field, method, constructor references - jar.visitReader(name -> new IndexReferenceVisitor(this, Opcodes.ASM5), ClassReader.SKIP_FRAMES); - - // step 4: index access and bridged methods - for (MethodDefEntry methodEntry : methods.values()) { - // look for access and bridged methods - MethodEntry accessedMethod = findAccessMethod(methodEntry); - if (accessedMethod != null) { - if (isBridgedMethod(accessedMethod, methodEntry)) { - this.bridgedMethods.put(methodEntry, accessedMethod); - } - } - } - - if (buildInnerClasses) { - // step 6: update other indices with inner class info - Map renames = Maps.newHashMap(); - for (ClassEntry innerClassEntry : this.innerClassesByOuter.values()) { - String newName = innerClassEntry.buildClassEntry(getObfClassChain(innerClassEntry)).getName(); - if (!innerClassEntry.getName().equals(newName)) { - // DEBUG - //System.out.println("REPLACE: " + innerClassEntry.getName() + " WITH " + newName); - renames.put(innerClassEntry.getName(), newName); - } - } - EntryRenamer.renameClassesInSet(renames, this.obfClassEntries); - this.translationIndex.renameClasses(renames); - EntryRenamer.renameClassesInMultimap(renames, this.methodImplementations); - EntryRenamer.renameClassesInMultimap(renames, this.methodsReferencingClasses); - EntryRenamer.renameClassesInMultimap(renames, this.methodsReferencing); - EntryRenamer.renameClassesInMultimap(renames, this.methodReferences); - EntryRenamer.renameClassesInMultimap(renames, this.fieldReferences); - EntryRenamer.renameClassesInMap(renames, this.access); - } - } - - protected ClassDefEntry indexClass(int access, String name, String signature, String superName, String[] interfaces) { - for (String interfaceName : interfaces) { - if (name.equals(interfaceName)) { - throw new IllegalArgumentException("Class cannot be its own interface! " + name); - } - } - ClassDefEntry entry = this.translationIndex.indexClass(access, name, signature, superName, interfaces); - this.access.put(entry, entry.getAccess()); - return entry; - } - - protected void indexField(ClassDefEntry owner, int access, String name, String desc, String signature) { - FieldDefEntry fieldEntry = new FieldDefEntry(owner, name, new TypeDescriptor(desc), Signature.createTypedSignature(signature), new AccessFlags(access)); - this.translationIndex.indexField(fieldEntry); - this.access.put(fieldEntry, fieldEntry.getAccess()); - this.fields.put(fieldEntry.getOwnerClassEntry(), fieldEntry); - } - - protected void indexMethod(ClassDefEntry owner, int access, String name, String desc, String signature) { - MethodDefEntry methodEntry = new MethodDefEntry(owner, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access)); - this.translationIndex.indexMethod(methodEntry); - this.access.put(methodEntry, methodEntry.getAccess()); - this.methods.put(methodEntry.getOwnerClassEntry(), methodEntry); - - if (new AccessFlags(access).isSynthetic()) { - syntheticMethods.add(methodEntry); - } - - // we don't care about constructors here - if (!methodEntry.isConstructor()) { - // index implementation - this.methodImplementations.put(methodEntry.getClassName(), methodEntry); - } - } - - protected void indexMethodCall(MethodDefEntry callerEntry, String owner, String name, String desc) { - ClassEntry referencedClass = entryPool.getClass(owner); - MethodEntry referencedMethod = new MethodEntry(referencedClass, name, new MethodDescriptor(desc)); - ClassEntry resolvedClassEntry = translationIndex.resolveEntryOwner(referencedMethod); - if (resolvedClassEntry != null && !resolvedClassEntry.equals(referencedMethod.getOwnerClassEntry())) { - referencedMethod = referencedMethod.updateOwnership(resolvedClassEntry); - } - methodsReferencing.put(referencedMethod, new EntryReference<>(referencedMethod, referencedMethod.getName(), callerEntry)); - if (referencedMethod.isConstructor()) { - methodsReferencingClasses.put(referencedClass, new EntryReference<>(referencedClass, referencedMethod.getName(), callerEntry)); - } - methodReferences.put(callerEntry, referencedMethod); - } - - protected void indexFieldAccess(MethodDefEntry callerEntry, String owner, String name, String desc) { - FieldEntry referencedField = new FieldEntry(entryPool.getClass(owner), name, new TypeDescriptor(desc)); - ClassEntry resolvedClassEntry = translationIndex.resolveEntryOwner(referencedField); - if (resolvedClassEntry != null && !resolvedClassEntry.equals(referencedField.getOwnerClassEntry())) { - referencedField = referencedField.updateOwnership(resolvedClassEntry); - } - fieldReferences.put(referencedField, new EntryReference<>(referencedField, referencedField.getName(), callerEntry)); - } - - public void indexInnerClass(ClassEntry innerEntry, ClassEntry outerEntry) { - this.innerClassesByOuter.put(outerEntry, innerEntry); - this.outerClassesByInner.putIfAbsent(innerEntry, outerEntry); - } - - private MethodEntry findAccessMethod(MethodDefEntry method) { - - // we want to find all compiler-added methods that directly call another with no processing - - // skip non-synthetic methods - if (!method.getAccess().isSynthetic()) { - return null; - } - - // get all the methods that we call - final Collection referencedMethods = methodReferences.get(method); - - // is there just one? - if (referencedMethods.size() != 1) { - return null; - } - - return referencedMethods.stream().findFirst().orElse(null); - } - - private boolean isBridgedMethod(MethodEntry called, MethodEntry access) { - // Bridged methods will always have the same name as the method they are calling - // They will also have the same amount of parameters (though equal descriptors cannot be guaranteed) - if (!called.getName().equals(access.getName()) || called.getDesc().getArgumentDescs().size() != access.getDesc().getArgumentDescs().size()) { - return false; - } - - TypeDescriptor accessReturn = access.getDesc().getReturnDesc(); - TypeDescriptor calledReturn = called.getDesc().getReturnDesc(); - if (calledReturn.isVoid() || calledReturn.isPrimitive() || accessReturn.isVoid() || accessReturn.isPrimitive()) { - return false; - } - - // Bridged methods will never have the same type as what they are calling - if (accessReturn.equals(calledReturn)) { - return false; - } - - String accessType = accessReturn.toString(); - - // If we're casting down from generic type to type-erased Object we're a bridge method - if (accessType.equals("Ljava/lang/Object;")) { - return true; - } - - // Now we need to detect cases where we are being casted down to a higher type bound - List calledAncestry = translationIndex.getAncestry(calledReturn.getTypeEntry()); - return calledAncestry.contains(accessReturn.getTypeEntry()); - } - - public Set getObfClassEntries() { - return this.obfClassEntries; - } - - public Collection getObfFieldEntries() { - return this.fields.values(); - } - - public Collection getObfFieldEntries(ClassEntry classEntry) { - return this.fields.get(classEntry); - } - - public Collection getObfBehaviorEntries() { - return this.methods.values(); - } - - public Collection getObfBehaviorEntries(ClassEntry classEntry) { - return this.methods.get(classEntry); - } - - public TranslationIndex getTranslationIndex() { - return this.translationIndex; - } - - @Deprecated - public Access getAccess(Entry entry) { - AccessFlags flags = getAccessFlags(entry); - return flags != null ? Access.get(flags) : null; - } - - public AccessFlags getAccessFlags(Entry entry) { - return this.access.get(entry); - } - - public ClassInheritanceTreeNode getClassInheritance(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) { - - // get the root node - List ancestry = Lists.newArrayList(); - ancestry.add(obfClassEntry.getName()); - for (ClassEntry classEntry : this.translationIndex.getAncestry(obfClassEntry)) { - if (containsObfClass(classEntry)) { - ancestry.add(classEntry.getName()); - } - } - ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode( - deobfuscatingTranslator, - ancestry.get(ancestry.size() - 1) - ); - - // expand all children recursively - rootNode.load(this.translationIndex, true); - - return rootNode; - } - - public ClassImplementationsTreeNode getClassImplementations(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) { - - // is this even an interface? - if (isInterface(obfClassEntry.getClassName())) { - ClassImplementationsTreeNode node = new ClassImplementationsTreeNode(deobfuscatingTranslator, obfClassEntry); - node.load(this); - return node; - } - return null; - } - - public MethodInheritanceTreeNode getMethodInheritance(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) { - // travel to the ancestor implementation - LinkedList entries = new LinkedList<>(); - entries.add(obfMethodEntry.getOwnerClassEntry()); - - // TODO: This could be optimized to not go through interfaces repeatedly... - - ClassEntry baseImplementationClassEntry = obfMethodEntry.getOwnerClassEntry(); - - for (ClassEntry itf : getInterfaces(obfMethodEntry.getOwnerClassEntry().getClassName())) { - MethodEntry itfMethodEntry = entryPool.getMethod(itf, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString()); - if (itfMethodEntry != null && containsObfMethod(itfMethodEntry)) { - baseImplementationClassEntry = itf; - } - } - - for (ClassEntry ancestorClassEntry : this.translationIndex.getAncestry(entries.remove())) { - MethodEntry ancestorMethodEntry = entryPool.getMethod(ancestorClassEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString()); - if (ancestorMethodEntry != null) { - if (containsObfMethod(ancestorMethodEntry)) { - baseImplementationClassEntry = ancestorClassEntry; - } - - for (ClassEntry itf : getInterfaces(ancestorClassEntry.getClassName())) { - MethodEntry itfMethodEntry = entryPool.getMethod(itf, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString()); - if (itfMethodEntry != null && containsObfMethod(itfMethodEntry)) { - baseImplementationClassEntry = itf; - } - } - } - } - - // make a root node at the base - MethodEntry methodEntry = entryPool.getMethod(baseImplementationClassEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString()); - MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode( - deobfuscatingTranslator, - methodEntry, - containsObfMethod(methodEntry) - ); - - // expand the full tree - rootNode.load(this, true); - - return rootNode; - } - - public List getMethodImplementations(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) { - - List interfaceMethodEntries = Lists.newArrayList(); - - // is this method on an interface? - if (isInterface(obfMethodEntry.getClassName())) { - interfaceMethodEntries.add(obfMethodEntry); - } else { - // get the interface class - for (ClassEntry interfaceEntry : getInterfaces(obfMethodEntry.getClassName())) { - - // is this method defined in this interface? - MethodEntry methodInterface = entryPool.getMethod(interfaceEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString()); - if (methodInterface != null && containsObfMethod(methodInterface)) { - interfaceMethodEntries.add(methodInterface); - } - } - } - - List nodes = Lists.newArrayList(); - if (!interfaceMethodEntries.isEmpty()) { - for (MethodEntry interfaceMethodEntry : interfaceMethodEntries) { - MethodImplementationsTreeNode node = new MethodImplementationsTreeNode(deobfuscatingTranslator, interfaceMethodEntry); - node.load(this); - nodes.add(node); - } - } - return nodes; - } - - public Set getRelatedMethodImplementations(MethodEntry obfMethodEntry) { - AccessFlags flags = getAccessFlags(obfMethodEntry); - if (flags.isPrivate() || flags.isStatic()) { - return Collections.singleton(obfMethodEntry); - } - - Set methodEntries = Sets.newHashSet(); - getRelatedMethodImplementations(methodEntries, getMethodInheritance(new DirectionalTranslator(entryPool), obfMethodEntry)); - return methodEntries; - } - - private void getRelatedMethodImplementations(Set methodEntries, MethodInheritanceTreeNode node) { - MethodEntry methodEntry = node.getMethodEntry(); - if (methodEntries.contains(methodEntry)) { - return; - } - - if (containsObfMethod(methodEntry)) { - AccessFlags flags = getAccessFlags(methodEntry); - if (!flags.isPrivate() && !flags.isStatic()) { - // collect the entry - methodEntries.add(methodEntry); - } - } - - // look at bridge methods! - MethodEntry bridgedMethod = getBridgedMethod(methodEntry); - while (bridgedMethod != null) { - methodEntries.addAll(getRelatedMethodImplementations(bridgedMethod)); - bridgedMethod = getBridgedMethod(bridgedMethod); - } - - // look at interface methods too - for (MethodImplementationsTreeNode implementationsNode : getMethodImplementations(new DirectionalTranslator(entryPool), methodEntry)) { - getRelatedMethodImplementations(methodEntries, implementationsNode); - } - - // recurse - for (int i = 0; i < node.getChildCount(); i++) { - getRelatedMethodImplementations(methodEntries, (MethodInheritanceTreeNode) node.getChildAt(i)); - } - } - - private void getRelatedMethodImplementations(Set methodEntries, MethodImplementationsTreeNode node) { - MethodEntry methodEntry = node.getMethodEntry(); - if (containsObfMethod(methodEntry)) { - AccessFlags flags = getAccessFlags(methodEntry); - if (!flags.isPrivate() && !flags.isStatic()) { - // collect the entry - methodEntries.add(methodEntry); - } - } - - // look at bridge methods! - MethodEntry bridgedMethod = getBridgedMethod(methodEntry); - while (bridgedMethod != null) { - methodEntries.addAll(getRelatedMethodImplementations(bridgedMethod)); - bridgedMethod = getBridgedMethod(bridgedMethod); - } - - // recurse - for (int i = 0; i < node.getChildCount(); i++) { - getRelatedMethodImplementations(methodEntries, (MethodImplementationsTreeNode) node.getChildAt(i)); - } - } - - public Collection> getFieldReferences(FieldEntry fieldEntry) { - return this.fieldReferences.get(fieldEntry); - } - - public Collection getReferencedFields(MethodDefEntry methodEntry) { - // linear search is fast enough for now - Set fieldEntries = Sets.newHashSet(); - for (EntryReference reference : this.fieldReferences.values()) { - if (reference.context == methodEntry) { - fieldEntries.add(reference.entry); - } - } - return fieldEntries; - } - - public Collection> getMethodsReferencing(ClassEntry classEntry) { - return this.methodsReferencingClasses.get(classEntry); - } - - @Deprecated - public Collection> getMethodsReferencing(MethodEntry methodEntry) { - return getMethodsReferencing(methodEntry, false); - } - - public Collection> getMethodsReferencing(MethodEntry methodEntry, boolean recurse) { - if (!recurse) { - return this.methodsReferencing.get(methodEntry); - } - - List> references = new ArrayList<>(); - Set methodEntries = getRelatedMethodImplementations(methodEntry); - for (MethodEntry entry : methodEntries) { - references.addAll(getMethodsReferencing(entry, false)); - } - return references; - } - - public Collection getReferencedMethods(MethodDefEntry methodEntry) { - return this.methodReferences.get(methodEntry); - } - - public Collection getInnerClasses(ClassEntry obfOuterClassEntry) { - return this.innerClassesByOuter.get(obfOuterClassEntry); - } - - public ClassEntry getOuterClass(ClassEntry obfInnerClassEntry) { - return this.outerClassesByInner.get(obfInnerClassEntry); - } - - public boolean isSyntheticMethod(MethodEntry methodEntry) { - return this.syntheticMethods.contains(methodEntry); - } - - public Set getInterfaces(String className) { - ClassEntry classEntry = entryPool.getClass(className); - Set interfaces = new HashSet<>(this.translationIndex.getInterfaces(classEntry)); - for (ClassEntry ancestor : this.translationIndex.getAncestry(classEntry)) { - interfaces.addAll(this.translationIndex.getInterfaces(ancestor)); - } - return interfaces; - } - - public Set getImplementingClasses(String targetInterfaceName) { - - // linear search is fast enough for now - Set classNames = Sets.newHashSet(); - for (Map.Entry entry : this.translationIndex.getClassInterfaces()) { - ClassEntry classEntry = entry.getKey(); - ClassEntry interfaceEntry = entry.getValue(); - if (interfaceEntry.getName().equals(targetInterfaceName)) { - String className = classEntry.getClassName(); - classNames.add(className); - if (isInterface(className)) { - classNames.addAll(getImplementingClasses(className)); - } - - this.translationIndex.getSubclassNamesRecursively(classNames, classEntry); - } - } - return classNames; - } - - public boolean isInterface(String className) { - return this.translationIndex.isInterface(entryPool.getClass(className)); - } - - public boolean containsObfClass(ClassEntry obfClassEntry) { - return this.obfClassEntries.contains(obfClassEntry); - } - - public boolean containsObfField(FieldEntry obfFieldEntry) { - return this.access.containsKey(obfFieldEntry); - } - - public boolean containsObfMethod(MethodEntry obfMethodEntry) { - return this.access.containsKey(obfMethodEntry); - } - - public boolean containsEntryWithSameName(Entry entry) { - for (Entry target : this.access.keySet()) - if (target.getName().equals(entry.getName()) && entry.getClass().isInstance(target.getClass())) - return true; - return false; - } - - public boolean containsObfVariable(LocalVariableEntry obfVariableEntry) { - // check the behavior - if (!containsObfMethod(obfVariableEntry.getOwnerEntry())) { - return false; - } - - return true; - } - - public boolean containsObfEntry(Entry obfEntry) { - if (obfEntry instanceof ClassEntry) { - return containsObfClass((ClassEntry) obfEntry); - } else if (obfEntry instanceof FieldEntry) { - return containsObfField((FieldEntry) obfEntry); - } else if (obfEntry instanceof MethodEntry) { - return containsObfMethod((MethodEntry) obfEntry); - } else if (obfEntry instanceof LocalVariableEntry) { - return containsObfVariable((LocalVariableEntry) obfEntry); - } else { - throw new Error("Entry desc not supported: " + obfEntry.getClass().getName()); - } - } - - public MethodEntry getBridgedMethod(MethodEntry bridgeMethodEntry) { - return this.bridgedMethods.get(bridgeMethodEntry); - } - - public List getObfClassChain(ClassEntry obfClassEntry) { - - // build class chain in inner-to-outer order - List obfClassChain = Lists.newArrayList(obfClassEntry); - ClassEntry checkClassEntry = obfClassEntry; - while (true) { - ClassEntry obfOuterClassEntry = getOuterClass(checkClassEntry); - if (obfOuterClassEntry != null) { - obfClassChain.add(obfOuterClassEntry); - checkClassEntry = obfOuterClassEntry; - } else { - break; - } - } - - // switch to outer-to-inner order - Collections.reverse(obfClassChain); - - return obfClassChain; - } -} -- cgit v1.2.3