Package cuchaz.enigma.analysis

Source Code of cuchaz.enigma.analysis.JarIndex

/*******************************************************************************
* 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.lang.reflect.Modifier;
import java.util.Collection;
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.FieldInfo;
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.BehaviorEntryFactory;
import cuchaz.enigma.mapping.ClassEntry;
import cuchaz.enigma.mapping.ConstructorEntry;
import cuchaz.enigma.mapping.Entry;
import cuchaz.enigma.mapping.FieldEntry;
import cuchaz.enigma.mapping.MethodEntry;
import cuchaz.enigma.mapping.SignatureUpdater;
import cuchaz.enigma.mapping.Translator;

public class JarIndex
{
  private Set<ClassEntry> m_obfClassEntries;
  private TranslationIndex m_translationIndex;
  private Multimap<String,String> m_interfaces;
  private Map<Entry,Access> m_access;
  private Map<FieldEntry,ClassEntry> m_fieldClasses;
  private Multimap<String,MethodEntry> m_methodImplementations;
  private Multimap<BehaviorEntry,EntryReference<BehaviorEntry,BehaviorEntry>> m_behaviorReferences;
  private Multimap<FieldEntry,EntryReference<FieldEntry,BehaviorEntry>> m_fieldReferences;
  private Multimap<String,String> m_innerClasses;
  private Map<String,String> m_outerClasses;
  private Map<String,BehaviorEntry> m_anonymousClasses;
  private Map<MethodEntry,MethodEntry> m_bridgeMethods;
 
  public JarIndex( )
  {
    m_obfClassEntries = Sets.newHashSet();
    m_translationIndex = new TranslationIndex();
    m_interfaces = HashMultimap.create();
    m_access = Maps.newHashMap();
    m_fieldClasses = Maps.newHashMap();
    m_methodImplementations = HashMultimap.create();
    m_behaviorReferences = HashMultimap.create();
    m_fieldReferences = HashMultimap.create();
    m_innerClasses = HashMultimap.create();
    m_outerClasses = Maps.newHashMap();
    m_anonymousClasses = Maps.newHashMap();
    m_bridgeMethods = 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 );
      ClassEntry classEntry = new ClassEntry( Descriptor.toJvmName( c.getName() ) );
      for( CtField field : c.getDeclaredFields() )
      {
        FieldEntry fieldEntry = new FieldEntry( classEntry, field.getName() );
        m_access.put( fieldEntry, Access.get( field ) );
      }
      for( CtMethod method : c.getDeclaredMethods() )
      {
        MethodEntry methodEntry = new MethodEntry( classEntry, method.getName(), method.getSignature() );
        m_access.put( methodEntry, Access.get( method ) );
      }
      for( CtConstructor constructor : c.getDeclaredConstructors() )
      {
        ConstructorEntry constructorEntry = new ConstructorEntry( classEntry, constructor.getSignature() );
        m_access.put( constructorEntry, Access.get( constructor ) );
      }
    }
   
    // step 3: index extends, implements, fields, and methods
    for( CtClass c : JarClassIterator.classes( jar ) )
    {
      ClassRenamer.moveAllClassesOutOfDefaultPackage( c, Constants.NonePackage );
      String className = Descriptor.toJvmName( c.getName() );
      m_translationIndex.addSuperclass( className, Descriptor.toJvmName( c.getClassFile().getSuperclass() ) );
      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 );
        }
        m_interfaces.put( className, interfaceName );
      }
      for( CtField field : c.getDeclaredFields() )
      {
        indexField( field );
      }
      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 );
        String outerClassName = findOuterClass( c );
        if( outerClassName != null )
        {
          String innerClassName = c.getSimpleName();
          m_innerClasses.put( outerClassName, innerClassName );
          boolean innerWasAdded = m_outerClasses.put( innerClassName, outerClassName ) == null;
          assert( innerWasAdded );
         
          BehaviorEntry enclosingBehavior = isAnonymousClass( c, outerClassName );
          if( enclosingBehavior != null )
          {
            m_anonymousClasses.put( innerClassName, enclosingBehavior );
           
            // DEBUG
            //System.out.println( "ANONYMOUS: " + outerClassName + "$" + innerClassName );
          }
          else
          {
            // DEBUG
            //System.out.println( "INNER: " + outerClassName + "$" + innerClassName );
          }
        }
      }
     
      // step 6: update other indices with inner class info
      Map<String,String> renames = Maps.newHashMap();
      for( Map.Entry<String,String> entry : m_outerClasses.entrySet() )
      {
        renames.put( Constants.NonePackage + "/" + entry.getKey(), entry.getValue() + "$" + entry.getKey() );
      }
      EntryRenamer.renameClassesInSet( renames, m_obfClassEntries );
      m_translationIndex.renameClasses( renames );
      EntryRenamer.renameClassesInMultimap( renames, m_interfaces );
      EntryRenamer.renameClassesInMultimap( renames, m_methodImplementations );
      EntryRenamer.renameClassesInMultimap( renames, m_behaviorReferences );
      EntryRenamer.renameClassesInMultimap( renames, m_fieldReferences );
      EntryRenamer.renameClassesInMap( renames, m_bridgeMethods );
      EntryRenamer.renameClassesInMap( renames, m_access );
    }
   
    // step 6: update other indices with bridge method info
    EntryRenamer.renameMethodsInMultimap( m_bridgeMethods, m_methodImplementations );
    EntryRenamer.renameMethodsInMultimap( m_bridgeMethods, m_behaviorReferences );
    EntryRenamer.renameMethodsInMultimap( m_bridgeMethods, m_fieldReferences );
    EntryRenamer.renameMethodsInMap( m_bridgeMethods, m_access );
  }
 
  private void indexField( CtField field )
  {
    // get the field entry
    String className = Descriptor.toJvmName( field.getDeclaringClass().getName() );
    FieldEntry fieldEntry = new FieldEntry( new ClassEntry( className ), field.getName() );
   
    m_translationIndex.addField( className, field.getName() );
   
    // is the field a class type?
    if( field.getSignature().startsWith( "L" ) )
    {
      ClassEntry fieldTypeEntry = new ClassEntry( field.getSignature().substring( 1, field.getSignature().length() - 1 ) );
      m_fieldClasses.put( fieldEntry, fieldTypeEntry );
    }
  }

  private void indexBehavior( CtBehavior behavior )
  {
    // get the behavior entry
    final BehaviorEntry behaviorEntry = BehaviorEntryFactory.create( behavior );
    if( behaviorEntry instanceof MethodEntry )
    {
      MethodEntry methodEntry = (MethodEntry)behaviorEntry;
     
      // index implementation
      m_methodImplementations.put( behaviorEntry.getClassName(), methodEntry );
     
      // look for bridge methods
      CtMethod bridgedMethod = getBridgedMethod( (CtMethod)behavior );
      if( bridgedMethod != null )
      {
        MethodEntry bridgedMethodEntry = new MethodEntry(
          behaviorEntry.getClassEntry(),
          bridgedMethod.getName(),
          bridgedMethod.getSignature()
        );
        m_bridgeMethods.put( bridgedMethodEntry, methodEntry );
      }
    }
    // looks like we don't care about constructors here
  }
 
  private void indexBehaviorReferences( CtBehavior behavior )
  {
    // index method calls
    final BehaviorEntry behaviorEntry = BehaviorEntryFactory.create( behavior );
    try
    {
      behavior.instrument( new ExprEditor( )
      {
        @Override
        public void edit( MethodCall call )
        {
          String className = Descriptor.toJvmName( call.getClassName() );
          MethodEntry calledMethodEntry = new MethodEntry(
            new ClassEntry( className ),
            call.getMethodName(),
            call.getSignature()
          );
          ClassEntry resolvedClassEntry = resolveEntryClass( calledMethodEntry );
          if( resolvedClassEntry != null && !resolvedClassEntry.equals( calledMethodEntry.getClassEntry() ) )
          {
            calledMethodEntry = new MethodEntry(
              resolvedClassEntry,
              call.getMethodName(),
              call.getSignature()
            );
          }
          EntryReference<BehaviorEntry,BehaviorEntry> reference = new EntryReference<BehaviorEntry,BehaviorEntry>(
            calledMethodEntry,
            call.getMethodName(),
            behaviorEntry
          );
          m_behaviorReferences.put( calledMethodEntry, reference );
        }
       
        @Override
        public void edit( FieldAccess call )
        {
          String className = Descriptor.toJvmName( call.getClassName() );
          FieldEntry calledFieldEntry = new FieldEntry(
            new ClassEntry( className ),
            call.getFieldName()
          );
          ClassEntry resolvedClassEntry = resolveEntryClass( calledFieldEntry );
          if( resolvedClassEntry != null && !resolvedClassEntry.equals( calledFieldEntry.getClassEntry() ) )
          {
            calledFieldEntry = new FieldEntry(
              resolvedClassEntry,
              call.getFieldName()
            );
          }
          EntryReference<FieldEntry,BehaviorEntry> reference = new EntryReference<FieldEntry,BehaviorEntry>(
            calledFieldEntry,
            call.getFieldName(),
            behaviorEntry
          );
          m_fieldReferences.put( calledFieldEntry, reference );
        }
       
        @Override
        public void edit( ConstructorCall call )
        {
          String className = Descriptor.toJvmName( call.getClassName() );
          ConstructorEntry calledConstructorEntry = new ConstructorEntry(
            new ClassEntry( className ),
            call.getSignature()
          );
          EntryReference<BehaviorEntry,BehaviorEntry> reference = new EntryReference<BehaviorEntry,BehaviorEntry>(
            calledConstructorEntry,
            call.getMethodName(),
            behaviorEntry
          );
          m_behaviorReferences.put( calledConstructorEntry, reference );
        }
       
        @Override
        public void edit( NewExpr call )
        {
          String className = Descriptor.toJvmName( call.getClassName() );
          ConstructorEntry calledConstructorEntry = new ConstructorEntry(
            new ClassEntry( className ),
            call.getSignature()
          );
          EntryReference<BehaviorEntry,BehaviorEntry> reference = new EntryReference<BehaviorEntry,BehaviorEntry>(
            calledConstructorEntry,
            call.getClassName(),
            behaviorEntry
          );
          m_behaviorReferences.put( calledConstructorEntry, reference );
        }
      } );
    }
    catch( CannotCompileException ex )
    {
      throw new Error( ex );
    }
  }
 
  public ClassEntry resolveEntryClass( Entry obfEntry )
  {
    // 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( !containsObfEntry( obfEntry ) )
    {
      // is there a parent class?
      String superclassName = m_translationIndex.getSuperclassName( obfEntry.getClassName() );
      if( superclassName == 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
      obfEntry = obfEntry.cloneToNewClass( new ClassEntry( superclassName ) );
    }
    return obfEntry.getClassEntry();
  }

  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<MethodCall> 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 String findOuterClass( CtClass c )
  {
    // 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<String> syntheticFieldTypes = Sets.newHashSet();
      if( !isIllegalConstructor( syntheticFieldTypes, constructor ) )
      {
        continue;
      }
     
      ClassEntry classEntry = new ClassEntry( Descriptor.toJvmName( c.getName() ) );
      ConstructorEntry constructorEntry = new ConstructorEntry( classEntry, constructor.getMethodInfo().getDescriptor() );
     
      // gather the classes from the illegally-set synthetic fields
      Set<ClassEntry> 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<ClassEntry> callerClasses = Sets.newHashSet();
      for( EntryReference<BehaviorEntry,BehaviorEntry> 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?
          String calledClassName = reference.entry.getClassName();
          String callerSuperclassName = m_translationIndex.getSuperclassName( reference.context.getClassName() );
          if( callerSuperclassName != null && callerSuperclassName.equals( calledClassName ) )
          {
            // 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().getName();
        }
        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().getName();
        }
        else
        {
          // multiple callers, do the illegally set classes narrow it down?
          Set<ClassEntry> intersection = Sets.newHashSet( callerClasses );
          intersection.retainAll( illegallySetClasses );
          if( intersection.size() == 1 )
          {
            return intersection.iterator().next().getName();
          }
          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<String> 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<FieldAccess> illegalFieldWrites = Lists.newArrayList();
    final List<ConstructorCall> 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<FieldInfo>)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, String outerClassName )
  {
    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 = new ConstructorEntry( innerClassEntry, constructor.getMethodInfo().getDescriptor() );
    Collection<EntryReference<BehaviorEntry,BehaviorEntry>> 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 ) )
    {
      ClassEntry fieldClass = getFieldClass( fieldEntry );
      if( fieldClass != null && fieldClass.equals( innerClassEntry ) )
      {
        // caller references this type, so it can't be anonymous
        return null;
      }
    }
    for( BehaviorEntry behaviorEntry : getReferencedBehaviors( caller ) )
    {
      // get the class types from the signature
      for( String className : SignatureUpdater.getClasses( behaviorEntry.getSignature() ) )
      {
        if( className.equals( innerClassEntry.getName() ) )
        {
          // caller references this type, so it can't be anonymous
          return null;
        }
      }
    }
   
    return caller;
  }

  public Set<ClassEntry> getObfClassEntries( )
  {
    return m_obfClassEntries;
  }
 
  public TranslationIndex getTranslationIndex( )
  {
    return m_translationIndex;
  }
 
  public Access getAccess( Entry entry )
  {
    return m_access.get( entry );
  }
 
  public ClassEntry getFieldClass( FieldEntry fieldEntry )
  {
    return m_fieldClasses.get( fieldEntry );
  }
 
  public ClassInheritanceTreeNode getClassInheritance( Translator deobfuscatingTranslator, ClassEntry obfClassEntry )
  {
    // get the root node
    List<String> ancestry = Lists.newArrayList();
    ancestry.add( obfClassEntry.getName() );
    ancestry.addAll( m_translationIndex.getAncestry( obfClassEntry.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
    String baseImplementationClassName = obfMethodEntry.getClassName();
    for( String ancestorClassName : m_translationIndex.getAncestry( obfMethodEntry.getClassName() ) )
    {
      MethodEntry ancestorMethodEntry = new MethodEntry(
        new ClassEntry( ancestorClassName ),
        obfMethodEntry.getName(),
        obfMethodEntry.getSignature()
      );
      if( containsObfBehavior( ancestorMethodEntry ) )
      {
        baseImplementationClassName = ancestorClassName;
      }
    }
   
    // make a root node at the base
    MethodEntry methodEntry = new MethodEntry(
      new ClassEntry( baseImplementationClassName ),
      obfMethodEntry.getName(),
      obfMethodEntry.getSignature()
    );
    MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode(
      deobfuscatingTranslator,
      methodEntry,
      containsObfBehavior( methodEntry )
    );
   
    // expand the full tree
    rootNode.load( this, true );
   
    return rootNode;
  }
 
  public MethodImplementationsTreeNode getMethodImplementations( Translator deobfuscatingTranslator, MethodEntry obfMethodEntry )
  {
    MethodEntry interfaceMethodEntry;
   
    // is this method on an interface?
    if( isInterface( obfMethodEntry.getClassName() ) )
    {
      interfaceMethodEntry = obfMethodEntry;
    }
    else
    {
      // get the interface class
      List<MethodEntry> methodInterfaces = Lists.newArrayList();
      for( String interfaceName : getInterfaces( obfMethodEntry.getClassName() ) )
      {
        // is this method defined in this interface?
        MethodEntry methodInterface = new MethodEntry(
          new ClassEntry( interfaceName ),
          obfMethodEntry.getName(),
          obfMethodEntry.getSignature()
        );
        if( containsObfBehavior( 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<MethodEntry> getRelatedMethodImplementations( MethodEntry obfMethodEntry )
  {
    Set<MethodEntry> methodEntries = Sets.newHashSet();
    getRelatedMethodImplementations( methodEntries, getMethodInheritance( null, obfMethodEntry ) );
    return methodEntries;
  }
 
  private void getRelatedMethodImplementations( Set<MethodEntry> methodEntries, MethodInheritanceTreeNode node )
  {
    MethodEntry methodEntry = node.getMethodEntry();
    if( containsObfBehavior( methodEntry ) )
    {
      // collect the entry
      methodEntries.add( methodEntry );
    }
   
    // look at interface methods too
    MethodImplementationsTreeNode implementations = getMethodImplementations( null, methodEntry );
    if( implementations != null )
    {
      getRelatedMethodImplementations( methodEntries, implementations );
    }
   
    // recurse
    for( int i=0; i<node.getChildCount(); i++ )
    {
      getRelatedMethodImplementations( methodEntries, (MethodInheritanceTreeNode)node.getChildAt( i ) );
    }
  }
 
  private void getRelatedMethodImplementations( Set<MethodEntry> 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<EntryReference<FieldEntry,BehaviorEntry>> getFieldReferences( FieldEntry fieldEntry )
  {
    return m_fieldReferences.get( fieldEntry );
  }
 
  public Collection<FieldEntry> getReferencedFields( BehaviorEntry behaviorEntry )
  {
    // linear search is fast enough for now
    Set<FieldEntry> fieldEntries = Sets.newHashSet();
    for( EntryReference<FieldEntry,BehaviorEntry> reference : m_fieldReferences.values() )
    {
      if( reference.context == behaviorEntry )
      {
        fieldEntries.add( reference.entry );
      }
    }
    return fieldEntries;
  }
 
  public Collection<EntryReference<BehaviorEntry,BehaviorEntry>> getBehaviorReferences( BehaviorEntry behaviorEntry )
  {
    return m_behaviorReferences.get( behaviorEntry );
  }

  public Collection<BehaviorEntry> getReferencedBehaviors( BehaviorEntry behaviorEntry )
  {
    // linear search is fast enough for now
    Set<BehaviorEntry> behaviorEntries = Sets.newHashSet();
    for( EntryReference<BehaviorEntry,BehaviorEntry> reference : m_behaviorReferences.values() )
    {
      if( reference.context == behaviorEntry )
      {
        behaviorEntries.add( reference.entry );
      }
    }
    return behaviorEntries;
  }
 
  public Collection<String> getInnerClasses( String obfOuterClassName )
  {
    return m_innerClasses.get( obfOuterClassName );
  }
 
  public String getOuterClass( String obfInnerClassName )
  {
    // make sure we use the right name
    if( new ClassEntry( obfInnerClassName ).getPackageName() != null )
    {
      throw new IllegalArgumentException( "Don't reference obfuscated inner classes using packages: " + obfInnerClassName );
    }
    return m_outerClasses.get( obfInnerClassName );
  }
 
  public boolean isAnonymousClass( String obfInnerClassName )
  {
    return m_anonymousClasses.containsKey( obfInnerClassName );
  }
 
  public BehaviorEntry getAnonymousClassCaller( String obfInnerClassName )
  {
    return m_anonymousClasses.get( obfInnerClassName );
  }
 
  public Set<String> getInterfaces( String className )
  {
    Set<String> interfaceNames = new HashSet<String>();
    interfaceNames.addAll( m_interfaces.get( className ) );
    for( String ancestor : m_translationIndex.getAncestry( className ) )
    {
      interfaceNames.addAll( m_interfaces.get( ancestor ) );
    }
    return interfaceNames;
  }
 
  public Set<String> getImplementingClasses( String targetInterfaceName )
  {
    // linear search is fast enough for now
    Set<String> classNames = Sets.newHashSet();
    for( Map.Entry<String,String> entry : m_interfaces.entries() )
    {
      String className = entry.getKey();
      String interfaceName = entry.getValue();
      if( interfaceName.equals( targetInterfaceName ) )
      {
        classNames.add( className );
        m_translationIndex.getSubclassNamesRecursively( classNames, className );
      }
    }
    return classNames;
  }
 
  public boolean isInterface( String className )
  {
    return m_interfaces.containsValue( className );
  }
 
  public MethodEntry getBridgeMethod( MethodEntry methodEntry )
  {
    return m_bridgeMethods.get( methodEntry );
  }
 
  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() >= Descriptor.numOfParameters( obfArgumentEntry.getBehaviorEntry().getSignature() ) )
    {
      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() );
    }
  }
}
TOP

Related Classes of cuchaz.enigma.analysis.JarIndex

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.