Dependencies.java revision 2942:08092deced3f
1/* 2 * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25package com.sun.tools.classfile; 26 27import java.util.Deque; 28import java.util.HashMap; 29import java.util.HashSet; 30import java.util.LinkedList; 31import java.util.List; 32import java.util.Map; 33import java.util.Objects; 34import java.util.Set; 35import java.util.regex.Pattern; 36 37import com.sun.tools.classfile.Dependency.Filter; 38import com.sun.tools.classfile.Dependency.Finder; 39import com.sun.tools.classfile.Dependency.Location; 40import com.sun.tools.classfile.Type.ArrayType; 41import com.sun.tools.classfile.Type.ClassSigType; 42import com.sun.tools.classfile.Type.ClassType; 43import com.sun.tools.classfile.Type.MethodType; 44import com.sun.tools.classfile.Type.SimpleType; 45import com.sun.tools.classfile.Type.TypeParamType; 46import com.sun.tools.classfile.Type.WildcardType; 47 48import static com.sun.tools.classfile.ConstantPool.*; 49 50/** 51 * A framework for determining {@link Dependency dependencies} between class files. 52 * 53 * A {@link Dependency.Finder finder} is used to identify the dependencies of 54 * individual classes. Some finders may return subtypes of {@code Dependency} to 55 * further characterize the type of dependency, such as a dependency on a 56 * method within a class. 57 * 58 * A {@link Dependency.Filter filter} may be used to restrict the set of 59 * dependencies found by a finder. 60 * 61 * Dependencies that are found may be passed to a {@link Dependencies.Recorder 62 * recorder} so that the dependencies can be stored in a custom data structure. 63 */ 64public class Dependencies { 65 /** 66 * Thrown when a class file cannot be found. 67 */ 68 public static class ClassFileNotFoundException extends Exception { 69 private static final long serialVersionUID = 3632265927794475048L; 70 71 public ClassFileNotFoundException(String className) { 72 super(className); 73 this.className = className; 74 } 75 76 public ClassFileNotFoundException(String className, Throwable cause) { 77 this(className); 78 initCause(cause); 79 } 80 81 public final String className; 82 } 83 84 /** 85 * Thrown when an exception is found processing a class file. 86 */ 87 public static class ClassFileError extends Error { 88 private static final long serialVersionUID = 4111110813961313203L; 89 90 public ClassFileError(Throwable cause) { 91 initCause(cause); 92 } 93 } 94 95 /** 96 * Service provider interface to locate and read class files. 97 */ 98 public interface ClassFileReader { 99 /** 100 * Get the ClassFile object for a specified class. 101 * @param className the name of the class to be returned. 102 * @return the ClassFile for the given class 103 * @throws Dependencies.ClassFileNotFoundException if the classfile cannot be 104 * found 105 */ 106 public ClassFile getClassFile(String className) 107 throws ClassFileNotFoundException; 108 } 109 110 /** 111 * Service provide interface to handle results. 112 */ 113 public interface Recorder { 114 /** 115 * Record a dependency that has been found. 116 * @param d 117 */ 118 public void addDependency(Dependency d); 119 } 120 121 /** 122 * Get the default finder used to locate the dependencies for a class. 123 * @return the default finder 124 */ 125 public static Finder getDefaultFinder() { 126 return new APIDependencyFinder(AccessFlags.ACC_PRIVATE); 127 } 128 129 /** 130 * Get a finder used to locate the API dependencies for a class. 131 * These include the superclass, superinterfaces, and classes referenced in 132 * the declarations of fields and methods. The fields and methods that 133 * are checked can be limited according to a specified access. 134 * The access parameter must be one of {@link AccessFlags#ACC_PUBLIC ACC_PUBLIC}, 135 * {@link AccessFlags#ACC_PRIVATE ACC_PRIVATE}, 136 * {@link AccessFlags#ACC_PROTECTED ACC_PROTECTED}, or 0 for 137 * package private access. Members with greater than or equal accessibility 138 * to that specified will be searched for dependencies. 139 * @param access the access of members to be checked 140 * @return an API finder 141 */ 142 public static Finder getAPIFinder(int access) { 143 return new APIDependencyFinder(access); 144 } 145 146 /** 147 * Get a finder to do class dependency analysis. 148 * 149 * @return a Class dependency finder 150 */ 151 public static Finder getClassDependencyFinder() { 152 return new ClassDependencyFinder(); 153 } 154 155 /** 156 * Get the finder used to locate the dependencies for a class. 157 * @return the finder 158 */ 159 public Finder getFinder() { 160 if (finder == null) 161 finder = getDefaultFinder(); 162 return finder; 163 } 164 165 /** 166 * Set the finder used to locate the dependencies for a class. 167 * @param f the finder 168 */ 169 public void setFinder(Finder f) { 170 finder = Objects.requireNonNull(f); 171 } 172 173 /** 174 * Get the default filter used to determine included when searching 175 * the transitive closure of all the dependencies. 176 * Unless overridden, the default filter accepts all dependencies. 177 * @return the default filter. 178 */ 179 public static Filter getDefaultFilter() { 180 return DefaultFilter.instance(); 181 } 182 183 /** 184 * Get a filter which uses a regular expression on the target's class name 185 * to determine if a dependency is of interest. 186 * @param pattern the pattern used to match the target's class name 187 * @return a filter for matching the target class name with a regular expression 188 */ 189 public static Filter getRegexFilter(Pattern pattern) { 190 return new TargetRegexFilter(pattern); 191 } 192 193 /** 194 * Get a filter which checks the package of a target's class name 195 * to determine if a dependency is of interest. The filter checks if the 196 * package of the target's class matches any of a set of given package 197 * names. The match may optionally match subpackages of the given names as well. 198 * @param packageNames the package names used to match the target's class name 199 * @param matchSubpackages whether or not to match subpackages as well 200 * @return a filter for checking the target package name against a list of package names 201 */ 202 public static Filter getPackageFilter(Set<String> packageNames, boolean matchSubpackages) { 203 return new TargetPackageFilter(packageNames, matchSubpackages); 204 } 205 206 /** 207 * Get the filter used to determine the dependencies included when searching 208 * the transitive closure of all the dependencies. 209 * Unless overridden, the default filter accepts all dependencies. 210 * @return the filter 211 */ 212 public Filter getFilter() { 213 if (filter == null) 214 filter = getDefaultFilter(); 215 return filter; 216 } 217 218 /** 219 * Set the filter used to determine the dependencies included when searching 220 * the transitive closure of all the dependencies. 221 * @param f the filter 222 */ 223 public void setFilter(Filter f) { 224 filter = Objects.requireNonNull(f); 225 } 226 227 /** 228 * Find the dependencies of a class, using the current 229 * {@link Dependencies#getFinder finder} and 230 * {@link Dependencies#getFilter filter}. 231 * The search may optionally include the transitive closure of all the 232 * filtered dependencies, by also searching in the classes named in those 233 * dependencies. 234 * @param classFinder a finder to locate class files 235 * @param rootClassNames the names of the root classes from which to begin 236 * searching 237 * @param transitiveClosure whether or not to also search those classes 238 * named in any filtered dependencies that are found. 239 * @return the set of dependencies that were found 240 * @throws ClassFileNotFoundException if a required class file cannot be found 241 * @throws ClassFileError if an error occurs while processing a class file, 242 * such as an error in the internal class file structure. 243 */ 244 public Set<Dependency> findAllDependencies( 245 ClassFileReader classFinder, Set<String> rootClassNames, 246 boolean transitiveClosure) 247 throws ClassFileNotFoundException { 248 final Set<Dependency> results = new HashSet<>(); 249 Recorder r = new Recorder() { 250 public void addDependency(Dependency d) { 251 results.add(d); 252 } 253 }; 254 findAllDependencies(classFinder, rootClassNames, transitiveClosure, r); 255 return results; 256 } 257 258 /** 259 * Find the dependencies of a class, using the current 260 * {@link Dependencies#getFinder finder} and 261 * {@link Dependencies#getFilter filter}. 262 * The search may optionally include the transitive closure of all the 263 * filtered dependencies, by also searching in the classes named in those 264 * dependencies. 265 * @param classFinder a finder to locate class files 266 * @param rootClassNames the names of the root classes from which to begin 267 * searching 268 * @param transitiveClosure whether or not to also search those classes 269 * named in any filtered dependencies that are found. 270 * @param recorder a recorder for handling the results 271 * @throws ClassFileNotFoundException if a required class file cannot be found 272 * @throws ClassFileError if an error occurs while processing a class file, 273 * such as an error in the internal class file structure. 274 */ 275 public void findAllDependencies( 276 ClassFileReader classFinder, Set<String> rootClassNames, 277 boolean transitiveClosure, Recorder recorder) 278 throws ClassFileNotFoundException { 279 Set<String> doneClasses = new HashSet<>(); 280 281 getFinder(); // ensure initialized 282 getFilter(); // ensure initialized 283 284 // Work queue of names of classfiles to be searched. 285 // Entries will be unique, and for classes that do not yet have 286 // dependencies in the results map. 287 Deque<String> deque = new LinkedList<>(rootClassNames); 288 289 String className; 290 while ((className = deque.poll()) != null) { 291 assert (!doneClasses.contains(className)); 292 doneClasses.add(className); 293 294 ClassFile cf = classFinder.getClassFile(className); 295 296 // The following code just applies the filter to the dependencies 297 // followed for the transitive closure. 298 for (Dependency d: finder.findDependencies(cf)) { 299 recorder.addDependency(d); 300 if (transitiveClosure && filter.accepts(d)) { 301 String cn = d.getTarget().getClassName(); 302 if (!doneClasses.contains(cn)) 303 deque.add(cn); 304 } 305 } 306 } 307 } 308 309 private Filter filter; 310 private Finder finder; 311 312 /** 313 * A location identifying a class. 314 */ 315 static class SimpleLocation implements Location { 316 public SimpleLocation(String name) { 317 this.name = name; 318 this.className = name.replace('/', '.'); 319 } 320 321 public String getName() { 322 return name; 323 } 324 325 public String getClassName() { 326 return className; 327 } 328 329 public String getPackageName() { 330 int i = name.lastIndexOf('/'); 331 return (i > 0) ? name.substring(0, i).replace('/', '.') : ""; 332 } 333 334 @Override 335 public boolean equals(Object other) { 336 if (this == other) 337 return true; 338 if (!(other instanceof SimpleLocation)) 339 return false; 340 return (name.equals(((SimpleLocation) other).name)); 341 } 342 343 @Override 344 public int hashCode() { 345 return name.hashCode(); 346 } 347 348 @Override 349 public String toString() { 350 return name; 351 } 352 353 private String name; 354 private String className; 355 } 356 357 /** 358 * A dependency of one class on another. 359 */ 360 static class SimpleDependency implements Dependency { 361 public SimpleDependency(Location origin, Location target) { 362 this.origin = origin; 363 this.target = target; 364 } 365 366 public Location getOrigin() { 367 return origin; 368 } 369 370 public Location getTarget() { 371 return target; 372 } 373 374 @Override 375 public boolean equals(Object other) { 376 if (this == other) 377 return true; 378 if (!(other instanceof SimpleDependency)) 379 return false; 380 SimpleDependency o = (SimpleDependency) other; 381 return (origin.equals(o.origin) && target.equals(o.target)); 382 } 383 384 @Override 385 public int hashCode() { 386 return origin.hashCode() * 31 + target.hashCode(); 387 } 388 389 @Override 390 public String toString() { 391 return origin + ":" + target; 392 } 393 394 private Location origin; 395 private Location target; 396 } 397 398 399 /** 400 * This class accepts all dependencies. 401 */ 402 static class DefaultFilter implements Filter { 403 private static DefaultFilter instance; 404 405 static DefaultFilter instance() { 406 if (instance == null) 407 instance = new DefaultFilter(); 408 return instance; 409 } 410 411 public boolean accepts(Dependency dependency) { 412 return true; 413 } 414 } 415 416 /** 417 * This class accepts those dependencies whose target's class name matches a 418 * regular expression. 419 */ 420 static class TargetRegexFilter implements Filter { 421 TargetRegexFilter(Pattern pattern) { 422 this.pattern = pattern; 423 } 424 425 public boolean accepts(Dependency dependency) { 426 return pattern.matcher(dependency.getTarget().getClassName()).matches(); 427 } 428 429 private final Pattern pattern; 430 } 431 432 /** 433 * This class accepts those dependencies whose class name is in a given 434 * package. 435 */ 436 static class TargetPackageFilter implements Filter { 437 TargetPackageFilter(Set<String> packageNames, boolean matchSubpackages) { 438 for (String pn: packageNames) { 439 if (pn.length() == 0) // implies null check as well 440 throw new IllegalArgumentException(); 441 } 442 this.packageNames = packageNames; 443 this.matchSubpackages = matchSubpackages; 444 } 445 446 public boolean accepts(Dependency dependency) { 447 String pn = dependency.getTarget().getPackageName(); 448 if (packageNames.contains(pn)) 449 return true; 450 451 if (matchSubpackages) { 452 for (String n: packageNames) { 453 if (pn.startsWith(n + ".")) 454 return true; 455 } 456 } 457 458 return false; 459 } 460 461 private final Set<String> packageNames; 462 private final boolean matchSubpackages; 463 } 464 465 /** 466 * This class identifies class names directly or indirectly in the constant pool. 467 */ 468 static class ClassDependencyFinder extends BasicDependencyFinder { 469 public Iterable<? extends Dependency> findDependencies(ClassFile classfile) { 470 Visitor v = new Visitor(classfile); 471 for (CPInfo cpInfo: classfile.constant_pool.entries()) { 472 v.scan(cpInfo); 473 } 474 try { 475 v.addClass(classfile.super_class); 476 v.addClasses(classfile.interfaces); 477 v.scan(classfile.attributes); 478 479 for (Field f : classfile.fields) { 480 v.scan(f.descriptor, f.attributes); 481 } 482 for (Method m : classfile.methods) { 483 v.scan(m.descriptor, m.attributes); 484 Exceptions_attribute e = 485 (Exceptions_attribute)m.attributes.get(Attribute.Exceptions); 486 if (e != null) { 487 v.addClasses(e.exception_index_table); 488 } 489 } 490 } catch (ConstantPoolException e) { 491 throw new ClassFileError(e); 492 } 493 494 return v.deps; 495 } 496 } 497 498 /** 499 * This class identifies class names in the signatures of classes, fields, 500 * and methods in a class. 501 */ 502 static class APIDependencyFinder extends BasicDependencyFinder { 503 APIDependencyFinder(int access) { 504 switch (access) { 505 case AccessFlags.ACC_PUBLIC: 506 case AccessFlags.ACC_PROTECTED: 507 case AccessFlags.ACC_PRIVATE: 508 case 0: 509 showAccess = access; 510 break; 511 default: 512 throw new IllegalArgumentException("invalid access 0x" 513 + Integer.toHexString(access)); 514 } 515 } 516 517 public Iterable<? extends Dependency> findDependencies(ClassFile classfile) { 518 try { 519 Visitor v = new Visitor(classfile); 520 v.addClass(classfile.super_class); 521 v.addClasses(classfile.interfaces); 522 // inner classes? 523 for (Field f : classfile.fields) { 524 if (checkAccess(f.access_flags)) 525 v.scan(f.descriptor, f.attributes); 526 } 527 for (Method m : classfile.methods) { 528 if (checkAccess(m.access_flags)) { 529 v.scan(m.descriptor, m.attributes); 530 Exceptions_attribute e = 531 (Exceptions_attribute) m.attributes.get(Attribute.Exceptions); 532 if (e != null) 533 v.addClasses(e.exception_index_table); 534 } 535 } 536 return v.deps; 537 } catch (ConstantPoolException e) { 538 throw new ClassFileError(e); 539 } 540 } 541 542 boolean checkAccess(AccessFlags flags) { 543 // code copied from javap.Options.checkAccess 544 boolean isPublic = flags.is(AccessFlags.ACC_PUBLIC); 545 boolean isProtected = flags.is(AccessFlags.ACC_PROTECTED); 546 boolean isPrivate = flags.is(AccessFlags.ACC_PRIVATE); 547 boolean isPackage = !(isPublic || isProtected || isPrivate); 548 549 if ((showAccess == AccessFlags.ACC_PUBLIC) && (isProtected || isPrivate || isPackage)) 550 return false; 551 else if ((showAccess == AccessFlags.ACC_PROTECTED) && (isPrivate || isPackage)) 552 return false; 553 else if ((showAccess == 0) && (isPrivate)) 554 return false; 555 else 556 return true; 557 } 558 559 private int showAccess; 560 } 561 562 static abstract class BasicDependencyFinder implements Finder { 563 private Map<String,Location> locations = new HashMap<>(); 564 565 Location getLocation(String className) { 566 Location l = locations.get(className); 567 if (l == null) 568 locations.put(className, l = new SimpleLocation(className)); 569 return l; 570 } 571 572 class Visitor implements ConstantPool.Visitor<Void,Void>, Type.Visitor<Void, Void> { 573 private ConstantPool constant_pool; 574 private Location origin; 575 Set<Dependency> deps; 576 577 Visitor(ClassFile classFile) { 578 try { 579 constant_pool = classFile.constant_pool; 580 origin = getLocation(classFile.getName()); 581 deps = new HashSet<>(); 582 } catch (ConstantPoolException e) { 583 throw new ClassFileError(e); 584 } 585 } 586 587 void scan(Descriptor d, Attributes attrs) { 588 try { 589 scan(new Signature(d.index).getType(constant_pool)); 590 scan(attrs); 591 } catch (ConstantPoolException e) { 592 throw new ClassFileError(e); 593 } 594 } 595 596 void scan(CPInfo cpInfo) { 597 cpInfo.accept(this, null); 598 } 599 600 void scan(Type t) { 601 t.accept(this, null); 602 } 603 604 void scan(Attributes attrs) { 605 try { 606 Signature_attribute sa = (Signature_attribute)attrs.get(Attribute.Signature); 607 if (sa != null) 608 scan(sa.getParsedSignature().getType(constant_pool)); 609 610 scan((RuntimeVisibleAnnotations_attribute) 611 attrs.get(Attribute.RuntimeVisibleAnnotations)); 612 scan((RuntimeVisibleParameterAnnotations_attribute) 613 attrs.get(Attribute.RuntimeVisibleParameterAnnotations)); 614 } catch (ConstantPoolException e) { 615 throw new ClassFileError(e); 616 } 617 } 618 619 private void scan(RuntimeAnnotations_attribute attr) throws ConstantPoolException { 620 if (attr == null) { 621 return; 622 } 623 for (int i = 0; i < attr.annotations.length; i++) { 624 int index = attr.annotations[i].type_index; 625 scan(new Signature(index).getType(constant_pool)); 626 } 627 } 628 629 private void scan(RuntimeParameterAnnotations_attribute attr) throws ConstantPoolException { 630 if (attr == null) { 631 return; 632 } 633 for (int param = 0; param < attr.parameter_annotations.length; param++) { 634 for (int i = 0; i < attr.parameter_annotations[param].length; i++) { 635 int index = attr.parameter_annotations[param][i].type_index; 636 scan(new Signature(index).getType(constant_pool)); 637 } 638 } 639 } 640 641 void addClass(int index) throws ConstantPoolException { 642 if (index != 0) { 643 String name = constant_pool.getClassInfo(index).getBaseName(); 644 if (name != null) 645 addDependency(name); 646 } 647 } 648 649 void addClasses(int[] indices) throws ConstantPoolException { 650 for (int i: indices) 651 addClass(i); 652 } 653 654 private void addDependency(String name) { 655 deps.add(new SimpleDependency(origin, getLocation(name))); 656 } 657 658 // ConstantPool.Visitor methods 659 660 public Void visitClass(CONSTANT_Class_info info, Void p) { 661 try { 662 if (info.getName().startsWith("[")) 663 new Signature(info.name_index).getType(constant_pool).accept(this, null); 664 else 665 addDependency(info.getBaseName()); 666 return null; 667 } catch (ConstantPoolException e) { 668 throw new ClassFileError(e); 669 } 670 } 671 672 public Void visitDouble(CONSTANT_Double_info info, Void p) { 673 return null; 674 } 675 676 public Void visitFieldref(CONSTANT_Fieldref_info info, Void p) { 677 return visitRef(info, p); 678 } 679 680 public Void visitFloat(CONSTANT_Float_info info, Void p) { 681 return null; 682 } 683 684 public Void visitInteger(CONSTANT_Integer_info info, Void p) { 685 return null; 686 } 687 688 public Void visitInterfaceMethodref(CONSTANT_InterfaceMethodref_info info, Void p) { 689 return visitRef(info, p); 690 } 691 692 public Void visitInvokeDynamic(CONSTANT_InvokeDynamic_info info, Void p) { 693 return null; 694 } 695 696 public Void visitLong(CONSTANT_Long_info info, Void p) { 697 return null; 698 } 699 700 public Void visitMethodHandle(CONSTANT_MethodHandle_info info, Void p) { 701 return null; 702 } 703 704 public Void visitMethodType(CONSTANT_MethodType_info info, Void p) { 705 return null; 706 } 707 708 public Void visitMethodref(CONSTANT_Methodref_info info, Void p) { 709 return visitRef(info, p); 710 } 711 712 public Void visitNameAndType(CONSTANT_NameAndType_info info, Void p) { 713 try { 714 new Signature(info.type_index).getType(constant_pool).accept(this, null); 715 return null; 716 } catch (ConstantPoolException e) { 717 throw new ClassFileError(e); 718 } 719 } 720 721 public Void visitString(CONSTANT_String_info info, Void p) { 722 return null; 723 } 724 725 public Void visitUtf8(CONSTANT_Utf8_info info, Void p) { 726 return null; 727 } 728 729 private Void visitRef(CPRefInfo info, Void p) { 730 try { 731 visitClass(info.getClassInfo(), p); 732 return null; 733 } catch (ConstantPoolException e) { 734 throw new ClassFileError(e); 735 } 736 } 737 738 // Type.Visitor methods 739 740 private void findDependencies(Type t) { 741 if (t != null) 742 t.accept(this, null); 743 } 744 745 private void findDependencies(List<? extends Type> ts) { 746 if (ts != null) { 747 for (Type t: ts) 748 t.accept(this, null); 749 } 750 } 751 752 public Void visitSimpleType(SimpleType type, Void p) { 753 return null; 754 } 755 756 public Void visitArrayType(ArrayType type, Void p) { 757 findDependencies(type.elemType); 758 return null; 759 } 760 761 public Void visitMethodType(MethodType type, Void p) { 762 findDependencies(type.paramTypes); 763 findDependencies(type.returnType); 764 findDependencies(type.throwsTypes); 765 findDependencies(type.typeParamTypes); 766 return null; 767 } 768 769 public Void visitClassSigType(ClassSigType type, Void p) { 770 findDependencies(type.superclassType); 771 findDependencies(type.superinterfaceTypes); 772 return null; 773 } 774 775 public Void visitClassType(ClassType type, Void p) { 776 findDependencies(type.outerType); 777 addDependency(type.getBinaryName()); 778 findDependencies(type.typeArgs); 779 return null; 780 } 781 782 public Void visitTypeParamType(TypeParamType type, Void p) { 783 findDependencies(type.classBound); 784 findDependencies(type.interfaceBounds); 785 return null; 786 } 787 788 public Void visitWildcardType(WildcardType type, Void p) { 789 findDependencies(type.boundType); 790 return null; 791 } 792 } 793 } 794} 795