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