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