From a88175ffc95792b88a8724f66db6dda2b8cc32ee Mon Sep 17 00:00:00 2001 From: gegy1000 Date: Tue, 17 Jul 2018 19:14:08 +0200 Subject: ASM Based Class Translator (#1) * Initial port to ASM * Package updates * Annotation + inner class translation * Fix inner class mapping * More bytecode translation * Signature refactoring * Fix highlighting of mapped names * Fix parameter name offset * Fix anonymous class generation * Fix issues with inner class signature transformation * Fix bridged method detection * Fix compile issues * Resolve all failed tests * Apply deobfuscated name to transformed classes * Fix class signatures not being translated * Fix frame array type translation * Fix frame array type translation * Fix array translation in method calls * Fix method reference and bridge detection * Fix handling of null deobf mappings * Parameter translation in interfaces * Fix enum parameter index offset * Fix parsed local variable indexing * Fix stackoverflow on rebuilding method names * Ignore invalid decompiled variable indices * basic source jar * Output directly to file on source export * Make decompile parallel * fix incorrect super calls * Use previous save state to delete old mapping files * Fix old mappings not properly being removed * Fix old mappings not properly being removed * make isMethodProvider public (cherry picked from commit ebad6a9) * speed up Deobfuscator's getSources by using a single TranslatingTypeloader and caching the ClassLoaderTypeloader * ignore .idea project folders * move SynchronizedTypeLoader to a non-inner * fix signature remap of inners for now * index & resolve method/field references for usages view * Allow reader/writer subclasses to provide the underlying file operations * fix giving obf classes a name not removing them from the panel * buffer the ParsedJar class entry inputstream, allow use with a jarinputstream * make CachingClasspathTypeLoader public * make CachingClasspathTypeLoader public * support enum switches with obfuscated SwitchMaps --- src/main/java/cuchaz/enigma/analysis/Access.java | 11 +- .../enigma/analysis/BehaviorReferenceTreeNode.java | 93 --- .../java/cuchaz/enigma/analysis/BridgeMarker.java | 38 -- .../analysis/ClassImplementationsTreeNode.java | 12 +- .../enigma/analysis/ClassInheritanceTreeNode.java | 16 +- .../cuchaz/enigma/analysis/EntryReference.java | 18 +- .../java/cuchaz/enigma/analysis/EntryRenamer.java | 63 +- .../enigma/analysis/FieldReferenceTreeNode.java | 29 +- .../cuchaz/enigma/analysis/IndexClassVisitor.java | 38 ++ .../enigma/analysis/IndexInnerClassVisitor.java | 23 + .../enigma/analysis/IndexReferenceVisitor.java | 77 +++ .../cuchaz/enigma/analysis/JarClassIterator.java | 123 ---- src/main/java/cuchaz/enigma/analysis/JarIndex.java | 635 ++++++--------------- .../analysis/MethodImplementationsTreeNode.java | 13 +- .../enigma/analysis/MethodInheritanceTreeNode.java | 16 +- .../enigma/analysis/MethodReferenceTreeNode.java | 94 +++ .../java/cuchaz/enigma/analysis/ParsedJar.java | 95 +++ .../cuchaz/enigma/analysis/ReferenceTreeNode.java | 2 +- .../java/cuchaz/enigma/analysis/SourceIndex.java | 2 +- .../analysis/SourceIndexBehaviorVisitor.java | 204 ------- .../enigma/analysis/SourceIndexClassVisitor.java | 44 +- .../enigma/analysis/SourceIndexMethodVisitor.java | 224 ++++++++ .../cuchaz/enigma/analysis/SourceIndexVisitor.java | 14 +- .../cuchaz/enigma/analysis/TranslationIndex.java | 122 ++-- 24 files changed, 900 insertions(+), 1106 deletions(-) delete mode 100644 src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/BridgeMarker.java create mode 100644 src/main/java/cuchaz/enigma/analysis/IndexClassVisitor.java create mode 100644 src/main/java/cuchaz/enigma/analysis/IndexInnerClassVisitor.java create mode 100644 src/main/java/cuchaz/enigma/analysis/IndexReferenceVisitor.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/JarClassIterator.java create mode 100644 src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java create mode 100644 src/main/java/cuchaz/enigma/analysis/ParsedJar.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java create mode 100644 src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java (limited to 'src/main/java/cuchaz/enigma/analysis') diff --git a/src/main/java/cuchaz/enigma/analysis/Access.java b/src/main/java/cuchaz/enigma/analysis/Access.java index 547d85e..8181418 100644 --- a/src/main/java/cuchaz/enigma/analysis/Access.java +++ b/src/main/java/cuchaz/enigma/analysis/Access.java @@ -11,8 +11,7 @@ package cuchaz.enigma.analysis; -import javassist.CtBehavior; -import javassist.CtField; +import cuchaz.enigma.bytecode.AccessFlags; import java.lang.reflect.Modifier; @@ -20,12 +19,8 @@ public enum Access { PUBLIC, PROTECTED, PACKAGE, PRIVATE; - public static Access get(CtBehavior behavior) { - return get(behavior.getModifiers()); - } - - public static Access get(CtField field) { - return get(field.getModifiers()); + public static Access get(AccessFlags flags) { + return get(flags.getFlags()); } public static Access get(int modifiers) { diff --git a/src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java deleted file mode 100644 index 6556b2c..0000000 --- a/src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java +++ /dev/null @@ -1,93 +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.Sets; -import cuchaz.enigma.mapping.BehaviorEntry; -import cuchaz.enigma.mapping.Entry; -import cuchaz.enigma.mapping.Translator; - -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.TreeNode; -import java.util.Set; - -public class BehaviorReferenceTreeNode extends DefaultMutableTreeNode - implements ReferenceTreeNode { - - private Translator deobfuscatingTranslator; - private BehaviorEntry entry; - private EntryReference reference; - private Access access; - - public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, BehaviorEntry entry) { - this.deobfuscatingTranslator = deobfuscatingTranslator; - this.entry = entry; - this.reference = null; - } - - public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, - EntryReference reference, Access access) { - this.deobfuscatingTranslator = deobfuscatingTranslator; - this.entry = reference.entry; - this.reference = reference; - this.access = access; - } - - @Override - public BehaviorEntry getEntry() { - return this.entry; - } - - @Override - public EntryReference getReference() { - return this.reference; - } - - @Override - public String toString() { - if (this.reference != null) { - return String.format("%s (%s)", this.deobfuscatingTranslator.translateEntry(this.reference.context), - this.access); - } - return this.deobfuscatingTranslator.translateEntry(this.entry).toString(); - } - - public void load(JarIndex index, boolean recurse) { - // get all the child nodes - for (EntryReference reference : index.getBehaviorReferences(this.entry)) { - add(new BehaviorReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.entry))); - } - - if (recurse && this.children != null) { - for (Object child : this.children) { - if (child instanceof BehaviorReferenceTreeNode) { - BehaviorReferenceTreeNode node = (BehaviorReferenceTreeNode) child; - - // don't recurse into ancestor - Set ancestors = Sets.newHashSet(); - TreeNode n = node; - while (n.getParent() != null) { - n = n.getParent(); - if (n instanceof BehaviorReferenceTreeNode) { - ancestors.add(((BehaviorReferenceTreeNode) n).getEntry()); - } - } - if (ancestors.contains(node.getEntry())) { - continue; - } - - node.load(index, true); - } - } - } - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java b/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java deleted file mode 100644 index a2f1f90..0000000 --- a/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java +++ /dev/null @@ -1,38 +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 cuchaz.enigma.mapping.EntryFactory; -import cuchaz.enigma.mapping.MethodEntry; -import javassist.CtClass; -import javassist.CtMethod; -import javassist.bytecode.AccessFlag; - -public class BridgeMarker { - - public static void markBridges(JarIndex jarIndex, CtClass c) { - - for (CtMethod method : c.getDeclaredMethods()) { - MethodEntry methodEntry = EntryFactory.getMethodEntry(method); - - // is this a bridge method? - MethodEntry bridgedMethodEntry = jarIndex.getBridgedMethod(methodEntry); - if (bridgedMethodEntry != null) { - - // it's a bridge method! add the bridge flag - int flags = method.getMethodInfo().getAccessFlags(); - flags |= AccessFlag.BRIDGE; - method.getMethodInfo().setAccessFlags(flags); - } - } - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java index f2fb2f8..e876bb0 100644 --- a/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java @@ -12,8 +12,8 @@ package cuchaz.enigma.analysis; import com.google.common.collect.Lists; -import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.MethodEntry; import cuchaz.enigma.mapping.Translator; import javax.swing.tree.DefaultMutableTreeNode; @@ -21,8 +21,8 @@ import java.util.List; public class ClassImplementationsTreeNode extends DefaultMutableTreeNode { - private Translator deobfuscatingTranslator; - private ClassEntry entry; + private final Translator deobfuscatingTranslator; + private final ClassEntry entry; public ClassImplementationsTreeNode(Translator deobfuscatingTranslator, ClassEntry entry) { this.deobfuscatingTranslator = deobfuscatingTranslator; @@ -31,7 +31,7 @@ public class ClassImplementationsTreeNode extends DefaultMutableTreeNode { public static ClassImplementationsTreeNode findNode(ClassImplementationsTreeNode node, MethodEntry entry) { // is this the node? - if (node.entry.equals(entry.getClassEntry())) { + if (node.entry.equals(entry.getOwnerClassEntry())) { return node; } @@ -50,7 +50,7 @@ public class ClassImplementationsTreeNode extends DefaultMutableTreeNode { } public String getDeobfClassName() { - return this.deobfuscatingTranslator.translateClass(this.entry.getClassName()); + return this.deobfuscatingTranslator.getTranslatedClass(entry).getClassName(); } @Override diff --git a/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java index 24e7cb0..b8ee17d 100644 --- a/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java @@ -12,7 +12,7 @@ package cuchaz.enigma.analysis; import com.google.common.collect.Lists; -import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.entry.ClassEntry; import cuchaz.enigma.mapping.Translator; import javax.swing.tree.DefaultMutableTreeNode; @@ -20,12 +20,12 @@ import java.util.List; public class ClassInheritanceTreeNode extends DefaultMutableTreeNode { - private Translator deobfuscatingTranslator; - private String obfClassName; + private final Translator deobfuscatingTranslator; + private final ClassEntry obfClassEntry; public ClassInheritanceTreeNode(Translator deobfuscatingTranslator, String obfClassName) { this.deobfuscatingTranslator = deobfuscatingTranslator; - this.obfClassName = obfClassName; + this.obfClassEntry = new ClassEntry(obfClassName); } public static ClassInheritanceTreeNode findNode(ClassInheritanceTreeNode node, ClassEntry entry) { @@ -45,11 +45,11 @@ public class ClassInheritanceTreeNode extends DefaultMutableTreeNode { } public String getObfClassName() { - return this.obfClassName; + return this.obfClassEntry.getClassName(); } public String getDeobfClassName() { - return this.deobfuscatingTranslator.translateClass(this.obfClassName); + return this.deobfuscatingTranslator.getTranslatedClass(this.obfClassEntry).getClassName(); } @Override @@ -58,13 +58,13 @@ public class ClassInheritanceTreeNode extends DefaultMutableTreeNode { if (deobfClassName != null) { return deobfClassName; } - return this.obfClassName; + return this.obfClassEntry.getName(); } public void load(TranslationIndex ancestries, boolean recurse) { // get all the child nodes List nodes = Lists.newArrayList(); - for (ClassEntry subclassEntry : ancestries.getSubclass(new ClassEntry(this.obfClassName))) { + for (ClassEntry subclassEntry : ancestries.getSubclass(this.obfClassEntry)) { nodes.add(new ClassInheritanceTreeNode(this.deobfuscatingTranslator, subclassEntry.getName())); } diff --git a/src/main/java/cuchaz/enigma/analysis/EntryReference.java b/src/main/java/cuchaz/enigma/analysis/EntryReference.java index 3761fca..101729d 100644 --- a/src/main/java/cuchaz/enigma/analysis/EntryReference.java +++ b/src/main/java/cuchaz/enigma/analysis/EntryReference.java @@ -11,9 +11,9 @@ package cuchaz.enigma.analysis; -import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.mapping.ConstructorEntry; -import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.Entry; +import cuchaz.enigma.mapping.entry.MethodEntry; import cuchaz.enigma.utils.Utils; import java.util.Arrays; @@ -21,7 +21,7 @@ import java.util.List; public class EntryReference { - private static final List ConstructorNonNames = Arrays.asList("this", "super", "static"); + private static final List CONSTRUCTOR_NON_NAMES = Arrays.asList("this", "super", "static"); public E entry; public C context; @@ -40,7 +40,7 @@ public class EntryReference { this.context = context; this.sourceName = sourceName != null && !sourceName.isEmpty(); - if (entry instanceof ConstructorEntry && ConstructorNonNames.contains(sourceName)) { + if (entry instanceof MethodEntry && ((MethodEntry) entry).isConstructor() && CONSTRUCTOR_NON_NAMES.contains(sourceName)) { this.sourceName = false; } } @@ -53,9 +53,9 @@ public class EntryReference { public ClassEntry getLocationClassEntry() { if (context != null) { - return context.getClassEntry(); + return context.getOwnerClassEntry(); } - return entry.getClassEntry(); + return entry.getOwnerClassEntry(); } public boolean isNamed() { @@ -63,9 +63,9 @@ public class EntryReference { } public Entry getNameableEntry() { - if (entry instanceof ConstructorEntry) { + if (entry instanceof MethodEntry && ((MethodEntry) entry).isConstructor()) { // renaming a constructor really means renaming the class - return entry.getClassEntry(); + return entry.getOwnerClassEntry(); } return entry; } diff --git a/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java b/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java index 75806c3..9be8378 100644 --- a/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java +++ b/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java @@ -15,6 +15,7 @@ import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import cuchaz.enigma.mapping.*; +import cuchaz.enigma.mapping.entry.*; import java.util.AbstractMap; import java.util.List; @@ -87,18 +88,18 @@ public class EntryRenamer { MethodEntry newMethodEntry = renames.get(methodEntry); if (newMethodEntry != null) { return (T) new MethodEntry( - methodEntry.getClassEntry(), - newMethodEntry.getName(), - methodEntry.getSignature() + methodEntry.getOwnerClassEntry(), + newMethodEntry.getName(), + methodEntry.getDesc() ); } return thing; - } else if (thing instanceof ArgumentEntry) { - ArgumentEntry argumentEntry = (ArgumentEntry) thing; - return (T) new ArgumentEntry( - renameMethodsInThing(renames, argumentEntry.getBehaviorEntry()), - argumentEntry.getIndex(), - argumentEntry.getName() + } else if (thing instanceof LocalVariableEntry) { + LocalVariableEntry variableEntry = (LocalVariableEntry) thing; + return (T) new LocalVariableEntry( + renameMethodsInThing(renames, variableEntry.getOwnerEntry()), + variableEntry.getIndex(), + variableEntry.getName() ); } else if (thing instanceof EntryReference) { EntryReference reference = (EntryReference) thing; @@ -119,27 +120,45 @@ public class EntryRenamer { } else if (thing instanceof ClassEntry) { ClassEntry classEntry = (ClassEntry) thing; return (T) new ClassEntry(renameClassesInThing(renames, classEntry.getClassName())); - } else if (thing instanceof FieldEntry) { - FieldEntry fieldEntry = (FieldEntry) thing; - return (T) new FieldEntry(renameClassesInThing(renames, fieldEntry.getClassEntry()), fieldEntry.getName(), renameClassesInThing(renames, fieldEntry.getType())); - } else if (thing instanceof ConstructorEntry) { - ConstructorEntry constructorEntry = (ConstructorEntry) thing; - return (T) new ConstructorEntry(renameClassesInThing(renames, constructorEntry.getClassEntry()), renameClassesInThing(renames, constructorEntry.getSignature())); + } else if (thing instanceof FieldDefEntry) { + FieldDefEntry fieldEntry = (FieldDefEntry) thing; + return (T) new FieldDefEntry( + renameClassesInThing(renames, fieldEntry.getOwnerClassEntry()), + fieldEntry.getName(), + renameClassesInThing(renames, fieldEntry.getDesc()), + renameClassesInThing(renames, fieldEntry.getSignature()), + fieldEntry.getAccess() + ); + } else if (thing instanceof MethodDefEntry) { + MethodDefEntry methodEntry = (MethodDefEntry) thing; + return (T) new MethodDefEntry( + renameClassesInThing(renames, methodEntry.getOwnerClassEntry()), + methodEntry.getName(), + renameClassesInThing(renames, methodEntry.getDesc()), + renameClassesInThing(renames, methodEntry.getSignature()), + methodEntry.getAccess() + ); } else if (thing instanceof MethodEntry) { MethodEntry methodEntry = (MethodEntry) thing; - return (T) new MethodEntry(renameClassesInThing(renames, methodEntry.getClassEntry()), methodEntry.getName(), renameClassesInThing(renames, methodEntry.getSignature())); - } else if (thing instanceof ArgumentEntry) { - ArgumentEntry argumentEntry = (ArgumentEntry) thing; - return (T) new ArgumentEntry(renameClassesInThing(renames, argumentEntry.getBehaviorEntry()), argumentEntry.getIndex(), argumentEntry.getName()); + return (T) new MethodEntry( + renameClassesInThing(renames, methodEntry.getOwnerClassEntry()), + methodEntry.getName(), + renameClassesInThing(renames, methodEntry.getDesc()) + ); + } else if (thing instanceof LocalVariableEntry) { + LocalVariableEntry argumentEntry = (LocalVariableEntry) thing; + return (T) new LocalVariableEntry(renameClassesInThing(renames, argumentEntry.getOwnerEntry()), argumentEntry.getIndex(), argumentEntry.getName()); } else if (thing instanceof EntryReference) { EntryReference reference = (EntryReference) thing; reference.entry = renameClassesInThing(renames, reference.entry); reference.context = renameClassesInThing(renames, reference.context); return thing; + } else if (thing instanceof MethodDescriptor) { + return (T) ((MethodDescriptor) thing).remap(className -> renameClassesInThing(renames, className)); + } else if (thing instanceof TypeDescriptor) { + return (T) ((TypeDescriptor) thing).remap(className -> renameClassesInThing(renames, className)); } else if (thing instanceof Signature) { - return (T) new Signature((Signature) thing, className -> renameClassesInThing(renames, className)); - } else if (thing instanceof Type) { - return (T) new Type((Type) thing, className -> renameClassesInThing(renames, className)); + return (T) ((Signature) thing).remap(className -> renameClassesInThing(renames, className)); } return thing; diff --git a/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java index 34d2eff..f63b779 100644 --- a/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java @@ -11,17 +11,18 @@ package cuchaz.enigma.analysis; -import cuchaz.enigma.mapping.BehaviorEntry; -import cuchaz.enigma.mapping.FieldEntry; -import cuchaz.enigma.mapping.Translator; +import cuchaz.enigma.mapping.*; +import cuchaz.enigma.mapping.entry.FieldEntry; +import cuchaz.enigma.mapping.entry.MethodDefEntry; +import cuchaz.enigma.mapping.entry.MethodEntry; import javax.swing.tree.DefaultMutableTreeNode; -public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode { +public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode { private Translator deobfuscatingTranslator; private FieldEntry entry; - private EntryReference reference; + private EntryReference reference; private Access access; public FieldReferenceTreeNode(Translator deobfuscatingTranslator, FieldEntry entry) { @@ -30,7 +31,7 @@ public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements Re this.reference = null; } - private FieldReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference reference, Access access) { + private FieldReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference reference, Access access) { this.deobfuscatingTranslator = deobfuscatingTranslator; this.entry = reference.entry; this.reference = reference; @@ -43,34 +44,34 @@ public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements Re } @Override - public EntryReference getReference() { + public EntryReference getReference() { return this.reference; } @Override public String toString() { if (this.reference != null) { - return String.format("%s (%s)", this.deobfuscatingTranslator.translateEntry(this.reference.context), this.access); + return String.format("%s (%s)", this.deobfuscatingTranslator.getTranslatedMethodDef(this.reference.context), this.access); } - return this.deobfuscatingTranslator.translateEntry(this.entry).toString(); + return deobfuscatingTranslator.getTranslatedField(entry).getName(); } public void load(JarIndex index, boolean recurse) { // get all the child nodes if (this.reference == null) { - for (EntryReference reference : index.getFieldReferences(this.entry)) { + for (EntryReference reference : index.getFieldReferences(this.entry)) { add(new FieldReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.entry))); } } else { - for (EntryReference reference : index.getBehaviorReferences(this.reference.context)) { - add(new BehaviorReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.reference.context))); + for (EntryReference reference : index.getMethodsReferencing(this.reference.context)) { + add(new MethodReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.reference.context))); } } if (recurse && children != null) { for (Object node : children) { - if (node instanceof BehaviorReferenceTreeNode) { - ((BehaviorReferenceTreeNode) node).load(index, true); + if (node instanceof MethodReferenceTreeNode) { + ((MethodReferenceTreeNode) node).load(index, true); } else if (node instanceof FieldReferenceTreeNode) { ((FieldReferenceTreeNode) node).load(index, true); } diff --git a/src/main/java/cuchaz/enigma/analysis/IndexClassVisitor.java b/src/main/java/cuchaz/enigma/analysis/IndexClassVisitor.java new file mode 100644 index 0000000..69fe54f --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/IndexClassVisitor.java @@ -0,0 +1,38 @@ +package cuchaz.enigma.analysis; + +import cuchaz.enigma.mapping.entry.ClassDefEntry; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; + +public class IndexClassVisitor extends ClassVisitor { + private final JarIndex index; + private ClassDefEntry classEntry; + + public IndexClassVisitor(JarIndex index, int api) { + super(api); + this.index = index; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + this.classEntry = this.index.indexClass(access, name, signature, superName, interfaces); + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + if (this.classEntry != null) { + this.index.indexField(this.classEntry, access, name, desc, signature); + } + return super.visitField(access, name, desc, signature, value); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + if (this.classEntry != null) { + this.index.indexMethod(this.classEntry, access, name, desc, signature); + } + return super.visitMethod(access, name, desc, signature, exceptions); + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/IndexInnerClassVisitor.java b/src/main/java/cuchaz/enigma/analysis/IndexInnerClassVisitor.java new file mode 100644 index 0000000..0474227 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/IndexInnerClassVisitor.java @@ -0,0 +1,23 @@ +package cuchaz.enigma.analysis; + +import cuchaz.enigma.mapping.entry.ClassEntry; +import org.objectweb.asm.ClassVisitor; + +public class IndexInnerClassVisitor extends ClassVisitor { + private final JarIndex index; + + public IndexInnerClassVisitor(JarIndex index, int api) { + super(api); + this.index = index; + } + + @Override + public void visitInnerClass(String name, String outerName, String innerName, int access) { + ClassEntry entry = new ClassEntry(name); + // Ignore anonymous classes + if (innerName != null && outerName != null) { + ClassEntry outerEntry = new ClassEntry(outerName); + index.indexInnerClass(entry, outerEntry); + } + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/IndexReferenceVisitor.java b/src/main/java/cuchaz/enigma/analysis/IndexReferenceVisitor.java new file mode 100644 index 0000000..f37f1e9 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/IndexReferenceVisitor.java @@ -0,0 +1,77 @@ +package cuchaz.enigma.analysis; + +import cuchaz.enigma.bytecode.AccessFlags; +import cuchaz.enigma.mapping.MethodDescriptor; +import cuchaz.enigma.mapping.Signature; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.MethodDefEntry; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Handle; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +public class IndexReferenceVisitor extends ClassVisitor { + private final JarIndex index; + private ClassEntry classEntry; + + public IndexReferenceVisitor(JarIndex index, int api) { + super(api); + this.index = index; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + this.classEntry = new ClassEntry(name); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + MethodDefEntry entry = new MethodDefEntry(classEntry, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access)); + return new Method(this.index, entry, this.api); + } + + private class Method extends MethodVisitor { + private final JarIndex index; + private final MethodDefEntry callerEntry; + + public Method(JarIndex index, MethodDefEntry callerEntry, int api) { + super(api); + this.index = index; + this.callerEntry = callerEntry; + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + this.index.indexFieldAccess(callerEntry, owner, name, desc); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + this.index.indexMethodCall(callerEntry, owner, name, desc); + } + + @Override + public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { + for (Object bsmArg : bsmArgs){ + if (bsmArg instanceof Handle){ + Handle handle = (Handle)bsmArg; + switch (handle.getTag()){ + case Opcodes.H_GETFIELD: + case Opcodes.H_GETSTATIC: + case Opcodes.H_PUTFIELD: + case Opcodes.H_PUTSTATIC: + this.index.indexFieldAccess(callerEntry, handle.getOwner(), handle.getName(), handle.getDesc()); + break; + case Opcodes.H_INVOKEINTERFACE: + case Opcodes.H_INVOKESPECIAL: + case Opcodes.H_INVOKESTATIC: + case Opcodes.H_INVOKEVIRTUAL: + case Opcodes.H_NEWINVOKESPECIAL: + this.index.indexMethodCall(callerEntry, handle.getOwner(), handle.getName(), handle.getDesc()); + break; + } + } + } + } + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/JarClassIterator.java b/src/main/java/cuchaz/enigma/analysis/JarClassIterator.java deleted file mode 100644 index 87d3797..0000000 --- a/src/main/java/cuchaz/enigma/analysis/JarClassIterator.java +++ /dev/null @@ -1,123 +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.Lists; -import cuchaz.enigma.Constants; -import cuchaz.enigma.mapping.ClassEntry; -import javassist.ByteArrayClassPath; -import javassist.ClassPool; -import javassist.CtClass; -import javassist.NotFoundException; -import javassist.bytecode.Descriptor; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Enumeration; -import java.util.Iterator; -import java.util.List; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; - -public class JarClassIterator implements Iterator { - - private JarFile jar; - private Iterator iter; - - public JarClassIterator(JarFile jar) { - this.jar = jar; - - // get the jar entries that correspond to classes - List classEntries = Lists.newArrayList(); - Enumeration entries = this.jar.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - - // is this a class file? - if (entry.getName().endsWith(".class")) { - classEntries.add(entry); - } - } - this.iter = classEntries.iterator(); - } - - public static List getClassEntries(JarFile jar) { - List classEntries = Lists.newArrayList(); - Enumeration entries = jar.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - - // is this a class file? - if (!entry.isDirectory() && entry.getName().endsWith(".class")) { - classEntries.add(getClassEntry(entry)); - } - } - return classEntries; - } - - public static Iterable classes(final JarFile jar) { - return () -> new JarClassIterator(jar); - } - - private static CtClass getClass(JarFile jar, JarEntry entry) throws IOException, NotFoundException { - // read the class into a buffer - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - byte[] buf = new byte[Constants.KiB]; - int totalNumBytesRead = 0; - InputStream in = jar.getInputStream(entry); - while (in.available() > 0) { - int numBytesRead = in.read(buf); - if (numBytesRead < 0) { - break; - } - bos.write(buf, 0, numBytesRead); - - // sanity checking - totalNumBytesRead += numBytesRead; - if (totalNumBytesRead > Constants.MiB) { - throw new Error("Class file " + entry.getName() + " larger than 1 MiB! Something is wrong!"); - } - } - - // get a javassist handle for the class - String className = Descriptor.toJavaName(getClassEntry(entry).getName()); - ClassPool classPool = new ClassPool(); - classPool.appendSystemPath(); - classPool.insertClassPath(new ByteArrayClassPath(className, bos.toByteArray())); - return classPool.get(className); - } - - private static ClassEntry getClassEntry(JarEntry entry) { - return new ClassEntry(entry.getName().substring(0, entry.getName().length() - ".class".length())); - } - - @Override - public boolean hasNext() { - return this.iter.hasNext(); - } - - @Override - public CtClass next() { - JarEntry entry = this.iter.next(); - try { - return getClass(this.jar, entry); - } catch (IOException | NotFoundException ex) { - throw new Error("Unable to load class: " + entry.getName()); - } - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/JarIndex.java b/src/main/java/cuchaz/enigma/analysis/JarIndex.java index d0d0f2c..5917a32 100644 --- a/src/main/java/cuchaz/enigma/analysis/JarIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/JarIndex.java @@ -12,113 +12,73 @@ package cuchaz.enigma.analysis; import com.google.common.collect.*; +import cuchaz.enigma.bytecode.AccessFlags; import cuchaz.enigma.mapping.*; -import cuchaz.enigma.mapping.Translator; -import javassist.*; -import javassist.bytecode.*; -import javassist.expr.*; +import cuchaz.enigma.mapping.entry.*; +import org.objectweb.asm.Opcodes; -import java.lang.reflect.Modifier; import java.util.*; -import java.util.jar.JarFile; public class JarIndex { + private final ReferencedEntryPool entryPool; + private Set obfClassEntries; private TranslationIndex translationIndex; private Map access; - private Multimap fields; - private Multimap behaviors; - private Multimap methodImplementations; - private Multimap> behaviorReferences; - private Multimap> fieldReferences; + private Multimap fields; + private Multimap methods; + private Multimap methodImplementations; + private Multimap> methodsReferencing; + private Multimap methodReferences; + private Multimap> fieldReferences; private Multimap innerClassesByOuter; private Map outerClassesByInner; - private Map anonymousClasses; private Map bridgedMethods; private Set syntheticMethods; - public JarIndex() { + public JarIndex(ReferencedEntryPool entryPool) { + this.entryPool = entryPool; this.obfClassEntries = Sets.newHashSet(); - this.translationIndex = new TranslationIndex(); + this.translationIndex = new TranslationIndex(entryPool); this.access = Maps.newHashMap(); this.fields = HashMultimap.create(); - this.behaviors = HashMultimap.create(); + this.methods = HashMultimap.create(); this.methodImplementations = HashMultimap.create(); - this.behaviorReferences = HashMultimap.create(); + this.methodsReferencing = HashMultimap.create(); + this.methodReferences = HashMultimap.create(); this.fieldReferences = HashMultimap.create(); this.innerClassesByOuter = HashMultimap.create(); this.outerClassesByInner = Maps.newHashMap(); - this.anonymousClasses = Maps.newHashMap(); this.bridgedMethods = Maps.newHashMap(); this.syntheticMethods = Sets.newHashSet(); } - public void indexJar(JarFile jar, boolean buildInnerClasses) { + public void indexJar(ParsedJar jar, boolean buildInnerClasses) { // step 1: read the class names - this.obfClassEntries.addAll(JarClassIterator.getClassEntries(jar)); - - // step 2: index field/method/constructor access - for (CtClass c : JarClassIterator.classes(jar)) { - for (CtField field : c.getDeclaredFields()) { - FieldEntry fieldEntry = EntryFactory.getFieldEntry(field); - this.access.put(fieldEntry, Access.get(field)); - this.fields.put(fieldEntry.getClassEntry(), fieldEntry); - } - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); - this.access.put(behaviorEntry, Access.get(behavior)); - this.behaviors.put(behaviorEntry.getClassEntry(), behaviorEntry); - } - } + obfClassEntries.addAll(jar.getClassEntries()); - // step 3: index extends, implements, fields, and methods - for (CtClass c : JarClassIterator.classes(jar)) { - this.translationIndex.indexClass(c); - String className = Descriptor.toJvmName(c.getName()); - for (String interfaceName : c.getClassFile().getInterfaces()) { - className = Descriptor.toJvmName(className); - interfaceName = Descriptor.toJvmName(interfaceName); - if (className.equals(interfaceName)) { - throw new IllegalArgumentException("Class cannot be its own interface! " + className); - } - } - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - indexBehavior(behavior); - } - } + // step 2: index classes, fields, methods, interfaces + jar.visit(node -> node.accept(new IndexClassVisitor(this, Opcodes.ASM5))); - // step 4: index field, method, constructor references - for (CtClass c : JarClassIterator.classes(jar)) { - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - indexBehaviorReferences(behavior); + // step 3: index field, method, constructor references + jar.visit(node -> node.accept(new IndexReferenceVisitor(this, Opcodes.ASM5))); + + // 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 5: index inner classes and anonymous classes - for (CtClass c : JarClassIterator.classes(jar)) { - ClassEntry innerClassEntry = EntryFactory.getClassEntry(c); - ClassEntry outerClassEntry = findOuterClass(c); - if (outerClassEntry != null) { - this.innerClassesByOuter.put(outerClassEntry, innerClassEntry); - boolean innerWasAdded = this.outerClassesByInner.put(innerClassEntry, outerClassEntry) == null; - assert (innerWasAdded); - - BehaviorEntry enclosingBehavior = isAnonymousClass(c, outerClassEntry); - if (enclosingBehavior != null) { - this.anonymousClasses.put(innerClassEntry, enclosingBehavior); - - // DEBUG - //System.out.println("ANONYMOUS: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName()); - }/* else { - // DEBUG - //System.out.println("INNER: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName()); - }*/ - } - } + jar.visit(node -> node.accept(new IndexInnerClassVisitor(this, Opcodes.ASM5))); // step 6: update other indices with inner class info Map renames = Maps.newHashMap(); @@ -133,385 +93,138 @@ public class JarIndex { EntryRenamer.renameClassesInSet(renames, this.obfClassEntries); this.translationIndex.renameClasses(renames); EntryRenamer.renameClassesInMultimap(renames, this.methodImplementations); - EntryRenamer.renameClassesInMultimap(renames, this.behaviorReferences); + EntryRenamer.renameClassesInMultimap(renames, this.methodsReferencing); + EntryRenamer.renameClassesInMultimap(renames, this.methodReferences); EntryRenamer.renameClassesInMultimap(renames, this.fieldReferences); EntryRenamer.renameClassesInMap(renames, this.access); } } - private void indexBehavior(CtBehavior behavior) { - // get the behavior entry - final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); - if (behaviorEntry instanceof MethodEntry) { - MethodEntry methodEntry = (MethodEntry) behaviorEntry; - - // is synthetic - if ((behavior.getModifiers() & AccessFlag.SYNTHETIC) != 0) { - syntheticMethods.add(methodEntry); + 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); } - - // index implementation - this.methodImplementations.put(behaviorEntry.getClassName(), methodEntry); - - // look for bridge and bridged methods - CtMethod bridgedMethod = getBridgedMethod((CtMethod) behavior); - if (bridgedMethod != null) { - this.bridgedMethods.put(methodEntry, EntryFactory.getMethodEntry(bridgedMethod)); - } - } - // looks like we don't care about constructors here - } - - private void indexBehaviorReferences(CtBehavior behavior) { - // index method calls - final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); - try { - behavior.instrument(new ExprEditor() { - @Override - public void edit(MethodCall call) { - MethodEntry calledMethodEntry = EntryFactory.getMethodEntry(call); - ClassEntry resolvedClassEntry = translationIndex.resolveEntryClass(calledMethodEntry); - if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledMethodEntry.getClassEntry())) { - calledMethodEntry = new MethodEntry( - resolvedClassEntry, - calledMethodEntry.getName(), - calledMethodEntry.getSignature() - ); - } - EntryReference reference = new EntryReference<>( - calledMethodEntry, - call.getMethodName(), - behaviorEntry - ); - behaviorReferences.put(calledMethodEntry, reference); - } - - @Override - public void edit(FieldAccess call) { - FieldEntry calledFieldEntry = EntryFactory.getFieldEntry(call); - ClassEntry resolvedClassEntry = translationIndex.resolveEntryClass(calledFieldEntry); - if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledFieldEntry.getClassEntry())) { - calledFieldEntry = new FieldEntry(calledFieldEntry, resolvedClassEntry); - } - EntryReference reference = new EntryReference<>( - calledFieldEntry, - call.getFieldName(), - behaviorEntry - ); - fieldReferences.put(calledFieldEntry, reference); - } - - @Override - public void edit(ConstructorCall call) { - ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call); - EntryReference reference = new EntryReference<>( - calledConstructorEntry, - call.getMethodName(), - behaviorEntry - ); - behaviorReferences.put(calledConstructorEntry, reference); - } - - @Override - public void edit(NewExpr call) { - ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call); - EntryReference reference = new EntryReference<>( - calledConstructorEntry, - call.getClassName(), - behaviorEntry - ); - behaviorReferences.put(calledConstructorEntry, reference); - } - }); - } catch (CannotCompileException ex) { - throw new Error(ex); } + return this.translationIndex.indexClass(access, name, signature, superName, interfaces); } - private CtMethod getBridgedMethod(CtMethod method) { - - // bridge methods just call another method, cast it to the return type, and return the result - // let's see if we can detect this scenario - - // skip non-synthetic methods - if ((method.getModifiers() & AccessFlag.SYNTHETIC) == 0) { - return null; - } + 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, Access.get(access)); + this.fields.put(fieldEntry.getOwnerClassEntry(), fieldEntry); + } - // get all the called methods - final List methodCalls = Lists.newArrayList(); - try { - method.instrument(new ExprEditor() { - @Override - public void edit(MethodCall call) { - methodCalls.add(call); - } - }); - } catch (CannotCompileException ex) { - // this is stupid... we're not even compiling anything - throw new Error(ex); - } + 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, Access.get(access)); + this.methods.put(methodEntry.getOwnerClassEntry(), methodEntry); - // is there just one? - if (methodCalls.size() != 1) { - return null; + if (new AccessFlags(access).isSynthetic()) { + syntheticMethods.add(methodEntry); } - MethodCall call = methodCalls.get(0); - try { - // we have a bridge method! - return call.getMethod(); - } catch (NotFoundException ex) { - // can't find the type? not a bridge method - return null; + // we don't care about constructors here + if (!methodEntry.isConstructor()) { + // index implementation + this.methodImplementations.put(methodEntry.getClassName(), methodEntry); } } - private ClassEntry findOuterClass(CtClass c) { - - ClassEntry classEntry = EntryFactory.getClassEntry(c); - - // does this class already have an outer class? - if (classEntry.isInnerClass()) { - return classEntry.getOuterClassEntry(); + protected void indexMethodCall(MethodDefEntry callerEntry, String owner, String name, String desc) { + MethodEntry referencedMethod = new MethodEntry(entryPool.getClass(owner), name, new MethodDescriptor(desc)); + ClassEntry resolvedClassEntry = translationIndex.resolveEntryOwner(referencedMethod); + if (resolvedClassEntry != null && !resolvedClassEntry.equals(referencedMethod.getOwnerClassEntry())) { + referencedMethod = referencedMethod.updateOwnership(resolvedClassEntry); } - - // inner classes: - // have constructors that can (illegally) set synthetic fields - // the outer class is the only class that calls constructors - - // use the synthetic fields to find the synthetic constructors - for (CtConstructor constructor : c.getDeclaredConstructors()) { - Set syntheticFieldTypes = Sets.newHashSet(); - if (!isIllegalConstructor(syntheticFieldTypes, constructor)) { - continue; - } - - ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor); - - // gather the classes from the illegally-set synthetic fields - Set illegallySetClasses = Sets.newHashSet(); - for (String type : syntheticFieldTypes) { - if (type.startsWith("L")) { - ClassEntry outerClassEntry = new ClassEntry(type.substring(1, type.length() - 1)); - if (isSaneOuterClass(outerClassEntry, classEntry)) { - illegallySetClasses.add(outerClassEntry); - } - } - } - - // who calls this constructor? - Set callerClasses = Sets.newHashSet(); - for (EntryReference reference : getBehaviorReferences(constructorEntry)) { - - // make sure it's not a call to super - if (reference.entry instanceof ConstructorEntry && reference.context instanceof ConstructorEntry) { - - // is the entry a superclass of the context? - ClassEntry calledClassEntry = reference.entry.getClassEntry(); - ClassEntry superclassEntry = this.translationIndex.getSuperclass(reference.context.getClassEntry()); - if (superclassEntry != null && superclassEntry.equals(calledClassEntry)) { - // it's a super call, skip - continue; - } - } - - if (isSaneOuterClass(reference.context.getClassEntry(), classEntry)) { - callerClasses.add(reference.context.getClassEntry()); - } - } - - // do we have an answer yet? - if (callerClasses.isEmpty()) { - if (illegallySetClasses.size() == 1) { - return illegallySetClasses.iterator().next(); - } else { - System.out.println(String.format("WARNING: Unable to find outer class for %s. No caller and no illegally set field classes.", classEntry)); - } - } else { - if (callerClasses.size() == 1) { - return callerClasses.iterator().next(); - } else { - // multiple callers, do the illegally set classes narrow it down? - Set intersection = Sets.newHashSet(callerClasses); - intersection.retainAll(illegallySetClasses); - if (intersection.size() == 1) { - return intersection.iterator().next(); - } else { - System.out.println(String.format("WARNING: Unable to choose outer class for %s among options: %s", classEntry, callerClasses)); - } - } - } - } - - return null; + methodsReferencing.put(referencedMethod, new EntryReference<>(referencedMethod, referencedMethod.getName(), callerEntry)); + methodReferences.put(callerEntry, referencedMethod); } - private boolean isSaneOuterClass(ClassEntry outerClassEntry, ClassEntry innerClassEntry) { - - // clearly this would be silly - if (outerClassEntry.equals(innerClassEntry)) { - return false; + 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); } - - // is the outer class in the jar? - return this.obfClassEntries.contains(outerClassEntry); - + fieldReferences.put(referencedField, new EntryReference<>(referencedField, referencedField.getName(), callerEntry)); } - @SuppressWarnings("unchecked") - private boolean isIllegalConstructor(Set syntheticFieldTypes, CtConstructor constructor) { + public void indexInnerClass(ClassEntry innerEntry, ClassEntry outerEntry) { + this.innerClassesByOuter.put(outerEntry, innerEntry); + this.outerClassesByInner.putIfAbsent(innerEntry, outerEntry); + } - // illegal constructors only set synthetic member fields, then call super() - String className = constructor.getDeclaringClass().getName(); + private MethodEntry findAccessMethod(MethodDefEntry method) { - // collect all the field accesses, constructor calls, and method calls - final List illegalFieldWrites = Lists.newArrayList(); - final List constructorCalls = Lists.newArrayList(); - try { - constructor.instrument(new ExprEditor() { - @Override - public void edit(FieldAccess fieldAccess) { - if (fieldAccess.isWriter() && constructorCalls.isEmpty()) { - illegalFieldWrites.add(fieldAccess); - } - } + // we want to find all compiler-added methods that directly call another with no processing - @Override - public void edit(ConstructorCall constructorCall) { - constructorCalls.add(constructorCall); - } - }); - } catch (CannotCompileException ex) { - // we're not compiling anything... this is stupid - throw new Error(ex); - } - - // are there any illegal field writes? - if (illegalFieldWrites.isEmpty()) { - return false; + // skip non-synthetic methods + if (!method.getAccess().isSynthetic()) { + return null; } - // are all the writes to synthetic fields? - for (FieldAccess fieldWrite : illegalFieldWrites) { - - // all illegal writes have to be to the local class - if (!fieldWrite.getClassName().equals(className)) { - System.err.println(String.format("WARNING: illegal write to non-member field %s.%s", fieldWrite.getClassName(), fieldWrite.getFieldName())); - return false; - } - - // find the field - FieldInfo fieldInfo = null; - for (FieldInfo info : (List) constructor.getDeclaringClass().getClassFile().getFields()) { - if (info.getName().equals(fieldWrite.getFieldName()) && info.getDescriptor().equals(fieldWrite.getSignature())) { - fieldInfo = info; - break; - } - } - if (fieldInfo == null) { - // field is in a superclass or something, can't be a local synthetic member - return false; - } + // get all the methods that we call + final Collection referencedMethods = methodReferences.get(method); - // is this field synthetic? - boolean isSynthetic = (fieldInfo.getAccessFlags() & AccessFlag.SYNTHETIC) != 0; - if (isSynthetic) { - syntheticFieldTypes.add(fieldInfo.getDescriptor()); - } else { - System.err.println(String.format("WARNING: illegal write to non synthetic field %s %s.%s", fieldInfo.getDescriptor(), className, fieldInfo.getName())); - return false; - } + // is there just one? + if (referencedMethods.size() != 1) { + return null; } - // we passed all the tests! - return true; + return referencedMethods.stream().findFirst().orElse(null); } - private BehaviorEntry isAnonymousClass(CtClass c, ClassEntry outerClassEntry) { - - // is this class already marked anonymous? - EnclosingMethodAttribute enclosingMethodAttribute = (EnclosingMethodAttribute) c.getClassFile().getAttribute(EnclosingMethodAttribute.tag); - if (enclosingMethodAttribute != null) { - if (enclosingMethodAttribute.methodIndex() > 0) { - return EntryFactory.getBehaviorEntry( - Descriptor.toJvmName(enclosingMethodAttribute.className()), - enclosingMethodAttribute.methodName(), - enclosingMethodAttribute.methodDescriptor() - ); - } else { - // an attribute but no method? assume not anonymous - return null; - } - } - - // if there's an inner class attribute, but not an enclosing method attribute, then it's not anonymous - InnerClassesAttribute innerClassesAttribute = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); - if (innerClassesAttribute != null) { - return 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; } - ClassEntry innerClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); - - // anonymous classes: - // can't be abstract - // have only one constructor - // it's called exactly once by the outer class - // the type the instance is assigned to can't be this type - - // is abstract? - if (Modifier.isAbstract(c.getModifiers())) { - return null; + TypeDescriptor accessReturn = access.getDesc().getReturnDesc(); + TypeDescriptor calledReturn = called.getDesc().getReturnDesc(); + if (calledReturn.isVoid() || calledReturn.isPrimitive() || accessReturn.isVoid() || accessReturn.isPrimitive()) { + return false; } - // is there exactly one constructor? - if (c.getDeclaredConstructors().length != 1) { - return null; + // Bridged methods will never have the same type as what they are calling + if (accessReturn.equals(calledReturn)) { + return false; } - CtConstructor constructor = c.getDeclaredConstructors()[0]; - // is this constructor called exactly once? - ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor); - Collection> references = getBehaviorReferences(constructorEntry); - if (references.size() != 1) { - return null; - } + String accessType = accessReturn.toString(); - // does the caller use this type? - BehaviorEntry caller = references.iterator().next().context; - for (FieldEntry fieldEntry : getReferencedFields(caller)) { - if (fieldEntry.getType().hasClass() && fieldEntry.getType().getClassEntry().equals(innerClassEntry)) { - // caller references this type, so it can't be anonymous - return null; - } - } - for (BehaviorEntry behaviorEntry : getReferencedBehaviors(caller)) { - if (behaviorEntry.getSignature().hasClass(innerClassEntry)) { - return null; - } + // 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; } - return caller; + // 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() { + public Collection getObfFieldEntries() { return this.fields.values(); } - public Collection getObfFieldEntries(ClassEntry classEntry) { + public Collection getObfFieldEntries(ClassEntry classEntry) { return this.fields.get(classEntry); } - public Collection getObfBehaviorEntries() { - return this.behaviors.values(); + public Collection getObfBehaviorEntries() { + return this.methods.values(); } - public Collection getObfBehaviorEntries(ClassEntry classEntry) { - return this.behaviors.get(classEntry); + public Collection getObfBehaviorEntries(ClassEntry classEntry) { + return this.methods.get(classEntry); } public TranslationIndex getTranslationIndex() { @@ -533,8 +246,8 @@ public class JarIndex { } } ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode( - deobfuscatingTranslator, - ancestry.get(ancestry.size() - 1) + deobfuscatingTranslator, + ancestry.get(ancestry.size() - 1) ); // expand all children recursively @@ -557,28 +270,20 @@ public class JarIndex { public MethodInheritanceTreeNode getMethodInheritance(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) { // travel to the ancestor implementation - ClassEntry baseImplementationClassEntry = obfMethodEntry.getClassEntry(); - for (ClassEntry ancestorClassEntry : this.translationIndex.getAncestry(obfMethodEntry.getClassEntry())) { - MethodEntry ancestorMethodEntry = new MethodEntry( - new ClassEntry(ancestorClassEntry), - obfMethodEntry.getName(), - obfMethodEntry.getSignature() - ); - if (containsObfBehavior(ancestorMethodEntry)) { + ClassEntry baseImplementationClassEntry = obfMethodEntry.getOwnerClassEntry(); + for (ClassEntry ancestorClassEntry : this.translationIndex.getAncestry(obfMethodEntry.getOwnerClassEntry())) { + MethodEntry ancestorMethodEntry = entryPool.getMethod(ancestorClassEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString()); + if (ancestorMethodEntry != null && containsObfMethod(ancestorMethodEntry)) { baseImplementationClassEntry = ancestorClassEntry; } } // make a root node at the base - MethodEntry methodEntry = new MethodEntry( - baseImplementationClassEntry, - obfMethodEntry.getName(), - obfMethodEntry.getSignature() - ); + MethodEntry methodEntry = entryPool.getMethod(baseImplementationClassEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString()); MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode( - deobfuscatingTranslator, - methodEntry, - containsObfBehavior(methodEntry) + deobfuscatingTranslator, + methodEntry, + containsObfMethod(methodEntry) ); // expand the full tree @@ -599,12 +304,8 @@ public class JarIndex { for (ClassEntry interfaceEntry : getInterfaces(obfMethodEntry.getClassName())) { // is this method defined in this interface? - MethodEntry methodInterface = new MethodEntry( - interfaceEntry, - obfMethodEntry.getName(), - obfMethodEntry.getSignature() - ); - if (containsObfBehavior(methodInterface)) { + MethodEntry methodInterface = entryPool.getMethod(interfaceEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString()); + if (methodInterface != null && containsObfMethod(methodInterface)) { interfaceMethodEntries.add(methodInterface); } } @@ -623,27 +324,30 @@ public class JarIndex { public Set getRelatedMethodImplementations(MethodEntry obfMethodEntry) { Set methodEntries = Sets.newHashSet(); - getRelatedMethodImplementations(methodEntries, getMethodInheritance(new Translator(), obfMethodEntry)); + 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 (containsObfBehavior(methodEntry)) { + if (containsObfMethod(methodEntry)) { // collect the entry methodEntries.add(methodEntry); } - // look at bridged methods! - MethodEntry bridgedEntry = getBridgedMethod(methodEntry); - while (bridgedEntry != null) { - methodEntries.addAll(getRelatedMethodImplementations(bridgedEntry)); - bridgedEntry = getBridgedMethod(bridgedEntry); + // 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 Translator(), methodEntry)) { + for (MethodImplementationsTreeNode implementationsNode : getMethodImplementations(new DirectionalTranslator(entryPool), methodEntry)) { getRelatedMethodImplementations(methodEntries, implementationsNode); } @@ -655,16 +359,16 @@ public class JarIndex { private void getRelatedMethodImplementations(Set methodEntries, MethodImplementationsTreeNode node) { MethodEntry methodEntry = node.getMethodEntry(); - if (containsObfBehavior(methodEntry)) { + if (containsObfMethod(methodEntry)) { // collect the entry methodEntries.add(methodEntry); } - // look at bridged methods! - MethodEntry bridgedEntry = getBridgedMethod(methodEntry); - while (bridgedEntry != null) { - methodEntries.addAll(getRelatedMethodImplementations(bridgedEntry)); - bridgedEntry = getBridgedMethod(bridgedEntry); + // look at bridge methods! + MethodEntry bridgedMethod = getBridgedMethod(methodEntry); + while (bridgedMethod != null) { + methodEntries.addAll(getRelatedMethodImplementations(bridgedMethod)); + bridgedMethod = getBridgedMethod(bridgedMethod); } // recurse @@ -673,34 +377,27 @@ public class JarIndex { } } - public Collection> getFieldReferences(FieldEntry fieldEntry) { + public Collection> getFieldReferences(FieldEntry fieldEntry) { return this.fieldReferences.get(fieldEntry); } - public Collection getReferencedFields(BehaviorEntry behaviorEntry) { + 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 == behaviorEntry) { + for (EntryReference reference : this.fieldReferences.values()) { + if (reference.context == methodEntry) { fieldEntries.add(reference.entry); } } return fieldEntries; } - public Collection> getBehaviorReferences(BehaviorEntry behaviorEntry) { - return this.behaviorReferences.get(behaviorEntry); + public Collection> getMethodsReferencing(MethodEntry methodEntry) { + return this.methodsReferencing.get(methodEntry); } - public Collection getReferencedBehaviors(BehaviorEntry behaviorEntry) { - // linear search is fast enough for now - Set behaviorEntries = Sets.newHashSet(); - for (EntryReference reference : this.behaviorReferences.values()) { - if (reference.context == behaviorEntry) { - behaviorEntries.add(reference.entry); - } - } - return behaviorEntries; + public Collection getReferencedMethods(MethodDefEntry methodEntry) { + return this.methodReferences.get(methodEntry); } public Collection getInnerClasses(ClassEntry obfOuterClassEntry) { @@ -711,22 +408,13 @@ public class JarIndex { return this.outerClassesByInner.get(obfInnerClassEntry); } - public boolean isAnonymousClass(ClassEntry obfInnerClassEntry) { - return this.anonymousClasses.containsKey(obfInnerClassEntry); - } - public boolean isSyntheticMethod(MethodEntry methodEntry) { return this.syntheticMethods.contains(methodEntry); } - public BehaviorEntry getAnonymousClassCaller(ClassEntry obfInnerClassName) { - return this.anonymousClasses.get(obfInnerClassName); - } - public Set getInterfaces(String className) { - ClassEntry classEntry = new ClassEntry(className); - Set interfaces = new HashSet<>(); - interfaces.addAll(this.translationIndex.getInterfaces(classEntry)); + 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)); } @@ -754,7 +442,7 @@ public class JarIndex { } public boolean isInterface(String className) { - return this.translationIndex.isInterface(new ClassEntry(className)); + return this.translationIndex.isInterface(entryPool.getClass(className)); } public boolean containsObfClass(ClassEntry obfClassEntry) { @@ -765,8 +453,8 @@ public class JarIndex { return this.access.containsKey(obfFieldEntry); } - public boolean containsObfBehavior(BehaviorEntry obfBehaviorEntry) { - return this.access.containsKey(obfBehaviorEntry); + public boolean containsObfMethod(MethodEntry obfMethodEntry) { + return this.access.containsKey(obfMethodEntry); } public boolean containsEntryWithSameName(Entry entry) { @@ -776,15 +464,13 @@ public class JarIndex { return false; } - public boolean containsObfArgument(ArgumentEntry obfArgumentEntry) { + public boolean containsObfVariable(LocalVariableEntry obfVariableEntry) { // check the behavior - if (!containsObfBehavior(obfArgumentEntry.getBehaviorEntry())) { + if (!containsObfMethod(obfVariableEntry.getOwnerEntry())) { return false; } - // check the argument - return obfArgumentEntry.getIndex() < obfArgumentEntry.getBehaviorEntry().getSignature().getArgumentTypes().size(); - + return true; } public boolean containsObfEntry(Entry obfEntry) { @@ -792,15 +478,12 @@ public class JarIndex { return containsObfClass((ClassEntry) obfEntry); } else if (obfEntry instanceof FieldEntry) { return containsObfField((FieldEntry) obfEntry); - } else if (obfEntry instanceof BehaviorEntry) { - return containsObfBehavior((BehaviorEntry) obfEntry); - } else if (obfEntry instanceof ArgumentEntry) { - return containsObfArgument((ArgumentEntry) obfEntry); + } else if (obfEntry instanceof MethodEntry) { + return containsObfMethod((MethodEntry) obfEntry); } else if (obfEntry instanceof LocalVariableEntry) { - // TODO: Implement it - return false; + return containsObfVariable((LocalVariableEntry) obfEntry); } else { - throw new Error("Entry type not supported: " + obfEntry.getClass().getName()); + throw new Error("Entry desc not supported: " + obfEntry.getClass().getName()); } } diff --git a/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java b/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java index bacb1aa..723fffe 100644 --- a/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java @@ -12,8 +12,8 @@ package cuchaz.enigma.analysis; import com.google.common.collect.Lists; -import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.MethodEntry; import cuchaz.enigma.mapping.Translator; import javax.swing.tree.DefaultMutableTreeNode; @@ -54,11 +54,11 @@ public class MethodImplementationsTreeNode extends DefaultMutableTreeNode { } public String getDeobfClassName() { - return this.deobfuscatingTranslator.translateClass(this.entry.getClassName()); + return this.deobfuscatingTranslator.getTranslatedClass(this.entry.getOwnerClassEntry()).getClassName(); } public String getDeobfMethodName() { - return this.deobfuscatingTranslator.translate(this.entry); + return this.deobfuscatingTranslator.getTranslatedMethod(this.entry).getName(); } @Override @@ -80,9 +80,8 @@ public class MethodImplementationsTreeNode extends DefaultMutableTreeNode { // get all method implementations List nodes = Lists.newArrayList(); for (String implementingClassName : index.getImplementingClasses(this.entry.getClassName())) { - MethodEntry methodEntry = new MethodEntry(new ClassEntry(implementingClassName), this.entry.getName(), this.entry.getSignature() - ); - if (index.containsObfBehavior(methodEntry)) { + MethodEntry methodEntry = new MethodEntry(new ClassEntry(implementingClassName), this.entry.getName(), this.entry.getDesc()); + if (index.containsObfMethod(methodEntry)) { nodes.add(new MethodImplementationsTreeNode(this.deobfuscatingTranslator, methodEntry)); } } diff --git a/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java index 4f84dd0..904e594 100644 --- a/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java @@ -12,8 +12,8 @@ package cuchaz.enigma.analysis; import com.google.common.collect.Lists; -import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.MethodEntry; import cuchaz.enigma.mapping.Translator; import javax.swing.tree.DefaultMutableTreeNode; @@ -52,11 +52,11 @@ public class MethodInheritanceTreeNode extends DefaultMutableTreeNode { } public String getDeobfClassName() { - return this.deobfuscatingTranslator.translateClass(this.entry.getClassName()); + return this.deobfuscatingTranslator.getTranslatedClass(this.entry.getOwnerClassEntry()).getName(); } public String getDeobfMethodName() { - return this.deobfuscatingTranslator.translate(this.entry); + return this.deobfuscatingTranslator.getTranslatedMethod(this.entry).getName(); } public boolean isImplemented() { @@ -84,11 +84,9 @@ public class MethodInheritanceTreeNode extends DefaultMutableTreeNode { public void load(JarIndex index, boolean recurse) { // get all the child nodes List nodes = Lists.newArrayList(); - for (ClassEntry subclassEntry : index.getTranslationIndex().getSubclass(this.entry.getClassEntry())) { - MethodEntry methodEntry = new MethodEntry(subclassEntry, this.entry.getName(), this.entry.getSignature() - ); - nodes.add(new MethodInheritanceTreeNode(this.deobfuscatingTranslator, methodEntry, index.containsObfBehavior(methodEntry) - )); + for (ClassEntry subclassEntry : index.getTranslationIndex().getSubclass(this.entry.getOwnerClassEntry())) { + MethodEntry methodEntry = new MethodEntry(subclassEntry, this.entry.getName(), this.entry.getDesc()); + nodes.add(new MethodInheritanceTreeNode(this.deobfuscatingTranslator, methodEntry, index.containsObfMethod(methodEntry))); } // add them to this node diff --git a/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java new file mode 100644 index 0000000..76c73c1 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * 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.Sets; +import cuchaz.enigma.mapping.*; +import cuchaz.enigma.mapping.entry.Entry; +import cuchaz.enigma.mapping.entry.MethodDefEntry; +import cuchaz.enigma.mapping.entry.MethodEntry; + +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreeNode; +import java.util.Set; + +public class MethodReferenceTreeNode extends DefaultMutableTreeNode + implements ReferenceTreeNode { + + private Translator deobfuscatingTranslator; + private MethodEntry entry; + private EntryReference reference; + private Access access; + + public MethodReferenceTreeNode(Translator deobfuscatingTranslator, MethodEntry entry) { + this.deobfuscatingTranslator = deobfuscatingTranslator; + this.entry = entry; + this.reference = null; + } + + public MethodReferenceTreeNode(Translator deobfuscatingTranslator, + EntryReference reference, Access access) { + this.deobfuscatingTranslator = deobfuscatingTranslator; + this.entry = reference.entry; + this.reference = reference; + this.access = access; + } + + @Override + public MethodEntry getEntry() { + return this.entry; + } + + @Override + public EntryReference getReference() { + return this.reference; + } + + @Override + public String toString() { + if (this.reference != null) { + return String.format("%s (%s)", this.deobfuscatingTranslator.getTranslatedMethodDef(this.reference.context), + this.access); + } + return this.deobfuscatingTranslator.getTranslatedMethod(this.entry).getName(); + } + + public void load(JarIndex index, boolean recurse) { + // get all the child nodes + for (EntryReference reference : index.getMethodsReferencing(this.entry)) { + add(new MethodReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.entry))); + } + + if (recurse && this.children != null) { + for (Object child : this.children) { + if (child instanceof MethodReferenceTreeNode) { + MethodReferenceTreeNode node = (MethodReferenceTreeNode) child; + + // don't recurse into ancestor + Set ancestors = Sets.newHashSet(); + TreeNode n = node; + while (n.getParent() != null) { + n = n.getParent(); + if (n instanceof MethodReferenceTreeNode) { + ancestors.add(((MethodReferenceTreeNode) n).getEntry()); + } + } + if (ancestors.contains(node.getEntry())) { + continue; + } + + node.load(index, true); + } + } + } + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/ParsedJar.java b/src/main/java/cuchaz/enigma/analysis/ParsedJar.java new file mode 100644 index 0000000..55f2141 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/ParsedJar.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * 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 cuchaz.enigma.mapping.entry.ClassEntry; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.tree.ClassNode; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.*; +import java.util.function.Consumer; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; + +public class ParsedJar { + private final Map nodes = new LinkedHashMap<>(); + + public ParsedJar(JarFile jar) throws IOException { + try { + // get the jar entries that correspond to classes + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + // is this a class file? + if (entry.getName().endsWith(".class")) { + try (InputStream input = new BufferedInputStream(jar.getInputStream(entry))) { + // read the ClassNode from the jar + ClassReader reader = new ClassReader(input); + ClassNode node = new ClassNode(); + reader.accept(node, 0); + String path = entry.getName().substring(0, entry.getName().length() - ".class".length()); + nodes.put(path, node); + } + } + } + } finally { + jar.close(); + } + } + + public ParsedJar(JarInputStream jar) throws IOException { + try { + // get the jar entries that correspond to classes + JarEntry entry; + while ((entry = jar.getNextJarEntry()) != null) { + // is this a class file? + if (entry.getName().endsWith(".class")) { + // read the ClassNode from the jar + ClassReader reader = new ClassReader(jar); + ClassNode node = new ClassNode(); + reader.accept(node, 0); + String path = entry.getName().substring(0, entry.getName().length() - ".class".length()); + nodes.put(path, node); + jar.closeEntry(); + } + } + } finally { + jar.close(); + } + } + + public void visit(Consumer visitor) { + for (ClassNode node : nodes.values()) { + visitor.accept(node); + } + } + + public int getClassCount() { + return nodes.size(); + } + + public List getClassEntries() { + List entries = new ArrayList<>(nodes.size()); + for (ClassNode node : nodes.values()) { + entries.add(new ClassEntry(node.name)); + } + return entries; + } + + public ClassNode getClassNode(String name) { + return nodes.get(name); + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java index 0469363..3950d16 100644 --- a/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java @@ -11,7 +11,7 @@ package cuchaz.enigma.analysis; -import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.entry.Entry; public interface ReferenceTreeNode { E getEntry(); diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndex.java b/src/main/java/cuchaz/enigma/analysis/SourceIndex.java index 19250c8..14b2e76 100644 --- a/src/main/java/cuchaz/enigma/analysis/SourceIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/SourceIndex.java @@ -18,7 +18,7 @@ import com.google.common.collect.Multimap; import com.strobel.decompiler.languages.Region; import com.strobel.decompiler.languages.java.ast.AstNode; import com.strobel.decompiler.languages.java.ast.Identifier; -import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.entry.Entry; import java.util.Collection; import java.util.List; diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java deleted file mode 100644 index 1b61916..0000000 --- a/src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java +++ /dev/null @@ -1,204 +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.HashMultimap; -import com.google.common.collect.Multimap; -import com.strobel.assembler.metadata.MemberReference; -import com.strobel.assembler.metadata.MethodReference; -import com.strobel.assembler.metadata.ParameterDefinition; -import com.strobel.assembler.metadata.TypeReference; -import com.strobel.decompiler.languages.TextLocation; -import com.strobel.decompiler.languages.java.ast.*; -import cuchaz.enigma.mapping.*; -import javassist.bytecode.Descriptor; - -import java.util.HashMap; -import java.util.Map; - -public class SourceIndexBehaviorVisitor extends SourceIndexVisitor { - private BehaviorEntry behaviorEntry; - - // TODO: Really fix Procyon index problem with inner classes - private int argumentPosition; - private int localsPosition; - private Multimap unmatchedIdentifier = HashMultimap.create(); - private Map identifierEntryCache = new HashMap<>(); - - public SourceIndexBehaviorVisitor(BehaviorEntry behaviorEntry, boolean isEnum) { - this.behaviorEntry = behaviorEntry; - this.argumentPosition = isEnum ? 2 : 0; - this.localsPosition = 0; - } - - @Override - public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) { - MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); - - // get the behavior entry - ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); - BehaviorEntry behaviorEntry = null; - if (ref instanceof MethodReference) { - MethodReference methodRef = (MethodReference) ref; - if (methodRef.isConstructor()) { - behaviorEntry = new ConstructorEntry(classEntry, new Signature(ref.getErasedSignature())); - } else if (methodRef.isTypeInitializer()) { - behaviorEntry = new ConstructorEntry(classEntry); - } else { - behaviorEntry = new MethodEntry(classEntry, ref.getName(), new Signature(ref.getErasedSignature())); - } - } - if (behaviorEntry != null) { - // get the node for the token - AstNode tokenNode = null; - if (node.getTarget() instanceof MemberReferenceExpression) { - tokenNode = ((MemberReferenceExpression) node.getTarget()).getMemberNameToken(); - } else if (node.getTarget() instanceof SuperReferenceExpression) { - tokenNode = node.getTarget(); - } else if (node.getTarget() instanceof ThisReferenceExpression) { - tokenNode = node.getTarget(); - } - if (tokenNode != null) { - index.addReference(tokenNode, behaviorEntry, this.behaviorEntry); - } - } - - // Check for identifier - node.getArguments().stream().filter(expression -> expression instanceof IdentifierExpression) - .forEach(expression -> this.checkIdentifier((IdentifierExpression) expression, index)); - return recurse(node, index); - } - - @Override - public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) { - MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); - if (ref != null) { - // make sure this is actually a field - if (ref.getErasedSignature().indexOf('(') >= 0) { - throw new Error("Expected a field here! got " + ref); - } - - ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); - FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getErasedSignature())); - index.addReference(node.getMemberNameToken(), fieldEntry, this.behaviorEntry); - } - - return recurse(node, index); - } - - @Override - public Void visitSimpleType(SimpleType node, SourceIndex index) { - TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE); - if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) { - ClassEntry classEntry = new ClassEntry(ref.getInternalName()); - index.addReference(node.getIdentifierToken(), classEntry, this.behaviorEntry); - } - - return recurse(node, index); - } - - @Override - public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) { - ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION); - if (def.getMethod() instanceof MemberReference && def.getMethod() instanceof MethodReference) { - ArgumentEntry argumentEntry = new ArgumentEntry(ProcyonEntryFactory.getBehaviorEntry((MethodReference) def.getMethod()), - argumentPosition++, node.getName()); - Identifier identifier = node.getNameToken(); - // cache the argument entry and the identifier - identifierEntryCache.put(identifier.getName(), argumentEntry); - index.addDeclaration(identifier, argumentEntry); - } - - return recurse(node, index); - } - - @Override - public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) { - MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); - if (ref != null) { - ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); - FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getErasedSignature())); - index.addReference(node.getIdentifierToken(), fieldEntry, this.behaviorEntry); - } else - this.checkIdentifier(node, index); - return recurse(node, index); - } - - private void checkIdentifier(IdentifierExpression node, SourceIndex index) { - if (identifierEntryCache.containsKey(node.getIdentifier())) // If it's in the argument cache, create a token! - index.addDeclaration(node.getIdentifierToken(), identifierEntryCache.get(node.getIdentifier())); - else - unmatchedIdentifier.put(node.getIdentifier(), node.getIdentifierToken()); // Not matched actually, put it! - } - - private void addDeclarationToUnmatched(String key, SourceIndex index) { - Entry entry = identifierEntryCache.get(key); - - // This cannot happened in theory - if (entry == null) - return; - for (Identifier identifier : unmatchedIdentifier.get(key)) - index.addDeclaration(identifier, entry); - unmatchedIdentifier.removeAll(key); - } - - @Override - public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) { - MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); - if (ref != null) { - ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); - ConstructorEntry constructorEntry = new ConstructorEntry(classEntry, new Signature(ref.getErasedSignature())); - if (node.getType() instanceof SimpleType) { - SimpleType simpleTypeNode = (SimpleType) node.getType(); - index.addReference(simpleTypeNode.getIdentifierToken(), constructorEntry, this.behaviorEntry); - } - } - - return recurse(node, index); - } - - @Override - public Void visitForEachStatement(ForEachStatement node, SourceIndex index) { - if (node.getVariableType() instanceof SimpleType) { - SimpleType type = (SimpleType) node.getVariableType(); - TypeReference typeReference = type.getUserData(Keys.TYPE_REFERENCE); - Identifier identifier = node.getVariableNameToken(); - String signature = Descriptor.of(typeReference.getErasedDescription()); - LocalVariableEntry localVariableEntry = new LocalVariableEntry(behaviorEntry, argumentPosition + localsPosition++, identifier.getName(), new Type(signature)); - identifierEntryCache.put(identifier.getName(), localVariableEntry); - addDeclarationToUnmatched(identifier.getName(), index); - index.addDeclaration(identifier, localVariableEntry); - } - return recurse(node, index); - } - - @Override - public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) { - AstNodeCollection variables = node.getVariables(); - - // Single assignation - if (variables.size() == 1) { - VariableInitializer initializer = variables.firstOrNullObject(); - if (initializer != null && node.getType() instanceof SimpleType) { - SimpleType type = (SimpleType) node.getType(); - TypeReference typeReference = type.getUserData(Keys.TYPE_REFERENCE); - String signature = Descriptor.of(typeReference.getErasedDescription()); - Identifier identifier = initializer.getNameToken(); - LocalVariableEntry localVariableEntry = new LocalVariableEntry(behaviorEntry, argumentPosition + localsPosition++, initializer.getName(), new Type(signature)); - identifierEntryCache.put(identifier.getName(), localVariableEntry); - addDeclarationToUnmatched(identifier.getName(), index); - index.addDeclaration(identifier, localVariableEntry); - } - } - return recurse(node, index); - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java index b13415d..dd5bcef 100644 --- a/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java +++ b/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java @@ -17,14 +17,21 @@ import com.strobel.assembler.metadata.TypeDefinition; import com.strobel.assembler.metadata.TypeReference; import com.strobel.decompiler.languages.TextLocation; import com.strobel.decompiler.languages.java.ast.*; -import cuchaz.enigma.mapping.*; +import cuchaz.enigma.bytecode.AccessFlags; +import cuchaz.enigma.mapping.Signature; +import cuchaz.enigma.mapping.entry.*; public class SourceIndexClassVisitor extends SourceIndexVisitor { + private final ReferencedEntryPool entryPool; + private final ProcyonEntryFactory entryFactory; - private ClassEntry classEntry; + private ClassDefEntry classEntry; private boolean isEnum; - public SourceIndexClassVisitor(ClassEntry classEntry) { + public SourceIndexClassVisitor(ReferencedEntryPool entryPool, ClassDefEntry classEntry) { + super(entryPool); + this.entryPool = entryPool; + this.entryFactory = new ProcyonEntryFactory(entryPool); this.classEntry = classEntry; } @@ -32,11 +39,11 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor { public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { // is this this class, or a subtype? TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); - ClassEntry classEntry = new ClassEntry(def.getInternalName()); + ClassDefEntry classEntry = new ClassDefEntry(def.getInternalName(), Signature.createSignature(def.getSignature()), new AccessFlags(def.getModifiers())); if (!classEntry.equals(this.classEntry)) { - // it's a sub-type, recurse + // it's a subtype, recurse index.addDeclaration(node.getNameToken(), classEntry); - return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); + return node.acceptVisitor(new SourceIndexClassVisitor(entryPool, classEntry), index); } return recurse(node, index); @@ -56,31 +63,28 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor { @Override public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) { MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); - BehaviorEntry behaviorEntry = ProcyonEntryFactory.getBehaviorEntry(def); + MethodDefEntry methodEntry = entryFactory.getMethodDefEntry(def); AstNode tokenNode = node.getNameToken(); - if (behaviorEntry instanceof ConstructorEntry) { - ConstructorEntry constructorEntry = (ConstructorEntry) behaviorEntry; - if (constructorEntry.isStatic()) { - // for static initializers, check elsewhere for the token node - tokenNode = node.getModifiers().firstOrNullObject(); - } + if (methodEntry.isConstructor() && methodEntry.getName().equals("")) { + // for static initializers, check elsewhere for the token node + tokenNode = node.getModifiers().firstOrNullObject(); } - index.addDeclaration(tokenNode, behaviorEntry); - return node.acceptVisitor(new SourceIndexBehaviorVisitor(behaviorEntry, false), index); + index.addDeclaration(tokenNode, methodEntry); + return node.acceptVisitor(new SourceIndexMethodVisitor(entryPool, classEntry, methodEntry), index); } @Override public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) { MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); - ConstructorEntry constructorEntry = ProcyonEntryFactory.getConstructorEntry(def); - index.addDeclaration(node.getNameToken(), constructorEntry); - return node.acceptVisitor(new SourceIndexBehaviorVisitor(constructorEntry, isEnum), index); + MethodDefEntry methodEntry = entryFactory.getMethodDefEntry(def); + index.addDeclaration(node.getNameToken(), methodEntry); + return node.acceptVisitor(new SourceIndexMethodVisitor(entryPool, classEntry, methodEntry), index); } @Override public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) { FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); - FieldEntry fieldEntry = ProcyonEntryFactory.getFieldEntry(def); + FieldDefEntry fieldEntry = entryFactory.getFieldDefEntry(def); assert (node.getVariables().size() == 1); VariableInitializer variable = node.getVariables().firstOrNullObject(); index.addDeclaration(variable.getNameToken(), fieldEntry); @@ -92,7 +96,7 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor { public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) { // treat enum declarations as field declarations FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); - FieldEntry fieldEntry = ProcyonEntryFactory.getFieldEntry(def); + FieldDefEntry fieldEntry = entryFactory.getFieldDefEntry(def); index.addDeclaration(node.getNameToken(), fieldEntry); this.isEnum = true; return recurse(node, index); diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java new file mode 100644 index 0000000..83fe296 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java @@ -0,0 +1,224 @@ +/******************************************************************************* + * 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.HashMultimap; +import com.google.common.collect.Multimap; +import com.strobel.assembler.metadata.*; +import com.strobel.decompiler.ast.Variable; +import com.strobel.decompiler.languages.TextLocation; +import com.strobel.decompiler.languages.java.ast.*; +import cuchaz.enigma.mapping.TypeDescriptor; +import cuchaz.enigma.mapping.entry.*; + +import java.lang.Error; +import java.util.HashMap; +import java.util.Map; + +public class SourceIndexMethodVisitor extends SourceIndexVisitor { + private final ReferencedEntryPool entryPool; + private final ProcyonEntryFactory entryFactory; + + private final ClassDefEntry ownerEntry; + private final MethodDefEntry methodEntry; + + private Multimap unmatchedIdentifier = HashMultimap.create(); + private Map identifierEntryCache = new HashMap<>(); + + public SourceIndexMethodVisitor(ReferencedEntryPool entryPool, ClassDefEntry ownerEntry, MethodDefEntry methodEntry) { + super(entryPool); + this.entryPool = entryPool; + this.entryFactory = new ProcyonEntryFactory(entryPool); + this.ownerEntry = ownerEntry; + this.methodEntry = methodEntry; + } + + @Override + public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + + // get the behavior entry + ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName()); + MethodEntry methodEntry = null; + if (ref instanceof MethodReference) { + methodEntry = entryPool.getMethod(classEntry, ref.getName(), ref.getErasedSignature()); + } + if (methodEntry != null) { + // get the node for the token + AstNode tokenNode = null; + if (node.getTarget() instanceof MemberReferenceExpression) { + tokenNode = ((MemberReferenceExpression) node.getTarget()).getMemberNameToken(); + } else if (node.getTarget() instanceof SuperReferenceExpression) { + tokenNode = node.getTarget(); + } else if (node.getTarget() instanceof ThisReferenceExpression) { + tokenNode = node.getTarget(); + } + if (tokenNode != null) { + index.addReference(tokenNode, methodEntry, this.methodEntry); + } + } + + // Check for identifier + node.getArguments().stream().filter(expression -> expression instanceof IdentifierExpression) + .forEach(expression -> this.checkIdentifier((IdentifierExpression) expression, index)); + return recurse(node, index); + } + + @Override + public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + if (ref != null) { + // make sure this is actually a field + String erasedSignature = ref.getErasedSignature(); + if (erasedSignature.indexOf('(') >= 0) { + throw new Error("Expected a field here! got " + ref); + } + + ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName()); + FieldEntry fieldEntry = entryPool.getField(classEntry, ref.getName(), new TypeDescriptor(erasedSignature)); + if (fieldEntry == null) { + throw new Error("Failed to find field " + ref.getName() + " on " + classEntry.getName()); + } + index.addReference(node.getMemberNameToken(), fieldEntry, this.methodEntry); + } + + return recurse(node, index); + } + + @Override + public Void visitSimpleType(SimpleType node, SourceIndex index) { + TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE); + if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) { + ClassEntry classEntry = entryPool.getClass(ref.getInternalName()); + index.addReference(node.getIdentifierToken(), classEntry, this.methodEntry); + } + + return recurse(node, index); + } + + @Override + public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) { + ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION); + + int variableOffset = this.methodEntry.getVariableOffset(ownerEntry); + int parameterIndex = def.getSlot() - variableOffset; + + if (parameterIndex >= 0) { + LocalVariableEntry localVariableEntry = new LocalVariableEntry(methodEntry, parameterIndex, node.getName()); + Identifier identifier = node.getNameToken(); + // cache the argument entry and the identifier + identifierEntryCache.put(identifier.getName(), localVariableEntry); + index.addDeclaration(identifier, localVariableEntry); + } + + return recurse(node, index); + } + + @Override + public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + if (ref != null) { + ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName()); + FieldEntry fieldEntry = entryPool.getField(classEntry, ref.getName(), new TypeDescriptor(ref.getErasedSignature())); + if (fieldEntry == null) { + throw new Error("Failed to find field " + ref.getName() + " on " + classEntry.getName()); + } + index.addReference(node.getIdentifierToken(), fieldEntry, this.methodEntry); + } else + this.checkIdentifier(node, index); + return recurse(node, index); + } + + private void checkIdentifier(IdentifierExpression node, SourceIndex index) { + if (identifierEntryCache.containsKey(node.getIdentifier())) // If it's in the argument cache, create a token! + index.addDeclaration(node.getIdentifierToken(), identifierEntryCache.get(node.getIdentifier())); + else + unmatchedIdentifier.put(node.getIdentifier(), node.getIdentifierToken()); // Not matched actually, put it! + } + + private void addDeclarationToUnmatched(String key, SourceIndex index) { + Entry entry = identifierEntryCache.get(key); + + // This cannot happened in theory + if (entry == null) + return; + for (Identifier identifier : unmatchedIdentifier.get(key)) + index.addDeclaration(identifier, entry); + unmatchedIdentifier.removeAll(key); + } + + @Override + public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + if (ref != null) { + ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName()); + MethodEntry constructorEntry = entryPool.getMethod(classEntry, "", ref.getErasedSignature()); + if (node.getType() instanceof SimpleType) { + SimpleType simpleTypeNode = (SimpleType) node.getType(); + index.addReference(simpleTypeNode.getIdentifierToken(), constructorEntry, this.methodEntry); + } + } + + return recurse(node, index); + } + + @Override + public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) { + AstNodeCollection variables = node.getVariables(); + + // Single assignation + if (variables.size() == 1) { + VariableInitializer initializer = variables.firstOrNullObject(); + if (initializer != null && node.getType() instanceof SimpleType) { + Identifier identifier = initializer.getNameToken(); + Variable variable = initializer.getUserData(Keys.VARIABLE); + if (variable != null) { + VariableDefinition originalVariable = variable.getOriginalVariable(); + if (originalVariable != null) { + int variableOffset = methodEntry.getVariableOffset(ownerEntry); + int variableIndex = originalVariable.getSlot() - variableOffset; + if (variableIndex >= 0) { + LocalVariableEntry localVariableEntry = new LocalVariableEntry(methodEntry, variableIndex, initializer.getName()); + identifierEntryCache.put(identifier.getName(), localVariableEntry); + addDeclarationToUnmatched(identifier.getName(), index); + index.addDeclaration(identifier, localVariableEntry); + } + } + } + } + } + return recurse(node, index); + } + + @Override + public Void visitMethodGroupExpression(MethodGroupExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + + if (ref instanceof MethodReference) { + // get the behavior entry + ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName()); + MethodEntry methodEntry = null; + + methodEntry = entryPool.getMethod(classEntry, ref.getName(), ref.getErasedSignature()); + // get the node for the token + AstNode tokenNode = node.getMethodNameToken(); + if (tokenNode == null || (tokenNode.getRegion().getBeginLine() == 0 || tokenNode.getRegion().getEndLine() == 0)){ + tokenNode = node.getTarget(); + } + if (tokenNode != null) { + index.addReference(tokenNode, methodEntry, this.methodEntry); + } + } + + return recurse(node, index); + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java index a94a55b..e588d24 100644 --- a/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java +++ b/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java @@ -14,17 +14,25 @@ package cuchaz.enigma.analysis; import com.strobel.assembler.metadata.TypeDefinition; import com.strobel.decompiler.languages.java.ast.*; import com.strobel.decompiler.patterns.Pattern; -import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.bytecode.AccessFlags; +import cuchaz.enigma.mapping.Signature; +import cuchaz.enigma.mapping.entry.ClassDefEntry; +import cuchaz.enigma.mapping.entry.ReferencedEntryPool; public class SourceIndexVisitor implements IAstVisitor { + private final ReferencedEntryPool entryPool; + + public SourceIndexVisitor(ReferencedEntryPool entryPool) { + this.entryPool = entryPool; + } @Override public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); - ClassEntry classEntry = new ClassEntry(def.getInternalName()); + ClassDefEntry classEntry = new ClassDefEntry(def.getInternalName(), Signature.createSignature(def.getSignature()), new AccessFlags(def.getModifiers())); index.addDeclaration(node.getNameToken(), classEntry); - return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); + return node.acceptVisitor(new SourceIndexClassVisitor(entryPool, classEntry), index); } protected Void recurse(AstNode node, SourceIndex index) { diff --git a/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java b/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java index 26be05b..b2ddc5f 100644 --- a/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java @@ -15,11 +15,9 @@ import com.google.common.collect.HashMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; +import cuchaz.enigma.bytecode.AccessFlags; import cuchaz.enigma.mapping.*; -import javassist.CtBehavior; -import javassist.CtClass; -import javassist.CtField; -import javassist.bytecode.Descriptor; +import cuchaz.enigma.mapping.entry.*; import java.util.Collection; import java.util.List; @@ -28,96 +26,91 @@ import java.util.Set; public class TranslationIndex { + private final ReferencedEntryPool entryPool; private Map superclasses; - private Multimap fieldEntries; - private Multimap behaviorEntries; + private Multimap fieldEntries; + private Multimap methodEntries; private Multimap interfaces; - public TranslationIndex() { + public TranslationIndex(ReferencedEntryPool entryPool) { + this.entryPool = entryPool; this.superclasses = Maps.newHashMap(); this.fieldEntries = HashMultimap.create(); - this.behaviorEntries = HashMultimap.create(); + this.methodEntries = HashMultimap.create(); this.interfaces = HashMultimap.create(); } public TranslationIndex(TranslationIndex other, Translator translator) { + this.entryPool = other.entryPool; + // translate the superclasses this.superclasses = Maps.newHashMap(); for (Map.Entry mapEntry : other.superclasses.entrySet()) { - this.superclasses.put(translator.translateEntry(mapEntry.getKey()), translator.translateEntry(mapEntry.getValue())); + this.superclasses.put(translator.getTranslatedClass(mapEntry.getKey()), translator.getTranslatedClass(mapEntry.getValue())); } // translate the interfaces this.interfaces = HashMultimap.create(); for (Map.Entry mapEntry : other.interfaces.entries()) { this.interfaces.put( - translator.translateEntry(mapEntry.getKey()), - translator.translateEntry(mapEntry.getValue()) + translator.getTranslatedClass(mapEntry.getKey()), + translator.getTranslatedClass(mapEntry.getValue()) ); } // translate the fields this.fieldEntries = HashMultimap.create(); - for (Map.Entry mapEntry : other.fieldEntries.entries()) { + for (Map.Entry mapEntry : other.fieldEntries.entries()) { this.fieldEntries.put( - translator.translateEntry(mapEntry.getKey()), - translator.translateEntry(mapEntry.getValue()) + translator.getTranslatedClass(mapEntry.getKey()), + translator.getTranslatedFieldDef(mapEntry.getValue()) ); } - this.behaviorEntries = HashMultimap.create(); - for (Map.Entry mapEntry : other.behaviorEntries.entries()) { - this.behaviorEntries.put( - translator.translateEntry(mapEntry.getKey()), - translator.translateEntry(mapEntry.getValue()) + this.methodEntries = HashMultimap.create(); + for (Map.Entry mapEntry : other.methodEntries.entries()) { + this.methodEntries.put( + translator.getTranslatedClass(mapEntry.getKey()), + translator.getTranslatedMethodDef(mapEntry.getValue()) ); } } - public void indexClass(CtClass c) { - indexClass(c, true); - } - - public void indexClass(CtClass c, boolean indexMembers) { - ClassEntry classEntry = EntryFactory.getClassEntry(c); + protected ClassDefEntry indexClass(int access, String name, String signature, String superName, String[] interfaces) { + ClassDefEntry classEntry = new ClassDefEntry(name, Signature.createSignature(signature), new AccessFlags(access)); if (isJre(classEntry)) { - return; + return null; } // add the superclass - ClassEntry superclassEntry = EntryFactory.getSuperclassEntry(c); + ClassEntry superclassEntry = entryPool.getClass(superName); if (superclassEntry != null) { this.superclasses.put(classEntry, superclassEntry); } // add the interfaces - for (String interfaceClassName : c.getClassFile().getInterfaces()) { - ClassEntry interfaceClassEntry = new ClassEntry(Descriptor.toJvmName(interfaceClassName)); + for (String interfaceClassName : interfaces) { + ClassEntry interfaceClassEntry = entryPool.getClass(interfaceClassName); if (!isJre(interfaceClassEntry)) { - this.interfaces.put(classEntry, interfaceClassEntry); } } - if (indexMembers) { - // add fields - for (CtField field : c.getDeclaredFields()) { - FieldEntry fieldEntry = EntryFactory.getFieldEntry(field); - this.fieldEntries.put(fieldEntry.getClassEntry(), fieldEntry); - } + return classEntry; + } - // add behaviors - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); - this.behaviorEntries.put(behaviorEntry.getClassEntry(), behaviorEntry); - } - } + protected void indexField(FieldDefEntry fieldEntry) { + this.fieldEntries.put(fieldEntry.getOwnerClassEntry(), fieldEntry); + } + + protected void indexMethod(MethodDefEntry methodEntry) { + this.methodEntries.put(methodEntry.getOwnerClassEntry(), methodEntry); } public void renameClasses(Map renames) { EntryRenamer.renameClassesInMap(renames, this.superclasses); EntryRenamer.renameClassesInMultimap(renames, this.fieldEntries); - EntryRenamer.renameClassesInMultimap(renames, this.behaviorEntries); + EntryRenamer.renameClassesInMultimap(renames, this.methodEntries); } public ClassEntry getSuperclass(ClassEntry classEntry) { @@ -175,31 +168,32 @@ public class TranslationIndex { } public boolean entryExists(Entry entry) { + if (entry == null) { + return false; + } if (entry instanceof FieldEntry) { return fieldExists((FieldEntry) entry); - } else if (entry instanceof BehaviorEntry) { - return behaviorExists((BehaviorEntry) entry); - } else if (entry instanceof ArgumentEntry) { - return behaviorExists(((ArgumentEntry) entry).getBehaviorEntry()); + } else if (entry instanceof MethodEntry) { + return methodExists((MethodEntry) entry); } else if (entry instanceof LocalVariableEntry) { - return behaviorExists(((LocalVariableEntry) entry).getBehaviorEntry()); + return methodExists(((LocalVariableEntry) entry).getOwnerEntry()); } throw new IllegalArgumentException("Cannot check existence for " + entry.getClass()); } public boolean fieldExists(FieldEntry fieldEntry) { - return this.fieldEntries.containsEntry(fieldEntry.getClassEntry(), fieldEntry); + return this.fieldEntries.containsEntry(fieldEntry.getOwnerClassEntry(), fieldEntry); } - public boolean behaviorExists(BehaviorEntry behaviorEntry) { - return this.behaviorEntries.containsEntry(behaviorEntry.getClassEntry(), behaviorEntry); + public boolean methodExists(MethodEntry methodEntry) { + return this.methodEntries.containsEntry(methodEntry.getOwnerClassEntry(), methodEntry); } - public ClassEntry resolveEntryClass(Entry entry) { - return resolveEntryClass(entry, false); + public ClassEntry resolveEntryOwner(Entry entry) { + return resolveEntryOwner(entry, false); } - public ClassEntry resolveEntryClass(Entry entry, boolean checkSuperclassBeforeChild) { + public ClassEntry resolveEntryOwner(Entry entry, boolean checkSuperclassBeforeChild) { if (entry instanceof ClassEntry) { return (ClassEntry) entry; } @@ -227,12 +221,12 @@ public class TranslationIndex { Entry originalEntry = entry; // Get all possible superclasses and reverse the list - List superclasses = Lists.reverse(getAncestry(originalEntry.getClassEntry())); + List superclasses = Lists.reverse(getAncestry(originalEntry.getOwnerClassEntry())); boolean existInEntry = false; for (ClassEntry classEntry : superclasses) { - entry = entry.cloneToNewClass(classEntry); + entry = entry.updateOwnership(classEntry); existInEntry = entryExists(entry); // Check for possible entry in interfaces of superclasses @@ -245,9 +239,9 @@ public class TranslationIndex { // Doesn't exists in superclasses? check the child or return null if (!existInEntry) - return !entryExists(originalEntry) ? null : originalEntry.getClassEntry(); + return !entryExists(originalEntry) ? null : originalEntry.getOwnerClassEntry(); - return entry.getClassEntry(); + return entry.getOwnerClassEntry(); } public ClassEntry resolveSuperclass(Entry entry) { @@ -256,7 +250,7 @@ public class TranslationIndex { while (!entryExists(entry)) { // is there a parent class? - ClassEntry superclassEntry = getSuperclass(entry.getClassEntry()); + ClassEntry superclassEntry = getSuperclass(entry.getOwnerClassEntry()); if (superclassEntry == null) { // this is probably a method from a class in a library // we can't trace the implementation up any higher unless we index the library @@ -264,23 +258,23 @@ public class TranslationIndex { } // move up to the parent class - entry = entry.cloneToNewClass(superclassEntry); + entry = entry.updateOwnership(superclassEntry); } - return entry.getClassEntry(); + return entry.getOwnerClassEntry(); } public ClassEntry resolveInterface(Entry entry) { // the interfaces for any class is a forest // so let's look at all the trees - for (ClassEntry interfaceEntry : this.interfaces.get(entry.getClassEntry())) { + for (ClassEntry interfaceEntry : this.interfaces.get(entry.getOwnerClassEntry())) { Collection subInterface = this.interfaces.get(interfaceEntry); if (subInterface != null && !subInterface.isEmpty()) { - ClassEntry result = resolveInterface(entry.cloneToNewClass(interfaceEntry)); + ClassEntry result = resolveInterface(entry.updateOwnership(interfaceEntry)); if (result != null) return result; } - ClassEntry resolvedClassEntry = resolveSuperclass(entry.cloneToNewClass(interfaceEntry)); + ClassEntry resolvedClassEntry = resolveSuperclass(entry.updateOwnership(interfaceEntry)); if (resolvedClassEntry != null) { return resolvedClassEntry; } -- cgit v1.2.3