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