summaryrefslogtreecommitdiff
path: root/src/cuchaz/enigma/analysis/JarIndex.java
diff options
context:
space:
mode:
authorGravatar lclc982016-06-30 00:49:21 +1000
committerGravatar GitHub2016-06-30 00:49:21 +1000
commit4be005617b3b8c3578cca07c5d085d12916f0d1d (patch)
treedb163431f38703e26da417ef05eaea2b27a498b9 /src/cuchaz/enigma/analysis/JarIndex.java
parentSome small changes to fix idea importing (diff)
downloadenigma-fork-4be005617b3b8c3578cca07c5d085d12916f0d1d.tar.gz
enigma-fork-4be005617b3b8c3578cca07c5d085d12916f0d1d.tar.xz
enigma-fork-4be005617b3b8c3578cca07c5d085d12916f0d1d.zip
Json format (#2)
* Added new format * Fixed bug * Updated Version
Diffstat (limited to 'src/cuchaz/enigma/analysis/JarIndex.java')
-rw-r--r--src/cuchaz/enigma/analysis/JarIndex.java839
1 files changed, 0 insertions, 839 deletions
diff --git a/src/cuchaz/enigma/analysis/JarIndex.java b/src/cuchaz/enigma/analysis/JarIndex.java
deleted file mode 100644
index 7e3c1b5..0000000
--- a/src/cuchaz/enigma/analysis/JarIndex.java
+++ /dev/null
@@ -1,839 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.lang.reflect.Modifier;
14import java.util.Collection;
15import java.util.Collections;
16import java.util.HashSet;
17import java.util.List;
18import java.util.Map;
19import java.util.Set;
20import java.util.jar.JarFile;
21
22import javassist.CannotCompileException;
23import javassist.CtBehavior;
24import javassist.CtClass;
25import javassist.CtConstructor;
26import javassist.CtField;
27import javassist.CtMethod;
28import javassist.NotFoundException;
29import javassist.bytecode.AccessFlag;
30import javassist.bytecode.Descriptor;
31import javassist.bytecode.EnclosingMethodAttribute;
32import javassist.bytecode.FieldInfo;
33import javassist.bytecode.InnerClassesAttribute;
34import javassist.expr.ConstructorCall;
35import javassist.expr.ExprEditor;
36import javassist.expr.FieldAccess;
37import javassist.expr.MethodCall;
38import javassist.expr.NewExpr;
39
40import com.google.common.collect.HashMultimap;
41import com.google.common.collect.Lists;
42import com.google.common.collect.Maps;
43import com.google.common.collect.Multimap;
44import com.google.common.collect.Sets;
45
46import cuchaz.enigma.Constants;
47import cuchaz.enigma.bytecode.ClassRenamer;
48import cuchaz.enigma.mapping.ArgumentEntry;
49import cuchaz.enigma.mapping.BehaviorEntry;
50import cuchaz.enigma.mapping.ClassEntry;
51import cuchaz.enigma.mapping.ConstructorEntry;
52import cuchaz.enigma.mapping.Entry;
53import cuchaz.enigma.mapping.EntryFactory;
54import cuchaz.enigma.mapping.FieldEntry;
55import cuchaz.enigma.mapping.MethodEntry;
56import cuchaz.enigma.mapping.Translator;
57
58public class JarIndex {
59
60 private Set<ClassEntry> m_obfClassEntries;
61 private TranslationIndex m_translationIndex;
62 private Map<Entry,Access> m_access;
63 private Multimap<ClassEntry,FieldEntry> m_fields;
64 private Multimap<ClassEntry,BehaviorEntry> m_behaviors;
65 private Multimap<String,MethodEntry> m_methodImplementations;
66 private Multimap<BehaviorEntry,EntryReference<BehaviorEntry,BehaviorEntry>> m_behaviorReferences;
67 private Multimap<FieldEntry,EntryReference<FieldEntry,BehaviorEntry>> m_fieldReferences;
68 private Multimap<ClassEntry,ClassEntry> m_innerClassesByOuter;
69 private Map<ClassEntry,ClassEntry> m_outerClassesByInner;
70 private Map<ClassEntry,BehaviorEntry> m_anonymousClasses;
71 private Map<MethodEntry,MethodEntry> m_bridgedMethods;
72
73 public JarIndex() {
74 m_obfClassEntries = Sets.newHashSet();
75 m_translationIndex = new TranslationIndex();
76 m_access = Maps.newHashMap();
77 m_fields = HashMultimap.create();
78 m_behaviors = HashMultimap.create();
79 m_methodImplementations = HashMultimap.create();
80 m_behaviorReferences = HashMultimap.create();
81 m_fieldReferences = HashMultimap.create();
82 m_innerClassesByOuter = HashMultimap.create();
83 m_outerClassesByInner = Maps.newHashMap();
84 m_anonymousClasses = Maps.newHashMap();
85 m_bridgedMethods = Maps.newHashMap();
86 }
87
88 public void indexJar(JarFile jar, boolean buildInnerClasses) {
89
90 // step 1: read the class names
91 for (ClassEntry classEntry : JarClassIterator.getClassEntries(jar)) {
92 if (classEntry.isInDefaultPackage()) {
93 // move out of default package
94 classEntry = new ClassEntry(Constants.NonePackage + "/" + classEntry.getName());
95 }
96 m_obfClassEntries.add(classEntry);
97 }
98
99 // step 2: index field/method/constructor access
100 for (CtClass c : JarClassIterator.classes(jar)) {
101 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
102 for (CtField field : c.getDeclaredFields()) {
103 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
104 m_access.put(fieldEntry, Access.get(field));
105 m_fields.put(fieldEntry.getClassEntry(), fieldEntry);
106 }
107 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
108 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
109 m_access.put(behaviorEntry, Access.get(behavior));
110 m_behaviors.put(behaviorEntry.getClassEntry(), behaviorEntry);
111 }
112 }
113
114 // step 3: index extends, implements, fields, and methods
115 for (CtClass c : JarClassIterator.classes(jar)) {
116 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
117 m_translationIndex.indexClass(c);
118 String className = Descriptor.toJvmName(c.getName());
119 for (String interfaceName : c.getClassFile().getInterfaces()) {
120 className = Descriptor.toJvmName(className);
121 interfaceName = Descriptor.toJvmName(interfaceName);
122 if (className.equals(interfaceName)) {
123 throw new IllegalArgumentException("Class cannot be its own interface! " + className);
124 }
125 }
126 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
127 indexBehavior(behavior);
128 }
129 }
130
131 // step 4: index field, method, constructor references
132 for (CtClass c : JarClassIterator.classes(jar)) {
133 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
134 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
135 indexBehaviorReferences(behavior);
136 }
137 }
138
139 if (buildInnerClasses) {
140
141 // step 5: index inner classes and anonymous classes
142 for (CtClass c : JarClassIterator.classes(jar)) {
143 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
144 ClassEntry innerClassEntry = EntryFactory.getClassEntry(c);
145 ClassEntry outerClassEntry = findOuterClass(c);
146 if (outerClassEntry != null) {
147 m_innerClassesByOuter.put(outerClassEntry, innerClassEntry);
148 boolean innerWasAdded = m_outerClassesByInner.put(innerClassEntry, outerClassEntry) == null;
149 assert (innerWasAdded);
150
151 BehaviorEntry enclosingBehavior = isAnonymousClass(c, outerClassEntry);
152 if (enclosingBehavior != null) {
153 m_anonymousClasses.put(innerClassEntry, enclosingBehavior);
154
155 // DEBUG
156 //System.out.println("ANONYMOUS: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName());
157 } else {
158 // DEBUG
159 //System.out.println("INNER: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName());
160 }
161 }
162 }
163
164 // step 6: update other indices with inner class info
165 Map<String,String> renames = Maps.newHashMap();
166 for (ClassEntry innerClassEntry : m_innerClassesByOuter.values()) {
167 String newName = innerClassEntry.buildClassEntry(getObfClassChain(innerClassEntry)).getName();
168 if (!innerClassEntry.getName().equals(newName)) {
169 // DEBUG
170 //System.out.println("REPLACE: " + innerClassEntry.getName() + " WITH " + newName);
171 renames.put(innerClassEntry.getName(), newName);
172 }
173 }
174 EntryRenamer.renameClassesInSet(renames, m_obfClassEntries);
175 m_translationIndex.renameClasses(renames);
176 EntryRenamer.renameClassesInMultimap(renames, m_methodImplementations);
177 EntryRenamer.renameClassesInMultimap(renames, m_behaviorReferences);
178 EntryRenamer.renameClassesInMultimap(renames, m_fieldReferences);
179 EntryRenamer.renameClassesInMap(renames, m_access);
180 }
181 }
182
183 private void indexBehavior(CtBehavior behavior) {
184 // get the behavior entry
185 final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
186 if (behaviorEntry instanceof MethodEntry) {
187 MethodEntry methodEntry = (MethodEntry)behaviorEntry;
188
189 // index implementation
190 m_methodImplementations.put(behaviorEntry.getClassName(), methodEntry);
191
192 // look for bridge and bridged methods
193 CtMethod bridgedMethod = getBridgedMethod((CtMethod)behavior);
194 if (bridgedMethod != null) {
195 m_bridgedMethods.put(methodEntry, EntryFactory.getMethodEntry(bridgedMethod));
196 }
197 }
198 // looks like we don't care about constructors here
199 }
200
201 private void indexBehaviorReferences(CtBehavior behavior) {
202 // index method calls
203 final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
204 try {
205 behavior.instrument(new ExprEditor() {
206 @Override
207 public void edit(MethodCall call) {
208 MethodEntry calledMethodEntry = EntryFactory.getMethodEntry(call);
209 ClassEntry resolvedClassEntry = m_translationIndex.resolveEntryClass(calledMethodEntry);
210 if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledMethodEntry.getClassEntry())) {
211 calledMethodEntry = new MethodEntry(
212 resolvedClassEntry,
213 calledMethodEntry.getName(),
214 calledMethodEntry.getSignature()
215 );
216 }
217 EntryReference<BehaviorEntry,BehaviorEntry> reference = new EntryReference<BehaviorEntry,BehaviorEntry>(
218 calledMethodEntry,
219 call.getMethodName(),
220 behaviorEntry
221 );
222 m_behaviorReferences.put(calledMethodEntry, reference);
223 }
224
225 @Override
226 public void edit(FieldAccess call) {
227 FieldEntry calledFieldEntry = EntryFactory.getFieldEntry(call);
228 ClassEntry resolvedClassEntry = m_translationIndex.resolveEntryClass(calledFieldEntry);
229 if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledFieldEntry.getClassEntry())) {
230 calledFieldEntry = new FieldEntry(calledFieldEntry, resolvedClassEntry);
231 }
232 EntryReference<FieldEntry,BehaviorEntry> reference = new EntryReference<FieldEntry,BehaviorEntry>(
233 calledFieldEntry,
234 call.getFieldName(),
235 behaviorEntry
236 );
237 m_fieldReferences.put(calledFieldEntry, reference);
238 }
239
240 @Override
241 public void edit(ConstructorCall call) {
242 ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call);
243 EntryReference<BehaviorEntry,BehaviorEntry> reference = new EntryReference<BehaviorEntry,BehaviorEntry>(
244 calledConstructorEntry,
245 call.getMethodName(),
246 behaviorEntry
247 );
248 m_behaviorReferences.put(calledConstructorEntry, reference);
249 }
250
251 @Override
252 public void edit(NewExpr call) {
253 ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call);
254 EntryReference<BehaviorEntry,BehaviorEntry> reference = new EntryReference<BehaviorEntry,BehaviorEntry>(
255 calledConstructorEntry,
256 call.getClassName(),
257 behaviorEntry
258 );
259 m_behaviorReferences.put(calledConstructorEntry, reference);
260 }
261 });
262 } catch (CannotCompileException ex) {
263 throw new Error(ex);
264 }
265 }
266
267 private CtMethod getBridgedMethod(CtMethod method) {
268
269 // bridge methods just call another method, cast it to the return type, and return the result
270 // let's see if we can detect this scenario
271
272 // skip non-synthetic methods
273 if ((method.getModifiers() & AccessFlag.SYNTHETIC) == 0) {
274 return null;
275 }
276
277 // get all the called methods
278 final List<MethodCall> methodCalls = Lists.newArrayList();
279 try {
280 method.instrument(new ExprEditor() {
281 @Override
282 public void edit(MethodCall call) {
283 methodCalls.add(call);
284 }
285 });
286 } catch (CannotCompileException ex) {
287 // this is stupid... we're not even compiling anything
288 throw new Error(ex);
289 }
290
291 // is there just one?
292 if (methodCalls.size() != 1) {
293 return null;
294 }
295 MethodCall call = methodCalls.get(0);
296
297 try {
298 // we have a bridge method!
299 return call.getMethod();
300 } catch (NotFoundException ex) {
301 // can't find the type? not a bridge method
302 return null;
303 }
304 }
305
306 private ClassEntry findOuterClass(CtClass c) {
307
308 ClassEntry classEntry = EntryFactory.getClassEntry(c);
309
310 // does this class already have an outer class?
311 if (classEntry.isInnerClass()) {
312 return classEntry.getOuterClassEntry();
313 }
314
315 // inner classes:
316 // have constructors that can (illegally) set synthetic fields
317 // the outer class is the only class that calls constructors
318
319 // use the synthetic fields to find the synthetic constructors
320 for (CtConstructor constructor : c.getDeclaredConstructors()) {
321 Set<String> syntheticFieldTypes = Sets.newHashSet();
322 if (!isIllegalConstructor(syntheticFieldTypes, constructor)) {
323 continue;
324 }
325
326 ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor);
327
328 // gather the classes from the illegally-set synthetic fields
329 Set<ClassEntry> illegallySetClasses = Sets.newHashSet();
330 for (String type : syntheticFieldTypes) {
331 if (type.startsWith("L")) {
332 ClassEntry outerClassEntry = new ClassEntry(type.substring(1, type.length() - 1));
333 if (isSaneOuterClass(outerClassEntry, classEntry)) {
334 illegallySetClasses.add(outerClassEntry);
335 }
336 }
337 }
338
339 // who calls this constructor?
340 Set<ClassEntry> callerClasses = Sets.newHashSet();
341 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : getBehaviorReferences(constructorEntry)) {
342
343 // make sure it's not a call to super
344 if (reference.entry instanceof ConstructorEntry && reference.context instanceof ConstructorEntry) {
345
346 // is the entry a superclass of the context?
347 ClassEntry calledClassEntry = reference.entry.getClassEntry();
348 ClassEntry superclassEntry = m_translationIndex.getSuperclass(reference.context.getClassEntry());
349 if (superclassEntry != null && superclassEntry.equals(calledClassEntry)) {
350 // it's a super call, skip
351 continue;
352 }
353 }
354
355 if (isSaneOuterClass(reference.context.getClassEntry(), classEntry)) {
356 callerClasses.add(reference.context.getClassEntry());
357 }
358 }
359
360 // do we have an answer yet?
361 if (callerClasses.isEmpty()) {
362 if (illegallySetClasses.size() == 1) {
363 return illegallySetClasses.iterator().next();
364 } else {
365 System.out.println(String.format("WARNING: Unable to find outer class for %s. No caller and no illegally set field classes.", classEntry));
366 }
367 } else {
368 if (callerClasses.size() == 1) {
369 return callerClasses.iterator().next();
370 } else {
371 // multiple callers, do the illegally set classes narrow it down?
372 Set<ClassEntry> intersection = Sets.newHashSet(callerClasses);
373 intersection.retainAll(illegallySetClasses);
374 if (intersection.size() == 1) {
375 return intersection.iterator().next();
376 } else {
377 System.out.println(String.format("WARNING: Unable to choose outer class for %s among options: %s", classEntry, callerClasses));
378 }
379 }
380 }
381 }
382
383 return null;
384 }
385
386 private boolean isSaneOuterClass(ClassEntry outerClassEntry, ClassEntry innerClassEntry) {
387
388 // clearly this would be silly
389 if (outerClassEntry.equals(innerClassEntry)) {
390 return false;
391 }
392
393 // is the outer class in the jar?
394 if (!m_obfClassEntries.contains(outerClassEntry)) {
395 return false;
396 }
397
398 return true;
399 }
400
401 @SuppressWarnings("unchecked")
402 private boolean isIllegalConstructor(Set<String> syntheticFieldTypes, CtConstructor constructor) {
403
404 // illegal constructors only set synthetic member fields, then call super()
405 String className = constructor.getDeclaringClass().getName();
406
407 // collect all the field accesses, constructor calls, and method calls
408 final List<FieldAccess> illegalFieldWrites = Lists.newArrayList();
409 final List<ConstructorCall> constructorCalls = Lists.newArrayList();
410 try {
411 constructor.instrument(new ExprEditor() {
412 @Override
413 public void edit(FieldAccess fieldAccess) {
414 if (fieldAccess.isWriter() && constructorCalls.isEmpty()) {
415 illegalFieldWrites.add(fieldAccess);
416 }
417 }
418
419 @Override
420 public void edit(ConstructorCall constructorCall) {
421 constructorCalls.add(constructorCall);
422 }
423 });
424 } catch (CannotCompileException ex) {
425 // we're not compiling anything... this is stupid
426 throw new Error(ex);
427 }
428
429 // are there any illegal field writes?
430 if (illegalFieldWrites.isEmpty()) {
431 return false;
432 }
433
434 // are all the writes to synthetic fields?
435 for (FieldAccess fieldWrite : illegalFieldWrites) {
436
437 // all illegal writes have to be to the local class
438 if (!fieldWrite.getClassName().equals(className)) {
439 System.err.println(String.format("WARNING: illegal write to non-member field %s.%s", fieldWrite.getClassName(), fieldWrite.getFieldName()));
440 return false;
441 }
442
443 // find the field
444 FieldInfo fieldInfo = null;
445 for (FieldInfo info : (List<FieldInfo>)constructor.getDeclaringClass().getClassFile().getFields()) {
446 if (info.getName().equals(fieldWrite.getFieldName()) && info.getDescriptor().equals(fieldWrite.getSignature())) {
447 fieldInfo = info;
448 break;
449 }
450 }
451 if (fieldInfo == null) {
452 // field is in a superclass or something, can't be a local synthetic member
453 return false;
454 }
455
456 // is this field synthetic?
457 boolean isSynthetic = (fieldInfo.getAccessFlags() & AccessFlag.SYNTHETIC) != 0;
458 if (isSynthetic) {
459 syntheticFieldTypes.add(fieldInfo.getDescriptor());
460 } else {
461 System.err.println(String.format("WARNING: illegal write to non synthetic field %s %s.%s", fieldInfo.getDescriptor(), className, fieldInfo.getName()));
462 return false;
463 }
464 }
465
466 // we passed all the tests!
467 return true;
468 }
469
470 private BehaviorEntry isAnonymousClass(CtClass c, ClassEntry outerClassEntry) {
471
472 // is this class already marked anonymous?
473 EnclosingMethodAttribute enclosingMethodAttribute = (EnclosingMethodAttribute)c.getClassFile().getAttribute(EnclosingMethodAttribute.tag);
474 if (enclosingMethodAttribute != null) {
475 if (enclosingMethodAttribute.methodIndex() > 0) {
476 return EntryFactory.getBehaviorEntry(
477 Descriptor.toJvmName(enclosingMethodAttribute.className()),
478 enclosingMethodAttribute.methodName(),
479 enclosingMethodAttribute.methodDescriptor()
480 );
481 } else {
482 // an attribute but no method? assume not anonymous
483 return null;
484 }
485 }
486
487 // if there's an inner class attribute, but not an enclosing method attribute, then it's not anonymous
488 InnerClassesAttribute innerClassesAttribute = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag);
489 if (innerClassesAttribute != null) {
490 return null;
491 }
492
493 ClassEntry innerClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
494
495 // anonymous classes:
496 // can't be abstract
497 // have only one constructor
498 // it's called exactly once by the outer class
499 // the type the instance is assigned to can't be this type
500
501 // is abstract?
502 if (Modifier.isAbstract(c.getModifiers())) {
503 return null;
504 }
505
506 // is there exactly one constructor?
507 if (c.getDeclaredConstructors().length != 1) {
508 return null;
509 }
510 CtConstructor constructor = c.getDeclaredConstructors()[0];
511
512 // is this constructor called exactly once?
513 ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor);
514 Collection<EntryReference<BehaviorEntry,BehaviorEntry>> references = getBehaviorReferences(constructorEntry);
515 if (references.size() != 1) {
516 return null;
517 }
518
519 // does the caller use this type?
520 BehaviorEntry caller = references.iterator().next().context;
521 for (FieldEntry fieldEntry : getReferencedFields(caller)) {
522 if (fieldEntry.getType().hasClass() && fieldEntry.getType().getClassEntry().equals(innerClassEntry)) {
523 // caller references this type, so it can't be anonymous
524 return null;
525 }
526 }
527 for (BehaviorEntry behaviorEntry : getReferencedBehaviors(caller)) {
528 if (behaviorEntry.getSignature().hasClass(innerClassEntry)) {
529 return null;
530 }
531 }
532
533 return caller;
534 }
535
536 public Set<ClassEntry> getObfClassEntries() {
537 return m_obfClassEntries;
538 }
539
540 public Collection<FieldEntry> getObfFieldEntries() {
541 return m_fields.values();
542 }
543
544 public Collection<FieldEntry> getObfFieldEntries(ClassEntry classEntry) {
545 return m_fields.get(classEntry);
546 }
547
548 public Collection<BehaviorEntry> getObfBehaviorEntries() {
549 return m_behaviors.values();
550 }
551
552 public Collection<BehaviorEntry> getObfBehaviorEntries(ClassEntry classEntry) {
553 return m_behaviors.get(classEntry);
554 }
555
556 public TranslationIndex getTranslationIndex() {
557 return m_translationIndex;
558 }
559
560 public Access getAccess(Entry entry) {
561 return m_access.get(entry);
562 }
563
564 public ClassInheritanceTreeNode getClassInheritance(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) {
565
566 // get the root node
567 List<String> ancestry = Lists.newArrayList();
568 ancestry.add(obfClassEntry.getName());
569 for (ClassEntry classEntry : m_translationIndex.getAncestry(obfClassEntry)) {
570 if (containsObfClass(classEntry)) {
571 ancestry.add(classEntry.getName());
572 }
573 }
574 ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode(
575 deobfuscatingTranslator,
576 ancestry.get(ancestry.size() - 1)
577 );
578
579 // expand all children recursively
580 rootNode.load(m_translationIndex, true);
581
582 return rootNode;
583 }
584
585 public ClassImplementationsTreeNode getClassImplementations(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) {
586
587 // is this even an interface?
588 if (isInterface(obfClassEntry.getClassName())) {
589 ClassImplementationsTreeNode node = new ClassImplementationsTreeNode(deobfuscatingTranslator, obfClassEntry);
590 node.load(this);
591 return node;
592 }
593 return null;
594 }
595
596 public MethodInheritanceTreeNode getMethodInheritance(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) {
597
598 // travel to the ancestor implementation
599 ClassEntry baseImplementationClassEntry = obfMethodEntry.getClassEntry();
600 for (ClassEntry ancestorClassEntry : m_translationIndex.getAncestry(obfMethodEntry.getClassEntry())) {
601 MethodEntry ancestorMethodEntry = new MethodEntry(
602 new ClassEntry(ancestorClassEntry),
603 obfMethodEntry.getName(),
604 obfMethodEntry.getSignature()
605 );
606 if (containsObfBehavior(ancestorMethodEntry)) {
607 baseImplementationClassEntry = ancestorClassEntry;
608 }
609 }
610
611 // make a root node at the base
612 MethodEntry methodEntry = new MethodEntry(
613 baseImplementationClassEntry,
614 obfMethodEntry.getName(),
615 obfMethodEntry.getSignature()
616 );
617 MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode(
618 deobfuscatingTranslator,
619 methodEntry,
620 containsObfBehavior(methodEntry)
621 );
622
623 // expand the full tree
624 rootNode.load(this, true);
625
626 return rootNode;
627 }
628
629 public List<MethodImplementationsTreeNode> getMethodImplementations(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) {
630
631 List<MethodEntry> interfaceMethodEntries = Lists.newArrayList();
632
633 // is this method on an interface?
634 if (isInterface(obfMethodEntry.getClassName())) {
635 interfaceMethodEntries.add(obfMethodEntry);
636 } else {
637 // get the interface class
638 for (ClassEntry interfaceEntry : getInterfaces(obfMethodEntry.getClassName())) {
639
640 // is this method defined in this interface?
641 MethodEntry methodInterface = new MethodEntry(
642 interfaceEntry,
643 obfMethodEntry.getName(),
644 obfMethodEntry.getSignature()
645 );
646 if (containsObfBehavior(methodInterface)) {
647 interfaceMethodEntries.add(methodInterface);
648 }
649 }
650 }
651
652 List<MethodImplementationsTreeNode> nodes = Lists.newArrayList();
653 if (!interfaceMethodEntries.isEmpty()) {
654 for (MethodEntry interfaceMethodEntry : interfaceMethodEntries) {
655 MethodImplementationsTreeNode node = new MethodImplementationsTreeNode(deobfuscatingTranslator, interfaceMethodEntry);
656 node.load(this);
657 nodes.add(node);
658 }
659 }
660 return nodes;
661 }
662
663 public Set<MethodEntry> getRelatedMethodImplementations(MethodEntry obfMethodEntry) {
664 Set<MethodEntry> methodEntries = Sets.newHashSet();
665 getRelatedMethodImplementations(methodEntries, getMethodInheritance(null, obfMethodEntry));
666 return methodEntries;
667 }
668
669 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodInheritanceTreeNode node) {
670 MethodEntry methodEntry = node.getMethodEntry();
671 if (containsObfBehavior(methodEntry)) {
672 // collect the entry
673 methodEntries.add(methodEntry);
674 }
675
676 // look at interface methods too
677 for (MethodImplementationsTreeNode implementationsNode : getMethodImplementations(null, methodEntry)) {
678 getRelatedMethodImplementations(methodEntries, implementationsNode);
679 }
680
681 // recurse
682 for (int i = 0; i < node.getChildCount(); i++) {
683 getRelatedMethodImplementations(methodEntries, (MethodInheritanceTreeNode)node.getChildAt(i));
684 }
685 }
686
687 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodImplementationsTreeNode node) {
688 MethodEntry methodEntry = node.getMethodEntry();
689 if (containsObfBehavior(methodEntry)) {
690 // collect the entry
691 methodEntries.add(methodEntry);
692 }
693
694 // recurse
695 for (int i = 0; i < node.getChildCount(); i++) {
696 getRelatedMethodImplementations(methodEntries, (MethodImplementationsTreeNode)node.getChildAt(i));
697 }
698 }
699
700 public Collection<EntryReference<FieldEntry,BehaviorEntry>> getFieldReferences(FieldEntry fieldEntry) {
701 return m_fieldReferences.get(fieldEntry);
702 }
703
704 public Collection<FieldEntry> getReferencedFields(BehaviorEntry behaviorEntry) {
705 // linear search is fast enough for now
706 Set<FieldEntry> fieldEntries = Sets.newHashSet();
707 for (EntryReference<FieldEntry,BehaviorEntry> reference : m_fieldReferences.values()) {
708 if (reference.context == behaviorEntry) {
709 fieldEntries.add(reference.entry);
710 }
711 }
712 return fieldEntries;
713 }
714
715 public Collection<EntryReference<BehaviorEntry,BehaviorEntry>> getBehaviorReferences(BehaviorEntry behaviorEntry) {
716 return m_behaviorReferences.get(behaviorEntry);
717 }
718
719 public Collection<BehaviorEntry> getReferencedBehaviors(BehaviorEntry behaviorEntry) {
720 // linear search is fast enough for now
721 Set<BehaviorEntry> behaviorEntries = Sets.newHashSet();
722 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : m_behaviorReferences.values()) {
723 if (reference.context == behaviorEntry) {
724 behaviorEntries.add(reference.entry);
725 }
726 }
727 return behaviorEntries;
728 }
729
730 public Collection<ClassEntry> getInnerClasses(ClassEntry obfOuterClassEntry) {
731 return m_innerClassesByOuter.get(obfOuterClassEntry);
732 }
733
734 public ClassEntry getOuterClass(ClassEntry obfInnerClassEntry) {
735 return m_outerClassesByInner.get(obfInnerClassEntry);
736 }
737
738 public boolean isAnonymousClass(ClassEntry obfInnerClassEntry) {
739 return m_anonymousClasses.containsKey(obfInnerClassEntry);
740 }
741
742 public BehaviorEntry getAnonymousClassCaller(ClassEntry obfInnerClassName) {
743 return m_anonymousClasses.get(obfInnerClassName);
744 }
745
746 public Set<ClassEntry> getInterfaces(String className) {
747 ClassEntry classEntry = new ClassEntry(className);
748 Set<ClassEntry> interfaces = new HashSet<ClassEntry>();
749 interfaces.addAll(m_translationIndex.getInterfaces(classEntry));
750 for (ClassEntry ancestor : m_translationIndex.getAncestry(classEntry)) {
751 interfaces.addAll(m_translationIndex.getInterfaces(ancestor));
752 }
753 return interfaces;
754 }
755
756 public Set<String> getImplementingClasses(String targetInterfaceName) {
757
758 // linear search is fast enough for now
759 Set<String> classNames = Sets.newHashSet();
760 for (Map.Entry<ClassEntry,ClassEntry> entry : m_translationIndex.getClassInterfaces()) {
761 ClassEntry classEntry = entry.getKey();
762 ClassEntry interfaceEntry = entry.getValue();
763 if (interfaceEntry.getName().equals(targetInterfaceName)) {
764 classNames.add(classEntry.getClassName());
765 m_translationIndex.getSubclassNamesRecursively(classNames, classEntry);
766 }
767 }
768 return classNames;
769 }
770
771 public boolean isInterface(String className) {
772 return m_translationIndex.isInterface(new ClassEntry(className));
773 }
774
775 public boolean containsObfClass(ClassEntry obfClassEntry) {
776 return m_obfClassEntries.contains(obfClassEntry);
777 }
778
779 public boolean containsObfField(FieldEntry obfFieldEntry) {
780 return m_access.containsKey(obfFieldEntry);
781 }
782
783 public boolean containsObfBehavior(BehaviorEntry obfBehaviorEntry) {
784 return m_access.containsKey(obfBehaviorEntry);
785 }
786
787 public boolean containsObfArgument(ArgumentEntry obfArgumentEntry) {
788 // check the behavior
789 if (!containsObfBehavior(obfArgumentEntry.getBehaviorEntry())) {
790 return false;
791 }
792
793 // check the argument
794 if (obfArgumentEntry.getIndex() >= obfArgumentEntry.getBehaviorEntry().getSignature().getArgumentTypes().size()) {
795 return false;
796 }
797
798 return true;
799 }
800
801 public boolean containsObfEntry(Entry obfEntry) {
802 if (obfEntry instanceof ClassEntry) {
803 return containsObfClass((ClassEntry)obfEntry);
804 } else if (obfEntry instanceof FieldEntry) {
805 return containsObfField((FieldEntry)obfEntry);
806 } else if (obfEntry instanceof BehaviorEntry) {
807 return containsObfBehavior((BehaviorEntry)obfEntry);
808 } else if (obfEntry instanceof ArgumentEntry) {
809 return containsObfArgument((ArgumentEntry)obfEntry);
810 } else {
811 throw new Error("Entry type not supported: " + obfEntry.getClass().getName());
812 }
813 }
814
815 public MethodEntry getBridgedMethod(MethodEntry bridgeMethodEntry) {
816 return m_bridgedMethods.get(bridgeMethodEntry);
817 }
818
819 public List<ClassEntry> getObfClassChain(ClassEntry obfClassEntry) {
820
821 // build class chain in inner-to-outer order
822 List<ClassEntry> obfClassChain = Lists.newArrayList(obfClassEntry);
823 ClassEntry checkClassEntry = obfClassEntry;
824 while (true) {
825 ClassEntry obfOuterClassEntry = getOuterClass(checkClassEntry);
826 if (obfOuterClassEntry != null) {
827 obfClassChain.add(obfOuterClassEntry);
828 checkClassEntry = obfOuterClassEntry;
829 } else {
830 break;
831 }
832 }
833
834 // switch to outer-to-inner order
835 Collections.reverse(obfClassChain);
836
837 return obfClassChain;
838 }
839}