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