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