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 26 package com.sun.tools.classfile; 27 28 import java.util.Deque; 29 import java.util.HashMap; 30 import java.util.HashSet; 31 import java.util.LinkedList; 32 import java.util.List; 33 import java.util.Map; 34 import java.util.Objects; 35 import java.util.Set; 36 import java.util.concurrent.ConcurrentHashMap; 37 import java.util.regex.Pattern; 38 39 import com.sun.tools.classfile.Dependency.Filter; 40 import com.sun.tools.classfile.Dependency.Finder; 41 import com.sun.tools.classfile.Dependency.Location; 42 import com.sun.tools.classfile.Type.ArrayType; 43 import com.sun.tools.classfile.Type.ClassSigType; 44 import com.sun.tools.classfile.Type.ClassType; 45 import com.sun.tools.classfile.Type.MethodType; 46 import com.sun.tools.classfile.Type.SimpleType; 47 import com.sun.tools.classfile.Type.TypeParamType; 48 import com.sun.tools.classfile.Type.WildcardType; 49 50 import 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 */ 66 public 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 = new Recorder() { 252 public void addDependency(Dependency d) { 253 results.add(d); 254 } 255 }; 256 findAllDependencies(classFinder, rootClassNames, transitiveClosure, r); 257 return results; 258 } 259 260 /** 261 * Find the dependencies of a class, using the current 262 * {@link Dependencies#getFinder finder} and 263 * {@link Dependencies#getFilter filter}. 264 * The search may optionally include the transitive closure of all the 265 * filtered dependencies, by also searching in the classes named in those 266 * dependencies. 267 * @param classFinder a finder to locate class files 268 * @param rootClassNames the names of the root classes from which to begin 269 * searching 270 * @param transitiveClosure whether or not to also search those classes 271 * named in any filtered dependencies that are found. 272 * @param recorder a recorder for handling the results 273 * @throws ClassFileNotFoundException if a required class file cannot be found 274 * @throws ClassFileError if an error occurs while processing a class file, 275 * such as an error in the internal class file structure. 276 */ 277 public void findAllDependencies( 278 ClassFileReader classFinder, Set<String> rootClassNames, 279 boolean transitiveClosure, Recorder recorder) 280 throws ClassFileNotFoundException { 281 Set<String> doneClasses = new HashSet<>(); 282 283 getFinder(); // ensure initialized 284 getFilter(); // ensure initialized 285 286 // Work queue of names of classfiles to be searched. 287 // Entries will be unique, and for classes that do not yet have 288 // dependencies in the results map. 289 Deque<String> deque = new LinkedList<>(rootClassNames); 290 291 String className; 292 while ((className = deque.poll()) != null) { 293 assert (!doneClasses.contains(className)); 294 doneClasses.add(className); 295 296 ClassFile cf = classFinder.getClassFile(className); 297 298 // The following code just applies the filter to the dependencies 299 // followed for the transitive closure. 300 for (Dependency d: finder.findDependencies(cf)) { 301 recorder.addDependency(d); 302 if (transitiveClosure && filter.accepts(d)) { 303 String cn = d.getTarget().getClassName(); 304 if (!doneClasses.contains(cn)) 305 deque.add(cn); 306 } 307 } 308 } 309 } 310 311 private Filter filter; 312 private Finder finder; 313 314 /** 315 * A location identifying a class. 316 */ 317 static class SimpleLocation implements Location { 318 public SimpleLocation(String name) { 319 this.name = name; 320 this.className = name.replace('/', '.'); 321 } 322 323 public String getName() { 324 return name; 325 } 326 327 public String getClassName() { 328 return className; 329 } 330 331 public String getPackageName() { 332 int i = name.lastIndexOf('/'); 333 return (i > 0) ? name.substring(0, i).replace('/', '.') : ""; 334 } 335 336 @Override 337 public boolean equals(Object other) { 338 if (this == other) 339 return true; 340 if (!(other instanceof SimpleLocation)) 341 return false; 342 return (name.equals(((SimpleLocation) other).name)); 343 } 344 345 @Override 346 public int hashCode() { 347 return name.hashCode(); 348 } 349 350 @Override 351 public String toString() { 352 return name; 353 } 354 355 private String name; 356 private String className; 357 } 358 359 /** 360 * A dependency of one class on another. 361 */ 362 static class SimpleDependency implements Dependency { 363 public SimpleDependency(Location origin, Location target) { 364 this.origin = origin; 365 this.target = target; 366 } 367 368 public Location getOrigin() { 369 return origin; 370 } 371 372 public Location getTarget() { 373 return target; 374 } 375 376 @Override 377 public boolean equals(Object other) { 378 if (this == other) 379 return true; 380 if (!(other instanceof SimpleDependency)) 381 return false; 382 SimpleDependency o = (SimpleDependency) other; 383 return (origin.equals(o.origin) && target.equals(o.target)); 384 } 385 386 @Override 387 public int hashCode() { 388 return origin.hashCode() * 31 + target.hashCode(); 389 } 390 391 @Override 392 public String toString() { 393 return origin + ":" + target; 394 } 395 396 private Location origin; 397 private Location target; 398 } 399 400 401 /** 402 * This class accepts all dependencies. 403 */ 404 static class DefaultFilter implements Filter { 405 private static DefaultFilter instance; 406 407 static DefaultFilter instance() { 408 if (instance == null) 409 instance = new DefaultFilter(); 410 return instance; 411 } 412 413 public boolean accepts(Dependency dependency) { 414 return true; 415 } 416 } 417 418 /** 419 * This class accepts those dependencies whose target's class name matches a 420 * regular expression. 421 */ 422 static class TargetRegexFilter implements Filter { 423 TargetRegexFilter(Pattern pattern) { 424 this.pattern = pattern; 425 } 426 427 public boolean accepts(Dependency dependency) { 428 return pattern.matcher(dependency.getTarget().getClassName()).matches(); 429 } 430 431 private final Pattern pattern; 432 } 433 434 /** 435 * This class accepts those dependencies whose class name is in a given 436 * package. 437 */ 438 static class TargetPackageFilter implements Filter { 439 TargetPackageFilter(Set<String> packageNames, boolean matchSubpackages) { 440 for (String pn: packageNames) { 441 if (pn.length() == 0) // implies null check as well 442 throw new IllegalArgumentException(); 443 } 444 this.packageNames = packageNames; 445 this.matchSubpackages = matchSubpackages; 446 } 447 448 public boolean accepts(Dependency dependency) { 449 String pn = dependency.getTarget().getPackageName(); 450 if (packageNames.contains(pn)) 451 return true; 452 453 if (matchSubpackages) { 454 for (String n: packageNames) { 455 if (pn.startsWith(n + ".")) 456 return true; 457 } 458 } 459 460 return false; 461 } 462 463 private final Set<String> packageNames; 464 private final boolean matchSubpackages; 465 } 466 467 /** 468 * This class identifies class names directly or indirectly in the constant pool. 469 */ 470 static class ClassDependencyFinder extends BasicDependencyFinder { 471 public Iterable<? extends Dependency> findDependencies(ClassFile classfile) { 472 Visitor v = new Visitor(classfile); 473 for (CPInfo cpInfo: classfile.constant_pool.entries()) { 474 v.scan(cpInfo); 475 } 476 try { 477 v.addClass(classfile.super_class); 478 v.addClasses(classfile.interfaces); 479 v.scan(classfile.attributes); 480 481 for (Field f : classfile.fields) { 482 v.scan(f.descriptor, f.attributes); 483 } 484 for (Method m : classfile.methods) { 485 v.scan(m.descriptor, m.attributes); 486 Exceptions_attribute e = 487 (Exceptions_attribute)m.attributes.get(Attribute.Exceptions); 488 if (e != null) { 489 v.addClasses(e.exception_index_table); 490 } 491 } 492 } catch (ConstantPoolException e) { 493 throw new ClassFileError(e); 494 } 495 496 return v.deps; 497 } 498 } 499 500 /** 501 * This class identifies class names in the signatures of classes, fields, 502 * and methods in a class. 503 */ 504 static class APIDependencyFinder extends BasicDependencyFinder { 505 APIDependencyFinder(int access) { 506 switch (access) { 507 case AccessFlags.ACC_PUBLIC: 508 case AccessFlags.ACC_PROTECTED: 509 case AccessFlags.ACC_PRIVATE: 510 case 0: 511 showAccess = access; 512 break; 513 default: 514 throw new IllegalArgumentException("invalid access 0x" 515 + Integer.toHexString(access)); 516 } 517 } 518 519 public Iterable<? extends Dependency> findDependencies(ClassFile classfile) { 520 try { 521 Visitor v = new Visitor(classfile); 522 v.addClass(classfile.super_class); 523 v.addClasses(classfile.interfaces); 524 // inner classes? 525 for (Field f : classfile.fields) { 526 if (checkAccess(f.access_flags)) 527 v.scan(f.descriptor, f.attributes); 528 } 529 for (Method m : classfile.methods) { 530 if (checkAccess(m.access_flags)) { 531 v.scan(m.descriptor, m.attributes); 532 Exceptions_attribute e = 533 (Exceptions_attribute) m.attributes.get(Attribute.Exceptions); 534 if (e != null) 535 v.addClasses(e.exception_index_table); 536 } 537 } 538 return v.deps; 539 } catch (ConstantPoolException e) { 540 throw new ClassFileError(e); 541 } 542 } 543 544 boolean checkAccess(AccessFlags flags) { 545 // code copied from javap.Options.checkAccess 546 boolean isPublic = flags.is(AccessFlags.ACC_PUBLIC); 547 boolean isProtected = flags.is(AccessFlags.ACC_PROTECTED); 548 boolean isPrivate = flags.is(AccessFlags.ACC_PRIVATE); 549 boolean isPackage = !(isPublic || isProtected || isPrivate); 550 551 if ((showAccess == AccessFlags.ACC_PUBLIC) && (isProtected || isPrivate || isPackage)) 552 return false; 553 else if ((showAccess == AccessFlags.ACC_PROTECTED) && (isPrivate || isPackage)) 554 return false; 555 else if ((showAccess == 0) && (isPrivate)) 556 return false; 557 else 558 return true; 559 } 560 561 private int showAccess; 562 } 563 564 static abstract class BasicDependencyFinder implements Finder { 565 private Map<String,Location> locations = new ConcurrentHashMap<>(); 566 567 Location getLocation(String className) { 568 return locations.computeIfAbsent(className, cn -> new SimpleLocation(cn)); 569 } 570 571 class Visitor implements ConstantPool.Visitor<Void,Void>, Type.Visitor<Void, Void> { 572 private ConstantPool constant_pool; 573 private Location origin; 574 Set<Dependency> deps; 575 576 Visitor(ClassFile classFile) { 577 try { 578 constant_pool = classFile.constant_pool; 579 origin = getLocation(classFile.getName()); 580 deps = new HashSet<>(); 581 } catch (ConstantPoolException e) { 582 throw new ClassFileError(e); 583 } 584 } 585 586 void scan(Descriptor d, Attributes attrs) { 587 try { 588 scan(new Signature(d.index).getType(constant_pool)); 589 scan(attrs); 590 } catch (ConstantPoolException e) { 591 throw new ClassFileError(e); 592 } 593 } 594 595 void scan(CPInfo cpInfo) { 596 cpInfo.accept(this, null); 597 } 598 599 void scan(Type t) { 600 t.accept(this, null); 601 } 602 603 void scan(Attributes attrs) { 604 try { 605 Signature_attribute sa = (Signature_attribute)attrs.get(Attribute.Signature); 606 if (sa != null) 607 scan(sa.getParsedSignature().getType(constant_pool)); 608 609 scan((RuntimeVisibleAnnotations_attribute) 610 attrs.get(Attribute.RuntimeVisibleAnnotations)); 611 scan((RuntimeVisibleParameterAnnotations_attribute) 612 attrs.get(Attribute.RuntimeVisibleParameterAnnotations)); 613 } catch (ConstantPoolException e) { 614 throw new ClassFileError(e); 615 } 616 } 617 618 private void scan(RuntimeAnnotations_attribute attr) throws ConstantPoolException { 619 if (attr == null) { 620 return; 621 } 622 for (int i = 0; i < attr.annotations.length; i++) { 623 int index = attr.annotations[i].type_index; 624 scan(new Signature(index).getType(constant_pool)); 625 } 626 } 627 628 private void scan(RuntimeParameterAnnotations_attribute attr) throws ConstantPoolException { 629 if (attr == null) { 630 return; 631 } 632 for (int param = 0; param < attr.parameter_annotations.length; param++) { 633 for (int i = 0; i < attr.parameter_annotations[param].length; i++) { 634 int index = attr.parameter_annotations[param][i].type_index; 635 scan(new Signature(index).getType(constant_pool)); 636 } 637 } 638 } 639 640 void addClass(int index) throws ConstantPoolException { 641 if (index != 0) { 642 String name = constant_pool.getClassInfo(index).getBaseName(); 643 if (name != null) 644 addDependency(name); 645 } 646 } 647 648 void addClasses(int[] indices) throws ConstantPoolException { 649 for (int i: indices) 650 addClass(i); 651 } 652 653 private void addDependency(String name) { 654 deps.add(new SimpleDependency(origin, getLocation(name))); 655 } 656 657 // ConstantPool.Visitor methods 658 659 public Void visitClass(CONSTANT_Class_info info, Void p) { 660 try { 661 if (info.getName().startsWith("[")) 662 new Signature(info.name_index).getType(constant_pool).accept(this, null); 663 else 664 addDependency(info.getBaseName()); 665 return null; 666 } catch (ConstantPoolException e) { 667 throw new ClassFileError(e); 668 } 669 } 670 671 public Void visitDouble(CONSTANT_Double_info info, Void p) { 672 return null; 673 } 674 675 public Void visitFieldref(CONSTANT_Fieldref_info info, Void p) { 676 return visitRef(info, p); 677 } 678 679 public Void visitFloat(CONSTANT_Float_info info, Void p) { 680 return null; 681 } 682 683 public Void visitInteger(CONSTANT_Integer_info info, Void p) { 684 return null; 685 } 686 687 public Void visitInterfaceMethodref(CONSTANT_InterfaceMethodref_info info, Void p) { 688 return visitRef(info, p); 689 } 690 691 public Void visitInvokeDynamic(CONSTANT_InvokeDynamic_info info, Void p) { 692 return null; 693 } 694 695 public Void visitLong(CONSTANT_Long_info info, Void p) { 696 return null; 697 } 698 699 public Void visitMethodHandle(CONSTANT_MethodHandle_info info, Void p) { 700 return null; 701 } 702 703 public Void visitMethodType(CONSTANT_MethodType_info info, Void p) { 704 return null; 705 } 706 707 public Void visitMethodref(CONSTANT_Methodref_info info, Void p) { 708 return visitRef(info, p); 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 visitString(CONSTANT_String_info info, Void p) { 721 return null; 722 } 723 724 public Void visitUtf8(CONSTANT_Utf8_info info, Void p) { 725 return null; 726 } 727 728 private Void visitRef(CPRefInfo info, Void p) { 729 try { 730 visitClass(info.getClassInfo(), p); 731 return null; 732 } catch (ConstantPoolException e) { 733 throw new ClassFileError(e); 734 } 735 } 736 737 // Type.Visitor methods 738 739 private void findDependencies(Type t) { 740 if (t != null) 741 t.accept(this, null); 742 } 743 744 private void findDependencies(List<? extends Type> ts) { 745 if (ts != null) { 746 for (Type t: ts) 747 t.accept(this, null); 748 } 749 } 750 751 public Void visitSimpleType(SimpleType type, Void p) { 752 return null; 753 } 754 755 public Void visitArrayType(ArrayType type, Void p) { 756 findDependencies(type.elemType); 757 return null; 758 } 759 760 public Void visitMethodType(MethodType type, Void p) { 761 findDependencies(type.paramTypes); 762 findDependencies(type.returnType); 763 findDependencies(type.throwsTypes); 764 findDependencies(type.typeParamTypes); 765 return null; 766 } 767 768 public Void visitClassSigType(ClassSigType type, Void p) { 769 findDependencies(type.superclassType); 770 findDependencies(type.superinterfaceTypes); 771 return null; 772 } 773 774 public Void visitClassType(ClassType type, Void p) { 775 findDependencies(type.outerType); 776 addDependency(type.getBinaryName()); 777 findDependencies(type.typeArgs); 778 return null; 779 } 780 781 public Void visitTypeParamType(TypeParamType type, Void p) { 782 findDependencies(type.classBound); 783 findDependencies(type.interfaceBounds); 784 return null; 785 } 786 787 public Void visitWildcardType(WildcardType type, Void p) { 788 findDependencies(type.boundType); 789 return null; 790 } 791 } 792 } 793 }