/*******************************************************************************
* 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.google.common.collect.*;
import cuchaz.enigma.mapping.*;
import cuchaz.enigma.mapping.Translator;
import javassist.*;
import javassist.bytecode.*;
import javassist.expr.*;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.jar.JarFile;
public class JarIndex {
private Set obfClassEntries;
private TranslationIndex translationIndex;
private Map access;
private Multimap fields;
private Multimap behaviors;
private Multimap methodImplementations;
private Multimap> behaviorReferences;
private Multimap> fieldReferences;
private Multimap innerClassesByOuter;
private Map outerClassesByInner;
private Map anonymousClasses;
private Map bridgedMethods;
private Set syntheticMethods;
public JarIndex() {
this.obfClassEntries = Sets.newHashSet();
this.translationIndex = new TranslationIndex();
this.access = Maps.newHashMap();
this.fields = HashMultimap.create();
this.behaviors = HashMultimap.create();
this.methodImplementations = HashMultimap.create();
this.behaviorReferences = HashMultimap.create();
this.fieldReferences = HashMultimap.create();
this.innerClassesByOuter = HashMultimap.create();
this.outerClassesByInner = Maps.newHashMap();
this.anonymousClasses = Maps.newHashMap();
this.bridgedMethods = Maps.newHashMap();
this.syntheticMethods = Sets.newHashSet();
}
public void indexJar(JarFile jar, boolean buildInnerClasses) {
// step 1: read the class names
this.obfClassEntries.addAll(JarClassIterator.getClassEntries(jar));
// step 2: index field/method/constructor access
for (CtClass c : JarClassIterator.classes(jar)) {
for (CtField field : c.getDeclaredFields()) {
FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
this.access.put(fieldEntry, Access.get(field));
this.fields.put(fieldEntry.getClassEntry(), fieldEntry);
}
for (CtBehavior behavior : c.getDeclaredBehaviors()) {
BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
this.access.put(behaviorEntry, Access.get(behavior));
this.behaviors.put(behaviorEntry.getClassEntry(), behaviorEntry);
}
}
// step 3: index extends, implements, fields, and methods
for (CtClass c : JarClassIterator.classes(jar)) {
this.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)) {
for (CtBehavior behavior : c.getDeclaredBehaviors()) {
indexBehaviorReferences(behavior);
}
}
if (buildInnerClasses) {
// step 5: index inner classes and anonymous classes
for (CtClass c : JarClassIterator.classes(jar)) {
ClassEntry innerClassEntry = EntryFactory.getClassEntry(c);
ClassEntry outerClassEntry = findOuterClass(c);
if (outerClassEntry != null) {
this.innerClassesByOuter.put(outerClassEntry, innerClassEntry);
boolean innerWasAdded = this.outerClassesByInner.put(innerClassEntry, outerClassEntry) == null;
assert (innerWasAdded);
BehaviorEntry enclosingBehavior = isAnonymousClass(c, outerClassEntry);
if (enclosingBehavior != null) {
this.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 : this.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, this.obfClassEntries);
this.translationIndex.renameClasses(renames);
EntryRenamer.renameClassesInMultimap(renames, this.methodImplementations);
EntryRenamer.renameClassesInMultimap(renames, this.behaviorReferences);
EntryRenamer.renameClassesInMultimap(renames, this.fieldReferences);
EntryRenamer.renameClassesInMap(renames, this.access);
}
}
private void indexBehavior(CtBehavior behavior) {
// get the behavior entry
final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
if (behaviorEntry instanceof MethodEntry) {
MethodEntry methodEntry = (MethodEntry) behaviorEntry;
// is synthetic
if ((behavior.getModifiers() & AccessFlag.SYNTHETIC) != 0) {
syntheticMethods.add(methodEntry);
}
// index implementation
this.methodImplementations.put(behaviorEntry.getClassName(), methodEntry);
// look for bridge and bridged methods
CtMethod bridgedMethod = getBridgedMethod((CtMethod) behavior);
if (bridgedMethod != null) {
this.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 = 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
);
behaviorReferences.put(calledMethodEntry, reference);
}
@Override
public void edit(FieldAccess call) {
FieldEntry calledFieldEntry = EntryFactory.getFieldEntry(call);
ClassEntry resolvedClassEntry = translationIndex.resolveEntryClass(calledFieldEntry);
if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledFieldEntry.getClassEntry())) {
calledFieldEntry = new FieldEntry(calledFieldEntry, resolvedClassEntry);
}
EntryReference reference = new EntryReference<>(
calledFieldEntry,
call.getFieldName(),
behaviorEntry
);
fieldReferences.put(calledFieldEntry, reference);
}
@Override
public void edit(ConstructorCall call) {
ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call);
EntryReference reference = new EntryReference<>(
calledConstructorEntry,
call.getMethodName(),
behaviorEntry
);
behaviorReferences.put(calledConstructorEntry, reference);
}
@Override
public void edit(NewExpr call) {
ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call);
EntryReference reference = new EntryReference<>(
calledConstructorEntry,
call.getClassName(),
behaviorEntry
);
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 = this.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?
return this.obfClassEntries.contains(outerClassEntry);
}
@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 this.obfClassEntries;
}
public Collection getObfFieldEntries() {
return this.fields.values();
}
public Collection getObfFieldEntries(ClassEntry classEntry) {
return this.fields.get(classEntry);
}
public Collection getObfBehaviorEntries() {
return this.behaviors.values();
}
public Collection getObfBehaviorEntries(ClassEntry classEntry) {
return this.behaviors.get(classEntry);
}
public TranslationIndex getTranslationIndex() {
return this.translationIndex;
}
public Access getAccess(Entry entry) {
return this.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 : this.translationIndex.getAncestry(obfClassEntry)) {
if (containsObfClass(classEntry)) {
ancestry.add(classEntry.getName());
}
}
ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode(
deobfuscatingTranslator,
ancestry.get(ancestry.size() - 1)
);
// expand all children recursively
rootNode.load(this.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 : this.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(new Translator(), 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 bridged methods!
MethodEntry bridgedEntry = getBridgedMethod(methodEntry);
while (bridgedEntry != null) {
methodEntries.addAll(getRelatedMethodImplementations(bridgedEntry));
bridgedEntry = getBridgedMethod(bridgedEntry);
}
// look at interface methods too
for (MethodImplementationsTreeNode implementationsNode : getMethodImplementations(new Translator(), 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);
}
// look at bridged methods!
MethodEntry bridgedEntry = getBridgedMethod(methodEntry);
while (bridgedEntry != null) {
methodEntries.addAll(getRelatedMethodImplementations(bridgedEntry));
bridgedEntry = getBridgedMethod(bridgedEntry);
}
// recurse
for (int i = 0; i < node.getChildCount(); i++) {
getRelatedMethodImplementations(methodEntries, (MethodImplementationsTreeNode) node.getChildAt(i));
}
}
public Collection> getFieldReferences(FieldEntry fieldEntry) {
return this.fieldReferences.get(fieldEntry);
}
public Collection getReferencedFields(BehaviorEntry behaviorEntry) {
// linear search is fast enough for now
Set fieldEntries = Sets.newHashSet();
for (EntryReference reference : this.fieldReferences.values()) {
if (reference.context == behaviorEntry) {
fieldEntries.add(reference.entry);
}
}
return fieldEntries;
}
public Collection> getBehaviorReferences(BehaviorEntry behaviorEntry) {
return this.behaviorReferences.get(behaviorEntry);
}
public Collection getReferencedBehaviors(BehaviorEntry behaviorEntry) {
// linear search is fast enough for now
Set behaviorEntries = Sets.newHashSet();
for (EntryReference reference : this.behaviorReferences.values()) {
if (reference.context == behaviorEntry) {
behaviorEntries.add(reference.entry);
}
}
return behaviorEntries;
}
public Collection getInnerClasses(ClassEntry obfOuterClassEntry) {
return this.innerClassesByOuter.get(obfOuterClassEntry);
}
public ClassEntry getOuterClass(ClassEntry obfInnerClassEntry) {
return this.outerClassesByInner.get(obfInnerClassEntry);
}
public boolean isAnonymousClass(ClassEntry obfInnerClassEntry) {
return this.anonymousClasses.containsKey(obfInnerClassEntry);
}
public boolean isSyntheticMethod(MethodEntry methodEntry) {
return this.syntheticMethods.contains(methodEntry);
}
public BehaviorEntry getAnonymousClassCaller(ClassEntry obfInnerClassName) {
return this.anonymousClasses.get(obfInnerClassName);
}
public Set getInterfaces(String className) {
ClassEntry classEntry = new ClassEntry(className);
Set interfaces = new HashSet<>();
interfaces.addAll(this.translationIndex.getInterfaces(classEntry));
for (ClassEntry ancestor : this.translationIndex.getAncestry(classEntry)) {
interfaces.addAll(this.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 : this.translationIndex.getClassInterfaces()) {
ClassEntry classEntry = entry.getKey();
ClassEntry interfaceEntry = entry.getValue();
if (interfaceEntry.getName().equals(targetInterfaceName)) {
String className = classEntry.getClassName();
classNames.add(className);
if (isInterface(className)) {
classNames.addAll(getImplementingClasses(className));
}
this.translationIndex.getSubclassNamesRecursively(classNames, classEntry);
}
}
return classNames;
}
public boolean isInterface(String className) {
return this.translationIndex.isInterface(new ClassEntry(className));
}
public boolean containsObfClass(ClassEntry obfClassEntry) {
return this.obfClassEntries.contains(obfClassEntry);
}
public boolean containsObfField(FieldEntry obfFieldEntry) {
return this.access.containsKey(obfFieldEntry);
}
public boolean containsObfBehavior(BehaviorEntry obfBehaviorEntry) {
return this.access.containsKey(obfBehaviorEntry);
}
public boolean containsEntryWithSameName(Entry entry) {
for (Entry target : this.access.keySet())
if (target.getName().equals(entry.getName()) && entry.getClass().isInstance(target.getClass()))
return true;
return false;
}
public boolean containsObfArgument(ArgumentEntry obfArgumentEntry) {
// check the behavior
if (!containsObfBehavior(obfArgumentEntry.getBehaviorEntry())) {
return false;
}
// check the argument
return obfArgumentEntry.getIndex() < obfArgumentEntry.getBehaviorEntry().getSignature().getArgumentTypes().size();
}
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 if (obfEntry instanceof LocalVariableEntry) {
// TODO: Implement it
return false;
} else {
throw new Error("Entry type not supported: " + obfEntry.getClass().getName());
}
}
public MethodEntry getBridgedMethod(MethodEntry bridgeMethodEntry) {
return this.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;
}
}