summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/analysis/JarIndex.java
diff options
context:
space:
mode:
authorGravatar Modmuss502018-07-18 13:46:00 +0100
committerGravatar GitHub2018-07-18 13:46:00 +0100
commit1ebe691c12f68beea378b133ddc4bcbde7f3f795 (patch)
treefb051d9fde5644bd144a7e9d7bcecc70a256359c /src/main/java/cuchaz/enigma/analysis/JarIndex.java
parentRecursively rebuild method names (diff)
parentUpdate version number (diff)
downloadenigma-fork-1ebe691c12f68beea378b133ddc4bcbde7f3f795.tar.gz
enigma-fork-1ebe691c12f68beea378b133ddc4bcbde7f3f795.tar.xz
enigma-fork-1ebe691c12f68beea378b133ddc4bcbde7f3f795.zip
Merge pull request #62 from OpenModLoader/asm
ASM based class translator
Diffstat (limited to 'src/main/java/cuchaz/enigma/analysis/JarIndex.java')
-rw-r--r--src/main/java/cuchaz/enigma/analysis/JarIndex.java635
1 files changed, 159 insertions, 476 deletions
diff --git a/src/main/java/cuchaz/enigma/analysis/JarIndex.java b/src/main/java/cuchaz/enigma/analysis/JarIndex.java
index d0d0f2c..5917a32 100644
--- a/src/main/java/cuchaz/enigma/analysis/JarIndex.java
+++ b/src/main/java/cuchaz/enigma/analysis/JarIndex.java
@@ -12,113 +12,73 @@
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 cuchaz.enigma.mapping.entry.*;
17import javassist.*; 18import org.objectweb.asm.Opcodes;
18import javassist.bytecode.*;
19import javassist.expr.*;
20 19
21import java.lang.reflect.Modifier;
22import java.util.*; 20import java.util.*;
23import java.util.jar.JarFile;
24 21
25public class JarIndex { 22public class JarIndex {
26 23
24 private final ReferencedEntryPool entryPool;
25
27 private Set<ClassEntry> obfClassEntries; 26 private Set<ClassEntry> obfClassEntries;
28 private TranslationIndex translationIndex; 27 private TranslationIndex translationIndex;
29 private Map<Entry, Access> access; 28 private Map<Entry, Access> access;
30 private Multimap<ClassEntry, FieldEntry> fields; 29 private Multimap<ClassEntry, FieldDefEntry> fields;
31 private Multimap<ClassEntry, BehaviorEntry> behaviors; 30 private Multimap<ClassEntry, MethodDefEntry> methods;
32 private Multimap<String, MethodEntry> methodImplementations; 31 private Multimap<String, MethodDefEntry> methodImplementations;
33 private Multimap<BehaviorEntry, EntryReference<BehaviorEntry, BehaviorEntry>> behaviorReferences; 32 private Multimap<MethodEntry, EntryReference<MethodEntry, MethodDefEntry>> methodsReferencing;
34 private Multimap<FieldEntry, EntryReference<FieldEntry, BehaviorEntry>> fieldReferences; 33 private Multimap<MethodEntry, MethodEntry> methodReferences;
34 private Multimap<FieldEntry, EntryReference<FieldEntry, MethodDefEntry>> fieldReferences;
35 private Multimap<ClassEntry, ClassEntry> innerClassesByOuter; 35 private Multimap<ClassEntry, ClassEntry> innerClassesByOuter;
36 private Map<ClassEntry, ClassEntry> outerClassesByInner; 36 private Map<ClassEntry, ClassEntry> outerClassesByInner;
37 private Map<ClassEntry, BehaviorEntry> anonymousClasses;
38 private Map<MethodEntry, MethodEntry> bridgedMethods; 37 private Map<MethodEntry, MethodEntry> bridgedMethods;
39 private Set<MethodEntry> syntheticMethods; 38 private Set<MethodEntry> syntheticMethods;
40 39
41 public JarIndex() { 40 public JarIndex(ReferencedEntryPool entryPool) {
41 this.entryPool = entryPool;
42 this.obfClassEntries = Sets.newHashSet(); 42 this.obfClassEntries = Sets.newHashSet();
43 this.translationIndex = new TranslationIndex(); 43 this.translationIndex = new TranslationIndex(entryPool);
44 this.access = Maps.newHashMap(); 44 this.access = Maps.newHashMap();
45 this.fields = HashMultimap.create(); 45 this.fields = HashMultimap.create();
46 this.behaviors = HashMultimap.create(); 46 this.methods = HashMultimap.create();
47 this.methodImplementations = HashMultimap.create(); 47 this.methodImplementations = HashMultimap.create();
48 this.behaviorReferences = HashMultimap.create(); 48 this.methodsReferencing = HashMultimap.create();
49 this.methodReferences = HashMultimap.create();
49 this.fieldReferences = HashMultimap.create(); 50 this.fieldReferences = HashMultimap.create();
50 this.innerClassesByOuter = HashMultimap.create(); 51 this.innerClassesByOuter = HashMultimap.create();
51 this.outerClassesByInner = Maps.newHashMap(); 52 this.outerClassesByInner = Maps.newHashMap();
52 this.anonymousClasses = Maps.newHashMap();
53 this.bridgedMethods = Maps.newHashMap(); 53 this.bridgedMethods = Maps.newHashMap();
54 this.syntheticMethods = Sets.newHashSet(); 54 this.syntheticMethods = Sets.newHashSet();
55 } 55 }
56 56
57 public void indexJar(JarFile jar, boolean buildInnerClasses) { 57 public void indexJar(ParsedJar jar, boolean buildInnerClasses) {
58 58
59 // step 1: read the class names 59 // step 1: read the class names
60 this.obfClassEntries.addAll(JarClassIterator.getClassEntries(jar)); 60 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 61
76 // step 3: index extends, implements, fields, and methods 62 // step 2: index classes, fields, methods, interfaces
77 for (CtClass c : JarClassIterator.classes(jar)) { 63 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 64
92 // step 4: index field, method, constructor references 65 // step 3: index field, method, constructor references
93 for (CtClass c : JarClassIterator.classes(jar)) { 66 jar.visit(node -> node.accept(new IndexReferenceVisitor(this, Opcodes.ASM5)));
94 for (CtBehavior behavior : c.getDeclaredBehaviors()) { 67
95 indexBehaviorReferences(behavior); 68 // step 4: index access and bridged methods
69 for (MethodDefEntry methodEntry : methods.values()) {
70 // look for access and bridged methods
71 MethodEntry accessedMethod = findAccessMethod(methodEntry);
72 if (accessedMethod != null) {
73 if (isBridgedMethod(accessedMethod, methodEntry)) {
74 this.bridgedMethods.put(methodEntry, accessedMethod);
75 }
96 } 76 }
97 } 77 }
98 78
99 if (buildInnerClasses) { 79 if (buildInnerClasses) {
100
101 // step 5: index inner classes and anonymous classes 80 // step 5: index inner classes and anonymous classes
102 for (CtClass c : JarClassIterator.classes(jar)) { 81 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 82
123 // step 6: update other indices with inner class info 83 // step 6: update other indices with inner class info
124 Map<String, String> renames = Maps.newHashMap(); 84 Map<String, String> renames = Maps.newHashMap();
@@ -133,385 +93,138 @@ public class JarIndex {
133 EntryRenamer.renameClassesInSet(renames, this.obfClassEntries); 93 EntryRenamer.renameClassesInSet(renames, this.obfClassEntries);
134 this.translationIndex.renameClasses(renames); 94 this.translationIndex.renameClasses(renames);
135 EntryRenamer.renameClassesInMultimap(renames, this.methodImplementations); 95 EntryRenamer.renameClassesInMultimap(renames, this.methodImplementations);
136 EntryRenamer.renameClassesInMultimap(renames, this.behaviorReferences); 96 EntryRenamer.renameClassesInMultimap(renames, this.methodsReferencing);
97 EntryRenamer.renameClassesInMultimap(renames, this.methodReferences);
137 EntryRenamer.renameClassesInMultimap(renames, this.fieldReferences); 98 EntryRenamer.renameClassesInMultimap(renames, this.fieldReferences);
138 EntryRenamer.renameClassesInMap(renames, this.access); 99 EntryRenamer.renameClassesInMap(renames, this.access);
139 } 100 }
140 } 101 }
141 102
142 private void indexBehavior(CtBehavior behavior) { 103 protected ClassDefEntry indexClass(int access, String name, String signature, String superName, String[] interfaces) {
143 // get the behavior entry 104 for (String interfaceName : interfaces) {
144 final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); 105 if (name.equals(interfaceName)) {
145 if (behaviorEntry instanceof MethodEntry) { 106 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 } 107 }
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 }
161 }
162 // looks like we don't care about constructors here
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 } 108 }
109 return this.translationIndex.indexClass(access, name, signature, superName, interfaces);
229 } 110 }
230 111
231 private CtMethod getBridgedMethod(CtMethod method) { 112 protected void indexField(ClassDefEntry owner, int access, String name, String desc, String signature) {
232 113 FieldDefEntry fieldEntry = new FieldDefEntry(owner, name, new TypeDescriptor(desc), Signature.createTypedSignature(signature), new AccessFlags(access));
233 // bridge methods just call another method, cast it to the return type, and return the result 114 this.translationIndex.indexField(fieldEntry);
234 // let's see if we can detect this scenario 115 this.access.put(fieldEntry, Access.get(access));
235 116 this.fields.put(fieldEntry.getOwnerClassEntry(), fieldEntry);
236 // skip non-synthetic methods 117 }
237 if ((method.getModifiers() & AccessFlag.SYNTHETIC) == 0) {
238 return null;
239 }
240 118
241 // get all the called methods 119 protected void indexMethod(ClassDefEntry owner, int access, String name, String desc, String signature) {
242 final List<MethodCall> methodCalls = Lists.newArrayList(); 120 MethodDefEntry methodEntry = new MethodDefEntry(owner, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access));
243 try { 121 this.translationIndex.indexMethod(methodEntry);
244 method.instrument(new ExprEditor() { 122 this.access.put(methodEntry, Access.get(access));
245 @Override 123 this.methods.put(methodEntry.getOwnerClassEntry(), methodEntry);
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 124
255 // is there just one? 125 if (new AccessFlags(access).isSynthetic()) {
256 if (methodCalls.size() != 1) { 126 syntheticMethods.add(methodEntry);
257 return null;
258 } 127 }
259 MethodCall call = methodCalls.get(0);
260 128
261 try { 129 // we don't care about constructors here
262 // we have a bridge method! 130 if (!methodEntry.isConstructor()) {
263 return call.getMethod(); 131 // index implementation
264 } catch (NotFoundException ex) { 132 this.methodImplementations.put(methodEntry.getClassName(), methodEntry);
265 // can't find the type? not a bridge method
266 return null;
267 } 133 }
268 } 134 }
269 135
270 private ClassEntry findOuterClass(CtClass c) { 136 protected void indexMethodCall(MethodDefEntry callerEntry, String owner, String name, String desc) {
271 137 MethodEntry referencedMethod = new MethodEntry(entryPool.getClass(owner), name, new MethodDescriptor(desc));
272 ClassEntry classEntry = EntryFactory.getClassEntry(c); 138 ClassEntry resolvedClassEntry = translationIndex.resolveEntryOwner(referencedMethod);
273 139 if (resolvedClassEntry != null && !resolvedClassEntry.equals(referencedMethod.getOwnerClassEntry())) {
274 // does this class already have an outer class? 140 referencedMethod = referencedMethod.updateOwnership(resolvedClassEntry);
275 if (classEntry.isInnerClass()) {
276 return classEntry.getOuterClassEntry();
277 } 141 }
278 142 methodsReferencing.put(referencedMethod, new EntryReference<>(referencedMethod, referencedMethod.getName(), callerEntry));
279 // inner classes: 143 methodReferences.put(callerEntry, referencedMethod);
280 // have constructors that can (illegally) set synthetic fields
281 // the outer class is the only class that calls constructors
282
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 }
346
347 return null;
348 } 144 }
349 145
350 private boolean isSaneOuterClass(ClassEntry outerClassEntry, ClassEntry innerClassEntry) { 146 protected void indexFieldAccess(MethodDefEntry callerEntry, String owner, String name, String desc) {
351 147 FieldEntry referencedField = new FieldEntry(entryPool.getClass(owner), name, new TypeDescriptor(desc));
352 // clearly this would be silly 148 ClassEntry resolvedClassEntry = translationIndex.resolveEntryOwner(referencedField);
353 if (outerClassEntry.equals(innerClassEntry)) { 149 if (resolvedClassEntry != null && !resolvedClassEntry.equals(referencedField.getOwnerClassEntry())) {
354 return false; 150 referencedField = referencedField.updateOwnership(resolvedClassEntry);
355 } 151 }
356 152 fieldReferences.put(referencedField, new EntryReference<>(referencedField, referencedField.getName(), callerEntry));
357 // is the outer class in the jar?
358 return this.obfClassEntries.contains(outerClassEntry);
359
360 } 153 }
361 154
362 @SuppressWarnings("unchecked") 155 public void indexInnerClass(ClassEntry innerEntry, ClassEntry outerEntry) {
363 private boolean isIllegalConstructor(Set<String> syntheticFieldTypes, CtConstructor constructor) { 156 this.innerClassesByOuter.put(outerEntry, innerEntry);
157 this.outerClassesByInner.putIfAbsent(innerEntry, outerEntry);
158 }
364 159
365 // illegal constructors only set synthetic member fields, then call super() 160 private MethodEntry findAccessMethod(MethodDefEntry method) {
366 String className = constructor.getDeclaringClass().getName();
367 161
368 // collect all the field accesses, constructor calls, and method calls 162 // we want to find all compiler-added methods that directly call another with no processing
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 163
380 @Override 164 // skip non-synthetic methods
381 public void edit(ConstructorCall constructorCall) { 165 if (!method.getAccess().isSynthetic()) {
382 constructorCalls.add(constructorCall); 166 return null;
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 } 167 }
394 168
395 // are all the writes to synthetic fields? 169 // get all the methods that we call
396 for (FieldAccess fieldWrite : illegalFieldWrites) { 170 final Collection<MethodEntry> referencedMethods = methodReferences.get(method);
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 171
417 // is this field synthetic? 172 // is there just one?
418 boolean isSynthetic = (fieldInfo.getAccessFlags() & AccessFlag.SYNTHETIC) != 0; 173 if (referencedMethods.size() != 1) {
419 if (isSynthetic) { 174 return null;
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 } 175 }
426 176
427 // we passed all the tests! 177 return referencedMethods.stream().findFirst().orElse(null);
428 return true;
429 } 178 }
430 179
431 private BehaviorEntry isAnonymousClass(CtClass c, ClassEntry outerClassEntry) { 180 private boolean isBridgedMethod(MethodEntry called, MethodEntry access) {
432 181 // Bridged methods will always have the same name as the method they are calling
433 // is this class already marked anonymous? 182 // They will also have the same amount of parameters (though equal descriptors cannot be guaranteed)
434 EnclosingMethodAttribute enclosingMethodAttribute = (EnclosingMethodAttribute) c.getClassFile().getAttribute(EnclosingMethodAttribute.tag); 183 if (!called.getName().equals(access.getName()) || called.getDesc().getArgumentDescs().size() != access.getDesc().getArgumentDescs().size()) {
435 if (enclosingMethodAttribute != null) { 184 return false;
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 } 185 }
453 186
454 ClassEntry innerClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); 187 TypeDescriptor accessReturn = access.getDesc().getReturnDesc();
455 188 TypeDescriptor calledReturn = called.getDesc().getReturnDesc();
456 // anonymous classes: 189 if (calledReturn.isVoid() || calledReturn.isPrimitive() || accessReturn.isVoid() || accessReturn.isPrimitive()) {
457 // can't be abstract 190 return false;
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
462 // is abstract?
463 if (Modifier.isAbstract(c.getModifiers())) {
464 return null;
465 } 191 }
466 192
467 // is there exactly one constructor? 193 // Bridged methods will never have the same type as what they are calling
468 if (c.getDeclaredConstructors().length != 1) { 194 if (accessReturn.equals(calledReturn)) {
469 return null; 195 return false;
470 } 196 }
471 CtConstructor constructor = c.getDeclaredConstructors()[0];
472 197
473 // is this constructor called exactly once? 198 String accessType = accessReturn.toString();
474 ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor);
475 Collection<EntryReference<BehaviorEntry, BehaviorEntry>> references = getBehaviorReferences(constructorEntry);
476 if (references.size() != 1) {
477 return null;
478 }
479 199
480 // does the caller use this type? 200 // If we're casting down from generic type to type-erased Object we're a bridge method
481 BehaviorEntry caller = references.iterator().next().context; 201 if (accessType.equals("Ljava/lang/Object;")) {
482 for (FieldEntry fieldEntry : getReferencedFields(caller)) { 202 return true;
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 } 203 }
493 204
494 return caller; 205 // Now we need to detect cases where we are being casted down to a higher type bound
206 List<ClassEntry> calledAncestry = translationIndex.getAncestry(calledReturn.getTypeEntry());
207 return calledAncestry.contains(accessReturn.getTypeEntry());
495 } 208 }
496 209
497 public Set<ClassEntry> getObfClassEntries() { 210 public Set<ClassEntry> getObfClassEntries() {
498 return this.obfClassEntries; 211 return this.obfClassEntries;
499 } 212 }
500 213
501 public Collection<FieldEntry> getObfFieldEntries() { 214 public Collection<FieldDefEntry> getObfFieldEntries() {
502 return this.fields.values(); 215 return this.fields.values();
503 } 216 }
504 217
505 public Collection<FieldEntry> getObfFieldEntries(ClassEntry classEntry) { 218 public Collection<FieldDefEntry> getObfFieldEntries(ClassEntry classEntry) {
506 return this.fields.get(classEntry); 219 return this.fields.get(classEntry);
507 } 220 }
508 221
509 public Collection<BehaviorEntry> getObfBehaviorEntries() { 222 public Collection<MethodDefEntry> getObfBehaviorEntries() {
510 return this.behaviors.values(); 223 return this.methods.values();
511 } 224 }
512 225
513 public Collection<BehaviorEntry> getObfBehaviorEntries(ClassEntry classEntry) { 226 public Collection<MethodDefEntry> getObfBehaviorEntries(ClassEntry classEntry) {
514 return this.behaviors.get(classEntry); 227 return this.methods.get(classEntry);
515 } 228 }
516 229
517 public TranslationIndex getTranslationIndex() { 230 public TranslationIndex getTranslationIndex() {
@@ -533,8 +246,8 @@ public class JarIndex {
533 } 246 }
534 } 247 }
535 ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode( 248 ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode(
536 deobfuscatingTranslator, 249 deobfuscatingTranslator,
537 ancestry.get(ancestry.size() - 1) 250 ancestry.get(ancestry.size() - 1)
538 ); 251 );
539 252
540 // expand all children recursively 253 // expand all children recursively
@@ -557,28 +270,20 @@ public class JarIndex {
557 public MethodInheritanceTreeNode getMethodInheritance(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) { 270 public MethodInheritanceTreeNode getMethodInheritance(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) {
558 271
559 // travel to the ancestor implementation 272 // travel to the ancestor implementation
560 ClassEntry baseImplementationClassEntry = obfMethodEntry.getClassEntry(); 273 ClassEntry baseImplementationClassEntry = obfMethodEntry.getOwnerClassEntry();
561 for (ClassEntry ancestorClassEntry : this.translationIndex.getAncestry(obfMethodEntry.getClassEntry())) { 274 for (ClassEntry ancestorClassEntry : this.translationIndex.getAncestry(obfMethodEntry.getOwnerClassEntry())) {
562 MethodEntry ancestorMethodEntry = new MethodEntry( 275 MethodEntry ancestorMethodEntry = entryPool.getMethod(ancestorClassEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString());
563 new ClassEntry(ancestorClassEntry), 276 if (ancestorMethodEntry != null && containsObfMethod(ancestorMethodEntry)) {
564 obfMethodEntry.getName(),
565 obfMethodEntry.getSignature()
566 );
567 if (containsObfBehavior(ancestorMethodEntry)) {
568 baseImplementationClassEntry = ancestorClassEntry; 277 baseImplementationClassEntry = ancestorClassEntry;
569 } 278 }
570 } 279 }
571 280
572 // make a root node at the base 281 // make a root node at the base
573 MethodEntry methodEntry = new MethodEntry( 282 MethodEntry methodEntry = entryPool.getMethod(baseImplementationClassEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString());
574 baseImplementationClassEntry,
575 obfMethodEntry.getName(),
576 obfMethodEntry.getSignature()
577 );
578 MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode( 283 MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode(
579 deobfuscatingTranslator, 284 deobfuscatingTranslator,
580 methodEntry, 285 methodEntry,
581 containsObfBehavior(methodEntry) 286 containsObfMethod(methodEntry)
582 ); 287 );
583 288
584 // expand the full tree 289 // expand the full tree
@@ -599,12 +304,8 @@ public class JarIndex {
599 for (ClassEntry interfaceEntry : getInterfaces(obfMethodEntry.getClassName())) { 304 for (ClassEntry interfaceEntry : getInterfaces(obfMethodEntry.getClassName())) {
600 305
601 // is this method defined in this interface? 306 // is this method defined in this interface?
602 MethodEntry methodInterface = new MethodEntry( 307 MethodEntry methodInterface = entryPool.getMethod(interfaceEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString());
603 interfaceEntry, 308 if (methodInterface != null && containsObfMethod(methodInterface)) {
604 obfMethodEntry.getName(),
605 obfMethodEntry.getSignature()
606 );
607 if (containsObfBehavior(methodInterface)) {
608 interfaceMethodEntries.add(methodInterface); 309 interfaceMethodEntries.add(methodInterface);
609 } 310 }
610 } 311 }
@@ -623,27 +324,30 @@ public class JarIndex {
623 324
624 public Set<MethodEntry> getRelatedMethodImplementations(MethodEntry obfMethodEntry) { 325 public Set<MethodEntry> getRelatedMethodImplementations(MethodEntry obfMethodEntry) {
625 Set<MethodEntry> methodEntries = Sets.newHashSet(); 326 Set<MethodEntry> methodEntries = Sets.newHashSet();
626 getRelatedMethodImplementations(methodEntries, getMethodInheritance(new Translator(), obfMethodEntry)); 327 getRelatedMethodImplementations(methodEntries, getMethodInheritance(new DirectionalTranslator(entryPool), obfMethodEntry));
627 return methodEntries; 328 return methodEntries;
628 } 329 }
629 330
630 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodInheritanceTreeNode node) { 331 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodInheritanceTreeNode node) {
631 MethodEntry methodEntry = node.getMethodEntry(); 332 MethodEntry methodEntry = node.getMethodEntry();
333 if (methodEntries.contains(methodEntry)) {
334 return;
335 }
632 336
633 if (containsObfBehavior(methodEntry)) { 337 if (containsObfMethod(methodEntry)) {
634 // collect the entry 338 // collect the entry
635 methodEntries.add(methodEntry); 339 methodEntries.add(methodEntry);
636 } 340 }
637 341
638 // look at bridged methods! 342 // look at bridge methods!
639 MethodEntry bridgedEntry = getBridgedMethod(methodEntry); 343 MethodEntry bridgedMethod = getBridgedMethod(methodEntry);
640 while (bridgedEntry != null) { 344 while (bridgedMethod != null) {
641 methodEntries.addAll(getRelatedMethodImplementations(bridgedEntry)); 345 methodEntries.addAll(getRelatedMethodImplementations(bridgedMethod));
642 bridgedEntry = getBridgedMethod(bridgedEntry); 346 bridgedMethod = getBridgedMethod(bridgedMethod);
643 } 347 }
644 348
645 // look at interface methods too 349 // look at interface methods too
646 for (MethodImplementationsTreeNode implementationsNode : getMethodImplementations(new Translator(), methodEntry)) { 350 for (MethodImplementationsTreeNode implementationsNode : getMethodImplementations(new DirectionalTranslator(entryPool), methodEntry)) {
647 getRelatedMethodImplementations(methodEntries, implementationsNode); 351 getRelatedMethodImplementations(methodEntries, implementationsNode);
648 } 352 }
649 353
@@ -655,16 +359,16 @@ public class JarIndex {
655 359
656 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodImplementationsTreeNode node) { 360 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodImplementationsTreeNode node) {
657 MethodEntry methodEntry = node.getMethodEntry(); 361 MethodEntry methodEntry = node.getMethodEntry();
658 if (containsObfBehavior(methodEntry)) { 362 if (containsObfMethod(methodEntry)) {
659 // collect the entry 363 // collect the entry
660 methodEntries.add(methodEntry); 364 methodEntries.add(methodEntry);
661 } 365 }
662 366
663 // look at bridged methods! 367 // look at bridge methods!
664 MethodEntry bridgedEntry = getBridgedMethod(methodEntry); 368 MethodEntry bridgedMethod = getBridgedMethod(methodEntry);
665 while (bridgedEntry != null) { 369 while (bridgedMethod != null) {
666 methodEntries.addAll(getRelatedMethodImplementations(bridgedEntry)); 370 methodEntries.addAll(getRelatedMethodImplementations(bridgedMethod));
667 bridgedEntry = getBridgedMethod(bridgedEntry); 371 bridgedMethod = getBridgedMethod(bridgedMethod);
668 } 372 }
669 373
670 // recurse 374 // recurse
@@ -673,34 +377,27 @@ public class JarIndex {
673 } 377 }
674 } 378 }
675 379
676 public Collection<EntryReference<FieldEntry, BehaviorEntry>> getFieldReferences(FieldEntry fieldEntry) { 380 public Collection<EntryReference<FieldEntry, MethodDefEntry>> getFieldReferences(FieldEntry fieldEntry) {
677 return this.fieldReferences.get(fieldEntry); 381 return this.fieldReferences.get(fieldEntry);
678 } 382 }
679 383
680 public Collection<FieldEntry> getReferencedFields(BehaviorEntry behaviorEntry) { 384 public Collection<FieldEntry> getReferencedFields(MethodDefEntry methodEntry) {
681 // linear search is fast enough for now 385 // linear search is fast enough for now
682 Set<FieldEntry> fieldEntries = Sets.newHashSet(); 386 Set<FieldEntry> fieldEntries = Sets.newHashSet();
683 for (EntryReference<FieldEntry, BehaviorEntry> reference : this.fieldReferences.values()) { 387 for (EntryReference<FieldEntry, MethodDefEntry> reference : this.fieldReferences.values()) {
684 if (reference.context == behaviorEntry) { 388 if (reference.context == methodEntry) {
685 fieldEntries.add(reference.entry); 389 fieldEntries.add(reference.entry);
686 } 390 }
687 } 391 }
688 return fieldEntries; 392 return fieldEntries;
689 } 393 }
690 394
691 public Collection<EntryReference<BehaviorEntry, BehaviorEntry>> getBehaviorReferences(BehaviorEntry behaviorEntry) { 395 public Collection<EntryReference<MethodEntry, MethodDefEntry>> getMethodsReferencing(MethodEntry methodEntry) {
692 return this.behaviorReferences.get(behaviorEntry); 396 return this.methodsReferencing.get(methodEntry);
693 } 397 }
694 398
695 public Collection<BehaviorEntry> getReferencedBehaviors(BehaviorEntry behaviorEntry) { 399 public Collection<MethodEntry> getReferencedMethods(MethodDefEntry methodEntry) {
696 // linear search is fast enough for now 400 return this.methodReferences.get(methodEntry);
697 Set<BehaviorEntry> behaviorEntries = Sets.newHashSet();
698 for (EntryReference<BehaviorEntry, BehaviorEntry> reference : this.behaviorReferences.values()) {
699 if (reference.context == behaviorEntry) {
700 behaviorEntries.add(reference.entry);
701 }
702 }
703 return behaviorEntries;
704 } 401 }
705 402
706 public Collection<ClassEntry> getInnerClasses(ClassEntry obfOuterClassEntry) { 403 public Collection<ClassEntry> getInnerClasses(ClassEntry obfOuterClassEntry) {
@@ -711,22 +408,13 @@ public class JarIndex {
711 return this.outerClassesByInner.get(obfInnerClassEntry); 408 return this.outerClassesByInner.get(obfInnerClassEntry);
712 } 409 }
713 410
714 public boolean isAnonymousClass(ClassEntry obfInnerClassEntry) {
715 return this.anonymousClasses.containsKey(obfInnerClassEntry);
716 }
717
718 public boolean isSyntheticMethod(MethodEntry methodEntry) { 411 public boolean isSyntheticMethod(MethodEntry methodEntry) {
719 return this.syntheticMethods.contains(methodEntry); 412 return this.syntheticMethods.contains(methodEntry);
720 } 413 }
721 414
722 public BehaviorEntry getAnonymousClassCaller(ClassEntry obfInnerClassName) {
723 return this.anonymousClasses.get(obfInnerClassName);
724 }
725
726 public Set<ClassEntry> getInterfaces(String className) { 415 public Set<ClassEntry> getInterfaces(String className) {
727 ClassEntry classEntry = new ClassEntry(className); 416 ClassEntry classEntry = entryPool.getClass(className);
728 Set<ClassEntry> interfaces = new HashSet<>(); 417 Set<ClassEntry> interfaces = new HashSet<>(this.translationIndex.getInterfaces(classEntry));
729 interfaces.addAll(this.translationIndex.getInterfaces(classEntry));
730 for (ClassEntry ancestor : this.translationIndex.getAncestry(classEntry)) { 418 for (ClassEntry ancestor : this.translationIndex.getAncestry(classEntry)) {
731 interfaces.addAll(this.translationIndex.getInterfaces(ancestor)); 419 interfaces.addAll(this.translationIndex.getInterfaces(ancestor));
732 } 420 }
@@ -754,7 +442,7 @@ public class JarIndex {
754 } 442 }
755 443
756 public boolean isInterface(String className) { 444 public boolean isInterface(String className) {
757 return this.translationIndex.isInterface(new ClassEntry(className)); 445 return this.translationIndex.isInterface(entryPool.getClass(className));
758 } 446 }
759 447
760 public boolean containsObfClass(ClassEntry obfClassEntry) { 448 public boolean containsObfClass(ClassEntry obfClassEntry) {
@@ -765,8 +453,8 @@ public class JarIndex {
765 return this.access.containsKey(obfFieldEntry); 453 return this.access.containsKey(obfFieldEntry);
766 } 454 }
767 455
768 public boolean containsObfBehavior(BehaviorEntry obfBehaviorEntry) { 456 public boolean containsObfMethod(MethodEntry obfMethodEntry) {
769 return this.access.containsKey(obfBehaviorEntry); 457 return this.access.containsKey(obfMethodEntry);
770 } 458 }
771 459
772 public boolean containsEntryWithSameName(Entry entry) { 460 public boolean containsEntryWithSameName(Entry entry) {
@@ -776,15 +464,13 @@ public class JarIndex {
776 return false; 464 return false;
777 } 465 }
778 466
779 public boolean containsObfArgument(ArgumentEntry obfArgumentEntry) { 467 public boolean containsObfVariable(LocalVariableEntry obfVariableEntry) {
780 // check the behavior 468 // check the behavior
781 if (!containsObfBehavior(obfArgumentEntry.getBehaviorEntry())) { 469 if (!containsObfMethod(obfVariableEntry.getOwnerEntry())) {
782 return false; 470 return false;
783 } 471 }
784 472
785 // check the argument 473 return true;
786 return obfArgumentEntry.getIndex() < obfArgumentEntry.getBehaviorEntry().getSignature().getArgumentTypes().size();
787
788 } 474 }
789 475
790 public boolean containsObfEntry(Entry obfEntry) { 476 public boolean containsObfEntry(Entry obfEntry) {
@@ -792,15 +478,12 @@ public class JarIndex {
792 return containsObfClass((ClassEntry) obfEntry); 478 return containsObfClass((ClassEntry) obfEntry);
793 } else if (obfEntry instanceof FieldEntry) { 479 } else if (obfEntry instanceof FieldEntry) {
794 return containsObfField((FieldEntry) obfEntry); 480 return containsObfField((FieldEntry) obfEntry);
795 } else if (obfEntry instanceof BehaviorEntry) { 481 } else if (obfEntry instanceof MethodEntry) {
796 return containsObfBehavior((BehaviorEntry) obfEntry); 482 return containsObfMethod((MethodEntry) obfEntry);
797 } else if (obfEntry instanceof ArgumentEntry) {
798 return containsObfArgument((ArgumentEntry) obfEntry);
799 } else if (obfEntry instanceof LocalVariableEntry) { 483 } else if (obfEntry instanceof LocalVariableEntry) {
800 // TODO: Implement it 484 return containsObfVariable((LocalVariableEntry) obfEntry);
801 return false;
802 } else { 485 } else {
803 throw new Error("Entry type not supported: " + obfEntry.getClass().getName()); 486 throw new Error("Entry desc not supported: " + obfEntry.getClass().getName());
804 } 487 }
805 } 488 }
806 489