summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/analysis
diff options
context:
space:
mode:
authorGravatar gegy10002018-07-17 19:14:08 +0200
committerGravatar GitHub2018-07-17 19:14:08 +0200
commita88175ffc95792b88a8724f66db6dda2b8cc32ee (patch)
tree65895bbc6cf1766f4ca01e1257619ab1993e71dc /src/main/java/cuchaz/enigma/analysis
parentMerge pull request #3 from thiakil/src-jar (diff)
downloadenigma-fork-a88175ffc95792b88a8724f66db6dda2b8cc32ee.tar.gz
enigma-fork-a88175ffc95792b88a8724f66db6dda2b8cc32ee.tar.xz
enigma-fork-a88175ffc95792b88a8724f66db6dda2b8cc32ee.zip
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
Diffstat (limited to 'src/main/java/cuchaz/enigma/analysis')
-rw-r--r--src/main/java/cuchaz/enigma/analysis/Access.java11
-rw-r--r--src/main/java/cuchaz/enigma/analysis/BridgeMarker.java38
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java12
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java16
-rw-r--r--src/main/java/cuchaz/enigma/analysis/EntryReference.java18
-rw-r--r--src/main/java/cuchaz/enigma/analysis/EntryRenamer.java63
-rw-r--r--src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java29
-rw-r--r--src/main/java/cuchaz/enigma/analysis/IndexClassVisitor.java38
-rw-r--r--src/main/java/cuchaz/enigma/analysis/IndexInnerClassVisitor.java23
-rw-r--r--src/main/java/cuchaz/enigma/analysis/IndexReferenceVisitor.java77
-rw-r--r--src/main/java/cuchaz/enigma/analysis/JarClassIterator.java123
-rw-r--r--src/main/java/cuchaz/enigma/analysis/JarIndex.java635
-rw-r--r--src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java13
-rw-r--r--src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java16
-rw-r--r--src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java (renamed from src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java)41
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ParsedJar.java95
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java2
-rw-r--r--src/main/java/cuchaz/enigma/analysis/SourceIndex.java2
-rw-r--r--src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java44
-rw-r--r--src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java (renamed from src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java)154
-rw-r--r--src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java14
-rw-r--r--src/main/java/cuchaz/enigma/analysis/TranslationIndex.java122
22 files changed, 690 insertions, 896 deletions
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 @@
11 11
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import javassist.CtBehavior; 14import cuchaz.enigma.bytecode.AccessFlags;
15import javassist.CtField;
16 15
17import java.lang.reflect.Modifier; 16import java.lang.reflect.Modifier;
18 17
@@ -20,12 +19,8 @@ public enum Access {
20 19
21 PUBLIC, PROTECTED, PACKAGE, PRIVATE; 20 PUBLIC, PROTECTED, PACKAGE, PRIVATE;
22 21
23 public static Access get(CtBehavior behavior) { 22 public static Access get(AccessFlags flags) {
24 return get(behavior.getModifiers()); 23 return get(flags.getFlags());
25 }
26
27 public static Access get(CtField field) {
28 return get(field.getModifiers());
29 } 24 }
30 25
31 public static Access get(int modifiers) { 26 public static Access get(int modifiers) {
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import cuchaz.enigma.mapping.EntryFactory;
15import cuchaz.enigma.mapping.MethodEntry;
16import javassist.CtClass;
17import javassist.CtMethod;
18import javassist.bytecode.AccessFlag;
19
20public class BridgeMarker {
21
22 public static void markBridges(JarIndex jarIndex, CtClass c) {
23
24 for (CtMethod method : c.getDeclaredMethods()) {
25 MethodEntry methodEntry = EntryFactory.getMethodEntry(method);
26
27 // is this a bridge method?
28 MethodEntry bridgedMethodEntry = jarIndex.getBridgedMethod(methodEntry);
29 if (bridgedMethodEntry != null) {
30
31 // it's a bridge method! add the bridge flag
32 int flags = method.getMethodInfo().getAccessFlags();
33 flags |= AccessFlag.BRIDGE;
34 method.getMethodInfo().setAccessFlags(flags);
35 }
36 }
37 }
38}
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 @@
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import com.google.common.collect.Lists; 14import com.google.common.collect.Lists;
15import cuchaz.enigma.mapping.ClassEntry; 15import cuchaz.enigma.mapping.entry.ClassEntry;
16import cuchaz.enigma.mapping.MethodEntry; 16import cuchaz.enigma.mapping.entry.MethodEntry;
17import cuchaz.enigma.mapping.Translator; 17import cuchaz.enigma.mapping.Translator;
18 18
19import javax.swing.tree.DefaultMutableTreeNode; 19import javax.swing.tree.DefaultMutableTreeNode;
@@ -21,8 +21,8 @@ import java.util.List;
21 21
22public class ClassImplementationsTreeNode extends DefaultMutableTreeNode { 22public class ClassImplementationsTreeNode extends DefaultMutableTreeNode {
23 23
24 private Translator deobfuscatingTranslator; 24 private final Translator deobfuscatingTranslator;
25 private ClassEntry entry; 25 private final ClassEntry entry;
26 26
27 public ClassImplementationsTreeNode(Translator deobfuscatingTranslator, ClassEntry entry) { 27 public ClassImplementationsTreeNode(Translator deobfuscatingTranslator, ClassEntry entry) {
28 this.deobfuscatingTranslator = deobfuscatingTranslator; 28 this.deobfuscatingTranslator = deobfuscatingTranslator;
@@ -31,7 +31,7 @@ public class ClassImplementationsTreeNode extends DefaultMutableTreeNode {
31 31
32 public static ClassImplementationsTreeNode findNode(ClassImplementationsTreeNode node, MethodEntry entry) { 32 public static ClassImplementationsTreeNode findNode(ClassImplementationsTreeNode node, MethodEntry entry) {
33 // is this the node? 33 // is this the node?
34 if (node.entry.equals(entry.getClassEntry())) { 34 if (node.entry.equals(entry.getOwnerClassEntry())) {
35 return node; 35 return node;
36 } 36 }
37 37
@@ -50,7 +50,7 @@ public class ClassImplementationsTreeNode extends DefaultMutableTreeNode {
50 } 50 }
51 51
52 public String getDeobfClassName() { 52 public String getDeobfClassName() {
53 return this.deobfuscatingTranslator.translateClass(this.entry.getClassName()); 53 return this.deobfuscatingTranslator.getTranslatedClass(entry).getClassName();
54 } 54 }
55 55
56 @Override 56 @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 @@
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import com.google.common.collect.Lists; 14import com.google.common.collect.Lists;
15import cuchaz.enigma.mapping.ClassEntry; 15import cuchaz.enigma.mapping.entry.ClassEntry;
16import cuchaz.enigma.mapping.Translator; 16import cuchaz.enigma.mapping.Translator;
17 17
18import javax.swing.tree.DefaultMutableTreeNode; 18import javax.swing.tree.DefaultMutableTreeNode;
@@ -20,12 +20,12 @@ import java.util.List;
20 20
21public class ClassInheritanceTreeNode extends DefaultMutableTreeNode { 21public class ClassInheritanceTreeNode extends DefaultMutableTreeNode {
22 22
23 private Translator deobfuscatingTranslator; 23 private final Translator deobfuscatingTranslator;
24 private String obfClassName; 24 private final ClassEntry obfClassEntry;
25 25
26 public ClassInheritanceTreeNode(Translator deobfuscatingTranslator, String obfClassName) { 26 public ClassInheritanceTreeNode(Translator deobfuscatingTranslator, String obfClassName) {
27 this.deobfuscatingTranslator = deobfuscatingTranslator; 27 this.deobfuscatingTranslator = deobfuscatingTranslator;
28 this.obfClassName = obfClassName; 28 this.obfClassEntry = new ClassEntry(obfClassName);
29 } 29 }
30 30
31 public static ClassInheritanceTreeNode findNode(ClassInheritanceTreeNode node, ClassEntry entry) { 31 public static ClassInheritanceTreeNode findNode(ClassInheritanceTreeNode node, ClassEntry entry) {
@@ -45,11 +45,11 @@ public class ClassInheritanceTreeNode extends DefaultMutableTreeNode {
45 } 45 }
46 46
47 public String getObfClassName() { 47 public String getObfClassName() {
48 return this.obfClassName; 48 return this.obfClassEntry.getClassName();
49 } 49 }
50 50
51 public String getDeobfClassName() { 51 public String getDeobfClassName() {
52 return this.deobfuscatingTranslator.translateClass(this.obfClassName); 52 return this.deobfuscatingTranslator.getTranslatedClass(this.obfClassEntry).getClassName();
53 } 53 }
54 54
55 @Override 55 @Override
@@ -58,13 +58,13 @@ public class ClassInheritanceTreeNode extends DefaultMutableTreeNode {
58 if (deobfClassName != null) { 58 if (deobfClassName != null) {
59 return deobfClassName; 59 return deobfClassName;
60 } 60 }
61 return this.obfClassName; 61 return this.obfClassEntry.getName();
62 } 62 }
63 63
64 public void load(TranslationIndex ancestries, boolean recurse) { 64 public void load(TranslationIndex ancestries, boolean recurse) {
65 // get all the child nodes 65 // get all the child nodes
66 List<ClassInheritanceTreeNode> nodes = Lists.newArrayList(); 66 List<ClassInheritanceTreeNode> nodes = Lists.newArrayList();
67 for (ClassEntry subclassEntry : ancestries.getSubclass(new ClassEntry(this.obfClassName))) { 67 for (ClassEntry subclassEntry : ancestries.getSubclass(this.obfClassEntry)) {
68 nodes.add(new ClassInheritanceTreeNode(this.deobfuscatingTranslator, subclassEntry.getName())); 68 nodes.add(new ClassInheritanceTreeNode(this.deobfuscatingTranslator, subclassEntry.getName()));
69 } 69 }
70 70
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 @@
11 11
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import cuchaz.enigma.mapping.ClassEntry; 14import cuchaz.enigma.mapping.entry.ClassEntry;
15import cuchaz.enigma.mapping.ConstructorEntry; 15import cuchaz.enigma.mapping.entry.Entry;
16import cuchaz.enigma.mapping.Entry; 16import cuchaz.enigma.mapping.entry.MethodEntry;
17import cuchaz.enigma.utils.Utils; 17import cuchaz.enigma.utils.Utils;
18 18
19import java.util.Arrays; 19import java.util.Arrays;
@@ -21,7 +21,7 @@ import java.util.List;
21 21
22public class EntryReference<E extends Entry, C extends Entry> { 22public class EntryReference<E extends Entry, C extends Entry> {
23 23
24 private static final List<String> ConstructorNonNames = Arrays.asList("this", "super", "static"); 24 private static final List<String> CONSTRUCTOR_NON_NAMES = Arrays.asList("this", "super", "static");
25 public E entry; 25 public E entry;
26 public C context; 26 public C context;
27 27
@@ -40,7 +40,7 @@ public class EntryReference<E extends Entry, C extends Entry> {
40 this.context = context; 40 this.context = context;
41 41
42 this.sourceName = sourceName != null && !sourceName.isEmpty(); 42 this.sourceName = sourceName != null && !sourceName.isEmpty();
43 if (entry instanceof ConstructorEntry && ConstructorNonNames.contains(sourceName)) { 43 if (entry instanceof MethodEntry && ((MethodEntry) entry).isConstructor() && CONSTRUCTOR_NON_NAMES.contains(sourceName)) {
44 this.sourceName = false; 44 this.sourceName = false;
45 } 45 }
46 } 46 }
@@ -53,9 +53,9 @@ public class EntryReference<E extends Entry, C extends Entry> {
53 53
54 public ClassEntry getLocationClassEntry() { 54 public ClassEntry getLocationClassEntry() {
55 if (context != null) { 55 if (context != null) {
56 return context.getClassEntry(); 56 return context.getOwnerClassEntry();
57 } 57 }
58 return entry.getClassEntry(); 58 return entry.getOwnerClassEntry();
59 } 59 }
60 60
61 public boolean isNamed() { 61 public boolean isNamed() {
@@ -63,9 +63,9 @@ public class EntryReference<E extends Entry, C extends Entry> {
63 } 63 }
64 64
65 public Entry getNameableEntry() { 65 public Entry getNameableEntry() {
66 if (entry instanceof ConstructorEntry) { 66 if (entry instanceof MethodEntry && ((MethodEntry) entry).isConstructor()) {
67 // renaming a constructor really means renaming the class 67 // renaming a constructor really means renaming the class
68 return entry.getClassEntry(); 68 return entry.getOwnerClassEntry();
69 } 69 }
70 return entry; 70 return entry;
71 } 71 }
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;
15import com.google.common.collect.Multimap; 15import com.google.common.collect.Multimap;
16import com.google.common.collect.Sets; 16import com.google.common.collect.Sets;
17import cuchaz.enigma.mapping.*; 17import cuchaz.enigma.mapping.*;
18import cuchaz.enigma.mapping.entry.*;
18 19
19import java.util.AbstractMap; 20import java.util.AbstractMap;
20import java.util.List; 21import java.util.List;
@@ -87,18 +88,18 @@ public class EntryRenamer {
87 MethodEntry newMethodEntry = renames.get(methodEntry); 88 MethodEntry newMethodEntry = renames.get(methodEntry);
88 if (newMethodEntry != null) { 89 if (newMethodEntry != null) {
89 return (T) new MethodEntry( 90 return (T) new MethodEntry(
90 methodEntry.getClassEntry(), 91 methodEntry.getOwnerClassEntry(),
91 newMethodEntry.getName(), 92 newMethodEntry.getName(),
92 methodEntry.getSignature() 93 methodEntry.getDesc()
93 ); 94 );
94 } 95 }
95 return thing; 96 return thing;
96 } else if (thing instanceof ArgumentEntry) { 97 } else if (thing instanceof LocalVariableEntry) {
97 ArgumentEntry argumentEntry = (ArgumentEntry) thing; 98 LocalVariableEntry variableEntry = (LocalVariableEntry) thing;
98 return (T) new ArgumentEntry( 99 return (T) new LocalVariableEntry(
99 renameMethodsInThing(renames, argumentEntry.getBehaviorEntry()), 100 renameMethodsInThing(renames, variableEntry.getOwnerEntry()),
100 argumentEntry.getIndex(), 101 variableEntry.getIndex(),
101 argumentEntry.getName() 102 variableEntry.getName()
102 ); 103 );
103 } else if (thing instanceof EntryReference) { 104 } else if (thing instanceof EntryReference) {
104 EntryReference<Entry, Entry> reference = (EntryReference<Entry, Entry>) thing; 105 EntryReference<Entry, Entry> reference = (EntryReference<Entry, Entry>) thing;
@@ -119,27 +120,45 @@ public class EntryRenamer {
119 } else if (thing instanceof ClassEntry) { 120 } else if (thing instanceof ClassEntry) {
120 ClassEntry classEntry = (ClassEntry) thing; 121 ClassEntry classEntry = (ClassEntry) thing;
121 return (T) new ClassEntry(renameClassesInThing(renames, classEntry.getClassName())); 122 return (T) new ClassEntry(renameClassesInThing(renames, classEntry.getClassName()));
122 } else if (thing instanceof FieldEntry) { 123 } else if (thing instanceof FieldDefEntry) {
123 FieldEntry fieldEntry = (FieldEntry) thing; 124 FieldDefEntry fieldEntry = (FieldDefEntry) thing;
124 return (T) new FieldEntry(renameClassesInThing(renames, fieldEntry.getClassEntry()), fieldEntry.getName(), renameClassesInThing(renames, fieldEntry.getType())); 125 return (T) new FieldDefEntry(
125 } else if (thing instanceof ConstructorEntry) { 126 renameClassesInThing(renames, fieldEntry.getOwnerClassEntry()),
126 ConstructorEntry constructorEntry = (ConstructorEntry) thing; 127 fieldEntry.getName(),
127 return (T) new ConstructorEntry(renameClassesInThing(renames, constructorEntry.getClassEntry()), renameClassesInThing(renames, constructorEntry.getSignature())); 128 renameClassesInThing(renames, fieldEntry.getDesc()),
129 renameClassesInThing(renames, fieldEntry.getSignature()),
130 fieldEntry.getAccess()
131 );
132 } else if (thing instanceof MethodDefEntry) {
133 MethodDefEntry methodEntry = (MethodDefEntry) thing;
134 return (T) new MethodDefEntry(
135 renameClassesInThing(renames, methodEntry.getOwnerClassEntry()),
136 methodEntry.getName(),
137 renameClassesInThing(renames, methodEntry.getDesc()),
138 renameClassesInThing(renames, methodEntry.getSignature()),
139 methodEntry.getAccess()
140 );
128 } else if (thing instanceof MethodEntry) { 141 } else if (thing instanceof MethodEntry) {
129 MethodEntry methodEntry = (MethodEntry) thing; 142 MethodEntry methodEntry = (MethodEntry) thing;
130 return (T) new MethodEntry(renameClassesInThing(renames, methodEntry.getClassEntry()), methodEntry.getName(), renameClassesInThing(renames, methodEntry.getSignature())); 143 return (T) new MethodEntry(
131 } else if (thing instanceof ArgumentEntry) { 144 renameClassesInThing(renames, methodEntry.getOwnerClassEntry()),
132 ArgumentEntry argumentEntry = (ArgumentEntry) thing; 145 methodEntry.getName(),
133 return (T) new ArgumentEntry(renameClassesInThing(renames, argumentEntry.getBehaviorEntry()), argumentEntry.getIndex(), argumentEntry.getName()); 146 renameClassesInThing(renames, methodEntry.getDesc())
147 );
148 } else if (thing instanceof LocalVariableEntry) {
149 LocalVariableEntry argumentEntry = (LocalVariableEntry) thing;
150 return (T) new LocalVariableEntry(renameClassesInThing(renames, argumentEntry.getOwnerEntry()), argumentEntry.getIndex(), argumentEntry.getName());
134 } else if (thing instanceof EntryReference) { 151 } else if (thing instanceof EntryReference) {
135 EntryReference<Entry, Entry> reference = (EntryReference<Entry, Entry>) thing; 152 EntryReference<Entry, Entry> reference = (EntryReference<Entry, Entry>) thing;
136 reference.entry = renameClassesInThing(renames, reference.entry); 153 reference.entry = renameClassesInThing(renames, reference.entry);
137 reference.context = renameClassesInThing(renames, reference.context); 154 reference.context = renameClassesInThing(renames, reference.context);
138 return thing; 155 return thing;
156 } else if (thing instanceof MethodDescriptor) {
157 return (T) ((MethodDescriptor) thing).remap(className -> renameClassesInThing(renames, className));
158 } else if (thing instanceof TypeDescriptor) {
159 return (T) ((TypeDescriptor) thing).remap(className -> renameClassesInThing(renames, className));
139 } else if (thing instanceof Signature) { 160 } else if (thing instanceof Signature) {
140 return (T) new Signature((Signature) thing, className -> renameClassesInThing(renames, className)); 161 return (T) ((Signature) thing).remap(className -> renameClassesInThing(renames, className));
141 } else if (thing instanceof Type) {
142 return (T) new Type((Type) thing, className -> renameClassesInThing(renames, className));
143 } 162 }
144 163
145 return thing; 164 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 @@
11 11
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import cuchaz.enigma.mapping.BehaviorEntry; 14import cuchaz.enigma.mapping.*;
15import cuchaz.enigma.mapping.FieldEntry; 15import cuchaz.enigma.mapping.entry.FieldEntry;
16import cuchaz.enigma.mapping.Translator; 16import cuchaz.enigma.mapping.entry.MethodDefEntry;
17import cuchaz.enigma.mapping.entry.MethodEntry;
17 18
18import javax.swing.tree.DefaultMutableTreeNode; 19import javax.swing.tree.DefaultMutableTreeNode;
19 20
20public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode<FieldEntry, BehaviorEntry> { 21public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode<FieldEntry, MethodDefEntry> {
21 22
22 private Translator deobfuscatingTranslator; 23 private Translator deobfuscatingTranslator;
23 private FieldEntry entry; 24 private FieldEntry entry;
24 private EntryReference<FieldEntry, BehaviorEntry> reference; 25 private EntryReference<FieldEntry, MethodDefEntry> reference;
25 private Access access; 26 private Access access;
26 27
27 public FieldReferenceTreeNode(Translator deobfuscatingTranslator, FieldEntry entry) { 28 public FieldReferenceTreeNode(Translator deobfuscatingTranslator, FieldEntry entry) {
@@ -30,7 +31,7 @@ public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements Re
30 this.reference = null; 31 this.reference = null;
31 } 32 }
32 33
33 private FieldReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference<FieldEntry, BehaviorEntry> reference, Access access) { 34 private FieldReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference<FieldEntry, MethodDefEntry> reference, Access access) {
34 this.deobfuscatingTranslator = deobfuscatingTranslator; 35 this.deobfuscatingTranslator = deobfuscatingTranslator;
35 this.entry = reference.entry; 36 this.entry = reference.entry;
36 this.reference = reference; 37 this.reference = reference;
@@ -43,34 +44,34 @@ public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements Re
43 } 44 }
44 45
45 @Override 46 @Override
46 public EntryReference<FieldEntry, BehaviorEntry> getReference() { 47 public EntryReference<FieldEntry, MethodDefEntry> getReference() {
47 return this.reference; 48 return this.reference;
48 } 49 }
49 50
50 @Override 51 @Override
51 public String toString() { 52 public String toString() {
52 if (this.reference != null) { 53 if (this.reference != null) {
53 return String.format("%s (%s)", this.deobfuscatingTranslator.translateEntry(this.reference.context), this.access); 54 return String.format("%s (%s)", this.deobfuscatingTranslator.getTranslatedMethodDef(this.reference.context), this.access);
54 } 55 }
55 return this.deobfuscatingTranslator.translateEntry(this.entry).toString(); 56 return deobfuscatingTranslator.getTranslatedField(entry).getName();
56 } 57 }
57 58
58 public void load(JarIndex index, boolean recurse) { 59 public void load(JarIndex index, boolean recurse) {
59 // get all the child nodes 60 // get all the child nodes
60 if (this.reference == null) { 61 if (this.reference == null) {
61 for (EntryReference<FieldEntry, BehaviorEntry> reference : index.getFieldReferences(this.entry)) { 62 for (EntryReference<FieldEntry, MethodDefEntry> reference : index.getFieldReferences(this.entry)) {
62 add(new FieldReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.entry))); 63 add(new FieldReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.entry)));
63 } 64 }
64 } else { 65 } else {
65 for (EntryReference<BehaviorEntry, BehaviorEntry> reference : index.getBehaviorReferences(this.reference.context)) { 66 for (EntryReference<MethodEntry, MethodDefEntry> reference : index.getMethodsReferencing(this.reference.context)) {
66 add(new BehaviorReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.reference.context))); 67 add(new MethodReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.reference.context)));
67 } 68 }
68 } 69 }
69 70
70 if (recurse && children != null) { 71 if (recurse && children != null) {
71 for (Object node : children) { 72 for (Object node : children) {
72 if (node instanceof BehaviorReferenceTreeNode) { 73 if (node instanceof MethodReferenceTreeNode) {
73 ((BehaviorReferenceTreeNode) node).load(index, true); 74 ((MethodReferenceTreeNode) node).load(index, true);
74 } else if (node instanceof FieldReferenceTreeNode) { 75 } else if (node instanceof FieldReferenceTreeNode) {
75 ((FieldReferenceTreeNode) node).load(index, true); 76 ((FieldReferenceTreeNode) node).load(index, true);
76 } 77 }
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 @@
1package cuchaz.enigma.analysis;
2
3import cuchaz.enigma.mapping.entry.ClassDefEntry;
4import org.objectweb.asm.ClassVisitor;
5import org.objectweb.asm.FieldVisitor;
6import org.objectweb.asm.MethodVisitor;
7
8public class IndexClassVisitor extends ClassVisitor {
9 private final JarIndex index;
10 private ClassDefEntry classEntry;
11
12 public IndexClassVisitor(JarIndex index, int api) {
13 super(api);
14 this.index = index;
15 }
16
17 @Override
18 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
19 this.classEntry = this.index.indexClass(access, name, signature, superName, interfaces);
20 super.visit(version, access, name, signature, superName, interfaces);
21 }
22
23 @Override
24 public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
25 if (this.classEntry != null) {
26 this.index.indexField(this.classEntry, access, name, desc, signature);
27 }
28 return super.visitField(access, name, desc, signature, value);
29 }
30
31 @Override
32 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
33 if (this.classEntry != null) {
34 this.index.indexMethod(this.classEntry, access, name, desc, signature);
35 }
36 return super.visitMethod(access, name, desc, signature, exceptions);
37 }
38}
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 @@
1package cuchaz.enigma.analysis;
2
3import cuchaz.enigma.mapping.entry.ClassEntry;
4import org.objectweb.asm.ClassVisitor;
5
6public class IndexInnerClassVisitor extends ClassVisitor {
7 private final JarIndex index;
8
9 public IndexInnerClassVisitor(JarIndex index, int api) {
10 super(api);
11 this.index = index;
12 }
13
14 @Override
15 public void visitInnerClass(String name, String outerName, String innerName, int access) {
16 ClassEntry entry = new ClassEntry(name);
17 // Ignore anonymous classes
18 if (innerName != null && outerName != null) {
19 ClassEntry outerEntry = new ClassEntry(outerName);
20 index.indexInnerClass(entry, outerEntry);
21 }
22 }
23}
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 @@
1package cuchaz.enigma.analysis;
2
3import cuchaz.enigma.bytecode.AccessFlags;
4import cuchaz.enigma.mapping.MethodDescriptor;
5import cuchaz.enigma.mapping.Signature;
6import cuchaz.enigma.mapping.entry.ClassEntry;
7import cuchaz.enigma.mapping.entry.MethodDefEntry;
8import org.objectweb.asm.ClassVisitor;
9import org.objectweb.asm.Handle;
10import org.objectweb.asm.MethodVisitor;
11import org.objectweb.asm.Opcodes;
12
13public class IndexReferenceVisitor extends ClassVisitor {
14 private final JarIndex index;
15 private ClassEntry classEntry;
16
17 public IndexReferenceVisitor(JarIndex index, int api) {
18 super(api);
19 this.index = index;
20 }
21
22 @Override
23 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
24 this.classEntry = new ClassEntry(name);
25 }
26
27 @Override
28 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
29 MethodDefEntry entry = new MethodDefEntry(classEntry, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access));
30 return new Method(this.index, entry, this.api);
31 }
32
33 private class Method extends MethodVisitor {
34 private final JarIndex index;
35 private final MethodDefEntry callerEntry;
36
37 public Method(JarIndex index, MethodDefEntry callerEntry, int api) {
38 super(api);
39 this.index = index;
40 this.callerEntry = callerEntry;
41 }
42
43 @Override
44 public void visitFieldInsn(int opcode, String owner, String name, String desc) {
45 this.index.indexFieldAccess(callerEntry, owner, name, desc);
46 }
47
48 @Override
49 public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
50 this.index.indexMethodCall(callerEntry, owner, name, desc);
51 }
52
53 @Override
54 public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
55 for (Object bsmArg : bsmArgs){
56 if (bsmArg instanceof Handle){
57 Handle handle = (Handle)bsmArg;
58 switch (handle.getTag()){
59 case Opcodes.H_GETFIELD:
60 case Opcodes.H_GETSTATIC:
61 case Opcodes.H_PUTFIELD:
62 case Opcodes.H_PUTSTATIC:
63 this.index.indexFieldAccess(callerEntry, handle.getOwner(), handle.getName(), handle.getDesc());
64 break;
65 case Opcodes.H_INVOKEINTERFACE:
66 case Opcodes.H_INVOKESPECIAL:
67 case Opcodes.H_INVOKESTATIC:
68 case Opcodes.H_INVOKEVIRTUAL:
69 case Opcodes.H_NEWINVOKESPECIAL:
70 this.index.indexMethodCall(callerEntry, handle.getOwner(), handle.getName(), handle.getDesc());
71 break;
72 }
73 }
74 }
75 }
76 }
77}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import com.google.common.collect.Lists;
15import cuchaz.enigma.Constants;
16import cuchaz.enigma.mapping.ClassEntry;
17import javassist.ByteArrayClassPath;
18import javassist.ClassPool;
19import javassist.CtClass;
20import javassist.NotFoundException;
21import javassist.bytecode.Descriptor;
22
23import java.io.ByteArrayOutputStream;
24import java.io.IOException;
25import java.io.InputStream;
26import java.util.Enumeration;
27import java.util.Iterator;
28import java.util.List;
29import java.util.jar.JarEntry;
30import java.util.jar.JarFile;
31
32public class JarClassIterator implements Iterator<CtClass> {
33
34 private JarFile jar;
35 private Iterator<JarEntry> iter;
36
37 public JarClassIterator(JarFile jar) {
38 this.jar = jar;
39
40 // get the jar entries that correspond to classes
41 List<JarEntry> classEntries = Lists.newArrayList();
42 Enumeration<JarEntry> entries = this.jar.entries();
43 while (entries.hasMoreElements()) {
44 JarEntry entry = entries.nextElement();
45
46 // is this a class file?
47 if (entry.getName().endsWith(".class")) {
48 classEntries.add(entry);
49 }
50 }
51 this.iter = classEntries.iterator();
52 }
53
54 public static List<ClassEntry> getClassEntries(JarFile jar) {
55 List<ClassEntry> classEntries = Lists.newArrayList();
56 Enumeration<JarEntry> entries = jar.entries();
57 while (entries.hasMoreElements()) {
58 JarEntry entry = entries.nextElement();
59
60 // is this a class file?
61 if (!entry.isDirectory() && entry.getName().endsWith(".class")) {
62 classEntries.add(getClassEntry(entry));
63 }
64 }
65 return classEntries;
66 }
67
68 public static Iterable<CtClass> classes(final JarFile jar) {
69 return () -> new JarClassIterator(jar);
70 }
71
72 private static CtClass getClass(JarFile jar, JarEntry entry) throws IOException, NotFoundException {
73 // read the class into a buffer
74 ByteArrayOutputStream bos = new ByteArrayOutputStream();
75 byte[] buf = new byte[Constants.KiB];
76 int totalNumBytesRead = 0;
77 InputStream in = jar.getInputStream(entry);
78 while (in.available() > 0) {
79 int numBytesRead = in.read(buf);
80 if (numBytesRead < 0) {
81 break;
82 }
83 bos.write(buf, 0, numBytesRead);
84
85 // sanity checking
86 totalNumBytesRead += numBytesRead;
87 if (totalNumBytesRead > Constants.MiB) {
88 throw new Error("Class file " + entry.getName() + " larger than 1 MiB! Something is wrong!");
89 }
90 }
91
92 // get a javassist handle for the class
93 String className = Descriptor.toJavaName(getClassEntry(entry).getName());
94 ClassPool classPool = new ClassPool();
95 classPool.appendSystemPath();
96 classPool.insertClassPath(new ByteArrayClassPath(className, bos.toByteArray()));
97 return classPool.get(className);
98 }
99
100 private static ClassEntry getClassEntry(JarEntry entry) {
101 return new ClassEntry(entry.getName().substring(0, entry.getName().length() - ".class".length()));
102 }
103
104 @Override
105 public boolean hasNext() {
106 return this.iter.hasNext();
107 }
108
109 @Override
110 public CtClass next() {
111 JarEntry entry = this.iter.next();
112 try {
113 return getClass(this.jar, entry);
114 } catch (IOException | NotFoundException ex) {
115 throw new Error("Unable to load class: " + entry.getName());
116 }
117 }
118
119 @Override
120 public void remove() {
121 throw new UnsupportedOperationException();
122 }
123}
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 @@
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import com.google.common.collect.*; 14import com.google.common.collect.*;
15import cuchaz.enigma.bytecode.AccessFlags;
15import cuchaz.enigma.mapping.*; 16import cuchaz.enigma.mapping.*;
16import cuchaz.enigma.mapping.Translator; 17import cuchaz.enigma.mapping.entry.*;
17import javassist.*; 18import org.objectweb.asm.Opcodes;
18import javassist.bytecode.*;
19import javassist.expr.*;
20 19
21import java.lang.reflect.Modifier;
22import java.util.*; 20import java.util.*;
23import java.util.jar.JarFile;
24 21
25public class JarIndex { 22public class JarIndex {
26 23
24 private final ReferencedEntryPool entryPool;
25
27 private Set<ClassEntry> obfClassEntries; 26 private Set<ClassEntry> obfClassEntries;
28 private TranslationIndex translationIndex; 27 private TranslationIndex translationIndex;
29 private Map<Entry, Access> access; 28 private Map<Entry, Access> access;
30 private Multimap<ClassEntry, FieldEntry> fields; 29 private Multimap<ClassEntry, FieldDefEntry> fields;
31 private Multimap<ClassEntry, BehaviorEntry> behaviors; 30 private Multimap<ClassEntry, MethodDefEntry> methods;
32 private Multimap<String, MethodEntry> methodImplementations; 31 private Multimap<String, MethodDefEntry> methodImplementations;
33 private Multimap<BehaviorEntry, EntryReference<BehaviorEntry, BehaviorEntry>> behaviorReferences; 32 private Multimap<MethodEntry, EntryReference<MethodEntry, MethodDefEntry>> methodsReferencing;
34 private Multimap<FieldEntry, EntryReference<FieldEntry, BehaviorEntry>> fieldReferences; 33 private Multimap<MethodEntry, MethodEntry> methodReferences;
34 private Multimap<FieldEntry, EntryReference<FieldEntry, MethodDefEntry>> fieldReferences;
35 private Multimap<ClassEntry, ClassEntry> innerClassesByOuter; 35 private Multimap<ClassEntry, ClassEntry> innerClassesByOuter;
36 private Map<ClassEntry, ClassEntry> outerClassesByInner; 36 private Map<ClassEntry, ClassEntry> outerClassesByInner;
37 private Map<ClassEntry, BehaviorEntry> anonymousClasses;
38 private Map<MethodEntry, MethodEntry> bridgedMethods; 37 private Map<MethodEntry, MethodEntry> bridgedMethods;
39 private Set<MethodEntry> syntheticMethods; 38 private Set<MethodEntry> syntheticMethods;
40 39
41 public JarIndex() { 40 public JarIndex(ReferencedEntryPool entryPool) {
41 this.entryPool = entryPool;
42 this.obfClassEntries = Sets.newHashSet(); 42 this.obfClassEntries = Sets.newHashSet();
43 this.translationIndex = new TranslationIndex(); 43 this.translationIndex = new TranslationIndex(entryPool);
44 this.access = Maps.newHashMap(); 44 this.access = Maps.newHashMap();
45 this.fields = HashMultimap.create(); 45 this.fields = HashMultimap.create();
46 this.behaviors = HashMultimap.create(); 46 this.methods = HashMultimap.create();
47 this.methodImplementations = HashMultimap.create(); 47 this.methodImplementations = HashMultimap.create();
48 this.behaviorReferences = HashMultimap.create(); 48 this.methodsReferencing = HashMultimap.create();
49 this.methodReferences = HashMultimap.create();
49 this.fieldReferences = HashMultimap.create(); 50 this.fieldReferences = HashMultimap.create();
50 this.innerClassesByOuter = HashMultimap.create(); 51 this.innerClassesByOuter = HashMultimap.create();
51 this.outerClassesByInner = Maps.newHashMap(); 52 this.outerClassesByInner = Maps.newHashMap();
52 this.anonymousClasses = Maps.newHashMap();
53 this.bridgedMethods = Maps.newHashMap(); 53 this.bridgedMethods = Maps.newHashMap();
54 this.syntheticMethods = Sets.newHashSet(); 54 this.syntheticMethods = Sets.newHashSet();
55 } 55 }
56 56
57 public void indexJar(JarFile jar, boolean buildInnerClasses) { 57 public void indexJar(ParsedJar jar, boolean buildInnerClasses) {
58 58
59 // step 1: read the class names 59 // step 1: read the class names
60 this.obfClassEntries.addAll(JarClassIterator.getClassEntries(jar)); 60 obfClassEntries.addAll(jar.getClassEntries());
61
62 // step 2: index field/method/constructor access
63 for (CtClass c : JarClassIterator.classes(jar)) {
64 for (CtField field : c.getDeclaredFields()) {
65 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
66 this.access.put(fieldEntry, Access.get(field));
67 this.fields.put(fieldEntry.getClassEntry(), fieldEntry);
68 }
69 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
70 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
71 this.access.put(behaviorEntry, Access.get(behavior));
72 this.behaviors.put(behaviorEntry.getClassEntry(), behaviorEntry);
73 }
74 }
75 61
76 // step 3: index extends, implements, fields, and methods 62 // step 2: index classes, fields, methods, interfaces
77 for (CtClass c : JarClassIterator.classes(jar)) { 63 jar.visit(node -> node.accept(new IndexClassVisitor(this, Opcodes.ASM5)));
78 this.translationIndex.indexClass(c);
79 String className = Descriptor.toJvmName(c.getName());
80 for (String interfaceName : c.getClassFile().getInterfaces()) {
81 className = Descriptor.toJvmName(className);
82 interfaceName = Descriptor.toJvmName(interfaceName);
83 if (className.equals(interfaceName)) {
84 throw new IllegalArgumentException("Class cannot be its own interface! " + className);
85 }
86 }
87 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
88 indexBehavior(behavior);
89 }
90 }
91 64
92 // step 4: index field, method, constructor references 65 // step 3: index field, method, constructor references
93 for (CtClass c : JarClassIterator.classes(jar)) { 66 jar.visit(node -> node.accept(new IndexReferenceVisitor(this, Opcodes.ASM5)));
94 for (CtBehavior behavior : c.getDeclaredBehaviors()) { 67
95 indexBehaviorReferences(behavior); 68 // step 4: index access and bridged methods
69 for (MethodDefEntry methodEntry : methods.values()) {
70 // look for access and bridged methods
71 MethodEntry accessedMethod = findAccessMethod(methodEntry);
72 if (accessedMethod != null) {
73 if (isBridgedMethod(accessedMethod, methodEntry)) {
74 this.bridgedMethods.put(methodEntry, accessedMethod);
75 }
96 } 76 }
97 } 77 }
98 78
99 if (buildInnerClasses) { 79 if (buildInnerClasses) {
100
101 // step 5: index inner classes and anonymous classes 80 // step 5: index inner classes and anonymous classes
102 for (CtClass c : JarClassIterator.classes(jar)) { 81 jar.visit(node -> node.accept(new IndexInnerClassVisitor(this, Opcodes.ASM5)));
103 ClassEntry innerClassEntry = EntryFactory.getClassEntry(c);
104 ClassEntry outerClassEntry = findOuterClass(c);
105 if (outerClassEntry != null) {
106 this.innerClassesByOuter.put(outerClassEntry, innerClassEntry);
107 boolean innerWasAdded = this.outerClassesByInner.put(innerClassEntry, outerClassEntry) == null;
108 assert (innerWasAdded);
109
110 BehaviorEntry enclosingBehavior = isAnonymousClass(c, outerClassEntry);
111 if (enclosingBehavior != null) {
112 this.anonymousClasses.put(innerClassEntry, enclosingBehavior);
113
114 // DEBUG
115 //System.out.println("ANONYMOUS: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName());
116 }/* else {
117 // DEBUG
118 //System.out.println("INNER: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName());
119 }*/
120 }
121 }
122 82
123 // step 6: update other indices with inner class info 83 // step 6: update other indices with inner class info
124 Map<String, String> renames = Maps.newHashMap(); 84 Map<String, String> renames = Maps.newHashMap();
@@ -133,385 +93,138 @@ public class JarIndex {
133 EntryRenamer.renameClassesInSet(renames, this.obfClassEntries); 93 EntryRenamer.renameClassesInSet(renames, this.obfClassEntries);
134 this.translationIndex.renameClasses(renames); 94 this.translationIndex.renameClasses(renames);
135 EntryRenamer.renameClassesInMultimap(renames, this.methodImplementations); 95 EntryRenamer.renameClassesInMultimap(renames, this.methodImplementations);
136 EntryRenamer.renameClassesInMultimap(renames, this.behaviorReferences); 96 EntryRenamer.renameClassesInMultimap(renames, this.methodsReferencing);
97 EntryRenamer.renameClassesInMultimap(renames, this.methodReferences);
137 EntryRenamer.renameClassesInMultimap(renames, this.fieldReferences); 98 EntryRenamer.renameClassesInMultimap(renames, this.fieldReferences);
138 EntryRenamer.renameClassesInMap(renames, this.access); 99 EntryRenamer.renameClassesInMap(renames, this.access);
139 } 100 }
140 } 101 }
141 102
142 private void indexBehavior(CtBehavior behavior) { 103 protected ClassDefEntry indexClass(int access, String name, String signature, String superName, String[] interfaces) {
143 // get the behavior entry 104 for (String interfaceName : interfaces) {
144 final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); 105 if (name.equals(interfaceName)) {
145 if (behaviorEntry instanceof MethodEntry) { 106 throw new IllegalArgumentException("Class cannot be its own interface! " + name);
146 MethodEntry methodEntry = (MethodEntry) behaviorEntry;
147
148 // is synthetic
149 if ((behavior.getModifiers() & AccessFlag.SYNTHETIC) != 0) {
150 syntheticMethods.add(methodEntry);
151 } 107 }
152
153 // index implementation
154 this.methodImplementations.put(behaviorEntry.getClassName(), methodEntry);
155
156 // look for bridge and bridged methods
157 CtMethod bridgedMethod = getBridgedMethod((CtMethod) behavior);
158 if (bridgedMethod != null) {
159 this.bridgedMethods.put(methodEntry, EntryFactory.getMethodEntry(bridgedMethod));
160 }
161 }
162 // looks like we don't care about constructors here
163 }
164
165 private void indexBehaviorReferences(CtBehavior behavior) {
166 // index method calls
167 final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
168 try {
169 behavior.instrument(new ExprEditor() {
170 @Override
171 public void edit(MethodCall call) {
172 MethodEntry calledMethodEntry = EntryFactory.getMethodEntry(call);
173 ClassEntry resolvedClassEntry = translationIndex.resolveEntryClass(calledMethodEntry);
174 if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledMethodEntry.getClassEntry())) {
175 calledMethodEntry = new MethodEntry(
176 resolvedClassEntry,
177 calledMethodEntry.getName(),
178 calledMethodEntry.getSignature()
179 );
180 }
181 EntryReference<BehaviorEntry, BehaviorEntry> reference = new EntryReference<>(
182 calledMethodEntry,
183 call.getMethodName(),
184 behaviorEntry
185 );
186 behaviorReferences.put(calledMethodEntry, reference);
187 }
188
189 @Override
190 public void edit(FieldAccess call) {
191 FieldEntry calledFieldEntry = EntryFactory.getFieldEntry(call);
192 ClassEntry resolvedClassEntry = translationIndex.resolveEntryClass(calledFieldEntry);
193 if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledFieldEntry.getClassEntry())) {
194 calledFieldEntry = new FieldEntry(calledFieldEntry, resolvedClassEntry);
195 }
196 EntryReference<FieldEntry, BehaviorEntry> reference = new EntryReference<>(
197 calledFieldEntry,
198 call.getFieldName(),
199 behaviorEntry
200 );
201 fieldReferences.put(calledFieldEntry, reference);
202 }
203
204 @Override
205 public void edit(ConstructorCall call) {
206 ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call);
207 EntryReference<BehaviorEntry, BehaviorEntry> reference = new EntryReference<>(
208 calledConstructorEntry,
209 call.getMethodName(),
210 behaviorEntry
211 );
212 behaviorReferences.put(calledConstructorEntry, reference);
213 }
214
215 @Override
216 public void edit(NewExpr call) {
217 ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call);
218 EntryReference<BehaviorEntry, BehaviorEntry> reference = new EntryReference<>(
219 calledConstructorEntry,
220 call.getClassName(),
221 behaviorEntry
222 );
223 behaviorReferences.put(calledConstructorEntry, reference);
224 }
225 });
226 } catch (CannotCompileException ex) {
227 throw new Error(ex);
228 } 108 }
109 return this.translationIndex.indexClass(access, name, signature, superName, interfaces);
229 } 110 }
230 111
231 private CtMethod getBridgedMethod(CtMethod method) { 112 protected void indexField(ClassDefEntry owner, int access, String name, String desc, String signature) {
232 113 FieldDefEntry fieldEntry = new FieldDefEntry(owner, name, new TypeDescriptor(desc), Signature.createTypedSignature(signature), new AccessFlags(access));
233 // bridge methods just call another method, cast it to the return type, and return the result 114 this.translationIndex.indexField(fieldEntry);
234 // let's see if we can detect this scenario 115 this.access.put(fieldEntry, Access.get(access));
235 116 this.fields.put(fieldEntry.getOwnerClassEntry(), fieldEntry);
236 // skip non-synthetic methods 117 }
237 if ((method.getModifiers() & AccessFlag.SYNTHETIC) == 0) {
238 return null;
239 }
240 118
241 // get all the called methods 119 protected void indexMethod(ClassDefEntry owner, int access, String name, String desc, String signature) {
242 final List<MethodCall> methodCalls = Lists.newArrayList(); 120 MethodDefEntry methodEntry = new MethodDefEntry(owner, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access));
243 try { 121 this.translationIndex.indexMethod(methodEntry);
244 method.instrument(new ExprEditor() { 122 this.access.put(methodEntry, Access.get(access));
245 @Override 123 this.methods.put(methodEntry.getOwnerClassEntry(), methodEntry);
246 public void edit(MethodCall call) {
247 methodCalls.add(call);
248 }
249 });
250 } catch (CannotCompileException ex) {
251 // this is stupid... we're not even compiling anything
252 throw new Error(ex);
253 }
254 124
255 // is there just one? 125 if (new AccessFlags(access).isSynthetic()) {
256 if (methodCalls.size() != 1) { 126 syntheticMethods.add(methodEntry);
257 return null;
258 } 127 }
259 MethodCall call = methodCalls.get(0);
260 128
261 try { 129 // we don't care about constructors here
262 // we have a bridge method! 130 if (!methodEntry.isConstructor()) {
263 return call.getMethod(); 131 // index implementation
264 } catch (NotFoundException ex) { 132 this.methodImplementations.put(methodEntry.getClassName(), methodEntry);
265 // can't find the type? not a bridge method
266 return null;
267 } 133 }
268 } 134 }
269 135
270 private ClassEntry findOuterClass(CtClass c) { 136 protected void indexMethodCall(MethodDefEntry callerEntry, String owner, String name, String desc) {
271 137 MethodEntry referencedMethod = new MethodEntry(entryPool.getClass(owner), name, new MethodDescriptor(desc));
272 ClassEntry classEntry = EntryFactory.getClassEntry(c); 138 ClassEntry resolvedClassEntry = translationIndex.resolveEntryOwner(referencedMethod);
273 139 if (resolvedClassEntry != null && !resolvedClassEntry.equals(referencedMethod.getOwnerClassEntry())) {
274 // does this class already have an outer class? 140 referencedMethod = referencedMethod.updateOwnership(resolvedClassEntry);
275 if (classEntry.isInnerClass()) {
276 return classEntry.getOuterClassEntry();
277 } 141 }
278 142 methodsReferencing.put(referencedMethod, new EntryReference<>(referencedMethod, referencedMethod.getName(), callerEntry));
279 // inner classes: 143 methodReferences.put(callerEntry, referencedMethod);
280 // have constructors that can (illegally) set synthetic fields
281 // the outer class is the only class that calls constructors
282
283 // use the synthetic fields to find the synthetic constructors
284 for (CtConstructor constructor : c.getDeclaredConstructors()) {
285 Set<String> syntheticFieldTypes = Sets.newHashSet();
286 if (!isIllegalConstructor(syntheticFieldTypes, constructor)) {
287 continue;
288 }
289
290 ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor);
291
292 // gather the classes from the illegally-set synthetic fields
293 Set<ClassEntry> illegallySetClasses = Sets.newHashSet();
294 for (String type : syntheticFieldTypes) {
295 if (type.startsWith("L")) {
296 ClassEntry outerClassEntry = new ClassEntry(type.substring(1, type.length() - 1));
297 if (isSaneOuterClass(outerClassEntry, classEntry)) {
298 illegallySetClasses.add(outerClassEntry);
299 }
300 }
301 }
302
303 // who calls this constructor?
304 Set<ClassEntry> callerClasses = Sets.newHashSet();
305 for (EntryReference<BehaviorEntry, BehaviorEntry> reference : getBehaviorReferences(constructorEntry)) {
306
307 // make sure it's not a call to super
308 if (reference.entry instanceof ConstructorEntry && reference.context instanceof ConstructorEntry) {
309
310 // is the entry a superclass of the context?
311 ClassEntry calledClassEntry = reference.entry.getClassEntry();
312 ClassEntry superclassEntry = this.translationIndex.getSuperclass(reference.context.getClassEntry());
313 if (superclassEntry != null && superclassEntry.equals(calledClassEntry)) {
314 // it's a super call, skip
315 continue;
316 }
317 }
318
319 if (isSaneOuterClass(reference.context.getClassEntry(), classEntry)) {
320 callerClasses.add(reference.context.getClassEntry());
321 }
322 }
323
324 // do we have an answer yet?
325 if (callerClasses.isEmpty()) {
326 if (illegallySetClasses.size() == 1) {
327 return illegallySetClasses.iterator().next();
328 } else {
329 System.out.println(String.format("WARNING: Unable to find outer class for %s. No caller and no illegally set field classes.", classEntry));
330 }
331 } else {
332 if (callerClasses.size() == 1) {
333 return callerClasses.iterator().next();
334 } else {
335 // multiple callers, do the illegally set classes narrow it down?
336 Set<ClassEntry> intersection = Sets.newHashSet(callerClasses);
337 intersection.retainAll(illegallySetClasses);
338 if (intersection.size() == 1) {
339 return intersection.iterator().next();
340 } else {
341 System.out.println(String.format("WARNING: Unable to choose outer class for %s among options: %s", classEntry, callerClasses));
342 }
343 }
344 }
345 }
346
347 return null;
348 } 144 }
349 145
350 private boolean isSaneOuterClass(ClassEntry outerClassEntry, ClassEntry innerClassEntry) { 146 protected void indexFieldAccess(MethodDefEntry callerEntry, String owner, String name, String desc) {
351 147 FieldEntry referencedField = new FieldEntry(entryPool.getClass(owner), name, new TypeDescriptor(desc));
352 // clearly this would be silly 148 ClassEntry resolvedClassEntry = translationIndex.resolveEntryOwner(referencedField);
353 if (outerClassEntry.equals(innerClassEntry)) { 149 if (resolvedClassEntry != null && !resolvedClassEntry.equals(referencedField.getOwnerClassEntry())) {
354 return false; 150 referencedField = referencedField.updateOwnership(resolvedClassEntry);
355 } 151 }
356 152 fieldReferences.put(referencedField, new EntryReference<>(referencedField, referencedField.getName(), callerEntry));
357 // is the outer class in the jar?
358 return this.obfClassEntries.contains(outerClassEntry);
359
360 } 153 }
361 154
362 @SuppressWarnings("unchecked") 155 public void indexInnerClass(ClassEntry innerEntry, ClassEntry outerEntry) {
363 private boolean isIllegalConstructor(Set<String> syntheticFieldTypes, CtConstructor constructor) { 156 this.innerClassesByOuter.put(outerEntry, innerEntry);
157 this.outerClassesByInner.putIfAbsent(innerEntry, outerEntry);
158 }
364 159
365 // illegal constructors only set synthetic member fields, then call super() 160 private MethodEntry findAccessMethod(MethodDefEntry method) {
366 String className = constructor.getDeclaringClass().getName();
367 161
368 // collect all the field accesses, constructor calls, and method calls 162 // we want to find all compiler-added methods that directly call another with no processing
369 final List<FieldAccess> illegalFieldWrites = Lists.newArrayList();
370 final List<ConstructorCall> constructorCalls = Lists.newArrayList();
371 try {
372 constructor.instrument(new ExprEditor() {
373 @Override
374 public void edit(FieldAccess fieldAccess) {
375 if (fieldAccess.isWriter() && constructorCalls.isEmpty()) {
376 illegalFieldWrites.add(fieldAccess);
377 }
378 }
379 163
380 @Override 164 // skip non-synthetic methods
381 public void edit(ConstructorCall constructorCall) { 165 if (!method.getAccess().isSynthetic()) {
382 constructorCalls.add(constructorCall); 166 return null;
383 }
384 });
385 } catch (CannotCompileException ex) {
386 // we're not compiling anything... this is stupid
387 throw new Error(ex);
388 }
389
390 // are there any illegal field writes?
391 if (illegalFieldWrites.isEmpty()) {
392 return false;
393 } 167 }
394 168
395 // are all the writes to synthetic fields? 169 // get all the methods that we call
396 for (FieldAccess fieldWrite : illegalFieldWrites) { 170 final Collection<MethodEntry> referencedMethods = methodReferences.get(method);
397
398 // all illegal writes have to be to the local class
399 if (!fieldWrite.getClassName().equals(className)) {
400 System.err.println(String.format("WARNING: illegal write to non-member field %s.%s", fieldWrite.getClassName(), fieldWrite.getFieldName()));
401 return false;
402 }
403
404 // find the field
405 FieldInfo fieldInfo = null;
406 for (FieldInfo info : (List<FieldInfo>) constructor.getDeclaringClass().getClassFile().getFields()) {
407 if (info.getName().equals(fieldWrite.getFieldName()) && info.getDescriptor().equals(fieldWrite.getSignature())) {
408 fieldInfo = info;
409 break;
410 }
411 }
412 if (fieldInfo == null) {
413 // field is in a superclass or something, can't be a local synthetic member
414 return false;
415 }
416 171
417 // is this field synthetic? 172 // is there just one?
418 boolean isSynthetic = (fieldInfo.getAccessFlags() & AccessFlag.SYNTHETIC) != 0; 173 if (referencedMethods.size() != 1) {
419 if (isSynthetic) { 174 return null;
420 syntheticFieldTypes.add(fieldInfo.getDescriptor());
421 } else {
422 System.err.println(String.format("WARNING: illegal write to non synthetic field %s %s.%s", fieldInfo.getDescriptor(), className, fieldInfo.getName()));
423 return false;
424 }
425 } 175 }
426 176
427 // we passed all the tests! 177 return referencedMethods.stream().findFirst().orElse(null);
428 return true;
429 } 178 }
430 179
431 private BehaviorEntry isAnonymousClass(CtClass c, ClassEntry outerClassEntry) { 180 private boolean isBridgedMethod(MethodEntry called, MethodEntry access) {
432 181 // Bridged methods will always have the same name as the method they are calling
433 // is this class already marked anonymous? 182 // They will also have the same amount of parameters (though equal descriptors cannot be guaranteed)
434 EnclosingMethodAttribute enclosingMethodAttribute = (EnclosingMethodAttribute) c.getClassFile().getAttribute(EnclosingMethodAttribute.tag); 183 if (!called.getName().equals(access.getName()) || called.getDesc().getArgumentDescs().size() != access.getDesc().getArgumentDescs().size()) {
435 if (enclosingMethodAttribute != null) { 184 return false;
436 if (enclosingMethodAttribute.methodIndex() > 0) {
437 return EntryFactory.getBehaviorEntry(
438 Descriptor.toJvmName(enclosingMethodAttribute.className()),
439 enclosingMethodAttribute.methodName(),
440 enclosingMethodAttribute.methodDescriptor()
441 );
442 } else {
443 // an attribute but no method? assume not anonymous
444 return null;
445 }
446 }
447
448 // if there's an inner class attribute, but not an enclosing method attribute, then it's not anonymous
449 InnerClassesAttribute innerClassesAttribute = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag);
450 if (innerClassesAttribute != null) {
451 return null;
452 } 185 }
453 186
454 ClassEntry innerClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); 187 TypeDescriptor accessReturn = access.getDesc().getReturnDesc();
455 188 TypeDescriptor calledReturn = called.getDesc().getReturnDesc();
456 // anonymous classes: 189 if (calledReturn.isVoid() || calledReturn.isPrimitive() || accessReturn.isVoid() || accessReturn.isPrimitive()) {
457 // can't be abstract 190 return false;
458 // have only one constructor
459 // it's called exactly once by the outer class
460 // the type the instance is assigned to can't be this type
461
462 // is abstract?
463 if (Modifier.isAbstract(c.getModifiers())) {
464 return null;
465 } 191 }
466 192
467 // is there exactly one constructor? 193 // Bridged methods will never have the same type as what they are calling
468 if (c.getDeclaredConstructors().length != 1) { 194 if (accessReturn.equals(calledReturn)) {
469 return null; 195 return false;
470 } 196 }
471 CtConstructor constructor = c.getDeclaredConstructors()[0];
472 197
473 // is this constructor called exactly once? 198 String accessType = accessReturn.toString();
474 ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor);
475 Collection<EntryReference<BehaviorEntry, BehaviorEntry>> references = getBehaviorReferences(constructorEntry);
476 if (references.size() != 1) {
477 return null;
478 }
479 199
480 // does the caller use this type? 200 // If we're casting down from generic type to type-erased Object we're a bridge method
481 BehaviorEntry caller = references.iterator().next().context; 201 if (accessType.equals("Ljava/lang/Object;")) {
482 for (FieldEntry fieldEntry : getReferencedFields(caller)) { 202 return true;
483 if (fieldEntry.getType().hasClass() && fieldEntry.getType().getClassEntry().equals(innerClassEntry)) {
484 // caller references this type, so it can't be anonymous
485 return null;
486 }
487 }
488 for (BehaviorEntry behaviorEntry : getReferencedBehaviors(caller)) {
489 if (behaviorEntry.getSignature().hasClass(innerClassEntry)) {
490 return null;
491 }
492 } 203 }
493 204
494 return caller; 205 // Now we need to detect cases where we are being casted down to a higher type bound
206 List<ClassEntry> calledAncestry = translationIndex.getAncestry(calledReturn.getTypeEntry());
207 return calledAncestry.contains(accessReturn.getTypeEntry());
495 } 208 }
496 209
497 public Set<ClassEntry> getObfClassEntries() { 210 public Set<ClassEntry> getObfClassEntries() {
498 return this.obfClassEntries; 211 return this.obfClassEntries;
499 } 212 }
500 213
501 public Collection<FieldEntry> getObfFieldEntries() { 214 public Collection<FieldDefEntry> getObfFieldEntries() {
502 return this.fields.values(); 215 return this.fields.values();
503 } 216 }
504 217
505 public Collection<FieldEntry> getObfFieldEntries(ClassEntry classEntry) { 218 public Collection<FieldDefEntry> getObfFieldEntries(ClassEntry classEntry) {
506 return this.fields.get(classEntry); 219 return this.fields.get(classEntry);
507 } 220 }
508 221
509 public Collection<BehaviorEntry> getObfBehaviorEntries() { 222 public Collection<MethodDefEntry> getObfBehaviorEntries() {
510 return this.behaviors.values(); 223 return this.methods.values();
511 } 224 }
512 225
513 public Collection<BehaviorEntry> getObfBehaviorEntries(ClassEntry classEntry) { 226 public Collection<MethodDefEntry> getObfBehaviorEntries(ClassEntry classEntry) {
514 return this.behaviors.get(classEntry); 227 return this.methods.get(classEntry);
515 } 228 }
516 229
517 public TranslationIndex getTranslationIndex() { 230 public TranslationIndex getTranslationIndex() {
@@ -533,8 +246,8 @@ public class JarIndex {
533 } 246 }
534 } 247 }
535 ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode( 248 ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode(
536 deobfuscatingTranslator, 249 deobfuscatingTranslator,
537 ancestry.get(ancestry.size() - 1) 250 ancestry.get(ancestry.size() - 1)
538 ); 251 );
539 252
540 // expand all children recursively 253 // expand all children recursively
@@ -557,28 +270,20 @@ public class JarIndex {
557 public MethodInheritanceTreeNode getMethodInheritance(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) { 270 public MethodInheritanceTreeNode getMethodInheritance(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) {
558 271
559 // travel to the ancestor implementation 272 // travel to the ancestor implementation
560 ClassEntry baseImplementationClassEntry = obfMethodEntry.getClassEntry(); 273 ClassEntry baseImplementationClassEntry = obfMethodEntry.getOwnerClassEntry();
561 for (ClassEntry ancestorClassEntry : this.translationIndex.getAncestry(obfMethodEntry.getClassEntry())) { 274 for (ClassEntry ancestorClassEntry : this.translationIndex.getAncestry(obfMethodEntry.getOwnerClassEntry())) {
562 MethodEntry ancestorMethodEntry = new MethodEntry( 275 MethodEntry ancestorMethodEntry = entryPool.getMethod(ancestorClassEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString());
563 new ClassEntry(ancestorClassEntry), 276 if (ancestorMethodEntry != null && containsObfMethod(ancestorMethodEntry)) {
564 obfMethodEntry.getName(),
565 obfMethodEntry.getSignature()
566 );
567 if (containsObfBehavior(ancestorMethodEntry)) {
568 baseImplementationClassEntry = ancestorClassEntry; 277 baseImplementationClassEntry = ancestorClassEntry;
569 } 278 }
570 } 279 }
571 280
572 // make a root node at the base 281 // make a root node at the base
573 MethodEntry methodEntry = new MethodEntry( 282 MethodEntry methodEntry = entryPool.getMethod(baseImplementationClassEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString());
574 baseImplementationClassEntry,
575 obfMethodEntry.getName(),
576 obfMethodEntry.getSignature()
577 );
578 MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode( 283 MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode(
579 deobfuscatingTranslator, 284 deobfuscatingTranslator,
580 methodEntry, 285 methodEntry,
581 containsObfBehavior(methodEntry) 286 containsObfMethod(methodEntry)
582 ); 287 );
583 288
584 // expand the full tree 289 // expand the full tree
@@ -599,12 +304,8 @@ public class JarIndex {
599 for (ClassEntry interfaceEntry : getInterfaces(obfMethodEntry.getClassName())) { 304 for (ClassEntry interfaceEntry : getInterfaces(obfMethodEntry.getClassName())) {
600 305
601 // is this method defined in this interface? 306 // is this method defined in this interface?
602 MethodEntry methodInterface = new MethodEntry( 307 MethodEntry methodInterface = entryPool.getMethod(interfaceEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString());
603 interfaceEntry, 308 if (methodInterface != null && containsObfMethod(methodInterface)) {
604 obfMethodEntry.getName(),
605 obfMethodEntry.getSignature()
606 );
607 if (containsObfBehavior(methodInterface)) {
608 interfaceMethodEntries.add(methodInterface); 309 interfaceMethodEntries.add(methodInterface);
609 } 310 }
610 } 311 }
@@ -623,27 +324,30 @@ public class JarIndex {
623 324
624 public Set<MethodEntry> getRelatedMethodImplementations(MethodEntry obfMethodEntry) { 325 public Set<MethodEntry> getRelatedMethodImplementations(MethodEntry obfMethodEntry) {
625 Set<MethodEntry> methodEntries = Sets.newHashSet(); 326 Set<MethodEntry> methodEntries = Sets.newHashSet();
626 getRelatedMethodImplementations(methodEntries, getMethodInheritance(new Translator(), obfMethodEntry)); 327 getRelatedMethodImplementations(methodEntries, getMethodInheritance(new DirectionalTranslator(entryPool), obfMethodEntry));
627 return methodEntries; 328 return methodEntries;
628 } 329 }
629 330
630 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodInheritanceTreeNode node) { 331 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodInheritanceTreeNode node) {
631 MethodEntry methodEntry = node.getMethodEntry(); 332 MethodEntry methodEntry = node.getMethodEntry();
333 if (methodEntries.contains(methodEntry)) {
334 return;
335 }
632 336
633 if (containsObfBehavior(methodEntry)) { 337 if (containsObfMethod(methodEntry)) {
634 // collect the entry 338 // collect the entry
635 methodEntries.add(methodEntry); 339 methodEntries.add(methodEntry);
636 } 340 }
637 341
638 // look at bridged methods! 342 // look at bridge methods!
639 MethodEntry bridgedEntry = getBridgedMethod(methodEntry); 343 MethodEntry bridgedMethod = getBridgedMethod(methodEntry);
640 while (bridgedEntry != null) { 344 while (bridgedMethod != null) {
641 methodEntries.addAll(getRelatedMethodImplementations(bridgedEntry)); 345 methodEntries.addAll(getRelatedMethodImplementations(bridgedMethod));
642 bridgedEntry = getBridgedMethod(bridgedEntry); 346 bridgedMethod = getBridgedMethod(bridgedMethod);
643 } 347 }
644 348
645 // look at interface methods too 349 // look at interface methods too
646 for (MethodImplementationsTreeNode implementationsNode : getMethodImplementations(new Translator(), methodEntry)) { 350 for (MethodImplementationsTreeNode implementationsNode : getMethodImplementations(new DirectionalTranslator(entryPool), methodEntry)) {
647 getRelatedMethodImplementations(methodEntries, implementationsNode); 351 getRelatedMethodImplementations(methodEntries, implementationsNode);
648 } 352 }
649 353
@@ -655,16 +359,16 @@ public class JarIndex {
655 359
656 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodImplementationsTreeNode node) { 360 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodImplementationsTreeNode node) {
657 MethodEntry methodEntry = node.getMethodEntry(); 361 MethodEntry methodEntry = node.getMethodEntry();
658 if (containsObfBehavior(methodEntry)) { 362 if (containsObfMethod(methodEntry)) {
659 // collect the entry 363 // collect the entry
660 methodEntries.add(methodEntry); 364 methodEntries.add(methodEntry);
661 } 365 }
662 366
663 // look at bridged methods! 367 // look at bridge methods!
664 MethodEntry bridgedEntry = getBridgedMethod(methodEntry); 368 MethodEntry bridgedMethod = getBridgedMethod(methodEntry);
665 while (bridgedEntry != null) { 369 while (bridgedMethod != null) {
666 methodEntries.addAll(getRelatedMethodImplementations(bridgedEntry)); 370 methodEntries.addAll(getRelatedMethodImplementations(bridgedMethod));
667 bridgedEntry = getBridgedMethod(bridgedEntry); 371 bridgedMethod = getBridgedMethod(bridgedMethod);
668 } 372 }
669 373
670 // recurse 374 // recurse
@@ -673,34 +377,27 @@ public class JarIndex {
673 } 377 }
674 } 378 }
675 379
676 public Collection<EntryReference<FieldEntry, BehaviorEntry>> getFieldReferences(FieldEntry fieldEntry) { 380 public Collection<EntryReference<FieldEntry, MethodDefEntry>> getFieldReferences(FieldEntry fieldEntry) {
677 return this.fieldReferences.get(fieldEntry); 381 return this.fieldReferences.get(fieldEntry);
678 } 382 }
679 383
680 public Collection<FieldEntry> getReferencedFields(BehaviorEntry behaviorEntry) { 384 public Collection<FieldEntry> getReferencedFields(MethodDefEntry methodEntry) {
681 // linear search is fast enough for now 385 // linear search is fast enough for now
682 Set<FieldEntry> fieldEntries = Sets.newHashSet(); 386 Set<FieldEntry> fieldEntries = Sets.newHashSet();
683 for (EntryReference<FieldEntry, BehaviorEntry> reference : this.fieldReferences.values()) { 387 for (EntryReference<FieldEntry, MethodDefEntry> reference : this.fieldReferences.values()) {
684 if (reference.context == behaviorEntry) { 388 if (reference.context == methodEntry) {
685 fieldEntries.add(reference.entry); 389 fieldEntries.add(reference.entry);
686 } 390 }
687 } 391 }
688 return fieldEntries; 392 return fieldEntries;
689 } 393 }
690 394
691 public Collection<EntryReference<BehaviorEntry, BehaviorEntry>> getBehaviorReferences(BehaviorEntry behaviorEntry) { 395 public Collection<EntryReference<MethodEntry, MethodDefEntry>> getMethodsReferencing(MethodEntry methodEntry) {
692 return this.behaviorReferences.get(behaviorEntry); 396 return this.methodsReferencing.get(methodEntry);
693 } 397 }
694 398
695 public Collection<BehaviorEntry> getReferencedBehaviors(BehaviorEntry behaviorEntry) { 399 public Collection<MethodEntry> getReferencedMethods(MethodDefEntry methodEntry) {
696 // linear search is fast enough for now 400 return this.methodReferences.get(methodEntry);
697 Set<BehaviorEntry> behaviorEntries = Sets.newHashSet();
698 for (EntryReference<BehaviorEntry, BehaviorEntry> reference : this.behaviorReferences.values()) {
699 if (reference.context == behaviorEntry) {
700 behaviorEntries.add(reference.entry);
701 }
702 }
703 return behaviorEntries;
704 } 401 }
705 402
706 public Collection<ClassEntry> getInnerClasses(ClassEntry obfOuterClassEntry) { 403 public Collection<ClassEntry> getInnerClasses(ClassEntry obfOuterClassEntry) {
@@ -711,22 +408,13 @@ public class JarIndex {
711 return this.outerClassesByInner.get(obfInnerClassEntry); 408 return this.outerClassesByInner.get(obfInnerClassEntry);
712 } 409 }
713 410
714 public boolean isAnonymousClass(ClassEntry obfInnerClassEntry) {
715 return this.anonymousClasses.containsKey(obfInnerClassEntry);
716 }
717
718 public boolean isSyntheticMethod(MethodEntry methodEntry) { 411 public boolean isSyntheticMethod(MethodEntry methodEntry) {
719 return this.syntheticMethods.contains(methodEntry); 412 return this.syntheticMethods.contains(methodEntry);
720 } 413 }
721 414
722 public BehaviorEntry getAnonymousClassCaller(ClassEntry obfInnerClassName) {
723 return this.anonymousClasses.get(obfInnerClassName);
724 }
725
726 public Set<ClassEntry> getInterfaces(String className) { 415 public Set<ClassEntry> getInterfaces(String className) {
727 ClassEntry classEntry = new ClassEntry(className); 416 ClassEntry classEntry = entryPool.getClass(className);
728 Set<ClassEntry> interfaces = new HashSet<>(); 417 Set<ClassEntry> interfaces = new HashSet<>(this.translationIndex.getInterfaces(classEntry));
729 interfaces.addAll(this.translationIndex.getInterfaces(classEntry));
730 for (ClassEntry ancestor : this.translationIndex.getAncestry(classEntry)) { 418 for (ClassEntry ancestor : this.translationIndex.getAncestry(classEntry)) {
731 interfaces.addAll(this.translationIndex.getInterfaces(ancestor)); 419 interfaces.addAll(this.translationIndex.getInterfaces(ancestor));
732 } 420 }
@@ -754,7 +442,7 @@ public class JarIndex {
754 } 442 }
755 443
756 public boolean isInterface(String className) { 444 public boolean isInterface(String className) {
757 return this.translationIndex.isInterface(new ClassEntry(className)); 445 return this.translationIndex.isInterface(entryPool.getClass(className));
758 } 446 }
759 447
760 public boolean containsObfClass(ClassEntry obfClassEntry) { 448 public boolean containsObfClass(ClassEntry obfClassEntry) {
@@ -765,8 +453,8 @@ public class JarIndex {
765 return this.access.containsKey(obfFieldEntry); 453 return this.access.containsKey(obfFieldEntry);
766 } 454 }
767 455
768 public boolean containsObfBehavior(BehaviorEntry obfBehaviorEntry) { 456 public boolean containsObfMethod(MethodEntry obfMethodEntry) {
769 return this.access.containsKey(obfBehaviorEntry); 457 return this.access.containsKey(obfMethodEntry);
770 } 458 }
771 459
772 public boolean containsEntryWithSameName(Entry entry) { 460 public boolean containsEntryWithSameName(Entry entry) {
@@ -776,15 +464,13 @@ public class JarIndex {
776 return false; 464 return false;
777 } 465 }
778 466
779 public boolean containsObfArgument(ArgumentEntry obfArgumentEntry) { 467 public boolean containsObfVariable(LocalVariableEntry obfVariableEntry) {
780 // check the behavior 468 // check the behavior
781 if (!containsObfBehavior(obfArgumentEntry.getBehaviorEntry())) { 469 if (!containsObfMethod(obfVariableEntry.getOwnerEntry())) {
782 return false; 470 return false;
783 } 471 }
784 472
785 // check the argument 473 return true;
786 return obfArgumentEntry.getIndex() < obfArgumentEntry.getBehaviorEntry().getSignature().getArgumentTypes().size();
787
788 } 474 }
789 475
790 public boolean containsObfEntry(Entry obfEntry) { 476 public boolean containsObfEntry(Entry obfEntry) {
@@ -792,15 +478,12 @@ public class JarIndex {
792 return containsObfClass((ClassEntry) obfEntry); 478 return containsObfClass((ClassEntry) obfEntry);
793 } else if (obfEntry instanceof FieldEntry) { 479 } else if (obfEntry instanceof FieldEntry) {
794 return containsObfField((FieldEntry) obfEntry); 480 return containsObfField((FieldEntry) obfEntry);
795 } else if (obfEntry instanceof BehaviorEntry) { 481 } else if (obfEntry instanceof MethodEntry) {
796 return containsObfBehavior((BehaviorEntry) obfEntry); 482 return containsObfMethod((MethodEntry) obfEntry);
797 } else if (obfEntry instanceof ArgumentEntry) {
798 return containsObfArgument((ArgumentEntry) obfEntry);
799 } else if (obfEntry instanceof LocalVariableEntry) { 483 } else if (obfEntry instanceof LocalVariableEntry) {
800 // TODO: Implement it 484 return containsObfVariable((LocalVariableEntry) obfEntry);
801 return false;
802 } else { 485 } else {
803 throw new Error("Entry type not supported: " + obfEntry.getClass().getName()); 486 throw new Error("Entry desc not supported: " + obfEntry.getClass().getName());
804 } 487 }
805 } 488 }
806 489
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 @@
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import com.google.common.collect.Lists; 14import com.google.common.collect.Lists;
15import cuchaz.enigma.mapping.ClassEntry; 15import cuchaz.enigma.mapping.entry.ClassEntry;
16import cuchaz.enigma.mapping.MethodEntry; 16import cuchaz.enigma.mapping.entry.MethodEntry;
17import cuchaz.enigma.mapping.Translator; 17import cuchaz.enigma.mapping.Translator;
18 18
19import javax.swing.tree.DefaultMutableTreeNode; 19import javax.swing.tree.DefaultMutableTreeNode;
@@ -54,11 +54,11 @@ public class MethodImplementationsTreeNode extends DefaultMutableTreeNode {
54 } 54 }
55 55
56 public String getDeobfClassName() { 56 public String getDeobfClassName() {
57 return this.deobfuscatingTranslator.translateClass(this.entry.getClassName()); 57 return this.deobfuscatingTranslator.getTranslatedClass(this.entry.getOwnerClassEntry()).getClassName();
58 } 58 }
59 59
60 public String getDeobfMethodName() { 60 public String getDeobfMethodName() {
61 return this.deobfuscatingTranslator.translate(this.entry); 61 return this.deobfuscatingTranslator.getTranslatedMethod(this.entry).getName();
62 } 62 }
63 63
64 @Override 64 @Override
@@ -80,9 +80,8 @@ public class MethodImplementationsTreeNode extends DefaultMutableTreeNode {
80 // get all method implementations 80 // get all method implementations
81 List<MethodImplementationsTreeNode> nodes = Lists.newArrayList(); 81 List<MethodImplementationsTreeNode> nodes = Lists.newArrayList();
82 for (String implementingClassName : index.getImplementingClasses(this.entry.getClassName())) { 82 for (String implementingClassName : index.getImplementingClasses(this.entry.getClassName())) {
83 MethodEntry methodEntry = new MethodEntry(new ClassEntry(implementingClassName), this.entry.getName(), this.entry.getSignature() 83 MethodEntry methodEntry = new MethodEntry(new ClassEntry(implementingClassName), this.entry.getName(), this.entry.getDesc());
84 ); 84 if (index.containsObfMethod(methodEntry)) {
85 if (index.containsObfBehavior(methodEntry)) {
86 nodes.add(new MethodImplementationsTreeNode(this.deobfuscatingTranslator, methodEntry)); 85 nodes.add(new MethodImplementationsTreeNode(this.deobfuscatingTranslator, methodEntry));
87 } 86 }
88 } 87 }
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 @@
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import com.google.common.collect.Lists; 14import com.google.common.collect.Lists;
15import cuchaz.enigma.mapping.ClassEntry; 15import cuchaz.enigma.mapping.entry.ClassEntry;
16import cuchaz.enigma.mapping.MethodEntry; 16import cuchaz.enigma.mapping.entry.MethodEntry;
17import cuchaz.enigma.mapping.Translator; 17import cuchaz.enigma.mapping.Translator;
18 18
19import javax.swing.tree.DefaultMutableTreeNode; 19import javax.swing.tree.DefaultMutableTreeNode;
@@ -52,11 +52,11 @@ public class MethodInheritanceTreeNode extends DefaultMutableTreeNode {
52 } 52 }
53 53
54 public String getDeobfClassName() { 54 public String getDeobfClassName() {
55 return this.deobfuscatingTranslator.translateClass(this.entry.getClassName()); 55 return this.deobfuscatingTranslator.getTranslatedClass(this.entry.getOwnerClassEntry()).getName();
56 } 56 }
57 57
58 public String getDeobfMethodName() { 58 public String getDeobfMethodName() {
59 return this.deobfuscatingTranslator.translate(this.entry); 59 return this.deobfuscatingTranslator.getTranslatedMethod(this.entry).getName();
60 } 60 }
61 61
62 public boolean isImplemented() { 62 public boolean isImplemented() {
@@ -84,11 +84,9 @@ public class MethodInheritanceTreeNode extends DefaultMutableTreeNode {
84 public void load(JarIndex index, boolean recurse) { 84 public void load(JarIndex index, boolean recurse) {
85 // get all the child nodes 85 // get all the child nodes
86 List<MethodInheritanceTreeNode> nodes = Lists.newArrayList(); 86 List<MethodInheritanceTreeNode> nodes = Lists.newArrayList();
87 for (ClassEntry subclassEntry : index.getTranslationIndex().getSubclass(this.entry.getClassEntry())) { 87 for (ClassEntry subclassEntry : index.getTranslationIndex().getSubclass(this.entry.getOwnerClassEntry())) {
88 MethodEntry methodEntry = new MethodEntry(subclassEntry, this.entry.getName(), this.entry.getSignature() 88 MethodEntry methodEntry = new MethodEntry(subclassEntry, this.entry.getName(), this.entry.getDesc());
89 ); 89 nodes.add(new MethodInheritanceTreeNode(this.deobfuscatingTranslator, methodEntry, index.containsObfMethod(methodEntry)));
90 nodes.add(new MethodInheritanceTreeNode(this.deobfuscatingTranslator, methodEntry, index.containsObfBehavior(methodEntry)
91 ));
92 } 90 }
93 91
94 // add them to this node 92 // add them to this node
diff --git a/src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java
index 6556b2c..76c73c1 100644
--- a/src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java
+++ b/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java
@@ -12,30 +12,31 @@
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import com.google.common.collect.Sets; 14import com.google.common.collect.Sets;
15import cuchaz.enigma.mapping.BehaviorEntry; 15import cuchaz.enigma.mapping.*;
16import cuchaz.enigma.mapping.Entry; 16import cuchaz.enigma.mapping.entry.Entry;
17import cuchaz.enigma.mapping.Translator; 17import cuchaz.enigma.mapping.entry.MethodDefEntry;
18import cuchaz.enigma.mapping.entry.MethodEntry;
18 19
19import javax.swing.tree.DefaultMutableTreeNode; 20import javax.swing.tree.DefaultMutableTreeNode;
20import javax.swing.tree.TreeNode; 21import javax.swing.tree.TreeNode;
21import java.util.Set; 22import java.util.Set;
22 23
23public class BehaviorReferenceTreeNode extends DefaultMutableTreeNode 24public class MethodReferenceTreeNode extends DefaultMutableTreeNode
24 implements ReferenceTreeNode<BehaviorEntry, BehaviorEntry> { 25 implements ReferenceTreeNode<MethodEntry, MethodDefEntry> {
25 26
26 private Translator deobfuscatingTranslator; 27 private Translator deobfuscatingTranslator;
27 private BehaviorEntry entry; 28 private MethodEntry entry;
28 private EntryReference<BehaviorEntry, BehaviorEntry> reference; 29 private EntryReference<MethodEntry, MethodDefEntry> reference;
29 private Access access; 30 private Access access;
30 31
31 public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, BehaviorEntry entry) { 32 public MethodReferenceTreeNode(Translator deobfuscatingTranslator, MethodEntry entry) {
32 this.deobfuscatingTranslator = deobfuscatingTranslator; 33 this.deobfuscatingTranslator = deobfuscatingTranslator;
33 this.entry = entry; 34 this.entry = entry;
34 this.reference = null; 35 this.reference = null;
35 } 36 }
36 37
37 public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, 38 public MethodReferenceTreeNode(Translator deobfuscatingTranslator,
38 EntryReference<BehaviorEntry, BehaviorEntry> reference, Access access) { 39 EntryReference<MethodEntry, MethodDefEntry> reference, Access access) {
39 this.deobfuscatingTranslator = deobfuscatingTranslator; 40 this.deobfuscatingTranslator = deobfuscatingTranslator;
40 this.entry = reference.entry; 41 this.entry = reference.entry;
41 this.reference = reference; 42 this.reference = reference;
@@ -43,42 +44,42 @@ public class BehaviorReferenceTreeNode extends DefaultMutableTreeNode
43 } 44 }
44 45
45 @Override 46 @Override
46 public BehaviorEntry getEntry() { 47 public MethodEntry getEntry() {
47 return this.entry; 48 return this.entry;
48 } 49 }
49 50
50 @Override 51 @Override
51 public EntryReference<BehaviorEntry, BehaviorEntry> getReference() { 52 public EntryReference<MethodEntry, MethodDefEntry> getReference() {
52 return this.reference; 53 return this.reference;
53 } 54 }
54 55
55 @Override 56 @Override
56 public String toString() { 57 public String toString() {
57 if (this.reference != null) { 58 if (this.reference != null) {
58 return String.format("%s (%s)", this.deobfuscatingTranslator.translateEntry(this.reference.context), 59 return String.format("%s (%s)", this.deobfuscatingTranslator.getTranslatedMethodDef(this.reference.context),
59 this.access); 60 this.access);
60 } 61 }
61 return this.deobfuscatingTranslator.translateEntry(this.entry).toString(); 62 return this.deobfuscatingTranslator.getTranslatedMethod(this.entry).getName();
62 } 63 }
63 64
64 public void load(JarIndex index, boolean recurse) { 65 public void load(JarIndex index, boolean recurse) {
65 // get all the child nodes 66 // get all the child nodes
66 for (EntryReference<BehaviorEntry, BehaviorEntry> reference : index.getBehaviorReferences(this.entry)) { 67 for (EntryReference<MethodEntry, MethodDefEntry> reference : index.getMethodsReferencing(this.entry)) {
67 add(new BehaviorReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.entry))); 68 add(new MethodReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.entry)));
68 } 69 }
69 70
70 if (recurse && this.children != null) { 71 if (recurse && this.children != null) {
71 for (Object child : this.children) { 72 for (Object child : this.children) {
72 if (child instanceof BehaviorReferenceTreeNode) { 73 if (child instanceof MethodReferenceTreeNode) {
73 BehaviorReferenceTreeNode node = (BehaviorReferenceTreeNode) child; 74 MethodReferenceTreeNode node = (MethodReferenceTreeNode) child;
74 75
75 // don't recurse into ancestor 76 // don't recurse into ancestor
76 Set<Entry> ancestors = Sets.newHashSet(); 77 Set<Entry> ancestors = Sets.newHashSet();
77 TreeNode n = node; 78 TreeNode n = node;
78 while (n.getParent() != null) { 79 while (n.getParent() != null) {
79 n = n.getParent(); 80 n = n.getParent();
80 if (n instanceof BehaviorReferenceTreeNode) { 81 if (n instanceof MethodReferenceTreeNode) {
81 ancestors.add(((BehaviorReferenceTreeNode) n).getEntry()); 82 ancestors.add(((MethodReferenceTreeNode) n).getEntry());
82 } 83 }
83 } 84 }
84 if (ancestors.contains(node.getEntry())) { 85 if (ancestors.contains(node.getEntry())) {
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import cuchaz.enigma.mapping.entry.ClassEntry;
15import org.objectweb.asm.ClassReader;
16import org.objectweb.asm.tree.ClassNode;
17
18import java.io.BufferedInputStream;
19import java.io.IOException;
20import java.io.InputStream;
21import java.util.*;
22import java.util.function.Consumer;
23import java.util.jar.JarEntry;
24import java.util.jar.JarFile;
25import java.util.jar.JarInputStream;
26
27public class ParsedJar {
28 private final Map<String, ClassNode> nodes = new LinkedHashMap<>();
29
30 public ParsedJar(JarFile jar) throws IOException {
31 try {
32 // get the jar entries that correspond to classes
33 Enumeration<JarEntry> entries = jar.entries();
34 while (entries.hasMoreElements()) {
35 JarEntry entry = entries.nextElement();
36 // is this a class file?
37 if (entry.getName().endsWith(".class")) {
38 try (InputStream input = new BufferedInputStream(jar.getInputStream(entry))) {
39 // read the ClassNode from the jar
40 ClassReader reader = new ClassReader(input);
41 ClassNode node = new ClassNode();
42 reader.accept(node, 0);
43 String path = entry.getName().substring(0, entry.getName().length() - ".class".length());
44 nodes.put(path, node);
45 }
46 }
47 }
48 } finally {
49 jar.close();
50 }
51 }
52
53 public ParsedJar(JarInputStream jar) throws IOException {
54 try {
55 // get the jar entries that correspond to classes
56 JarEntry entry;
57 while ((entry = jar.getNextJarEntry()) != null) {
58 // is this a class file?
59 if (entry.getName().endsWith(".class")) {
60 // read the ClassNode from the jar
61 ClassReader reader = new ClassReader(jar);
62 ClassNode node = new ClassNode();
63 reader.accept(node, 0);
64 String path = entry.getName().substring(0, entry.getName().length() - ".class".length());
65 nodes.put(path, node);
66 jar.closeEntry();
67 }
68 }
69 } finally {
70 jar.close();
71 }
72 }
73
74 public void visit(Consumer<ClassNode> visitor) {
75 for (ClassNode node : nodes.values()) {
76 visitor.accept(node);
77 }
78 }
79
80 public int getClassCount() {
81 return nodes.size();
82 }
83
84 public List<ClassEntry> getClassEntries() {
85 List<ClassEntry> entries = new ArrayList<>(nodes.size());
86 for (ClassNode node : nodes.values()) {
87 entries.add(new ClassEntry(node.name));
88 }
89 return entries;
90 }
91
92 public ClassNode getClassNode(String name) {
93 return nodes.get(name);
94 }
95}
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 @@
11 11
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import cuchaz.enigma.mapping.Entry; 14import cuchaz.enigma.mapping.entry.Entry;
15 15
16public interface ReferenceTreeNode<E extends Entry, C extends Entry> { 16public interface ReferenceTreeNode<E extends Entry, C extends Entry> {
17 E getEntry(); 17 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;
18import com.strobel.decompiler.languages.Region; 18import com.strobel.decompiler.languages.Region;
19import com.strobel.decompiler.languages.java.ast.AstNode; 19import com.strobel.decompiler.languages.java.ast.AstNode;
20import com.strobel.decompiler.languages.java.ast.Identifier; 20import com.strobel.decompiler.languages.java.ast.Identifier;
21import cuchaz.enigma.mapping.Entry; 21import cuchaz.enigma.mapping.entry.Entry;
22 22
23import java.util.Collection; 23import java.util.Collection;
24import java.util.List; 24import java.util.List;
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;
17import com.strobel.assembler.metadata.TypeReference; 17import com.strobel.assembler.metadata.TypeReference;
18import com.strobel.decompiler.languages.TextLocation; 18import com.strobel.decompiler.languages.TextLocation;
19import com.strobel.decompiler.languages.java.ast.*; 19import com.strobel.decompiler.languages.java.ast.*;
20import cuchaz.enigma.mapping.*; 20import cuchaz.enigma.bytecode.AccessFlags;
21import cuchaz.enigma.mapping.Signature;
22import cuchaz.enigma.mapping.entry.*;
21 23
22public class SourceIndexClassVisitor extends SourceIndexVisitor { 24public class SourceIndexClassVisitor extends SourceIndexVisitor {
25 private final ReferencedEntryPool entryPool;
26 private final ProcyonEntryFactory entryFactory;
23 27
24 private ClassEntry classEntry; 28 private ClassDefEntry classEntry;
25 private boolean isEnum; 29 private boolean isEnum;
26 30
27 public SourceIndexClassVisitor(ClassEntry classEntry) { 31 public SourceIndexClassVisitor(ReferencedEntryPool entryPool, ClassDefEntry classEntry) {
32 super(entryPool);
33 this.entryPool = entryPool;
34 this.entryFactory = new ProcyonEntryFactory(entryPool);
28 this.classEntry = classEntry; 35 this.classEntry = classEntry;
29 } 36 }
30 37
@@ -32,11 +39,11 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor {
32 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { 39 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) {
33 // is this this class, or a subtype? 40 // is this this class, or a subtype?
34 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); 41 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION);
35 ClassEntry classEntry = new ClassEntry(def.getInternalName()); 42 ClassDefEntry classEntry = new ClassDefEntry(def.getInternalName(), Signature.createSignature(def.getSignature()), new AccessFlags(def.getModifiers()));
36 if (!classEntry.equals(this.classEntry)) { 43 if (!classEntry.equals(this.classEntry)) {
37 // it's a sub-type, recurse 44 // it's a subtype, recurse
38 index.addDeclaration(node.getNameToken(), classEntry); 45 index.addDeclaration(node.getNameToken(), classEntry);
39 return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); 46 return node.acceptVisitor(new SourceIndexClassVisitor(entryPool, classEntry), index);
40 } 47 }
41 48
42 return recurse(node, index); 49 return recurse(node, index);
@@ -56,31 +63,28 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor {
56 @Override 63 @Override
57 public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) { 64 public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) {
58 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); 65 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
59 BehaviorEntry behaviorEntry = ProcyonEntryFactory.getBehaviorEntry(def); 66 MethodDefEntry methodEntry = entryFactory.getMethodDefEntry(def);
60 AstNode tokenNode = node.getNameToken(); 67 AstNode tokenNode = node.getNameToken();
61 if (behaviorEntry instanceof ConstructorEntry) { 68 if (methodEntry.isConstructor() && methodEntry.getName().equals("<clinit>")) {
62 ConstructorEntry constructorEntry = (ConstructorEntry) behaviorEntry; 69 // for static initializers, check elsewhere for the token node
63 if (constructorEntry.isStatic()) { 70 tokenNode = node.getModifiers().firstOrNullObject();
64 // for static initializers, check elsewhere for the token node
65 tokenNode = node.getModifiers().firstOrNullObject();
66 }
67 } 71 }
68 index.addDeclaration(tokenNode, behaviorEntry); 72 index.addDeclaration(tokenNode, methodEntry);
69 return node.acceptVisitor(new SourceIndexBehaviorVisitor(behaviorEntry, false), index); 73 return node.acceptVisitor(new SourceIndexMethodVisitor(entryPool, classEntry, methodEntry), index);
70 } 74 }
71 75
72 @Override 76 @Override
73 public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) { 77 public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) {
74 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); 78 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
75 ConstructorEntry constructorEntry = ProcyonEntryFactory.getConstructorEntry(def); 79 MethodDefEntry methodEntry = entryFactory.getMethodDefEntry(def);
76 index.addDeclaration(node.getNameToken(), constructorEntry); 80 index.addDeclaration(node.getNameToken(), methodEntry);
77 return node.acceptVisitor(new SourceIndexBehaviorVisitor(constructorEntry, isEnum), index); 81 return node.acceptVisitor(new SourceIndexMethodVisitor(entryPool, classEntry, methodEntry), index);
78 } 82 }
79 83
80 @Override 84 @Override
81 public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) { 85 public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) {
82 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); 86 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION);
83 FieldEntry fieldEntry = ProcyonEntryFactory.getFieldEntry(def); 87 FieldDefEntry fieldEntry = entryFactory.getFieldDefEntry(def);
84 assert (node.getVariables().size() == 1); 88 assert (node.getVariables().size() == 1);
85 VariableInitializer variable = node.getVariables().firstOrNullObject(); 89 VariableInitializer variable = node.getVariables().firstOrNullObject();
86 index.addDeclaration(variable.getNameToken(), fieldEntry); 90 index.addDeclaration(variable.getNameToken(), fieldEntry);
@@ -92,7 +96,7 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor {
92 public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) { 96 public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) {
93 // treat enum declarations as field declarations 97 // treat enum declarations as field declarations
94 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); 98 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION);
95 FieldEntry fieldEntry = ProcyonEntryFactory.getFieldEntry(def); 99 FieldDefEntry fieldEntry = entryFactory.getFieldDefEntry(def);
96 index.addDeclaration(node.getNameToken(), fieldEntry); 100 index.addDeclaration(node.getNameToken(), fieldEntry);
97 this.isEnum = true; 101 this.isEnum = true;
98 return recurse(node, index); 102 return recurse(node, index);
diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java
index 1b61916..83fe296 100644
--- a/src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java
+++ b/src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java
@@ -13,31 +13,33 @@ package cuchaz.enigma.analysis;
13 13
14import com.google.common.collect.HashMultimap; 14import com.google.common.collect.HashMultimap;
15import com.google.common.collect.Multimap; 15import com.google.common.collect.Multimap;
16import com.strobel.assembler.metadata.MemberReference; 16import com.strobel.assembler.metadata.*;
17import com.strobel.assembler.metadata.MethodReference; 17import com.strobel.decompiler.ast.Variable;
18import com.strobel.assembler.metadata.ParameterDefinition;
19import com.strobel.assembler.metadata.TypeReference;
20import com.strobel.decompiler.languages.TextLocation; 18import com.strobel.decompiler.languages.TextLocation;
21import com.strobel.decompiler.languages.java.ast.*; 19import com.strobel.decompiler.languages.java.ast.*;
22import cuchaz.enigma.mapping.*; 20import cuchaz.enigma.mapping.TypeDescriptor;
23import javassist.bytecode.Descriptor; 21import cuchaz.enigma.mapping.entry.*;
24 22
23import java.lang.Error;
25import java.util.HashMap; 24import java.util.HashMap;
26import java.util.Map; 25import java.util.Map;
27 26
28public class SourceIndexBehaviorVisitor extends SourceIndexVisitor { 27public class SourceIndexMethodVisitor extends SourceIndexVisitor {
29 private BehaviorEntry behaviorEntry; 28 private final ReferencedEntryPool entryPool;
29 private final ProcyonEntryFactory entryFactory;
30
31 private final ClassDefEntry ownerEntry;
32 private final MethodDefEntry methodEntry;
30 33
31 // TODO: Really fix Procyon index problem with inner classes
32 private int argumentPosition;
33 private int localsPosition;
34 private Multimap<String, Identifier> unmatchedIdentifier = HashMultimap.create(); 34 private Multimap<String, Identifier> unmatchedIdentifier = HashMultimap.create();
35 private Map<String, Entry> identifierEntryCache = new HashMap<>(); 35 private Map<String, Entry> identifierEntryCache = new HashMap<>();
36 36
37 public SourceIndexBehaviorVisitor(BehaviorEntry behaviorEntry, boolean isEnum) { 37 public SourceIndexMethodVisitor(ReferencedEntryPool entryPool, ClassDefEntry ownerEntry, MethodDefEntry methodEntry) {
38 this.behaviorEntry = behaviorEntry; 38 super(entryPool);
39 this.argumentPosition = isEnum ? 2 : 0; 39 this.entryPool = entryPool;
40 this.localsPosition = 0; 40 this.entryFactory = new ProcyonEntryFactory(entryPool);
41 this.ownerEntry = ownerEntry;
42 this.methodEntry = methodEntry;
41 } 43 }
42 44
43 @Override 45 @Override
@@ -45,19 +47,12 @@ public class SourceIndexBehaviorVisitor extends SourceIndexVisitor {
45 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); 47 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
46 48
47 // get the behavior entry 49 // get the behavior entry
48 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); 50 ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName());
49 BehaviorEntry behaviorEntry = null; 51 MethodEntry methodEntry = null;
50 if (ref instanceof MethodReference) { 52 if (ref instanceof MethodReference) {
51 MethodReference methodRef = (MethodReference) ref; 53 methodEntry = entryPool.getMethod(classEntry, ref.getName(), ref.getErasedSignature());
52 if (methodRef.isConstructor()) {
53 behaviorEntry = new ConstructorEntry(classEntry, new Signature(ref.getErasedSignature()));
54 } else if (methodRef.isTypeInitializer()) {
55 behaviorEntry = new ConstructorEntry(classEntry);
56 } else {
57 behaviorEntry = new MethodEntry(classEntry, ref.getName(), new Signature(ref.getErasedSignature()));
58 }
59 } 54 }
60 if (behaviorEntry != null) { 55 if (methodEntry != null) {
61 // get the node for the token 56 // get the node for the token
62 AstNode tokenNode = null; 57 AstNode tokenNode = null;
63 if (node.getTarget() instanceof MemberReferenceExpression) { 58 if (node.getTarget() instanceof MemberReferenceExpression) {
@@ -68,13 +63,13 @@ public class SourceIndexBehaviorVisitor extends SourceIndexVisitor {
68 tokenNode = node.getTarget(); 63 tokenNode = node.getTarget();
69 } 64 }
70 if (tokenNode != null) { 65 if (tokenNode != null) {
71 index.addReference(tokenNode, behaviorEntry, this.behaviorEntry); 66 index.addReference(tokenNode, methodEntry, this.methodEntry);
72 } 67 }
73 } 68 }
74 69
75 // Check for identifier 70 // Check for identifier
76 node.getArguments().stream().filter(expression -> expression instanceof IdentifierExpression) 71 node.getArguments().stream().filter(expression -> expression instanceof IdentifierExpression)
77 .forEach(expression -> this.checkIdentifier((IdentifierExpression) expression, index)); 72 .forEach(expression -> this.checkIdentifier((IdentifierExpression) expression, index));
78 return recurse(node, index); 73 return recurse(node, index);
79 } 74 }
80 75
@@ -83,13 +78,17 @@ public class SourceIndexBehaviorVisitor extends SourceIndexVisitor {
83 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); 78 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
84 if (ref != null) { 79 if (ref != null) {
85 // make sure this is actually a field 80 // make sure this is actually a field
86 if (ref.getErasedSignature().indexOf('(') >= 0) { 81 String erasedSignature = ref.getErasedSignature();
82 if (erasedSignature.indexOf('(') >= 0) {
87 throw new Error("Expected a field here! got " + ref); 83 throw new Error("Expected a field here! got " + ref);
88 } 84 }
89 85
90 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); 86 ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName());
91 FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getErasedSignature())); 87 FieldEntry fieldEntry = entryPool.getField(classEntry, ref.getName(), new TypeDescriptor(erasedSignature));
92 index.addReference(node.getMemberNameToken(), fieldEntry, this.behaviorEntry); 88 if (fieldEntry == null) {
89 throw new Error("Failed to find field " + ref.getName() + " on " + classEntry.getName());
90 }
91 index.addReference(node.getMemberNameToken(), fieldEntry, this.methodEntry);
93 } 92 }
94 93
95 return recurse(node, index); 94 return recurse(node, index);
@@ -99,8 +98,8 @@ public class SourceIndexBehaviorVisitor extends SourceIndexVisitor {
99 public Void visitSimpleType(SimpleType node, SourceIndex index) { 98 public Void visitSimpleType(SimpleType node, SourceIndex index) {
100 TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE); 99 TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE);
101 if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) { 100 if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) {
102 ClassEntry classEntry = new ClassEntry(ref.getInternalName()); 101 ClassEntry classEntry = entryPool.getClass(ref.getInternalName());
103 index.addReference(node.getIdentifierToken(), classEntry, this.behaviorEntry); 102 index.addReference(node.getIdentifierToken(), classEntry, this.methodEntry);
104 } 103 }
105 104
106 return recurse(node, index); 105 return recurse(node, index);
@@ -109,13 +108,16 @@ public class SourceIndexBehaviorVisitor extends SourceIndexVisitor {
109 @Override 108 @Override
110 public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) { 109 public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) {
111 ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION); 110 ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION);
112 if (def.getMethod() instanceof MemberReference && def.getMethod() instanceof MethodReference) { 111
113 ArgumentEntry argumentEntry = new ArgumentEntry(ProcyonEntryFactory.getBehaviorEntry((MethodReference) def.getMethod()), 112 int variableOffset = this.methodEntry.getVariableOffset(ownerEntry);
114 argumentPosition++, node.getName()); 113 int parameterIndex = def.getSlot() - variableOffset;
114
115 if (parameterIndex >= 0) {
116 LocalVariableEntry localVariableEntry = new LocalVariableEntry(methodEntry, parameterIndex, node.getName());
115 Identifier identifier = node.getNameToken(); 117 Identifier identifier = node.getNameToken();
116 // cache the argument entry and the identifier 118 // cache the argument entry and the identifier
117 identifierEntryCache.put(identifier.getName(), argumentEntry); 119 identifierEntryCache.put(identifier.getName(), localVariableEntry);
118 index.addDeclaration(identifier, argumentEntry); 120 index.addDeclaration(identifier, localVariableEntry);
119 } 121 }
120 122
121 return recurse(node, index); 123 return recurse(node, index);
@@ -125,9 +127,12 @@ public class SourceIndexBehaviorVisitor extends SourceIndexVisitor {
125 public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) { 127 public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) {
126 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); 128 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
127 if (ref != null) { 129 if (ref != null) {
128 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); 130 ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName());
129 FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getErasedSignature())); 131 FieldEntry fieldEntry = entryPool.getField(classEntry, ref.getName(), new TypeDescriptor(ref.getErasedSignature()));
130 index.addReference(node.getIdentifierToken(), fieldEntry, this.behaviorEntry); 132 if (fieldEntry == null) {
133 throw new Error("Failed to find field " + ref.getName() + " on " + classEntry.getName());
134 }
135 index.addReference(node.getIdentifierToken(), fieldEntry, this.methodEntry);
131 } else 136 } else
132 this.checkIdentifier(node, index); 137 this.checkIdentifier(node, index);
133 return recurse(node, index); 138 return recurse(node, index);
@@ -155,11 +160,11 @@ public class SourceIndexBehaviorVisitor extends SourceIndexVisitor {
155 public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) { 160 public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) {
156 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); 161 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
157 if (ref != null) { 162 if (ref != null) {
158 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); 163 ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName());
159 ConstructorEntry constructorEntry = new ConstructorEntry(classEntry, new Signature(ref.getErasedSignature())); 164 MethodEntry constructorEntry = entryPool.getMethod(classEntry, "<init>", ref.getErasedSignature());
160 if (node.getType() instanceof SimpleType) { 165 if (node.getType() instanceof SimpleType) {
161 SimpleType simpleTypeNode = (SimpleType) node.getType(); 166 SimpleType simpleTypeNode = (SimpleType) node.getType();
162 index.addReference(simpleTypeNode.getIdentifierToken(), constructorEntry, this.behaviorEntry); 167 index.addReference(simpleTypeNode.getIdentifierToken(), constructorEntry, this.methodEntry);
163 } 168 }
164 } 169 }
165 170
@@ -167,21 +172,6 @@ public class SourceIndexBehaviorVisitor extends SourceIndexVisitor {
167 } 172 }
168 173
169 @Override 174 @Override
170 public Void visitForEachStatement(ForEachStatement node, SourceIndex index) {
171 if (node.getVariableType() instanceof SimpleType) {
172 SimpleType type = (SimpleType) node.getVariableType();
173 TypeReference typeReference = type.getUserData(Keys.TYPE_REFERENCE);
174 Identifier identifier = node.getVariableNameToken();
175 String signature = Descriptor.of(typeReference.getErasedDescription());
176 LocalVariableEntry localVariableEntry = new LocalVariableEntry(behaviorEntry, argumentPosition + localsPosition++, identifier.getName(), new Type(signature));
177 identifierEntryCache.put(identifier.getName(), localVariableEntry);
178 addDeclarationToUnmatched(identifier.getName(), index);
179 index.addDeclaration(identifier, localVariableEntry);
180 }
181 return recurse(node, index);
182 }
183
184 @Override
185 public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) { 175 public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) {
186 AstNodeCollection<VariableInitializer> variables = node.getVariables(); 176 AstNodeCollection<VariableInitializer> variables = node.getVariables();
187 177
@@ -189,16 +179,46 @@ public class SourceIndexBehaviorVisitor extends SourceIndexVisitor {
189 if (variables.size() == 1) { 179 if (variables.size() == 1) {
190 VariableInitializer initializer = variables.firstOrNullObject(); 180 VariableInitializer initializer = variables.firstOrNullObject();
191 if (initializer != null && node.getType() instanceof SimpleType) { 181 if (initializer != null && node.getType() instanceof SimpleType) {
192 SimpleType type = (SimpleType) node.getType();
193 TypeReference typeReference = type.getUserData(Keys.TYPE_REFERENCE);
194 String signature = Descriptor.of(typeReference.getErasedDescription());
195 Identifier identifier = initializer.getNameToken(); 182 Identifier identifier = initializer.getNameToken();
196 LocalVariableEntry localVariableEntry = new LocalVariableEntry(behaviorEntry, argumentPosition + localsPosition++, initializer.getName(), new Type(signature)); 183 Variable variable = initializer.getUserData(Keys.VARIABLE);
197 identifierEntryCache.put(identifier.getName(), localVariableEntry); 184 if (variable != null) {
198 addDeclarationToUnmatched(identifier.getName(), index); 185 VariableDefinition originalVariable = variable.getOriginalVariable();
199 index.addDeclaration(identifier, localVariableEntry); 186 if (originalVariable != null) {
187 int variableOffset = methodEntry.getVariableOffset(ownerEntry);
188 int variableIndex = originalVariable.getSlot() - variableOffset;
189 if (variableIndex >= 0) {
190 LocalVariableEntry localVariableEntry = new LocalVariableEntry(methodEntry, variableIndex, initializer.getName());
191 identifierEntryCache.put(identifier.getName(), localVariableEntry);
192 addDeclarationToUnmatched(identifier.getName(), index);
193 index.addDeclaration(identifier, localVariableEntry);
194 }
195 }
196 }
200 } 197 }
201 } 198 }
202 return recurse(node, index); 199 return recurse(node, index);
203 } 200 }
201
202 @Override
203 public Void visitMethodGroupExpression(MethodGroupExpression node, SourceIndex index) {
204 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
205
206 if (ref instanceof MethodReference) {
207 // get the behavior entry
208 ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName());
209 MethodEntry methodEntry = null;
210
211 methodEntry = entryPool.getMethod(classEntry, ref.getName(), ref.getErasedSignature());
212 // get the node for the token
213 AstNode tokenNode = node.getMethodNameToken();
214 if (tokenNode == null || (tokenNode.getRegion().getBeginLine() == 0 || tokenNode.getRegion().getEndLine() == 0)){
215 tokenNode = node.getTarget();
216 }
217 if (tokenNode != null) {
218 index.addReference(tokenNode, methodEntry, this.methodEntry);
219 }
220 }
221
222 return recurse(node, index);
223 }
204} 224}
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;
14import com.strobel.assembler.metadata.TypeDefinition; 14import com.strobel.assembler.metadata.TypeDefinition;
15import com.strobel.decompiler.languages.java.ast.*; 15import com.strobel.decompiler.languages.java.ast.*;
16import com.strobel.decompiler.patterns.Pattern; 16import com.strobel.decompiler.patterns.Pattern;
17import cuchaz.enigma.mapping.ClassEntry; 17import cuchaz.enigma.bytecode.AccessFlags;
18import cuchaz.enigma.mapping.Signature;
19import cuchaz.enigma.mapping.entry.ClassDefEntry;
20import cuchaz.enigma.mapping.entry.ReferencedEntryPool;
18 21
19public class SourceIndexVisitor implements IAstVisitor<SourceIndex, Void> { 22public class SourceIndexVisitor implements IAstVisitor<SourceIndex, Void> {
23 private final ReferencedEntryPool entryPool;
24
25 public SourceIndexVisitor(ReferencedEntryPool entryPool) {
26 this.entryPool = entryPool;
27 }
20 28
21 @Override 29 @Override
22 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { 30 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) {
23 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); 31 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION);
24 ClassEntry classEntry = new ClassEntry(def.getInternalName()); 32 ClassDefEntry classEntry = new ClassDefEntry(def.getInternalName(), Signature.createSignature(def.getSignature()), new AccessFlags(def.getModifiers()));
25 index.addDeclaration(node.getNameToken(), classEntry); 33 index.addDeclaration(node.getNameToken(), classEntry);
26 34
27 return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); 35 return node.acceptVisitor(new SourceIndexClassVisitor(entryPool, classEntry), index);
28 } 36 }
29 37
30 protected Void recurse(AstNode node, SourceIndex index) { 38 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;
15import com.google.common.collect.Lists; 15import com.google.common.collect.Lists;
16import com.google.common.collect.Maps; 16import com.google.common.collect.Maps;
17import com.google.common.collect.Multimap; 17import com.google.common.collect.Multimap;
18import cuchaz.enigma.bytecode.AccessFlags;
18import cuchaz.enigma.mapping.*; 19import cuchaz.enigma.mapping.*;
19import javassist.CtBehavior; 20import cuchaz.enigma.mapping.entry.*;
20import javassist.CtClass;
21import javassist.CtField;
22import javassist.bytecode.Descriptor;
23 21
24import java.util.Collection; 22import java.util.Collection;
25import java.util.List; 23import java.util.List;
@@ -28,96 +26,91 @@ import java.util.Set;
28 26
29public class TranslationIndex { 27public class TranslationIndex {
30 28
29 private final ReferencedEntryPool entryPool;
31 private Map<ClassEntry, ClassEntry> superclasses; 30 private Map<ClassEntry, ClassEntry> superclasses;
32 private Multimap<ClassEntry, FieldEntry> fieldEntries; 31 private Multimap<ClassEntry, FieldDefEntry> fieldEntries;
33 private Multimap<ClassEntry, BehaviorEntry> behaviorEntries; 32 private Multimap<ClassEntry, MethodDefEntry> methodEntries;
34 private Multimap<ClassEntry, ClassEntry> interfaces; 33 private Multimap<ClassEntry, ClassEntry> interfaces;
35 34
36 public TranslationIndex() { 35 public TranslationIndex(ReferencedEntryPool entryPool) {
36 this.entryPool = entryPool;
37 this.superclasses = Maps.newHashMap(); 37 this.superclasses = Maps.newHashMap();
38 this.fieldEntries = HashMultimap.create(); 38 this.fieldEntries = HashMultimap.create();
39 this.behaviorEntries = HashMultimap.create(); 39 this.methodEntries = HashMultimap.create();
40 this.interfaces = HashMultimap.create(); 40 this.interfaces = HashMultimap.create();
41 } 41 }
42 42
43 public TranslationIndex(TranslationIndex other, Translator translator) { 43 public TranslationIndex(TranslationIndex other, Translator translator) {
44 this.entryPool = other.entryPool;
45
44 // translate the superclasses 46 // translate the superclasses
45 this.superclasses = Maps.newHashMap(); 47 this.superclasses = Maps.newHashMap();
46 for (Map.Entry<ClassEntry, ClassEntry> mapEntry : other.superclasses.entrySet()) { 48 for (Map.Entry<ClassEntry, ClassEntry> mapEntry : other.superclasses.entrySet()) {
47 this.superclasses.put(translator.translateEntry(mapEntry.getKey()), translator.translateEntry(mapEntry.getValue())); 49 this.superclasses.put(translator.getTranslatedClass(mapEntry.getKey()), translator.getTranslatedClass(mapEntry.getValue()));
48 } 50 }
49 51
50 // translate the interfaces 52 // translate the interfaces
51 this.interfaces = HashMultimap.create(); 53 this.interfaces = HashMultimap.create();
52 for (Map.Entry<ClassEntry, ClassEntry> mapEntry : other.interfaces.entries()) { 54 for (Map.Entry<ClassEntry, ClassEntry> mapEntry : other.interfaces.entries()) {
53 this.interfaces.put( 55 this.interfaces.put(
54 translator.translateEntry(mapEntry.getKey()), 56 translator.getTranslatedClass(mapEntry.getKey()),
55 translator.translateEntry(mapEntry.getValue()) 57 translator.getTranslatedClass(mapEntry.getValue())
56 ); 58 );
57 } 59 }
58 60
59 // translate the fields 61 // translate the fields
60 this.fieldEntries = HashMultimap.create(); 62 this.fieldEntries = HashMultimap.create();
61 for (Map.Entry<ClassEntry, FieldEntry> mapEntry : other.fieldEntries.entries()) { 63 for (Map.Entry<ClassEntry, FieldDefEntry> mapEntry : other.fieldEntries.entries()) {
62 this.fieldEntries.put( 64 this.fieldEntries.put(
63 translator.translateEntry(mapEntry.getKey()), 65 translator.getTranslatedClass(mapEntry.getKey()),
64 translator.translateEntry(mapEntry.getValue()) 66 translator.getTranslatedFieldDef(mapEntry.getValue())
65 ); 67 );
66 } 68 }
67 69
68 this.behaviorEntries = HashMultimap.create(); 70 this.methodEntries = HashMultimap.create();
69 for (Map.Entry<ClassEntry, BehaviorEntry> mapEntry : other.behaviorEntries.entries()) { 71 for (Map.Entry<ClassEntry, MethodDefEntry> mapEntry : other.methodEntries.entries()) {
70 this.behaviorEntries.put( 72 this.methodEntries.put(
71 translator.translateEntry(mapEntry.getKey()), 73 translator.getTranslatedClass(mapEntry.getKey()),
72 translator.translateEntry(mapEntry.getValue()) 74 translator.getTranslatedMethodDef(mapEntry.getValue())
73 ); 75 );
74 } 76 }
75 } 77 }
76 78
77 public void indexClass(CtClass c) { 79 protected ClassDefEntry indexClass(int access, String name, String signature, String superName, String[] interfaces) {
78 indexClass(c, true); 80 ClassDefEntry classEntry = new ClassDefEntry(name, Signature.createSignature(signature), new AccessFlags(access));
79 }
80
81 public void indexClass(CtClass c, boolean indexMembers) {
82 ClassEntry classEntry = EntryFactory.getClassEntry(c);
83 if (isJre(classEntry)) { 81 if (isJre(classEntry)) {
84 return; 82 return null;
85 } 83 }
86 84
87 // add the superclass 85 // add the superclass
88 ClassEntry superclassEntry = EntryFactory.getSuperclassEntry(c); 86 ClassEntry superclassEntry = entryPool.getClass(superName);
89 if (superclassEntry != null) { 87 if (superclassEntry != null) {
90 this.superclasses.put(classEntry, superclassEntry); 88 this.superclasses.put(classEntry, superclassEntry);
91 } 89 }
92 90
93 // add the interfaces 91 // add the interfaces
94 for (String interfaceClassName : c.getClassFile().getInterfaces()) { 92 for (String interfaceClassName : interfaces) {
95 ClassEntry interfaceClassEntry = new ClassEntry(Descriptor.toJvmName(interfaceClassName)); 93 ClassEntry interfaceClassEntry = entryPool.getClass(interfaceClassName);
96 if (!isJre(interfaceClassEntry)) { 94 if (!isJre(interfaceClassEntry)) {
97
98 this.interfaces.put(classEntry, interfaceClassEntry); 95 this.interfaces.put(classEntry, interfaceClassEntry);
99 } 96 }
100 } 97 }
101 98
102 if (indexMembers) { 99 return classEntry;
103 // add fields 100 }
104 for (CtField field : c.getDeclaredFields()) {
105 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
106 this.fieldEntries.put(fieldEntry.getClassEntry(), fieldEntry);
107 }
108 101
109 // add behaviors 102 protected void indexField(FieldDefEntry fieldEntry) {
110 for (CtBehavior behavior : c.getDeclaredBehaviors()) { 103 this.fieldEntries.put(fieldEntry.getOwnerClassEntry(), fieldEntry);
111 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); 104 }
112 this.behaviorEntries.put(behaviorEntry.getClassEntry(), behaviorEntry); 105
113 } 106 protected void indexMethod(MethodDefEntry methodEntry) {
114 } 107 this.methodEntries.put(methodEntry.getOwnerClassEntry(), methodEntry);
115 } 108 }
116 109
117 public void renameClasses(Map<String, String> renames) { 110 public void renameClasses(Map<String, String> renames) {
118 EntryRenamer.renameClassesInMap(renames, this.superclasses); 111 EntryRenamer.renameClassesInMap(renames, this.superclasses);
119 EntryRenamer.renameClassesInMultimap(renames, this.fieldEntries); 112 EntryRenamer.renameClassesInMultimap(renames, this.fieldEntries);
120 EntryRenamer.renameClassesInMultimap(renames, this.behaviorEntries); 113 EntryRenamer.renameClassesInMultimap(renames, this.methodEntries);
121 } 114 }
122 115
123 public ClassEntry getSuperclass(ClassEntry classEntry) { 116 public ClassEntry getSuperclass(ClassEntry classEntry) {
@@ -175,31 +168,32 @@ public class TranslationIndex {
175 } 168 }
176 169
177 public boolean entryExists(Entry entry) { 170 public boolean entryExists(Entry entry) {
171 if (entry == null) {
172 return false;
173 }
178 if (entry instanceof FieldEntry) { 174 if (entry instanceof FieldEntry) {
179 return fieldExists((FieldEntry) entry); 175 return fieldExists((FieldEntry) entry);
180 } else if (entry instanceof BehaviorEntry) { 176 } else if (entry instanceof MethodEntry) {
181 return behaviorExists((BehaviorEntry) entry); 177 return methodExists((MethodEntry) entry);
182 } else if (entry instanceof ArgumentEntry) {
183 return behaviorExists(((ArgumentEntry) entry).getBehaviorEntry());
184 } else if (entry instanceof LocalVariableEntry) { 178 } else if (entry instanceof LocalVariableEntry) {
185 return behaviorExists(((LocalVariableEntry) entry).getBehaviorEntry()); 179 return methodExists(((LocalVariableEntry) entry).getOwnerEntry());
186 } 180 }
187 throw new IllegalArgumentException("Cannot check existence for " + entry.getClass()); 181 throw new IllegalArgumentException("Cannot check existence for " + entry.getClass());
188 } 182 }
189 183
190 public boolean fieldExists(FieldEntry fieldEntry) { 184 public boolean fieldExists(FieldEntry fieldEntry) {
191 return this.fieldEntries.containsEntry(fieldEntry.getClassEntry(), fieldEntry); 185 return this.fieldEntries.containsEntry(fieldEntry.getOwnerClassEntry(), fieldEntry);
192 } 186 }
193 187
194 public boolean behaviorExists(BehaviorEntry behaviorEntry) { 188 public boolean methodExists(MethodEntry methodEntry) {
195 return this.behaviorEntries.containsEntry(behaviorEntry.getClassEntry(), behaviorEntry); 189 return this.methodEntries.containsEntry(methodEntry.getOwnerClassEntry(), methodEntry);
196 } 190 }
197 191
198 public ClassEntry resolveEntryClass(Entry entry) { 192 public ClassEntry resolveEntryOwner(Entry entry) {
199 return resolveEntryClass(entry, false); 193 return resolveEntryOwner(entry, false);
200 } 194 }
201 195
202 public ClassEntry resolveEntryClass(Entry entry, boolean checkSuperclassBeforeChild) { 196 public ClassEntry resolveEntryOwner(Entry entry, boolean checkSuperclassBeforeChild) {
203 if (entry instanceof ClassEntry) { 197 if (entry instanceof ClassEntry) {
204 return (ClassEntry) entry; 198 return (ClassEntry) entry;
205 } 199 }
@@ -227,12 +221,12 @@ public class TranslationIndex {
227 Entry originalEntry = entry; 221 Entry originalEntry = entry;
228 222
229 // Get all possible superclasses and reverse the list 223 // Get all possible superclasses and reverse the list
230 List<ClassEntry> superclasses = Lists.reverse(getAncestry(originalEntry.getClassEntry())); 224 List<ClassEntry> superclasses = Lists.reverse(getAncestry(originalEntry.getOwnerClassEntry()));
231 225
232 boolean existInEntry = false; 226 boolean existInEntry = false;
233 227
234 for (ClassEntry classEntry : superclasses) { 228 for (ClassEntry classEntry : superclasses) {
235 entry = entry.cloneToNewClass(classEntry); 229 entry = entry.updateOwnership(classEntry);
236 existInEntry = entryExists(entry); 230 existInEntry = entryExists(entry);
237 231
238 // Check for possible entry in interfaces of superclasses 232 // Check for possible entry in interfaces of superclasses
@@ -245,9 +239,9 @@ public class TranslationIndex {
245 239
246 // Doesn't exists in superclasses? check the child or return null 240 // Doesn't exists in superclasses? check the child or return null
247 if (!existInEntry) 241 if (!existInEntry)
248 return !entryExists(originalEntry) ? null : originalEntry.getClassEntry(); 242 return !entryExists(originalEntry) ? null : originalEntry.getOwnerClassEntry();
249 243
250 return entry.getClassEntry(); 244 return entry.getOwnerClassEntry();
251 } 245 }
252 246
253 public ClassEntry resolveSuperclass(Entry entry) { 247 public ClassEntry resolveSuperclass(Entry entry) {
@@ -256,7 +250,7 @@ public class TranslationIndex {
256 250
257 while (!entryExists(entry)) { 251 while (!entryExists(entry)) {
258 // is there a parent class? 252 // is there a parent class?
259 ClassEntry superclassEntry = getSuperclass(entry.getClassEntry()); 253 ClassEntry superclassEntry = getSuperclass(entry.getOwnerClassEntry());
260 if (superclassEntry == null) { 254 if (superclassEntry == null) {
261 // this is probably a method from a class in a library 255 // this is probably a method from a class in a library
262 // we can't trace the implementation up any higher unless we index the library 256 // we can't trace the implementation up any higher unless we index the library
@@ -264,23 +258,23 @@ public class TranslationIndex {
264 } 258 }
265 259
266 // move up to the parent class 260 // move up to the parent class
267 entry = entry.cloneToNewClass(superclassEntry); 261 entry = entry.updateOwnership(superclassEntry);
268 } 262 }
269 return entry.getClassEntry(); 263 return entry.getOwnerClassEntry();
270 } 264 }
271 265
272 public ClassEntry resolveInterface(Entry entry) { 266 public ClassEntry resolveInterface(Entry entry) {
273 // the interfaces for any class is a forest 267 // the interfaces for any class is a forest
274 // so let's look at all the trees 268 // so let's look at all the trees
275 269
276 for (ClassEntry interfaceEntry : this.interfaces.get(entry.getClassEntry())) { 270 for (ClassEntry interfaceEntry : this.interfaces.get(entry.getOwnerClassEntry())) {
277 Collection<ClassEntry> subInterface = this.interfaces.get(interfaceEntry); 271 Collection<ClassEntry> subInterface = this.interfaces.get(interfaceEntry);
278 if (subInterface != null && !subInterface.isEmpty()) { 272 if (subInterface != null && !subInterface.isEmpty()) {
279 ClassEntry result = resolveInterface(entry.cloneToNewClass(interfaceEntry)); 273 ClassEntry result = resolveInterface(entry.updateOwnership(interfaceEntry));
280 if (result != null) 274 if (result != null)
281 return result; 275 return result;
282 } 276 }
283 ClassEntry resolvedClassEntry = resolveSuperclass(entry.cloneToNewClass(interfaceEntry)); 277 ClassEntry resolvedClassEntry = resolveSuperclass(entry.updateOwnership(interfaceEntry));
284 if (resolvedClassEntry != null) { 278 if (resolvedClassEntry != null) {
285 return resolvedClassEntry; 279 return resolvedClassEntry;
286 } 280 }