Scan.java revision 4149:13f457e05af0
1/* 2 * Copyright (c) 2016, 2017, 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.jdeprscan.scan; 27 28import java.io.IOException; 29import java.io.PrintStream; 30import java.nio.file.Files; 31import java.nio.file.NoSuchFileException; 32import java.nio.file.Path; 33import java.nio.file.Paths; 34import java.util.ArrayDeque; 35import java.util.Deque; 36import java.util.Enumeration; 37import java.util.List; 38import java.util.jar.JarEntry; 39import java.util.jar.JarFile; 40import java.util.regex.Matcher; 41import java.util.regex.Pattern; 42import java.util.stream.Collectors; 43import java.util.stream.Stream; 44 45import com.sun.tools.classfile.*; 46import com.sun.tools.jdeprscan.DeprData; 47import com.sun.tools.jdeprscan.DeprDB; 48import com.sun.tools.jdeprscan.Messages; 49 50import static com.sun.tools.classfile.AccessFlags.*; 51import static com.sun.tools.classfile.ConstantPool.*; 52 53/** 54 * An object that represents the scanning phase of deprecation usage checking. 55 * Given a deprecation database, scans the targeted directory hierarchy, jar 56 * file, or individual class for uses of deprecated APIs. 57 */ 58public class Scan { 59 final PrintStream out; 60 final PrintStream err; 61 final List<String> classPath; 62 final DeprDB db; 63 final boolean verbose; 64 65 final ClassFinder finder; 66 boolean errorOccurred = false; 67 68 public Scan(PrintStream out, 69 PrintStream err, 70 List<String> classPath, 71 DeprDB db, 72 boolean verbose) { 73 this.out = out; 74 this.err = err; 75 this.classPath = classPath; 76 this.db = db; 77 this.verbose = verbose; 78 79 ClassFinder f = new ClassFinder(verbose); 80 81 // TODO: this isn't quite right. If we've specified a release other than the current 82 // one, we should instead add a reference to the symbol file for that release instead 83 // of the current image. The problems are a) it's unclear how to get from a release 84 // to paths that reference the symbol files, as this might be internal to the file 85 // manager; and b) the symbol file includes .sig files, not class files, which ClassFile 86 // might not be able to handle. 87 f.addJrt(); 88 89 for (String name : classPath) { 90 if (name.endsWith(".jar")) { 91 f.addJar(name); 92 } else { 93 f.addDir(name); 94 } 95 } 96 97 finder = f; 98 } 99 100 /** 101 * Given a descriptor type, extracts and returns the class name from it, if any. 102 * These types are obtained from field descriptors (JVMS 4.3.2) and method 103 * descriptors (JVMS 4.3.3). They have one of the following forms: 104 * 105 * I // or any other primitive, or V for void 106 * [I // array of primitives, including multi-dimensional 107 * Lname; // the named class 108 * [Lname; // array whose component is the named class (also multi-d) 109 * 110 * This method extracts and returns the class name, or returns empty for primitives, void, 111 * or array of primitives. 112 * 113 * Returns nullable reference instead of Optional because downstream 114 * processing can throw checked exceptions. 115 * 116 * @param descType the type from a descriptor 117 * @return the extracted class name, or null 118 */ 119 String nameFromDescType(String descType) { 120 Matcher matcher = descTypePattern.matcher(descType); 121 if (matcher.matches()) { 122 return matcher.group(1); 123 } else { 124 return null; 125 } 126 } 127 128 Pattern descTypePattern = Pattern.compile("\\[*L(.*);"); 129 130 /** 131 * Given a ref type name, extracts and returns the class name from it, if any. 132 * Ref type names are obtained from a Class_info structure (JVMS 4.4.1) and from 133 * Fieldref_info, Methodref_info, and InterfaceMethodref_info structures (JVMS 4.4.2). 134 * They represent named classes or array classes mentioned by name, and they 135 * represent class or interface types that have the referenced field or method 136 * as a member. They have one of the following forms: 137 * 138 * [I // array of primitives, including multi-dimensional 139 * name // the named class 140 * [Lname; // array whose component is the named class (also multi-d) 141 * 142 * Notably, a plain class name doesn't have the L prefix and ; suffix, and 143 * primitives and void do not occur. 144 * 145 * Returns nullable reference instead of Optional because downstream 146 * processing can throw checked exceptions. 147 * 148 * @param refType a reference type name 149 * @return the extracted class name, or null 150 */ 151 String nameFromRefType(String refType) { 152 Matcher matcher = refTypePattern.matcher(refType); 153 if (matcher.matches()) { 154 return matcher.group(1); 155 } else if (refType.startsWith("[")) { 156 return null; 157 } else { 158 return refType; 159 } 160 } 161 162 Pattern refTypePattern = Pattern.compile("\\[+L(.*);"); 163 164 String typeKind(ClassFile cf) { 165 AccessFlags flags = cf.access_flags; 166 if (flags.is(ACC_ENUM)) { 167 return "enum"; 168 } else if (flags.is(ACC_ANNOTATION)) { 169 return "@interface"; 170 } else if (flags.is(ACC_INTERFACE)) { 171 return "interface"; 172 } else { 173 return "class"; 174 } 175 } 176 177 String dep(boolean forRemoval) { 178 return Messages.get(forRemoval ? "scan.dep.removal" : "scan.dep.normal"); 179 } 180 181 void printType(String key, ClassFile cf, String cname, boolean r) 182 throws ConstantPoolException { 183 out.println(Messages.get(key, typeKind(cf), cf.getName(), cname, dep(r))); 184 } 185 186 void printMethod(String key, ClassFile cf, String cname, String mname, String rtype, 187 boolean r) throws ConstantPoolException { 188 out.println(Messages.get(key, typeKind(cf), cf.getName(), cname, mname, rtype, dep(r))); 189 } 190 191 void printField(String key, ClassFile cf, String cname, String fname, 192 boolean r) throws ConstantPoolException { 193 out.println(Messages.get(key, typeKind(cf), cf.getName(), cname, fname, dep(r))); 194 } 195 196 void printFieldType(String key, ClassFile cf, String cname, String fname, String type, 197 boolean r) throws ConstantPoolException { 198 out.println(Messages.get(key, typeKind(cf), cf.getName(), cname, fname, type, dep(r))); 199 } 200 201 void printHasField(ClassFile cf, String fname, String type, boolean r) 202 throws ConstantPoolException { 203 out.println(Messages.get("scan.out.hasfield", typeKind(cf), cf.getName(), fname, type, dep(r))); 204 } 205 206 void printHasMethodParmType(ClassFile cf, String mname, String parmType, boolean r) 207 throws ConstantPoolException { 208 out.println(Messages.get("scan.out.methodparmtype", typeKind(cf), cf.getName(), mname, parmType, dep(r))); 209 } 210 211 void printHasMethodRetType(ClassFile cf, String mname, String retType, boolean r) 212 throws ConstantPoolException { 213 out.println(Messages.get("scan.out.methodrettype", typeKind(cf), cf.getName(), mname, retType, dep(r))); 214 } 215 216 void printHasOverriddenMethod(ClassFile cf, String overridden, String mname, String desc, boolean r) 217 throws ConstantPoolException { 218 out.println(Messages.get("scan.out.methodoverride", typeKind(cf), cf.getName(), overridden, 219 mname, desc, dep(r))); 220 } 221 222 void errorException(Exception ex) { 223 errorOccurred = true; 224 err.println(Messages.get("scan.err.exception", ex.toString())); 225 if (verbose) { 226 ex.printStackTrace(err); 227 } 228 } 229 230 void errorNoClass(String className) { 231 errorOccurred = true; 232 err.println(Messages.get("scan.err.noclass", className)); 233 } 234 235 void errorNoFile(String fileName) { 236 errorOccurred = true; 237 err.println(Messages.get("scan.err.nofile", fileName)); 238 } 239 240 void errorNoMethod(String className, String methodName, String desc) { 241 errorOccurred = true; 242 err.println(Messages.get("scan.err.nomethod", className, methodName, desc)); 243 } 244 245 /** 246 * Checks whether a member (method or field) is present in a class. 247 * The checkMethod parameter determines whether this checks for a method 248 * or for a field. 249 * 250 * @param targetClass the ClassFile of the class to search 251 * @param targetName the method or field's name 252 * @param targetDesc the methods descriptor (ignored if checkMethod is false) 253 * @param checkMethod true if checking for method, false if checking for field 254 * @return boolean indicating whether the member is present 255 * @throws ConstantPoolException if a constant pool entry cannot be found 256 */ 257 boolean isMemberPresent(ClassFile targetClass, 258 String targetName, 259 String targetDesc, 260 boolean checkMethod) 261 throws ConstantPoolException { 262 if (checkMethod) { 263 for (Method m : targetClass.methods) { 264 String mname = m.getName(targetClass.constant_pool); 265 String mdesc = targetClass.constant_pool.getUTF8Value(m.descriptor.index); 266 if (targetName.equals(mname) && targetDesc.equals(mdesc)) { 267 return true; 268 } 269 } 270 } else { 271 for (Field f : targetClass.fields) { 272 String fname = f.getName(targetClass.constant_pool); 273 if (targetName.equals(fname)) { 274 return true; 275 } 276 } 277 } 278 return false; 279 } 280 281 /** 282 * Adds all interfaces from this class to the deque of interfaces. 283 * 284 * @param intfs the deque of interfaces 285 * @param cf the ClassFile of this class 286 * @throws ConstantPoolException if a constant pool entry cannot be found 287 */ 288 void addInterfaces(Deque<String> intfs, ClassFile cf) 289 throws ConstantPoolException { 290 int count = cf.interfaces.length; 291 for (int i = 0; i < count; i++) { 292 intfs.addLast(cf.getInterfaceName(i)); 293 } 294 } 295 296 /** 297 * Resolves a member by searching this class and all its superclasses and 298 * implemented interfaces. 299 * 300 * TODO: handles a few too many cases; needs cleanup. 301 * 302 * TODO: refine error handling 303 * 304 * @param cf the ClassFile of this class 305 * @param startClassName the name of the class at which to start searching 306 * @param findName the member name to search for 307 * @param findDesc the method descriptor to search for (ignored for fields) 308 * @param resolveMethod true if resolving a method, false if resolving a field 309 * @param checkStartClass true if the start class should be searched, false if 310 * it should be skipped 311 * @return the name of the class where the member resolved, or null 312 * @throws ConstantPoolException if a constant pool entry cannot be found 313 */ 314 String resolveMember( 315 ClassFile cf, String startClassName, String findName, String findDesc, 316 boolean resolveMethod, boolean checkStartClass) 317 throws ConstantPoolException { 318 ClassFile startClass; 319 320 if (cf.getName().equals(startClassName)) { 321 startClass = cf; 322 } else { 323 startClass = finder.find(startClassName); 324 if (startClass == null) { 325 errorNoClass(startClassName); 326 return startClassName; 327 } 328 } 329 330 // follow super_class until it's 0, meaning we've reached Object 331 // accumulate interfaces of superclasses as we go along 332 333 ClassFile curClass = startClass; 334 Deque<String> intfs = new ArrayDeque<>(); 335 while (true) { 336 if ((checkStartClass || curClass != startClass) && 337 isMemberPresent(curClass, findName, findDesc, resolveMethod)) { 338 break; 339 } 340 341 if (curClass.super_class == 0) { // reached Object 342 curClass = null; 343 break; 344 } 345 346 String superName = curClass.getSuperclassName(); 347 curClass = finder.find(superName); 348 if (curClass == null) { 349 errorNoClass(superName); 350 break; 351 } 352 addInterfaces(intfs, curClass); 353 } 354 355 // search interfaces: add all interfaces and superinterfaces to queue 356 // search until it's empty 357 358 if (curClass == null) { 359 addInterfaces(intfs, startClass); 360 while (intfs.size() > 0) { 361 String intf = intfs.removeFirst(); 362 curClass = finder.find(intf); 363 if (curClass == null) { 364 errorNoClass(intf); 365 break; 366 } 367 368 if (isMemberPresent(curClass, findName, findDesc, resolveMethod)) { 369 break; 370 } 371 372 addInterfaces(intfs, curClass); 373 } 374 } 375 376 if (curClass == null) { 377 if (checkStartClass) { 378 errorNoMethod(startClassName, findName, findDesc); 379 return startClassName; 380 } else { 381 // TODO: refactor this 382 // checkStartClass == false means we're checking for overrides 383 // so not being able to resolve a method simply means there's 384 // no overriding, which isn't an error 385 return null; 386 } 387 } else { 388 String foundClassName = curClass.getName(); 389 return foundClassName; 390 } 391 } 392 393 /** 394 * Checks the superclass of this class. 395 * 396 * @param cf the ClassFile of this class 397 * @throws ConstantPoolException if a constant pool entry cannot be found 398 */ 399 void checkSuper(ClassFile cf) throws ConstantPoolException { 400 String sname = cf.getSuperclassName(); 401 DeprData dd = db.getTypeDeprecated(sname); 402 if (dd != null) { 403 printType("scan.out.extends", cf, sname, dd.isForRemoval()); 404 } 405 } 406 407 /** 408 * Checks the interfaces of this class. 409 * 410 * @param cf the ClassFile of this class 411 * @throws ConstantPoolException if a constant pool entry cannot be found 412 */ 413 void checkInterfaces(ClassFile cf) throws ConstantPoolException { 414 int ni = cf.interfaces.length; 415 for (int i = 0; i < ni; i++) { 416 String iname = cf.getInterfaceName(i); 417 DeprData dd = db.getTypeDeprecated(iname); 418 if (dd != null) { 419 printType("scan.out.implements", cf, iname, dd.isForRemoval()); 420 } 421 } 422 } 423 424 /** 425 * Checks Class_info entries in the constant pool. 426 * 427 * @param cf the ClassFile of this class 428 * @param entries constant pool entries collected from this class 429 * @throws ConstantPoolException if a constant pool entry cannot be found 430 */ 431 void checkClasses(ClassFile cf, CPEntries entries) throws ConstantPoolException { 432 for (ConstantPool.CONSTANT_Class_info ci : entries.classes) { 433 String name = nameFromRefType(ci.getName()); 434 if (name != null) { 435 DeprData dd = db.getTypeDeprecated(name); 436 if (dd != null) { 437 printType("scan.out.usesclass", cf, name, dd.isForRemoval()); 438 } 439 } 440 } 441 } 442 443 /** 444 * Checks methods referred to from the constant pool. 445 * 446 * @param cf the ClassFile of this class 447 * @param clname the class name 448 * @param nti the NameAndType_info from a MethodRef or InterfaceMethodRef entry 449 * @param msgKey message key for localization 450 * @throws ConstantPoolException if a constant pool entry cannot be found 451 */ 452 void checkMethodRef(ClassFile cf, 453 String clname, 454 CONSTANT_NameAndType_info nti, 455 String msgKey) throws ConstantPoolException { 456 String name = nti.getName(); 457 String type = nti.getType(); 458 clname = nameFromRefType(clname); 459 if (clname != null) { 460 clname = resolveMember(cf, clname, name, type, true, true); 461 DeprData dd = db.getMethodDeprecated(clname, name, type); 462 if (dd != null) { 463 printMethod(msgKey, cf, clname, name, type, dd.isForRemoval()); 464 } 465 } 466 } 467 468 /** 469 * Checks fields referred to from the constant pool. 470 * 471 * @param cf the ClassFile of this class 472 * @throws ConstantPoolException if a constant pool entry cannot be found 473 */ 474 void checkFieldRef(ClassFile cf, 475 ConstantPool.CONSTANT_Fieldref_info fri) throws ConstantPoolException { 476 String clname = nameFromRefType(fri.getClassName()); 477 CONSTANT_NameAndType_info nti = fri.getNameAndTypeInfo(); 478 String name = nti.getName(); 479 String type = nti.getType(); 480 481 if (clname != null) { 482 clname = resolveMember(cf, clname, name, type, false, true); 483 DeprData dd = db.getFieldDeprecated(clname, name); 484 if (dd != null) { 485 printField("scan.out.usesfield", cf, clname, name, dd.isForRemoval()); 486 } 487 } 488 } 489 490 /** 491 * Checks the fields declared in this class. 492 * 493 * @param cf the ClassFile of this class 494 * @throws ConstantPoolException if a constant pool entry cannot be found 495 */ 496 void checkFields(ClassFile cf) throws ConstantPoolException { 497 for (Field f : cf.fields) { 498 String type = nameFromDescType(cf.constant_pool.getUTF8Value(f.descriptor.index)); 499 if (type != null) { 500 DeprData dd = db.getTypeDeprecated(type); 501 if (dd != null) { 502 printHasField(cf, f.getName(cf.constant_pool), type, dd.isForRemoval()); 503 } 504 } 505 } 506 } 507 508 /** 509 * Checks the methods declared in this class. 510 * 511 * @param cf the ClassFile object of this class 512 * @throws ConstantPoolException if a constant pool entry cannot be found 513 */ 514 void checkMethods(ClassFile cf) throws ConstantPoolException { 515 for (Method m : cf.methods) { 516 String mname = m.getName(cf.constant_pool); 517 String desc = cf.constant_pool.getUTF8Value(m.descriptor.index); 518 MethodSig sig = MethodSig.fromDesc(desc); 519 DeprData dd; 520 521 for (String parm : sig.getParameters()) { 522 parm = nameFromDescType(parm); 523 if (parm != null) { 524 dd = db.getTypeDeprecated(parm); 525 if (dd != null) { 526 printHasMethodParmType(cf, mname, parm, dd.isForRemoval()); 527 } 528 } 529 } 530 531 String ret = nameFromDescType(sig.getReturnType()); 532 if (ret != null) { 533 dd = db.getTypeDeprecated(ret); 534 if (dd != null) { 535 printHasMethodRetType(cf, mname, ret, dd.isForRemoval()); 536 } 537 } 538 539 // check overrides 540 String overridden = resolveMember(cf, cf.getName(), mname, desc, true, false); 541 if (overridden != null) { 542 dd = db.getMethodDeprecated(overridden, mname, desc); 543 if (dd != null) { 544 printHasOverriddenMethod(cf, overridden, mname, desc, dd.isForRemoval()); 545 } 546 } 547 } 548 } 549 550 /** 551 * Processes a single class file. 552 * 553 * @param cf the ClassFile of the class 554 * @throws ConstantPoolException if a constant pool entry cannot be found 555 */ 556 void processClass(ClassFile cf) throws ConstantPoolException { 557 if (verbose) { 558 out.println(Messages.get("scan.process.class", cf.getName())); 559 } 560 561 CPEntries entries = CPEntries.loadFrom(cf); 562 563 checkSuper(cf); 564 checkInterfaces(cf); 565 checkClasses(cf, entries); 566 567 for (ConstantPool.CONSTANT_Methodref_info mri : entries.methodRefs) { 568 String clname = mri.getClassName(); 569 CONSTANT_NameAndType_info nti = mri.getNameAndTypeInfo(); 570 checkMethodRef(cf, clname, nti, "scan.out.usesmethod"); 571 } 572 573 for (ConstantPool.CONSTANT_InterfaceMethodref_info imri : entries.intfMethodRefs) { 574 String clname = imri.getClassName(); 575 CONSTANT_NameAndType_info nti = imri.getNameAndTypeInfo(); 576 checkMethodRef(cf, clname, nti, "scan.out.usesintfmethod"); 577 } 578 579 for (ConstantPool.CONSTANT_Fieldref_info fri : entries.fieldRefs) { 580 checkFieldRef(cf, fri); 581 } 582 583 checkFields(cf); 584 checkMethods(cf); 585 } 586 587 /** 588 * Scans a jar file for uses of deprecated APIs. 589 * 590 * @param jarname the jar file to process 591 * @return true on success, false on failure 592 */ 593 public boolean scanJar(String jarname) { 594 try (JarFile jf = new JarFile(jarname)) { 595 out.println(Messages.get("scan.head.jar", jarname)); 596 finder.addJar(jarname); 597 Enumeration<JarEntry> entries = jf.entries(); 598 while (entries.hasMoreElements()) { 599 JarEntry entry = entries.nextElement(); 600 String name = entry.getName(); 601 if (name.endsWith(".class") 602 && !name.endsWith("package-info.class") 603 && !name.endsWith("module-info.class")) { 604 processClass(ClassFile.read(jf.getInputStream(entry))); 605 } 606 } 607 return true; 608 } catch (NoSuchFileException nsfe) { 609 errorNoFile(jarname); 610 } catch (IOException | ConstantPoolException ex) { 611 errorException(ex); 612 } 613 return false; 614 } 615 616 /** 617 * Scans class files in the named directory hierarchy for uses of deprecated APIs. 618 * 619 * @param dirname the directory hierarchy to process 620 * @return true on success, false on failure 621 */ 622 public boolean scanDir(String dirname) { 623 Path base = Paths.get(dirname); 624 int baseCount = base.getNameCount(); 625 finder.addDir(dirname); 626 try (Stream<Path> paths = Files.walk(Paths.get(dirname))) { 627 List<Path> classes = 628 paths.filter(p -> p.getNameCount() > baseCount) 629 .filter(path -> path.toString().endsWith(".class")) 630 .filter(path -> !path.toString().endsWith("package-info.class")) 631 .filter(path -> !path.toString().endsWith("module-info.class")) 632 .collect(Collectors.toList()); 633 634 out.println(Messages.get("scan.head.dir", dirname)); 635 636 for (Path p : classes) { 637 processClass(ClassFile.read(p)); 638 } 639 return true; 640 } catch (IOException | ConstantPoolException ex) { 641 errorException(ex); 642 return false; 643 } 644 } 645 646 /** 647 * Scans the named class for uses of deprecated APIs. 648 * 649 * @param className the class to scan 650 * @return true on success, false on failure 651 */ 652 public boolean processClassName(String className) { 653 try { 654 ClassFile cf = finder.find(className); 655 if (cf == null) { 656 errorNoClass(className); 657 return false; 658 } else { 659 processClass(cf); 660 return true; 661 } 662 } catch (ConstantPoolException ex) { 663 errorException(ex); 664 return false; 665 } 666 } 667 668 /** 669 * Scans the named class file for uses of deprecated APIs. 670 * 671 * @param fileName the class file to scan 672 * @return true on success, false on failure 673 */ 674 public boolean processClassFile(String fileName) { 675 Path path = Paths.get(fileName); 676 try { 677 ClassFile cf = ClassFile.read(path); 678 processClass(cf); 679 return true; 680 } catch (NoSuchFileException nsfe) { 681 errorNoFile(fileName); 682 } catch (IOException | ConstantPoolException ex) { 683 errorException(ex); 684 } 685 return false; 686 } 687} 688