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