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