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