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