summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/convert/MappingsConverter.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/cuchaz/enigma/convert/MappingsConverter.java')
-rw-r--r--src/main/java/cuchaz/enigma/convert/MappingsConverter.java711
1 files changed, 0 insertions, 711 deletions
diff --git a/src/main/java/cuchaz/enigma/convert/MappingsConverter.java b/src/main/java/cuchaz/enigma/convert/MappingsConverter.java
deleted file mode 100644
index fa3e936..0000000
--- a/src/main/java/cuchaz/enigma/convert/MappingsConverter.java
+++ /dev/null
@@ -1,711 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.convert;
13
14import com.google.common.collect.*;
15import cuchaz.enigma.Deobfuscator;
16import cuchaz.enigma.TranslatingTypeLoader;
17import cuchaz.enigma.analysis.JarIndex;
18import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
19import cuchaz.enigma.mapping.*;
20import cuchaz.enigma.throwables.MappingConflict;
21import javassist.CtClass;
22import javassist.CtMethod;
23import javassist.NotFoundException;
24import javassist.bytecode.BadBytecode;
25import javassist.bytecode.CodeAttribute;
26import javassist.bytecode.CodeIterator;
27
28import java.util.*;
29import java.util.jar.JarFile;
30
31public class MappingsConverter {
32
33 public static ClassMatches computeClassMatches(JarFile sourceJar, JarFile destJar, Mappings mappings) {
34
35 // index jars
36 System.out.println("Indexing source jar...");
37 JarIndex sourceIndex = new JarIndex();
38 sourceIndex.indexJar(sourceJar, false);
39 System.out.println("Indexing dest jar...");
40 JarIndex destIndex = new JarIndex();
41 destIndex.indexJar(destJar, false);
42
43 // compute the matching
44 ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex, null);
45 return new ClassMatches(matching.matches());
46 }
47
48 public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex, BiMap<ClassEntry, ClassEntry> knownMatches) {
49
50 System.out.println("Iteratively matching classes");
51
52 ClassMatching lastMatching = null;
53 int round = 0;
54 SidedClassNamer sourceNamer = null;
55 SidedClassNamer destNamer = null;
56 for (boolean useReferences : Arrays.asList(false, true)) {
57
58 int numUniqueMatchesLastTime = 0;
59 if (lastMatching != null) {
60 numUniqueMatchesLastTime = lastMatching.uniqueMatches().size();
61 }
62
63 while (true) {
64
65 System.out.println("Round " + (++round) + "...");
66
67 // init the matching with identity settings
68 ClassMatching matching = new ClassMatching(
69 new ClassIdentifier(sourceJar, sourceIndex, sourceNamer, useReferences),
70 new ClassIdentifier(destJar, destIndex, destNamer, useReferences)
71 );
72
73 if (knownMatches != null) {
74 matching.addKnownMatches(knownMatches);
75 }
76
77 if (lastMatching == null) {
78 // search all classes
79 matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries());
80 } else {
81 // we already know about these matches from last time
82 matching.addKnownMatches(lastMatching.uniqueMatches());
83
84 // search unmatched and ambiguously-matched classes
85 matching.match(lastMatching.unmatchedSourceClasses(), lastMatching.unmatchedDestClasses());
86 for (ClassMatch match : lastMatching.ambiguousMatches()) {
87 matching.match(match.sourceClasses, match.destClasses);
88 }
89 }
90 System.out.println(matching);
91 BiMap<ClassEntry, ClassEntry> uniqueMatches = matching.uniqueMatches();
92
93 // did we match anything new this time?
94 if (uniqueMatches.size() > numUniqueMatchesLastTime) {
95 numUniqueMatchesLastTime = uniqueMatches.size();
96 lastMatching = matching;
97 } else {
98 break;
99 }
100
101 // update the namers
102 ClassNamer namer = new ClassNamer(uniqueMatches);
103 sourceNamer = namer.getSourceNamer();
104 destNamer = namer.getDestNamer();
105 }
106 }
107
108 return lastMatching;
109 }
110
111 public static Mappings newMappings(ClassMatches matches, Mappings oldMappings, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator)
112 throws MappingConflict {
113 // sort the unique matches by size of inner class chain
114 Multimap<Integer, java.util.Map.Entry<ClassEntry, ClassEntry>> matchesByDestChainSize = HashMultimap.create();
115 for (java.util.Map.Entry<ClassEntry, ClassEntry> match : matches.getUniqueMatches().entrySet()) {
116 int chainSize = destDeobfuscator.getJarIndex().getObfClassChain(match.getValue()).size();
117 matchesByDestChainSize.put(chainSize, match);
118 }
119
120 // build the mappings (in order of small-to-large inner chains)
121 Mappings newMappings = new Mappings();
122 List<Integer> chainSizes = Lists.newArrayList(matchesByDestChainSize.keySet());
123 Collections.sort(chainSizes);
124 for (int chainSize : chainSizes) {
125 for (java.util.Map.Entry<ClassEntry, ClassEntry> match : matchesByDestChainSize.get(chainSize)) {
126 // get class info
127 ClassEntry obfSourceClassEntry = match.getKey();
128 ClassEntry obfDestClassEntry = match.getValue();
129 List<ClassEntry> destClassChain = destDeobfuscator.getJarIndex().getObfClassChain(obfDestClassEntry);
130
131 ClassMapping sourceMapping;
132 if (obfSourceClassEntry.isInnerClass()) {
133 List<ClassMapping> srcClassChain = sourceDeobfuscator.getMappings().getClassMappingChain(obfSourceClassEntry);
134 sourceMapping = srcClassChain.get(srcClassChain.size() - 1);
135 } else {
136 sourceMapping = sourceDeobfuscator.getMappings().getClassByObf(obfSourceClassEntry);
137 }
138
139 if (sourceMapping == null) {
140 // if this class was never deobfuscated, don't try to match it
141 continue;
142 }
143
144 // find out where to make the dest class mapping
145 if (destClassChain.size() == 1) {
146 // not an inner class, add directly to mappings
147 newMappings.addClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, false));
148 } else {
149 // inner class, find the outer class mapping
150 ClassMapping destMapping = null;
151 for (int i = 0; i < destClassChain.size() - 1; i++) {
152 ClassEntry destChainClassEntry = destClassChain.get(i);
153 if (destMapping == null) {
154 destMapping = newMappings.getClassByObf(destChainClassEntry);
155 if (destMapping == null) {
156 destMapping = new ClassMapping(destChainClassEntry.getName());
157 newMappings.addClassMapping(destMapping);
158 }
159 } else {
160 destMapping = destMapping.getInnerClassByObfSimple(destChainClassEntry.getInnermostClassName());
161 if (destMapping == null) {
162 destMapping = new ClassMapping(destChainClassEntry.getName());
163 destMapping.addInnerClassMapping(destMapping);
164 }
165 }
166 }
167 destMapping.addInnerClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, true));
168 }
169 }
170 }
171 return newMappings;
172 }
173
174 private static ClassMapping migrateClassMapping(ClassEntry newObfClass, ClassMapping oldClassMapping, final ClassMatches matches, boolean useSimpleName) {
175
176 ClassNameReplacer replacer = className ->
177 {
178 ClassEntry newClassEntry = matches.getUniqueMatches().get(new ClassEntry(className));
179 if (newClassEntry != null) {
180 return newClassEntry.getName();
181 }
182 return null;
183 };
184
185 ClassMapping newClassMapping;
186 String deobfName = oldClassMapping.getDeobfName();
187 if (deobfName != null) {
188 if (useSimpleName) {
189 deobfName = new ClassEntry(deobfName).getSimpleName();
190 }
191 newClassMapping = new ClassMapping(newObfClass.getName(), deobfName);
192 } else {
193 newClassMapping = new ClassMapping(newObfClass.getName());
194 }
195
196 // migrate fields
197 for (FieldMapping oldFieldMapping : oldClassMapping.fields()) {
198 if (canMigrate(oldFieldMapping.getObfType(), matches)) {
199 newClassMapping.addFieldMapping(new FieldMapping(oldFieldMapping, replacer));
200 } else {
201 System.out.println(String.format("Can't map field, dropping: %s.%s %s",
202 oldClassMapping.getDeobfName(),
203 oldFieldMapping.getDeobfName(),
204 oldFieldMapping.getObfType()
205 ));
206 }
207 }
208
209 // migrate methods
210 for (MethodMapping oldMethodMapping : oldClassMapping.methods()) {
211 if (canMigrate(oldMethodMapping.getObfSignature(), matches)) {
212 newClassMapping.addMethodMapping(new MethodMapping(oldMethodMapping, replacer));
213 } else {
214 System.out.println(String.format("Can't map method, dropping: %s.%s %s",
215 oldClassMapping.getDeobfName(),
216 oldMethodMapping.getDeobfName(),
217 oldMethodMapping.getObfSignature()
218 ));
219 }
220 }
221
222 return newClassMapping;
223 }
224
225 private static boolean canMigrate(Signature oldObfSignature, ClassMatches classMatches) {
226 for (Type oldObfType : oldObfSignature.types()) {
227 if (!canMigrate(oldObfType, classMatches)) {
228 return false;
229 }
230 }
231 return true;
232 }
233
234 private static boolean canMigrate(Type oldObfType, ClassMatches classMatches) {
235
236 // non classes can be migrated
237 if (!oldObfType.hasClass()) {
238 return true;
239 }
240
241 // non obfuscated classes can be migrated
242 ClassEntry classEntry = oldObfType.getClassEntry();
243 if (classEntry.getPackageName() != null) {
244 return true;
245 }
246
247 // obfuscated classes with mappings can be migrated
248 return classMatches.getUniqueMatches().containsKey(classEntry);
249 }
250
251 public static void convertMappings(Mappings mappings, BiMap<ClassEntry, ClassEntry> changes) {
252
253 // sort the changes so classes are renamed in the correct order
254 // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b
255 LinkedHashMap<ClassEntry, ClassEntry> sortedChanges = Maps.newLinkedHashMap();
256 int numChangesLeft = changes.size();
257 while (!changes.isEmpty()) {
258 Iterator<Map.Entry<ClassEntry, ClassEntry>> iter = changes.entrySet().iterator();
259 while (iter.hasNext()) {
260 Map.Entry<ClassEntry, ClassEntry> change = iter.next();
261 if (changes.containsKey(change.getValue())) {
262 sortedChanges.put(change.getKey(), change.getValue());
263 iter.remove();
264 }
265 }
266
267 // did we remove any changes?
268 if (numChangesLeft - changes.size() > 0) {
269 // keep going
270 numChangesLeft = changes.size();
271 } else {
272 // can't sort anymore. There must be a loop
273 break;
274 }
275 }
276 if (!changes.isEmpty()) {
277 throw new Error("Unable to sort class changes! There must be a cycle.");
278 }
279
280 // convert the mappings in the correct class order
281 for (Map.Entry<ClassEntry, ClassEntry> entry : sortedChanges.entrySet()) {
282 mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName());
283 }
284 }
285
286 public static Doer<FieldEntry> getFieldDoer() {
287 return new Doer<FieldEntry>() {
288
289 @Override
290 public Collection<FieldEntry> getDroppedEntries(MappingsChecker checker) {
291 return checker.getDroppedFieldMappings().keySet();
292 }
293
294 @Override
295 public Collection<FieldEntry> getObfEntries(JarIndex jarIndex) {
296 return jarIndex.getObfFieldEntries();
297 }
298
299 @Override
300 public Collection<? extends MemberMapping<FieldEntry>> getMappings(ClassMapping destClassMapping) {
301 return (Collection<? extends MemberMapping<FieldEntry>>) destClassMapping.fields();
302 }
303
304 @Override
305 public Set<FieldEntry> filterEntries(Collection<FieldEntry> obfDestFields, FieldEntry obfSourceField, ClassMatches classMatches) {
306 Set<FieldEntry> out = Sets.newHashSet();
307 for (FieldEntry obfDestField : obfDestFields) {
308 Type translatedDestType = translate(obfDestField.getType(), classMatches.getUniqueMatches().inverse());
309 if (translatedDestType.equals(obfSourceField.getType())) {
310 out.add(obfDestField);
311 }
312 }
313 return out;
314 }
315
316 @Override
317 public void setUpdateObfMember(ClassMapping classMapping, MemberMapping<FieldEntry> memberMapping, FieldEntry newField) {
318 FieldMapping fieldMapping = (FieldMapping) memberMapping;
319 classMapping.setFieldObfNameAndType(fieldMapping.getObfName(), fieldMapping.getObfType(), newField.getName(), newField.getType());
320 }
321
322 @Override
323 public boolean hasObfMember(ClassMapping classMapping, FieldEntry obfField) {
324 return classMapping.getFieldByObf(obfField.getName(), obfField.getType()) != null;
325 }
326
327 @Override
328 public void removeMemberByObf(ClassMapping classMapping, FieldEntry obfField) {
329 classMapping.removeFieldMapping(classMapping.getFieldByObf(obfField.getName(), obfField.getType()));
330 }
331 };
332 }
333
334 public static Doer<BehaviorEntry> getMethodDoer() {
335 return new Doer<BehaviorEntry>() {
336
337 @Override
338 public Collection<BehaviorEntry> getDroppedEntries(MappingsChecker checker) {
339 return checker.getDroppedMethodMappings().keySet();
340 }
341
342 @Override
343 public Collection<BehaviorEntry> getObfEntries(JarIndex jarIndex) {
344 return jarIndex.getObfBehaviorEntries();
345 }
346
347 @Override
348 public Collection<? extends MemberMapping<BehaviorEntry>> getMappings(ClassMapping destClassMapping) {
349 return (Collection<? extends MemberMapping<BehaviorEntry>>) destClassMapping.methods();
350 }
351
352 @Override
353 public Set<BehaviorEntry> filterEntries(Collection<BehaviorEntry> obfDestFields, BehaviorEntry obfSourceField, ClassMatches classMatches) {
354 Set<BehaviorEntry> out = Sets.newHashSet();
355 for (BehaviorEntry obfDestField : obfDestFields) {
356 // Try to translate the signature
357 Signature translatedDestSignature = translate(obfDestField.getSignature(), classMatches.getUniqueMatches().inverse());
358 if (translatedDestSignature != null && obfSourceField.getSignature() != null && translatedDestSignature.equals(obfSourceField.getSignature()))
359 out.add(obfDestField);
360 }
361 return out;
362 }
363
364 @Override
365 public void setUpdateObfMember(ClassMapping classMapping, MemberMapping<BehaviorEntry> memberMapping, BehaviorEntry newBehavior) {
366 MethodMapping methodMapping = (MethodMapping) memberMapping;
367 classMapping.setMethodObfNameAndSignature(methodMapping.getObfName(), methodMapping.getObfSignature(), newBehavior.getName(), newBehavior.getSignature());
368 }
369
370 @Override
371 public boolean hasObfMember(ClassMapping classMapping, BehaviorEntry obfBehavior) {
372 return classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature()) != null;
373 }
374
375 @Override
376 public void removeMemberByObf(ClassMapping classMapping, BehaviorEntry obfBehavior) {
377 classMapping.removeMethodMapping(classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature()));
378 }
379 };
380 }
381
382 public static int compareMethodByteCode(CodeIterator sourceIt, CodeIterator destIt) {
383 int sourcePos = 0;
384 int destPos = 0;
385 while (sourceIt.hasNext() && destIt.hasNext()) {
386 try {
387 sourcePos = sourceIt.next();
388 destPos = destIt.next();
389 if (sourceIt.byteAt(sourcePos) != destIt.byteAt(destPos))
390 return sourcePos;
391 } catch (BadBytecode badBytecode) {
392 // Ignore bad bytecode (it might be a little bit dangerous...)
393 }
394 }
395 if (sourcePos < destPos)
396 return sourcePos;
397 else if (destPos < sourcePos)
398 return destPos;
399 return sourcePos;
400 }
401
402 public static BehaviorEntry compareMethods(CtClass destCtClass, CtClass sourceCtClass, BehaviorEntry obfSourceEntry,
403 Set<BehaviorEntry> obfDestEntries) {
404 try {
405 // Get the source method with Javassist
406 CtMethod sourceCtClassMethod = sourceCtClass.getMethod(obfSourceEntry.getName(), obfSourceEntry.getSignature().toString());
407 CodeAttribute sourceAttribute = sourceCtClassMethod.getMethodInfo().getCodeAttribute();
408
409 // Empty method body, ignore!
410 if (sourceAttribute == null)
411 return null;
412 for (BehaviorEntry desEntry : obfDestEntries) {
413 try {
414 CtMethod destCtClassMethod = destCtClass
415 .getMethod(desEntry.getName(), desEntry.getSignature().toString());
416 CodeAttribute destAttribute = destCtClassMethod.getMethodInfo().getCodeAttribute();
417
418 // Ignore empty body methods
419 if (destAttribute == null)
420 continue;
421 CodeIterator destIterator = destAttribute.iterator();
422 int maxPos = compareMethodByteCode(sourceAttribute.iterator(), destIterator);
423
424 // The bytecode is identical to the original method, assuming that the method is correct!
425 if (sourceAttribute.getCodeLength() == (maxPos + 1) && maxPos > 1)
426 return desEntry;
427 } catch (NotFoundException e) {
428 e.printStackTrace();
429 }
430 }
431 } catch (NotFoundException e) {
432 e.printStackTrace();
433 return null;
434 }
435 return null;
436 }
437
438 public static MemberMatches<BehaviorEntry> computeMethodsMatches(Deobfuscator destDeobfuscator,
439 Mappings destMappings,
440 Deobfuscator sourceDeobfuscator,
441 Mappings sourceMappings,
442 ClassMatches classMatches,
443 Doer<BehaviorEntry> doer) {
444
445 MemberMatches<BehaviorEntry> memberMatches = new MemberMatches<>();
446
447 // unmatched source fields are easy
448 MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex());
449 checker.dropBrokenMappings(destMappings);
450 for (BehaviorEntry destObfEntry : doer.getDroppedEntries(checker)) {
451 BehaviorEntry srcObfEntry = translate(destObfEntry, classMatches.getUniqueMatches().inverse());
452 memberMatches.addUnmatchedSourceEntry(srcObfEntry);
453 }
454
455 // get matched fields (anything that's left after the checks/drops is matched(
456 for (ClassMapping classMapping : destMappings.classes())
457 collectMatchedFields(memberMatches, classMapping, classMatches, doer);
458
459 // get unmatched dest fields
460 doer.getObfEntries(destDeobfuscator.getJarIndex()).stream()
461 .filter(destEntry -> !memberMatches.isMatchedDestEntry(destEntry))
462 .forEach(memberMatches::addUnmatchedDestEntry);
463
464 // Apply mappings to deobfuscator
465
466 // Create type loader
467 TranslatingTypeLoader destTypeLoader = destDeobfuscator.createTypeLoader();
468 TranslatingTypeLoader sourceTypeLoader = sourceDeobfuscator.createTypeLoader();
469
470 System.out.println("Automatching " + memberMatches.getUnmatchedSourceEntries().size() + " unmatched source entries...");
471
472 // go through the unmatched source fields and try to pick out the easy matches
473 for (ClassEntry obfSourceClass : Lists.newArrayList(memberMatches.getSourceClassesWithUnmatchedEntries())) {
474 for (BehaviorEntry obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) {
475
476 // get the possible dest matches
477 ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass);
478
479 // filter by type/signature
480 Set<BehaviorEntry> obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches);
481
482 if (obfDestEntries.size() == 1) {
483 // make the easy match
484 memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next());
485 } else if (obfDestEntries.isEmpty()) {
486 // no match is possible =(
487 memberMatches.makeSourceUnmatchable(obfSourceEntry, null);
488 } else {
489 // Multiple matches! Scan methods instructions
490 CtClass destCtClass = destTypeLoader.loadClass(obfDestClass.getClassName());
491 CtClass sourceCtClass = sourceTypeLoader.loadClass(obfSourceClass.getClassName());
492 BehaviorEntry match = compareMethods(destCtClass, sourceCtClass, obfSourceEntry, obfDestEntries);
493 // the method match correctly, match it on the member mapping!
494 if (match != null)
495 memberMatches.makeMatch(obfSourceEntry, match);
496 }
497 }
498 }
499
500 System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries",
501 memberMatches.getUnmatchedSourceEntries().size(),
502 memberMatches.getUnmatchableSourceEntries().size()
503 ));
504
505 return memberMatches;
506 }
507
508 public static <T extends Entry> MemberMatches<T> computeMemberMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches, Doer<T> doer) {
509
510 MemberMatches<T> memberMatches = new MemberMatches<>();
511
512 // unmatched source fields are easy
513 MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex());
514 checker.dropBrokenMappings(destMappings);
515 for (T destObfEntry : doer.getDroppedEntries(checker)) {
516 T srcObfEntry = translate(destObfEntry, classMatches.getUniqueMatches().inverse());
517 memberMatches.addUnmatchedSourceEntry(srcObfEntry);
518 }
519
520 // get matched fields (anything that's left after the checks/drops is matched(
521 for (ClassMapping classMapping : destMappings.classes()) {
522 collectMatchedFields(memberMatches, classMapping, classMatches, doer);
523 }
524
525 // get unmatched dest fields
526 for (T destEntry : doer.getObfEntries(destDeobfuscator.getJarIndex())) {
527 if (!memberMatches.isMatchedDestEntry(destEntry)) {
528 memberMatches.addUnmatchedDestEntry(destEntry);
529 }
530 }
531
532 System.out.println("Automatching " + memberMatches.getUnmatchedSourceEntries().size() + " unmatched source entries...");
533
534 // go through the unmatched source fields and try to pick out the easy matches
535 for (ClassEntry obfSourceClass : Lists.newArrayList(memberMatches.getSourceClassesWithUnmatchedEntries())) {
536 for (T obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) {
537
538 // get the possible dest matches
539 ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass);
540
541 // filter by type/signature
542 Set<T> obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches);
543
544 if (obfDestEntries.size() == 1) {
545 // make the easy match
546 memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next());
547 } else if (obfDestEntries.isEmpty()) {
548 // no match is possible =(
549 memberMatches.makeSourceUnmatchable(obfSourceEntry, null);
550 }
551 }
552 }
553
554 System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries",
555 memberMatches.getUnmatchedSourceEntries().size(),
556 memberMatches.getUnmatchableSourceEntries().size()
557 ));
558
559 return memberMatches;
560 }
561
562 private static <T extends Entry> void collectMatchedFields(MemberMatches<T> memberMatches, ClassMapping destClassMapping, ClassMatches classMatches, Doer<T> doer) {
563
564 // get the fields for this class
565 for (MemberMapping<T> destEntryMapping : doer.getMappings(destClassMapping)) {
566 T destObfField = destEntryMapping.getObfEntry(destClassMapping.getObfEntry());
567 T srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse());
568 memberMatches.addMatch(srcObfField, destObfField);
569 }
570
571 // recurse
572 for (ClassMapping destInnerClassMapping : destClassMapping.innerClasses()) {
573 collectMatchedFields(memberMatches, destInnerClassMapping, classMatches, doer);
574 }
575 }
576
577 @SuppressWarnings("unchecked")
578 private static <T extends Entry> T translate(T in, BiMap<ClassEntry, ClassEntry> map) {
579 if (in instanceof FieldEntry) {
580 return (T) new FieldEntry(
581 map.get(in.getClassEntry()),
582 in.getName(),
583 translate(((FieldEntry) in).getType(), map)
584 );
585 } else if (in instanceof MethodEntry) {
586 return (T) new MethodEntry(
587 map.get(in.getClassEntry()),
588 in.getName(),
589 translate(((MethodEntry) in).getSignature(), map)
590 );
591 } else if (in instanceof ConstructorEntry) {
592 return (T) new ConstructorEntry(
593 map.get(in.getClassEntry()),
594 translate(((ConstructorEntry) in).getSignature(), map)
595 );
596 }
597 throw new Error("Unhandled entry type: " + in.getClass());
598 }
599
600 private static Type translate(Type type, final BiMap<ClassEntry, ClassEntry> map) {
601 return new Type(type, inClassName ->
602 {
603 ClassEntry outClassEntry = map.get(new ClassEntry(inClassName));
604 if (outClassEntry == null) {
605 return null;
606 }
607 return outClassEntry.getName();
608 });
609 }
610
611 private static Signature translate(Signature signature, final BiMap<ClassEntry, ClassEntry> map) {
612 if (signature == null) {
613 return null;
614 }
615 return new Signature(signature, inClassName ->
616 {
617 ClassEntry outClassEntry = map.get(new ClassEntry(inClassName));
618 if (outClassEntry == null) {
619 return null;
620 }
621 return outClassEntry.getName();
622 });
623 }
624
625 public static <T extends Entry> void applyMemberMatches(Mappings mappings, ClassMatches classMatches, MemberMatches<T> memberMatches, Doer<T> doer) {
626 for (ClassMapping classMapping : mappings.classes()) {
627 applyMemberMatches(classMapping, classMatches, memberMatches, doer);
628 }
629 }
630
631 private static <T extends Entry> void applyMemberMatches(ClassMapping classMapping, ClassMatches classMatches, MemberMatches<T> memberMatches, Doer<T> doer) {
632
633 // get the classes
634 ClassEntry obfDestClass = new ClassEntry(classMapping.getObfFullName());
635
636 // make a map of all the renames we need to make
637 Map<T, T> renames = Maps.newHashMap();
638 for (MemberMapping<T> memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) {
639 T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass);
640 T obfSourceEntry = getSourceEntryFromDestMapping(memberMapping, obfDestClass, classMatches);
641
642 // but drop the unmatchable things
643 if (memberMatches.isUnmatchableSourceEntry(obfSourceEntry)) {
644 doer.removeMemberByObf(classMapping, obfOldDestEntry);
645 continue;
646 }
647
648 T obfNewDestEntry = memberMatches.matches().get(obfSourceEntry);
649 if (obfNewDestEntry != null && !obfOldDestEntry.getName().equals(obfNewDestEntry.getName())) {
650 renames.put(obfOldDestEntry, obfNewDestEntry);
651 }
652 }
653
654 if (!renames.isEmpty()) {
655
656 // apply to this class (should never need more than n passes)
657 int numRenamesAppliedThisRound;
658 do {
659 numRenamesAppliedThisRound = 0;
660
661 for (MemberMapping<T> memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) {
662 T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass);
663 T obfNewDestEntry = renames.get(obfOldDestEntry);
664 if (obfNewDestEntry != null) {
665 // make sure this rename won't cause a collision
666 // otherwise, save it for the next round and try again next time
667 if (!doer.hasObfMember(classMapping, obfNewDestEntry)) {
668 doer.setUpdateObfMember(classMapping, memberMapping, obfNewDestEntry);
669 renames.remove(obfOldDestEntry);
670 numRenamesAppliedThisRound++;
671 }
672 }
673 }
674 } while (numRenamesAppliedThisRound > 0);
675
676 if (!renames.isEmpty()) {
677 System.err.println(String.format("WARNING: Couldn't apply all the renames for class %s. %d renames left.",
678 classMapping.getObfFullName(), renames.size()
679 ));
680 for (Map.Entry<T, T> entry : renames.entrySet()) {
681 System.err.println(String.format("\t%s -> %s", entry.getKey().getName(), entry.getValue().getName()));
682 }
683 }
684 }
685
686 // recurse
687 for (ClassMapping innerClassMapping : classMapping.innerClasses()) {
688 applyMemberMatches(innerClassMapping, classMatches, memberMatches, doer);
689 }
690 }
691
692 private static <T extends Entry> T getSourceEntryFromDestMapping(MemberMapping<T> destMemberMapping, ClassEntry obfDestClass, ClassMatches classMatches) {
693 return translate(destMemberMapping.getObfEntry(obfDestClass), classMatches.getUniqueMatches().inverse());
694 }
695
696 public interface Doer<T extends Entry> {
697 Collection<T> getDroppedEntries(MappingsChecker checker);
698
699 Collection<T> getObfEntries(JarIndex jarIndex);
700
701 Collection<? extends MemberMapping<T>> getMappings(ClassMapping destClassMapping);
702
703 Set<T> filterEntries(Collection<T> obfEntries, T obfSourceEntry, ClassMatches classMatches);
704
705 void setUpdateObfMember(ClassMapping classMapping, MemberMapping<T> memberMapping, T newEntry);
706
707 boolean hasObfMember(ClassMapping classMapping, T obfEntry);
708
709 void removeMemberByObf(ClassMapping classMapping, T obfEntry);
710 }
711}