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