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