test/tools/javac/diags/CheckResourceKeys.java

Wed, 06 Apr 2011 20:33:44 -0700

author
ohair
date
Wed, 06 Apr 2011 20:33:44 -0700
changeset 962
0ff2bbd38f10
parent 916
cb9493a80341
child 1230
b14d9583ce92
permissions
-rw-r--r--

7033660: Update copyright year to 2011 on any files changed in 2011
Reviewed-by: dholmes

     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  */
    24 /*
    25  * @test
    26  * @bug 6964768 6964461 6964469 6964487 6964460 6964481 6980021
    27  * @summary need test program to validate javac resource bundles
    28  */
    30 import java.io.*;
    31 import java.util.*;
    32 import javax.tools.*;
    33 import com.sun.tools.classfile.*;
    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;
    54         if (is_jtreg())
    55             throw new Exception(c.errors + " errors occurred");
    56         else
    57             System.exit(1);
    58     }
    60     static boolean is_jtreg() {
    61         return (System.getProperty("test.src") != null);
    62     }
    64     /**
    65      * Main entry point.
    66      */
    67     boolean run(String... args) throws Exception {
    68         boolean findDeadKeys = false;
    69         boolean findMissingKeys = false;
    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         }
    93         if (errors > 0)
    94             return false;
    96         Set<String> codeStrings = getCodeStrings();
    97         Set<String> resourceKeys = getResourceKeys();
    99         if (findDeadKeys)
   100             findDeadKeys(codeStrings, resourceKeys);
   102         if (findMissingKeys)
   103             findMissingKeys(codeStrings, resourceKeys);
   105         return (errors == 0);
   106     }
   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;
   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             }
   136             if (codeStrings.contains(s))
   137                 continue;
   139             // keys ending in .1 are often synthesized
   140             if (s.endsWith(".1") && codeStrings.contains(s.substring(0, s.length() - 2)))
   141                 continue;
   143             // verbose keys are generated by ClassReader.printVerbose
   144             if (s.startsWith("verbose.") && codeStrings.contains(s.substring(8)))
   145                 continue;
   147             // mandatory warning messages are synthesized with no characteristic substring
   148             if (isMandatoryWarningString(s))
   149                 continue;
   151             // check known (valid) exceptions
   152             if (knownRequired.contains(rk))
   153                 continue;
   155             // check known suspects
   156             if (needToInvestigate.contains(rk))
   157                 continue;
   159             error("Resource key not found in code: " + rk);
   160         }
   161     }
   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     }
   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         ));
   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         ));
   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     ));
   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     }
   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     }
   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     }
   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     }
   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     }
   371     /**
   372      * Report an error.
   373      */
   374     void error(String msg) {
   375         System.err.println("Error: " + msg);
   376         errors++;
   377     }
   379     int errors;
   380 }

mercurial