summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/analysis
diff options
context:
space:
mode:
authorGravatar lclc982016-06-30 00:49:21 +1000
committerGravatar GitHub2016-06-30 00:49:21 +1000
commit4be005617b3b8c3578cca07c5d085d12916f0d1d (patch)
treedb163431f38703e26da417ef05eaea2b27a498b9 /src/main/java/cuchaz/enigma/analysis
parentSome small changes to fix idea importing (diff)
downloadenigma-fork-4be005617b3b8c3578cca07c5d085d12916f0d1d.tar.gz
enigma-fork-4be005617b3b8c3578cca07c5d085d12916f0d1d.tar.xz
enigma-fork-4be005617b3b8c3578cca07c5d085d12916f0d1d.zip
Json format (#2)
* Added new format * Fixed bug * Updated Version
Diffstat (limited to 'src/main/java/cuchaz/enigma/analysis')
-rw-r--r--src/main/java/cuchaz/enigma/analysis/Access.java43
-rw-r--r--src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java93
-rw-r--r--src/main/java/cuchaz/enigma/analysis/BridgeMarker.java43
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java78
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java83
-rw-r--r--src/main/java/cuchaz/enigma/analysis/EntryReference.java126
-rw-r--r--src/main/java/cuchaz/enigma/analysis/EntryRenamer.java184
-rw-r--r--src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java81
-rw-r--r--src/main/java/cuchaz/enigma/analysis/JarClassIterator.java136
-rw-r--r--src/main/java/cuchaz/enigma/analysis/JarIndex.java802
-rw-r--r--src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java101
-rw-r--r--src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java114
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java19
-rw-r--r--src/main/java/cuchaz/enigma/analysis/RelatedMethodChecker.java104
-rw-r--r--src/main/java/cuchaz/enigma/analysis/SourceIndex.java185
-rw-r--r--src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java131
-rw-r--r--src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java100
-rw-r--r--src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java381
-rw-r--r--src/main/java/cuchaz/enigma/analysis/Token.java56
-rw-r--r--src/main/java/cuchaz/enigma/analysis/TranslationIndex.java282
-rw-r--r--src/main/java/cuchaz/enigma/analysis/TreeDumpVisitor.java441
21 files changed, 3583 insertions, 0 deletions
diff --git a/src/main/java/cuchaz/enigma/analysis/Access.java b/src/main/java/cuchaz/enigma/analysis/Access.java
new file mode 100644
index 0000000..877327f
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/Access.java
@@ -0,0 +1,43 @@
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 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.lang.reflect.Modifier;
14
15import javassist.CtBehavior;
16import javassist.CtField;
17
18public enum Access {
19
20 Public,
21 Protected,
22 Private;
23
24 public static Access get(CtBehavior behavior) {
25 return get(behavior.getModifiers());
26 }
27
28 public static Access get(CtField field) {
29 return get(field.getModifiers());
30 }
31
32 public static Access get(int modifiers) {
33 if (Modifier.isPublic(modifiers)) {
34 return Public;
35 } else if (Modifier.isProtected(modifiers)) {
36 return Protected;
37 } else if (Modifier.isPrivate(modifiers)) {
38 return Private;
39 }
40 // assume public by default
41 return Public;
42 }
43}
diff --git a/src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java
new file mode 100644
index 0000000..776f090
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java
@@ -0,0 +1,93 @@
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 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.google.common.collect.Sets;
14
15import java.util.Set;
16
17import javax.swing.tree.DefaultMutableTreeNode;
18import javax.swing.tree.TreeNode;
19
20import cuchaz.enigma.mapping.BehaviorEntry;
21import cuchaz.enigma.mapping.Entry;
22import cuchaz.enigma.mapping.Translator;
23
24public class BehaviorReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode<BehaviorEntry, BehaviorEntry> {
25
26 private static final long serialVersionUID = -3658163700783307520L;
27
28 private Translator m_deobfuscatingTranslator;
29 private BehaviorEntry m_entry;
30 private EntryReference<BehaviorEntry, BehaviorEntry> m_reference;
31 private Access m_access;
32
33 public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, BehaviorEntry entry) {
34 m_deobfuscatingTranslator = deobfuscatingTranslator;
35 m_entry = entry;
36 m_reference = null;
37 }
38
39 public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference<BehaviorEntry, BehaviorEntry> reference, Access access) {
40 m_deobfuscatingTranslator = deobfuscatingTranslator;
41 m_entry = reference.entry;
42 m_reference = reference;
43 m_access = access;
44 }
45
46 @Override
47 public BehaviorEntry getEntry() {
48 return m_entry;
49 }
50
51 @Override
52 public EntryReference<BehaviorEntry, BehaviorEntry> getReference() {
53 return m_reference;
54 }
55
56 @Override
57 public String toString() {
58 if (m_reference != null) {
59 return String.format("%s (%s)", m_deobfuscatingTranslator.translateEntry(m_reference.context), m_access);
60 }
61 return m_deobfuscatingTranslator.translateEntry(m_entry).toString();
62 }
63
64 public void load(JarIndex index, boolean recurse) {
65 // get all the child nodes
66 for (EntryReference<BehaviorEntry, BehaviorEntry> reference : index.getBehaviorReferences(m_entry)) {
67 add(new BehaviorReferenceTreeNode(m_deobfuscatingTranslator, reference, index.getAccess(m_entry)));
68 }
69
70 if (recurse && children != null) {
71 for (Object child : children) {
72 if (child instanceof BehaviorReferenceTreeNode) {
73 BehaviorReferenceTreeNode node = (BehaviorReferenceTreeNode) child;
74
75 // don't recurse into ancestor
76 Set<Entry> ancestors = Sets.newHashSet();
77 TreeNode n = (TreeNode) node;
78 while (n.getParent() != null) {
79 n = n.getParent();
80 if (n instanceof BehaviorReferenceTreeNode) {
81 ancestors.add(((BehaviorReferenceTreeNode) n).getEntry());
82 }
83 }
84 if (ancestors.contains(node.getEntry())) {
85 continue;
86 }
87
88 node.load(index, true);
89 }
90 }
91 }
92 }
93}
diff --git a/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java b/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java
new file mode 100644
index 0000000..1df7625
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java
@@ -0,0 +1,43 @@
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 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import cuchaz.enigma.mapping.EntryFactory;
14import cuchaz.enigma.mapping.MethodEntry;
15import javassist.CtClass;
16import javassist.CtMethod;
17import javassist.bytecode.AccessFlag;
18
19public class BridgeMarker {
20
21 private JarIndex m_jarIndex;
22
23 public BridgeMarker(JarIndex jarIndex) {
24 m_jarIndex = jarIndex;
25 }
26
27 public void markBridges(CtClass c) {
28
29 for (CtMethod method : c.getDeclaredMethods()) {
30 MethodEntry methodEntry = EntryFactory.getMethodEntry(method);
31
32 // is this a bridge method?
33 MethodEntry bridgedMethodEntry = m_jarIndex.getBridgedMethod(methodEntry);
34 if (bridgedMethodEntry != null) {
35
36 // it's a bridge method! add the bridge flag
37 int flags = method.getMethodInfo().getAccessFlags();
38 flags |= AccessFlag.BRIDGE;
39 method.getMethodInfo().setAccessFlags(flags);
40 }
41 }
42 }
43}
diff --git a/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java
new file mode 100644
index 0000000..8f8986c
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java
@@ -0,0 +1,78 @@
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 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.google.common.collect.Lists;
14
15import java.util.List;
16
17import javax.swing.tree.DefaultMutableTreeNode;
18
19import cuchaz.enigma.mapping.ClassEntry;
20import cuchaz.enigma.mapping.MethodEntry;
21import cuchaz.enigma.mapping.Translator;
22
23public class ClassImplementationsTreeNode extends DefaultMutableTreeNode {
24
25 private static final long serialVersionUID = 3112703459157851912L;
26
27 private Translator m_deobfuscatingTranslator;
28 private ClassEntry m_entry;
29
30 public ClassImplementationsTreeNode(Translator deobfuscatingTranslator, ClassEntry entry) {
31 m_deobfuscatingTranslator = deobfuscatingTranslator;
32 m_entry = entry;
33 }
34
35 public ClassEntry getClassEntry() {
36 return m_entry;
37 }
38
39 public String getDeobfClassName() {
40 return m_deobfuscatingTranslator.translateClass(m_entry.getClassName());
41 }
42
43 @Override
44 public String toString() {
45 String className = getDeobfClassName();
46 if (className == null) {
47 className = m_entry.getClassName();
48 }
49 return className;
50 }
51
52 public void load(JarIndex index) {
53 // get all method implementations
54 List<ClassImplementationsTreeNode> nodes = Lists.newArrayList();
55 for (String implementingClassName : index.getImplementingClasses(m_entry.getClassName())) {
56 nodes.add(new ClassImplementationsTreeNode(m_deobfuscatingTranslator, new ClassEntry(implementingClassName)));
57 }
58
59 // add them to this node
60 nodes.forEach(this::add);
61 }
62
63 public static ClassImplementationsTreeNode findNode(ClassImplementationsTreeNode node, MethodEntry entry) {
64 // is this the node?
65 if (node.m_entry.equals(entry)) {
66 return node;
67 }
68
69 // recurse
70 for (int i = 0; i < node.getChildCount(); i++) {
71 ClassImplementationsTreeNode foundNode = findNode((ClassImplementationsTreeNode) node.getChildAt(i), entry);
72 if (foundNode != null) {
73 return foundNode;
74 }
75 }
76 return null;
77 }
78}
diff --git a/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java
new file mode 100644
index 0000000..ca2b821
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java
@@ -0,0 +1,83 @@
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 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.google.common.collect.Lists;
14
15import java.util.List;
16
17import javax.swing.tree.DefaultMutableTreeNode;
18
19import cuchaz.enigma.mapping.ClassEntry;
20import cuchaz.enigma.mapping.Translator;
21
22public class ClassInheritanceTreeNode extends DefaultMutableTreeNode {
23
24 private static final long serialVersionUID = 4432367405826178490L;
25
26 private Translator m_deobfuscatingTranslator;
27 private String m_obfClassName;
28
29 public ClassInheritanceTreeNode(Translator deobfuscatingTranslator, String obfClassName) {
30 m_deobfuscatingTranslator = deobfuscatingTranslator;
31 m_obfClassName = obfClassName;
32 }
33
34 public String getObfClassName() {
35 return m_obfClassName;
36 }
37
38 public String getDeobfClassName() {
39 return m_deobfuscatingTranslator.translateClass(m_obfClassName);
40 }
41
42 @Override
43 public String toString() {
44 String deobfClassName = getDeobfClassName();
45 if (deobfClassName != null) {
46 return deobfClassName;
47 }
48 return m_obfClassName;
49 }
50
51 public void load(TranslationIndex ancestries, boolean recurse) {
52 // get all the child nodes
53 List<ClassInheritanceTreeNode> nodes = Lists.newArrayList();
54 for (ClassEntry subclassEntry : ancestries.getSubclass(new ClassEntry(m_obfClassName))) {
55 nodes.add(new ClassInheritanceTreeNode(m_deobfuscatingTranslator, subclassEntry.getName()));
56 }
57
58 // add them to this node
59 nodes.forEach(this::add);
60
61 if (recurse) {
62 for (ClassInheritanceTreeNode node : nodes) {
63 node.load(ancestries, true);
64 }
65 }
66 }
67
68 public static ClassInheritanceTreeNode findNode(ClassInheritanceTreeNode node, ClassEntry entry) {
69 // is this the node?
70 if (node.getObfClassName().equals(entry.getName())) {
71 return node;
72 }
73
74 // recurse
75 for (int i = 0; i < node.getChildCount(); i++) {
76 ClassInheritanceTreeNode foundNode = findNode((ClassInheritanceTreeNode) node.getChildAt(i), entry);
77 if (foundNode != null) {
78 return foundNode;
79 }
80 }
81 return null;
82 }
83}
diff --git a/src/main/java/cuchaz/enigma/analysis/EntryReference.java b/src/main/java/cuchaz/enigma/analysis/EntryReference.java
new file mode 100644
index 0000000..eb58388
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/EntryReference.java
@@ -0,0 +1,126 @@
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 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.Arrays;
14import java.util.List;
15
16import cuchaz.enigma.Util;
17import cuchaz.enigma.mapping.ClassEntry;
18import cuchaz.enigma.mapping.ConstructorEntry;
19import cuchaz.enigma.mapping.Entry;
20
21public class EntryReference<E extends Entry, C extends Entry> {
22
23 private static final List<String> ConstructorNonNames = Arrays.asList("this", "super", "static");
24 public E entry;
25 public C context;
26
27 private boolean m_isNamed;
28
29 public EntryReference(E entry, String sourceName) {
30 this(entry, sourceName, null);
31 }
32
33 public EntryReference(E entry, String sourceName, C context) {
34 if (entry == null) {
35 throw new IllegalArgumentException("Entry cannot be null!");
36 }
37
38 this.entry = entry;
39 this.context = context;
40
41 m_isNamed = sourceName != null && sourceName.length() > 0;
42 if (entry instanceof ConstructorEntry && ConstructorNonNames.contains(sourceName)) {
43 m_isNamed = false;
44 }
45 }
46
47 public EntryReference(E entry, C context, EntryReference<E, C> other) {
48 this.entry = entry;
49 this.context = context;
50 m_isNamed = other.m_isNamed;
51 }
52
53 public ClassEntry getLocationClassEntry() {
54 if (context != null) {
55 return context.getClassEntry();
56 }
57 return entry.getClassEntry();
58 }
59
60 public boolean isNamed() {
61 return m_isNamed;
62 }
63
64 public Entry getNameableEntry() {
65 if (entry instanceof ConstructorEntry) {
66 // renaming a constructor really means renaming the class
67 return entry.getClassEntry();
68 }
69 return entry;
70 }
71
72 public String getNamableName() {
73 if (getNameableEntry() instanceof ClassEntry) {
74 ClassEntry classEntry = (ClassEntry) getNameableEntry();
75 if (classEntry.isInnerClass()) {
76 // make sure we only rename the inner class name
77 return classEntry.getInnermostClassName();
78 }
79 }
80
81 return getNameableEntry().getName();
82 }
83
84 @Override
85 public int hashCode() {
86 if (context != null) {
87 return Util.combineHashesOrdered(entry.hashCode(), context.hashCode());
88 }
89 return entry.hashCode();
90 }
91
92 @Override
93 public boolean equals(Object other) {
94 if (other instanceof EntryReference) {
95 return equals((EntryReference<?, ?>) other);
96 }
97 return false;
98 }
99
100 public boolean equals(EntryReference<?, ?> other) {
101 // check entry first
102 boolean isEntrySame = entry.equals(other.entry);
103 if (!isEntrySame) {
104 return false;
105 }
106
107 // check caller
108 if (context == null && other.context == null) {
109 return true;
110 } else if (context != null && other.context != null) {
111 return context.equals(other.context);
112 }
113 return false;
114 }
115
116 @Override
117 public String toString() {
118 StringBuilder buf = new StringBuilder();
119 buf.append(entry);
120 if (context != null) {
121 buf.append(" called from ");
122 buf.append(context);
123 }
124 return buf.toString();
125 }
126}
diff --git a/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java b/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java
new file mode 100644
index 0000000..b99537c
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java
@@ -0,0 +1,184 @@
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 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.google.common.collect.Lists;
14import com.google.common.collect.Multimap;
15import com.google.common.collect.Sets;
16
17import java.util.AbstractMap;
18import java.util.List;
19import java.util.Map;
20import java.util.Set;
21
22import cuchaz.enigma.mapping.*;
23
24public class EntryRenamer {
25
26 public static <T> void renameClassesInSet(Map<String, String> renames, Set<T> set) {
27 List<T> entries = Lists.newArrayList();
28 for (T val : set) {
29 entries.add(renameClassesInThing(renames, val));
30 }
31 set.clear();
32 set.addAll(entries);
33 }
34
35 public static <Key, Val> void renameClassesInMap(Map<String, String> renames, Map<Key, Val> map) {
36 // for each key/value pair...
37 Set<Map.Entry<Key, Val>> entriesToAdd = Sets.newHashSet();
38 for (Map.Entry<Key, Val> entry : map.entrySet()) {
39 entriesToAdd.add(new AbstractMap.SimpleEntry<Key, Val>(
40 renameClassesInThing(renames, entry.getKey()),
41 renameClassesInThing(renames, entry.getValue())
42 ));
43 }
44 map.clear();
45 for (Map.Entry<Key, Val> entry : entriesToAdd) {
46 map.put(entry.getKey(), entry.getValue());
47 }
48 }
49
50 public static <Key, Val> void renameClassesInMultimap(Map<String, String> renames, Multimap<Key, Val> map) {
51 // for each key/value pair...
52 Set<Map.Entry<Key, Val>> entriesToAdd = Sets.newHashSet();
53 for (Map.Entry<Key, Val> entry : map.entries()) {
54 entriesToAdd.add(new AbstractMap.SimpleEntry<Key, Val>(
55 renameClassesInThing(renames, entry.getKey()),
56 renameClassesInThing(renames, entry.getValue())
57 ));
58 }
59 map.clear();
60 for (Map.Entry<Key, Val> entry : entriesToAdd) {
61 map.put(entry.getKey(), entry.getValue());
62 }
63 }
64
65 public static <Key, Val> void renameMethodsInMultimap(Map<MethodEntry, MethodEntry> renames, Multimap<Key, Val> map) {
66 // for each key/value pair...
67 Set<Map.Entry<Key, Val>> entriesToAdd = Sets.newHashSet();
68 for (Map.Entry<Key, Val> entry : map.entries()) {
69 entriesToAdd.add(new AbstractMap.SimpleEntry<Key, Val>(
70 renameMethodsInThing(renames, entry.getKey()),
71 renameMethodsInThing(renames, entry.getValue())
72 ));
73 }
74 map.clear();
75 for (Map.Entry<Key, Val> entry : entriesToAdd) {
76 map.put(entry.getKey(), entry.getValue());
77 }
78 }
79
80 public static <Key, Val> void renameMethodsInMap(Map<MethodEntry, MethodEntry> renames, Map<Key, Val> map) {
81 // for each key/value pair...
82 Set<Map.Entry<Key, Val>> entriesToAdd = Sets.newHashSet();
83 for (Map.Entry<Key, Val> entry : map.entrySet()) {
84 entriesToAdd.add(new AbstractMap.SimpleEntry<Key, Val>(
85 renameMethodsInThing(renames, entry.getKey()),
86 renameMethodsInThing(renames, entry.getValue())
87 ));
88 }
89 map.clear();
90 for (Map.Entry<Key, Val> entry : entriesToAdd) {
91 map.put(entry.getKey(), entry.getValue());
92 }
93 }
94
95 @SuppressWarnings("unchecked")
96 public static <T> T renameMethodsInThing(Map<MethodEntry, MethodEntry> renames, T thing) {
97 if (thing instanceof MethodEntry) {
98 MethodEntry methodEntry = (MethodEntry) thing;
99 MethodEntry newMethodEntry = renames.get(methodEntry);
100 if (newMethodEntry != null) {
101 return (T) new MethodEntry(
102 methodEntry.getClassEntry(),
103 newMethodEntry.getName(),
104 methodEntry.getSignature()
105 );
106 }
107 return thing;
108 } else if (thing instanceof ArgumentEntry) {
109 ArgumentEntry argumentEntry = (ArgumentEntry) thing;
110 return (T) new ArgumentEntry(
111 renameMethodsInThing(renames, argumentEntry.getBehaviorEntry()),
112 argumentEntry.getIndex(),
113 argumentEntry.getName()
114 );
115 } else if (thing instanceof EntryReference) {
116 EntryReference<Entry, Entry> reference = (EntryReference<Entry, Entry>) thing;
117 reference.entry = renameMethodsInThing(renames, reference.entry);
118 reference.context = renameMethodsInThing(renames, reference.context);
119 return thing;
120 }
121 return thing;
122 }
123
124 @SuppressWarnings("unchecked")
125 public static <T> T renameClassesInThing(final Map<String, String> renames, T thing) {
126 if (thing instanceof String) {
127 String stringEntry = (String) thing;
128 if (renames.containsKey(stringEntry)) {
129 return (T) renames.get(stringEntry);
130 }
131 } else if (thing instanceof ClassEntry) {
132 ClassEntry classEntry = (ClassEntry) thing;
133 return (T) new ClassEntry(renameClassesInThing(renames, classEntry.getClassName()));
134 } else if (thing instanceof FieldEntry) {
135 FieldEntry fieldEntry = (FieldEntry) thing;
136 return (T) new FieldEntry(
137 renameClassesInThing(renames, fieldEntry.getClassEntry()),
138 fieldEntry.getName(),
139 renameClassesInThing(renames, fieldEntry.getType())
140 );
141 } else if (thing instanceof ConstructorEntry) {
142 ConstructorEntry constructorEntry = (ConstructorEntry) thing;
143 return (T) new ConstructorEntry(
144 renameClassesInThing(renames, constructorEntry.getClassEntry()),
145 renameClassesInThing(renames, constructorEntry.getSignature())
146 );
147 } else if (thing instanceof MethodEntry) {
148 MethodEntry methodEntry = (MethodEntry) thing;
149 return (T) new MethodEntry(
150 renameClassesInThing(renames, methodEntry.getClassEntry()),
151 methodEntry.getName(),
152 renameClassesInThing(renames, methodEntry.getSignature())
153 );
154 } else if (thing instanceof ArgumentEntry) {
155 ArgumentEntry argumentEntry = (ArgumentEntry) thing;
156 return (T) new ArgumentEntry(
157 renameClassesInThing(renames, argumentEntry.getBehaviorEntry()),
158 argumentEntry.getIndex(),
159 argumentEntry.getName()
160 );
161 } else if (thing instanceof EntryReference) {
162 EntryReference<Entry, Entry> reference = (EntryReference<Entry, Entry>) thing;
163 reference.entry = renameClassesInThing(renames, reference.entry);
164 reference.context = renameClassesInThing(renames, reference.context);
165 return thing;
166 } else if (thing instanceof Signature) {
167 return (T) new Signature((Signature) thing, new ClassNameReplacer() {
168 @Override
169 public String replace(String className) {
170 return renameClassesInThing(renames, className);
171 }
172 });
173 } else if (thing instanceof Type) {
174 return (T) new Type((Type) thing, new ClassNameReplacer() {
175 @Override
176 public String replace(String className) {
177 return renameClassesInThing(renames, className);
178 }
179 });
180 }
181
182 return thing;
183 }
184}
diff --git a/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java
new file mode 100644
index 0000000..4b302e0
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java
@@ -0,0 +1,81 @@
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 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import javax.swing.tree.DefaultMutableTreeNode;
14
15import cuchaz.enigma.mapping.BehaviorEntry;
16import cuchaz.enigma.mapping.FieldEntry;
17import cuchaz.enigma.mapping.Translator;
18
19public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode<FieldEntry, BehaviorEntry> {
20
21 private static final long serialVersionUID = -7934108091928699835L;
22
23 private Translator m_deobfuscatingTranslator;
24 private FieldEntry m_entry;
25 private EntryReference<FieldEntry, BehaviorEntry> m_reference;
26 private Access m_access;
27
28 public FieldReferenceTreeNode(Translator deobfuscatingTranslator, FieldEntry entry) {
29 m_deobfuscatingTranslator = deobfuscatingTranslator;
30 m_entry = entry;
31 m_reference = null;
32 }
33
34 private FieldReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference<FieldEntry, BehaviorEntry> reference, Access access) {
35 m_deobfuscatingTranslator = deobfuscatingTranslator;
36 m_entry = reference.entry;
37 m_reference = reference;
38 m_access = access;
39 }
40
41 @Override
42 public FieldEntry getEntry() {
43 return m_entry;
44 }
45
46 @Override
47 public EntryReference<FieldEntry, BehaviorEntry> getReference() {
48 return m_reference;
49 }
50
51 @Override
52 public String toString() {
53 if (m_reference != null) {
54 return String.format("%s (%s)", m_deobfuscatingTranslator.translateEntry(m_reference.context), m_access);
55 }
56 return m_deobfuscatingTranslator.translateEntry(m_entry).toString();
57 }
58
59 public void load(JarIndex index, boolean recurse) {
60 // get all the child nodes
61 if (m_reference == null) {
62 for (EntryReference<FieldEntry, BehaviorEntry> reference : index.getFieldReferences(m_entry)) {
63 add(new FieldReferenceTreeNode(m_deobfuscatingTranslator, reference, index.getAccess(m_entry)));
64 }
65 } else {
66 for (EntryReference<BehaviorEntry, BehaviorEntry> reference : index.getBehaviorReferences(m_reference.context)) {
67 add(new BehaviorReferenceTreeNode(m_deobfuscatingTranslator, reference, index.getAccess(m_reference.context)));
68 }
69 }
70
71 if (recurse && children != null) {
72 for (Object node : children) {
73 if (node instanceof BehaviorReferenceTreeNode) {
74 ((BehaviorReferenceTreeNode) node).load(index, true);
75 } else if (node instanceof FieldReferenceTreeNode) {
76 ((FieldReferenceTreeNode) node).load(index, true);
77 }
78 }
79 }
80 }
81}
diff --git a/src/main/java/cuchaz/enigma/analysis/JarClassIterator.java b/src/main/java/cuchaz/enigma/analysis/JarClassIterator.java
new file mode 100644
index 0000000..17a1715
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/JarClassIterator.java
@@ -0,0 +1,136 @@
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 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.google.common.collect.Lists;
14
15import java.io.ByteArrayOutputStream;
16import java.io.IOException;
17import java.io.InputStream;
18import java.util.Enumeration;
19import java.util.Iterator;
20import java.util.List;
21import java.util.jar.JarEntry;
22import java.util.jar.JarFile;
23
24import cuchaz.enigma.Constants;
25import cuchaz.enigma.mapping.ClassEntry;
26import javassist.ByteArrayClassPath;
27import javassist.ClassPool;
28import javassist.CtClass;
29import javassist.NotFoundException;
30import javassist.bytecode.Descriptor;
31
32public class JarClassIterator implements Iterator<CtClass> {
33
34 private JarFile m_jar;
35 private Iterator<JarEntry> m_iter;
36
37 public JarClassIterator(JarFile jar) {
38 m_jar = jar;
39
40 // get the jar entries that correspond to classes
41 List<JarEntry> classEntries = Lists.newArrayList();
42 Enumeration<JarEntry> entries = m_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 m_iter = classEntries.iterator();
52 }
53
54 @Override
55 public boolean hasNext() {
56 return m_iter.hasNext();
57 }
58
59 @Override
60 public CtClass next() {
61 JarEntry entry = m_iter.next();
62 try {
63 return getClass(m_jar, entry);
64 } catch (IOException | NotFoundException ex) {
65 throw new Error("Unable to load class: " + entry.getName());
66 }
67 }
68
69 @Override
70 public void remove() {
71 throw new UnsupportedOperationException();
72 }
73
74 public static List<ClassEntry> getClassEntries(JarFile jar) {
75 List<ClassEntry> classEntries = Lists.newArrayList();
76 Enumeration<JarEntry> entries = jar.entries();
77 while (entries.hasMoreElements()) {
78 JarEntry entry = entries.nextElement();
79
80 // is this a class file?
81 if (!entry.isDirectory() && entry.getName().endsWith(".class")) {
82 classEntries.add(getClassEntry(entry));
83 }
84 }
85 return classEntries;
86 }
87
88 public static Iterable<CtClass> classes(final JarFile jar) {
89 return new Iterable<CtClass>() {
90 @Override
91 public Iterator<CtClass> iterator() {
92 return new JarClassIterator(jar);
93 }
94 };
95 }
96
97 public static CtClass getClass(JarFile jar, ClassEntry classEntry) {
98 try {
99 return getClass(jar, new JarEntry(classEntry.getName() + ".class"));
100 } catch (IOException | NotFoundException ex) {
101 throw new Error("Unable to load class: " + classEntry.getName());
102 }
103 }
104
105 private static CtClass getClass(JarFile jar, JarEntry entry) throws IOException, NotFoundException {
106 // read the class into a buffer
107 ByteArrayOutputStream bos = new ByteArrayOutputStream();
108 byte[] buf = new byte[Constants.KiB];
109 int totalNumBytesRead = 0;
110 InputStream in = jar.getInputStream(entry);
111 while (in.available() > 0) {
112 int numBytesRead = in.read(buf);
113 if (numBytesRead < 0) {
114 break;
115 }
116 bos.write(buf, 0, numBytesRead);
117
118 // sanity checking
119 totalNumBytesRead += numBytesRead;
120 if (totalNumBytesRead > Constants.MiB) {
121 throw new Error("Class file " + entry.getName() + " larger than 1 MiB! Something is wrong!");
122 }
123 }
124
125 // get a javassist handle for the class
126 String className = Descriptor.toJavaName(getClassEntry(entry).getName());
127 ClassPool classPool = new ClassPool();
128 classPool.appendSystemPath();
129 classPool.insertClassPath(new ByteArrayClassPath(className, bos.toByteArray()));
130 return classPool.get(className);
131 }
132
133 private static ClassEntry getClassEntry(JarEntry entry) {
134 return new ClassEntry(entry.getName().substring(0, entry.getName().length() - ".class".length()));
135 }
136}
diff --git a/src/main/java/cuchaz/enigma/analysis/JarIndex.java b/src/main/java/cuchaz/enigma/analysis/JarIndex.java
new file mode 100644
index 0000000..848d851
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/JarIndex.java
@@ -0,0 +1,802 @@
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 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.google.common.collect.*;
14
15import java.lang.reflect.Modifier;
16import java.util.*;
17import java.util.jar.JarFile;
18
19import cuchaz.enigma.Constants;
20import cuchaz.enigma.bytecode.ClassRenamer;
21import cuchaz.enigma.mapping.*;
22import cuchaz.enigma.mapping.Translator;
23import javassist.*;
24import javassist.bytecode.*;
25import javassist.expr.*;
26
27public class JarIndex {
28
29 private Set<ClassEntry> m_obfClassEntries;
30 private TranslationIndex m_translationIndex;
31 private Map<Entry, Access> m_access;
32 private Multimap<ClassEntry, FieldEntry> m_fields;
33 private Multimap<ClassEntry, BehaviorEntry> m_behaviors;
34 private Multimap<String, MethodEntry> m_methodImplementations;
35 private Multimap<BehaviorEntry, EntryReference<BehaviorEntry, BehaviorEntry>> m_behaviorReferences;
36 private Multimap<FieldEntry, EntryReference<FieldEntry, BehaviorEntry>> m_fieldReferences;
37 private Multimap<ClassEntry, ClassEntry> m_innerClassesByOuter;
38 private Map<ClassEntry, ClassEntry> m_outerClassesByInner;
39 private Map<ClassEntry, BehaviorEntry> m_anonymousClasses;
40 private Map<MethodEntry, MethodEntry> m_bridgedMethods;
41
42 public JarIndex() {
43 m_obfClassEntries = Sets.newHashSet();
44 m_translationIndex = new TranslationIndex();
45 m_access = Maps.newHashMap();
46 m_fields = HashMultimap.create();
47 m_behaviors = HashMultimap.create();
48 m_methodImplementations = HashMultimap.create();
49 m_behaviorReferences = HashMultimap.create();
50 m_fieldReferences = HashMultimap.create();
51 m_innerClassesByOuter = HashMultimap.create();
52 m_outerClassesByInner = Maps.newHashMap();
53 m_anonymousClasses = Maps.newHashMap();
54 m_bridgedMethods = Maps.newHashMap();
55 }
56
57 public void indexJar(JarFile jar, boolean buildInnerClasses) {
58
59 // step 1: read the class names
60 for (ClassEntry classEntry : JarClassIterator.getClassEntries(jar)) {
61 if (classEntry.isInDefaultPackage()) {
62 // move out of default package
63 classEntry = new ClassEntry(Constants.NonePackage + "/" + classEntry.getName());
64 }
65 m_obfClassEntries.add(classEntry);
66 }
67
68 // step 2: index field/method/constructor access
69 for (CtClass c : JarClassIterator.classes(jar)) {
70 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
71 for (CtField field : c.getDeclaredFields()) {
72 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
73 m_access.put(fieldEntry, Access.get(field));
74 m_fields.put(fieldEntry.getClassEntry(), fieldEntry);
75 }
76 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
77 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
78 m_access.put(behaviorEntry, Access.get(behavior));
79 m_behaviors.put(behaviorEntry.getClassEntry(), behaviorEntry);
80 }
81 }
82
83 // step 3: index extends, implements, fields, and methods
84 for (CtClass c : JarClassIterator.classes(jar)) {
85 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
86 m_translationIndex.indexClass(c);
87 String className = Descriptor.toJvmName(c.getName());
88 for (String interfaceName : c.getClassFile().getInterfaces()) {
89 className = Descriptor.toJvmName(className);
90 interfaceName = Descriptor.toJvmName(interfaceName);
91 if (className.equals(interfaceName)) {
92 throw new IllegalArgumentException("Class cannot be its own interface! " + className);
93 }
94 }
95 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
96 indexBehavior(behavior);
97 }
98 }
99
100 // step 4: index field, method, constructor references
101 for (CtClass c : JarClassIterator.classes(jar)) {
102 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
103 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
104 indexBehaviorReferences(behavior);
105 }
106 }
107
108 if (buildInnerClasses) {
109
110 // step 5: index inner classes and anonymous classes
111 for (CtClass c : JarClassIterator.classes(jar)) {
112 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
113 ClassEntry innerClassEntry = EntryFactory.getClassEntry(c);
114 ClassEntry outerClassEntry = findOuterClass(c);
115 if (outerClassEntry != null) {
116 m_innerClassesByOuter.put(outerClassEntry, innerClassEntry);
117 boolean innerWasAdded = m_outerClassesByInner.put(innerClassEntry, outerClassEntry) == null;
118 assert (innerWasAdded);
119
120 BehaviorEntry enclosingBehavior = isAnonymousClass(c, outerClassEntry);
121 if (enclosingBehavior != null) {
122 m_anonymousClasses.put(innerClassEntry, enclosingBehavior);
123
124 // DEBUG
125 //System.out.println("ANONYMOUS: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName());
126 } else {
127 // DEBUG
128 //System.out.println("INNER: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName());
129 }
130 }
131 }
132
133 // step 6: update other indices with inner class info
134 Map<String, String> renames = Maps.newHashMap();
135 for (ClassEntry innerClassEntry : m_innerClassesByOuter.values()) {
136 String newName = innerClassEntry.buildClassEntry(getObfClassChain(innerClassEntry)).getName();
137 if (!innerClassEntry.getName().equals(newName)) {
138 // DEBUG
139 //System.out.println("REPLACE: " + innerClassEntry.getName() + " WITH " + newName);
140 renames.put(innerClassEntry.getName(), newName);
141 }
142 }
143 EntryRenamer.renameClassesInSet(renames, m_obfClassEntries);
144 m_translationIndex.renameClasses(renames);
145 EntryRenamer.renameClassesInMultimap(renames, m_methodImplementations);
146 EntryRenamer.renameClassesInMultimap(renames, m_behaviorReferences);
147 EntryRenamer.renameClassesInMultimap(renames, m_fieldReferences);
148 EntryRenamer.renameClassesInMap(renames, m_access);
149 }
150 }
151
152 private void indexBehavior(CtBehavior behavior) {
153 // get the behavior entry
154 final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
155 if (behaviorEntry instanceof MethodEntry) {
156 MethodEntry methodEntry = (MethodEntry) behaviorEntry;
157
158 // index implementation
159 m_methodImplementations.put(behaviorEntry.getClassName(), methodEntry);
160
161 // look for bridge and bridged methods
162 CtMethod bridgedMethod = getBridgedMethod((CtMethod) behavior);
163 if (bridgedMethod != null) {
164 m_bridgedMethods.put(methodEntry, EntryFactory.getMethodEntry(bridgedMethod));
165 }
166 }
167 // looks like we don't care about constructors here
168 }
169
170 private void indexBehaviorReferences(CtBehavior behavior) {
171 // index method calls
172 final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
173 try {
174 behavior.instrument(new ExprEditor() {
175 @Override
176 public void edit(MethodCall call) {
177 MethodEntry calledMethodEntry = EntryFactory.getMethodEntry(call);
178 ClassEntry resolvedClassEntry = m_translationIndex.resolveEntryClass(calledMethodEntry);
179 if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledMethodEntry.getClassEntry())) {
180 calledMethodEntry = new MethodEntry(
181 resolvedClassEntry,
182 calledMethodEntry.getName(),
183 calledMethodEntry.getSignature()
184 );
185 }
186 EntryReference<BehaviorEntry, BehaviorEntry> reference = new EntryReference<BehaviorEntry, BehaviorEntry>(
187 calledMethodEntry,
188 call.getMethodName(),
189 behaviorEntry
190 );
191 m_behaviorReferences.put(calledMethodEntry, reference);
192 }
193
194 @Override
195 public void edit(FieldAccess call) {
196 FieldEntry calledFieldEntry = EntryFactory.getFieldEntry(call);
197 ClassEntry resolvedClassEntry = m_translationIndex.resolveEntryClass(calledFieldEntry);
198 if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledFieldEntry.getClassEntry())) {
199 calledFieldEntry = new FieldEntry(calledFieldEntry, resolvedClassEntry);
200 }
201 EntryReference<FieldEntry, BehaviorEntry> reference = new EntryReference<FieldEntry, BehaviorEntry>(
202 calledFieldEntry,
203 call.getFieldName(),
204 behaviorEntry
205 );
206 m_fieldReferences.put(calledFieldEntry, reference);
207 }
208
209 @Override
210 public void edit(ConstructorCall call) {
211 ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call);
212 EntryReference<BehaviorEntry, BehaviorEntry> reference = new EntryReference<BehaviorEntry, BehaviorEntry>(
213 calledConstructorEntry,
214 call.getMethodName(),
215 behaviorEntry
216 );
217 m_behaviorReferences.put(calledConstructorEntry, reference);
218 }
219
220 @Override
221 public void edit(NewExpr call) {
222 ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call);
223 EntryReference<BehaviorEntry, BehaviorEntry> reference = new EntryReference<BehaviorEntry, BehaviorEntry>(
224 calledConstructorEntry,
225 call.getClassName(),
226 behaviorEntry
227 );
228 m_behaviorReferences.put(calledConstructorEntry, reference);
229 }
230 });
231 } catch (CannotCompileException ex) {
232 throw new Error(ex);
233 }
234 }
235
236 private CtMethod getBridgedMethod(CtMethod method) {
237
238 // bridge methods just call another method, cast it to the return type, and return the result
239 // let's see if we can detect this scenario
240
241 // skip non-synthetic methods
242 if ((method.getModifiers() & AccessFlag.SYNTHETIC) == 0) {
243 return null;
244 }
245
246 // get all the called methods
247 final List<MethodCall> methodCalls = Lists.newArrayList();
248 try {
249 method.instrument(new ExprEditor() {
250 @Override
251 public void edit(MethodCall call) {
252 methodCalls.add(call);
253 }
254 });
255 } catch (CannotCompileException ex) {
256 // this is stupid... we're not even compiling anything
257 throw new Error(ex);
258 }
259
260 // is there just one?
261 if (methodCalls.size() != 1) {
262 return null;
263 }
264 MethodCall call = methodCalls.get(0);
265
266 try {
267 // we have a bridge method!
268 return call.getMethod();
269 } catch (NotFoundException ex) {
270 // can't find the type? not a bridge method
271 return null;
272 }
273 }
274
275 private ClassEntry findOuterClass(CtClass c) {
276
277 ClassEntry classEntry = EntryFactory.getClassEntry(c);
278
279 // does this class already have an outer class?
280 if (classEntry.isInnerClass()) {
281 return classEntry.getOuterClassEntry();
282 }
283
284 // inner classes:
285 // have constructors that can (illegally) set synthetic fields
286 // the outer class is the only class that calls constructors
287
288 // use the synthetic fields to find the synthetic constructors
289 for (CtConstructor constructor : c.getDeclaredConstructors()) {
290 Set<String> syntheticFieldTypes = Sets.newHashSet();
291 if (!isIllegalConstructor(syntheticFieldTypes, constructor)) {
292 continue;
293 }
294
295 ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor);
296
297 // gather the classes from the illegally-set synthetic fields
298 Set<ClassEntry> illegallySetClasses = Sets.newHashSet();
299 for (String type : syntheticFieldTypes) {
300 if (type.startsWith("L")) {
301 ClassEntry outerClassEntry = new ClassEntry(type.substring(1, type.length() - 1));
302 if (isSaneOuterClass(outerClassEntry, classEntry)) {
303 illegallySetClasses.add(outerClassEntry);
304 }
305 }
306 }
307
308 // who calls this constructor?
309 Set<ClassEntry> callerClasses = Sets.newHashSet();
310 for (EntryReference<BehaviorEntry, BehaviorEntry> reference : getBehaviorReferences(constructorEntry)) {
311
312 // make sure it's not a call to super
313 if (reference.entry instanceof ConstructorEntry && reference.context instanceof ConstructorEntry) {
314
315 // is the entry a superclass of the context?
316 ClassEntry calledClassEntry = reference.entry.getClassEntry();
317 ClassEntry superclassEntry = m_translationIndex.getSuperclass(reference.context.getClassEntry());
318 if (superclassEntry != null && superclassEntry.equals(calledClassEntry)) {
319 // it's a super call, skip
320 continue;
321 }
322 }
323
324 if (isSaneOuterClass(reference.context.getClassEntry(), classEntry)) {
325 callerClasses.add(reference.context.getClassEntry());
326 }
327 }
328
329 // do we have an answer yet?
330 if (callerClasses.isEmpty()) {
331 if (illegallySetClasses.size() == 1) {
332 return illegallySetClasses.iterator().next();
333 } else {
334 System.out.println(String.format("WARNING: Unable to find outer class for %s. No caller and no illegally set field classes.", classEntry));
335 }
336 } else {
337 if (callerClasses.size() == 1) {
338 return callerClasses.iterator().next();
339 } else {
340 // multiple callers, do the illegally set classes narrow it down?
341 Set<ClassEntry> intersection = Sets.newHashSet(callerClasses);
342 intersection.retainAll(illegallySetClasses);
343 if (intersection.size() == 1) {
344 return intersection.iterator().next();
345 } else {
346 System.out.println(String.format("WARNING: Unable to choose outer class for %s among options: %s", classEntry, callerClasses));
347 }
348 }
349 }
350 }
351
352 return null;
353 }
354
355 private boolean isSaneOuterClass(ClassEntry outerClassEntry, ClassEntry innerClassEntry) {
356
357 // clearly this would be silly
358 if (outerClassEntry.equals(innerClassEntry)) {
359 return false;
360 }
361
362 // is the outer class in the jar?
363 return m_obfClassEntries.contains(outerClassEntry);
364
365 }
366
367 @SuppressWarnings("unchecked")
368 private boolean isIllegalConstructor(Set<String> syntheticFieldTypes, CtConstructor constructor) {
369
370 // illegal constructors only set synthetic member fields, then call super()
371 String className = constructor.getDeclaringClass().getName();
372
373 // collect all the field accesses, constructor calls, and method calls
374 final List<FieldAccess> illegalFieldWrites = Lists.newArrayList();
375 final List<ConstructorCall> constructorCalls = Lists.newArrayList();
376 try {
377 constructor.instrument(new ExprEditor() {
378 @Override
379 public void edit(FieldAccess fieldAccess) {
380 if (fieldAccess.isWriter() && constructorCalls.isEmpty()) {
381 illegalFieldWrites.add(fieldAccess);
382 }
383 }
384
385 @Override
386 public void edit(ConstructorCall constructorCall) {
387 constructorCalls.add(constructorCall);
388 }
389 });
390 } catch (CannotCompileException ex) {
391 // we're not compiling anything... this is stupid
392 throw new Error(ex);
393 }
394
395 // are there any illegal field writes?
396 if (illegalFieldWrites.isEmpty()) {
397 return false;
398 }
399
400 // are all the writes to synthetic fields?
401 for (FieldAccess fieldWrite : illegalFieldWrites) {
402
403 // all illegal writes have to be to the local class
404 if (!fieldWrite.getClassName().equals(className)) {
405 System.err.println(String.format("WARNING: illegal write to non-member field %s.%s", fieldWrite.getClassName(), fieldWrite.getFieldName()));
406 return false;
407 }
408
409 // find the field
410 FieldInfo fieldInfo = null;
411 for (FieldInfo info : (List<FieldInfo>) constructor.getDeclaringClass().getClassFile().getFields()) {
412 if (info.getName().equals(fieldWrite.getFieldName()) && info.getDescriptor().equals(fieldWrite.getSignature())) {
413 fieldInfo = info;
414 break;
415 }
416 }
417 if (fieldInfo == null) {
418 // field is in a superclass or something, can't be a local synthetic member
419 return false;
420 }
421
422 // is this field synthetic?
423 boolean isSynthetic = (fieldInfo.getAccessFlags() & AccessFlag.SYNTHETIC) != 0;
424 if (isSynthetic) {
425 syntheticFieldTypes.add(fieldInfo.getDescriptor());
426 } else {
427 System.err.println(String.format("WARNING: illegal write to non synthetic field %s %s.%s", fieldInfo.getDescriptor(), className, fieldInfo.getName()));
428 return false;
429 }
430 }
431
432 // we passed all the tests!
433 return true;
434 }
435
436 private BehaviorEntry isAnonymousClass(CtClass c, ClassEntry outerClassEntry) {
437
438 // is this class already marked anonymous?
439 EnclosingMethodAttribute enclosingMethodAttribute = (EnclosingMethodAttribute) c.getClassFile().getAttribute(EnclosingMethodAttribute.tag);
440 if (enclosingMethodAttribute != null) {
441 if (enclosingMethodAttribute.methodIndex() > 0) {
442 return EntryFactory.getBehaviorEntry(
443 Descriptor.toJvmName(enclosingMethodAttribute.className()),
444 enclosingMethodAttribute.methodName(),
445 enclosingMethodAttribute.methodDescriptor()
446 );
447 } else {
448 // an attribute but no method? assume not anonymous
449 return null;
450 }
451 }
452
453 // if there's an inner class attribute, but not an enclosing method attribute, then it's not anonymous
454 InnerClassesAttribute innerClassesAttribute = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag);
455 if (innerClassesAttribute != null) {
456 return null;
457 }
458
459 ClassEntry innerClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
460
461 // anonymous classes:
462 // can't be abstract
463 // have only one constructor
464 // it's called exactly once by the outer class
465 // the type the instance is assigned to can't be this type
466
467 // is abstract?
468 if (Modifier.isAbstract(c.getModifiers())) {
469 return null;
470 }
471
472 // is there exactly one constructor?
473 if (c.getDeclaredConstructors().length != 1) {
474 return null;
475 }
476 CtConstructor constructor = c.getDeclaredConstructors()[0];
477
478 // is this constructor called exactly once?
479 ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor);
480 Collection<EntryReference<BehaviorEntry, BehaviorEntry>> references = getBehaviorReferences(constructorEntry);
481 if (references.size() != 1) {
482 return null;
483 }
484
485 // does the caller use this type?
486 BehaviorEntry caller = references.iterator().next().context;
487 for (FieldEntry fieldEntry : getReferencedFields(caller)) {
488 if (fieldEntry.getType().hasClass() && fieldEntry.getType().getClassEntry().equals(innerClassEntry)) {
489 // caller references this type, so it can't be anonymous
490 return null;
491 }
492 }
493 for (BehaviorEntry behaviorEntry : getReferencedBehaviors(caller)) {
494 if (behaviorEntry.getSignature().hasClass(innerClassEntry)) {
495 return null;
496 }
497 }
498
499 return caller;
500 }
501
502 public Set<ClassEntry> getObfClassEntries() {
503 return m_obfClassEntries;
504 }
505
506 public Collection<FieldEntry> getObfFieldEntries() {
507 return m_fields.values();
508 }
509
510 public Collection<FieldEntry> getObfFieldEntries(ClassEntry classEntry) {
511 return m_fields.get(classEntry);
512 }
513
514 public Collection<BehaviorEntry> getObfBehaviorEntries() {
515 return m_behaviors.values();
516 }
517
518 public Collection<BehaviorEntry> getObfBehaviorEntries(ClassEntry classEntry) {
519 return m_behaviors.get(classEntry);
520 }
521
522 public TranslationIndex getTranslationIndex() {
523 return m_translationIndex;
524 }
525
526 public Access getAccess(Entry entry) {
527 return m_access.get(entry);
528 }
529
530 public ClassInheritanceTreeNode getClassInheritance(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) {
531
532 // get the root node
533 List<String> ancestry = Lists.newArrayList();
534 ancestry.add(obfClassEntry.getName());
535 for (ClassEntry classEntry : m_translationIndex.getAncestry(obfClassEntry)) {
536 if (containsObfClass(classEntry)) {
537 ancestry.add(classEntry.getName());
538 }
539 }
540 ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode(
541 deobfuscatingTranslator,
542 ancestry.get(ancestry.size() - 1)
543 );
544
545 // expand all children recursively
546 rootNode.load(m_translationIndex, true);
547
548 return rootNode;
549 }
550
551 public ClassImplementationsTreeNode getClassImplementations(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) {
552
553 // is this even an interface?
554 if (isInterface(obfClassEntry.getClassName())) {
555 ClassImplementationsTreeNode node = new ClassImplementationsTreeNode(deobfuscatingTranslator, obfClassEntry);
556 node.load(this);
557 return node;
558 }
559 return null;
560 }
561
562 public MethodInheritanceTreeNode getMethodInheritance(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) {
563
564 // travel to the ancestor implementation
565 ClassEntry baseImplementationClassEntry = obfMethodEntry.getClassEntry();
566 for (ClassEntry ancestorClassEntry : m_translationIndex.getAncestry(obfMethodEntry.getClassEntry())) {
567 MethodEntry ancestorMethodEntry = new MethodEntry(
568 new ClassEntry(ancestorClassEntry),
569 obfMethodEntry.getName(),
570 obfMethodEntry.getSignature()
571 );
572 if (containsObfBehavior(ancestorMethodEntry)) {
573 baseImplementationClassEntry = ancestorClassEntry;
574 }
575 }
576
577 // make a root node at the base
578 MethodEntry methodEntry = new MethodEntry(
579 baseImplementationClassEntry,
580 obfMethodEntry.getName(),
581 obfMethodEntry.getSignature()
582 );
583 MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode(
584 deobfuscatingTranslator,
585 methodEntry,
586 containsObfBehavior(methodEntry)
587 );
588
589 // expand the full tree
590 rootNode.load(this, true);
591
592 return rootNode;
593 }
594
595 public List<MethodImplementationsTreeNode> getMethodImplementations(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) {
596
597 List<MethodEntry> interfaceMethodEntries = Lists.newArrayList();
598
599 // is this method on an interface?
600 if (isInterface(obfMethodEntry.getClassName())) {
601 interfaceMethodEntries.add(obfMethodEntry);
602 } else {
603 // get the interface class
604 for (ClassEntry interfaceEntry : getInterfaces(obfMethodEntry.getClassName())) {
605
606 // is this method defined in this interface?
607 MethodEntry methodInterface = new MethodEntry(
608 interfaceEntry,
609 obfMethodEntry.getName(),
610 obfMethodEntry.getSignature()
611 );
612 if (containsObfBehavior(methodInterface)) {
613 interfaceMethodEntries.add(methodInterface);
614 }
615 }
616 }
617
618 List<MethodImplementationsTreeNode> nodes = Lists.newArrayList();
619 if (!interfaceMethodEntries.isEmpty()) {
620 for (MethodEntry interfaceMethodEntry : interfaceMethodEntries) {
621 MethodImplementationsTreeNode node = new MethodImplementationsTreeNode(deobfuscatingTranslator, interfaceMethodEntry);
622 node.load(this);
623 nodes.add(node);
624 }
625 }
626 return nodes;
627 }
628
629 public Set<MethodEntry> getRelatedMethodImplementations(MethodEntry obfMethodEntry) {
630 Set<MethodEntry> methodEntries = Sets.newHashSet();
631 getRelatedMethodImplementations(methodEntries, getMethodInheritance(null, obfMethodEntry));
632 return methodEntries;
633 }
634
635 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodInheritanceTreeNode node) {
636 MethodEntry methodEntry = node.getMethodEntry();
637 if (containsObfBehavior(methodEntry)) {
638 // collect the entry
639 methodEntries.add(methodEntry);
640 }
641
642 // look at interface methods too
643 for (MethodImplementationsTreeNode implementationsNode : getMethodImplementations(null, methodEntry)) {
644 getRelatedMethodImplementations(methodEntries, implementationsNode);
645 }
646
647 // recurse
648 for (int i = 0; i < node.getChildCount(); i++) {
649 getRelatedMethodImplementations(methodEntries, (MethodInheritanceTreeNode) node.getChildAt(i));
650 }
651 }
652
653 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodImplementationsTreeNode node) {
654 MethodEntry methodEntry = node.getMethodEntry();
655 if (containsObfBehavior(methodEntry)) {
656 // collect the entry
657 methodEntries.add(methodEntry);
658 }
659
660 // recurse
661 for (int i = 0; i < node.getChildCount(); i++) {
662 getRelatedMethodImplementations(methodEntries, (MethodImplementationsTreeNode) node.getChildAt(i));
663 }
664 }
665
666 public Collection<EntryReference<FieldEntry, BehaviorEntry>> getFieldReferences(FieldEntry fieldEntry) {
667 return m_fieldReferences.get(fieldEntry);
668 }
669
670 public Collection<FieldEntry> getReferencedFields(BehaviorEntry behaviorEntry) {
671 // linear search is fast enough for now
672 Set<FieldEntry> fieldEntries = Sets.newHashSet();
673 for (EntryReference<FieldEntry, BehaviorEntry> reference : m_fieldReferences.values()) {
674 if (reference.context == behaviorEntry) {
675 fieldEntries.add(reference.entry);
676 }
677 }
678 return fieldEntries;
679 }
680
681 public Collection<EntryReference<BehaviorEntry, BehaviorEntry>> getBehaviorReferences(BehaviorEntry behaviorEntry) {
682 return m_behaviorReferences.get(behaviorEntry);
683 }
684
685 public Collection<BehaviorEntry> getReferencedBehaviors(BehaviorEntry behaviorEntry) {
686 // linear search is fast enough for now
687 Set<BehaviorEntry> behaviorEntries = Sets.newHashSet();
688 for (EntryReference<BehaviorEntry, BehaviorEntry> reference : m_behaviorReferences.values()) {
689 if (reference.context == behaviorEntry) {
690 behaviorEntries.add(reference.entry);
691 }
692 }
693 return behaviorEntries;
694 }
695
696 public Collection<ClassEntry> getInnerClasses(ClassEntry obfOuterClassEntry) {
697 return m_innerClassesByOuter.get(obfOuterClassEntry);
698 }
699
700 public ClassEntry getOuterClass(ClassEntry obfInnerClassEntry) {
701 return m_outerClassesByInner.get(obfInnerClassEntry);
702 }
703
704 public boolean isAnonymousClass(ClassEntry obfInnerClassEntry) {
705 return m_anonymousClasses.containsKey(obfInnerClassEntry);
706 }
707
708 public BehaviorEntry getAnonymousClassCaller(ClassEntry obfInnerClassName) {
709 return m_anonymousClasses.get(obfInnerClassName);
710 }
711
712 public Set<ClassEntry> getInterfaces(String className) {
713 ClassEntry classEntry = new ClassEntry(className);
714 Set<ClassEntry> interfaces = new HashSet<ClassEntry>();
715 interfaces.addAll(m_translationIndex.getInterfaces(classEntry));
716 for (ClassEntry ancestor : m_translationIndex.getAncestry(classEntry)) {
717 interfaces.addAll(m_translationIndex.getInterfaces(ancestor));
718 }
719 return interfaces;
720 }
721
722 public Set<String> getImplementingClasses(String targetInterfaceName) {
723
724 // linear search is fast enough for now
725 Set<String> classNames = Sets.newHashSet();
726 for (Map.Entry<ClassEntry, ClassEntry> entry : m_translationIndex.getClassInterfaces()) {
727 ClassEntry classEntry = entry.getKey();
728 ClassEntry interfaceEntry = entry.getValue();
729 if (interfaceEntry.getName().equals(targetInterfaceName)) {
730 classNames.add(classEntry.getClassName());
731 m_translationIndex.getSubclassNamesRecursively(classNames, classEntry);
732 }
733 }
734 return classNames;
735 }
736
737 public boolean isInterface(String className) {
738 return m_translationIndex.isInterface(new ClassEntry(className));
739 }
740
741 public boolean containsObfClass(ClassEntry obfClassEntry) {
742 return m_obfClassEntries.contains(obfClassEntry);
743 }
744
745 public boolean containsObfField(FieldEntry obfFieldEntry) {
746 return m_access.containsKey(obfFieldEntry);
747 }
748
749 public boolean containsObfBehavior(BehaviorEntry obfBehaviorEntry) {
750 return m_access.containsKey(obfBehaviorEntry);
751 }
752
753 public boolean containsObfArgument(ArgumentEntry obfArgumentEntry) {
754 // check the behavior
755 if (!containsObfBehavior(obfArgumentEntry.getBehaviorEntry())) {
756 return false;
757 }
758
759 // check the argument
760 return obfArgumentEntry.getIndex() < obfArgumentEntry.getBehaviorEntry().getSignature().getArgumentTypes().size();
761
762 }
763
764 public boolean containsObfEntry(Entry obfEntry) {
765 if (obfEntry instanceof ClassEntry) {
766 return containsObfClass((ClassEntry) obfEntry);
767 } else if (obfEntry instanceof FieldEntry) {
768 return containsObfField((FieldEntry) obfEntry);
769 } else if (obfEntry instanceof BehaviorEntry) {
770 return containsObfBehavior((BehaviorEntry) obfEntry);
771 } else if (obfEntry instanceof ArgumentEntry) {
772 return containsObfArgument((ArgumentEntry) obfEntry);
773 } else {
774 throw new Error("Entry type not supported: " + obfEntry.getClass().getName());
775 }
776 }
777
778 public MethodEntry getBridgedMethod(MethodEntry bridgeMethodEntry) {
779 return m_bridgedMethods.get(bridgeMethodEntry);
780 }
781
782 public List<ClassEntry> getObfClassChain(ClassEntry obfClassEntry) {
783
784 // build class chain in inner-to-outer order
785 List<ClassEntry> obfClassChain = Lists.newArrayList(obfClassEntry);
786 ClassEntry checkClassEntry = obfClassEntry;
787 while (true) {
788 ClassEntry obfOuterClassEntry = getOuterClass(checkClassEntry);
789 if (obfOuterClassEntry != null) {
790 obfClassChain.add(obfOuterClassEntry);
791 checkClassEntry = obfOuterClassEntry;
792 } else {
793 break;
794 }
795 }
796
797 // switch to outer-to-inner order
798 Collections.reverse(obfClassChain);
799
800 return obfClassChain;
801 }
802}
diff --git a/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java b/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java
new file mode 100644
index 0000000..2ee3ec1
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java
@@ -0,0 +1,101 @@
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 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.google.common.collect.Lists;
14
15import java.util.List;
16
17import javax.swing.tree.DefaultMutableTreeNode;
18
19import cuchaz.enigma.mapping.ClassEntry;
20import cuchaz.enigma.mapping.MethodEntry;
21import cuchaz.enigma.mapping.Translator;
22
23public class MethodImplementationsTreeNode extends DefaultMutableTreeNode {
24
25 private static final long serialVersionUID = 3781080657461899915L;
26
27 private Translator m_deobfuscatingTranslator;
28 private MethodEntry m_entry;
29
30 public MethodImplementationsTreeNode(Translator deobfuscatingTranslator, MethodEntry entry) {
31 if (entry == null) {
32 throw new IllegalArgumentException("entry cannot be null!");
33 }
34
35 m_deobfuscatingTranslator = deobfuscatingTranslator;
36 m_entry = entry;
37 }
38
39 public MethodEntry getMethodEntry() {
40 return m_entry;
41 }
42
43 public String getDeobfClassName() {
44 return m_deobfuscatingTranslator.translateClass(m_entry.getClassName());
45 }
46
47 public String getDeobfMethodName() {
48 return m_deobfuscatingTranslator.translate(m_entry);
49 }
50
51 @Override
52 public String toString() {
53 String className = getDeobfClassName();
54 if (className == null) {
55 className = m_entry.getClassName();
56 }
57
58 String methodName = getDeobfMethodName();
59 if (methodName == null) {
60 methodName = m_entry.getName();
61 }
62 return className + "." + methodName + "()";
63 }
64
65 public void load(JarIndex index) {
66
67 // get all method implementations
68 List<MethodImplementationsTreeNode> nodes = Lists.newArrayList();
69 for (String implementingClassName : index.getImplementingClasses(m_entry.getClassName())) {
70 MethodEntry methodEntry = new MethodEntry(
71 new ClassEntry(implementingClassName),
72 m_entry.getName(),
73 m_entry.getSignature()
74 );
75 if (index.containsObfBehavior(methodEntry)) {
76 nodes.add(new MethodImplementationsTreeNode(m_deobfuscatingTranslator, methodEntry));
77 }
78 }
79
80 // add them to this node
81 for (MethodImplementationsTreeNode node : nodes) {
82 this.add(node);
83 }
84 }
85
86 public static MethodImplementationsTreeNode findNode(MethodImplementationsTreeNode node, MethodEntry entry) {
87 // is this the node?
88 if (node.getMethodEntry().equals(entry)) {
89 return node;
90 }
91
92 // recurse
93 for (int i = 0; i < node.getChildCount(); i++) {
94 MethodImplementationsTreeNode foundNode = findNode((MethodImplementationsTreeNode) node.getChildAt(i), entry);
95 if (foundNode != null) {
96 return foundNode;
97 }
98 }
99 return null;
100 }
101}
diff --git a/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java
new file mode 100644
index 0000000..cf42ac7
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java
@@ -0,0 +1,114 @@
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 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.google.common.collect.Lists;
14
15import java.util.List;
16
17import javax.swing.tree.DefaultMutableTreeNode;
18
19import cuchaz.enigma.mapping.ClassEntry;
20import cuchaz.enigma.mapping.MethodEntry;
21import cuchaz.enigma.mapping.Translator;
22
23public class MethodInheritanceTreeNode extends DefaultMutableTreeNode {
24
25 private static final long serialVersionUID = 1096677030991810007L;
26
27 private Translator m_deobfuscatingTranslator;
28 private MethodEntry m_entry;
29 private boolean m_isImplemented;
30
31 public MethodInheritanceTreeNode(Translator deobfuscatingTranslator, MethodEntry entry, boolean isImplemented) {
32 m_deobfuscatingTranslator = deobfuscatingTranslator;
33 m_entry = entry;
34 m_isImplemented = isImplemented;
35 }
36
37 public MethodEntry getMethodEntry() {
38 return m_entry;
39 }
40
41 public String getDeobfClassName() {
42 return m_deobfuscatingTranslator.translateClass(m_entry.getClassName());
43 }
44
45 public String getDeobfMethodName() {
46 return m_deobfuscatingTranslator.translate(m_entry);
47 }
48
49 public boolean isImplemented() {
50 return m_isImplemented;
51 }
52
53 @Override
54 public String toString() {
55 String className = getDeobfClassName();
56 if (className == null) {
57 className = m_entry.getClassName();
58 }
59
60 if (!m_isImplemented) {
61 return className;
62 } else {
63 String methodName = getDeobfMethodName();
64 if (methodName == null) {
65 methodName = m_entry.getName();
66 }
67 return className + "." + methodName + "()";
68 }
69 }
70
71 public void load(JarIndex index, boolean recurse) {
72 // get all the child nodes
73 List<MethodInheritanceTreeNode> nodes = Lists.newArrayList();
74 for (ClassEntry subclassEntry : index.getTranslationIndex().getSubclass(m_entry.getClassEntry())) {
75 MethodEntry methodEntry = new MethodEntry(
76 subclassEntry,
77 m_entry.getName(),
78 m_entry.getSignature()
79 );
80 nodes.add(new MethodInheritanceTreeNode(
81 m_deobfuscatingTranslator,
82 methodEntry,
83 index.containsObfBehavior(methodEntry)
84 ));
85 }
86
87 // add them to this node
88 for (MethodInheritanceTreeNode node : nodes) {
89 this.add(node);
90 }
91
92 if (recurse) {
93 for (MethodInheritanceTreeNode node : nodes) {
94 node.load(index, true);
95 }
96 }
97 }
98
99 public static MethodInheritanceTreeNode findNode(MethodInheritanceTreeNode node, MethodEntry entry) {
100 // is this the node?
101 if (node.getMethodEntry().equals(entry)) {
102 return node;
103 }
104
105 // recurse
106 for (int i = 0; i < node.getChildCount(); i++) {
107 MethodInheritanceTreeNode foundNode = findNode((MethodInheritanceTreeNode) node.getChildAt(i), entry);
108 if (foundNode != null) {
109 return foundNode;
110 }
111 }
112 return null;
113 }
114}
diff --git a/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java
new file mode 100644
index 0000000..9392346
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java
@@ -0,0 +1,19 @@
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 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import cuchaz.enigma.mapping.Entry;
14
15public interface ReferenceTreeNode<E extends Entry, C extends Entry> {
16 E getEntry();
17
18 EntryReference<E, C> getReference();
19}
diff --git a/src/main/java/cuchaz/enigma/analysis/RelatedMethodChecker.java b/src/main/java/cuchaz/enigma/analysis/RelatedMethodChecker.java
new file mode 100644
index 0000000..08e2dbf
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/RelatedMethodChecker.java
@@ -0,0 +1,104 @@
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 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.google.common.collect.Maps;
14import com.google.common.collect.Sets;
15
16import java.util.Map;
17import java.util.Set;
18
19import cuchaz.enigma.mapping.*;
20
21public class RelatedMethodChecker {
22
23 private JarIndex m_jarIndex;
24 private Map<Set<MethodEntry>, String> m_deobfNamesByGroup;
25 private Map<MethodEntry, String> m_deobfNamesByObfMethod;
26 private Map<MethodEntry, Set<MethodEntry>> m_groupsByObfMethod;
27 private Set<Set<MethodEntry>> m_inconsistentGroups;
28
29 public RelatedMethodChecker(JarIndex jarIndex) {
30 m_jarIndex = jarIndex;
31 m_deobfNamesByGroup = Maps.newHashMap();
32 m_deobfNamesByObfMethod = Maps.newHashMap();
33 m_groupsByObfMethod = Maps.newHashMap();
34 m_inconsistentGroups = Sets.newHashSet();
35 }
36
37 public void checkMethod(ClassEntry classEntry, MethodMapping methodMapping) {
38
39 // TEMP: disable the expensive check for now, maybe we can optimize it later, or just use it for debugging
40 if (true) {
41 return;
42 }
43
44 BehaviorEntry obfBehaviorEntry = EntryFactory.getObfBehaviorEntry(classEntry, methodMapping);
45 if (!(obfBehaviorEntry instanceof MethodEntry)) {
46 // only methods have related implementations
47 return;
48 }
49 MethodEntry obfMethodEntry = (MethodEntry) obfBehaviorEntry;
50 String deobfName = methodMapping.getDeobfName();
51 m_deobfNamesByObfMethod.put(obfMethodEntry, deobfName);
52
53 // have we seen this method's group before?
54 Set<MethodEntry> group = m_groupsByObfMethod.get(obfMethodEntry);
55 if (group == null) {
56
57 // no, compute the group and save the name
58 group = m_jarIndex.getRelatedMethodImplementations(obfMethodEntry);
59 m_deobfNamesByGroup.put(group, deobfName);
60
61 assert (group.contains(obfMethodEntry));
62 for (MethodEntry relatedMethodEntry : group) {
63 m_groupsByObfMethod.put(relatedMethodEntry, group);
64 }
65 }
66
67 // check the name
68 if (!sameName(m_deobfNamesByGroup.get(group), deobfName)) {
69 m_inconsistentGroups.add(group);
70 }
71 }
72
73 private boolean sameName(String a, String b) {
74 if (a == null && b == null) {
75 return true;
76 } else if (a != null && b != null) {
77 return a.equals(b);
78 }
79 return false;
80 }
81
82 public boolean hasProblems() {
83 return m_inconsistentGroups.size() > 0;
84 }
85
86 public String getReport() {
87 StringBuilder buf = new StringBuilder();
88 buf.append(m_inconsistentGroups.size());
89 buf.append(" groups of methods related by inheritance and/or interfaces have different deobf names!\n");
90 for (Set<MethodEntry> group : m_inconsistentGroups) {
91 buf.append("\tGroup with ");
92 buf.append(group.size());
93 buf.append(" methods:\n");
94 for (MethodEntry methodEntry : group) {
95 buf.append("\t\t");
96 buf.append(methodEntry.toString());
97 buf.append(" => ");
98 buf.append(m_deobfNamesByObfMethod.get(methodEntry));
99 buf.append("\n");
100 }
101 }
102 return buf.toString();
103 }
104}
diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndex.java b/src/main/java/cuchaz/enigma/analysis/SourceIndex.java
new file mode 100644
index 0000000..a20fbb4
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/SourceIndex.java
@@ -0,0 +1,185 @@
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 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.google.common.collect.HashMultimap;
14import com.google.common.collect.Lists;
15import com.google.common.collect.Maps;
16import com.google.common.collect.Multimap;
17
18import com.strobel.decompiler.languages.Region;
19import com.strobel.decompiler.languages.java.ast.AstNode;
20import com.strobel.decompiler.languages.java.ast.Identifier;
21
22import java.util.Collection;
23import java.util.List;
24import java.util.Map;
25import java.util.TreeMap;
26
27import cuchaz.enigma.mapping.Entry;
28
29public class SourceIndex {
30
31 private String m_source;
32 private TreeMap<Token, EntryReference<Entry, Entry>> m_tokenToReference;
33 private Multimap<EntryReference<Entry, Entry>, Token> m_referenceToTokens;
34 private Map<Entry, Token> m_declarationToToken;
35 private List<Integer> m_lineOffsets;
36 private boolean m_ignoreBadTokens;
37
38 public SourceIndex(String source) {
39 this(source, true);
40 }
41
42 public SourceIndex(String source, boolean ignoreBadTokens) {
43 m_source = source;
44 m_ignoreBadTokens = ignoreBadTokens;
45 m_tokenToReference = Maps.newTreeMap();
46 m_referenceToTokens = HashMultimap.create();
47 m_declarationToToken = Maps.newHashMap();
48 m_lineOffsets = Lists.newArrayList();
49
50 // count the lines
51 m_lineOffsets.add(0);
52 for (int i = 0; i < source.length(); i++) {
53 if (source.charAt(i) == '\n') {
54 m_lineOffsets.add(i + 1);
55 }
56 }
57 }
58
59 public String getSource() {
60 return m_source;
61 }
62
63 public Token getToken(AstNode node) {
64
65 // get the text of the node
66 String name = "";
67 if (node instanceof Identifier) {
68 name = ((Identifier) node).getName();
69 }
70
71 // get a token for this node's region
72 Region region = node.getRegion();
73 if (region.getBeginLine() == 0 || region.getEndLine() == 0) {
74 // DEBUG
75 System.err.println(String.format("WARNING: %s \"%s\" has invalid region: %s", node.getNodeType(), name, region));
76 return null;
77 }
78 Token token = new Token(
79 toPos(region.getBeginLine(), region.getBeginColumn()),
80 toPos(region.getEndLine(), region.getEndColumn()),
81 m_source
82 );
83 if (token.start == 0) {
84 // DEBUG
85 System.err.println(String.format("WARNING: %s \"%s\" has invalid start: %s", node.getNodeType(), name, region));
86 return null;
87 }
88
89 // DEBUG
90 // System.out.println( String.format( "%s \"%s\" region: %s", node.getNodeType(), name, region ) );
91
92 // if the token has a $ in it, something's wrong. Ignore this token
93 if (name.lastIndexOf('$') >= 0 && m_ignoreBadTokens) {
94 // DEBUG
95 System.err.println(String.format("WARNING: %s \"%s\" is probably a bad token. It was ignored", node.getNodeType(), name));
96 return null;
97 }
98
99 return token;
100 }
101
102 public void addReference(AstNode node, Entry deobfEntry, Entry deobfContext) {
103 Token token = getToken(node);
104 if (token != null) {
105 EntryReference<Entry, Entry> deobfReference = new EntryReference<Entry, Entry>(deobfEntry, token.text, deobfContext);
106 m_tokenToReference.put(token, deobfReference);
107 m_referenceToTokens.put(deobfReference, token);
108 }
109 }
110
111 public void addDeclaration(AstNode node, Entry deobfEntry) {
112 Token token = getToken(node);
113 if (token != null) {
114 EntryReference<Entry, Entry> reference = new EntryReference<Entry, Entry>(deobfEntry, token.text);
115 m_tokenToReference.put(token, reference);
116 m_referenceToTokens.put(reference, token);
117 m_declarationToToken.put(deobfEntry, token);
118 }
119 }
120
121 public Token getReferenceToken(int pos) {
122 Token token = m_tokenToReference.floorKey(new Token(pos, pos, null));
123 if (token != null && token.contains(pos)) {
124 return token;
125 }
126 return null;
127 }
128
129 public Collection<Token> getReferenceTokens(EntryReference<Entry, Entry> deobfReference) {
130 return m_referenceToTokens.get(deobfReference);
131 }
132
133 public EntryReference<Entry, Entry> getDeobfReference(Token token) {
134 if (token == null) {
135 return null;
136 }
137 return m_tokenToReference.get(token);
138 }
139
140 public void replaceDeobfReference(Token token, EntryReference<Entry, Entry> newDeobfReference) {
141 EntryReference<Entry, Entry> oldDeobfReference = m_tokenToReference.get(token);
142 m_tokenToReference.put(token, newDeobfReference);
143 Collection<Token> tokens = m_referenceToTokens.get(oldDeobfReference);
144 m_referenceToTokens.removeAll(oldDeobfReference);
145 m_referenceToTokens.putAll(newDeobfReference, tokens);
146 }
147
148 public Iterable<Token> referenceTokens() {
149 return m_tokenToReference.keySet();
150 }
151
152 public Iterable<Token> declarationTokens() {
153 return m_declarationToToken.values();
154 }
155
156 public Iterable<Entry> declarations() {
157 return m_declarationToToken.keySet();
158 }
159
160 public Token getDeclarationToken(Entry deobfEntry) {
161 return m_declarationToToken.get(deobfEntry);
162 }
163
164 public int getLineNumber(int pos) {
165 // line number is 1-based
166 int line = 0;
167 for (Integer offset : m_lineOffsets) {
168 if (offset > pos) {
169 break;
170 }
171 line++;
172 }
173 return line;
174 }
175
176 public int getColumnNumber(int pos) {
177 // column number is 1-based
178 return pos - m_lineOffsets.get(getLineNumber(pos) - 1) + 1;
179 }
180
181 private int toPos(int line, int col) {
182 // line and col are 1-based
183 return m_lineOffsets.get(line - 1) + col - 1;
184 }
185}
diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java
new file mode 100644
index 0000000..e2b567e
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java
@@ -0,0 +1,131 @@
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 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.strobel.assembler.metadata.*;
14import com.strobel.decompiler.languages.TextLocation;
15import com.strobel.decompiler.languages.java.ast.*;
16
17import cuchaz.enigma.mapping.*;
18
19import java.lang.Error;
20
21public class SourceIndexBehaviorVisitor extends SourceIndexVisitor {
22
23 private BehaviorEntry m_behaviorEntry;
24
25 public SourceIndexBehaviorVisitor(BehaviorEntry behaviorEntry) {
26 m_behaviorEntry = behaviorEntry;
27 }
28
29 @Override
30 public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) {
31 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
32
33 // get the behavior entry
34 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
35 BehaviorEntry behaviorEntry = null;
36 if (ref instanceof MethodReference) {
37 MethodReference methodRef = (MethodReference) ref;
38 if (methodRef.isConstructor()) {
39 behaviorEntry = new ConstructorEntry(classEntry, new Signature(ref.getErasedSignature()));
40 } else if (methodRef.isTypeInitializer()) {
41 behaviorEntry = new ConstructorEntry(classEntry);
42 } else {
43 behaviorEntry = new MethodEntry(classEntry, ref.getName(), new Signature(ref.getErasedSignature()));
44 }
45 }
46 if (behaviorEntry != null) {
47 // get the node for the token
48 AstNode tokenNode = null;
49 if (node.getTarget() instanceof MemberReferenceExpression) {
50 tokenNode = ((MemberReferenceExpression) node.getTarget()).getMemberNameToken();
51 } else if (node.getTarget() instanceof SuperReferenceExpression) {
52 tokenNode = node.getTarget();
53 } else if (node.getTarget() instanceof ThisReferenceExpression) {
54 tokenNode = node.getTarget();
55 }
56 if (tokenNode != null) {
57 index.addReference(tokenNode, behaviorEntry, m_behaviorEntry);
58 }
59 }
60
61 return recurse(node, index);
62 }
63
64 @Override
65 public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) {
66 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
67 if (ref != null) {
68 // make sure this is actually a field
69 if (ref.getErasedSignature().indexOf('(') >= 0) {
70 throw new Error("Expected a field here! got " + ref);
71 }
72
73 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
74 FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getErasedSignature()));
75 index.addReference(node.getMemberNameToken(), fieldEntry, m_behaviorEntry);
76 }
77
78 return recurse(node, index);
79 }
80
81 @Override
82 public Void visitSimpleType(SimpleType node, SourceIndex index) {
83 TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE);
84 if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) {
85 ClassEntry classEntry = new ClassEntry(ref.getInternalName());
86 index.addReference(node.getIdentifierToken(), classEntry, m_behaviorEntry);
87 }
88
89 return recurse(node, index);
90 }
91
92 @Override
93 public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) {
94 ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION);
95 if (def.getMethod() instanceof MethodDefinition) {
96 MethodDefinition methodDef = (MethodDefinition) def.getMethod();
97 BehaviorEntry behaviorEntry = ProcyonEntryFactory.getBehaviorEntry(methodDef);
98 ArgumentEntry argumentEntry = new ArgumentEntry(behaviorEntry, def.getPosition(), node.getName());
99 index.addDeclaration(node.getNameToken(), argumentEntry);
100 }
101
102 return recurse(node, index);
103 }
104
105 @Override
106 public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) {
107 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
108 if (ref != null) {
109 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
110 FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getErasedSignature()));
111 index.addReference(node.getIdentifierToken(), fieldEntry, m_behaviorEntry);
112 }
113
114 return recurse(node, index);
115 }
116
117 @Override
118 public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) {
119 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
120 if (ref != null) {
121 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
122 ConstructorEntry constructorEntry = new ConstructorEntry(classEntry, new Signature(ref.getErasedSignature()));
123 if (node.getType() instanceof SimpleType) {
124 SimpleType simpleTypeNode = (SimpleType) node.getType();
125 index.addReference(simpleTypeNode.getIdentifierToken(), constructorEntry, m_behaviorEntry);
126 }
127 }
128
129 return recurse(node, index);
130 }
131}
diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java
new file mode 100644
index 0000000..0a3bad5
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java
@@ -0,0 +1,100 @@
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 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.strobel.assembler.metadata.FieldDefinition;
14import com.strobel.assembler.metadata.MethodDefinition;
15import com.strobel.assembler.metadata.TypeDefinition;
16import com.strobel.assembler.metadata.TypeReference;
17import com.strobel.decompiler.languages.TextLocation;
18import com.strobel.decompiler.languages.java.ast.*;
19
20import cuchaz.enigma.mapping.*;
21
22public class SourceIndexClassVisitor extends SourceIndexVisitor {
23
24 private ClassEntry m_classEntry;
25
26 public SourceIndexClassVisitor(ClassEntry classEntry) {
27 m_classEntry = classEntry;
28 }
29
30 @Override
31 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) {
32 // is this this class, or a subtype?
33 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION);
34 ClassEntry classEntry = new ClassEntry(def.getInternalName());
35 if (!classEntry.equals(m_classEntry)) {
36 // it's a sub-type, recurse
37 index.addDeclaration(node.getNameToken(), classEntry);
38 return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index);
39 }
40
41 return recurse(node, index);
42 }
43
44 @Override
45 public Void visitSimpleType(SimpleType node, SourceIndex index) {
46 TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE);
47 if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) {
48 ClassEntry classEntry = new ClassEntry(ref.getInternalName());
49 index.addReference(node.getIdentifierToken(), classEntry, m_classEntry);
50 }
51
52 return recurse(node, index);
53 }
54
55 @Override
56 public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) {
57 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
58 BehaviorEntry behaviorEntry = ProcyonEntryFactory.getBehaviorEntry(def);
59 AstNode tokenNode = node.getNameToken();
60
61 if (behaviorEntry instanceof ConstructorEntry) {
62 ConstructorEntry constructorEntry = (ConstructorEntry) behaviorEntry;
63 if (constructorEntry.isStatic()) {
64 // for static initializers, check elsewhere for the token node
65 tokenNode = node.getModifiers().firstOrNullObject();
66 }
67 }
68 index.addDeclaration(tokenNode, behaviorEntry);
69 return node.acceptVisitor(new SourceIndexBehaviorVisitor(behaviorEntry), index);
70 }
71
72 @Override
73 public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) {
74 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
75 ConstructorEntry constructorEntry = ProcyonEntryFactory.getConstructorEntry(def);
76 index.addDeclaration(node.getNameToken(), constructorEntry);
77 return node.acceptVisitor(new SourceIndexBehaviorVisitor(constructorEntry), index);
78 }
79
80 @Override
81 public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) {
82 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION);
83 FieldEntry fieldEntry = ProcyonEntryFactory.getFieldEntry(def);
84 assert (node.getVariables().size() == 1);
85 VariableInitializer variable = node.getVariables().firstOrNullObject();
86 index.addDeclaration(variable.getNameToken(), fieldEntry);
87
88 return recurse(node, index);
89 }
90
91 @Override
92 public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) {
93 // treat enum declarations as field declarations
94 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION);
95 FieldEntry fieldEntry = ProcyonEntryFactory.getFieldEntry(def);
96 index.addDeclaration(node.getNameToken(), fieldEntry);
97
98 return recurse(node, index);
99 }
100}
diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java
new file mode 100644
index 0000000..40381f4
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java
@@ -0,0 +1,381 @@
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 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.strobel.assembler.metadata.TypeDefinition;
14import com.strobel.decompiler.languages.java.ast.*;
15import com.strobel.decompiler.patterns.Pattern;
16
17import cuchaz.enigma.mapping.ClassEntry;
18
19public class SourceIndexVisitor implements IAstVisitor<SourceIndex, Void> {
20
21 @Override
22 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) {
23 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION);
24 ClassEntry classEntry = new ClassEntry(def.getInternalName());
25 index.addDeclaration(node.getNameToken(), classEntry);
26
27 return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index);
28 }
29
30 protected Void recurse(AstNode node, SourceIndex index) {
31 for (final AstNode child : node.getChildren()) {
32 child.acceptVisitor(this, index);
33 }
34 return null;
35 }
36
37 @Override
38 public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) {
39 return recurse(node, index);
40 }
41
42 @Override
43 public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) {
44 return recurse(node, index);
45 }
46
47 @Override
48 public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) {
49 return recurse(node, index);
50 }
51
52 @Override
53 public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) {
54 return recurse(node, index);
55 }
56
57 @Override
58 public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) {
59 return recurse(node, index);
60 }
61
62 @Override
63 public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) {
64 return recurse(node, index);
65 }
66
67 @Override
68 public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) {
69 return recurse(node, index);
70 }
71
72 @Override
73 public Void visitSimpleType(SimpleType node, SourceIndex index) {
74 return recurse(node, index);
75 }
76
77 @Override
78 public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) {
79 return recurse(node, index);
80 }
81
82 @Override
83 public Void visitComment(Comment node, SourceIndex index) {
84 return recurse(node, index);
85 }
86
87 @Override
88 public Void visitPatternPlaceholder(AstNode node, Pattern pattern, SourceIndex index) {
89 return recurse(node, index);
90 }
91
92 @Override
93 public Void visitTypeReference(TypeReferenceExpression node, SourceIndex index) {
94 return recurse(node, index);
95 }
96
97 @Override
98 public Void visitJavaTokenNode(JavaTokenNode node, SourceIndex index) {
99 return recurse(node, index);
100 }
101
102 @Override
103 public Void visitIdentifier(Identifier node, SourceIndex index) {
104 return recurse(node, index);
105 }
106
107 @Override
108 public Void visitNullReferenceExpression(NullReferenceExpression node, SourceIndex index) {
109 return recurse(node, index);
110 }
111
112 @Override
113 public Void visitThisReferenceExpression(ThisReferenceExpression node, SourceIndex index) {
114 return recurse(node, index);
115 }
116
117 @Override
118 public Void visitSuperReferenceExpression(SuperReferenceExpression node, SourceIndex index) {
119 return recurse(node, index);
120 }
121
122 @Override
123 public Void visitClassOfExpression(ClassOfExpression node, SourceIndex index) {
124 return recurse(node, index);
125 }
126
127 @Override
128 public Void visitBlockStatement(BlockStatement node, SourceIndex index) {
129 return recurse(node, index);
130 }
131
132 @Override
133 public Void visitExpressionStatement(ExpressionStatement node, SourceIndex index) {
134 return recurse(node, index);
135 }
136
137 @Override
138 public Void visitBreakStatement(BreakStatement node, SourceIndex index) {
139 return recurse(node, index);
140 }
141
142 @Override
143 public Void visitContinueStatement(ContinueStatement node, SourceIndex index) {
144 return recurse(node, index);
145 }
146
147 @Override
148 public Void visitDoWhileStatement(DoWhileStatement node, SourceIndex index) {
149 return recurse(node, index);
150 }
151
152 @Override
153 public Void visitEmptyStatement(EmptyStatement node, SourceIndex index) {
154 return recurse(node, index);
155 }
156
157 @Override
158 public Void visitIfElseStatement(IfElseStatement node, SourceIndex index) {
159 return recurse(node, index);
160 }
161
162 @Override
163 public Void visitLabelStatement(LabelStatement node, SourceIndex index) {
164 return recurse(node, index);
165 }
166
167 @Override
168 public Void visitLabeledStatement(LabeledStatement node, SourceIndex index) {
169 return recurse(node, index);
170 }
171
172 @Override
173 public Void visitReturnStatement(ReturnStatement node, SourceIndex index) {
174 return recurse(node, index);
175 }
176
177 @Override
178 public Void visitSwitchStatement(SwitchStatement node, SourceIndex index) {
179 return recurse(node, index);
180 }
181
182 @Override
183 public Void visitSwitchSection(SwitchSection node, SourceIndex index) {
184 return recurse(node, index);
185 }
186
187 @Override
188 public Void visitCaseLabel(CaseLabel node, SourceIndex index) {
189 return recurse(node, index);
190 }
191
192 @Override
193 public Void visitThrowStatement(ThrowStatement node, SourceIndex index) {
194 return recurse(node, index);
195 }
196
197 @Override
198 public Void visitCatchClause(CatchClause node, SourceIndex index) {
199 return recurse(node, index);
200 }
201
202 @Override
203 public Void visitAnnotation(Annotation node, SourceIndex index) {
204 return recurse(node, index);
205 }
206
207 @Override
208 public Void visitNewLine(NewLineNode node, SourceIndex index) {
209 return recurse(node, index);
210 }
211
212 @Override
213 public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) {
214 return recurse(node, index);
215 }
216
217 @Override
218 public Void visitVariableInitializer(VariableInitializer node, SourceIndex index) {
219 return recurse(node, index);
220 }
221
222 @Override
223 public Void visitText(TextNode node, SourceIndex index) {
224 return recurse(node, index);
225 }
226
227 @Override
228 public Void visitImportDeclaration(ImportDeclaration node, SourceIndex index) {
229 return recurse(node, index);
230 }
231
232 @Override
233 public Void visitInitializerBlock(InstanceInitializer node, SourceIndex index) {
234 return recurse(node, index);
235 }
236
237 @Override
238 public Void visitTypeParameterDeclaration(TypeParameterDeclaration node, SourceIndex index) {
239 return recurse(node, index);
240 }
241
242 @Override
243 public Void visitCompilationUnit(CompilationUnit node, SourceIndex index) {
244 return recurse(node, index);
245 }
246
247 @Override
248 public Void visitPackageDeclaration(PackageDeclaration node, SourceIndex index) {
249 return recurse(node, index);
250 }
251
252 @Override
253 public Void visitArraySpecifier(ArraySpecifier node, SourceIndex index) {
254 return recurse(node, index);
255 }
256
257 @Override
258 public Void visitComposedType(ComposedType node, SourceIndex index) {
259 return recurse(node, index);
260 }
261
262 @Override
263 public Void visitWhileStatement(WhileStatement node, SourceIndex index) {
264 return recurse(node, index);
265 }
266
267 @Override
268 public Void visitPrimitiveExpression(PrimitiveExpression node, SourceIndex index) {
269 return recurse(node, index);
270 }
271
272 @Override
273 public Void visitCastExpression(CastExpression node, SourceIndex index) {
274 return recurse(node, index);
275 }
276
277 @Override
278 public Void visitBinaryOperatorExpression(BinaryOperatorExpression node, SourceIndex index) {
279 return recurse(node, index);
280 }
281
282 @Override
283 public Void visitInstanceOfExpression(InstanceOfExpression node, SourceIndex index) {
284 return recurse(node, index);
285 }
286
287 @Override
288 public Void visitIndexerExpression(IndexerExpression node, SourceIndex index) {
289 return recurse(node, index);
290 }
291
292 @Override
293 public Void visitUnaryOperatorExpression(UnaryOperatorExpression node, SourceIndex index) {
294 return recurse(node, index);
295 }
296
297 @Override
298 public Void visitConditionalExpression(ConditionalExpression node, SourceIndex index) {
299 return recurse(node, index);
300 }
301
302 @Override
303 public Void visitArrayInitializerExpression(ArrayInitializerExpression node, SourceIndex index) {
304 return recurse(node, index);
305 }
306
307 @Override
308 public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) {
309 return recurse(node, index);
310 }
311
312 @Override
313 public Void visitArrayCreationExpression(ArrayCreationExpression node, SourceIndex index) {
314 return recurse(node, index);
315 }
316
317 @Override
318 public Void visitAssignmentExpression(AssignmentExpression node, SourceIndex index) {
319 return recurse(node, index);
320 }
321
322 @Override
323 public Void visitForStatement(ForStatement node, SourceIndex index) {
324 return recurse(node, index);
325 }
326
327 @Override
328 public Void visitForEachStatement(ForEachStatement node, SourceIndex index) {
329 return recurse(node, index);
330 }
331
332 @Override
333 public Void visitTryCatchStatement(TryCatchStatement node, SourceIndex index) {
334 return recurse(node, index);
335 }
336
337 @Override
338 public Void visitGotoStatement(GotoStatement node, SourceIndex index) {
339 return recurse(node, index);
340 }
341
342 @Override
343 public Void visitParenthesizedExpression(ParenthesizedExpression node, SourceIndex index) {
344 return recurse(node, index);
345 }
346
347 @Override
348 public Void visitSynchronizedStatement(SynchronizedStatement node, SourceIndex index) {
349 return recurse(node, index);
350 }
351
352 @Override
353 public Void visitAnonymousObjectCreationExpression(AnonymousObjectCreationExpression node, SourceIndex index) {
354 return recurse(node, index);
355 }
356
357 @Override
358 public Void visitWildcardType(WildcardType node, SourceIndex index) {
359 return recurse(node, index);
360 }
361
362 @Override
363 public Void visitMethodGroupExpression(MethodGroupExpression node, SourceIndex index) {
364 return recurse(node, index);
365 }
366
367 @Override
368 public Void visitAssertStatement(AssertStatement node, SourceIndex index) {
369 return recurse(node, index);
370 }
371
372 @Override
373 public Void visitLambdaExpression(LambdaExpression node, SourceIndex index) {
374 return recurse(node, index);
375 }
376
377 @Override
378 public Void visitLocalTypeDeclarationStatement(LocalTypeDeclarationStatement node, SourceIndex index) {
379 return recurse(node, index);
380 }
381}
diff --git a/src/main/java/cuchaz/enigma/analysis/Token.java b/src/main/java/cuchaz/enigma/analysis/Token.java
new file mode 100644
index 0000000..0103df2
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/Token.java
@@ -0,0 +1,56 @@
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 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13public class Token implements Comparable<Token> {
14
15 public int start;
16 public int end;
17 public String text;
18
19 public Token(int start, int end) {
20 this(start, end, null);
21 }
22
23 public Token(int start, int end, String source) {
24 this.start = start;
25 this.end = end;
26 if (source != null) {
27 this.text = source.substring(start, end);
28 }
29 }
30
31 public boolean contains(int pos) {
32 return pos >= start && pos <= end;
33 }
34
35 @Override
36 public int compareTo(Token other) {
37 return start - other.start;
38 }
39
40 @Override
41 public boolean equals(Object other) {
42 if (other instanceof Token) {
43 return equals((Token) other);
44 }
45 return false;
46 }
47
48 public boolean equals(Token other) {
49 return start == other.start && end == other.end;
50 }
51
52 @Override
53 public String toString() {
54 return String.format("[%d,%d]", start, end);
55 }
56}
diff --git a/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java b/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java
new file mode 100644
index 0000000..0261a96
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java
@@ -0,0 +1,282 @@
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 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.google.common.collect.HashMultimap;
14import com.google.common.collect.Lists;
15import com.google.common.collect.Maps;
16import com.google.common.collect.Multimap;
17
18import java.io.*;
19import java.util.*;
20import java.util.zip.GZIPInputStream;
21import java.util.zip.GZIPOutputStream;
22
23import cuchaz.enigma.mapping.*;
24import javassist.CtBehavior;
25import javassist.CtClass;
26import javassist.CtField;
27import javassist.bytecode.Descriptor;
28
29public class TranslationIndex implements Serializable {
30
31 private static final long serialVersionUID = 738687982126844179L;
32
33 private Map<ClassEntry, ClassEntry> m_superclasses;
34 private Multimap<ClassEntry, FieldEntry> m_fieldEntries;
35 private Multimap<ClassEntry, BehaviorEntry> m_behaviorEntries;
36 private Multimap<ClassEntry, ClassEntry> m_interfaces;
37
38 public TranslationIndex() {
39 m_superclasses = Maps.newHashMap();
40 m_fieldEntries = HashMultimap.create();
41 m_behaviorEntries = HashMultimap.create();
42 m_interfaces = HashMultimap.create();
43 }
44
45 public TranslationIndex(TranslationIndex other, Translator translator) {
46
47 // translate the superclasses
48 m_superclasses = Maps.newHashMap();
49 for (Map.Entry<ClassEntry, ClassEntry> mapEntry : other.m_superclasses.entrySet()) {
50 m_superclasses.put(
51 translator.translateEntry(mapEntry.getKey()),
52 translator.translateEntry(mapEntry.getValue())
53 );
54 }
55
56 // translate the interfaces
57 m_interfaces = HashMultimap.create();
58 for (Map.Entry<ClassEntry, ClassEntry> mapEntry : other.m_interfaces.entries()) {
59 m_interfaces.put(
60 translator.translateEntry(mapEntry.getKey()),
61 translator.translateEntry(mapEntry.getValue())
62 );
63 }
64
65 // translate the fields
66 m_fieldEntries = HashMultimap.create();
67 for (Map.Entry<ClassEntry, FieldEntry> mapEntry : other.m_fieldEntries.entries()) {
68 m_fieldEntries.put(
69 translator.translateEntry(mapEntry.getKey()),
70 translator.translateEntry(mapEntry.getValue())
71 );
72 }
73
74 m_behaviorEntries = HashMultimap.create();
75 for (Map.Entry<ClassEntry, BehaviorEntry> mapEntry : other.m_behaviorEntries.entries()) {
76 m_behaviorEntries.put(
77 translator.translateEntry(mapEntry.getKey()),
78 translator.translateEntry(mapEntry.getValue())
79 );
80 }
81 }
82
83 public void indexClass(CtClass c) {
84 indexClass(c, true);
85 }
86
87 public void indexClass(CtClass c, boolean indexMembers) {
88
89 ClassEntry classEntry = EntryFactory.getClassEntry(c);
90 if (isJre(classEntry)) {
91 return;
92 }
93
94 // add the superclass
95 ClassEntry superclassEntry = EntryFactory.getSuperclassEntry(c);
96 if (superclassEntry != null) {
97 m_superclasses.put(classEntry, superclassEntry);
98 }
99
100 // add the interfaces
101 for (String interfaceClassName : c.getClassFile().getInterfaces()) {
102 ClassEntry interfaceClassEntry = new ClassEntry(Descriptor.toJvmName(interfaceClassName));
103 if (!isJre(interfaceClassEntry)) {
104 m_interfaces.put(classEntry, interfaceClassEntry);
105 }
106 }
107
108 if (indexMembers) {
109 // add fields
110 for (CtField field : c.getDeclaredFields()) {
111 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
112 m_fieldEntries.put(fieldEntry.getClassEntry(), fieldEntry);
113 }
114
115 // add behaviors
116 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
117 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
118 m_behaviorEntries.put(behaviorEntry.getClassEntry(), behaviorEntry);
119 }
120 }
121 }
122
123 public void renameClasses(Map<String, String> renames) {
124 EntryRenamer.renameClassesInMap(renames, m_superclasses);
125 EntryRenamer.renameClassesInMultimap(renames, m_fieldEntries);
126 EntryRenamer.renameClassesInMultimap(renames, m_behaviorEntries);
127 }
128
129 public ClassEntry getSuperclass(ClassEntry classEntry) {
130 return m_superclasses.get(classEntry);
131 }
132
133 public List<ClassEntry> getAncestry(ClassEntry classEntry) {
134 List<ClassEntry> ancestors = Lists.newArrayList();
135 while (classEntry != null) {
136 classEntry = getSuperclass(classEntry);
137 if (classEntry != null) {
138 ancestors.add(classEntry);
139 }
140 }
141 return ancestors;
142 }
143
144 public List<ClassEntry> getSubclass(ClassEntry classEntry) {
145
146 // linear search is fast enough for now
147 List<ClassEntry> subclasses = Lists.newArrayList();
148 for (Map.Entry<ClassEntry, ClassEntry> entry : m_superclasses.entrySet()) {
149 ClassEntry subclass = entry.getKey();
150 ClassEntry superclass = entry.getValue();
151 if (classEntry.equals(superclass)) {
152 subclasses.add(subclass);
153 }
154 }
155 return subclasses;
156 }
157
158 public void getSubclassesRecursively(Set<ClassEntry> out, ClassEntry classEntry) {
159 for (ClassEntry subclassEntry : getSubclass(classEntry)) {
160 out.add(subclassEntry);
161 getSubclassesRecursively(out, subclassEntry);
162 }
163 }
164
165 public void getSubclassNamesRecursively(Set<String> out, ClassEntry classEntry) {
166 for (ClassEntry subclassEntry : getSubclass(classEntry)) {
167 out.add(subclassEntry.getName());
168 getSubclassNamesRecursively(out, subclassEntry);
169 }
170 }
171
172 public Collection<Map.Entry<ClassEntry, ClassEntry>> getClassInterfaces() {
173 return m_interfaces.entries();
174 }
175
176 public Collection<ClassEntry> getInterfaces(ClassEntry classEntry) {
177 return m_interfaces.get(classEntry);
178 }
179
180 public boolean isInterface(ClassEntry classEntry) {
181 return m_interfaces.containsValue(classEntry);
182 }
183
184 public boolean entryExists(Entry entry) {
185 if (entry instanceof FieldEntry) {
186 return fieldExists((FieldEntry) entry);
187 } else if (entry instanceof BehaviorEntry) {
188 return behaviorExists((BehaviorEntry) entry);
189 } else if (entry instanceof ArgumentEntry) {
190 return behaviorExists(((ArgumentEntry) entry).getBehaviorEntry());
191 }
192 throw new IllegalArgumentException("Cannot check existence for " + entry.getClass());
193 }
194
195 public boolean fieldExists(FieldEntry fieldEntry) {
196 return m_fieldEntries.containsEntry(fieldEntry.getClassEntry(), fieldEntry);
197 }
198
199 public boolean behaviorExists(BehaviorEntry behaviorEntry) {
200 return m_behaviorEntries.containsEntry(behaviorEntry.getClassEntry(), behaviorEntry);
201 }
202
203 public ClassEntry resolveEntryClass(Entry entry) {
204
205 if (entry instanceof ClassEntry) {
206 return (ClassEntry) entry;
207 }
208
209 ClassEntry superclassEntry = resolveSuperclass(entry);
210 if (superclassEntry != null) {
211 return superclassEntry;
212 }
213
214 ClassEntry interfaceEntry = resolveInterface(entry);
215 if (interfaceEntry != null) {
216 return interfaceEntry;
217 }
218
219 return null;
220 }
221
222 public ClassEntry resolveSuperclass(Entry entry) {
223
224 // this entry could refer to a method on a class where the method is not actually implemented
225 // travel up the inheritance tree to find the closest implementation
226 while (!entryExists(entry)) {
227
228 // is there a parent class?
229 ClassEntry superclassEntry = getSuperclass(entry.getClassEntry());
230 if (superclassEntry == null) {
231 // this is probably a method from a class in a library
232 // we can't trace the implementation up any higher unless we index the library
233 return null;
234 }
235
236 // move up to the parent class
237 entry = entry.cloneToNewClass(superclassEntry);
238 }
239 return entry.getClassEntry();
240 }
241
242 public ClassEntry resolveInterface(Entry entry) {
243
244 // the interfaces for any class is a forest
245 // so let's look at all the trees
246 for (ClassEntry interfaceEntry : m_interfaces.get(entry.getClassEntry())) {
247 ClassEntry resolvedClassEntry = resolveSuperclass(entry.cloneToNewClass(interfaceEntry));
248 if (resolvedClassEntry != null) {
249 return resolvedClassEntry;
250 }
251 }
252 return null;
253 }
254
255 private boolean isJre(ClassEntry classEntry) {
256 String packageName = classEntry.getPackageName();
257 return packageName != null && (packageName.startsWith("java") || packageName.startsWith("javax"));
258 }
259
260 public void write(OutputStream out)
261 throws IOException {
262 GZIPOutputStream gzipout = new GZIPOutputStream(out);
263 ObjectOutputStream oout = new ObjectOutputStream(gzipout);
264 oout.writeObject(m_superclasses);
265 oout.writeObject(m_fieldEntries);
266 oout.writeObject(m_behaviorEntries);
267 gzipout.finish();
268 }
269
270 @SuppressWarnings("unchecked")
271 public void read(InputStream in)
272 throws IOException {
273 try {
274 ObjectInputStream oin = new ObjectInputStream(new GZIPInputStream(in));
275 m_superclasses = (HashMap<ClassEntry, ClassEntry>) oin.readObject();
276 m_fieldEntries = (HashMultimap<ClassEntry, FieldEntry>) oin.readObject();
277 m_behaviorEntries = (HashMultimap<ClassEntry, BehaviorEntry>) oin.readObject();
278 } catch (ClassNotFoundException ex) {
279 throw new Error(ex);
280 }
281 }
282}
diff --git a/src/main/java/cuchaz/enigma/analysis/TreeDumpVisitor.java b/src/main/java/cuchaz/enigma/analysis/TreeDumpVisitor.java
new file mode 100644
index 0000000..ef8a190
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/TreeDumpVisitor.java
@@ -0,0 +1,441 @@
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 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.strobel.componentmodel.Key;
14import com.strobel.decompiler.languages.java.ast.*;
15import com.strobel.decompiler.patterns.Pattern;
16
17import java.io.File;
18import java.io.FileWriter;
19import java.io.IOException;
20import java.io.Writer;
21
22public class TreeDumpVisitor implements IAstVisitor<Void, Void> {
23
24 private File m_file;
25 private Writer m_out;
26
27 public TreeDumpVisitor(File file) {
28 m_file = file;
29 m_out = null;
30 }
31
32 @Override
33 public Void visitCompilationUnit(CompilationUnit node, Void ignored) {
34 try {
35 m_out = new FileWriter(m_file);
36 recurse(node, ignored);
37 m_out.close();
38 return null;
39 } catch (IOException ex) {
40 throw new Error(ex);
41 }
42 }
43
44 private Void recurse(AstNode node, Void ignored) {
45 // show the tree
46 try {
47 m_out.write(getIndent(node) + node.getClass().getSimpleName() + " " + getText(node) + " " + dumpUserData(node) + " " + node.getRegion() + "\n");
48 } catch (IOException ex) {
49 throw new Error(ex);
50 }
51
52 // recurse
53 for (final AstNode child : node.getChildren()) {
54 child.acceptVisitor(this, ignored);
55 }
56 return null;
57 }
58
59 private String getText(AstNode node) {
60 if (node instanceof Identifier) {
61 return "\"" + ((Identifier) node).getName() + "\"";
62 }
63 return "";
64 }
65
66 private String dumpUserData(AstNode node) {
67 StringBuilder buf = new StringBuilder();
68 for (Key<?> key : Keys.ALL_KEYS) {
69 Object val = node.getUserData(key);
70 if (val != null) {
71 buf.append(String.format(" [%s=%s]", key, val));
72 }
73 }
74 return buf.toString();
75 }
76
77 private String getIndent(AstNode node) {
78 StringBuilder buf = new StringBuilder();
79 int depth = getDepth(node);
80 for (int i = 0; i < depth; i++) {
81 buf.append("\t");
82 }
83 return buf.toString();
84 }
85
86 private int getDepth(AstNode node) {
87 int depth = -1;
88 while (node != null) {
89 depth++;
90 node = node.getParent();
91 }
92 return depth;
93 }
94
95 // OVERRIDES WE DON'T CARE ABOUT
96
97 @Override
98 public Void visitInvocationExpression(InvocationExpression node, Void ignored) {
99 return recurse(node, ignored);
100 }
101
102 @Override
103 public Void visitMemberReferenceExpression(MemberReferenceExpression node, Void ignored) {
104 return recurse(node, ignored);
105 }
106
107 @Override
108 public Void visitSimpleType(SimpleType node, Void ignored) {
109 return recurse(node, ignored);
110 }
111
112 @Override
113 public Void visitMethodDeclaration(MethodDeclaration node, Void ignored) {
114 return recurse(node, ignored);
115 }
116
117 @Override
118 public Void visitConstructorDeclaration(ConstructorDeclaration node, Void ignored) {
119 return recurse(node, ignored);
120 }
121
122 @Override
123 public Void visitParameterDeclaration(ParameterDeclaration node, Void ignored) {
124 return recurse(node, ignored);
125 }
126
127 @Override
128 public Void visitFieldDeclaration(FieldDeclaration node, Void ignored) {
129 return recurse(node, ignored);
130 }
131
132 @Override
133 public Void visitTypeDeclaration(TypeDeclaration node, Void ignored) {
134 return recurse(node, ignored);
135 }
136
137 @Override
138 public Void visitComment(Comment node, Void ignored) {
139 return recurse(node, ignored);
140 }
141
142 @Override
143 public Void visitPatternPlaceholder(AstNode node, Pattern pattern, Void ignored) {
144 return recurse(node, ignored);
145 }
146
147 @Override
148 public Void visitTypeReference(TypeReferenceExpression node, Void ignored) {
149 return recurse(node, ignored);
150 }
151
152 @Override
153 public Void visitJavaTokenNode(JavaTokenNode node, Void ignored) {
154 return recurse(node, ignored);
155 }
156
157 @Override
158 public Void visitIdentifier(Identifier node, Void ignored) {
159 return recurse(node, ignored);
160 }
161
162 @Override
163 public Void visitNullReferenceExpression(NullReferenceExpression node, Void ignored) {
164 return recurse(node, ignored);
165 }
166
167 @Override
168 public Void visitThisReferenceExpression(ThisReferenceExpression node, Void ignored) {
169 return recurse(node, ignored);
170 }
171
172 @Override
173 public Void visitSuperReferenceExpression(SuperReferenceExpression node, Void ignored) {
174 return recurse(node, ignored);
175 }
176
177 @Override
178 public Void visitClassOfExpression(ClassOfExpression node, Void ignored) {
179 return recurse(node, ignored);
180 }
181
182 @Override
183 public Void visitBlockStatement(BlockStatement node, Void ignored) {
184 return recurse(node, ignored);
185 }
186
187 @Override
188 public Void visitExpressionStatement(ExpressionStatement node, Void ignored) {
189 return recurse(node, ignored);
190 }
191
192 @Override
193 public Void visitBreakStatement(BreakStatement node, Void ignored) {
194 return recurse(node, ignored);
195 }
196
197 @Override
198 public Void visitContinueStatement(ContinueStatement node, Void ignored) {
199 return recurse(node, ignored);
200 }
201
202 @Override
203 public Void visitDoWhileStatement(DoWhileStatement node, Void ignored) {
204 return recurse(node, ignored);
205 }
206
207 @Override
208 public Void visitEmptyStatement(EmptyStatement node, Void ignored) {
209 return recurse(node, ignored);
210 }
211
212 @Override
213 public Void visitIfElseStatement(IfElseStatement node, Void ignored) {
214 return recurse(node, ignored);
215 }
216
217 @Override
218 public Void visitLabelStatement(LabelStatement node, Void ignored) {
219 return recurse(node, ignored);
220 }
221
222 @Override
223 public Void visitLabeledStatement(LabeledStatement node, Void ignored) {
224 return recurse(node, ignored);
225 }
226
227 @Override
228 public Void visitReturnStatement(ReturnStatement node, Void ignored) {
229 return recurse(node, ignored);
230 }
231
232 @Override
233 public Void visitSwitchStatement(SwitchStatement node, Void ignored) {
234 return recurse(node, ignored);
235 }
236
237 @Override
238 public Void visitSwitchSection(SwitchSection node, Void ignored) {
239 return recurse(node, ignored);
240 }
241
242 @Override
243 public Void visitCaseLabel(CaseLabel node, Void ignored) {
244 return recurse(node, ignored);
245 }
246
247 @Override
248 public Void visitThrowStatement(ThrowStatement node, Void ignored) {
249 return recurse(node, ignored);
250 }
251
252 @Override
253 public Void visitCatchClause(CatchClause node, Void ignored) {
254 return recurse(node, ignored);
255 }
256
257 @Override
258 public Void visitAnnotation(Annotation node, Void ignored) {
259 return recurse(node, ignored);
260 }
261
262 @Override
263 public Void visitNewLine(NewLineNode node, Void ignored) {
264 return recurse(node, ignored);
265 }
266
267 @Override
268 public Void visitVariableDeclaration(VariableDeclarationStatement node, Void ignored) {
269 return recurse(node, ignored);
270 }
271
272 @Override
273 public Void visitVariableInitializer(VariableInitializer node, Void ignored) {
274 return recurse(node, ignored);
275 }
276
277 @Override
278 public Void visitText(TextNode node, Void ignored) {
279 return recurse(node, ignored);
280 }
281
282 @Override
283 public Void visitImportDeclaration(ImportDeclaration node, Void ignored) {
284 return recurse(node, ignored);
285 }
286
287 @Override
288 public Void visitInitializerBlock(InstanceInitializer node, Void ignored) {
289 return recurse(node, ignored);
290 }
291
292 @Override
293 public Void visitTypeParameterDeclaration(TypeParameterDeclaration node, Void ignored) {
294 return recurse(node, ignored);
295 }
296
297 @Override
298 public Void visitPackageDeclaration(PackageDeclaration node, Void ignored) {
299 return recurse(node, ignored);
300 }
301
302 @Override
303 public Void visitArraySpecifier(ArraySpecifier node, Void ignored) {
304 return recurse(node, ignored);
305 }
306
307 @Override
308 public Void visitComposedType(ComposedType node, Void ignored) {
309 return recurse(node, ignored);
310 }
311
312 @Override
313 public Void visitWhileStatement(WhileStatement node, Void ignored) {
314 return recurse(node, ignored);
315 }
316
317 @Override
318 public Void visitPrimitiveExpression(PrimitiveExpression node, Void ignored) {
319 return recurse(node, ignored);
320 }
321
322 @Override
323 public Void visitCastExpression(CastExpression node, Void ignored) {
324 return recurse(node, ignored);
325 }
326
327 @Override
328 public Void visitBinaryOperatorExpression(BinaryOperatorExpression node, Void ignored) {
329 return recurse(node, ignored);
330 }
331
332 @Override
333 public Void visitInstanceOfExpression(InstanceOfExpression node, Void ignored) {
334 return recurse(node, ignored);
335 }
336
337 @Override
338 public Void visitIndexerExpression(IndexerExpression node, Void ignored) {
339 return recurse(node, ignored);
340 }
341
342 @Override
343 public Void visitIdentifierExpression(IdentifierExpression node, Void ignored) {
344 return recurse(node, ignored);
345 }
346
347 @Override
348 public Void visitUnaryOperatorExpression(UnaryOperatorExpression node, Void ignored) {
349 return recurse(node, ignored);
350 }
351
352 @Override
353 public Void visitConditionalExpression(ConditionalExpression node, Void ignored) {
354 return recurse(node, ignored);
355 }
356
357 @Override
358 public Void visitArrayInitializerExpression(ArrayInitializerExpression node, Void ignored) {
359 return recurse(node, ignored);
360 }
361
362 @Override
363 public Void visitObjectCreationExpression(ObjectCreationExpression node, Void ignored) {
364 return recurse(node, ignored);
365 }
366
367 @Override
368 public Void visitArrayCreationExpression(ArrayCreationExpression node, Void ignored) {
369 return recurse(node, ignored);
370 }
371
372 @Override
373 public Void visitAssignmentExpression(AssignmentExpression node, Void ignored) {
374 return recurse(node, ignored);
375 }
376
377 @Override
378 public Void visitForStatement(ForStatement node, Void ignored) {
379 return recurse(node, ignored);
380 }
381
382 @Override
383 public Void visitForEachStatement(ForEachStatement node, Void ignored) {
384 return recurse(node, ignored);
385 }
386
387 @Override
388 public Void visitTryCatchStatement(TryCatchStatement node, Void ignored) {
389 return recurse(node, ignored);
390 }
391
392 @Override
393 public Void visitGotoStatement(GotoStatement node, Void ignored) {
394 return recurse(node, ignored);
395 }
396
397 @Override
398 public Void visitParenthesizedExpression(ParenthesizedExpression node, Void ignored) {
399 return recurse(node, ignored);
400 }
401
402 @Override
403 public Void visitSynchronizedStatement(SynchronizedStatement node, Void ignored) {
404 return recurse(node, ignored);
405 }
406
407 @Override
408 public Void visitAnonymousObjectCreationExpression(AnonymousObjectCreationExpression node, Void ignored) {
409 return recurse(node, ignored);
410 }
411
412 @Override
413 public Void visitWildcardType(WildcardType node, Void ignored) {
414 return recurse(node, ignored);
415 }
416
417 @Override
418 public Void visitMethodGroupExpression(MethodGroupExpression node, Void ignored) {
419 return recurse(node, ignored);
420 }
421
422 @Override
423 public Void visitEnumValueDeclaration(EnumValueDeclaration node, Void ignored) {
424 return recurse(node, ignored);
425 }
426
427 @Override
428 public Void visitAssertStatement(AssertStatement node, Void ignored) {
429 return recurse(node, ignored);
430 }
431
432 @Override
433 public Void visitLambdaExpression(LambdaExpression node, Void ignored) {
434 return recurse(node, ignored);
435 }
436
437 @Override
438 public Void visitLocalTypeDeclarationStatement(LocalTypeDeclarationStatement node, Void ignored) {
439 return recurse(node, ignored);
440 }
441}