From 5f44aac70f59898197c2a7625b74f901c3b31106 Mon Sep 17 00:00:00 2001 From: jeff Date: Tue, 26 Aug 2014 00:27:44 -0400 Subject: implemented proper support for interfaces --- src/cuchaz/enigma/TranslatingTypeLoader.java | 5 +- src/cuchaz/enigma/analysis/Ancestries.java | 89 +++++++++++++++++ .../analysis/ClassImplementationsTreeNode.java | 92 +++++++++++++++++ src/cuchaz/enigma/analysis/JarIndex.java | 107 ++++++++++++++++++-- .../analysis/MethodImplementationsTreeNode.java | 111 +++++++++++++++++++++ src/cuchaz/enigma/gui/Gui.java | 104 ++++++++++++++++++- src/cuchaz/enigma/gui/GuiController.java | 25 +++++ src/cuchaz/enigma/mapping/Renamer.java | 21 +--- 8 files changed, 524 insertions(+), 30 deletions(-) create mode 100644 src/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java create mode 100644 src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java (limited to 'src') diff --git a/src/cuchaz/enigma/TranslatingTypeLoader.java b/src/cuchaz/enigma/TranslatingTypeLoader.java index 83f0baa4..1e0e95a2 100644 --- a/src/cuchaz/enigma/TranslatingTypeLoader.java +++ b/src/cuchaz/enigma/TranslatingTypeLoader.java @@ -13,7 +13,6 @@ package cuchaz.enigma; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.util.Arrays; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; @@ -105,12 +104,12 @@ public class TranslatingTypeLoader implements ITypeLoader return null; } - // DEBUG + /* DEBUG if( !Arrays.asList( "java", "org", "io" ).contains( deobfClassName.split( "/" )[0] ) ) { System.out.println( String.format( "Looking for %s (%s)", deobfClassEntry.getName(), obfClassEntry.getName() ) ); } - // + */ // get the jar entry String classFileName; diff --git a/src/cuchaz/enigma/analysis/Ancestries.java b/src/cuchaz/enigma/analysis/Ancestries.java index b9d8cbf4..97241084 100644 --- a/src/cuchaz/enigma/analysis/Ancestries.java +++ b/src/cuchaz/enigma/analysis/Ancestries.java @@ -11,24 +11,32 @@ package cuchaz.enigma.analysis; import java.io.Serializable; +import java.util.AbstractMap; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; 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 com.google.common.collect.Sets; public class Ancestries implements Serializable { private static final long serialVersionUID = 738687982126844179L; private Map m_superclasses; + private Multimap m_interfaces; public Ancestries( ) { m_superclasses = Maps.newHashMap(); + m_interfaces = HashMultimap.create(); } public void addSuperclass( String className, String superclassName ) @@ -47,8 +55,25 @@ public class Ancestries implements Serializable } } + public void addInterface( String className, String interfaceName ) + { + className = Descriptor.toJvmName( className ); + interfaceName = Descriptor.toJvmName( interfaceName ); + + if( className.equals( interfaceName ) ) + { + throw new IllegalArgumentException( "Class cannot be its own interface! " + className ); + } + + if( !isJre( className ) && !isJre( interfaceName ) ) + { + m_interfaces.put( className, interfaceName ); + } + } + public void renameClasses( Map renames ) { + // rename superclasses Map newSuperclasses = Maps.newHashMap(); for( Map.Entry entry : m_superclasses.entrySet() ) { @@ -65,6 +90,28 @@ public class Ancestries implements Serializable newSuperclasses.put( subclass, superclass ); } m_superclasses = newSuperclasses; + + // rename interfaces + Set> entriesToAdd = Sets.newHashSet(); + for( Map.Entry entry : m_interfaces.entries() ) + { + String className = renames.get( entry.getKey() ); + if( className == null ) + { + className = entry.getKey(); + } + String interfaceName = renames.get( entry.getValue() ); + if( interfaceName == null ) + { + interfaceName = entry.getValue(); + } + entriesToAdd.add( new AbstractMap.SimpleEntry( className, interfaceName ) ); + } + m_interfaces.clear(); + for( Map.Entry entry : entriesToAdd ) + { + m_interfaces.put( entry.getKey(), entry.getValue() ); + } } public String getSuperclassName( String className ) @@ -86,6 +133,17 @@ public class Ancestries implements Serializable return ancestors; } + public Set getInterfaces( String className ) + { + Set interfaceNames = new HashSet(); + interfaceNames.addAll( m_interfaces.get( className ) ); + for( String ancestor : getAncestry( className ) ) + { + interfaceNames.addAll( m_interfaces.get( ancestor ) ); + } + return interfaceNames; + } + public List getSubclasses( String className ) { // linear search is fast enough for now @@ -102,6 +160,37 @@ public class Ancestries implements Serializable return subclasses; } + public Set getImplementingClasses( String targetInterfaceName ) + { + // linear search is fast enough for now + Set classNames = Sets.newHashSet(); + for( Map.Entry entry : m_interfaces.entries() ) + { + String className = entry.getKey(); + String interfaceName = entry.getValue(); + if( interfaceName.equals( targetInterfaceName ) ) + { + classNames.add( className ); + collectSubclasses( classNames, className ); + } + } + return classNames; + } + + public boolean isInterface( String className ) + { + return m_interfaces.containsValue( className ); + } + + private void collectSubclasses( Set classNames, String className ) + { + for( String subclassName : getSubclasses( className ) ) + { + classNames.add( subclassName ); + collectSubclasses( classNames, subclassName ); + } + } + private boolean isJre( String className ) { return className.startsWith( "java/" ) diff --git a/src/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java b/src/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java new file mode 100644 index 00000000..98648305 --- /dev/null +++ b/src/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.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( Ancestries ancestries ) + { + // get all method implementations + List nodes = Lists.newArrayList(); + for( String implementingClassName : ancestries.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 1 ) { - // TEMP System.out.println( "WARNING: Illegal constructor called by more than one class!" + callerClasses ); } } @@ -542,6 +546,13 @@ public class JarIndex return rootNode; } + public ClassImplementationsTreeNode getClassImplementations( Translator deobfuscatingTranslator, ClassEntry obfClassEntry ) + { + ClassImplementationsTreeNode node = new ClassImplementationsTreeNode( deobfuscatingTranslator, obfClassEntry ); + node.load( m_ancestries ); + return node; + } + public MethodInheritanceTreeNode getMethodInheritance( Translator deobfuscatingTranslator, MethodEntry obfMethodEntry ) { // travel to the ancestor implementation @@ -577,6 +588,90 @@ public class JarIndex return rootNode; } + public MethodImplementationsTreeNode getMethodImplementations( Translator deobfuscatingTranslator, MethodEntry obfMethodEntry ) + { + MethodEntry interfaceMethodEntry; + + // is this method on an interface? + if( m_ancestries.isInterface( obfMethodEntry.getClassName() ) ) + { + interfaceMethodEntry = obfMethodEntry; + } + else + { + // get the interface class + List methodInterfaces = Lists.newArrayList(); + for( String interfaceName : m_ancestries.getInterfaces( obfMethodEntry.getClassName() ) ) + { + // is this method defined in this interface? + MethodEntry methodInterface = new MethodEntry( + new ClassEntry( interfaceName ), + obfMethodEntry.getName(), + obfMethodEntry.getSignature() + ); + if( isMethodImplemented( methodInterface ) ) + { + methodInterfaces.add( methodInterface ); + } + } + if( methodInterfaces.isEmpty() ) + { + return null; + } + if( methodInterfaces.size() > 1 ) + { + throw new Error( "Too many interfaces define this method! This is not yet supported by Enigma!" ); + } + interfaceMethodEntry = methodInterfaces.get( 0 ); + } + + MethodImplementationsTreeNode rootNode = new MethodImplementationsTreeNode( deobfuscatingTranslator, interfaceMethodEntry ); + rootNode.load( this ); + return rootNode; + } + + 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( isMethodImplemented( methodEntry ) ) + { + // collect the entry + methodEntries.add( methodEntry ); + } + + // look at interface methods too + getRelatedMethodImplementations( methodEntries, getMethodImplementations( null, methodEntry ) ); + + // recurse + for( int i=0; i methodEntries, MethodImplementationsTreeNode node ) + { + MethodEntry methodEntry = node.getMethodEntry(); + if( isMethodImplemented( methodEntry ) ) + { + // collect the entry + methodEntries.add( methodEntry ); + } + + // recurse + for( int i=0; i> getFieldReferences( FieldEntry fieldEntry ) { return m_fieldReferences.get( fieldEntry ); @@ -626,16 +721,14 @@ public class JarIndex { // for each key/value pair... Set> entriesToAdd = Sets.newHashSet(); - Iterator> iter = map.entries().iterator(); - while( iter.hasNext() ) + for( Map.Entry entry : map.entries() ) { - Map.Entry entry = iter.next(); - iter.remove(); 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() ); @@ -761,5 +854,5 @@ public class JarIndex return thing; } return thing; - } + } } diff --git a/src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java b/src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java new file mode 100644 index 00000000..b529f3f6 --- /dev/null +++ b/src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.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 ) + { + 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.getAncestries().getImplementingClasses( m_entry.getClassName() ) ) + { + MethodEntry methodEntry = new MethodEntry( + new ClassEntry( implementingClassName ), + m_entry.getName(), + m_entry.getSignature() + ); + if( index.isMethodImplemented( 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 m_tokens; private JTabbedPane m_tabs; @@ -163,6 +166,7 @@ public class Gui private JMenuItem m_openEntryMenu; private JMenuItem m_openPreviousMenu; private JMenuItem m_showCallsMenu; + private JMenuItem m_showImplementationsMenu; // state private EntryReference m_reference; @@ -285,6 +289,10 @@ public class Gui showInheritance(); break; + case KeyEvent.VK_M: + showImplementations(); + break; + case KeyEvent.VK_N: openDeclaration(); break; @@ -337,6 +345,21 @@ public class Gui popupMenu.add( menu ); m_showInheritanceMenu = menu; } + { + JMenuItem menu = new JMenuItem( "Show Implementations" ); + menu.addActionListener( new ActionListener( ) + { + @Override + public void actionPerformed( ActionEvent event ) + { + showImplementations(); + } + } ); + menu.setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_M, 0 ) ); + menu.setEnabled( false ); + popupMenu.add( menu ); + m_showImplementationsMenu = menu; + } { JMenuItem menu = new JMenuItem( "Show Calls" ); menu.addActionListener( new ActionListener( ) @@ -428,6 +451,41 @@ public class Gui inheritancePanel.setLayout( new BorderLayout() ); inheritancePanel.add( new JScrollPane( m_inheritanceTree ) ); + // init implementations panel + m_implementationsTree = new JTree(); + m_implementationsTree.setModel( null ); + m_implementationsTree.addMouseListener( new MouseAdapter( ) + { + @Override + public void mouseClicked( MouseEvent event ) + { + if( event.getClickCount() == 2 ) + { + // get the selected node + TreePath path = m_implementationsTree.getSelectionPath(); + if( path == null ) + { + return; + } + + Object node = path.getLastPathComponent(); + if( node instanceof ClassImplementationsTreeNode ) + { + ClassImplementationsTreeNode classNode = (ClassImplementationsTreeNode)node; + m_controller.openDeclaration( classNode.getClassEntry() ); + } + else if( node instanceof MethodImplementationsTreeNode ) + { + MethodImplementationsTreeNode methodNode = (MethodImplementationsTreeNode)node; + m_controller.openDeclaration( methodNode.getMethodEntry() ); + } + } + } + } ); + JPanel implementationsPanel = new JPanel(); + implementationsPanel.setLayout( new BorderLayout() ); + implementationsPanel.add( new JScrollPane( m_implementationsTree ) ); + // init call panel m_callsTree = new JTree(); m_callsTree.setModel( null ); @@ -501,6 +559,7 @@ public class Gui m_tabs = new JTabbedPane(); m_tabs.setPreferredSize( new Dimension( 250, 0 ) ); m_tabs.addTab( "Inheritance", inheritancePanel ); + m_tabs.addTab( "Implementations", implementationsPanel ); m_tabs.addTab( "Call Graph", callPanel ); JSplitPane splitRight = new JSplitPane( JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, m_tabs ); splitRight.setResizeWeight( 1 ); // let the left side take all the slack @@ -1023,6 +1082,7 @@ public class Gui m_renameMenu.setEnabled( isToken ); m_showInheritanceMenu.setEnabled( isClassEntry || isMethodEntry || isConstructorEntry ); + m_showImplementationsMenu.setEnabled( isClassEntry || isMethodEntry ); m_showCallsMenu.setEnabled( isFieldEntry || isMethodEntry || isConstructorEntry ); m_openEntryMenu.setEnabled( isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry ); m_openPreviousMenu.setEnabled( m_controller.hasPreviousLocation() ); @@ -1097,6 +1157,8 @@ public class Gui return; } + m_inheritanceTree.setModel( null ); + if( m_reference.entry instanceof ClassEntry ) { // get the class inheritance @@ -1124,6 +1186,46 @@ public class Gui redraw(); } + private void showImplementations( ) + { + if( m_reference == null ) + { + return; + } + + m_implementationsTree.setModel( null ); + + if( m_reference.entry instanceof ClassEntry ) + { + // get the class implementations + ClassImplementationsTreeNode node = m_controller.getClassImplementations( (ClassEntry)m_reference.entry ); + if( node != null ) + { + // show the tree at the root + TreePath path = getPathToRoot( node ); + m_implementationsTree.setModel( new DefaultTreeModel( (TreeNode)path.getPathComponent( 0 ) ) ); + m_implementationsTree.expandPath( path ); + m_implementationsTree.setSelectionRow( m_implementationsTree.getRowForPath( path ) ); + } + } + else if( m_reference.entry instanceof MethodEntry ) + { + // get the method implementations + MethodImplementationsTreeNode node = m_controller.getMethodImplementations( (MethodEntry)m_reference.entry ); + if( node != null ) + { + // show the tree at the root + TreePath path = getPathToRoot( node ); + m_implementationsTree.setModel( new DefaultTreeModel( (TreeNode)path.getPathComponent( 0 ) ) ); + m_implementationsTree.expandPath( path ); + m_implementationsTree.setSelectionRow( m_implementationsTree.getRowForPath( path ) ); + } + } + + m_tabs.setSelectedIndex( 1 ); + redraw(); + } + private void showCalls( ) { if( m_reference == null ) @@ -1147,7 +1249,7 @@ public class Gui m_callsTree.setModel( new DefaultTreeModel( node ) ); } - m_tabs.setSelectedIndex( 1 ); + m_tabs.setSelectedIndex( 2 ); redraw(); } diff --git a/src/cuchaz/enigma/gui/GuiController.java b/src/cuchaz/enigma/gui/GuiController.java index 90bce520..bd79e480 100644 --- a/src/cuchaz/enigma/gui/GuiController.java +++ b/src/cuchaz/enigma/gui/GuiController.java @@ -24,9 +24,11 @@ import com.strobel.decompiler.languages.java.ast.CompilationUnit; import cuchaz.enigma.Deobfuscator; import cuchaz.enigma.analysis.BehaviorReferenceTreeNode; +import cuchaz.enigma.analysis.ClassImplementationsTreeNode; import cuchaz.enigma.analysis.ClassInheritanceTreeNode; import cuchaz.enigma.analysis.EntryReference; import cuchaz.enigma.analysis.FieldReferenceTreeNode; +import cuchaz.enigma.analysis.MethodImplementationsTreeNode; import cuchaz.enigma.analysis.MethodInheritanceTreeNode; import cuchaz.enigma.analysis.SourceIndex; import cuchaz.enigma.analysis.Token; @@ -182,6 +184,15 @@ public class GuiController return ClassInheritanceTreeNode.findNode( rootNode, obfClassEntry ); } + public ClassImplementationsTreeNode getClassImplementations( ClassEntry deobfClassEntry ) + { + ClassEntry obfClassEntry = m_deobfuscator.obfuscateEntry( deobfClassEntry ); + return m_deobfuscator.getJarIndex().getClassImplementations( + m_deobfuscator.getTranslator( TranslationDirection.Deobfuscating ), + obfClassEntry + ); + } + public MethodInheritanceTreeNode getMethodInheritance( MethodEntry deobfMethodEntry ) { MethodEntry obfMethodEntry = m_deobfuscator.obfuscateEntry( deobfMethodEntry ); @@ -192,6 +203,20 @@ public class GuiController return MethodInheritanceTreeNode.findNode( rootNode, obfMethodEntry ); } + public MethodImplementationsTreeNode getMethodImplementations( MethodEntry deobfMethodEntry ) + { + MethodEntry obfMethodEntry = m_deobfuscator.obfuscateEntry( deobfMethodEntry ); + MethodImplementationsTreeNode rootNode = m_deobfuscator.getJarIndex().getMethodImplementations( + m_deobfuscator.getTranslator( TranslationDirection.Deobfuscating ), + obfMethodEntry + ); + if( rootNode == null ) + { + return null; + } + return MethodImplementationsTreeNode.findNode( rootNode, obfMethodEntry ); + } + public FieldReferenceTreeNode getFieldReferences( FieldEntry deobfFieldEntry ) { FieldEntry obfFieldEntry = m_deobfuscator.obfuscateEntry( deobfFieldEntry ); diff --git a/src/cuchaz/enigma/mapping/Renamer.java b/src/cuchaz/enigma/mapping/Renamer.java index 0bb8dc12..79cbd30d 100644 --- a/src/cuchaz/enigma/mapping/Renamer.java +++ b/src/cuchaz/enigma/mapping/Renamer.java @@ -16,7 +16,6 @@ import java.io.OutputStream; import java.util.zip.GZIPOutputStream; import cuchaz.enigma.analysis.JarIndex; -import cuchaz.enigma.analysis.MethodInheritanceTreeNode; public class Renamer { @@ -57,25 +56,9 @@ public class Renamer public void setMethodTreeName( MethodEntry obf, String deobfName ) { - // get the method tree - setMethodTreeName( - m_index.getMethodInheritance( m_mappings.getTranslator( m_index.getAncestries(), TranslationDirection.Deobfuscating ), obf ), - deobfName - ); - } - - private void setMethodTreeName( MethodInheritanceTreeNode node, String deobfName ) - { - if( node.isImplemented() ) - { - // apply the name here - setMethodName( node.getMethodEntry(), deobfName ); - } - - // recurse - for( int i=0; i