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