1/* 2 * Copyright (C) 2012 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 17package com.android.tools.layoutlib.create; 18 19import com.android.tools.layoutlib.annotations.VisibleForTesting; 20import com.android.tools.layoutlib.annotations.VisibleForTesting.Visibility; 21 22import org.objectweb.asm.AnnotationVisitor; 23import org.objectweb.asm.Attribute; 24import org.objectweb.asm.ClassReader; 25import org.objectweb.asm.ClassVisitor; 26import org.objectweb.asm.FieldVisitor; 27import org.objectweb.asm.Label; 28import org.objectweb.asm.MethodVisitor; 29import org.objectweb.asm.Opcodes; 30import org.objectweb.asm.Type; 31import org.objectweb.asm.signature.SignatureReader; 32import org.objectweb.asm.signature.SignatureVisitor; 33 34import java.io.IOException; 35import java.util.ArrayList; 36import java.util.Enumeration; 37import java.util.List; 38import java.util.Map; 39import java.util.Map.Entry; 40import java.util.Set; 41import java.util.TreeMap; 42import java.util.TreeSet; 43import java.util.zip.ZipEntry; 44import java.util.zip.ZipFile; 45 46/** 47 * Analyzes the input JAR using the ASM java bytecode manipulation library 48 * to list the classes and their dependencies. A "dependency" is a class 49 * used by another class. 50 */ 51public class DependencyFinder { 52 53 // Note: a bunch of stuff has package-level access for unit tests. Consider it private. 54 55 /** Output logger. */ 56 private final Log mLog; 57 58 /** 59 * Creates a new analyzer. 60 * 61 * @param log The log output. 62 */ 63 public DependencyFinder(Log log) { 64 mLog = log; 65 } 66 67 /** 68 * Starts the analysis using parameters from the constructor. 69 * 70 * @param osJarPath The input source JARs to parse. 71 * @return A pair: [0]: map { class FQCN => set of FQCN class dependencies }. 72 * [1]: map { missing class FQCN => set of FQCN class that uses it. } 73 */ 74 public List<Map<String, Set<String>>> findDeps(List<String> osJarPath) throws IOException { 75 76 Map<String, ClassReader> zipClasses = parseZip(osJarPath); 77 mLog.info("Found %d classes in input JAR%s.", 78 zipClasses.size(), 79 osJarPath.size() > 1 ? "s" : ""); 80 81 Map<String, Set<String>> deps = findClassesDeps(zipClasses); 82 83 Map<String, Set<String>> missing = findMissingClasses(deps, zipClasses.keySet()); 84 85 List<Map<String, Set<String>>> result = new ArrayList<Map<String,Set<String>>>(2); 86 result.add(deps); 87 result.add(missing); 88 return result; 89 } 90 91 /** 92 * Prints dependencies to the current logger, found stuff and missing stuff. 93 */ 94 public void printAllDeps(List<Map<String, Set<String>>> result) { 95 assert result.size() == 2; 96 Map<String, Set<String>> deps = result.get(0); 97 Map<String, Set<String>> missing = result.get(1); 98 99 // Print all dependences found in the format: 100 // +Found: <FQCN from zip> 101 // uses: FQCN 102 103 mLog.info("++++++ %d Entries found in source JARs", deps.size()); 104 mLog.info(""); 105 106 for (Entry<String, Set<String>> entry : deps.entrySet()) { 107 mLog.info( "+Found : %s", entry.getKey()); 108 for (String dep : entry.getValue()) { 109 mLog.info(" uses: %s", dep); 110 } 111 112 mLog.info(""); 113 } 114 115 116 // Now print all missing dependences in the format: 117 // -Missing <FQCN>: 118 // used by: <FQCN> 119 120 mLog.info(""); 121 mLog.info("------ %d Entries missing from source JARs", missing.size()); 122 mLog.info(""); 123 124 for (Entry<String, Set<String>> entry : missing.entrySet()) { 125 mLog.info( "-Missing : %s", entry.getKey()); 126 for (String dep : entry.getValue()) { 127 mLog.info(" used by: %s", dep); 128 } 129 130 mLog.info(""); 131 } 132 } 133 134 /** 135 * Prints only a summary of the missing dependencies to the current logger. 136 */ 137 public void printMissingDeps(List<Map<String, Set<String>>> result) { 138 assert result.size() == 2; 139 @SuppressWarnings("unused") Map<String, Set<String>> deps = result.get(0); 140 Map<String, Set<String>> missing = result.get(1); 141 142 for (String fqcn : missing.keySet()) { 143 mLog.info("%s", fqcn); 144 } 145 } 146 147 // ---------------- 148 149 /** 150 * Parses a JAR file and returns a list of all classes founds using a map 151 * class name => ASM ClassReader. Class names are in the form "android.view.View". 152 */ 153 Map<String,ClassReader> parseZip(List<String> jarPathList) throws IOException { 154 TreeMap<String, ClassReader> classes = new TreeMap<String, ClassReader>(); 155 156 for (String jarPath : jarPathList) { 157 ZipFile zip = new ZipFile(jarPath); 158 Enumeration<? extends ZipEntry> entries = zip.entries(); 159 ZipEntry entry; 160 while (entries.hasMoreElements()) { 161 entry = entries.nextElement(); 162 if (entry.getName().endsWith(".class")) { 163 ClassReader cr = new ClassReader(zip.getInputStream(entry)); 164 String className = classReaderToClassName(cr); 165 classes.put(className, cr); 166 } 167 } 168 } 169 170 return classes; 171 } 172 173 /** 174 * Utility that returns the fully qualified binary class name for a ClassReader. 175 * E.g. it returns something like android.view.View. 176 */ 177 static String classReaderToClassName(ClassReader classReader) { 178 if (classReader == null) { 179 return null; 180 } else { 181 return classReader.getClassName().replace('/', '.'); 182 } 183 } 184 185 /** 186 * Utility that returns the fully qualified binary class name from a path-like FQCN. 187 * E.g. it returns android.view.View from android/view/View. 188 */ 189 static String internalToBinaryClassName(String className) { 190 if (className == null) { 191 return null; 192 } else { 193 return className.replace('/', '.'); 194 } 195 } 196 197 /** 198 * Finds all dependencies for all classes in keepClasses which are also 199 * listed in zipClasses. Returns a map of all the dependencies found. 200 */ 201 Map<String, Set<String>> findClassesDeps(Map<String, ClassReader> zipClasses) { 202 203 // The dependencies that we'll collect. 204 // It's a map Class name => uses class names. 205 Map<String, Set<String>> dependencyMap = new TreeMap<String, Set<String>>(); 206 207 DependencyVisitor visitor = getVisitor(); 208 209 int count = 0; 210 try { 211 for (Entry<String, ClassReader> entry : zipClasses.entrySet()) { 212 String name = entry.getKey(); 213 214 TreeSet<String> set = new TreeSet<String>(); 215 dependencyMap.put(name, set); 216 visitor.setDependencySet(set); 217 218 ClassReader cr = entry.getValue(); 219 cr.accept(visitor, 0 /* flags */); 220 221 visitor.setDependencySet(null); 222 223 mLog.debugNoln("Visited %d classes\r", ++count); 224 } 225 } finally { 226 mLog.debugNoln("\n"); 227 } 228 229 return dependencyMap; 230 } 231 232 /** 233 * Computes which classes FQCN were found as dependencies that are NOT listed 234 * in the original JAR classes. 235 * 236 * @param deps The map { FQCN => dependencies[] } returned by {@link #findClassesDeps(Map)}. 237 * @param zipClasses The set of all classes FQCN found in the JAR files. 238 * @return A map { FQCN not found in the zipClasses => classes using it } 239 */ 240 private Map<String, Set<String>> findMissingClasses( 241 Map<String, Set<String>> deps, 242 Set<String> zipClasses) { 243 Map<String, Set<String>> missing = new TreeMap<String, Set<String>>(); 244 245 for (Entry<String, Set<String>> entry : deps.entrySet()) { 246 String name = entry.getKey(); 247 248 for (String dep : entry.getValue()) { 249 if (!zipClasses.contains(dep)) { 250 // This dependency doesn't exist in the zip classes. 251 Set<String> set = missing.get(dep); 252 if (set == null) { 253 set = new TreeSet<String>(); 254 missing.put(dep, set); 255 } 256 set.add(name); 257 } 258 } 259 260 } 261 262 return missing; 263 } 264 265 266 // ---------------------------------- 267 268 /** 269 * Instantiates a new DependencyVisitor. Useful for unit tests. 270 */ 271 @VisibleForTesting(visibility=Visibility.PRIVATE) 272 DependencyVisitor getVisitor() { 273 return new DependencyVisitor(); 274 } 275 276 /** 277 * Visitor to collect all the type dependencies from a class. 278 */ 279 public class DependencyVisitor extends ClassVisitor { 280 281 private Set<String> mCurrentDepSet; 282 283 /** 284 * Creates a new visitor that will find all the dependencies for the visited class. 285 */ 286 public DependencyVisitor() { 287 super(Opcodes.ASM4); 288 } 289 290 /** 291 * Sets the {@link Set} where to record direct dependencies for this class. 292 * This will change before each {@link ClassReader#accept(ClassVisitor, int)} call. 293 */ 294 public void setDependencySet(Set<String> set) { 295 mCurrentDepSet = set; 296 } 297 298 /** 299 * Considers the given class name as a dependency. 300 */ 301 public void considerName(String className) { 302 if (className == null) { 303 return; 304 } 305 306 className = internalToBinaryClassName(className); 307 308 try { 309 // exclude classes that are part of the default JRE (the one executing this program) 310 if (getClass().getClassLoader().loadClass(className) != null) { 311 return; 312 } 313 } catch (ClassNotFoundException e) { 314 // ignore 315 } 316 317 // Add it to the dependency set for the currently visited class, as needed. 318 assert mCurrentDepSet != null; 319 if (mCurrentDepSet != null) { 320 mCurrentDepSet.add(className); 321 } 322 } 323 324 /** 325 * Considers this array of names using considerName(). 326 */ 327 public void considerNames(String[] classNames) { 328 if (classNames != null) { 329 for (String className : classNames) { 330 considerName(className); 331 } 332 } 333 } 334 335 /** 336 * Considers this signature or type signature by invoking the {@link SignatureVisitor} 337 * on it. 338 */ 339 public void considerSignature(String signature) { 340 if (signature != null) { 341 SignatureReader sr = new SignatureReader(signature); 342 // SignatureReader.accept will call accessType so we don't really have 343 // to differentiate where the signature comes from. 344 sr.accept(new MySignatureVisitor()); 345 } 346 } 347 348 /** 349 * Considers this {@link Type}. For arrays, the element type is considered. 350 * If the type is an object, it's internal name is considered. 351 */ 352 public void considerType(Type t) { 353 if (t != null) { 354 if (t.getSort() == Type.ARRAY) { 355 t = t.getElementType(); 356 } 357 if (t.getSort() == Type.OBJECT) { 358 considerName(t.getInternalName()); 359 } 360 } 361 } 362 363 /** 364 * Considers a descriptor string. The descriptor is converted to a {@link Type} 365 * and then considerType() is invoked. 366 */ 367 public boolean considerDesc(String desc) { 368 if (desc != null) { 369 try { 370 if (desc.length() > 0 && desc.charAt(0) == '(') { 371 // This is a method descriptor with arguments and a return type. 372 Type t = Type.getReturnType(desc); 373 considerType(t); 374 375 for (Type arg : Type.getArgumentTypes(desc)) { 376 considerType(arg); 377 } 378 379 } else { 380 Type t = Type.getType(desc); 381 considerType(t); 382 } 383 return true; 384 } catch (ArrayIndexOutOfBoundsException e) { 385 // ignore, not a valid type. 386 } 387 } 388 return false; 389 } 390 391 392 // --------------------------------------------------- 393 // --- ClassVisitor, FieldVisitor 394 // --------------------------------------------------- 395 396 // Visits a class header 397 @Override 398 public void visit(int version, int access, String name, 399 String signature, String superName, String[] interfaces) { 400 // signature is the signature of this class. May be null if the class is not a generic 401 // one, and does not extend or implement generic classes or interfaces. 402 403 if (signature != null) { 404 considerSignature(signature); 405 } 406 407 // superName is the internal of name of the super class (see getInternalName). 408 // For interfaces, the super class is Object. May be null but only for the Object class. 409 considerName(superName); 410 411 // interfaces is the internal names of the class's interfaces (see getInternalName). 412 // May be null. 413 considerNames(interfaces); 414 } 415 416 417 @Override 418 public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 419 // desc is the class descriptor of the annotation class. 420 considerDesc(desc); 421 return new MyAnnotationVisitor(); 422 } 423 424 @Override 425 public void visitAttribute(Attribute attr) { 426 // pass 427 } 428 429 // Visits the end of a class 430 @Override 431 public void visitEnd() { 432 // pass 433 } 434 435 private class MyFieldVisitor extends FieldVisitor { 436 437 public MyFieldVisitor() { 438 super(Opcodes.ASM4); 439 } 440 441 @Override 442 public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 443 // desc is the class descriptor of the annotation class. 444 considerDesc(desc); 445 return new MyAnnotationVisitor(); 446 } 447 448 @Override 449 public void visitAttribute(Attribute attr) { 450 // pass 451 } 452 453 // Visits the end of a class 454 @Override 455 public void visitEnd() { 456 // pass 457 } 458 } 459 460 @Override 461 public FieldVisitor visitField(int access, String name, String desc, 462 String signature, Object value) { 463 // desc is the field's descriptor (see Type). 464 considerDesc(desc); 465 466 // signature is the field's signature. May be null if the field's type does not use 467 // generic types. 468 considerSignature(signature); 469 470 return new MyFieldVisitor(); 471 } 472 473 @Override 474 public void visitInnerClass(String name, String outerName, String innerName, int access) { 475 // name is the internal name of an inner class (see getInternalName). 476 // Note: outerName/innerName seems to be null when we're reading the 477 // _Original_ClassName classes generated by layoutlib_create. 478 if (outerName != null) { 479 considerName(name); 480 } 481 } 482 483 @Override 484 public MethodVisitor visitMethod(int access, String name, String desc, 485 String signature, String[] exceptions) { 486 // desc is the method's descriptor (see Type). 487 considerDesc(desc); 488 // signature is the method's signature. May be null if the method parameters, return 489 // type and exceptions do not use generic types. 490 considerSignature(signature); 491 492 return new MyMethodVisitor(); 493 } 494 495 @Override 496 public void visitOuterClass(String owner, String name, String desc) { 497 // pass 498 } 499 500 @Override 501 public void visitSource(String source, String debug) { 502 // pass 503 } 504 505 506 // --------------------------------------------------- 507 // --- MethodVisitor 508 // --------------------------------------------------- 509 510 private class MyMethodVisitor extends MethodVisitor { 511 512 public MyMethodVisitor() { 513 super(Opcodes.ASM4); 514 } 515 516 517 @Override 518 public AnnotationVisitor visitAnnotationDefault() { 519 return new MyAnnotationVisitor(); 520 } 521 522 @Override 523 public void visitCode() { 524 // pass 525 } 526 527 // field instruction 528 @Override 529 public void visitFieldInsn(int opcode, String owner, String name, String desc) { 530 // name is the field's name. 531 // desc is the field's descriptor (see Type). 532 considerDesc(desc); 533 } 534 535 @Override 536 public void visitFrame(int type, int local, Object[] local2, int stack, Object[] stack2) { 537 // pass 538 } 539 540 @Override 541 public void visitIincInsn(int var, int increment) { 542 // pass -- an IINC instruction 543 } 544 545 @Override 546 public void visitInsn(int opcode) { 547 // pass -- a zero operand instruction 548 } 549 550 @Override 551 public void visitIntInsn(int opcode, int operand) { 552 // pass -- a single int operand instruction 553 } 554 555 @Override 556 public void visitJumpInsn(int opcode, Label label) { 557 // pass -- a jump instruction 558 } 559 560 @Override 561 public void visitLabel(Label label) { 562 // pass -- a label target 563 } 564 565 // instruction to load a constant from the stack 566 @Override 567 public void visitLdcInsn(Object cst) { 568 if (cst instanceof Type) { 569 considerType((Type) cst); 570 } 571 } 572 573 @Override 574 public void visitLineNumber(int line, Label start) { 575 // pass 576 } 577 578 @Override 579 public void visitLocalVariable(String name, String desc, 580 String signature, Label start, Label end, int index) { 581 // desc is the type descriptor of this local variable. 582 considerDesc(desc); 583 // signature is the type signature of this local variable. May be null if the local 584 // variable type does not use generic types. 585 considerSignature(signature); 586 } 587 588 @Override 589 public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { 590 // pass -- a lookup switch instruction 591 } 592 593 @Override 594 public void visitMaxs(int maxStack, int maxLocals) { 595 // pass 596 } 597 598 // instruction that invokes a method 599 @Override 600 public void visitMethodInsn(int opcode, String owner, String name, String desc) { 601 602 // owner is the internal name of the method's owner class 603 if (!considerDesc(owner) && owner.indexOf('/') != -1) { 604 considerName(owner); 605 } 606 // desc is the method's descriptor (see Type). 607 considerDesc(desc); 608 } 609 610 // instruction multianewarray, whatever that is 611 @Override 612 public void visitMultiANewArrayInsn(String desc, int dims) { 613 614 // desc an array type descriptor. 615 considerDesc(desc); 616 } 617 618 @Override 619 public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, 620 boolean visible) { 621 // desc is the class descriptor of the annotation class. 622 considerDesc(desc); 623 return new MyAnnotationVisitor(); 624 } 625 626 @Override 627 public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) { 628 // pass -- table switch instruction 629 630 } 631 632 @Override 633 public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { 634 // type is the internal name of the type of exceptions handled by the handler, 635 // or null to catch any exceptions (for "finally" blocks). 636 considerName(type); 637 } 638 639 // type instruction 640 @Override 641 public void visitTypeInsn(int opcode, String type) { 642 // type is the operand of the instruction to be visited. This operand must be the 643 // internal name of an object or array class. 644 considerName(type); 645 } 646 647 @Override 648 public void visitVarInsn(int opcode, int var) { 649 // pass -- local variable instruction 650 } 651 } 652 653 private class MySignatureVisitor extends SignatureVisitor { 654 655 public MySignatureVisitor() { 656 super(Opcodes.ASM4); 657 } 658 659 // --------------------------------------------------- 660 // --- SignatureVisitor 661 // --------------------------------------------------- 662 663 private String mCurrentSignatureClass = null; 664 665 // Starts the visit of a signature corresponding to a class or interface type 666 @Override 667 public void visitClassType(String name) { 668 mCurrentSignatureClass = name; 669 considerName(name); 670 } 671 672 // Visits an inner class 673 @Override 674 public void visitInnerClassType(String name) { 675 if (mCurrentSignatureClass != null) { 676 mCurrentSignatureClass += "$" + name; 677 considerName(mCurrentSignatureClass); 678 } 679 } 680 681 @Override 682 public SignatureVisitor visitArrayType() { 683 return new MySignatureVisitor(); 684 } 685 686 @Override 687 public void visitBaseType(char descriptor) { 688 // pass -- a primitive type, ignored 689 } 690 691 @Override 692 public SignatureVisitor visitClassBound() { 693 return new MySignatureVisitor(); 694 } 695 696 @Override 697 public SignatureVisitor visitExceptionType() { 698 return new MySignatureVisitor(); 699 } 700 701 @Override 702 public void visitFormalTypeParameter(String name) { 703 // pass 704 } 705 706 @Override 707 public SignatureVisitor visitInterface() { 708 return new MySignatureVisitor(); 709 } 710 711 @Override 712 public SignatureVisitor visitInterfaceBound() { 713 return new MySignatureVisitor(); 714 } 715 716 @Override 717 public SignatureVisitor visitParameterType() { 718 return new MySignatureVisitor(); 719 } 720 721 @Override 722 public SignatureVisitor visitReturnType() { 723 return new MySignatureVisitor(); 724 } 725 726 @Override 727 public SignatureVisitor visitSuperclass() { 728 return new MySignatureVisitor(); 729 } 730 731 @Override 732 public SignatureVisitor visitTypeArgument(char wildcard) { 733 return new MySignatureVisitor(); 734 } 735 736 @Override 737 public void visitTypeVariable(String name) { 738 // pass 739 } 740 741 @Override 742 public void visitTypeArgument() { 743 // pass 744 } 745 } 746 747 748 // --------------------------------------------------- 749 // --- AnnotationVisitor 750 // --------------------------------------------------- 751 752 private class MyAnnotationVisitor extends AnnotationVisitor { 753 754 public MyAnnotationVisitor() { 755 super(Opcodes.ASM4); 756 } 757 758 // Visits a primitive value of an annotation 759 @Override 760 public void visit(String name, Object value) { 761 // value is the actual value, whose type must be Byte, Boolean, Character, Short, 762 // Integer, Long, Float, Double, String or Type 763 if (value instanceof Type) { 764 considerType((Type) value); 765 } 766 } 767 768 @Override 769 public AnnotationVisitor visitAnnotation(String name, String desc) { 770 // desc is the class descriptor of the nested annotation class. 771 considerDesc(desc); 772 return new MyAnnotationVisitor(); 773 } 774 775 @Override 776 public AnnotationVisitor visitArray(String name) { 777 return new MyAnnotationVisitor(); 778 } 779 780 @Override 781 public void visitEnum(String name, String desc, String value) { 782 // desc is the class descriptor of the enumeration class. 783 considerDesc(desc); 784 } 785 } 786 } 787} 788