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