/******************************************************************************* * 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 m_obfClassEntries; private TranslationIndex m_translationIndex; private Multimap m_interfaces; private Map m_access; private Map m_fieldClasses; private Multimap m_methodImplementations; private Multimap> m_behaviorReferences; private Multimap> m_fieldReferences; private Multimap m_innerClasses; private Map m_outerClasses; private Map m_anonymousClasses; private Map 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 renames = Maps.newHashMap(); for( Map.Entry 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 reference = new EntryReference( 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 reference = new EntryReference( 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 reference = new EntryReference( 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 reference = new EntryReference( 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 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 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 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? 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 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 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, 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> 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 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 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 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 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 MethodImplementationsTreeNode implementations = getMethodImplementations( null, methodEntry ); if( implementations != null ) { getRelatedMethodImplementations( methodEntries, implementations ); } // recurse for( int i=0; i methodEntries, MethodImplementationsTreeNode node ) { MethodEntry methodEntry = node.getMethodEntry(); if( containsObfBehavior( methodEntry ) ) { // collect the entry methodEntries.add( methodEntry ); } // recurse for( int i=0; i> 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( 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 getInterfaces( String className ) { Set interfaceNames = new HashSet(); interfaceNames.addAll( m_interfaces.get( className ) ); for( String ancestor : m_translationIndex.getAncestry( className ) ) { interfaceNames.addAll( m_interfaces.get( ancestor ) ); } return interfaceNames; } 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 ); 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() ); } } }