From 4be005617b3b8c3578cca07c5d085d12916f0d1d Mon Sep 17 00:00:00 2001
From: lclc98
Date: Thu, 30 Jun 2016 00:49:21 +1000
Subject: Json format (#2)
* Added new format
* Fixed bug
* Updated Version
---
src/main/java/cuchaz/enigma/analysis/Access.java | 43 ++
.../enigma/analysis/BehaviorReferenceTreeNode.java | 93 +++
.../java/cuchaz/enigma/analysis/BridgeMarker.java | 43 ++
.../analysis/ClassImplementationsTreeNode.java | 78 ++
.../enigma/analysis/ClassInheritanceTreeNode.java | 83 +++
.../cuchaz/enigma/analysis/EntryReference.java | 126 ++++
.../java/cuchaz/enigma/analysis/EntryRenamer.java | 184 +++++
.../enigma/analysis/FieldReferenceTreeNode.java | 81 +++
.../cuchaz/enigma/analysis/JarClassIterator.java | 136 ++++
src/main/java/cuchaz/enigma/analysis/JarIndex.java | 802 +++++++++++++++++++++
.../analysis/MethodImplementationsTreeNode.java | 101 +++
.../enigma/analysis/MethodInheritanceTreeNode.java | 114 +++
.../cuchaz/enigma/analysis/ReferenceTreeNode.java | 19 +
.../enigma/analysis/RelatedMethodChecker.java | 104 +++
.../java/cuchaz/enigma/analysis/SourceIndex.java | 185 +++++
.../analysis/SourceIndexBehaviorVisitor.java | 131 ++++
.../enigma/analysis/SourceIndexClassVisitor.java | 100 +++
.../cuchaz/enigma/analysis/SourceIndexVisitor.java | 381 ++++++++++
src/main/java/cuchaz/enigma/analysis/Token.java | 56 ++
.../cuchaz/enigma/analysis/TranslationIndex.java | 282 ++++++++
.../cuchaz/enigma/analysis/TreeDumpVisitor.java | 441 +++++++++++
21 files changed, 3583 insertions(+)
create mode 100644 src/main/java/cuchaz/enigma/analysis/Access.java
create mode 100644 src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java
create mode 100644 src/main/java/cuchaz/enigma/analysis/BridgeMarker.java
create mode 100644 src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java
create mode 100644 src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java
create mode 100644 src/main/java/cuchaz/enigma/analysis/EntryReference.java
create mode 100644 src/main/java/cuchaz/enigma/analysis/EntryRenamer.java
create mode 100644 src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java
create mode 100644 src/main/java/cuchaz/enigma/analysis/JarClassIterator.java
create mode 100644 src/main/java/cuchaz/enigma/analysis/JarIndex.java
create mode 100644 src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java
create mode 100644 src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java
create mode 100644 src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java
create mode 100644 src/main/java/cuchaz/enigma/analysis/RelatedMethodChecker.java
create mode 100644 src/main/java/cuchaz/enigma/analysis/SourceIndex.java
create mode 100644 src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java
create mode 100644 src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java
create mode 100644 src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java
create mode 100644 src/main/java/cuchaz/enigma/analysis/Token.java
create mode 100644 src/main/java/cuchaz/enigma/analysis/TranslationIndex.java
create mode 100644 src/main/java/cuchaz/enigma/analysis/TreeDumpVisitor.java
(limited to 'src/main/java/cuchaz/enigma/analysis')
diff --git a/src/main/java/cuchaz/enigma/analysis/Access.java b/src/main/java/cuchaz/enigma/analysis/Access.java
new file mode 100644
index 0000000..877327f
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/Access.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.analysis;
+
+import java.lang.reflect.Modifier;
+
+import javassist.CtBehavior;
+import javassist.CtField;
+
+public enum Access {
+
+ Public,
+ Protected,
+ Private;
+
+ public static Access get(CtBehavior behavior) {
+ return get(behavior.getModifiers());
+ }
+
+ public static Access get(CtField field) {
+ return get(field.getModifiers());
+ }
+
+ public static Access get(int modifiers) {
+ if (Modifier.isPublic(modifiers)) {
+ return Public;
+ } else if (Modifier.isProtected(modifiers)) {
+ return Protected;
+ } else if (Modifier.isPrivate(modifiers)) {
+ return Private;
+ }
+ // assume public by default
+ return Public;
+ }
+}
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 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.analysis;
+
+import com.google.common.collect.Sets;
+
+import java.util.Set;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreeNode;
+
+import cuchaz.enigma.mapping.BehaviorEntry;
+import cuchaz.enigma.mapping.Entry;
+import cuchaz.enigma.mapping.Translator;
+
+public class BehaviorReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode {
+
+ private static final long serialVersionUID = -3658163700783307520L;
+
+ private Translator m_deobfuscatingTranslator;
+ private BehaviorEntry m_entry;
+ private EntryReference m_reference;
+ private Access m_access;
+
+ public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, BehaviorEntry entry) {
+ m_deobfuscatingTranslator = deobfuscatingTranslator;
+ m_entry = entry;
+ m_reference = null;
+ }
+
+ public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference reference, Access access) {
+ m_deobfuscatingTranslator = deobfuscatingTranslator;
+ m_entry = reference.entry;
+ m_reference = reference;
+ m_access = access;
+ }
+
+ @Override
+ public BehaviorEntry getEntry() {
+ return m_entry;
+ }
+
+ @Override
+ public EntryReference getReference() {
+ return m_reference;
+ }
+
+ @Override
+ public String toString() {
+ if (m_reference != null) {
+ return String.format("%s (%s)", m_deobfuscatingTranslator.translateEntry(m_reference.context), m_access);
+ }
+ return m_deobfuscatingTranslator.translateEntry(m_entry).toString();
+ }
+
+ public void load(JarIndex index, boolean recurse) {
+ // get all the child nodes
+ for (EntryReference reference : index.getBehaviorReferences(m_entry)) {
+ add(new BehaviorReferenceTreeNode(m_deobfuscatingTranslator, reference, index.getAccess(m_entry)));
+ }
+
+ if (recurse && children != null) {
+ for (Object child : children) {
+ if (child instanceof BehaviorReferenceTreeNode) {
+ BehaviorReferenceTreeNode node = (BehaviorReferenceTreeNode) child;
+
+ // don't recurse into ancestor
+ Set ancestors = Sets.newHashSet();
+ TreeNode n = (TreeNode) node;
+ while (n.getParent() != null) {
+ n = n.getParent();
+ if (n instanceof BehaviorReferenceTreeNode) {
+ ancestors.add(((BehaviorReferenceTreeNode) n).getEntry());
+ }
+ }
+ if (ancestors.contains(node.getEntry())) {
+ continue;
+ }
+
+ node.load(index, true);
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java b/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java
new file mode 100644
index 0000000..1df7625
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.analysis;
+
+import cuchaz.enigma.mapping.EntryFactory;
+import cuchaz.enigma.mapping.MethodEntry;
+import javassist.CtClass;
+import javassist.CtMethod;
+import javassist.bytecode.AccessFlag;
+
+public class BridgeMarker {
+
+ private JarIndex m_jarIndex;
+
+ public BridgeMarker(JarIndex jarIndex) {
+ m_jarIndex = jarIndex;
+ }
+
+ public void markBridges(CtClass c) {
+
+ for (CtMethod method : c.getDeclaredMethods()) {
+ MethodEntry methodEntry = EntryFactory.getMethodEntry(method);
+
+ // is this a bridge method?
+ MethodEntry bridgedMethodEntry = m_jarIndex.getBridgedMethod(methodEntry);
+ if (bridgedMethodEntry != null) {
+
+ // it's a bridge method! add the bridge flag
+ int flags = method.getMethodInfo().getAccessFlags();
+ flags |= AccessFlag.BRIDGE;
+ method.getMethodInfo().setAccessFlags(flags);
+ }
+ }
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java
new file mode 100644
index 0000000..8f8986c
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.analysis;
+
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+
+import cuchaz.enigma.mapping.ClassEntry;
+import cuchaz.enigma.mapping.MethodEntry;
+import cuchaz.enigma.mapping.Translator;
+
+public class ClassImplementationsTreeNode extends DefaultMutableTreeNode {
+
+ private static final long serialVersionUID = 3112703459157851912L;
+
+ private Translator m_deobfuscatingTranslator;
+ private ClassEntry m_entry;
+
+ public ClassImplementationsTreeNode(Translator deobfuscatingTranslator, ClassEntry entry) {
+ m_deobfuscatingTranslator = deobfuscatingTranslator;
+ m_entry = entry;
+ }
+
+ public ClassEntry getClassEntry() {
+ return m_entry;
+ }
+
+ public String getDeobfClassName() {
+ return m_deobfuscatingTranslator.translateClass(m_entry.getClassName());
+ }
+
+ @Override
+ public String toString() {
+ String className = getDeobfClassName();
+ if (className == null) {
+ className = m_entry.getClassName();
+ }
+ return className;
+ }
+
+ public void load(JarIndex index) {
+ // get all method implementations
+ List nodes = Lists.newArrayList();
+ for (String implementingClassName : index.getImplementingClasses(m_entry.getClassName())) {
+ nodes.add(new ClassImplementationsTreeNode(m_deobfuscatingTranslator, new ClassEntry(implementingClassName)));
+ }
+
+ // add them to this node
+ nodes.forEach(this::add);
+ }
+
+ public static ClassImplementationsTreeNode findNode(ClassImplementationsTreeNode node, MethodEntry entry) {
+ // is this the node?
+ if (node.m_entry.equals(entry)) {
+ return node;
+ }
+
+ // recurse
+ for (int i = 0; i < node.getChildCount(); i++) {
+ ClassImplementationsTreeNode foundNode = findNode((ClassImplementationsTreeNode) node.getChildAt(i), entry);
+ if (foundNode != null) {
+ return foundNode;
+ }
+ }
+ return null;
+ }
+}
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 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.analysis;
+
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+
+import cuchaz.enigma.mapping.ClassEntry;
+import cuchaz.enigma.mapping.Translator;
+
+public class ClassInheritanceTreeNode extends DefaultMutableTreeNode {
+
+ private static final long serialVersionUID = 4432367405826178490L;
+
+ private Translator m_deobfuscatingTranslator;
+ private String m_obfClassName;
+
+ public ClassInheritanceTreeNode(Translator deobfuscatingTranslator, String obfClassName) {
+ m_deobfuscatingTranslator = deobfuscatingTranslator;
+ m_obfClassName = obfClassName;
+ }
+
+ public String getObfClassName() {
+ return m_obfClassName;
+ }
+
+ public String getDeobfClassName() {
+ return m_deobfuscatingTranslator.translateClass(m_obfClassName);
+ }
+
+ @Override
+ public String toString() {
+ String deobfClassName = getDeobfClassName();
+ if (deobfClassName != null) {
+ return deobfClassName;
+ }
+ return m_obfClassName;
+ }
+
+ public void load(TranslationIndex ancestries, boolean recurse) {
+ // get all the child nodes
+ List nodes = Lists.newArrayList();
+ for (ClassEntry subclassEntry : ancestries.getSubclass(new ClassEntry(m_obfClassName))) {
+ nodes.add(new ClassInheritanceTreeNode(m_deobfuscatingTranslator, subclassEntry.getName()));
+ }
+
+ // add them to this node
+ nodes.forEach(this::add);
+
+ if (recurse) {
+ for (ClassInheritanceTreeNode node : nodes) {
+ node.load(ancestries, true);
+ }
+ }
+ }
+
+ public static ClassInheritanceTreeNode findNode(ClassInheritanceTreeNode node, ClassEntry entry) {
+ // is this the node?
+ if (node.getObfClassName().equals(entry.getName())) {
+ return node;
+ }
+
+ // recurse
+ for (int i = 0; i < node.getChildCount(); i++) {
+ ClassInheritanceTreeNode foundNode = findNode((ClassInheritanceTreeNode) node.getChildAt(i), entry);
+ if (foundNode != null) {
+ return foundNode;
+ }
+ }
+ return null;
+ }
+}
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 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.analysis;
+
+import java.util.Arrays;
+import java.util.List;
+
+import cuchaz.enigma.Util;
+import cuchaz.enigma.mapping.ClassEntry;
+import cuchaz.enigma.mapping.ConstructorEntry;
+import cuchaz.enigma.mapping.Entry;
+
+public class EntryReference {
+
+ private static final List ConstructorNonNames = Arrays.asList("this", "super", "static");
+ public E entry;
+ public C context;
+
+ private boolean m_isNamed;
+
+ public EntryReference(E entry, String sourceName) {
+ this(entry, sourceName, null);
+ }
+
+ public EntryReference(E entry, String sourceName, C context) {
+ if (entry == null) {
+ throw new IllegalArgumentException("Entry cannot be null!");
+ }
+
+ this.entry = entry;
+ this.context = context;
+
+ m_isNamed = sourceName != null && sourceName.length() > 0;
+ if (entry instanceof ConstructorEntry && ConstructorNonNames.contains(sourceName)) {
+ m_isNamed = false;
+ }
+ }
+
+ public EntryReference(E entry, C context, EntryReference other) {
+ this.entry = entry;
+ this.context = context;
+ m_isNamed = other.m_isNamed;
+ }
+
+ public ClassEntry getLocationClassEntry() {
+ if (context != null) {
+ return context.getClassEntry();
+ }
+ return entry.getClassEntry();
+ }
+
+ public boolean isNamed() {
+ return m_isNamed;
+ }
+
+ public Entry getNameableEntry() {
+ if (entry instanceof ConstructorEntry) {
+ // renaming a constructor really means renaming the class
+ return entry.getClassEntry();
+ }
+ return entry;
+ }
+
+ public String getNamableName() {
+ if (getNameableEntry() instanceof ClassEntry) {
+ ClassEntry classEntry = (ClassEntry) getNameableEntry();
+ if (classEntry.isInnerClass()) {
+ // make sure we only rename the inner class name
+ return classEntry.getInnermostClassName();
+ }
+ }
+
+ return getNameableEntry().getName();
+ }
+
+ @Override
+ public int hashCode() {
+ if (context != null) {
+ return Util.combineHashesOrdered(entry.hashCode(), context.hashCode());
+ }
+ return entry.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof EntryReference) {
+ return equals((EntryReference, ?>) other);
+ }
+ return false;
+ }
+
+ public boolean equals(EntryReference, ?> other) {
+ // check entry first
+ boolean isEntrySame = entry.equals(other.entry);
+ if (!isEntrySame) {
+ return false;
+ }
+
+ // check caller
+ if (context == null && other.context == null) {
+ return true;
+ } else if (context != null && other.context != null) {
+ return context.equals(other.context);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+ buf.append(entry);
+ if (context != null) {
+ buf.append(" called from ");
+ buf.append(context);
+ }
+ return buf.toString();
+ }
+}
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 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.analysis;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+
+import java.util.AbstractMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import cuchaz.enigma.mapping.*;
+
+public class EntryRenamer {
+
+ public static void renameClassesInSet(Map renames, Set set) {
+ List entries = Lists.newArrayList();
+ for (T val : set) {
+ entries.add(renameClassesInThing(renames, val));
+ }
+ set.clear();
+ set.addAll(entries);
+ }
+
+ public static void renameClassesInMap(Map renames, Map map) {
+ // for each key/value pair...
+ Set> entriesToAdd = Sets.newHashSet();
+ for (Map.Entry entry : map.entrySet()) {
+ entriesToAdd.add(new AbstractMap.SimpleEntry(
+ renameClassesInThing(renames, entry.getKey()),
+ renameClassesInThing(renames, entry.getValue())
+ ));
+ }
+ map.clear();
+ for (Map.Entry entry : entriesToAdd) {
+ map.put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ public static void renameClassesInMultimap(Map renames, Multimap map) {
+ // for each key/value pair...
+ Set> entriesToAdd = Sets.newHashSet();
+ for (Map.Entry entry : map.entries()) {
+ entriesToAdd.add(new AbstractMap.SimpleEntry(
+ renameClassesInThing(renames, entry.getKey()),
+ renameClassesInThing(renames, entry.getValue())
+ ));
+ }
+ map.clear();
+ for (Map.Entry entry : entriesToAdd) {
+ map.put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ public static void renameMethodsInMultimap(Map renames, Multimap map) {
+ // for each key/value pair...
+ Set> entriesToAdd = Sets.newHashSet();
+ for (Map.Entry entry : map.entries()) {
+ entriesToAdd.add(new AbstractMap.SimpleEntry(
+ renameMethodsInThing(renames, entry.getKey()),
+ renameMethodsInThing(renames, entry.getValue())
+ ));
+ }
+ map.clear();
+ for (Map.Entry entry : entriesToAdd) {
+ map.put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ public static void renameMethodsInMap(Map renames, Map map) {
+ // for each key/value pair...
+ Set> entriesToAdd = Sets.newHashSet();
+ for (Map.Entry entry : map.entrySet()) {
+ entriesToAdd.add(new AbstractMap.SimpleEntry(
+ renameMethodsInThing(renames, entry.getKey()),
+ renameMethodsInThing(renames, entry.getValue())
+ ));
+ }
+ map.clear();
+ for (Map.Entry entry : entriesToAdd) {
+ map.put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static T renameMethodsInThing(Map renames, T thing) {
+ if (thing instanceof MethodEntry) {
+ MethodEntry methodEntry = (MethodEntry) thing;
+ MethodEntry newMethodEntry = renames.get(methodEntry);
+ if (newMethodEntry != null) {
+ return (T) new MethodEntry(
+ methodEntry.getClassEntry(),
+ newMethodEntry.getName(),
+ methodEntry.getSignature()
+ );
+ }
+ return thing;
+ } else if (thing instanceof ArgumentEntry) {
+ ArgumentEntry argumentEntry = (ArgumentEntry) thing;
+ return (T) new ArgumentEntry(
+ renameMethodsInThing(renames, argumentEntry.getBehaviorEntry()),
+ argumentEntry.getIndex(),
+ argumentEntry.getName()
+ );
+ } else if (thing instanceof EntryReference) {
+ EntryReference reference = (EntryReference) thing;
+ reference.entry = renameMethodsInThing(renames, reference.entry);
+ reference.context = renameMethodsInThing(renames, reference.context);
+ return thing;
+ }
+ return thing;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static T renameClassesInThing(final Map renames, T thing) {
+ if (thing instanceof String) {
+ String stringEntry = (String) thing;
+ if (renames.containsKey(stringEntry)) {
+ return (T) renames.get(stringEntry);
+ }
+ } else if (thing instanceof ClassEntry) {
+ ClassEntry classEntry = (ClassEntry) thing;
+ return (T) new ClassEntry(renameClassesInThing(renames, classEntry.getClassName()));
+ } else if (thing instanceof FieldEntry) {
+ FieldEntry fieldEntry = (FieldEntry) thing;
+ return (T) new FieldEntry(
+ renameClassesInThing(renames, fieldEntry.getClassEntry()),
+ fieldEntry.getName(),
+ renameClassesInThing(renames, fieldEntry.getType())
+ );
+ } else if (thing instanceof ConstructorEntry) {
+ ConstructorEntry constructorEntry = (ConstructorEntry) thing;
+ return (T) new ConstructorEntry(
+ renameClassesInThing(renames, constructorEntry.getClassEntry()),
+ renameClassesInThing(renames, constructorEntry.getSignature())
+ );
+ } else if (thing instanceof MethodEntry) {
+ MethodEntry methodEntry = (MethodEntry) thing;
+ return (T) new MethodEntry(
+ renameClassesInThing(renames, methodEntry.getClassEntry()),
+ methodEntry.getName(),
+ renameClassesInThing(renames, methodEntry.getSignature())
+ );
+ } else if (thing instanceof ArgumentEntry) {
+ ArgumentEntry argumentEntry = (ArgumentEntry) thing;
+ return (T) new ArgumentEntry(
+ renameClassesInThing(renames, argumentEntry.getBehaviorEntry()),
+ argumentEntry.getIndex(),
+ argumentEntry.getName()
+ );
+ } else if (thing instanceof EntryReference) {
+ EntryReference reference = (EntryReference) thing;
+ reference.entry = renameClassesInThing(renames, reference.entry);
+ reference.context = renameClassesInThing(renames, reference.context);
+ return thing;
+ } else if (thing instanceof Signature) {
+ return (T) new Signature((Signature) thing, new ClassNameReplacer() {
+ @Override
+ public String replace(String className) {
+ return renameClassesInThing(renames, className);
+ }
+ });
+ } else if (thing instanceof Type) {
+ return (T) new Type((Type) thing, new ClassNameReplacer() {
+ @Override
+ public String replace(String className) {
+ return renameClassesInThing(renames, className);
+ }
+ });
+ }
+
+ return thing;
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java
new file mode 100644
index 0000000..4b302e0
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.analysis;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+
+import cuchaz.enigma.mapping.BehaviorEntry;
+import cuchaz.enigma.mapping.FieldEntry;
+import cuchaz.enigma.mapping.Translator;
+
+public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode {
+
+ private static final long serialVersionUID = -7934108091928699835L;
+
+ private Translator m_deobfuscatingTranslator;
+ private FieldEntry m_entry;
+ private EntryReference m_reference;
+ private Access m_access;
+
+ public FieldReferenceTreeNode(Translator deobfuscatingTranslator, FieldEntry entry) {
+ m_deobfuscatingTranslator = deobfuscatingTranslator;
+ m_entry = entry;
+ m_reference = null;
+ }
+
+ private FieldReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference reference, Access access) {
+ m_deobfuscatingTranslator = deobfuscatingTranslator;
+ m_entry = reference.entry;
+ m_reference = reference;
+ m_access = access;
+ }
+
+ @Override
+ public FieldEntry getEntry() {
+ return m_entry;
+ }
+
+ @Override
+ public EntryReference getReference() {
+ return m_reference;
+ }
+
+ @Override
+ public String toString() {
+ if (m_reference != null) {
+ return String.format("%s (%s)", m_deobfuscatingTranslator.translateEntry(m_reference.context), m_access);
+ }
+ return m_deobfuscatingTranslator.translateEntry(m_entry).toString();
+ }
+
+ public void load(JarIndex index, boolean recurse) {
+ // get all the child nodes
+ if (m_reference == null) {
+ for (EntryReference reference : index.getFieldReferences(m_entry)) {
+ add(new FieldReferenceTreeNode(m_deobfuscatingTranslator, reference, index.getAccess(m_entry)));
+ }
+ } else {
+ for (EntryReference reference : index.getBehaviorReferences(m_reference.context)) {
+ add(new BehaviorReferenceTreeNode(m_deobfuscatingTranslator, reference, index.getAccess(m_reference.context)));
+ }
+ }
+
+ if (recurse && children != null) {
+ for (Object node : children) {
+ if (node instanceof BehaviorReferenceTreeNode) {
+ ((BehaviorReferenceTreeNode) node).load(index, true);
+ } else if (node instanceof FieldReferenceTreeNode) {
+ ((FieldReferenceTreeNode) node).load(index, true);
+ }
+ }
+ }
+ }
+}
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 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.analysis;
+
+import com.google.common.collect.Lists;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import cuchaz.enigma.Constants;
+import cuchaz.enigma.mapping.ClassEntry;
+import javassist.ByteArrayClassPath;
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.NotFoundException;
+import javassist.bytecode.Descriptor;
+
+public class JarClassIterator implements Iterator {
+
+ private JarFile m_jar;
+ private Iterator m_iter;
+
+ public JarClassIterator(JarFile jar) {
+ m_jar = jar;
+
+ // get the jar entries that correspond to classes
+ List classEntries = Lists.newArrayList();
+ Enumeration entries = m_jar.entries();
+ while (entries.hasMoreElements()) {
+ JarEntry entry = entries.nextElement();
+
+ // is this a class file?
+ if (entry.getName().endsWith(".class")) {
+ classEntries.add(entry);
+ }
+ }
+ m_iter = classEntries.iterator();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return m_iter.hasNext();
+ }
+
+ @Override
+ public CtClass next() {
+ JarEntry entry = m_iter.next();
+ try {
+ return getClass(m_jar, entry);
+ } catch (IOException | NotFoundException ex) {
+ throw new Error("Unable to load class: " + entry.getName());
+ }
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ public static List getClassEntries(JarFile jar) {
+ List classEntries = Lists.newArrayList();
+ Enumeration entries = jar.entries();
+ while (entries.hasMoreElements()) {
+ JarEntry entry = entries.nextElement();
+
+ // is this a class file?
+ if (!entry.isDirectory() && entry.getName().endsWith(".class")) {
+ classEntries.add(getClassEntry(entry));
+ }
+ }
+ return classEntries;
+ }
+
+ public static Iterable classes(final JarFile jar) {
+ return new Iterable() {
+ @Override
+ public Iterator iterator() {
+ return new JarClassIterator(jar);
+ }
+ };
+ }
+
+ public static CtClass getClass(JarFile jar, ClassEntry classEntry) {
+ try {
+ return getClass(jar, new JarEntry(classEntry.getName() + ".class"));
+ } catch (IOException | NotFoundException ex) {
+ throw new Error("Unable to load class: " + classEntry.getName());
+ }
+ }
+
+ private static CtClass getClass(JarFile jar, JarEntry entry) throws IOException, NotFoundException {
+ // read the class into a buffer
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ byte[] buf = new byte[Constants.KiB];
+ int totalNumBytesRead = 0;
+ InputStream in = jar.getInputStream(entry);
+ while (in.available() > 0) {
+ int numBytesRead = in.read(buf);
+ if (numBytesRead < 0) {
+ break;
+ }
+ bos.write(buf, 0, numBytesRead);
+
+ // sanity checking
+ totalNumBytesRead += numBytesRead;
+ if (totalNumBytesRead > Constants.MiB) {
+ throw new Error("Class file " + entry.getName() + " larger than 1 MiB! Something is wrong!");
+ }
+ }
+
+ // get a javassist handle for the class
+ String className = Descriptor.toJavaName(getClassEntry(entry).getName());
+ ClassPool classPool = new ClassPool();
+ classPool.appendSystemPath();
+ classPool.insertClassPath(new ByteArrayClassPath(className, bos.toByteArray()));
+ return classPool.get(className);
+ }
+
+ private static ClassEntry getClassEntry(JarEntry entry) {
+ return new ClassEntry(entry.getName().substring(0, entry.getName().length() - ".class".length()));
+ }
+}
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 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.analysis;
+
+import com.google.common.collect.*;
+
+import java.lang.reflect.Modifier;
+import java.util.*;
+import java.util.jar.JarFile;
+
+import cuchaz.enigma.Constants;
+import cuchaz.enigma.bytecode.ClassRenamer;
+import cuchaz.enigma.mapping.*;
+import cuchaz.enigma.mapping.Translator;
+import javassist.*;
+import javassist.bytecode.*;
+import javassist.expr.*;
+
+public class JarIndex {
+
+ private Set m_obfClassEntries;
+ private TranslationIndex m_translationIndex;
+ private Map m_access;
+ private Multimap m_fields;
+ private Multimap m_behaviors;
+ private Multimap m_methodImplementations;
+ private Multimap> m_behaviorReferences;
+ private Multimap> m_fieldReferences;
+ private Multimap m_innerClassesByOuter;
+ private Map m_outerClassesByInner;
+ private Map m_anonymousClasses;
+ private Map m_bridgedMethods;
+
+ public JarIndex() {
+ m_obfClassEntries = Sets.newHashSet();
+ m_translationIndex = new TranslationIndex();
+ m_access = Maps.newHashMap();
+ m_fields = HashMultimap.create();
+ m_behaviors = HashMultimap.create();
+ m_methodImplementations = HashMultimap.create();
+ m_behaviorReferences = HashMultimap.create();
+ m_fieldReferences = HashMultimap.create();
+ m_innerClassesByOuter = HashMultimap.create();
+ m_outerClassesByInner = Maps.newHashMap();
+ m_anonymousClasses = Maps.newHashMap();
+ m_bridgedMethods = Maps.newHashMap();
+ }
+
+ public void indexJar(JarFile jar, boolean buildInnerClasses) {
+
+ // step 1: read the class names
+ for (ClassEntry classEntry : JarClassIterator.getClassEntries(jar)) {
+ if (classEntry.isInDefaultPackage()) {
+ // move out of default package
+ classEntry = new ClassEntry(Constants.NonePackage + "/" + classEntry.getName());
+ }
+ m_obfClassEntries.add(classEntry);
+ }
+
+ // step 2: index field/method/constructor access
+ for (CtClass c : JarClassIterator.classes(jar)) {
+ ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
+ for (CtField field : c.getDeclaredFields()) {
+ FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
+ m_access.put(fieldEntry, Access.get(field));
+ m_fields.put(fieldEntry.getClassEntry(), fieldEntry);
+ }
+ for (CtBehavior behavior : c.getDeclaredBehaviors()) {
+ BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
+ m_access.put(behaviorEntry, Access.get(behavior));
+ m_behaviors.put(behaviorEntry.getClassEntry(), behaviorEntry);
+ }
+ }
+
+ // step 3: index extends, implements, fields, and methods
+ for (CtClass c : JarClassIterator.classes(jar)) {
+ ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
+ m_translationIndex.indexClass(c);
+ String className = Descriptor.toJvmName(c.getName());
+ for (String interfaceName : c.getClassFile().getInterfaces()) {
+ className = Descriptor.toJvmName(className);
+ interfaceName = Descriptor.toJvmName(interfaceName);
+ if (className.equals(interfaceName)) {
+ throw new IllegalArgumentException("Class cannot be its own interface! " + className);
+ }
+ }
+ for (CtBehavior behavior : c.getDeclaredBehaviors()) {
+ indexBehavior(behavior);
+ }
+ }
+
+ // step 4: index field, method, constructor references
+ for (CtClass c : JarClassIterator.classes(jar)) {
+ ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
+ for (CtBehavior behavior : c.getDeclaredBehaviors()) {
+ indexBehaviorReferences(behavior);
+ }
+ }
+
+ if (buildInnerClasses) {
+
+ // step 5: index inner classes and anonymous classes
+ for (CtClass c : JarClassIterator.classes(jar)) {
+ ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
+ ClassEntry innerClassEntry = EntryFactory.getClassEntry(c);
+ ClassEntry outerClassEntry = findOuterClass(c);
+ if (outerClassEntry != null) {
+ m_innerClassesByOuter.put(outerClassEntry, innerClassEntry);
+ boolean innerWasAdded = m_outerClassesByInner.put(innerClassEntry, outerClassEntry) == null;
+ assert (innerWasAdded);
+
+ BehaviorEntry enclosingBehavior = isAnonymousClass(c, outerClassEntry);
+ if (enclosingBehavior != null) {
+ m_anonymousClasses.put(innerClassEntry, enclosingBehavior);
+
+ // DEBUG
+ //System.out.println("ANONYMOUS: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName());
+ } else {
+ // DEBUG
+ //System.out.println("INNER: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName());
+ }
+ }
+ }
+
+ // step 6: update other indices with inner class info
+ Map renames = Maps.newHashMap();
+ for (ClassEntry innerClassEntry : m_innerClassesByOuter.values()) {
+ String newName = innerClassEntry.buildClassEntry(getObfClassChain(innerClassEntry)).getName();
+ if (!innerClassEntry.getName().equals(newName)) {
+ // DEBUG
+ //System.out.println("REPLACE: " + innerClassEntry.getName() + " WITH " + newName);
+ renames.put(innerClassEntry.getName(), newName);
+ }
+ }
+ EntryRenamer.renameClassesInSet(renames, m_obfClassEntries);
+ m_translationIndex.renameClasses(renames);
+ EntryRenamer.renameClassesInMultimap(renames, m_methodImplementations);
+ EntryRenamer.renameClassesInMultimap(renames, m_behaviorReferences);
+ EntryRenamer.renameClassesInMultimap(renames, m_fieldReferences);
+ EntryRenamer.renameClassesInMap(renames, m_access);
+ }
+ }
+
+ private void indexBehavior(CtBehavior behavior) {
+ // get the behavior entry
+ final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
+ if (behaviorEntry instanceof MethodEntry) {
+ MethodEntry methodEntry = (MethodEntry) behaviorEntry;
+
+ // index implementation
+ m_methodImplementations.put(behaviorEntry.getClassName(), methodEntry);
+
+ // look for bridge and bridged methods
+ CtMethod bridgedMethod = getBridgedMethod((CtMethod) behavior);
+ if (bridgedMethod != null) {
+ m_bridgedMethods.put(methodEntry, EntryFactory.getMethodEntry(bridgedMethod));
+ }
+ }
+ // looks like we don't care about constructors here
+ }
+
+ private void indexBehaviorReferences(CtBehavior behavior) {
+ // index method calls
+ final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
+ try {
+ behavior.instrument(new ExprEditor() {
+ @Override
+ public void edit(MethodCall call) {
+ MethodEntry calledMethodEntry = EntryFactory.getMethodEntry(call);
+ ClassEntry resolvedClassEntry = m_translationIndex.resolveEntryClass(calledMethodEntry);
+ if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledMethodEntry.getClassEntry())) {
+ calledMethodEntry = new MethodEntry(
+ resolvedClassEntry,
+ calledMethodEntry.getName(),
+ calledMethodEntry.getSignature()
+ );
+ }
+ EntryReference reference = new EntryReference(
+ calledMethodEntry,
+ call.getMethodName(),
+ behaviorEntry
+ );
+ m_behaviorReferences.put(calledMethodEntry, reference);
+ }
+
+ @Override
+ public void edit(FieldAccess call) {
+ FieldEntry calledFieldEntry = EntryFactory.getFieldEntry(call);
+ ClassEntry resolvedClassEntry = m_translationIndex.resolveEntryClass(calledFieldEntry);
+ if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledFieldEntry.getClassEntry())) {
+ calledFieldEntry = new FieldEntry(calledFieldEntry, resolvedClassEntry);
+ }
+ EntryReference reference = new EntryReference(
+ calledFieldEntry,
+ call.getFieldName(),
+ behaviorEntry
+ );
+ m_fieldReferences.put(calledFieldEntry, reference);
+ }
+
+ @Override
+ public void edit(ConstructorCall call) {
+ ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call);
+ EntryReference reference = new EntryReference(
+ calledConstructorEntry,
+ call.getMethodName(),
+ behaviorEntry
+ );
+ m_behaviorReferences.put(calledConstructorEntry, reference);
+ }
+
+ @Override
+ public void edit(NewExpr call) {
+ ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call);
+ EntryReference reference = new EntryReference(
+ calledConstructorEntry,
+ call.getClassName(),
+ behaviorEntry
+ );
+ m_behaviorReferences.put(calledConstructorEntry, reference);
+ }
+ });
+ } catch (CannotCompileException ex) {
+ throw new Error(ex);
+ }
+ }
+
+ private CtMethod getBridgedMethod(CtMethod method) {
+
+ // bridge methods just call another method, cast it to the return type, and return the result
+ // let's see if we can detect this scenario
+
+ // skip non-synthetic methods
+ if ((method.getModifiers() & AccessFlag.SYNTHETIC) == 0) {
+ return null;
+ }
+
+ // get all the called methods
+ final List methodCalls = Lists.newArrayList();
+ try {
+ method.instrument(new ExprEditor() {
+ @Override
+ public void edit(MethodCall call) {
+ methodCalls.add(call);
+ }
+ });
+ } catch (CannotCompileException ex) {
+ // this is stupid... we're not even compiling anything
+ throw new Error(ex);
+ }
+
+ // is there just one?
+ if (methodCalls.size() != 1) {
+ return null;
+ }
+ MethodCall call = methodCalls.get(0);
+
+ try {
+ // we have a bridge method!
+ return call.getMethod();
+ } catch (NotFoundException ex) {
+ // can't find the type? not a bridge method
+ return null;
+ }
+ }
+
+ private ClassEntry findOuterClass(CtClass c) {
+
+ ClassEntry classEntry = EntryFactory.getClassEntry(c);
+
+ // does this class already have an outer class?
+ if (classEntry.isInnerClass()) {
+ return classEntry.getOuterClassEntry();
+ }
+
+ // inner classes:
+ // have constructors that can (illegally) set synthetic fields
+ // the outer class is the only class that calls constructors
+
+ // use the synthetic fields to find the synthetic constructors
+ for (CtConstructor constructor : c.getDeclaredConstructors()) {
+ Set syntheticFieldTypes = Sets.newHashSet();
+ if (!isIllegalConstructor(syntheticFieldTypes, constructor)) {
+ continue;
+ }
+
+ ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor);
+
+ // gather the classes from the illegally-set synthetic fields
+ Set illegallySetClasses = Sets.newHashSet();
+ for (String type : syntheticFieldTypes) {
+ if (type.startsWith("L")) {
+ ClassEntry outerClassEntry = new ClassEntry(type.substring(1, type.length() - 1));
+ if (isSaneOuterClass(outerClassEntry, classEntry)) {
+ illegallySetClasses.add(outerClassEntry);
+ }
+ }
+ }
+
+ // who calls this constructor?
+ Set callerClasses = Sets.newHashSet();
+ for (EntryReference reference : getBehaviorReferences(constructorEntry)) {
+
+ // make sure it's not a call to super
+ if (reference.entry instanceof ConstructorEntry && reference.context instanceof ConstructorEntry) {
+
+ // is the entry a superclass of the context?
+ ClassEntry calledClassEntry = reference.entry.getClassEntry();
+ ClassEntry superclassEntry = m_translationIndex.getSuperclass(reference.context.getClassEntry());
+ if (superclassEntry != null && superclassEntry.equals(calledClassEntry)) {
+ // it's a super call, skip
+ continue;
+ }
+ }
+
+ if (isSaneOuterClass(reference.context.getClassEntry(), classEntry)) {
+ callerClasses.add(reference.context.getClassEntry());
+ }
+ }
+
+ // do we have an answer yet?
+ if (callerClasses.isEmpty()) {
+ if (illegallySetClasses.size() == 1) {
+ return illegallySetClasses.iterator().next();
+ } else {
+ System.out.println(String.format("WARNING: Unable to find outer class for %s. No caller and no illegally set field classes.", classEntry));
+ }
+ } else {
+ if (callerClasses.size() == 1) {
+ return callerClasses.iterator().next();
+ } else {
+ // multiple callers, do the illegally set classes narrow it down?
+ Set intersection = Sets.newHashSet(callerClasses);
+ intersection.retainAll(illegallySetClasses);
+ if (intersection.size() == 1) {
+ return intersection.iterator().next();
+ } else {
+ System.out.println(String.format("WARNING: Unable to choose outer class for %s among options: %s", classEntry, callerClasses));
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private boolean isSaneOuterClass(ClassEntry outerClassEntry, ClassEntry innerClassEntry) {
+
+ // clearly this would be silly
+ if (outerClassEntry.equals(innerClassEntry)) {
+ return false;
+ }
+
+ // is the outer class in the jar?
+ return m_obfClassEntries.contains(outerClassEntry);
+
+ }
+
+ @SuppressWarnings("unchecked")
+ private boolean isIllegalConstructor(Set syntheticFieldTypes, CtConstructor constructor) {
+
+ // illegal constructors only set synthetic member fields, then call super()
+ String className = constructor.getDeclaringClass().getName();
+
+ // collect all the field accesses, constructor calls, and method calls
+ final List illegalFieldWrites = Lists.newArrayList();
+ final List constructorCalls = Lists.newArrayList();
+ try {
+ constructor.instrument(new ExprEditor() {
+ @Override
+ public void edit(FieldAccess fieldAccess) {
+ if (fieldAccess.isWriter() && constructorCalls.isEmpty()) {
+ illegalFieldWrites.add(fieldAccess);
+ }
+ }
+
+ @Override
+ public void edit(ConstructorCall constructorCall) {
+ constructorCalls.add(constructorCall);
+ }
+ });
+ } catch (CannotCompileException ex) {
+ // we're not compiling anything... this is stupid
+ throw new Error(ex);
+ }
+
+ // are there any illegal field writes?
+ if (illegalFieldWrites.isEmpty()) {
+ return false;
+ }
+
+ // are all the writes to synthetic fields?
+ for (FieldAccess fieldWrite : illegalFieldWrites) {
+
+ // all illegal writes have to be to the local class
+ if (!fieldWrite.getClassName().equals(className)) {
+ System.err.println(String.format("WARNING: illegal write to non-member field %s.%s", fieldWrite.getClassName(), fieldWrite.getFieldName()));
+ return false;
+ }
+
+ // find the field
+ FieldInfo fieldInfo = null;
+ for (FieldInfo info : (List) constructor.getDeclaringClass().getClassFile().getFields()) {
+ if (info.getName().equals(fieldWrite.getFieldName()) && info.getDescriptor().equals(fieldWrite.getSignature())) {
+ fieldInfo = info;
+ break;
+ }
+ }
+ if (fieldInfo == null) {
+ // field is in a superclass or something, can't be a local synthetic member
+ return false;
+ }
+
+ // is this field synthetic?
+ boolean isSynthetic = (fieldInfo.getAccessFlags() & AccessFlag.SYNTHETIC) != 0;
+ if (isSynthetic) {
+ syntheticFieldTypes.add(fieldInfo.getDescriptor());
+ } else {
+ System.err.println(String.format("WARNING: illegal write to non synthetic field %s %s.%s", fieldInfo.getDescriptor(), className, fieldInfo.getName()));
+ return false;
+ }
+ }
+
+ // we passed all the tests!
+ return true;
+ }
+
+ private BehaviorEntry isAnonymousClass(CtClass c, ClassEntry outerClassEntry) {
+
+ // is this class already marked anonymous?
+ EnclosingMethodAttribute enclosingMethodAttribute = (EnclosingMethodAttribute) c.getClassFile().getAttribute(EnclosingMethodAttribute.tag);
+ if (enclosingMethodAttribute != null) {
+ if (enclosingMethodAttribute.methodIndex() > 0) {
+ return EntryFactory.getBehaviorEntry(
+ Descriptor.toJvmName(enclosingMethodAttribute.className()),
+ enclosingMethodAttribute.methodName(),
+ enclosingMethodAttribute.methodDescriptor()
+ );
+ } else {
+ // an attribute but no method? assume not anonymous
+ return null;
+ }
+ }
+
+ // if there's an inner class attribute, but not an enclosing method attribute, then it's not anonymous
+ InnerClassesAttribute innerClassesAttribute = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag);
+ if (innerClassesAttribute != null) {
+ return null;
+ }
+
+ ClassEntry innerClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
+
+ // anonymous classes:
+ // can't be abstract
+ // have only one constructor
+ // it's called exactly once by the outer class
+ // the type the instance is assigned to can't be this type
+
+ // is abstract?
+ if (Modifier.isAbstract(c.getModifiers())) {
+ return null;
+ }
+
+ // is there exactly one constructor?
+ if (c.getDeclaredConstructors().length != 1) {
+ return null;
+ }
+ CtConstructor constructor = c.getDeclaredConstructors()[0];
+
+ // is this constructor called exactly once?
+ ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor);
+ Collection> references = getBehaviorReferences(constructorEntry);
+ if (references.size() != 1) {
+ return null;
+ }
+
+ // does the caller use this type?
+ BehaviorEntry caller = references.iterator().next().context;
+ for (FieldEntry fieldEntry : getReferencedFields(caller)) {
+ if (fieldEntry.getType().hasClass() && fieldEntry.getType().getClassEntry().equals(innerClassEntry)) {
+ // caller references this type, so it can't be anonymous
+ return null;
+ }
+ }
+ for (BehaviorEntry behaviorEntry : getReferencedBehaviors(caller)) {
+ if (behaviorEntry.getSignature().hasClass(innerClassEntry)) {
+ return null;
+ }
+ }
+
+ return caller;
+ }
+
+ public Set getObfClassEntries() {
+ return m_obfClassEntries;
+ }
+
+ public Collection getObfFieldEntries() {
+ return m_fields.values();
+ }
+
+ public Collection getObfFieldEntries(ClassEntry classEntry) {
+ return m_fields.get(classEntry);
+ }
+
+ public Collection getObfBehaviorEntries() {
+ return m_behaviors.values();
+ }
+
+ public Collection getObfBehaviorEntries(ClassEntry classEntry) {
+ return m_behaviors.get(classEntry);
+ }
+
+ public TranslationIndex getTranslationIndex() {
+ return m_translationIndex;
+ }
+
+ public Access getAccess(Entry entry) {
+ return m_access.get(entry);
+ }
+
+ public ClassInheritanceTreeNode getClassInheritance(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) {
+
+ // get the root node
+ List ancestry = Lists.newArrayList();
+ ancestry.add(obfClassEntry.getName());
+ for (ClassEntry classEntry : m_translationIndex.getAncestry(obfClassEntry)) {
+ if (containsObfClass(classEntry)) {
+ ancestry.add(classEntry.getName());
+ }
+ }
+ ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode(
+ deobfuscatingTranslator,
+ ancestry.get(ancestry.size() - 1)
+ );
+
+ // expand all children recursively
+ rootNode.load(m_translationIndex, true);
+
+ return rootNode;
+ }
+
+ public ClassImplementationsTreeNode getClassImplementations(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) {
+
+ // is this even an interface?
+ if (isInterface(obfClassEntry.getClassName())) {
+ ClassImplementationsTreeNode node = new ClassImplementationsTreeNode(deobfuscatingTranslator, obfClassEntry);
+ node.load(this);
+ return node;
+ }
+ return null;
+ }
+
+ public MethodInheritanceTreeNode getMethodInheritance(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) {
+
+ // travel to the ancestor implementation
+ ClassEntry baseImplementationClassEntry = obfMethodEntry.getClassEntry();
+ for (ClassEntry ancestorClassEntry : m_translationIndex.getAncestry(obfMethodEntry.getClassEntry())) {
+ MethodEntry ancestorMethodEntry = new MethodEntry(
+ new ClassEntry(ancestorClassEntry),
+ obfMethodEntry.getName(),
+ obfMethodEntry.getSignature()
+ );
+ if (containsObfBehavior(ancestorMethodEntry)) {
+ baseImplementationClassEntry = ancestorClassEntry;
+ }
+ }
+
+ // make a root node at the base
+ MethodEntry methodEntry = new MethodEntry(
+ baseImplementationClassEntry,
+ obfMethodEntry.getName(),
+ obfMethodEntry.getSignature()
+ );
+ MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode(
+ deobfuscatingTranslator,
+ methodEntry,
+ containsObfBehavior(methodEntry)
+ );
+
+ // expand the full tree
+ rootNode.load(this, true);
+
+ return rootNode;
+ }
+
+ public List getMethodImplementations(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) {
+
+ List interfaceMethodEntries = Lists.newArrayList();
+
+ // is this method on an interface?
+ if (isInterface(obfMethodEntry.getClassName())) {
+ interfaceMethodEntries.add(obfMethodEntry);
+ } else {
+ // get the interface class
+ for (ClassEntry interfaceEntry : getInterfaces(obfMethodEntry.getClassName())) {
+
+ // is this method defined in this interface?
+ MethodEntry methodInterface = new MethodEntry(
+ interfaceEntry,
+ obfMethodEntry.getName(),
+ obfMethodEntry.getSignature()
+ );
+ if (containsObfBehavior(methodInterface)) {
+ interfaceMethodEntries.add(methodInterface);
+ }
+ }
+ }
+
+ List nodes = Lists.newArrayList();
+ if (!interfaceMethodEntries.isEmpty()) {
+ for (MethodEntry interfaceMethodEntry : interfaceMethodEntries) {
+ MethodImplementationsTreeNode node = new MethodImplementationsTreeNode(deobfuscatingTranslator, interfaceMethodEntry);
+ node.load(this);
+ nodes.add(node);
+ }
+ }
+ return nodes;
+ }
+
+ public Set getRelatedMethodImplementations(MethodEntry obfMethodEntry) {
+ Set methodEntries = Sets.newHashSet();
+ getRelatedMethodImplementations(methodEntries, getMethodInheritance(null, obfMethodEntry));
+ return methodEntries;
+ }
+
+ private void getRelatedMethodImplementations(Set methodEntries, MethodInheritanceTreeNode node) {
+ MethodEntry methodEntry = node.getMethodEntry();
+ if (containsObfBehavior(methodEntry)) {
+ // collect the entry
+ methodEntries.add(methodEntry);
+ }
+
+ // look at interface methods too
+ for (MethodImplementationsTreeNode implementationsNode : getMethodImplementations(null, methodEntry)) {
+ getRelatedMethodImplementations(methodEntries, implementationsNode);
+ }
+
+ // recurse
+ for (int i = 0; i < node.getChildCount(); i++) {
+ getRelatedMethodImplementations(methodEntries, (MethodInheritanceTreeNode) node.getChildAt(i));
+ }
+ }
+
+ private void getRelatedMethodImplementations(Set methodEntries, MethodImplementationsTreeNode node) {
+ MethodEntry methodEntry = node.getMethodEntry();
+ if (containsObfBehavior(methodEntry)) {
+ // collect the entry
+ methodEntries.add(methodEntry);
+ }
+
+ // recurse
+ for (int i = 0; i < node.getChildCount(); i++) {
+ getRelatedMethodImplementations(methodEntries, (MethodImplementationsTreeNode) node.getChildAt(i));
+ }
+ }
+
+ public Collection> getFieldReferences(FieldEntry fieldEntry) {
+ return m_fieldReferences.get(fieldEntry);
+ }
+
+ public Collection getReferencedFields(BehaviorEntry behaviorEntry) {
+ // linear search is fast enough for now
+ Set fieldEntries = Sets.newHashSet();
+ for (EntryReference reference : m_fieldReferences.values()) {
+ if (reference.context == behaviorEntry) {
+ fieldEntries.add(reference.entry);
+ }
+ }
+ return fieldEntries;
+ }
+
+ public Collection> getBehaviorReferences(BehaviorEntry behaviorEntry) {
+ return m_behaviorReferences.get(behaviorEntry);
+ }
+
+ public Collection getReferencedBehaviors(BehaviorEntry behaviorEntry) {
+ // linear search is fast enough for now
+ Set behaviorEntries = Sets.newHashSet();
+ for (EntryReference reference : m_behaviorReferences.values()) {
+ if (reference.context == behaviorEntry) {
+ behaviorEntries.add(reference.entry);
+ }
+ }
+ return behaviorEntries;
+ }
+
+ public Collection getInnerClasses(ClassEntry obfOuterClassEntry) {
+ return m_innerClassesByOuter.get(obfOuterClassEntry);
+ }
+
+ public ClassEntry getOuterClass(ClassEntry obfInnerClassEntry) {
+ return m_outerClassesByInner.get(obfInnerClassEntry);
+ }
+
+ public boolean isAnonymousClass(ClassEntry obfInnerClassEntry) {
+ return m_anonymousClasses.containsKey(obfInnerClassEntry);
+ }
+
+ public BehaviorEntry getAnonymousClassCaller(ClassEntry obfInnerClassName) {
+ return m_anonymousClasses.get(obfInnerClassName);
+ }
+
+ public Set getInterfaces(String className) {
+ ClassEntry classEntry = new ClassEntry(className);
+ Set interfaces = new HashSet();
+ interfaces.addAll(m_translationIndex.getInterfaces(classEntry));
+ for (ClassEntry ancestor : m_translationIndex.getAncestry(classEntry)) {
+ interfaces.addAll(m_translationIndex.getInterfaces(ancestor));
+ }
+ return interfaces;
+ }
+
+ public Set getImplementingClasses(String targetInterfaceName) {
+
+ // linear search is fast enough for now
+ Set classNames = Sets.newHashSet();
+ for (Map.Entry entry : m_translationIndex.getClassInterfaces()) {
+ ClassEntry classEntry = entry.getKey();
+ ClassEntry interfaceEntry = entry.getValue();
+ if (interfaceEntry.getName().equals(targetInterfaceName)) {
+ classNames.add(classEntry.getClassName());
+ m_translationIndex.getSubclassNamesRecursively(classNames, classEntry);
+ }
+ }
+ return classNames;
+ }
+
+ public boolean isInterface(String className) {
+ return m_translationIndex.isInterface(new ClassEntry(className));
+ }
+
+ public boolean containsObfClass(ClassEntry obfClassEntry) {
+ return m_obfClassEntries.contains(obfClassEntry);
+ }
+
+ public boolean containsObfField(FieldEntry obfFieldEntry) {
+ return m_access.containsKey(obfFieldEntry);
+ }
+
+ public boolean containsObfBehavior(BehaviorEntry obfBehaviorEntry) {
+ return m_access.containsKey(obfBehaviorEntry);
+ }
+
+ public boolean containsObfArgument(ArgumentEntry obfArgumentEntry) {
+ // check the behavior
+ if (!containsObfBehavior(obfArgumentEntry.getBehaviorEntry())) {
+ return false;
+ }
+
+ // check the argument
+ return obfArgumentEntry.getIndex() < obfArgumentEntry.getBehaviorEntry().getSignature().getArgumentTypes().size();
+
+ }
+
+ public boolean containsObfEntry(Entry obfEntry) {
+ if (obfEntry instanceof ClassEntry) {
+ return containsObfClass((ClassEntry) obfEntry);
+ } else if (obfEntry instanceof FieldEntry) {
+ return containsObfField((FieldEntry) obfEntry);
+ } else if (obfEntry instanceof BehaviorEntry) {
+ return containsObfBehavior((BehaviorEntry) obfEntry);
+ } else if (obfEntry instanceof ArgumentEntry) {
+ return containsObfArgument((ArgumentEntry) obfEntry);
+ } else {
+ throw new Error("Entry type not supported: " + obfEntry.getClass().getName());
+ }
+ }
+
+ public MethodEntry getBridgedMethod(MethodEntry bridgeMethodEntry) {
+ return m_bridgedMethods.get(bridgeMethodEntry);
+ }
+
+ public List getObfClassChain(ClassEntry obfClassEntry) {
+
+ // build class chain in inner-to-outer order
+ List obfClassChain = Lists.newArrayList(obfClassEntry);
+ ClassEntry checkClassEntry = obfClassEntry;
+ while (true) {
+ ClassEntry obfOuterClassEntry = getOuterClass(checkClassEntry);
+ if (obfOuterClassEntry != null) {
+ obfClassChain.add(obfOuterClassEntry);
+ checkClassEntry = obfOuterClassEntry;
+ } else {
+ break;
+ }
+ }
+
+ // switch to outer-to-inner order
+ Collections.reverse(obfClassChain);
+
+ return obfClassChain;
+ }
+}
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 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.analysis;
+
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+
+import cuchaz.enigma.mapping.ClassEntry;
+import cuchaz.enigma.mapping.MethodEntry;
+import cuchaz.enigma.mapping.Translator;
+
+public class MethodImplementationsTreeNode extends DefaultMutableTreeNode {
+
+ private static final long serialVersionUID = 3781080657461899915L;
+
+ private Translator m_deobfuscatingTranslator;
+ private MethodEntry m_entry;
+
+ public MethodImplementationsTreeNode(Translator deobfuscatingTranslator, MethodEntry entry) {
+ if (entry == null) {
+ throw new IllegalArgumentException("entry cannot be null!");
+ }
+
+ m_deobfuscatingTranslator = deobfuscatingTranslator;
+ m_entry = entry;
+ }
+
+ public MethodEntry getMethodEntry() {
+ return m_entry;
+ }
+
+ public String getDeobfClassName() {
+ return m_deobfuscatingTranslator.translateClass(m_entry.getClassName());
+ }
+
+ public String getDeobfMethodName() {
+ return m_deobfuscatingTranslator.translate(m_entry);
+ }
+
+ @Override
+ public String toString() {
+ String className = getDeobfClassName();
+ if (className == null) {
+ className = m_entry.getClassName();
+ }
+
+ String methodName = getDeobfMethodName();
+ if (methodName == null) {
+ methodName = m_entry.getName();
+ }
+ return className + "." + methodName + "()";
+ }
+
+ public void load(JarIndex index) {
+
+ // get all method implementations
+ List nodes = Lists.newArrayList();
+ for (String implementingClassName : index.getImplementingClasses(m_entry.getClassName())) {
+ MethodEntry methodEntry = new MethodEntry(
+ new ClassEntry(implementingClassName),
+ m_entry.getName(),
+ m_entry.getSignature()
+ );
+ if (index.containsObfBehavior(methodEntry)) {
+ nodes.add(new MethodImplementationsTreeNode(m_deobfuscatingTranslator, methodEntry));
+ }
+ }
+
+ // add them to this node
+ for (MethodImplementationsTreeNode node : nodes) {
+ this.add(node);
+ }
+ }
+
+ public static MethodImplementationsTreeNode findNode(MethodImplementationsTreeNode node, MethodEntry entry) {
+ // is this the node?
+ if (node.getMethodEntry().equals(entry)) {
+ return node;
+ }
+
+ // recurse
+ for (int i = 0; i < node.getChildCount(); i++) {
+ MethodImplementationsTreeNode foundNode = findNode((MethodImplementationsTreeNode) node.getChildAt(i), entry);
+ if (foundNode != null) {
+ return foundNode;
+ }
+ }
+ return null;
+ }
+}
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 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.analysis;
+
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+
+import cuchaz.enigma.mapping.ClassEntry;
+import cuchaz.enigma.mapping.MethodEntry;
+import cuchaz.enigma.mapping.Translator;
+
+public class MethodInheritanceTreeNode extends DefaultMutableTreeNode {
+
+ private static final long serialVersionUID = 1096677030991810007L;
+
+ private Translator m_deobfuscatingTranslator;
+ private MethodEntry m_entry;
+ private boolean m_isImplemented;
+
+ public MethodInheritanceTreeNode(Translator deobfuscatingTranslator, MethodEntry entry, boolean isImplemented) {
+ m_deobfuscatingTranslator = deobfuscatingTranslator;
+ m_entry = entry;
+ m_isImplemented = isImplemented;
+ }
+
+ public MethodEntry getMethodEntry() {
+ return m_entry;
+ }
+
+ public String getDeobfClassName() {
+ return m_deobfuscatingTranslator.translateClass(m_entry.getClassName());
+ }
+
+ public String getDeobfMethodName() {
+ return m_deobfuscatingTranslator.translate(m_entry);
+ }
+
+ public boolean isImplemented() {
+ return m_isImplemented;
+ }
+
+ @Override
+ public String toString() {
+ String className = getDeobfClassName();
+ if (className == null) {
+ className = m_entry.getClassName();
+ }
+
+ if (!m_isImplemented) {
+ return className;
+ } else {
+ String methodName = getDeobfMethodName();
+ if (methodName == null) {
+ methodName = m_entry.getName();
+ }
+ return className + "." + methodName + "()";
+ }
+ }
+
+ public void load(JarIndex index, boolean recurse) {
+ // get all the child nodes
+ List nodes = Lists.newArrayList();
+ for (ClassEntry subclassEntry : index.getTranslationIndex().getSubclass(m_entry.getClassEntry())) {
+ MethodEntry methodEntry = new MethodEntry(
+ subclassEntry,
+ m_entry.getName(),
+ m_entry.getSignature()
+ );
+ nodes.add(new MethodInheritanceTreeNode(
+ m_deobfuscatingTranslator,
+ methodEntry,
+ index.containsObfBehavior(methodEntry)
+ ));
+ }
+
+ // add them to this node
+ for (MethodInheritanceTreeNode node : nodes) {
+ this.add(node);
+ }
+
+ if (recurse) {
+ for (MethodInheritanceTreeNode node : nodes) {
+ node.load(index, true);
+ }
+ }
+ }
+
+ public static MethodInheritanceTreeNode findNode(MethodInheritanceTreeNode node, MethodEntry entry) {
+ // is this the node?
+ if (node.getMethodEntry().equals(entry)) {
+ return node;
+ }
+
+ // recurse
+ for (int i = 0; i < node.getChildCount(); i++) {
+ MethodInheritanceTreeNode foundNode = findNode((MethodInheritanceTreeNode) node.getChildAt(i), entry);
+ if (foundNode != null) {
+ return foundNode;
+ }
+ }
+ return null;
+ }
+}
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 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.analysis;
+
+import cuchaz.enigma.mapping.Entry;
+
+public interface ReferenceTreeNode {
+ E getEntry();
+
+ EntryReference getReference();
+}
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 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.analysis;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import java.util.Map;
+import java.util.Set;
+
+import cuchaz.enigma.mapping.*;
+
+public class RelatedMethodChecker {
+
+ private JarIndex m_jarIndex;
+ private Map, String> m_deobfNamesByGroup;
+ private Map m_deobfNamesByObfMethod;
+ private Map> m_groupsByObfMethod;
+ private Set> m_inconsistentGroups;
+
+ public RelatedMethodChecker(JarIndex jarIndex) {
+ m_jarIndex = jarIndex;
+ m_deobfNamesByGroup = Maps.newHashMap();
+ m_deobfNamesByObfMethod = Maps.newHashMap();
+ m_groupsByObfMethod = Maps.newHashMap();
+ m_inconsistentGroups = Sets.newHashSet();
+ }
+
+ public void checkMethod(ClassEntry classEntry, MethodMapping methodMapping) {
+
+ // TEMP: disable the expensive check for now, maybe we can optimize it later, or just use it for debugging
+ if (true) {
+ return;
+ }
+
+ BehaviorEntry obfBehaviorEntry = EntryFactory.getObfBehaviorEntry(classEntry, methodMapping);
+ if (!(obfBehaviorEntry instanceof MethodEntry)) {
+ // only methods have related implementations
+ return;
+ }
+ MethodEntry obfMethodEntry = (MethodEntry) obfBehaviorEntry;
+ String deobfName = methodMapping.getDeobfName();
+ m_deobfNamesByObfMethod.put(obfMethodEntry, deobfName);
+
+ // have we seen this method's group before?
+ Set group = m_groupsByObfMethod.get(obfMethodEntry);
+ if (group == null) {
+
+ // no, compute the group and save the name
+ group = m_jarIndex.getRelatedMethodImplementations(obfMethodEntry);
+ m_deobfNamesByGroup.put(group, deobfName);
+
+ assert (group.contains(obfMethodEntry));
+ for (MethodEntry relatedMethodEntry : group) {
+ m_groupsByObfMethod.put(relatedMethodEntry, group);
+ }
+ }
+
+ // check the name
+ if (!sameName(m_deobfNamesByGroup.get(group), deobfName)) {
+ m_inconsistentGroups.add(group);
+ }
+ }
+
+ private boolean sameName(String a, String b) {
+ if (a == null && b == null) {
+ return true;
+ } else if (a != null && b != null) {
+ return a.equals(b);
+ }
+ return false;
+ }
+
+ public boolean hasProblems() {
+ return m_inconsistentGroups.size() > 0;
+ }
+
+ public String getReport() {
+ StringBuilder buf = new StringBuilder();
+ buf.append(m_inconsistentGroups.size());
+ buf.append(" groups of methods related by inheritance and/or interfaces have different deobf names!\n");
+ for (Set group : m_inconsistentGroups) {
+ buf.append("\tGroup with ");
+ buf.append(group.size());
+ buf.append(" methods:\n");
+ for (MethodEntry methodEntry : group) {
+ buf.append("\t\t");
+ buf.append(methodEntry.toString());
+ buf.append(" => ");
+ buf.append(m_deobfNamesByObfMethod.get(methodEntry));
+ buf.append("\n");
+ }
+ }
+ return buf.toString();
+ }
+}
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 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.analysis;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+
+import com.strobel.decompiler.languages.Region;
+import com.strobel.decompiler.languages.java.ast.AstNode;
+import com.strobel.decompiler.languages.java.ast.Identifier;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import cuchaz.enigma.mapping.Entry;
+
+public class SourceIndex {
+
+ private String m_source;
+ private TreeMap> m_tokenToReference;
+ private Multimap, Token> m_referenceToTokens;
+ private Map m_declarationToToken;
+ private List m_lineOffsets;
+ private boolean m_ignoreBadTokens;
+
+ public SourceIndex(String source) {
+ this(source, true);
+ }
+
+ public SourceIndex(String source, boolean ignoreBadTokens) {
+ m_source = source;
+ m_ignoreBadTokens = ignoreBadTokens;
+ m_tokenToReference = Maps.newTreeMap();
+ m_referenceToTokens = HashMultimap.create();
+ m_declarationToToken = Maps.newHashMap();
+ m_lineOffsets = Lists.newArrayList();
+
+ // count the lines
+ m_lineOffsets.add(0);
+ for (int i = 0; i < source.length(); i++) {
+ if (source.charAt(i) == '\n') {
+ m_lineOffsets.add(i + 1);
+ }
+ }
+ }
+
+ public String getSource() {
+ return m_source;
+ }
+
+ public Token getToken(AstNode node) {
+
+ // get the text of the node
+ String name = "";
+ if (node instanceof Identifier) {
+ name = ((Identifier) node).getName();
+ }
+
+ // get a token for this node's region
+ Region region = node.getRegion();
+ if (region.getBeginLine() == 0 || region.getEndLine() == 0) {
+ // DEBUG
+ System.err.println(String.format("WARNING: %s \"%s\" has invalid region: %s", node.getNodeType(), name, region));
+ return null;
+ }
+ Token token = new Token(
+ toPos(region.getBeginLine(), region.getBeginColumn()),
+ toPos(region.getEndLine(), region.getEndColumn()),
+ m_source
+ );
+ if (token.start == 0) {
+ // DEBUG
+ System.err.println(String.format("WARNING: %s \"%s\" has invalid start: %s", node.getNodeType(), name, region));
+ return null;
+ }
+
+ // DEBUG
+ // System.out.println( String.format( "%s \"%s\" region: %s", node.getNodeType(), name, region ) );
+
+ // if the token has a $ in it, something's wrong. Ignore this token
+ if (name.lastIndexOf('$') >= 0 && m_ignoreBadTokens) {
+ // DEBUG
+ System.err.println(String.format("WARNING: %s \"%s\" is probably a bad token. It was ignored", node.getNodeType(), name));
+ return null;
+ }
+
+ return token;
+ }
+
+ public void addReference(AstNode node, Entry deobfEntry, Entry deobfContext) {
+ Token token = getToken(node);
+ if (token != null) {
+ EntryReference deobfReference = new EntryReference(deobfEntry, token.text, deobfContext);
+ m_tokenToReference.put(token, deobfReference);
+ m_referenceToTokens.put(deobfReference, token);
+ }
+ }
+
+ public void addDeclaration(AstNode node, Entry deobfEntry) {
+ Token token = getToken(node);
+ if (token != null) {
+ EntryReference reference = new EntryReference(deobfEntry, token.text);
+ m_tokenToReference.put(token, reference);
+ m_referenceToTokens.put(reference, token);
+ m_declarationToToken.put(deobfEntry, token);
+ }
+ }
+
+ public Token getReferenceToken(int pos) {
+ Token token = m_tokenToReference.floorKey(new Token(pos, pos, null));
+ if (token != null && token.contains(pos)) {
+ return token;
+ }
+ return null;
+ }
+
+ public Collection getReferenceTokens(EntryReference deobfReference) {
+ return m_referenceToTokens.get(deobfReference);
+ }
+
+ public EntryReference getDeobfReference(Token token) {
+ if (token == null) {
+ return null;
+ }
+ return m_tokenToReference.get(token);
+ }
+
+ public void replaceDeobfReference(Token token, EntryReference newDeobfReference) {
+ EntryReference oldDeobfReference = m_tokenToReference.get(token);
+ m_tokenToReference.put(token, newDeobfReference);
+ Collection tokens = m_referenceToTokens.get(oldDeobfReference);
+ m_referenceToTokens.removeAll(oldDeobfReference);
+ m_referenceToTokens.putAll(newDeobfReference, tokens);
+ }
+
+ public Iterable referenceTokens() {
+ return m_tokenToReference.keySet();
+ }
+
+ public Iterable declarationTokens() {
+ return m_declarationToToken.values();
+ }
+
+ public Iterable declarations() {
+ return m_declarationToToken.keySet();
+ }
+
+ public Token getDeclarationToken(Entry deobfEntry) {
+ return m_declarationToToken.get(deobfEntry);
+ }
+
+ public int getLineNumber(int pos) {
+ // line number is 1-based
+ int line = 0;
+ for (Integer offset : m_lineOffsets) {
+ if (offset > pos) {
+ break;
+ }
+ line++;
+ }
+ return line;
+ }
+
+ public int getColumnNumber(int pos) {
+ // column number is 1-based
+ return pos - m_lineOffsets.get(getLineNumber(pos) - 1) + 1;
+ }
+
+ private int toPos(int line, int col) {
+ // line and col are 1-based
+ return m_lineOffsets.get(line - 1) + col - 1;
+ }
+}
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 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.analysis;
+
+import com.strobel.assembler.metadata.*;
+import com.strobel.decompiler.languages.TextLocation;
+import com.strobel.decompiler.languages.java.ast.*;
+
+import cuchaz.enigma.mapping.*;
+
+import java.lang.Error;
+
+public class SourceIndexBehaviorVisitor extends SourceIndexVisitor {
+
+ private BehaviorEntry m_behaviorEntry;
+
+ public SourceIndexBehaviorVisitor(BehaviorEntry behaviorEntry) {
+ m_behaviorEntry = behaviorEntry;
+ }
+
+ @Override
+ public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) {
+ MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
+
+ // get the behavior entry
+ ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
+ BehaviorEntry behaviorEntry = null;
+ if (ref instanceof MethodReference) {
+ MethodReference methodRef = (MethodReference) ref;
+ if (methodRef.isConstructor()) {
+ behaviorEntry = new ConstructorEntry(classEntry, new Signature(ref.getErasedSignature()));
+ } else if (methodRef.isTypeInitializer()) {
+ behaviorEntry = new ConstructorEntry(classEntry);
+ } else {
+ behaviorEntry = new MethodEntry(classEntry, ref.getName(), new Signature(ref.getErasedSignature()));
+ }
+ }
+ if (behaviorEntry != null) {
+ // get the node for the token
+ AstNode tokenNode = null;
+ if (node.getTarget() instanceof MemberReferenceExpression) {
+ tokenNode = ((MemberReferenceExpression) node.getTarget()).getMemberNameToken();
+ } else if (node.getTarget() instanceof SuperReferenceExpression) {
+ tokenNode = node.getTarget();
+ } else if (node.getTarget() instanceof ThisReferenceExpression) {
+ tokenNode = node.getTarget();
+ }
+ if (tokenNode != null) {
+ index.addReference(tokenNode, behaviorEntry, m_behaviorEntry);
+ }
+ }
+
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) {
+ MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
+ if (ref != null) {
+ // make sure this is actually a field
+ if (ref.getErasedSignature().indexOf('(') >= 0) {
+ throw new Error("Expected a field here! got " + ref);
+ }
+
+ ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
+ FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getErasedSignature()));
+ index.addReference(node.getMemberNameToken(), fieldEntry, m_behaviorEntry);
+ }
+
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitSimpleType(SimpleType node, SourceIndex index) {
+ TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE);
+ if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) {
+ ClassEntry classEntry = new ClassEntry(ref.getInternalName());
+ index.addReference(node.getIdentifierToken(), classEntry, m_behaviorEntry);
+ }
+
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) {
+ ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION);
+ if (def.getMethod() instanceof MethodDefinition) {
+ MethodDefinition methodDef = (MethodDefinition) def.getMethod();
+ BehaviorEntry behaviorEntry = ProcyonEntryFactory.getBehaviorEntry(methodDef);
+ ArgumentEntry argumentEntry = new ArgumentEntry(behaviorEntry, def.getPosition(), node.getName());
+ index.addDeclaration(node.getNameToken(), argumentEntry);
+ }
+
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) {
+ MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
+ if (ref != null) {
+ ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
+ FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getErasedSignature()));
+ index.addReference(node.getIdentifierToken(), fieldEntry, m_behaviorEntry);
+ }
+
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) {
+ MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
+ if (ref != null) {
+ ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
+ ConstructorEntry constructorEntry = new ConstructorEntry(classEntry, new Signature(ref.getErasedSignature()));
+ if (node.getType() instanceof SimpleType) {
+ SimpleType simpleTypeNode = (SimpleType) node.getType();
+ index.addReference(simpleTypeNode.getIdentifierToken(), constructorEntry, m_behaviorEntry);
+ }
+ }
+
+ return recurse(node, index);
+ }
+}
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 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.analysis;
+
+import com.strobel.assembler.metadata.FieldDefinition;
+import com.strobel.assembler.metadata.MethodDefinition;
+import com.strobel.assembler.metadata.TypeDefinition;
+import com.strobel.assembler.metadata.TypeReference;
+import com.strobel.decompiler.languages.TextLocation;
+import com.strobel.decompiler.languages.java.ast.*;
+
+import cuchaz.enigma.mapping.*;
+
+public class SourceIndexClassVisitor extends SourceIndexVisitor {
+
+ private ClassEntry m_classEntry;
+
+ public SourceIndexClassVisitor(ClassEntry classEntry) {
+ m_classEntry = classEntry;
+ }
+
+ @Override
+ public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) {
+ // is this this class, or a subtype?
+ TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION);
+ ClassEntry classEntry = new ClassEntry(def.getInternalName());
+ if (!classEntry.equals(m_classEntry)) {
+ // it's a sub-type, recurse
+ index.addDeclaration(node.getNameToken(), classEntry);
+ return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index);
+ }
+
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitSimpleType(SimpleType node, SourceIndex index) {
+ TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE);
+ if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) {
+ ClassEntry classEntry = new ClassEntry(ref.getInternalName());
+ index.addReference(node.getIdentifierToken(), classEntry, m_classEntry);
+ }
+
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) {
+ MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
+ BehaviorEntry behaviorEntry = ProcyonEntryFactory.getBehaviorEntry(def);
+ AstNode tokenNode = node.getNameToken();
+
+ if (behaviorEntry instanceof ConstructorEntry) {
+ ConstructorEntry constructorEntry = (ConstructorEntry) behaviorEntry;
+ if (constructorEntry.isStatic()) {
+ // for static initializers, check elsewhere for the token node
+ tokenNode = node.getModifiers().firstOrNullObject();
+ }
+ }
+ index.addDeclaration(tokenNode, behaviorEntry);
+ return node.acceptVisitor(new SourceIndexBehaviorVisitor(behaviorEntry), index);
+ }
+
+ @Override
+ public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) {
+ MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
+ ConstructorEntry constructorEntry = ProcyonEntryFactory.getConstructorEntry(def);
+ index.addDeclaration(node.getNameToken(), constructorEntry);
+ return node.acceptVisitor(new SourceIndexBehaviorVisitor(constructorEntry), index);
+ }
+
+ @Override
+ public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) {
+ FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION);
+ FieldEntry fieldEntry = ProcyonEntryFactory.getFieldEntry(def);
+ assert (node.getVariables().size() == 1);
+ VariableInitializer variable = node.getVariables().firstOrNullObject();
+ index.addDeclaration(variable.getNameToken(), fieldEntry);
+
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) {
+ // treat enum declarations as field declarations
+ FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION);
+ FieldEntry fieldEntry = ProcyonEntryFactory.getFieldEntry(def);
+ index.addDeclaration(node.getNameToken(), fieldEntry);
+
+ return recurse(node, index);
+ }
+}
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 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.analysis;
+
+import com.strobel.assembler.metadata.TypeDefinition;
+import com.strobel.decompiler.languages.java.ast.*;
+import com.strobel.decompiler.patterns.Pattern;
+
+import cuchaz.enigma.mapping.ClassEntry;
+
+public class SourceIndexVisitor implements IAstVisitor {
+
+ @Override
+ public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) {
+ TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION);
+ ClassEntry classEntry = new ClassEntry(def.getInternalName());
+ index.addDeclaration(node.getNameToken(), classEntry);
+
+ return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index);
+ }
+
+ protected Void recurse(AstNode node, SourceIndex index) {
+ for (final AstNode child : node.getChildren()) {
+ child.acceptVisitor(this, index);
+ }
+ return null;
+ }
+
+ @Override
+ public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitSimpleType(SimpleType node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitComment(Comment node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitPatternPlaceholder(AstNode node, Pattern pattern, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitTypeReference(TypeReferenceExpression node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitJavaTokenNode(JavaTokenNode node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitIdentifier(Identifier node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitNullReferenceExpression(NullReferenceExpression node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitThisReferenceExpression(ThisReferenceExpression node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitSuperReferenceExpression(SuperReferenceExpression node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitClassOfExpression(ClassOfExpression node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitBlockStatement(BlockStatement node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitExpressionStatement(ExpressionStatement node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitBreakStatement(BreakStatement node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitContinueStatement(ContinueStatement node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitDoWhileStatement(DoWhileStatement node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitEmptyStatement(EmptyStatement node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitIfElseStatement(IfElseStatement node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitLabelStatement(LabelStatement node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitLabeledStatement(LabeledStatement node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitReturnStatement(ReturnStatement node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitSwitchStatement(SwitchStatement node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitSwitchSection(SwitchSection node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitCaseLabel(CaseLabel node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitThrowStatement(ThrowStatement node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitCatchClause(CatchClause node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitAnnotation(Annotation node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitNewLine(NewLineNode node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitVariableInitializer(VariableInitializer node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitText(TextNode node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitImportDeclaration(ImportDeclaration node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitInitializerBlock(InstanceInitializer node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitTypeParameterDeclaration(TypeParameterDeclaration node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitCompilationUnit(CompilationUnit node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitPackageDeclaration(PackageDeclaration node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitArraySpecifier(ArraySpecifier node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitComposedType(ComposedType node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitWhileStatement(WhileStatement node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitPrimitiveExpression(PrimitiveExpression node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitCastExpression(CastExpression node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitBinaryOperatorExpression(BinaryOperatorExpression node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitInstanceOfExpression(InstanceOfExpression node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitIndexerExpression(IndexerExpression node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitUnaryOperatorExpression(UnaryOperatorExpression node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitConditionalExpression(ConditionalExpression node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitArrayInitializerExpression(ArrayInitializerExpression node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitArrayCreationExpression(ArrayCreationExpression node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitAssignmentExpression(AssignmentExpression node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitForStatement(ForStatement node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitForEachStatement(ForEachStatement node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitTryCatchStatement(TryCatchStatement node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitGotoStatement(GotoStatement node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitParenthesizedExpression(ParenthesizedExpression node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitSynchronizedStatement(SynchronizedStatement node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitAnonymousObjectCreationExpression(AnonymousObjectCreationExpression node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitWildcardType(WildcardType node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitMethodGroupExpression(MethodGroupExpression node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitAssertStatement(AssertStatement node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitLambdaExpression(LambdaExpression node, SourceIndex index) {
+ return recurse(node, index);
+ }
+
+ @Override
+ public Void visitLocalTypeDeclarationStatement(LocalTypeDeclarationStatement node, SourceIndex index) {
+ return recurse(node, index);
+ }
+}
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 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.analysis;
+
+public class Token implements Comparable {
+
+ public int start;
+ public int end;
+ public String text;
+
+ public Token(int start, int end) {
+ this(start, end, null);
+ }
+
+ public Token(int start, int end, String source) {
+ this.start = start;
+ this.end = end;
+ if (source != null) {
+ this.text = source.substring(start, end);
+ }
+ }
+
+ public boolean contains(int pos) {
+ return pos >= start && pos <= end;
+ }
+
+ @Override
+ public int compareTo(Token other) {
+ return start - other.start;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof Token) {
+ return equals((Token) other);
+ }
+ return false;
+ }
+
+ public boolean equals(Token other) {
+ return start == other.start && end == other.end;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("[%d,%d]", start, end);
+ }
+}
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 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.analysis;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+
+import java.io.*;
+import java.util.*;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+import cuchaz.enigma.mapping.*;
+import javassist.CtBehavior;
+import javassist.CtClass;
+import javassist.CtField;
+import javassist.bytecode.Descriptor;
+
+public class TranslationIndex implements Serializable {
+
+ private static final long serialVersionUID = 738687982126844179L;
+
+ private Map m_superclasses;
+ private Multimap m_fieldEntries;
+ private Multimap m_behaviorEntries;
+ private Multimap m_interfaces;
+
+ public TranslationIndex() {
+ m_superclasses = Maps.newHashMap();
+ m_fieldEntries = HashMultimap.create();
+ m_behaviorEntries = HashMultimap.create();
+ m_interfaces = HashMultimap.create();
+ }
+
+ public TranslationIndex(TranslationIndex other, Translator translator) {
+
+ // translate the superclasses
+ m_superclasses = Maps.newHashMap();
+ for (Map.Entry mapEntry : other.m_superclasses.entrySet()) {
+ m_superclasses.put(
+ translator.translateEntry(mapEntry.getKey()),
+ translator.translateEntry(mapEntry.getValue())
+ );
+ }
+
+ // translate the interfaces
+ m_interfaces = HashMultimap.create();
+ for (Map.Entry mapEntry : other.m_interfaces.entries()) {
+ m_interfaces.put(
+ translator.translateEntry(mapEntry.getKey()),
+ translator.translateEntry(mapEntry.getValue())
+ );
+ }
+
+ // translate the fields
+ m_fieldEntries = HashMultimap.create();
+ for (Map.Entry mapEntry : other.m_fieldEntries.entries()) {
+ m_fieldEntries.put(
+ translator.translateEntry(mapEntry.getKey()),
+ translator.translateEntry(mapEntry.getValue())
+ );
+ }
+
+ m_behaviorEntries = HashMultimap.create();
+ for (Map.Entry mapEntry : other.m_behaviorEntries.entries()) {
+ m_behaviorEntries.put(
+ translator.translateEntry(mapEntry.getKey()),
+ translator.translateEntry(mapEntry.getValue())
+ );
+ }
+ }
+
+ public void indexClass(CtClass c) {
+ indexClass(c, true);
+ }
+
+ public void indexClass(CtClass c, boolean indexMembers) {
+
+ ClassEntry classEntry = EntryFactory.getClassEntry(c);
+ if (isJre(classEntry)) {
+ return;
+ }
+
+ // add the superclass
+ ClassEntry superclassEntry = EntryFactory.getSuperclassEntry(c);
+ if (superclassEntry != null) {
+ m_superclasses.put(classEntry, superclassEntry);
+ }
+
+ // add the interfaces
+ for (String interfaceClassName : c.getClassFile().getInterfaces()) {
+ ClassEntry interfaceClassEntry = new ClassEntry(Descriptor.toJvmName(interfaceClassName));
+ if (!isJre(interfaceClassEntry)) {
+ m_interfaces.put(classEntry, interfaceClassEntry);
+ }
+ }
+
+ if (indexMembers) {
+ // add fields
+ for (CtField field : c.getDeclaredFields()) {
+ FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
+ m_fieldEntries.put(fieldEntry.getClassEntry(), fieldEntry);
+ }
+
+ // add behaviors
+ for (CtBehavior behavior : c.getDeclaredBehaviors()) {
+ BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
+ m_behaviorEntries.put(behaviorEntry.getClassEntry(), behaviorEntry);
+ }
+ }
+ }
+
+ public void renameClasses(Map renames) {
+ EntryRenamer.renameClassesInMap(renames, m_superclasses);
+ EntryRenamer.renameClassesInMultimap(renames, m_fieldEntries);
+ EntryRenamer.renameClassesInMultimap(renames, m_behaviorEntries);
+ }
+
+ public ClassEntry getSuperclass(ClassEntry classEntry) {
+ return m_superclasses.get(classEntry);
+ }
+
+ public List getAncestry(ClassEntry classEntry) {
+ List ancestors = Lists.newArrayList();
+ while (classEntry != null) {
+ classEntry = getSuperclass(classEntry);
+ if (classEntry != null) {
+ ancestors.add(classEntry);
+ }
+ }
+ return ancestors;
+ }
+
+ public List getSubclass(ClassEntry classEntry) {
+
+ // linear search is fast enough for now
+ List subclasses = Lists.newArrayList();
+ for (Map.Entry entry : m_superclasses.entrySet()) {
+ ClassEntry subclass = entry.getKey();
+ ClassEntry superclass = entry.getValue();
+ if (classEntry.equals(superclass)) {
+ subclasses.add(subclass);
+ }
+ }
+ return subclasses;
+ }
+
+ public void getSubclassesRecursively(Set out, ClassEntry classEntry) {
+ for (ClassEntry subclassEntry : getSubclass(classEntry)) {
+ out.add(subclassEntry);
+ getSubclassesRecursively(out, subclassEntry);
+ }
+ }
+
+ public void getSubclassNamesRecursively(Set out, ClassEntry classEntry) {
+ for (ClassEntry subclassEntry : getSubclass(classEntry)) {
+ out.add(subclassEntry.getName());
+ getSubclassNamesRecursively(out, subclassEntry);
+ }
+ }
+
+ public Collection> getClassInterfaces() {
+ return m_interfaces.entries();
+ }
+
+ public Collection getInterfaces(ClassEntry classEntry) {
+ return m_interfaces.get(classEntry);
+ }
+
+ public boolean isInterface(ClassEntry classEntry) {
+ return m_interfaces.containsValue(classEntry);
+ }
+
+ public boolean entryExists(Entry entry) {
+ if (entry instanceof FieldEntry) {
+ return fieldExists((FieldEntry) entry);
+ } else if (entry instanceof BehaviorEntry) {
+ return behaviorExists((BehaviorEntry) entry);
+ } else if (entry instanceof ArgumentEntry) {
+ return behaviorExists(((ArgumentEntry) entry).getBehaviorEntry());
+ }
+ throw new IllegalArgumentException("Cannot check existence for " + entry.getClass());
+ }
+
+ public boolean fieldExists(FieldEntry fieldEntry) {
+ return m_fieldEntries.containsEntry(fieldEntry.getClassEntry(), fieldEntry);
+ }
+
+ public boolean behaviorExists(BehaviorEntry behaviorEntry) {
+ return m_behaviorEntries.containsEntry(behaviorEntry.getClassEntry(), behaviorEntry);
+ }
+
+ public ClassEntry resolveEntryClass(Entry entry) {
+
+ if (entry instanceof ClassEntry) {
+ return (ClassEntry) entry;
+ }
+
+ ClassEntry superclassEntry = resolveSuperclass(entry);
+ if (superclassEntry != null) {
+ return superclassEntry;
+ }
+
+ ClassEntry interfaceEntry = resolveInterface(entry);
+ if (interfaceEntry != null) {
+ return interfaceEntry;
+ }
+
+ return null;
+ }
+
+ public ClassEntry resolveSuperclass(Entry entry) {
+
+ // this entry could refer to a method on a class where the method is not actually implemented
+ // travel up the inheritance tree to find the closest implementation
+ while (!entryExists(entry)) {
+
+ // is there a parent class?
+ ClassEntry superclassEntry = getSuperclass(entry.getClassEntry());
+ if (superclassEntry == null) {
+ // this is probably a method from a class in a library
+ // we can't trace the implementation up any higher unless we index the library
+ return null;
+ }
+
+ // move up to the parent class
+ entry = entry.cloneToNewClass(superclassEntry);
+ }
+ return entry.getClassEntry();
+ }
+
+ public ClassEntry resolveInterface(Entry entry) {
+
+ // the interfaces for any class is a forest
+ // so let's look at all the trees
+ for (ClassEntry interfaceEntry : m_interfaces.get(entry.getClassEntry())) {
+ ClassEntry resolvedClassEntry = resolveSuperclass(entry.cloneToNewClass(interfaceEntry));
+ if (resolvedClassEntry != null) {
+ return resolvedClassEntry;
+ }
+ }
+ return null;
+ }
+
+ private boolean isJre(ClassEntry classEntry) {
+ String packageName = classEntry.getPackageName();
+ return packageName != null && (packageName.startsWith("java") || packageName.startsWith("javax"));
+ }
+
+ public void write(OutputStream out)
+ throws IOException {
+ GZIPOutputStream gzipout = new GZIPOutputStream(out);
+ ObjectOutputStream oout = new ObjectOutputStream(gzipout);
+ oout.writeObject(m_superclasses);
+ oout.writeObject(m_fieldEntries);
+ oout.writeObject(m_behaviorEntries);
+ gzipout.finish();
+ }
+
+ @SuppressWarnings("unchecked")
+ public void read(InputStream in)
+ throws IOException {
+ try {
+ ObjectInputStream oin = new ObjectInputStream(new GZIPInputStream(in));
+ m_superclasses = (HashMap) oin.readObject();
+ m_fieldEntries = (HashMultimap) oin.readObject();
+ m_behaviorEntries = (HashMultimap) oin.readObject();
+ } catch (ClassNotFoundException ex) {
+ throw new Error(ex);
+ }
+ }
+}
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 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.analysis;
+
+import com.strobel.componentmodel.Key;
+import com.strobel.decompiler.languages.java.ast.*;
+import com.strobel.decompiler.patterns.Pattern;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+
+public class TreeDumpVisitor implements IAstVisitor {
+
+ private File m_file;
+ private Writer m_out;
+
+ public TreeDumpVisitor(File file) {
+ m_file = file;
+ m_out = null;
+ }
+
+ @Override
+ public Void visitCompilationUnit(CompilationUnit node, Void ignored) {
+ try {
+ m_out = new FileWriter(m_file);
+ recurse(node, ignored);
+ m_out.close();
+ return null;
+ } catch (IOException ex) {
+ throw new Error(ex);
+ }
+ }
+
+ private Void recurse(AstNode node, Void ignored) {
+ // show the tree
+ try {
+ m_out.write(getIndent(node) + node.getClass().getSimpleName() + " " + getText(node) + " " + dumpUserData(node) + " " + node.getRegion() + "\n");
+ } catch (IOException ex) {
+ throw new Error(ex);
+ }
+
+ // recurse
+ for (final AstNode child : node.getChildren()) {
+ child.acceptVisitor(this, ignored);
+ }
+ return null;
+ }
+
+ private String getText(AstNode node) {
+ if (node instanceof Identifier) {
+ return "\"" + ((Identifier) node).getName() + "\"";
+ }
+ return "";
+ }
+
+ private String dumpUserData(AstNode node) {
+ StringBuilder buf = new StringBuilder();
+ for (Key> key : Keys.ALL_KEYS) {
+ Object val = node.getUserData(key);
+ if (val != null) {
+ buf.append(String.format(" [%s=%s]", key, val));
+ }
+ }
+ return buf.toString();
+ }
+
+ private String getIndent(AstNode node) {
+ StringBuilder buf = new StringBuilder();
+ int depth = getDepth(node);
+ for (int i = 0; i < depth; i++) {
+ buf.append("\t");
+ }
+ return buf.toString();
+ }
+
+ private int getDepth(AstNode node) {
+ int depth = -1;
+ while (node != null) {
+ depth++;
+ node = node.getParent();
+ }
+ return depth;
+ }
+
+ // OVERRIDES WE DON'T CARE ABOUT
+
+ @Override
+ public Void visitInvocationExpression(InvocationExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitMemberReferenceExpression(MemberReferenceExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitSimpleType(SimpleType node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitMethodDeclaration(MethodDeclaration node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitConstructorDeclaration(ConstructorDeclaration node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitParameterDeclaration(ParameterDeclaration node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitFieldDeclaration(FieldDeclaration node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitTypeDeclaration(TypeDeclaration node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitComment(Comment node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitPatternPlaceholder(AstNode node, Pattern pattern, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitTypeReference(TypeReferenceExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitJavaTokenNode(JavaTokenNode node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitIdentifier(Identifier node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitNullReferenceExpression(NullReferenceExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitThisReferenceExpression(ThisReferenceExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitSuperReferenceExpression(SuperReferenceExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitClassOfExpression(ClassOfExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitBlockStatement(BlockStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitExpressionStatement(ExpressionStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitBreakStatement(BreakStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitContinueStatement(ContinueStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitDoWhileStatement(DoWhileStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitEmptyStatement(EmptyStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitIfElseStatement(IfElseStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitLabelStatement(LabelStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitLabeledStatement(LabeledStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitReturnStatement(ReturnStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitSwitchStatement(SwitchStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitSwitchSection(SwitchSection node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitCaseLabel(CaseLabel node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitThrowStatement(ThrowStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitCatchClause(CatchClause node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitAnnotation(Annotation node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitNewLine(NewLineNode node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitVariableDeclaration(VariableDeclarationStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitVariableInitializer(VariableInitializer node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitText(TextNode node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitImportDeclaration(ImportDeclaration node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitInitializerBlock(InstanceInitializer node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitTypeParameterDeclaration(TypeParameterDeclaration node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitPackageDeclaration(PackageDeclaration node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitArraySpecifier(ArraySpecifier node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitComposedType(ComposedType node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitWhileStatement(WhileStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitPrimitiveExpression(PrimitiveExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitCastExpression(CastExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitBinaryOperatorExpression(BinaryOperatorExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitInstanceOfExpression(InstanceOfExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitIndexerExpression(IndexerExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitIdentifierExpression(IdentifierExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitUnaryOperatorExpression(UnaryOperatorExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitConditionalExpression(ConditionalExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitArrayInitializerExpression(ArrayInitializerExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitObjectCreationExpression(ObjectCreationExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitArrayCreationExpression(ArrayCreationExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitAssignmentExpression(AssignmentExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitForStatement(ForStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitForEachStatement(ForEachStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitTryCatchStatement(TryCatchStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitGotoStatement(GotoStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitParenthesizedExpression(ParenthesizedExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitSynchronizedStatement(SynchronizedStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitAnonymousObjectCreationExpression(AnonymousObjectCreationExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitWildcardType(WildcardType node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitMethodGroupExpression(MethodGroupExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitEnumValueDeclaration(EnumValueDeclaration node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitAssertStatement(AssertStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitLambdaExpression(LambdaExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitLocalTypeDeclarationStatement(LocalTypeDeclarationStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+}
--
cgit v1.2.3