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