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