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