1 /* 2 * Copyright (c) 2010, 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. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* 25 * @test 26 * @bug 6964768 6964461 6964469 6964487 6964460 6964481 6980021 27 * @summary need test program to validate javac resource bundles 28 * @modules jdk.compiler/com.sun.tools.javac.code 29 * jdk.compiler/com.sun.tools.javac.resources:open 30 * jdk.jdeps/com.sun.tools.classfile 31 */ 32 33 import java.io.*; 34 import java.lang.reflect.Layer; 35 import java.lang.reflect.Module; 36 import java.util.*; 37 import javax.tools.*; 38 import com.sun.tools.classfile.*; 39 import com.sun.tools.javac.code.Lint.LintCategory; 40 41 /** 42 * Compare string constants in javac classes against keys in javac resource bundles. 43 */ 44 public class CheckResourceKeys { 45 /** 46 * Main program. 47 * Options: 48 * -finddeadkeys 49 * look for keys in resource bundles that are no longer required 50 * -findmissingkeys 51 * look for keys in resource bundles that are missing 52 * 53 * @throws Exception if invoked by jtreg and errors occur 54 */ 55 public static void main(String... args) throws Exception { 56 CheckResourceKeys c = new CheckResourceKeys(); 57 if (c.run(args)) 58 return; 59 60 if (is_jtreg()) 61 throw new Exception(c.errors + " errors occurred"); 62 else 63 System.exit(1); 64 } 65 66 static boolean is_jtreg() { 67 return (System.getProperty("test.src") != null); 68 } 69 70 /** 71 * Main entry point. 72 */ 73 boolean run(String... args) throws Exception { 74 boolean findDeadKeys = false; 75 boolean findMissingKeys = false; 76 77 if (args.length == 0) { 78 if (is_jtreg()) { 79 findDeadKeys = true; 80 findMissingKeys = true; 81 } else { 82 System.err.println("Usage: java CheckResourceKeys <options>"); 83 System.err.println("where options include"); 84 System.err.println(" -finddeadkeys find keys in resource bundles which are no longer required"); 85 System.err.println(" -findmissingkeys find keys in resource bundles that are required but missing"); 86 return true; 87 } 88 } else { 89 for (String arg: args) { 90 if (arg.equalsIgnoreCase("-finddeadkeys")) 91 findDeadKeys = true; 92 else if (arg.equalsIgnoreCase("-findmissingkeys")) 93 findMissingKeys = true; 94 else 95 error("bad option: " + arg); 96 } 97 } 98 99 if (errors > 0) 100 return false; 101 102 Set<String> codeStrings = getCodeStrings(); 103 Set<String> resourceKeys = getResourceKeys(); 104 105 if (findDeadKeys) 106 findDeadKeys(codeStrings, resourceKeys); 107 108 if (findMissingKeys) 109 findMissingKeys(codeStrings, resourceKeys); 110 111 return (errors == 0); 112 } 113 114 /** 115 * Find keys in resource bundles which are probably no longer required. 116 * A key is probably required if there is a string fragment in the code 117 * that is part of the resource key, or if the key is well-known 118 * according to various pragmatic rules. 119 */ 120 void findDeadKeys(Set<String> codeStrings, Set<String> resourceKeys) { 121 String[] prefixes = { 122 "compiler.err.", "compiler.warn.", "compiler.note.", "compiler.misc.", 123 "javac." 124 }; 125 for (String rk: resourceKeys) { 126 // some keys are used directly, without a prefix. 127 if (codeStrings.contains(rk)) 128 continue; 129 130 // remove standard prefix 131 String s = null; 132 for (int i = 0; i < prefixes.length && s == null; i++) { 133 if (rk.startsWith(prefixes[i])) { 134 s = rk.substring(prefixes[i].length()); 135 } 136 } 137 if (s == null) { 138 error("Resource key does not start with a standard prefix: " + rk); 139 continue; 140 } 141 142 if (codeStrings.contains(s)) 143 continue; 144 145 // keys ending in .1 are often synthesized 146 if (s.endsWith(".1") && codeStrings.contains(s.substring(0, s.length() - 2))) 147 continue; 148 149 // verbose keys are generated by ClassReader.printVerbose 150 if (s.startsWith("verbose.") && codeStrings.contains(s.substring(8))) 151 continue; 152 153 // mandatory warning messages are synthesized with no characteristic substring 154 if (isMandatoryWarningString(s)) 155 continue; 156 157 // check known (valid) exceptions 158 if (knownRequired.contains(rk)) 159 continue; 160 161 // check known suspects 162 if (needToInvestigate.contains(rk)) 163 continue; 164 165 //check lint description keys: 166 if (s.startsWith("opt.Xlint.desc.")) { 167 String option = s.substring(15); 168 boolean found = false; 169 170 for (LintCategory lc : LintCategory.values()) { 171 if (option.equals(lc.option)) 172 found = true; 173 } 174 175 if (found) 176 continue; 177 } 178 179 error("Resource key not found in code: " + rk); 180 } 181 } 182 183 /** 184 * The keys for mandatory warning messages are all synthesized and do not 185 * have a significant recognizable substring to look for. 186 */ 187 private boolean isMandatoryWarningString(String s) { 188 String[] bases = { "deprecated", "unchecked", "varargs" }; 189 String[] tails = { ".filename", ".filename.additional", ".plural", ".plural.additional", ".recompile" }; 190 for (String b: bases) { 191 if (s.startsWith(b)) { 192 String tail = s.substring(b.length()); 193 for (String t: tails) { 194 if (tail.equals(t)) 195 return true; 196 } 197 } 198 } 199 return false; 200 } 201 202 Set<String> knownRequired = new TreeSet<String>(Arrays.asList( 203 // See Resolve.getErrorKey 204 "compiler.err.cant.resolve.args", 205 "compiler.err.cant.resolve.args.params", 206 "compiler.err.cant.resolve.location.args", 207 "compiler.err.cant.resolve.location.args.params", 208 "compiler.misc.cant.resolve.location.args", 209 "compiler.misc.cant.resolve.location.args.params", 210 // JavaCompiler, reports #errors and #warnings 211 "compiler.misc.count.error", 212 "compiler.misc.count.error.plural", 213 "compiler.misc.count.warn", 214 "compiler.misc.count.warn.plural", 215 // Used for LintCategory 216 "compiler.warn.lintOption", 217 // Other 218 "compiler.misc.base.membership" // (sic) 219 )); 220 221 222 Set<String> needToInvestigate = new TreeSet<String>(Arrays.asList( 223 "compiler.misc.fatal.err.cant.close.loader", // Supressed by JSR308 224 "compiler.err.cant.read.file", // UNUSED 225 "compiler.err.illegal.self.ref", // UNUSED 226 "compiler.err.io.exception", // UNUSED 227 "compiler.err.limit.pool.in.class", // UNUSED 228 "compiler.err.name.reserved.for.internal.use", // UNUSED 229 "compiler.err.no.match.entry", // UNUSED 230 "compiler.err.not.within.bounds.explain", // UNUSED 231 "compiler.err.signature.doesnt.match.intf", // UNUSED 232 "compiler.err.signature.doesnt.match.supertype", // UNUSED 233 "compiler.err.type.var.more.than.once", // UNUSED 234 "compiler.err.type.var.more.than.once.in.result", // UNUSED 235 "compiler.misc.non.denotable.type", // UNUSED 236 "compiler.misc.unnamed.package", // should be required, CR 6964147 237 "compiler.warn.proc.type.already.exists", // TODO in JavacFiler 238 "javac.err.invalid.arg", // UNUSED ?? 239 "javac.opt.arg.class", // UNUSED ?? 240 "javac.opt.arg.pathname", // UNUSED ?? 241 "javac.opt.moreinfo", // option commented out 242 "javac.opt.nogj", // UNUSED 243 "javac.opt.printsearch", // option commented out 244 "javac.opt.prompt", // option commented out 245 "javac.opt.s" // option commented out 246 )); 247 248 /** 249 * For all strings in the code that look like they might be fragments of 250 * a resource key, verify that a key exists. 251 */ 252 void findMissingKeys(Set<String> codeStrings, Set<String> resourceKeys) { 253 for (String cs: codeStrings) { 254 if (cs.matches("[A-Za-z][^.]*\\..*")) { 255 // ignore filenames (i.e. in SourceFile attribute 256 if (cs.matches(".*\\.java")) 257 continue; 258 // ignore package and class names 259 if (cs.matches("(com|java|javax|jdk|sun)\\.[A-Za-z.]+")) 260 continue; 261 if (cs.matches("(java|javax|sun)\\.")) 262 continue; 263 // ignore debug flag names 264 if (cs.startsWith("debug.")) 265 continue; 266 // ignore should-stop flag names 267 if (cs.startsWith("should-stop.")) 268 continue; 269 // ignore diagsformat flag names 270 if (cs.startsWith("diags.")) 271 continue; 272 // explicit known exceptions 273 if (noResourceRequired.contains(cs)) 274 continue; 275 // look for matching resource 276 if (hasMatch(resourceKeys, cs)) 277 continue; 278 error("no match for \"" + cs + "\""); 279 } 280 } 281 } 282 // where 283 private Set<String> noResourceRequired = new HashSet<String>(Arrays.asList( 284 // module names 285 "jdk.compiler", 286 "jdk.javadoc", 287 // system properties 288 "application.home", // in Paths.java 289 "env.class.path", 290 "line.separator", 291 "os.name", 292 "user.dir", 293 // file names 294 "ct.sym", 295 "rt.jar", 296 "jfxrt.jar", 297 "module-info.class", 298 "jrt-fs.jar", 299 // -XD option names 300 "process.packages", 301 "ignore.symbol.file", 302 "fileManager.deferClose", 303 // prefix/embedded strings 304 "compiler.", 305 "compiler.misc.", 306 "opt.Xlint.desc.", 307 "count.", 308 "illegal.", 309 "java.", 310 "javac.", 311 "verbose.", 312 "locn." 313 )); 314 315 /** 316 * Look for a resource that ends in this string fragment. 317 */ 318 boolean hasMatch(Set<String> resourceKeys, String s) { 319 for (String rk: resourceKeys) { 320 if (rk.endsWith(s)) 321 return true; 322 } 323 return false; 324 } 325 326 /** 327 * Get the set of strings from (most of) the javac classfiles. 328 */ 329 Set<String> getCodeStrings() throws IOException { 330 Set<String> results = new TreeSet<String>(); 331 JavaCompiler c = ToolProvider.getSystemJavaCompiler(); 332 try (JavaFileManager fm = c.getStandardFileManager(null, null, null)) { 333 JavaFileManager.Location javacLoc = findJavacLocation(fm); 334 String[] pkgs = { 335 "javax.annotation.processing", 336 "javax.lang.model", 337 "javax.tools", 338 "com.sun.source", 339 "com.sun.tools.javac" 340 }; 341 for (String pkg: pkgs) { 342 for (JavaFileObject fo: fm.list(javacLoc, 343 pkg, EnumSet.of(JavaFileObject.Kind.CLASS), true)) { 344 String name = fo.getName(); 345 // ignore resource files, and files which are not really part of javac 346 if (name.matches(".*resources.[A-Za-z_0-9]+\\.class.*") 347 || name.matches(".*CreateSymbols\\.class.*")) 348 continue; 349 scan(fo, results); 350 } 351 } 352 return results; 353 } 354 } 355 356 // depending on how the test is run, javac may be on bootclasspath or classpath 357 JavaFileManager.Location findJavacLocation(JavaFileManager fm) { 358 JavaFileManager.Location[] locns = 359 { StandardLocation.PLATFORM_CLASS_PATH, StandardLocation.CLASS_PATH }; 360 try { 361 for (JavaFileManager.Location l: locns) { 362 JavaFileObject fo = fm.getJavaFileForInput(l, 363 "com.sun.tools.javac.Main", JavaFileObject.Kind.CLASS); 364 if (fo != null) 365 return l; 366 } 367 } catch (IOException e) { 368 throw new Error(e); 369 } 370 throw new IllegalStateException("Cannot find javac"); 371 } 372 373 /** 374 * Get the set of strings from a class file. 375 * Only strings that look like they might be a resource key are returned. 376 */ 377 void scan(JavaFileObject fo, Set<String> results) throws IOException { 378 InputStream in = fo.openInputStream(); 379 try { 380 ClassFile cf = ClassFile.read(in); 381 for (ConstantPool.CPInfo cpinfo: cf.constant_pool.entries()) { 382 if (cpinfo.getTag() == ConstantPool.CONSTANT_Utf8) { 383 String v = ((ConstantPool.CONSTANT_Utf8_info) cpinfo).value; 384 if (v.matches("[A-Za-z0-9-_.]+")) 385 results.add(v); 386 } 387 } 388 } catch (ConstantPoolException ignore) { 389 } finally { 390 in.close(); 391 } 392 } 393 394 /** 395 * Get the set of keys from the javac resource bundles. 396 */ 397 Set<String> getResourceKeys() { 398 Module jdk_compiler = Layer.boot().findModule("jdk.compiler").get(); 399 Set<String> results = new TreeSet<String>(); 400 for (String name : new String[]{"javac", "compiler"}) { 401 ResourceBundle b = 402 ResourceBundle.getBundle("com.sun.tools.javac.resources." + name, jdk_compiler); 403 results.addAll(b.keySet()); 404 } 405 return results; 406 } 407 408 /** 409 * Report an error. 410 */ 411 void error(String msg) { 412 System.err.println("Error: " + msg); 413 errors++; 414 } 415 416 int errors; 417 }