summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/analysis/JarIndex.java
diff options
context:
space:
mode:
authorGravatar gegy10002018-05-19 17:02:46 +0200
committerGravatar gegy10002018-05-19 17:02:46 +0200
commit2b2249e873c4adfd2dd6e8f1f2489ccd9f6aa021 (patch)
tree14c8b1e806449ace1641a1dbafae162855f79670 /src/main/java/cuchaz/enigma/analysis/JarIndex.java
parentFix build (diff)
downloadenigma-fork-2b2249e873c4adfd2dd6e8f1f2489ccd9f6aa021.tar.gz
enigma-fork-2b2249e873c4adfd2dd6e8f1f2489ccd9f6aa021.tar.xz
enigma-fork-2b2249e873c4adfd2dd6e8f1f2489ccd9f6aa021.zip
Initial port to ASM
Diffstat (limited to 'src/main/java/cuchaz/enigma/analysis/JarIndex.java')
-rw-r--r--src/main/java/cuchaz/enigma/analysis/JarIndex.java588
1 files changed, 121 insertions, 467 deletions
diff --git a/src/main/java/cuchaz/enigma/analysis/JarIndex.java b/src/main/java/cuchaz/enigma/analysis/JarIndex.java
index d0d0f2c..972d4fe 100644
--- a/src/main/java/cuchaz/enigma/analysis/JarIndex.java
+++ b/src/main/java/cuchaz/enigma/analysis/JarIndex.java
@@ -12,113 +12,68 @@
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import com.google.common.collect.*; 14import com.google.common.collect.*;
15import cuchaz.enigma.bytecode.AccessFlags;
15import cuchaz.enigma.mapping.*; 16import cuchaz.enigma.mapping.*;
16import cuchaz.enigma.mapping.Translator; 17import org.objectweb.asm.Opcodes;
17import javassist.*;
18import javassist.bytecode.*;
19import javassist.expr.*;
20 18
21import java.lang.reflect.Modifier;
22import java.util.*; 19import java.util.*;
23import java.util.jar.JarFile;
24 20
25public class JarIndex { 21public class JarIndex {
26 22
23 private final ReferencedEntryPool entryPool;
24
27 private Set<ClassEntry> obfClassEntries; 25 private Set<ClassEntry> obfClassEntries;
28 private TranslationIndex translationIndex; 26 private TranslationIndex translationIndex;
29 private Map<Entry, Access> access; 27 private Map<Entry, Access> access;
30 private Multimap<ClassEntry, FieldEntry> fields; 28 private Multimap<ClassEntry, FieldDefEntry> fields;
31 private Multimap<ClassEntry, BehaviorEntry> behaviors; 29 private Multimap<ClassEntry, MethodDefEntry> methods;
32 private Multimap<String, MethodEntry> methodImplementations; 30 private Multimap<String, MethodDefEntry> methodImplementations;
33 private Multimap<BehaviorEntry, EntryReference<BehaviorEntry, BehaviorEntry>> behaviorReferences; 31 private Multimap<MethodEntry, EntryReference<MethodEntry, MethodDefEntry>> methodReferences;
34 private Multimap<FieldEntry, EntryReference<FieldEntry, BehaviorEntry>> fieldReferences; 32 private Multimap<FieldEntry, EntryReference<FieldEntry, MethodDefEntry>> fieldReferences;
35 private Multimap<ClassEntry, ClassEntry> innerClassesByOuter; 33 private Multimap<ClassEntry, ClassEntry> innerClassesByOuter;
36 private Map<ClassEntry, ClassEntry> outerClassesByInner; 34 private Map<ClassEntry, ClassEntry> outerClassesByInner;
37 private Map<ClassEntry, BehaviorEntry> anonymousClasses;
38 private Map<MethodEntry, MethodEntry> bridgedMethods; 35 private Map<MethodEntry, MethodEntry> bridgedMethods;
39 private Set<MethodEntry> syntheticMethods; 36 private Set<MethodEntry> syntheticMethods;
40 37
41 public JarIndex() { 38 public JarIndex(ReferencedEntryPool entryPool) {
39 this.entryPool = entryPool;
42 this.obfClassEntries = Sets.newHashSet(); 40 this.obfClassEntries = Sets.newHashSet();
43 this.translationIndex = new TranslationIndex(); 41 this.translationIndex = new TranslationIndex(entryPool);
44 this.access = Maps.newHashMap(); 42 this.access = Maps.newHashMap();
45 this.fields = HashMultimap.create(); 43 this.fields = HashMultimap.create();
46 this.behaviors = HashMultimap.create(); 44 this.methods = HashMultimap.create();
47 this.methodImplementations = HashMultimap.create(); 45 this.methodImplementations = HashMultimap.create();
48 this.behaviorReferences = HashMultimap.create(); 46 this.methodReferences = HashMultimap.create();
49 this.fieldReferences = HashMultimap.create(); 47 this.fieldReferences = HashMultimap.create();
50 this.innerClassesByOuter = HashMultimap.create(); 48 this.innerClassesByOuter = HashMultimap.create();
51 this.outerClassesByInner = Maps.newHashMap(); 49 this.outerClassesByInner = Maps.newHashMap();
52 this.anonymousClasses = Maps.newHashMap();
53 this.bridgedMethods = Maps.newHashMap(); 50 this.bridgedMethods = Maps.newHashMap();
54 this.syntheticMethods = Sets.newHashSet(); 51 this.syntheticMethods = Sets.newHashSet();
55 } 52 }
56 53
57 public void indexJar(JarFile jar, boolean buildInnerClasses) { 54 public void indexJar(ParsedJar jar, boolean buildInnerClasses) {
58 55
59 // step 1: read the class names 56 // step 1: read the class names
60 this.obfClassEntries.addAll(JarClassIterator.getClassEntries(jar)); 57 obfClassEntries.addAll(jar.getClassEntries());
61
62 // step 2: index field/method/constructor access
63 for (CtClass c : JarClassIterator.classes(jar)) {
64 for (CtField field : c.getDeclaredFields()) {
65 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
66 this.access.put(fieldEntry, Access.get(field));
67 this.fields.put(fieldEntry.getClassEntry(), fieldEntry);
68 }
69 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
70 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
71 this.access.put(behaviorEntry, Access.get(behavior));
72 this.behaviors.put(behaviorEntry.getClassEntry(), behaviorEntry);
73 }
74 }
75 58
76 // step 3: index extends, implements, fields, and methods 59 // step 2: index classes, fields, methods, interfaces
77 for (CtClass c : JarClassIterator.classes(jar)) { 60 jar.visit(node -> node.accept(new IndexClassVisitor(this, Opcodes.ASM5)));
78 this.translationIndex.indexClass(c);
79 String className = Descriptor.toJvmName(c.getName());
80 for (String interfaceName : c.getClassFile().getInterfaces()) {
81 className = Descriptor.toJvmName(className);
82 interfaceName = Descriptor.toJvmName(interfaceName);
83 if (className.equals(interfaceName)) {
84 throw new IllegalArgumentException("Class cannot be its own interface! " + className);
85 }
86 }
87 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
88 indexBehavior(behavior);
89 }
90 }
91 61
92 // step 4: index field, method, constructor references 62 // step 3: index field, method, constructor references
93 for (CtClass c : JarClassIterator.classes(jar)) { 63 jar.visit(node -> node.accept(new IndexReferenceVisitor(this, entryPool, Opcodes.ASM5)));
94 for (CtBehavior behavior : c.getDeclaredBehaviors()) { 64
95 indexBehaviorReferences(behavior); 65 // step 4: index bridged methods
66 for (MethodDefEntry methodEntry : methods.values()) {
67 // look for bridge and bridged methods
68 MethodEntry bridgedMethod = findBridgedMethod(methodEntry);
69 if (bridgedMethod != null) {
70 this.bridgedMethods.put(methodEntry, bridgedMethod);
96 } 71 }
97 } 72 }
98 73
99 if (buildInnerClasses) { 74 if (buildInnerClasses) {
100
101 // step 5: index inner classes and anonymous classes 75 // step 5: index inner classes and anonymous classes
102 for (CtClass c : JarClassIterator.classes(jar)) { 76 jar.visit(node -> node.accept(new IndexInnerClassVisitor(this, Opcodes.ASM5)));
103 ClassEntry innerClassEntry = EntryFactory.getClassEntry(c);
104 ClassEntry outerClassEntry = findOuterClass(c);
105 if (outerClassEntry != null) {
106 this.innerClassesByOuter.put(outerClassEntry, innerClassEntry);
107 boolean innerWasAdded = this.outerClassesByInner.put(innerClassEntry, outerClassEntry) == null;
108 assert (innerWasAdded);
109
110 BehaviorEntry enclosingBehavior = isAnonymousClass(c, outerClassEntry);
111 if (enclosingBehavior != null) {
112 this.anonymousClasses.put(innerClassEntry, enclosingBehavior);
113
114 // DEBUG
115 //System.out.println("ANONYMOUS: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName());
116 }/* else {
117 // DEBUG
118 //System.out.println("INNER: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName());
119 }*/
120 }
121 }
122 77
123 // step 6: update other indices with inner class info 78 // step 6: update other indices with inner class info
124 Map<String, String> renames = Maps.newHashMap(); 79 Map<String, String> renames = Maps.newHashMap();
@@ -133,385 +88,109 @@ public class JarIndex {
133 EntryRenamer.renameClassesInSet(renames, this.obfClassEntries); 88 EntryRenamer.renameClassesInSet(renames, this.obfClassEntries);
134 this.translationIndex.renameClasses(renames); 89 this.translationIndex.renameClasses(renames);
135 EntryRenamer.renameClassesInMultimap(renames, this.methodImplementations); 90 EntryRenamer.renameClassesInMultimap(renames, this.methodImplementations);
136 EntryRenamer.renameClassesInMultimap(renames, this.behaviorReferences); 91 EntryRenamer.renameClassesInMultimap(renames, this.methodReferences);
137 EntryRenamer.renameClassesInMultimap(renames, this.fieldReferences); 92 EntryRenamer.renameClassesInMultimap(renames, this.fieldReferences);
138 EntryRenamer.renameClassesInMap(renames, this.access); 93 EntryRenamer.renameClassesInMap(renames, this.access);
139 } 94 }
140 } 95 }
141 96
142 private void indexBehavior(CtBehavior behavior) { 97 protected ClassDefEntry indexClass(int access, String name, String superName, String[] interfaces) {
143 // get the behavior entry 98 for (String interfaceName : interfaces) {
144 final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); 99 if (name.equals(interfaceName)) {
145 if (behaviorEntry instanceof MethodEntry) { 100 throw new IllegalArgumentException("Class cannot be its own interface! " + name);
146 MethodEntry methodEntry = (MethodEntry) behaviorEntry;
147
148 // is synthetic
149 if ((behavior.getModifiers() & AccessFlag.SYNTHETIC) != 0) {
150 syntheticMethods.add(methodEntry);
151 }
152
153 // index implementation
154 this.methodImplementations.put(behaviorEntry.getClassName(), methodEntry);
155
156 // look for bridge and bridged methods
157 CtMethod bridgedMethod = getBridgedMethod((CtMethod) behavior);
158 if (bridgedMethod != null) {
159 this.bridgedMethods.put(methodEntry, EntryFactory.getMethodEntry(bridgedMethod));
160 } 101 }
161 } 102 }
162 // looks like we don't care about constructors here 103 return this.translationIndex.indexClass(access, name, superName, interfaces);
163 }
164
165 private void indexBehaviorReferences(CtBehavior behavior) {
166 // index method calls
167 final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
168 try {
169 behavior.instrument(new ExprEditor() {
170 @Override
171 public void edit(MethodCall call) {
172 MethodEntry calledMethodEntry = EntryFactory.getMethodEntry(call);
173 ClassEntry resolvedClassEntry = translationIndex.resolveEntryClass(calledMethodEntry);
174 if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledMethodEntry.getClassEntry())) {
175 calledMethodEntry = new MethodEntry(
176 resolvedClassEntry,
177 calledMethodEntry.getName(),
178 calledMethodEntry.getSignature()
179 );
180 }
181 EntryReference<BehaviorEntry, BehaviorEntry> reference = new EntryReference<>(
182 calledMethodEntry,
183 call.getMethodName(),
184 behaviorEntry
185 );
186 behaviorReferences.put(calledMethodEntry, reference);
187 }
188
189 @Override
190 public void edit(FieldAccess call) {
191 FieldEntry calledFieldEntry = EntryFactory.getFieldEntry(call);
192 ClassEntry resolvedClassEntry = translationIndex.resolveEntryClass(calledFieldEntry);
193 if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledFieldEntry.getClassEntry())) {
194 calledFieldEntry = new FieldEntry(calledFieldEntry, resolvedClassEntry);
195 }
196 EntryReference<FieldEntry, BehaviorEntry> reference = new EntryReference<>(
197 calledFieldEntry,
198 call.getFieldName(),
199 behaviorEntry
200 );
201 fieldReferences.put(calledFieldEntry, reference);
202 }
203
204 @Override
205 public void edit(ConstructorCall call) {
206 ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call);
207 EntryReference<BehaviorEntry, BehaviorEntry> reference = new EntryReference<>(
208 calledConstructorEntry,
209 call.getMethodName(),
210 behaviorEntry
211 );
212 behaviorReferences.put(calledConstructorEntry, reference);
213 }
214
215 @Override
216 public void edit(NewExpr call) {
217 ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call);
218 EntryReference<BehaviorEntry, BehaviorEntry> reference = new EntryReference<>(
219 calledConstructorEntry,
220 call.getClassName(),
221 behaviorEntry
222 );
223 behaviorReferences.put(calledConstructorEntry, reference);
224 }
225 });
226 } catch (CannotCompileException ex) {
227 throw new Error(ex);
228 }
229 } 104 }
230 105
231 private CtMethod getBridgedMethod(CtMethod method) { 106 protected void indexField(ClassDefEntry owner, int access, String name, String desc) {
232 107 FieldDefEntry fieldEntry = new FieldDefEntry(owner, name, new TypeDescriptor(desc), new AccessFlags(access));
233 // bridge methods just call another method, cast it to the return type, and return the result 108 this.translationIndex.indexField(fieldEntry);
234 // let's see if we can detect this scenario 109 this.access.put(fieldEntry, Access.get(access));
235 110 this.fields.put(fieldEntry.getOwnerClassEntry(), fieldEntry);
236 // skip non-synthetic methods
237 if ((method.getModifiers() & AccessFlag.SYNTHETIC) == 0) {
238 return null;
239 }
240
241 // get all the called methods
242 final List<MethodCall> methodCalls = Lists.newArrayList();
243 try {
244 method.instrument(new ExprEditor() {
245 @Override
246 public void edit(MethodCall call) {
247 methodCalls.add(call);
248 }
249 });
250 } catch (CannotCompileException ex) {
251 // this is stupid... we're not even compiling anything
252 throw new Error(ex);
253 }
254
255 // is there just one?
256 if (methodCalls.size() != 1) {
257 return null;
258 }
259 MethodCall call = methodCalls.get(0);
260
261 try {
262 // we have a bridge method!
263 return call.getMethod();
264 } catch (NotFoundException ex) {
265 // can't find the type? not a bridge method
266 return null;
267 }
268 } 111 }
269 112
270 private ClassEntry findOuterClass(CtClass c) { 113 protected void indexMethod(ClassDefEntry owner, int access, String name, String desc) {
271 114 MethodDefEntry methodEntry = new MethodDefEntry(owner, name, new MethodDescriptor(desc), new AccessFlags(access));
272 ClassEntry classEntry = EntryFactory.getClassEntry(c); 115 this.translationIndex.indexMethod(methodEntry);
116 this.access.put(methodEntry, Access.get(access));
117 this.methods.put(methodEntry.getOwnerClassEntry(), methodEntry);
273 118
274 // does this class already have an outer class? 119 if (new AccessFlags(access).isSynthetic()) {
275 if (classEntry.isInnerClass()) { 120 syntheticMethods.add(methodEntry);
276 return classEntry.getOuterClassEntry();
277 } 121 }
278 122
279 // inner classes: 123 // we don't care about constructors here
280 // have constructors that can (illegally) set synthetic fields 124 if (!methodEntry.isConstructor()) {
281 // the outer class is the only class that calls constructors 125 // index implementation
282 126 this.methodImplementations.put(methodEntry.getClassName(), methodEntry);
283 // use the synthetic fields to find the synthetic constructors
284 for (CtConstructor constructor : c.getDeclaredConstructors()) {
285 Set<String> syntheticFieldTypes = Sets.newHashSet();
286 if (!isIllegalConstructor(syntheticFieldTypes, constructor)) {
287 continue;
288 }
289
290 ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor);
291
292 // gather the classes from the illegally-set synthetic fields
293 Set<ClassEntry> illegallySetClasses = Sets.newHashSet();
294 for (String type : syntheticFieldTypes) {
295 if (type.startsWith("L")) {
296 ClassEntry outerClassEntry = new ClassEntry(type.substring(1, type.length() - 1));
297 if (isSaneOuterClass(outerClassEntry, classEntry)) {
298 illegallySetClasses.add(outerClassEntry);
299 }
300 }
301 }
302
303 // who calls this constructor?
304 Set<ClassEntry> callerClasses = Sets.newHashSet();
305 for (EntryReference<BehaviorEntry, BehaviorEntry> reference : getBehaviorReferences(constructorEntry)) {
306
307 // make sure it's not a call to super
308 if (reference.entry instanceof ConstructorEntry && reference.context instanceof ConstructorEntry) {
309
310 // is the entry a superclass of the context?
311 ClassEntry calledClassEntry = reference.entry.getClassEntry();
312 ClassEntry superclassEntry = this.translationIndex.getSuperclass(reference.context.getClassEntry());
313 if (superclassEntry != null && superclassEntry.equals(calledClassEntry)) {
314 // it's a super call, skip
315 continue;
316 }
317 }
318
319 if (isSaneOuterClass(reference.context.getClassEntry(), classEntry)) {
320 callerClasses.add(reference.context.getClassEntry());
321 }
322 }
323
324 // do we have an answer yet?
325 if (callerClasses.isEmpty()) {
326 if (illegallySetClasses.size() == 1) {
327 return illegallySetClasses.iterator().next();
328 } else {
329 System.out.println(String.format("WARNING: Unable to find outer class for %s. No caller and no illegally set field classes.", classEntry));
330 }
331 } else {
332 if (callerClasses.size() == 1) {
333 return callerClasses.iterator().next();
334 } else {
335 // multiple callers, do the illegally set classes narrow it down?
336 Set<ClassEntry> intersection = Sets.newHashSet(callerClasses);
337 intersection.retainAll(illegallySetClasses);
338 if (intersection.size() == 1) {
339 return intersection.iterator().next();
340 } else {
341 System.out.println(String.format("WARNING: Unable to choose outer class for %s among options: %s", classEntry, callerClasses));
342 }
343 }
344 }
345 } 127 }
346
347 return null;
348 } 128 }
349 129
350 private boolean isSaneOuterClass(ClassEntry outerClassEntry, ClassEntry innerClassEntry) { 130 protected void indexMethodCall(MethodDefEntry callerEntry, String owner, String name, String desc) {
351 131 MethodEntry referencedMethod = new MethodEntry(entryPool.getClass(owner), name, new MethodDescriptor(desc));
352 // clearly this would be silly 132 ClassEntry resolvedClassEntry = translationIndex.resolveEntryOwner(referencedMethod);
353 if (outerClassEntry.equals(innerClassEntry)) { 133 if (resolvedClassEntry != null && !resolvedClassEntry.equals(referencedMethod.getOwnerClassEntry())) {
354 return false; 134 referencedMethod = referencedMethod.updateOwnership(resolvedClassEntry);
355 } 135 }
356 136 methodReferences.put(referencedMethod, new EntryReference<>(referencedMethod, referencedMethod.getName(), callerEntry));
357 // is the outer class in the jar?
358 return this.obfClassEntries.contains(outerClassEntry);
359
360 } 137 }
361 138
362 @SuppressWarnings("unchecked") 139 protected void indexFieldAccess(MethodDefEntry callerEntry, String owner, String name, String desc) {
363 private boolean isIllegalConstructor(Set<String> syntheticFieldTypes, CtConstructor constructor) { 140 FieldEntry referencedField = new FieldEntry(entryPool.getClass(owner), name, new TypeDescriptor(desc));
364 141 ClassEntry resolvedClassEntry = translationIndex.resolveEntryOwner(referencedField);
365 // illegal constructors only set synthetic member fields, then call super() 142 if (resolvedClassEntry != null && !resolvedClassEntry.equals(referencedField.getOwnerClassEntry())) {
366 String className = constructor.getDeclaringClass().getName(); 143 referencedField = referencedField.updateOwnership(resolvedClassEntry);
367
368 // collect all the field accesses, constructor calls, and method calls
369 final List<FieldAccess> illegalFieldWrites = Lists.newArrayList();
370 final List<ConstructorCall> constructorCalls = Lists.newArrayList();
371 try {
372 constructor.instrument(new ExprEditor() {
373 @Override
374 public void edit(FieldAccess fieldAccess) {
375 if (fieldAccess.isWriter() && constructorCalls.isEmpty()) {
376 illegalFieldWrites.add(fieldAccess);
377 }
378 }
379
380 @Override
381 public void edit(ConstructorCall constructorCall) {
382 constructorCalls.add(constructorCall);
383 }
384 });
385 } catch (CannotCompileException ex) {
386 // we're not compiling anything... this is stupid
387 throw new Error(ex);
388 }
389
390 // are there any illegal field writes?
391 if (illegalFieldWrites.isEmpty()) {
392 return false;
393 }
394
395 // are all the writes to synthetic fields?
396 for (FieldAccess fieldWrite : illegalFieldWrites) {
397
398 // all illegal writes have to be to the local class
399 if (!fieldWrite.getClassName().equals(className)) {
400 System.err.println(String.format("WARNING: illegal write to non-member field %s.%s", fieldWrite.getClassName(), fieldWrite.getFieldName()));
401 return false;
402 }
403
404 // find the field
405 FieldInfo fieldInfo = null;
406 for (FieldInfo info : (List<FieldInfo>) constructor.getDeclaringClass().getClassFile().getFields()) {
407 if (info.getName().equals(fieldWrite.getFieldName()) && info.getDescriptor().equals(fieldWrite.getSignature())) {
408 fieldInfo = info;
409 break;
410 }
411 }
412 if (fieldInfo == null) {
413 // field is in a superclass or something, can't be a local synthetic member
414 return false;
415 }
416
417 // is this field synthetic?
418 boolean isSynthetic = (fieldInfo.getAccessFlags() & AccessFlag.SYNTHETIC) != 0;
419 if (isSynthetic) {
420 syntheticFieldTypes.add(fieldInfo.getDescriptor());
421 } else {
422 System.err.println(String.format("WARNING: illegal write to non synthetic field %s %s.%s", fieldInfo.getDescriptor(), className, fieldInfo.getName()));
423 return false;
424 }
425 } 144 }
426 145 fieldReferences.put(referencedField, new EntryReference<>(referencedField, referencedField.getName(), callerEntry));
427 // we passed all the tests!
428 return true;
429 } 146 }
430 147
431 private BehaviorEntry isAnonymousClass(CtClass c, ClassEntry outerClassEntry) { 148 public void indexInnerClass(ClassEntry innerEntry, ClassEntry outerEntry) {
432 149 this.innerClassesByOuter.put(outerEntry, innerEntry);
433 // is this class already marked anonymous? 150 boolean innerWasAdded = this.outerClassesByInner.put(innerEntry, outerEntry) == null;
434 EnclosingMethodAttribute enclosingMethodAttribute = (EnclosingMethodAttribute) c.getClassFile().getAttribute(EnclosingMethodAttribute.tag); 151 assert (innerWasAdded);
435 if (enclosingMethodAttribute != null) { 152 }
436 if (enclosingMethodAttribute.methodIndex() > 0) {
437 return EntryFactory.getBehaviorEntry(
438 Descriptor.toJvmName(enclosingMethodAttribute.className()),
439 enclosingMethodAttribute.methodName(),
440 enclosingMethodAttribute.methodDescriptor()
441 );
442 } else {
443 // an attribute but no method? assume not anonymous
444 return null;
445 }
446 }
447
448 // if there's an inner class attribute, but not an enclosing method attribute, then it's not anonymous
449 InnerClassesAttribute innerClassesAttribute = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag);
450 if (innerClassesAttribute != null) {
451 return null;
452 }
453 153
454 ClassEntry innerClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); 154 private MethodEntry findBridgedMethod(MethodDefEntry method) {
455 155
456 // anonymous classes: 156 // bridge methods just call another method, cast it to the return desc, and return the result
457 // can't be abstract 157 // let's see if we can detect this scenario
458 // have only one constructor
459 // it's called exactly once by the outer class
460 // the type the instance is assigned to can't be this type
461 158
462 // is abstract? 159 // skip non-synthetic methods
463 if (Modifier.isAbstract(c.getModifiers())) { 160 if (!method.getAccess().isSynthetic()) {
464 return null; 161 return null;
465 } 162 }
466 163
467 // is there exactly one constructor? 164 // get all the called methods
468 if (c.getDeclaredConstructors().length != 1) { 165 final Collection<EntryReference<MethodEntry, MethodDefEntry>> referencedMethods = methodReferences.get(method);
469 return null;
470 }
471 CtConstructor constructor = c.getDeclaredConstructors()[0];
472 166
473 // is this constructor called exactly once? 167 // is there just one?
474 ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor); 168 if (referencedMethods.size() != 1) {
475 Collection<EntryReference<BehaviorEntry, BehaviorEntry>> references = getBehaviorReferences(constructorEntry);
476 if (references.size() != 1) {
477 return null; 169 return null;
478 } 170 }
479 171
480 // does the caller use this type? 172 // we have a bridge method!
481 BehaviorEntry caller = references.iterator().next().context; 173 return referencedMethods.stream().findFirst().get().entry;
482 for (FieldEntry fieldEntry : getReferencedFields(caller)) {
483 if (fieldEntry.getType().hasClass() && fieldEntry.getType().getClassEntry().equals(innerClassEntry)) {
484 // caller references this type, so it can't be anonymous
485 return null;
486 }
487 }
488 for (BehaviorEntry behaviorEntry : getReferencedBehaviors(caller)) {
489 if (behaviorEntry.getSignature().hasClass(innerClassEntry)) {
490 return null;
491 }
492 }
493
494 return caller;
495 } 174 }
496 175
497 public Set<ClassEntry> getObfClassEntries() { 176 public Set<ClassEntry> getObfClassEntries() {
498 return this.obfClassEntries; 177 return this.obfClassEntries;
499 } 178 }
500 179
501 public Collection<FieldEntry> getObfFieldEntries() { 180 public Collection<FieldDefEntry> getObfFieldEntries() {
502 return this.fields.values(); 181 return this.fields.values();
503 } 182 }
504 183
505 public Collection<FieldEntry> getObfFieldEntries(ClassEntry classEntry) { 184 public Collection<FieldDefEntry> getObfFieldEntries(ClassEntry classEntry) {
506 return this.fields.get(classEntry); 185 return this.fields.get(classEntry);
507 } 186 }
508 187
509 public Collection<BehaviorEntry> getObfBehaviorEntries() { 188 public Collection<MethodDefEntry> getObfBehaviorEntries() {
510 return this.behaviors.values(); 189 return this.methods.values();
511 } 190 }
512 191
513 public Collection<BehaviorEntry> getObfBehaviorEntries(ClassEntry classEntry) { 192 public Collection<MethodDefEntry> getObfBehaviorEntries(ClassEntry classEntry) {
514 return this.behaviors.get(classEntry); 193 return this.methods.get(classEntry);
515 } 194 }
516 195
517 public TranslationIndex getTranslationIndex() { 196 public TranslationIndex getTranslationIndex() {
@@ -533,8 +212,8 @@ public class JarIndex {
533 } 212 }
534 } 213 }
535 ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode( 214 ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode(
536 deobfuscatingTranslator, 215 deobfuscatingTranslator,
537 ancestry.get(ancestry.size() - 1) 216 ancestry.get(ancestry.size() - 1)
538 ); 217 );
539 218
540 // expand all children recursively 219 // expand all children recursively
@@ -557,28 +236,20 @@ public class JarIndex {
557 public MethodInheritanceTreeNode getMethodInheritance(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) { 236 public MethodInheritanceTreeNode getMethodInheritance(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) {
558 237
559 // travel to the ancestor implementation 238 // travel to the ancestor implementation
560 ClassEntry baseImplementationClassEntry = obfMethodEntry.getClassEntry(); 239 ClassEntry baseImplementationClassEntry = obfMethodEntry.getOwnerClassEntry();
561 for (ClassEntry ancestorClassEntry : this.translationIndex.getAncestry(obfMethodEntry.getClassEntry())) { 240 for (ClassEntry ancestorClassEntry : this.translationIndex.getAncestry(obfMethodEntry.getOwnerClassEntry())) {
562 MethodEntry ancestorMethodEntry = new MethodEntry( 241 MethodEntry ancestorMethodEntry = entryPool.getMethod(ancestorClassEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString());
563 new ClassEntry(ancestorClassEntry), 242 if (ancestorMethodEntry != null && containsObfMethod(ancestorMethodEntry)) {
564 obfMethodEntry.getName(),
565 obfMethodEntry.getSignature()
566 );
567 if (containsObfBehavior(ancestorMethodEntry)) {
568 baseImplementationClassEntry = ancestorClassEntry; 243 baseImplementationClassEntry = ancestorClassEntry;
569 } 244 }
570 } 245 }
571 246
572 // make a root node at the base 247 // make a root node at the base
573 MethodEntry methodEntry = new MethodEntry( 248 MethodEntry methodEntry = entryPool.getMethod(baseImplementationClassEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString());
574 baseImplementationClassEntry,
575 obfMethodEntry.getName(),
576 obfMethodEntry.getSignature()
577 );
578 MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode( 249 MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode(
579 deobfuscatingTranslator, 250 deobfuscatingTranslator,
580 methodEntry, 251 methodEntry,
581 containsObfBehavior(methodEntry) 252 containsObfMethod(methodEntry)
582 ); 253 );
583 254
584 // expand the full tree 255 // expand the full tree
@@ -599,12 +270,8 @@ public class JarIndex {
599 for (ClassEntry interfaceEntry : getInterfaces(obfMethodEntry.getClassName())) { 270 for (ClassEntry interfaceEntry : getInterfaces(obfMethodEntry.getClassName())) {
600 271
601 // is this method defined in this interface? 272 // is this method defined in this interface?
602 MethodEntry methodInterface = new MethodEntry( 273 MethodEntry methodInterface = entryPool.getMethod(interfaceEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString());
603 interfaceEntry, 274 if (methodInterface != null && containsObfMethod(methodInterface)) {
604 obfMethodEntry.getName(),
605 obfMethodEntry.getSignature()
606 );
607 if (containsObfBehavior(methodInterface)) {
608 interfaceMethodEntries.add(methodInterface); 275 interfaceMethodEntries.add(methodInterface);
609 } 276 }
610 } 277 }
@@ -623,14 +290,14 @@ public class JarIndex {
623 290
624 public Set<MethodEntry> getRelatedMethodImplementations(MethodEntry obfMethodEntry) { 291 public Set<MethodEntry> getRelatedMethodImplementations(MethodEntry obfMethodEntry) {
625 Set<MethodEntry> methodEntries = Sets.newHashSet(); 292 Set<MethodEntry> methodEntries = Sets.newHashSet();
626 getRelatedMethodImplementations(methodEntries, getMethodInheritance(new Translator(), obfMethodEntry)); 293 getRelatedMethodImplementations(methodEntries, getMethodInheritance(new DirectionalTranslator(entryPool), obfMethodEntry));
627 return methodEntries; 294 return methodEntries;
628 } 295 }
629 296
630 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodInheritanceTreeNode node) { 297 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodInheritanceTreeNode node) {
631 MethodEntry methodEntry = node.getMethodEntry(); 298 MethodEntry methodEntry = node.getMethodEntry();
632 299
633 if (containsObfBehavior(methodEntry)) { 300 if (containsObfMethod(methodEntry)) {
634 // collect the entry 301 // collect the entry
635 methodEntries.add(methodEntry); 302 methodEntries.add(methodEntry);
636 } 303 }
@@ -643,7 +310,7 @@ public class JarIndex {
643 } 310 }
644 311
645 // look at interface methods too 312 // look at interface methods too
646 for (MethodImplementationsTreeNode implementationsNode : getMethodImplementations(new Translator(), methodEntry)) { 313 for (MethodImplementationsTreeNode implementationsNode : getMethodImplementations(new DirectionalTranslator(entryPool), methodEntry)) {
647 getRelatedMethodImplementations(methodEntries, implementationsNode); 314 getRelatedMethodImplementations(methodEntries, implementationsNode);
648 } 315 }
649 316
@@ -655,7 +322,7 @@ public class JarIndex {
655 322
656 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodImplementationsTreeNode node) { 323 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodImplementationsTreeNode node) {
657 MethodEntry methodEntry = node.getMethodEntry(); 324 MethodEntry methodEntry = node.getMethodEntry();
658 if (containsObfBehavior(methodEntry)) { 325 if (containsObfMethod(methodEntry)) {
659 // collect the entry 326 // collect the entry
660 methodEntries.add(methodEntry); 327 methodEntries.add(methodEntry);
661 } 328 }
@@ -673,30 +340,30 @@ public class JarIndex {
673 } 340 }
674 } 341 }
675 342
676 public Collection<EntryReference<FieldEntry, BehaviorEntry>> getFieldReferences(FieldEntry fieldEntry) { 343 public Collection<EntryReference<FieldEntry, MethodDefEntry>> getFieldReferences(FieldEntry fieldEntry) {
677 return this.fieldReferences.get(fieldEntry); 344 return this.fieldReferences.get(fieldEntry);
678 } 345 }
679 346
680 public Collection<FieldEntry> getReferencedFields(BehaviorEntry behaviorEntry) { 347 public Collection<FieldEntry> getReferencedFields(MethodDefEntry methodEntry) {
681 // linear search is fast enough for now 348 // linear search is fast enough for now
682 Set<FieldEntry> fieldEntries = Sets.newHashSet(); 349 Set<FieldEntry> fieldEntries = Sets.newHashSet();
683 for (EntryReference<FieldEntry, BehaviorEntry> reference : this.fieldReferences.values()) { 350 for (EntryReference<FieldEntry, MethodDefEntry> reference : this.fieldReferences.values()) {
684 if (reference.context == behaviorEntry) { 351 if (reference.context == methodEntry) {
685 fieldEntries.add(reference.entry); 352 fieldEntries.add(reference.entry);
686 } 353 }
687 } 354 }
688 return fieldEntries; 355 return fieldEntries;
689 } 356 }
690 357
691 public Collection<EntryReference<BehaviorEntry, BehaviorEntry>> getBehaviorReferences(BehaviorEntry behaviorEntry) { 358 public Collection<EntryReference<MethodEntry, MethodDefEntry>> getMethodReferences(MethodEntry methodEntry) {
692 return this.behaviorReferences.get(behaviorEntry); 359 return this.methodReferences.get(methodEntry);
693 } 360 }
694 361
695 public Collection<BehaviorEntry> getReferencedBehaviors(BehaviorEntry behaviorEntry) { 362 public Collection<MethodEntry> getReferencedMethods(MethodDefEntry methodEntry) {
696 // linear search is fast enough for now 363 // linear search is fast enough for now
697 Set<BehaviorEntry> behaviorEntries = Sets.newHashSet(); 364 Set<MethodEntry> behaviorEntries = Sets.newHashSet();
698 for (EntryReference<BehaviorEntry, BehaviorEntry> reference : this.behaviorReferences.values()) { 365 for (EntryReference<MethodEntry, MethodDefEntry> reference : this.methodReferences.values()) {
699 if (reference.context == behaviorEntry) { 366 if (reference.context == methodEntry) {
700 behaviorEntries.add(reference.entry); 367 behaviorEntries.add(reference.entry);
701 } 368 }
702 } 369 }
@@ -711,20 +378,12 @@ public class JarIndex {
711 return this.outerClassesByInner.get(obfInnerClassEntry); 378 return this.outerClassesByInner.get(obfInnerClassEntry);
712 } 379 }
713 380
714 public boolean isAnonymousClass(ClassEntry obfInnerClassEntry) {
715 return this.anonymousClasses.containsKey(obfInnerClassEntry);
716 }
717
718 public boolean isSyntheticMethod(MethodEntry methodEntry) { 381 public boolean isSyntheticMethod(MethodEntry methodEntry) {
719 return this.syntheticMethods.contains(methodEntry); 382 return this.syntheticMethods.contains(methodEntry);
720 } 383 }
721 384
722 public BehaviorEntry getAnonymousClassCaller(ClassEntry obfInnerClassName) {
723 return this.anonymousClasses.get(obfInnerClassName);
724 }
725
726 public Set<ClassEntry> getInterfaces(String className) { 385 public Set<ClassEntry> getInterfaces(String className) {
727 ClassEntry classEntry = new ClassEntry(className); 386 ClassEntry classEntry = entryPool.getClass(className);
728 Set<ClassEntry> interfaces = new HashSet<>(); 387 Set<ClassEntry> interfaces = new HashSet<>();
729 interfaces.addAll(this.translationIndex.getInterfaces(classEntry)); 388 interfaces.addAll(this.translationIndex.getInterfaces(classEntry));
730 for (ClassEntry ancestor : this.translationIndex.getAncestry(classEntry)) { 389 for (ClassEntry ancestor : this.translationIndex.getAncestry(classEntry)) {
@@ -754,7 +413,7 @@ public class JarIndex {
754 } 413 }
755 414
756 public boolean isInterface(String className) { 415 public boolean isInterface(String className) {
757 return this.translationIndex.isInterface(new ClassEntry(className)); 416 return this.translationIndex.isInterface(entryPool.getClass(className));
758 } 417 }
759 418
760 public boolean containsObfClass(ClassEntry obfClassEntry) { 419 public boolean containsObfClass(ClassEntry obfClassEntry) {
@@ -765,8 +424,8 @@ public class JarIndex {
765 return this.access.containsKey(obfFieldEntry); 424 return this.access.containsKey(obfFieldEntry);
766 } 425 }
767 426
768 public boolean containsObfBehavior(BehaviorEntry obfBehaviorEntry) { 427 public boolean containsObfMethod(MethodEntry obfMethodEntry) {
769 return this.access.containsKey(obfBehaviorEntry); 428 return this.access.containsKey(obfMethodEntry);
770 } 429 }
771 430
772 public boolean containsEntryWithSameName(Entry entry) { 431 public boolean containsEntryWithSameName(Entry entry) {
@@ -776,15 +435,13 @@ public class JarIndex {
776 return false; 435 return false;
777 } 436 }
778 437
779 public boolean containsObfArgument(ArgumentEntry obfArgumentEntry) { 438 public boolean containsObfVariable(LocalVariableEntry obfVariableEntry) {
780 // check the behavior 439 // check the behavior
781 if (!containsObfBehavior(obfArgumentEntry.getBehaviorEntry())) { 440 if (!containsObfMethod(obfVariableEntry.getOwnerEntry())) {
782 return false; 441 return false;
783 } 442 }
784 443
785 // check the argument 444 return true;
786 return obfArgumentEntry.getIndex() < obfArgumentEntry.getBehaviorEntry().getSignature().getArgumentTypes().size();
787
788 } 445 }
789 446
790 public boolean containsObfEntry(Entry obfEntry) { 447 public boolean containsObfEntry(Entry obfEntry) {
@@ -792,15 +449,12 @@ public class JarIndex {
792 return containsObfClass((ClassEntry) obfEntry); 449 return containsObfClass((ClassEntry) obfEntry);
793 } else if (obfEntry instanceof FieldEntry) { 450 } else if (obfEntry instanceof FieldEntry) {
794 return containsObfField((FieldEntry) obfEntry); 451 return containsObfField((FieldEntry) obfEntry);
795 } else if (obfEntry instanceof BehaviorEntry) { 452 } else if (obfEntry instanceof MethodEntry) {
796 return containsObfBehavior((BehaviorEntry) obfEntry); 453 return containsObfMethod((MethodEntry) obfEntry);
797 } else if (obfEntry instanceof ArgumentEntry) {
798 return containsObfArgument((ArgumentEntry) obfEntry);
799 } else if (obfEntry instanceof LocalVariableEntry) { 454 } else if (obfEntry instanceof LocalVariableEntry) {
800 // TODO: Implement it 455 return containsObfVariable((LocalVariableEntry) obfEntry);
801 return false;
802 } else { 456 } else {
803 throw new Error("Entry type not supported: " + obfEntry.getClass().getName()); 457 throw new Error("Entry desc not supported: " + obfEntry.getClass().getName());
804 } 458 }
805 } 459 }
806 460