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