diff options
| author | 2019-01-24 14:48:32 +0200 | |
|---|---|---|
| committer | 2019-01-24 13:48:32 +0100 | |
| commit | 00fcd0550fcdda621c2e4662f6ddd55ce673b931 (patch) | |
| tree | 6f9e4c24dbcc6d118fceec56adf7bf9d747a485c /src/main/java/cuchaz/enigma/analysis/JarIndex.java | |
| parent | mark as 0.13.0-SNAPSHOT for preliminary development (diff) | |
| download | enigma-fork-00fcd0550fcdda621c2e4662f6ddd55ce673b931.tar.gz enigma-fork-00fcd0550fcdda621c2e4662f6ddd55ce673b931.tar.xz enigma-fork-00fcd0550fcdda621c2e4662f6ddd55ce673b931.zip | |
[WIP] Mapping rework (#91)
* Move packages
* Mapping & entry refactor: first pass
* Fix deobf -> obf tree remapping
* Resolve various issues
* Give all entries the potential for parents and treat inner classes as children
* Deobf UI tree elements
* Tests pass
* Sort mapping output
* Fix delta tracking
* Index separation and first pass for #97
* Keep track of remapped jar index
* Fix child entries not being remapped
* Drop non-root entries
* Track dropped mappings
* Fix enigma mapping ordering
* EntryTreeNode interface
* Small tweaks
* Naive full index remap on rename
* Entries can resolve to more than one root entry
* Support alternative resolution strategies
* Bridge method resolution
* Tests pass
* Fix mappings being used where there are none
* Fix methods with different descriptors being considered unique. closes #89
Diffstat (limited to 'src/main/java/cuchaz/enigma/analysis/JarIndex.java')
| -rw-r--r-- | src/main/java/cuchaz/enigma/analysis/JarIndex.java | 583 |
1 files changed, 0 insertions, 583 deletions
diff --git a/src/main/java/cuchaz/enigma/analysis/JarIndex.java b/src/main/java/cuchaz/enigma/analysis/JarIndex.java deleted file mode 100644 index 361c8e7..0000000 --- a/src/main/java/cuchaz/enigma/analysis/JarIndex.java +++ /dev/null | |||
| @@ -1,583 +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 | |||
| 12 | package cuchaz.enigma.analysis; | ||
| 13 | |||
| 14 | import com.google.common.collect.*; | ||
| 15 | import cuchaz.enigma.bytecode.AccessFlags; | ||
| 16 | import cuchaz.enigma.mapping.*; | ||
| 17 | import cuchaz.enigma.mapping.entry.*; | ||
| 18 | import org.objectweb.asm.ClassReader; | ||
| 19 | import org.objectweb.asm.ClassVisitor; | ||
| 20 | import org.objectweb.asm.Opcodes; | ||
| 21 | |||
| 22 | import java.util.*; | ||
| 23 | |||
| 24 | public class JarIndex { | ||
| 25 | |||
| 26 | private final ReferencedEntryPool entryPool; | ||
| 27 | |||
| 28 | private Set<ClassEntry> obfClassEntries; | ||
| 29 | private TranslationIndex translationIndex; | ||
| 30 | private Map<Entry, AccessFlags> access; | ||
| 31 | private Multimap<ClassEntry, FieldDefEntry> fields; | ||
| 32 | private Multimap<ClassEntry, MethodDefEntry> methods; | ||
| 33 | private Multimap<String, MethodDefEntry> methodImplementations; | ||
| 34 | private Multimap<MethodEntry, EntryReference<MethodEntry, MethodDefEntry>> methodsReferencing; | ||
| 35 | private Multimap<ClassEntry, EntryReference<ClassEntry, MethodDefEntry>> methodsReferencingClasses; | ||
| 36 | private Multimap<MethodEntry, MethodEntry> methodReferences; | ||
| 37 | private Multimap<FieldEntry, EntryReference<FieldEntry, MethodDefEntry>> fieldReferences; | ||
| 38 | private Multimap<ClassEntry, ClassEntry> innerClassesByOuter; | ||
| 39 | private Map<ClassEntry, ClassEntry> outerClassesByInner; | ||
| 40 | private Map<MethodEntry, MethodEntry> bridgedMethods; | ||
| 41 | private Set<MethodEntry> syntheticMethods; | ||
| 42 | |||
| 43 | public JarIndex(ReferencedEntryPool entryPool) { | ||
| 44 | this.entryPool = entryPool; | ||
| 45 | this.obfClassEntries = Sets.newHashSet(); | ||
| 46 | this.translationIndex = new TranslationIndex(entryPool); | ||
| 47 | this.access = Maps.newHashMap(); | ||
| 48 | this.fields = HashMultimap.create(); | ||
| 49 | this.methods = HashMultimap.create(); | ||
| 50 | this.methodImplementations = HashMultimap.create(); | ||
| 51 | this.methodsReferencingClasses = HashMultimap.create(); | ||
| 52 | this.methodsReferencing = HashMultimap.create(); | ||
| 53 | this.methodReferences = HashMultimap.create(); | ||
| 54 | this.fieldReferences = HashMultimap.create(); | ||
| 55 | this.innerClassesByOuter = HashMultimap.create(); | ||
| 56 | this.outerClassesByInner = Maps.newHashMap(); | ||
| 57 | this.bridgedMethods = Maps.newHashMap(); | ||
| 58 | this.syntheticMethods = Sets.newHashSet(); | ||
| 59 | } | ||
| 60 | |||
| 61 | public void indexJar(ParsedJar jar, boolean buildInnerClasses) { | ||
| 62 | |||
| 63 | // step 1: read the class names | ||
| 64 | obfClassEntries.addAll(jar.getClassEntries()); | ||
| 65 | |||
| 66 | // step 2: index classes, fields, methods, interfaces | ||
| 67 | if (buildInnerClasses) { | ||
| 68 | // + step 5: index inner classes | ||
| 69 | jar.visitReader(name -> new IndexClassVisitor(this, Opcodes.ASM5, new IndexInnerClassVisitor(this, Opcodes.ASM5)), ClassReader.SKIP_CODE); | ||
| 70 | } else { | ||
| 71 | jar.visitReader(name -> new IndexClassVisitor(this, Opcodes.ASM5), ClassReader.SKIP_CODE); | ||
| 72 | } | ||
| 73 | |||
| 74 | // step 3: index field, method, constructor references | ||
| 75 | jar.visitReader(name -> new IndexReferenceVisitor(this, Opcodes.ASM5), ClassReader.SKIP_FRAMES); | ||
| 76 | |||
| 77 | // step 4: index access and bridged methods | ||
| 78 | for (MethodDefEntry methodEntry : methods.values()) { | ||
| 79 | // look for access and bridged methods | ||
| 80 | MethodEntry accessedMethod = findAccessMethod(methodEntry); | ||
| 81 | if (accessedMethod != null) { | ||
| 82 | if (isBridgedMethod(accessedMethod, methodEntry)) { | ||
| 83 | this.bridgedMethods.put(methodEntry, accessedMethod); | ||
| 84 | } | ||
| 85 | } | ||
| 86 | } | ||
| 87 | |||
| 88 | if (buildInnerClasses) { | ||
| 89 | // step 6: update other indices with inner class info | ||
| 90 | Map<String, String> renames = Maps.newHashMap(); | ||
| 91 | for (ClassEntry innerClassEntry : this.innerClassesByOuter.values()) { | ||
| 92 | String newName = innerClassEntry.buildClassEntry(getObfClassChain(innerClassEntry)).getName(); | ||
| 93 | if (!innerClassEntry.getName().equals(newName)) { | ||
| 94 | // DEBUG | ||
| 95 | //System.out.println("REPLACE: " + innerClassEntry.getName() + " WITH " + newName); | ||
| 96 | renames.put(innerClassEntry.getName(), newName); | ||
| 97 | } | ||
| 98 | } | ||
| 99 | EntryRenamer.renameClassesInSet(renames, this.obfClassEntries); | ||
| 100 | this.translationIndex.renameClasses(renames); | ||
| 101 | EntryRenamer.renameClassesInMultimap(renames, this.methodImplementations); | ||
| 102 | EntryRenamer.renameClassesInMultimap(renames, this.methodsReferencingClasses); | ||
| 103 | EntryRenamer.renameClassesInMultimap(renames, this.methodsReferencing); | ||
| 104 | EntryRenamer.renameClassesInMultimap(renames, this.methodReferences); | ||
| 105 | EntryRenamer.renameClassesInMultimap(renames, this.fieldReferences); | ||
| 106 | EntryRenamer.renameClassesInMap(renames, this.access); | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | protected ClassDefEntry indexClass(int access, String name, String signature, String superName, String[] interfaces) { | ||
| 111 | for (String interfaceName : interfaces) { | ||
| 112 | if (name.equals(interfaceName)) { | ||
| 113 | throw new IllegalArgumentException("Class cannot be its own interface! " + name); | ||
| 114 | } | ||
| 115 | } | ||
| 116 | ClassDefEntry entry = this.translationIndex.indexClass(access, name, signature, superName, interfaces); | ||
| 117 | this.access.put(entry, entry.getAccess()); | ||
| 118 | return entry; | ||
| 119 | } | ||
| 120 | |||
| 121 | protected void indexField(ClassDefEntry owner, int access, String name, String desc, String signature) { | ||
| 122 | FieldDefEntry fieldEntry = new FieldDefEntry(owner, name, new TypeDescriptor(desc), Signature.createTypedSignature(signature), new AccessFlags(access)); | ||
| 123 | this.translationIndex.indexField(fieldEntry); | ||
| 124 | this.access.put(fieldEntry, fieldEntry.getAccess()); | ||
| 125 | this.fields.put(fieldEntry.getOwnerClassEntry(), fieldEntry); | ||
| 126 | } | ||
| 127 | |||
| 128 | protected void indexMethod(ClassDefEntry owner, int access, String name, String desc, String signature) { | ||
| 129 | MethodDefEntry methodEntry = new MethodDefEntry(owner, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access)); | ||
| 130 | this.translationIndex.indexMethod(methodEntry); | ||
| 131 | this.access.put(methodEntry, methodEntry.getAccess()); | ||
| 132 | this.methods.put(methodEntry.getOwnerClassEntry(), methodEntry); | ||
| 133 | |||
| 134 | if (new AccessFlags(access).isSynthetic()) { | ||
| 135 | syntheticMethods.add(methodEntry); | ||
| 136 | } | ||
| 137 | |||
| 138 | // we don't care about constructors here | ||
| 139 | if (!methodEntry.isConstructor()) { | ||
| 140 | // index implementation | ||
| 141 | this.methodImplementations.put(methodEntry.getClassName(), methodEntry); | ||
| 142 | } | ||
| 143 | } | ||
| 144 | |||
| 145 | protected void indexMethodCall(MethodDefEntry callerEntry, String owner, String name, String desc) { | ||
| 146 | ClassEntry referencedClass = entryPool.getClass(owner); | ||
| 147 | MethodEntry referencedMethod = new MethodEntry(referencedClass, name, new MethodDescriptor(desc)); | ||
| 148 | ClassEntry resolvedClassEntry = translationIndex.resolveEntryOwner(referencedMethod); | ||
| 149 | if (resolvedClassEntry != null && !resolvedClassEntry.equals(referencedMethod.getOwnerClassEntry())) { | ||
| 150 | referencedMethod = referencedMethod.updateOwnership(resolvedClassEntry); | ||
| 151 | } | ||
| 152 | methodsReferencing.put(referencedMethod, new EntryReference<>(referencedMethod, referencedMethod.getName(), callerEntry)); | ||
| 153 | if (referencedMethod.isConstructor()) { | ||
| 154 | methodsReferencingClasses.put(referencedClass, new EntryReference<>(referencedClass, referencedMethod.getName(), callerEntry)); | ||
| 155 | } | ||
| 156 | methodReferences.put(callerEntry, referencedMethod); | ||
| 157 | } | ||
| 158 | |||
| 159 | protected void indexFieldAccess(MethodDefEntry callerEntry, String owner, String name, String desc) { | ||
| 160 | FieldEntry referencedField = new FieldEntry(entryPool.getClass(owner), name, new TypeDescriptor(desc)); | ||
| 161 | ClassEntry resolvedClassEntry = translationIndex.resolveEntryOwner(referencedField); | ||
| 162 | if (resolvedClassEntry != null && !resolvedClassEntry.equals(referencedField.getOwnerClassEntry())) { | ||
| 163 | referencedField = referencedField.updateOwnership(resolvedClassEntry); | ||
| 164 | } | ||
| 165 | fieldReferences.put(referencedField, new EntryReference<>(referencedField, referencedField.getName(), callerEntry)); | ||
| 166 | } | ||
| 167 | |||
| 168 | public void indexInnerClass(ClassEntry innerEntry, ClassEntry outerEntry) { | ||
| 169 | this.innerClassesByOuter.put(outerEntry, innerEntry); | ||
| 170 | this.outerClassesByInner.putIfAbsent(innerEntry, outerEntry); | ||
| 171 | } | ||
| 172 | |||
| 173 | private MethodEntry findAccessMethod(MethodDefEntry method) { | ||
| 174 | |||
| 175 | // we want to find all compiler-added methods that directly call another with no processing | ||
| 176 | |||
| 177 | // skip non-synthetic methods | ||
| 178 | if (!method.getAccess().isSynthetic()) { | ||
| 179 | return null; | ||
| 180 | } | ||
| 181 | |||
| 182 | // get all the methods that we call | ||
| 183 | final Collection<MethodEntry> referencedMethods = methodReferences.get(method); | ||
| 184 | |||
| 185 | // is there just one? | ||
| 186 | if (referencedMethods.size() != 1) { | ||
| 187 | return null; | ||
| 188 | } | ||
| 189 | |||
| 190 | return referencedMethods.stream().findFirst().orElse(null); | ||
| 191 | } | ||
| 192 | |||
| 193 | private boolean isBridgedMethod(MethodEntry called, MethodEntry access) { | ||
| 194 | // Bridged methods will always have the same name as the method they are calling | ||
| 195 | // They will also have the same amount of parameters (though equal descriptors cannot be guaranteed) | ||
| 196 | if (!called.getName().equals(access.getName()) || called.getDesc().getArgumentDescs().size() != access.getDesc().getArgumentDescs().size()) { | ||
| 197 | return false; | ||
| 198 | } | ||
| 199 | |||
| 200 | TypeDescriptor accessReturn = access.getDesc().getReturnDesc(); | ||
| 201 | TypeDescriptor calledReturn = called.getDesc().getReturnDesc(); | ||
| 202 | if (calledReturn.isVoid() || calledReturn.isPrimitive() || accessReturn.isVoid() || accessReturn.isPrimitive()) { | ||
| 203 | return false; | ||
| 204 | } | ||
| 205 | |||
| 206 | // Bridged methods will never have the same type as what they are calling | ||
| 207 | if (accessReturn.equals(calledReturn)) { | ||
| 208 | return false; | ||
| 209 | } | ||
| 210 | |||
| 211 | String accessType = accessReturn.toString(); | ||
| 212 | |||
| 213 | // If we're casting down from generic type to type-erased Object we're a bridge method | ||
| 214 | if (accessType.equals("Ljava/lang/Object;")) { | ||
| 215 | return true; | ||
| 216 | } | ||
| 217 | |||
| 218 | // Now we need to detect cases where we are being casted down to a higher type bound | ||
| 219 | List<ClassEntry> calledAncestry = translationIndex.getAncestry(calledReturn.getTypeEntry()); | ||
| 220 | return calledAncestry.contains(accessReturn.getTypeEntry()); | ||
| 221 | } | ||
| 222 | |||
| 223 | public Set<ClassEntry> getObfClassEntries() { | ||
| 224 | return this.obfClassEntries; | ||
| 225 | } | ||
| 226 | |||
| 227 | public Collection<FieldDefEntry> getObfFieldEntries() { | ||
| 228 | return this.fields.values(); | ||
| 229 | } | ||
| 230 | |||
| 231 | public Collection<FieldDefEntry> getObfFieldEntries(ClassEntry classEntry) { | ||
| 232 | return this.fields.get(classEntry); | ||
| 233 | } | ||
| 234 | |||
| 235 | public Collection<MethodDefEntry> getObfBehaviorEntries() { | ||
| 236 | return this.methods.values(); | ||
| 237 | } | ||
| 238 | |||
| 239 | public Collection<MethodDefEntry> getObfBehaviorEntries(ClassEntry classEntry) { | ||
| 240 | return this.methods.get(classEntry); | ||
| 241 | } | ||
| 242 | |||
| 243 | public TranslationIndex getTranslationIndex() { | ||
| 244 | return this.translationIndex; | ||
| 245 | } | ||
| 246 | |||
| 247 | @Deprecated | ||
| 248 | public Access getAccess(Entry entry) { | ||
| 249 | AccessFlags flags = getAccessFlags(entry); | ||
| 250 | return flags != null ? Access.get(flags) : null; | ||
| 251 | } | ||
| 252 | |||
| 253 | public AccessFlags getAccessFlags(Entry entry) { | ||
| 254 | return this.access.get(entry); | ||
| 255 | } | ||
| 256 | |||
| 257 | public ClassInheritanceTreeNode getClassInheritance(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) { | ||
| 258 | |||
| 259 | // get the root node | ||
| 260 | List<String> ancestry = Lists.newArrayList(); | ||
| 261 | ancestry.add(obfClassEntry.getName()); | ||
| 262 | for (ClassEntry classEntry : this.translationIndex.getAncestry(obfClassEntry)) { | ||
| 263 | if (containsObfClass(classEntry)) { | ||
| 264 | ancestry.add(classEntry.getName()); | ||
| 265 | } | ||
| 266 | } | ||
| 267 | ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode( | ||
| 268 | deobfuscatingTranslator, | ||
| 269 | ancestry.get(ancestry.size() - 1) | ||
| 270 | ); | ||
| 271 | |||
| 272 | // expand all children recursively | ||
| 273 | rootNode.load(this.translationIndex, true); | ||
| 274 | |||
| 275 | return rootNode; | ||
| 276 | } | ||
| 277 | |||
| 278 | public ClassImplementationsTreeNode getClassImplementations(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) { | ||
| 279 | |||
| 280 | // is this even an interface? | ||
| 281 | if (isInterface(obfClassEntry.getClassName())) { | ||
| 282 | ClassImplementationsTreeNode node = new ClassImplementationsTreeNode(deobfuscatingTranslator, obfClassEntry); | ||
| 283 | node.load(this); | ||
| 284 | return node; | ||
| 285 | } | ||
| 286 | return null; | ||
| 287 | } | ||
| 288 | |||
| 289 | public MethodInheritanceTreeNode getMethodInheritance(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) { | ||
| 290 | // travel to the ancestor implementation | ||
| 291 | LinkedList<ClassEntry> entries = new LinkedList<>(); | ||
| 292 | entries.add(obfMethodEntry.getOwnerClassEntry()); | ||
| 293 | |||
| 294 | // TODO: This could be optimized to not go through interfaces repeatedly... | ||
| 295 | |||
| 296 | ClassEntry baseImplementationClassEntry = obfMethodEntry.getOwnerClassEntry(); | ||
| 297 | |||
| 298 | for (ClassEntry itf : getInterfaces(obfMethodEntry.getOwnerClassEntry().getClassName())) { | ||
| 299 | MethodEntry itfMethodEntry = entryPool.getMethod(itf, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString()); | ||
| 300 | if (itfMethodEntry != null && containsObfMethod(itfMethodEntry)) { | ||
| 301 | baseImplementationClassEntry = itf; | ||
| 302 | } | ||
| 303 | } | ||
| 304 | |||
| 305 | for (ClassEntry ancestorClassEntry : this.translationIndex.getAncestry(entries.remove())) { | ||
| 306 | MethodEntry ancestorMethodEntry = entryPool.getMethod(ancestorClassEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString()); | ||
| 307 | if (ancestorMethodEntry != null) { | ||
| 308 | if (containsObfMethod(ancestorMethodEntry)) { | ||
| 309 | baseImplementationClassEntry = ancestorClassEntry; | ||
| 310 | } | ||
| 311 | |||
| 312 | for (ClassEntry itf : getInterfaces(ancestorClassEntry.getClassName())) { | ||
| 313 | MethodEntry itfMethodEntry = entryPool.getMethod(itf, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString()); | ||
| 314 | if (itfMethodEntry != null && containsObfMethod(itfMethodEntry)) { | ||
| 315 | baseImplementationClassEntry = itf; | ||
| 316 | } | ||
| 317 | } | ||
| 318 | } | ||
| 319 | } | ||
| 320 | |||
| 321 | // make a root node at the base | ||
| 322 | MethodEntry methodEntry = entryPool.getMethod(baseImplementationClassEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString()); | ||
| 323 | MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode( | ||
| 324 | deobfuscatingTranslator, | ||
| 325 | methodEntry, | ||
| 326 | containsObfMethod(methodEntry) | ||
| 327 | ); | ||
| 328 | |||
| 329 | // expand the full tree | ||
| 330 | rootNode.load(this, true); | ||
| 331 | |||
| 332 | return rootNode; | ||
| 333 | } | ||
| 334 | |||
| 335 | public List<MethodImplementationsTreeNode> getMethodImplementations(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) { | ||
| 336 | |||
| 337 | List<MethodEntry> interfaceMethodEntries = Lists.newArrayList(); | ||
| 338 | |||
| 339 | // is this method on an interface? | ||
| 340 | if (isInterface(obfMethodEntry.getClassName())) { | ||
| 341 | interfaceMethodEntries.add(obfMethodEntry); | ||
| 342 | } else { | ||
| 343 | // get the interface class | ||
| 344 | for (ClassEntry interfaceEntry : getInterfaces(obfMethodEntry.getClassName())) { | ||
| 345 | |||
| 346 | // is this method defined in this interface? | ||
| 347 | MethodEntry methodInterface = entryPool.getMethod(interfaceEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString()); | ||
| 348 | if (methodInterface != null && containsObfMethod(methodInterface)) { | ||
| 349 | interfaceMethodEntries.add(methodInterface); | ||
| 350 | } | ||
| 351 | } | ||
| 352 | } | ||
| 353 | |||
| 354 | List<MethodImplementationsTreeNode> nodes = Lists.newArrayList(); | ||
| 355 | if (!interfaceMethodEntries.isEmpty()) { | ||
| 356 | for (MethodEntry interfaceMethodEntry : interfaceMethodEntries) { | ||
| 357 | MethodImplementationsTreeNode node = new MethodImplementationsTreeNode(deobfuscatingTranslator, interfaceMethodEntry); | ||
| 358 | node.load(this); | ||
| 359 | nodes.add(node); | ||
| 360 | } | ||
| 361 | } | ||
| 362 | return nodes; | ||
| 363 | } | ||
| 364 | |||
| 365 | public Set<MethodEntry> getRelatedMethodImplementations(MethodEntry obfMethodEntry) { | ||
| 366 | AccessFlags flags = getAccessFlags(obfMethodEntry); | ||
| 367 | if (flags.isPrivate() || flags.isStatic()) { | ||
| 368 | return Collections.singleton(obfMethodEntry); | ||
| 369 | } | ||
| 370 | |||
| 371 | Set<MethodEntry> methodEntries = Sets.newHashSet(); | ||
| 372 | getRelatedMethodImplementations(methodEntries, getMethodInheritance(new DirectionalTranslator(entryPool), obfMethodEntry)); | ||
| 373 | return methodEntries; | ||
| 374 | } | ||
| 375 | |||
| 376 | private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodInheritanceTreeNode node) { | ||
| 377 | MethodEntry methodEntry = node.getMethodEntry(); | ||
| 378 | if (methodEntries.contains(methodEntry)) { | ||
| 379 | return; | ||
| 380 | } | ||
| 381 | |||
| 382 | if (containsObfMethod(methodEntry)) { | ||
| 383 | AccessFlags flags = getAccessFlags(methodEntry); | ||
| 384 | if (!flags.isPrivate() && !flags.isStatic()) { | ||
| 385 | // collect the entry | ||
| 386 | methodEntries.add(methodEntry); | ||
| 387 | } | ||
| 388 | } | ||
| 389 | |||
| 390 | // look at bridge methods! | ||
| 391 | MethodEntry bridgedMethod = getBridgedMethod(methodEntry); | ||
| 392 | while (bridgedMethod != null) { | ||
| 393 | methodEntries.addAll(getRelatedMethodImplementations(bridgedMethod)); | ||
| 394 | bridgedMethod = getBridgedMethod(bridgedMethod); | ||
| 395 | } | ||
| 396 | |||
| 397 | // look at interface methods too | ||
| 398 | for (MethodImplementationsTreeNode implementationsNode : getMethodImplementations(new DirectionalTranslator(entryPool), methodEntry)) { | ||
| 399 | getRelatedMethodImplementations(methodEntries, implementationsNode); | ||
| 400 | } | ||
| 401 | |||
| 402 | // recurse | ||
| 403 | for (int i = 0; i < node.getChildCount(); i++) { | ||
| 404 | getRelatedMethodImplementations(methodEntries, (MethodInheritanceTreeNode) node.getChildAt(i)); | ||
| 405 | } | ||
| 406 | } | ||
| 407 | |||
| 408 | private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodImplementationsTreeNode node) { | ||
| 409 | MethodEntry methodEntry = node.getMethodEntry(); | ||
| 410 | if (containsObfMethod(methodEntry)) { | ||
| 411 | AccessFlags flags = getAccessFlags(methodEntry); | ||
| 412 | if (!flags.isPrivate() && !flags.isStatic()) { | ||
| 413 | // collect the entry | ||
| 414 | methodEntries.add(methodEntry); | ||
| 415 | } | ||
| 416 | } | ||
| 417 | |||
| 418 | // look at bridge methods! | ||
| 419 | MethodEntry bridgedMethod = getBridgedMethod(methodEntry); | ||
| 420 | while (bridgedMethod != null) { | ||
| 421 | methodEntries.addAll(getRelatedMethodImplementations(bridgedMethod)); | ||
| 422 | bridgedMethod = getBridgedMethod(bridgedMethod); | ||
| 423 | } | ||
| 424 | |||
| 425 | // recurse | ||
| 426 | for (int i = 0; i < node.getChildCount(); i++) { | ||
| 427 | getRelatedMethodImplementations(methodEntries, (MethodImplementationsTreeNode) node.getChildAt(i)); | ||
| 428 | } | ||
| 429 | } | ||
| 430 | |||
| 431 | public Collection<EntryReference<FieldEntry, MethodDefEntry>> getFieldReferences(FieldEntry fieldEntry) { | ||
| 432 | return this.fieldReferences.get(fieldEntry); | ||
| 433 | } | ||
| 434 | |||
| 435 | public Collection<FieldEntry> getReferencedFields(MethodDefEntry methodEntry) { | ||
| 436 | // linear search is fast enough for now | ||
| 437 | Set<FieldEntry> fieldEntries = Sets.newHashSet(); | ||
| 438 | for (EntryReference<FieldEntry, MethodDefEntry> reference : this.fieldReferences.values()) { | ||
| 439 | if (reference.context == methodEntry) { | ||
| 440 | fieldEntries.add(reference.entry); | ||
| 441 | } | ||
| 442 | } | ||
| 443 | return fieldEntries; | ||
| 444 | } | ||
| 445 | |||
| 446 | public Collection<EntryReference<ClassEntry, MethodDefEntry>> getMethodsReferencing(ClassEntry classEntry) { | ||
| 447 | return this.methodsReferencingClasses.get(classEntry); | ||
| 448 | } | ||
| 449 | |||
| 450 | @Deprecated | ||
| 451 | public Collection<EntryReference<MethodEntry, MethodDefEntry>> getMethodsReferencing(MethodEntry methodEntry) { | ||
| 452 | return getMethodsReferencing(methodEntry, false); | ||
| 453 | } | ||
| 454 | |||
| 455 | public Collection<EntryReference<MethodEntry, MethodDefEntry>> getMethodsReferencing(MethodEntry methodEntry, boolean recurse) { | ||
| 456 | if (!recurse) { | ||
| 457 | return this.methodsReferencing.get(methodEntry); | ||
| 458 | } | ||
| 459 | |||
| 460 | List<EntryReference<MethodEntry, MethodDefEntry>> references = new ArrayList<>(); | ||
| 461 | Set<MethodEntry> methodEntries = getRelatedMethodImplementations(methodEntry); | ||
| 462 | for (MethodEntry entry : methodEntries) { | ||
| 463 | references.addAll(getMethodsReferencing(entry, false)); | ||
| 464 | } | ||
| 465 | return references; | ||
| 466 | } | ||
| 467 | |||
| 468 | public Collection<MethodEntry> getReferencedMethods(MethodDefEntry methodEntry) { | ||
| 469 | return this.methodReferences.get(methodEntry); | ||
| 470 | } | ||
| 471 | |||
| 472 | public Collection<ClassEntry> getInnerClasses(ClassEntry obfOuterClassEntry) { | ||
| 473 | return this.innerClassesByOuter.get(obfOuterClassEntry); | ||
| 474 | } | ||
| 475 | |||
| 476 | public ClassEntry getOuterClass(ClassEntry obfInnerClassEntry) { | ||
| 477 | return this.outerClassesByInner.get(obfInnerClassEntry); | ||
| 478 | } | ||
| 479 | |||
| 480 | public boolean isSyntheticMethod(MethodEntry methodEntry) { | ||
| 481 | return this.syntheticMethods.contains(methodEntry); | ||
| 482 | } | ||
| 483 | |||
| 484 | public Set<ClassEntry> getInterfaces(String className) { | ||
| 485 | ClassEntry classEntry = entryPool.getClass(className); | ||
| 486 | Set<ClassEntry> interfaces = new HashSet<>(this.translationIndex.getInterfaces(classEntry)); | ||
| 487 | for (ClassEntry ancestor : this.translationIndex.getAncestry(classEntry)) { | ||
| 488 | interfaces.addAll(this.translationIndex.getInterfaces(ancestor)); | ||
| 489 | } | ||
| 490 | return interfaces; | ||
| 491 | } | ||
| 492 | |||
| 493 | public Set<String> getImplementingClasses(String targetInterfaceName) { | ||
| 494 | |||
| 495 | // linear search is fast enough for now | ||
| 496 | Set<String> classNames = Sets.newHashSet(); | ||
| 497 | for (Map.Entry<ClassEntry, ClassEntry> entry : this.translationIndex.getClassInterfaces()) { | ||
| 498 | ClassEntry classEntry = entry.getKey(); | ||
| 499 | ClassEntry interfaceEntry = entry.getValue(); | ||
| 500 | if (interfaceEntry.getName().equals(targetInterfaceName)) { | ||
| 501 | String className = classEntry.getClassName(); | ||
| 502 | classNames.add(className); | ||
| 503 | if (isInterface(className)) { | ||
| 504 | classNames.addAll(getImplementingClasses(className)); | ||
| 505 | } | ||
| 506 | |||
| 507 | this.translationIndex.getSubclassNamesRecursively(classNames, classEntry); | ||
| 508 | } | ||
| 509 | } | ||
| 510 | return classNames; | ||
| 511 | } | ||
| 512 | |||
| 513 | public boolean isInterface(String className) { | ||
| 514 | return this.translationIndex.isInterface(entryPool.getClass(className)); | ||
| 515 | } | ||
| 516 | |||
| 517 | public boolean containsObfClass(ClassEntry obfClassEntry) { | ||
| 518 | return this.obfClassEntries.contains(obfClassEntry); | ||
| 519 | } | ||
| 520 | |||
| 521 | public boolean containsObfField(FieldEntry obfFieldEntry) { | ||
| 522 | return this.access.containsKey(obfFieldEntry); | ||
| 523 | } | ||
| 524 | |||
| 525 | public boolean containsObfMethod(MethodEntry obfMethodEntry) { | ||
| 526 | return this.access.containsKey(obfMethodEntry); | ||
| 527 | } | ||
| 528 | |||
| 529 | public boolean containsEntryWithSameName(Entry entry) { | ||
| 530 | for (Entry target : this.access.keySet()) | ||
| 531 | if (target.getName().equals(entry.getName()) && entry.getClass().isInstance(target.getClass())) | ||
| 532 | return true; | ||
| 533 | return false; | ||
| 534 | } | ||
| 535 | |||
| 536 | public boolean containsObfVariable(LocalVariableEntry obfVariableEntry) { | ||
| 537 | // check the behavior | ||
| 538 | if (!containsObfMethod(obfVariableEntry.getOwnerEntry())) { | ||
| 539 | return false; | ||
| 540 | } | ||
| 541 | |||
| 542 | return true; | ||
| 543 | } | ||
| 544 | |||
| 545 | public boolean containsObfEntry(Entry obfEntry) { | ||
| 546 | if (obfEntry instanceof ClassEntry) { | ||
| 547 | return containsObfClass((ClassEntry) obfEntry); | ||
| 548 | } else if (obfEntry instanceof FieldEntry) { | ||
| 549 | return containsObfField((FieldEntry) obfEntry); | ||
| 550 | } else if (obfEntry instanceof MethodEntry) { | ||
| 551 | return containsObfMethod((MethodEntry) obfEntry); | ||
| 552 | } else if (obfEntry instanceof LocalVariableEntry) { | ||
| 553 | return containsObfVariable((LocalVariableEntry) obfEntry); | ||
| 554 | } else { | ||
| 555 | throw new Error("Entry desc not supported: " + obfEntry.getClass().getName()); | ||
| 556 | } | ||
| 557 | } | ||
| 558 | |||
| 559 | public MethodEntry getBridgedMethod(MethodEntry bridgeMethodEntry) { | ||
| 560 | return this.bridgedMethods.get(bridgeMethodEntry); | ||
| 561 | } | ||
| 562 | |||
| 563 | public List<ClassEntry> getObfClassChain(ClassEntry obfClassEntry) { | ||
| 564 | |||
| 565 | // build class chain in inner-to-outer order | ||
| 566 | List<ClassEntry> obfClassChain = Lists.newArrayList(obfClassEntry); | ||
| 567 | ClassEntry checkClassEntry = obfClassEntry; | ||
| 568 | while (true) { | ||
| 569 | ClassEntry obfOuterClassEntry = getOuterClass(checkClassEntry); | ||
| 570 | if (obfOuterClassEntry != null) { | ||
| 571 | obfClassChain.add(obfOuterClassEntry); | ||
| 572 | checkClassEntry = obfOuterClassEntry; | ||
| 573 | } else { | ||
| 574 | break; | ||
| 575 | } | ||
| 576 | } | ||
| 577 | |||
| 578 | // switch to outer-to-inner order | ||
| 579 | Collections.reverse(obfClassChain); | ||
| 580 | |||
| 581 | return obfClassChain; | ||
| 582 | } | ||
| 583 | } | ||