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