1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tools.layoutlib.create; 18 19 import com.android.tools.layoutlib.annotations.NotNull; 20 import com.android.tools.layoutlib.annotations.Nullable; 21 import com.android.tools.layoutlib.create.ICreateInfo.MethodReplacer; 22 23 import org.objectweb.asm.AnnotationVisitor; 24 import org.objectweb.asm.Attribute; 25 import org.objectweb.asm.ClassReader; 26 import org.objectweb.asm.ClassVisitor; 27 import org.objectweb.asm.FieldVisitor; 28 import org.objectweb.asm.Label; 29 import org.objectweb.asm.MethodVisitor; 30 import org.objectweb.asm.Type; 31 import org.objectweb.asm.signature.SignatureReader; 32 import org.objectweb.asm.signature.SignatureVisitor; 33 34 import java.io.FileNotFoundException; 35 import java.io.IOException; 36 import java.io.InputStream; 37 import java.util.ArrayList; 38 import java.util.Arrays; 39 import java.util.Enumeration; 40 import java.util.HashMap; 41 import java.util.HashSet; 42 import java.util.List; 43 import java.util.Map; 44 import java.util.Map.Entry; 45 import java.util.Set; 46 import java.util.TreeMap; 47 import java.util.concurrent.ForkJoinPool; 48 import java.util.concurrent.ForkJoinTask; 49 import java.util.function.Consumer; 50 import java.util.regex.Pattern; 51 import java.util.zip.ZipEntry; 52 import java.util.zip.ZipFile; 53 54 /** 55 * Analyzes the input JAR using the ASM java bytecode manipulation library 56 * to list the desired classes and their dependencies. 57 */ 58 public class AsmAnalyzer { 59 60 public static class Result { 61 private final Map<String, ClassReader> mFound; 62 private final Map<String, ClassReader> mDeps; 63 private final Map<String, InputStream> mFilesFound; 64 private final Set<String> mReplaceMethodCallClasses; 65 Result(Map<String, ClassReader> found, Map<String, ClassReader> deps, Map<String, InputStream> filesFound, Set<String> replaceMethodCallClasses)66 private Result(Map<String, ClassReader> found, Map<String, ClassReader> deps, 67 Map<String, InputStream> filesFound, Set<String> replaceMethodCallClasses) { 68 mFound = found; 69 mDeps = deps; 70 mFilesFound = filesFound; 71 mReplaceMethodCallClasses = replaceMethodCallClasses; 72 } 73 getFound()74 public Map<String, ClassReader> getFound() { 75 return mFound; 76 } 77 getDeps()78 public Map<String, ClassReader> getDeps() { 79 return mDeps; 80 } 81 getFilesFound()82 public Map<String, InputStream> getFilesFound() { 83 return mFilesFound; 84 } 85 getReplaceMethodCallClasses()86 public Set<String> getReplaceMethodCallClasses() { 87 return mReplaceMethodCallClasses; 88 } 89 } 90 91 // Note: a bunch of stuff has package-level access for unit tests. Consider it private. 92 93 /** Output logger. */ 94 private final Log mLog; 95 /** The input source JAR to parse. */ 96 private final List<String> mOsSourceJar; 97 /** Keep all classes that derive from these one (these included). */ 98 private final String[] mDeriveFrom; 99 /** Glob patterns of classes to keep, e.g. "com.foo.*" */ 100 private final String[] mIncludeGlobs; 101 /** Glob patterns of classes to exclude.*/ 102 private final String[] mExcludedGlobs; 103 /** Glob patterns of files to keep as is. */ 104 private final String[] mIncludeFileGlobs; 105 /** Internal names of classes that contain method calls that need to be rewritten. */ 106 private final Set<String> mReplaceMethodCallClasses = new HashSet<>(); 107 /** Internal names of method calls that need to be rewritten. */ 108 private final MethodReplacer[] mMethodReplacers; 109 110 /** 111 * Creates a new analyzer. 112 * @param log The log output. 113 * @param osJarPath The input source JARs to parse. 114 * @param deriveFrom Keep all classes that derive from these one (these included). 115 * @param includeGlobs Glob patterns of classes to keep, e.g. "com.foo.*" 116 * ("*" does not matches dots whilst "**" does, "." and "$" are interpreted as-is) 117 * @param includeFileGlobs Glob patterns of files which are kept as is. This is only for files 118 * @param methodReplacers names of method calls that need to be rewritten 119 */ AsmAnalyzer(Log log, List<String> osJarPath, String[] deriveFrom, String[] includeGlobs, String[] excludedGlobs, String[] includeFileGlobs, MethodReplacer[] methodReplacers)120 public AsmAnalyzer(Log log, List<String> osJarPath, String[] deriveFrom, String[] includeGlobs, 121 String[] excludedGlobs, String[] includeFileGlobs, MethodReplacer[] methodReplacers) { 122 mLog = log; 123 mOsSourceJar = osJarPath != null ? osJarPath : new ArrayList<>(); 124 mDeriveFrom = deriveFrom != null ? deriveFrom : new String[0]; 125 mIncludeGlobs = includeGlobs != null ? includeGlobs : new String[0]; 126 mExcludedGlobs = excludedGlobs != null ? excludedGlobs : new String[0]; 127 mIncludeFileGlobs = includeFileGlobs != null ? includeFileGlobs : new String[0]; 128 mMethodReplacers = methodReplacers; 129 } 130 131 /** 132 * Starts the analysis using parameters from the constructor and returns the result. 133 */ 134 @NotNull analyze()135 public Result analyze() throws IOException { 136 Map<String, ClassReader> zipClasses = new TreeMap<>(); 137 Map<String, InputStream> filesFound = new TreeMap<>(); 138 139 parseZip(mOsSourceJar, zipClasses, filesFound); 140 mLog.info("Found %d classes in input JAR%s.", zipClasses.size(), 141 mOsSourceJar.size() > 1 ? "s" : ""); 142 143 Pattern[] includePatterns = Arrays.stream(mIncludeGlobs).parallel() 144 .map(AsmAnalyzer::getPatternFromGlob) 145 .toArray(Pattern[]::new); 146 Pattern[] excludePatterns = Arrays.stream(mExcludedGlobs).parallel() 147 .map(AsmAnalyzer::getPatternFromGlob) 148 .toArray(Pattern[]::new); 149 150 151 Map<String, ClassReader> found = new HashMap<>(); 152 findIncludes(mLog, includePatterns, mDeriveFrom, zipClasses, entry -> { 153 if (!matchesAny(entry.getKey(), excludePatterns)) { 154 found.put(entry.getKey(), entry.getValue()); 155 } 156 }); 157 158 Map<String, ClassReader> deps = new HashMap<>(); 159 findDeps(mLog, zipClasses, found, keepEntry -> { 160 if (!matchesAny(keepEntry.getKey(), excludePatterns)) { 161 found.put(keepEntry.getKey(), keepEntry.getValue()); 162 } 163 }, depEntry -> { 164 if (!matchesAny(depEntry.getKey(), excludePatterns)) { 165 deps.put(depEntry.getKey(), depEntry.getValue()); 166 } 167 }); 168 169 mLog.info("Found %1$d classes to keep, %2$d class dependencies.", 170 found.size(), deps.size()); 171 172 return new Result(found, deps, filesFound, mReplaceMethodCallClasses); 173 } 174 175 /** 176 * Parses a JAR file and adds all the classes found to <code>classes</code> 177 * and all other files to <code>filesFound</code>. 178 * 179 * @param classes The map of class name => ASM ClassReader. Class names are 180 * in the form "android.view.View". 181 * @param filesFound The map of file name => InputStream. The file name is 182 * in the form "android/data/dataFile". 183 */ parseZip(List<String> jarPathList, Map<String, ClassReader> classes, Map<String, InputStream> filesFound)184 void parseZip(List<String> jarPathList, Map<String, ClassReader> classes, 185 Map<String, InputStream> filesFound) throws IOException { 186 if (classes == null || filesFound == null) { 187 return; 188 } 189 190 Pattern[] includeFilePatterns = new Pattern[mIncludeFileGlobs.length]; 191 for (int i = 0; i < mIncludeFileGlobs.length; ++i) { 192 includeFilePatterns[i] = getPatternFromGlob(mIncludeFileGlobs[i]); 193 } 194 195 List<ForkJoinTask<?>> futures = new ArrayList<>(); 196 for (String jarPath : jarPathList) { 197 futures.add(ForkJoinPool.commonPool().submit(() -> { 198 try { 199 ZipFile zip = new ZipFile(jarPath); 200 Enumeration<? extends ZipEntry> entries = zip.entries(); 201 ZipEntry entry; 202 while (entries.hasMoreElements()) { 203 entry = entries.nextElement(); 204 if (entry.getName().endsWith(".class")) { 205 ClassReader cr = new ClassReader(zip.getInputStream(entry)); 206 String className = classReaderToClassName(cr); 207 synchronized (classes) { 208 classes.put(className, cr); 209 } 210 } else { 211 for (Pattern includeFilePattern : includeFilePatterns) { 212 if (includeFilePattern.matcher(entry.getName()).matches()) { 213 synchronized (filesFound) { 214 filesFound.put(entry.getName(), zip.getInputStream(entry)); 215 } 216 break; 217 } 218 } 219 } 220 } 221 } catch (IOException e) { 222 e.printStackTrace(); 223 } 224 })); 225 } 226 227 futures.forEach(ForkJoinTask::join); 228 } 229 230 /** 231 * Utility that returns the fully qualified binary class name for a ClassReader. 232 * E.g. it returns something like android.view.View. 233 */ classReaderToClassName(ClassReader classReader)234 static String classReaderToClassName(ClassReader classReader) { 235 if (classReader == null) { 236 return null; 237 } else { 238 return classReader.getClassName().replace('/', '.'); 239 } 240 } 241 242 /** 243 * Utility that returns the fully qualified binary class name from a path-like FQCN. 244 * E.g. it returns android.view.View from android/view/View. 245 */ internalToBinaryClassName(String className)246 private static String internalToBinaryClassName(String className) { 247 if (className == null) { 248 return null; 249 } else { 250 return className.replace('/', '.'); 251 } 252 } 253 matchesAny(@ullable String className, @NotNull Pattern[] patterns)254 private static boolean matchesAny(@Nullable String className, @NotNull Pattern[] patterns) { 255 for (int i = 0; i < patterns.length; i++) { 256 if (patterns[i].matcher(className).matches()) { 257 return true; 258 } 259 } 260 261 int dollarIdx = className.indexOf('$'); 262 if (dollarIdx != -1) { 263 // This is an inner class, if the outer class matches, we also consider this a match 264 return matchesAny(className.substring(0, dollarIdx), patterns); 265 } 266 267 return false; 268 } 269 270 /** 271 * Process the "includes" arrays. 272 * <p/> 273 * This updates the in_out_found map. 274 */ findIncludes(@otNull Log log, @NotNull Pattern[] includePatterns, @NotNull String[] deriveFrom, @NotNull Map<String, ClassReader> zipClasses, @NotNull Consumer<Entry<String, ClassReader>> newInclude)275 private static void findIncludes(@NotNull Log log, @NotNull Pattern[] includePatterns, 276 @NotNull String[] deriveFrom, @NotNull Map<String, ClassReader> zipClasses, 277 @NotNull Consumer<Entry<String, ClassReader>> newInclude) throws FileNotFoundException { 278 TreeMap<String, ClassReader> found = new TreeMap<>(); 279 280 log.debug("Find classes to include."); 281 282 zipClasses.entrySet().stream() 283 .filter(entry -> matchesAny(entry.getKey(), includePatterns)) 284 .forEach(entry -> found.put(entry.getKey(), entry.getValue())); 285 286 for (String entry : deriveFrom) { 287 findClassesDerivingFrom(entry, zipClasses, found); 288 } 289 290 found.entrySet().forEach(newInclude); 291 } 292 293 294 /** 295 * Uses ASM to find the class reader for the given FQCN class name. 296 * If found, insert it in the in_out_found map. 297 * Returns the class reader object. 298 */ findClass(String className, Map<String, ClassReader> zipClasses, Map<String, ClassReader> inOutFound)299 static ClassReader findClass(String className, Map<String, ClassReader> zipClasses, 300 Map<String, ClassReader> inOutFound) throws FileNotFoundException { 301 ClassReader classReader = zipClasses.get(className); 302 if (classReader == null) { 303 throw new FileNotFoundException(String.format("Class %s not found by ASM", className)); 304 } 305 306 inOutFound.put(className, classReader); 307 return classReader; 308 } 309 310 getPatternFromGlob(String globPattern)311 static Pattern getPatternFromGlob(String globPattern) { 312 // transforms the glob pattern in a regexp: 313 // - escape "." with "\." 314 // - replace "*" by "[^.]*" 315 // - escape "$" with "\$" 316 // - add end-of-line match $ 317 globPattern = globPattern.replaceAll("\\$", "\\\\\\$"); 318 globPattern = globPattern.replaceAll("\\.", "\\\\."); 319 // prevent ** from being altered by the next rule, then process the * rule and finally 320 // the real ** rule (which is now @) 321 globPattern = globPattern.replaceAll("\\*\\*", "@"); 322 globPattern = globPattern.replaceAll("\\*", "[^.]*"); 323 globPattern = globPattern.replaceAll("@", ".*"); 324 globPattern += "$"; 325 326 return Pattern.compile(globPattern); 327 } 328 329 /** 330 * Checks all the classes defined in the JarClassName instance and uses BCEL to 331 * determine if they are derived from the given FQCN super class name. 332 * Inserts the super class and all the class objects found in the map. 333 */ findClassesDerivingFrom(String super_name, Map<String, ClassReader> zipClasses, Map<String, ClassReader> inOutFound)334 static void findClassesDerivingFrom(String super_name, Map<String, ClassReader> zipClasses, 335 Map<String, ClassReader> inOutFound) throws FileNotFoundException { 336 findClass(super_name, zipClasses, inOutFound); 337 338 for (Entry<String, ClassReader> entry : zipClasses.entrySet()) { 339 String className = entry.getKey(); 340 if (super_name.equals(className)) { 341 continue; 342 } 343 ClassReader classReader = entry.getValue(); 344 ClassReader parent_cr = classReader; 345 while (parent_cr != null) { 346 String parent_name = internalToBinaryClassName(parent_cr.getSuperName()); 347 if (parent_name == null) { 348 // not found 349 break; 350 } else if (super_name.equals(parent_name)) { 351 inOutFound.put(className, classReader); 352 break; 353 } 354 parent_cr = zipClasses.get(parent_name); 355 } 356 } 357 } 358 359 /** 360 * Instantiates a new DependencyVisitor. Useful for unit tests. 361 */ getVisitor(Map<String, ClassReader> zipClasses, Map<String, ClassReader> inKeep, Map<String, ClassReader> outKeep, Map<String, ClassReader> inDeps, Map<String, ClassReader> outDeps)362 DependencyVisitor getVisitor(Map<String, ClassReader> zipClasses, 363 Map<String, ClassReader> inKeep, 364 Map<String, ClassReader> outKeep, 365 Map<String, ClassReader> inDeps, 366 Map<String, ClassReader> outDeps) { 367 return new DependencyVisitor(zipClasses, inKeep, outKeep, inDeps, outDeps); 368 } 369 370 /** 371 * Finds all dependencies for all classes in keepClasses which are also 372 * listed in zipClasses. Returns a map of all the dependencies found. 373 */ findDeps(Log log, Map<String, ClassReader> zipClasses, Map<String, ClassReader> inOutKeepClasses, Consumer<Entry<String, ClassReader>> newKeep, Consumer<Entry<String, ClassReader>> newDep)374 void findDeps(Log log, 375 Map<String, ClassReader> zipClasses, 376 Map<String, ClassReader> inOutKeepClasses, 377 Consumer<Entry<String, ClassReader>> newKeep, 378 Consumer<Entry<String, ClassReader>> newDep) { 379 380 TreeMap<String, ClassReader> keep = new TreeMap<>(inOutKeepClasses); 381 TreeMap<String, ClassReader> deps = new TreeMap<>(); 382 TreeMap<String, ClassReader> new_deps = new TreeMap<>(); 383 TreeMap<String, ClassReader> new_keep = new TreeMap<>(); 384 TreeMap<String, ClassReader> temp = new TreeMap<>(); 385 386 DependencyVisitor visitor = getVisitor(zipClasses, 387 keep, new_keep, 388 deps, new_deps); 389 390 for (ClassReader cr : inOutKeepClasses.values()) { 391 visitor.setClassName(cr.getClassName()); 392 cr.accept(visitor, 0 /* flags */); 393 } 394 395 while (new_deps.size() > 0 || new_keep.size() > 0) { 396 new_deps.entrySet().forEach(newDep); 397 new_keep.entrySet().forEach(newKeep); 398 keep.putAll(new_keep); 399 deps.putAll(new_deps); 400 401 temp.clear(); 402 temp.putAll(new_deps); 403 temp.putAll(new_keep); 404 new_deps.clear(); 405 new_keep.clear(); 406 log.debug("Found %1$d to keep, %2$d dependencies.", 407 inOutKeepClasses.size(), deps.size()); 408 409 for (ClassReader cr : temp.values()) { 410 visitor.setClassName(cr.getClassName()); 411 cr.accept(visitor, 0 /* flags */); 412 } 413 } 414 } 415 416 // ---------------------------------- 417 418 /** 419 * Visitor to collect all the type dependencies from a class. 420 */ 421 public class DependencyVisitor extends ClassVisitor { 422 423 /** All classes found in the source JAR. */ 424 private final Map<String, ClassReader> mZipClasses; 425 /** Classes from which dependencies are to be found. */ 426 private final Map<String, ClassReader> mInKeep; 427 /** Dependencies already known. */ 428 private final Map<String, ClassReader> mInDeps; 429 /** New dependencies found by this visitor. */ 430 private final Map<String, ClassReader> mOutDeps; 431 /** New classes to keep as-is found by this visitor. */ 432 private final Map<String, ClassReader> mOutKeep; 433 434 private String mClassName; 435 436 /** 437 * Creates a new visitor that will find all the dependencies for the visited class. 438 * Types which are already in the zipClasses, keepClasses or inDeps are not marked. 439 * New dependencies are marked in outDeps. 440 * 441 * @param zipClasses All classes found in the source JAR. 442 * @param inKeep Classes from which dependencies are to be found. 443 * @param inDeps Dependencies already known. 444 * @param outDeps New dependencies found by this visitor. 445 */ DependencyVisitor(Map<String, ClassReader> zipClasses, Map<String, ClassReader> inKeep, Map<String, ClassReader> outKeep, Map<String,ClassReader> inDeps, Map<String,ClassReader> outDeps)446 public DependencyVisitor(Map<String, ClassReader> zipClasses, 447 Map<String, ClassReader> inKeep, 448 Map<String, ClassReader> outKeep, 449 Map<String,ClassReader> inDeps, 450 Map<String,ClassReader> outDeps) { 451 super(Main.ASM_VERSION); 452 mZipClasses = zipClasses; 453 mInKeep = inKeep; 454 mOutKeep = outKeep; 455 mInDeps = inDeps; 456 mOutDeps = outDeps; 457 } 458 setClassName(String className)459 private void setClassName(String className) { 460 mClassName = className; 461 } 462 463 /** 464 * Considers the given class name as a dependency. 465 * If it does, add to the mOutDeps map. 466 */ considerName(String className)467 public void considerName(String className) { 468 if (className == null) { 469 return; 470 } 471 472 className = internalToBinaryClassName(className); 473 474 // exclude classes that have already been found or are marked to be excluded 475 if (mInKeep.containsKey(className) || 476 mOutKeep.containsKey(className) || 477 mInDeps.containsKey(className) || 478 mOutDeps.containsKey(className)) { 479 return; 480 } 481 482 // exclude classes that are not part of the JAR file being examined 483 ClassReader cr = mZipClasses.get(className); 484 if (cr == null) { 485 return; 486 } 487 488 try { 489 // exclude classes that are part of the default JRE (the one executing this program) 490 if (className.startsWith("java.") || className.startsWith("sun.") || 491 getClass().getClassLoader().getParent().loadClass(className) != null) { 492 return; 493 } 494 } catch (ClassNotFoundException e) { 495 // ignore 496 } 497 498 // accept this class: 499 // - android classes are added to dependencies 500 // - non-android classes are added to the list of classes to keep as-is (they don't need 501 // to be stubbed). 502 if (className.contains("android")) { // TODO make configurable 503 mOutDeps.put(className, cr); 504 } else { 505 mOutKeep.put(className, cr); 506 } 507 } 508 509 /** 510 * Considers this array of names using considerName(). 511 */ considerNames(String[] classNames)512 public void considerNames(String[] classNames) { 513 if (classNames != null) { 514 for (String className : classNames) { 515 considerName(className); 516 } 517 } 518 } 519 520 /** 521 * Considers this signature or type signature by invoking the {@link SignatureVisitor} 522 * on it. 523 */ considerSignature(String signature)524 public void considerSignature(String signature) { 525 if (signature != null) { 526 SignatureReader sr = new SignatureReader(signature); 527 // SignatureReader.accept will call accessType so we don't really have 528 // to differentiate where the signature comes from. 529 sr.accept(new MySignatureVisitor()); 530 } 531 } 532 533 /** 534 * Considers this {@link Type}. For arrays, the element type is considered. 535 * If the type is an object, its internal name is considered. If it is a method type, 536 * iterate through the argument and return types. 537 */ considerType(Type t)538 public void considerType(Type t) { 539 if (t != null) { 540 if (t.getSort() == Type.ARRAY) { 541 t = t.getElementType(); 542 } 543 if (t.getSort() == Type.OBJECT) { 544 considerName(t.getInternalName()); 545 } 546 if (t.getSort() == Type.METHOD) { 547 for (Type type : t.getArgumentTypes()) { 548 considerType(type); 549 } 550 considerType(t.getReturnType()); 551 } 552 } 553 } 554 555 /** 556 * Considers a descriptor string. The descriptor is converted to a {@link Type} 557 * and then considerType() is invoked. 558 */ considerDesc(String desc)559 public void considerDesc(String desc) { 560 if (desc != null) { 561 try { 562 Type t = Type.getType(desc); 563 considerType(t); 564 } catch (ArrayIndexOutOfBoundsException e) { 565 // ignore, not a valid type. 566 } 567 } 568 } 569 570 // --------------------------------------------------- 571 // --- ClassVisitor, FieldVisitor 572 // --------------------------------------------------- 573 574 // Visits a class header 575 @Override visit(int version, int access, String name, String signature, String superName, String[] interfaces)576 public void visit(int version, int access, String name, 577 String signature, String superName, String[] interfaces) { 578 // signature is the signature of this class. May be null if the class is not a generic 579 // one, and does not extend or implement generic classes or interfaces. 580 581 if (signature != null) { 582 considerSignature(signature); 583 } 584 585 // superName is the internal of name of the super class (see getInternalName). 586 // For interfaces, the super class is Object. May be null but only for the Object class. 587 considerName(superName); 588 589 // interfaces is the internal names of the class's interfaces (see getInternalName). 590 // May be null. 591 considerNames(interfaces); 592 } 593 594 595 @Override visitAnnotation(String desc, boolean visible)596 public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 597 // desc is the class descriptor of the annotation class. 598 considerDesc(desc); 599 return new MyAnnotationVisitor(); 600 } 601 602 @Override visitAttribute(Attribute attr)603 public void visitAttribute(Attribute attr) { 604 // pass 605 } 606 607 // Visits the end of a class 608 @Override visitEnd()609 public void visitEnd() { 610 // pass 611 } 612 613 private class MyFieldVisitor extends FieldVisitor { 614 MyFieldVisitor()615 public MyFieldVisitor() { 616 super(Main.ASM_VERSION); 617 } 618 619 @Override visitAnnotation(String desc, boolean visible)620 public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 621 // desc is the class descriptor of the annotation class. 622 considerDesc(desc); 623 return new MyAnnotationVisitor(); 624 } 625 626 @Override visitAttribute(Attribute attr)627 public void visitAttribute(Attribute attr) { 628 // pass 629 } 630 631 // Visits the end of a class 632 @Override visitEnd()633 public void visitEnd() { 634 // pass 635 } 636 } 637 638 @Override visitField(int access, String name, String desc, String signature, Object value)639 public FieldVisitor visitField(int access, String name, String desc, 640 String signature, Object value) { 641 // desc is the field's descriptor (see Type). 642 considerDesc(desc); 643 644 // signature is the field's signature. May be null if the field's type does not use 645 // generic types. 646 considerSignature(signature); 647 648 return new MyFieldVisitor(); 649 } 650 651 @Override visitInnerClass(String name, String outerName, String innerName, int access)652 public void visitInnerClass(String name, String outerName, String innerName, int access) { 653 // name is the internal name of an inner class (see getInternalName). 654 considerName(name); 655 } 656 657 @Override visitMethod(int access, String name, String desc, String signature, String[] exceptions)658 public MethodVisitor visitMethod(int access, String name, String desc, 659 String signature, String[] exceptions) { 660 // desc is the method's descriptor (see Type). 661 considerDesc(desc); 662 // signature is the method's signature. May be null if the method parameters, return 663 // type and exceptions do not use generic types. 664 considerSignature(signature); 665 666 return new MyMethodVisitor(mClassName); 667 } 668 669 @Override visitOuterClass(String owner, String name, String desc)670 public void visitOuterClass(String owner, String name, String desc) { 671 // pass 672 } 673 674 @Override visitSource(String source, String debug)675 public void visitSource(String source, String debug) { 676 // pass 677 } 678 679 680 // --------------------------------------------------- 681 // --- MethodVisitor 682 // --------------------------------------------------- 683 684 private class MyMethodVisitor extends MethodVisitor { 685 686 private String mOwnerClass; 687 MyMethodVisitor(String ownerClass)688 public MyMethodVisitor(String ownerClass) { 689 super(Main.ASM_VERSION); 690 mOwnerClass = ownerClass; 691 } 692 693 694 @Override visitAnnotationDefault()695 public AnnotationVisitor visitAnnotationDefault() { 696 return new MyAnnotationVisitor(); 697 } 698 699 @Override visitCode()700 public void visitCode() { 701 // pass 702 } 703 704 // field instruction 705 @Override visitFieldInsn(int opcode, String owner, String name, String desc)706 public void visitFieldInsn(int opcode, String owner, String name, String desc) { 707 // owner is the class that declares the field. 708 considerName(owner); 709 // desc is the field's descriptor (see Type). 710 considerDesc(desc); 711 } 712 713 @Override visitFrame(int type, int local, Object[] local2, int stack, Object[] stack2)714 public void visitFrame(int type, int local, Object[] local2, int stack, Object[] stack2) { 715 // pass 716 } 717 718 @Override visitIincInsn(int var, int increment)719 public void visitIincInsn(int var, int increment) { 720 // pass -- an IINC instruction 721 } 722 723 @Override visitInsn(int opcode)724 public void visitInsn(int opcode) { 725 // pass -- a zero operand instruction 726 } 727 728 @Override visitIntInsn(int opcode, int operand)729 public void visitIntInsn(int opcode, int operand) { 730 // pass -- a single int operand instruction 731 } 732 733 @Override visitJumpInsn(int opcode, Label label)734 public void visitJumpInsn(int opcode, Label label) { 735 // pass -- a jump instruction 736 } 737 738 @Override visitLabel(Label label)739 public void visitLabel(Label label) { 740 // pass -- a label target 741 } 742 743 // instruction to load a constant from the stack 744 @Override visitLdcInsn(Object cst)745 public void visitLdcInsn(Object cst) { 746 if (cst instanceof Type) { 747 considerType((Type) cst); 748 } 749 } 750 751 @Override visitLineNumber(int line, Label start)752 public void visitLineNumber(int line, Label start) { 753 // pass 754 } 755 756 @Override visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index)757 public void visitLocalVariable(String name, String desc, 758 String signature, Label start, Label end, int index) { 759 // desc is the type descriptor of this local variable. 760 considerDesc(desc); 761 // signature is the type signature of this local variable. May be null if the local 762 // variable type does not use generic types. 763 considerSignature(signature); 764 } 765 766 @Override visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels)767 public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { 768 // pass -- a lookup switch instruction 769 } 770 771 @Override visitMaxs(int maxStack, int maxLocals)772 public void visitMaxs(int maxStack, int maxLocals) { 773 // pass 774 } 775 776 /** 777 * If a method some.package.Class.Method(args) is called from some.other.Class, 778 * @param owner some/package/Class 779 * @param name Method 780 * @param desc (args)returnType 781 * @param sourceClass some/other/Class 782 * @return if the method invocation needs to be replaced by some other class. 783 */ isReplacementNeeded(String owner, String name, String desc, String sourceClass)784 private boolean isReplacementNeeded(String owner, String name, String desc, 785 String sourceClass) { 786 for (MethodReplacer replacer : mMethodReplacers) { 787 if (replacer.isNeeded(owner, name, desc, sourceClass)) { 788 return true; 789 } 790 } 791 return false; 792 } 793 794 // instruction that invokes a method 795 @Override visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf)796 public void visitMethodInsn(int opcode, String owner, String name, String desc, 797 boolean itf) { 798 799 // owner is the internal name of the method's owner class 800 considerName(owner); 801 // desc is the method's descriptor (see Type). 802 considerDesc(desc); 803 804 805 // Check if method needs to replaced by a call to a different method. 806 if (isReplacementNeeded(owner, name, desc, mOwnerClass)) { 807 mReplaceMethodCallClasses.add(mOwnerClass); 808 } 809 } 810 811 // instruction multianewarray, whatever that is 812 @Override visitMultiANewArrayInsn(String desc, int dims)813 public void visitMultiANewArrayInsn(String desc, int dims) { 814 815 // desc an array type descriptor. 816 considerDesc(desc); 817 } 818 819 @Override visitParameterAnnotation(int parameter, String desc, boolean visible)820 public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, 821 boolean visible) { 822 // desc is the class descriptor of the annotation class. 823 considerDesc(desc); 824 return new MyAnnotationVisitor(); 825 } 826 827 @Override visitTableSwitchInsn(int min, int max, Label dflt, Label... labels)828 public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { 829 // pass -- table switch instruction 830 831 } 832 833 @Override visitTryCatchBlock(Label start, Label end, Label handler, String type)834 public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { 835 // type is the internal name of the type of exceptions handled by the handler, 836 // or null to catch any exceptions (for "finally" blocks). 837 considerName(type); 838 } 839 840 // type instruction 841 @Override visitTypeInsn(int opcode, String type)842 public void visitTypeInsn(int opcode, String type) { 843 // type is the operand of the instruction to be visited. This operand must be the 844 // internal name of an object or array class. 845 considerName(type); 846 } 847 848 @Override visitVarInsn(int opcode, int var)849 public void visitVarInsn(int opcode, int var) { 850 // pass -- local variable instruction 851 } 852 } 853 854 private class MySignatureVisitor extends SignatureVisitor { 855 MySignatureVisitor()856 public MySignatureVisitor() { 857 super(Main.ASM_VERSION); 858 } 859 860 // --------------------------------------------------- 861 // --- SignatureVisitor 862 // --------------------------------------------------- 863 864 private String mCurrentSignatureClass = null; 865 866 // Starts the visit of a signature corresponding to a class or interface type 867 @Override visitClassType(String name)868 public void visitClassType(String name) { 869 mCurrentSignatureClass = name; 870 considerName(name); 871 } 872 873 // Visits an inner class 874 @Override visitInnerClassType(String name)875 public void visitInnerClassType(String name) { 876 if (mCurrentSignatureClass != null) { 877 mCurrentSignatureClass += "$" + name; 878 considerName(mCurrentSignatureClass); 879 } 880 } 881 882 @Override visitArrayType()883 public SignatureVisitor visitArrayType() { 884 return new MySignatureVisitor(); 885 } 886 887 @Override visitBaseType(char descriptor)888 public void visitBaseType(char descriptor) { 889 // pass -- a primitive type, ignored 890 } 891 892 @Override visitClassBound()893 public SignatureVisitor visitClassBound() { 894 return new MySignatureVisitor(); 895 } 896 897 @Override visitExceptionType()898 public SignatureVisitor visitExceptionType() { 899 return new MySignatureVisitor(); 900 } 901 902 @Override visitFormalTypeParameter(String name)903 public void visitFormalTypeParameter(String name) { 904 // pass 905 } 906 907 @Override visitInterface()908 public SignatureVisitor visitInterface() { 909 return new MySignatureVisitor(); 910 } 911 912 @Override visitInterfaceBound()913 public SignatureVisitor visitInterfaceBound() { 914 return new MySignatureVisitor(); 915 } 916 917 @Override visitParameterType()918 public SignatureVisitor visitParameterType() { 919 return new MySignatureVisitor(); 920 } 921 922 @Override visitReturnType()923 public SignatureVisitor visitReturnType() { 924 return new MySignatureVisitor(); 925 } 926 927 @Override visitSuperclass()928 public SignatureVisitor visitSuperclass() { 929 return new MySignatureVisitor(); 930 } 931 932 @Override visitTypeArgument(char wildcard)933 public SignatureVisitor visitTypeArgument(char wildcard) { 934 return new MySignatureVisitor(); 935 } 936 937 @Override visitTypeVariable(String name)938 public void visitTypeVariable(String name) { 939 // pass 940 } 941 942 @Override visitTypeArgument()943 public void visitTypeArgument() { 944 // pass 945 } 946 } 947 948 949 // --------------------------------------------------- 950 // --- AnnotationVisitor 951 // --------------------------------------------------- 952 953 private class MyAnnotationVisitor extends AnnotationVisitor { 954 MyAnnotationVisitor()955 public MyAnnotationVisitor() { 956 super(Main.ASM_VERSION); 957 } 958 959 // Visits a primitive value of an annotation 960 @Override visit(String name, Object value)961 public void visit(String name, Object value) { 962 // value is the actual value, whose type must be Byte, Boolean, Character, Short, 963 // Integer, Long, Float, Double, String or Type 964 if (value instanceof Type) { 965 considerType((Type) value); 966 } 967 } 968 969 @Override visitAnnotation(String name, String desc)970 public AnnotationVisitor visitAnnotation(String name, String desc) { 971 // desc is the class descriptor of the nested annotation class. 972 considerDesc(desc); 973 return new MyAnnotationVisitor(); 974 } 975 976 @Override visitArray(String name)977 public AnnotationVisitor visitArray(String name) { 978 return new MyAnnotationVisitor(); 979 } 980 981 @Override visitEnum(String name, String desc, String value)982 public void visitEnum(String name, String desc, String value) { 983 // desc is the class descriptor of the enumeration class. 984 considerDesc(desc); 985 } 986 } 987 } 988 } 989