test/tools/javac/diags/CheckResourceKeys.java

Tue, 13 Mar 2012 15:43:40 -0700

author
jjg
date
Tue, 13 Mar 2012 15:43:40 -0700
changeset 1230
b14d9583ce92
parent 962
0ff2bbd38f10
child 1352
d4b3cb1ece84
permissions
-rw-r--r--

7150368: javac should include basic ability to generate native headers
Reviewed-by: mcimadamore, darcy, ohrstrom

     1 /*
     2  * Copyright (c) 2010, 2012, 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             "os.name",
   263             "user.dir",
   264             // file names
   265             "ct.sym",
   266             "rt.jar",
   267             "tools.jar",
   268             // -XD option names
   269             "process.packages",
   270             "ignore.symbol.file",
   271             // prefix/embedded strings
   272             "compiler.",
   273             "compiler.misc.",
   274             "count.",
   275             "illegal.",
   276             "javac.",
   277             "verbose."
   278     ));
   280     /**
   281      * Look for a resource that ends in this string fragment.
   282      */
   283     boolean hasMatch(Set<String> resourceKeys, String s) {
   284         for (String rk: resourceKeys) {
   285             if (rk.endsWith(s))
   286                 return true;
   287         }
   288         return false;
   289     }
   291     /**
   292      * Get the set of strings from (most of) the javac classfiles.
   293      */
   294     Set<String> getCodeStrings() throws IOException {
   295         Set<String> results = new TreeSet<String>();
   296         JavaCompiler c = ToolProvider.getSystemJavaCompiler();
   297         JavaFileManager fm = c.getStandardFileManager(null, null, null);
   298         JavaFileManager.Location javacLoc = findJavacLocation(fm);
   299         String[] pkgs = {
   300             "javax.annotation.processing",
   301             "javax.lang.model",
   302             "javax.tools",
   303             "com.sun.source",
   304             "com.sun.tools.javac"
   305         };
   306         for (String pkg: pkgs) {
   307             for (JavaFileObject fo: fm.list(javacLoc,
   308                     pkg, EnumSet.of(JavaFileObject.Kind.CLASS), true)) {
   309                 String name = fo.getName();
   310                 // ignore resource files, and files which are not really part of javac
   311                 if (name.contains("resources")
   312                         || name.contains("Launcher.class")
   313                         || name.contains("CreateSymbols.class"))
   314                     continue;
   315                 scan(fo, results);
   316             }
   317         }
   318         return results;
   319     }
   321     // depending on how the test is run, javac may be on bootclasspath or classpath
   322     JavaFileManager.Location findJavacLocation(JavaFileManager fm) {
   323         JavaFileManager.Location[] locns =
   324             { StandardLocation.PLATFORM_CLASS_PATH, StandardLocation.CLASS_PATH };
   325         try {
   326             for (JavaFileManager.Location l: locns) {
   327                 JavaFileObject fo = fm.getJavaFileForInput(l,
   328                     "com.sun.tools.javac.Main", JavaFileObject.Kind.CLASS);
   329                 if (fo != null)
   330                     return l;
   331             }
   332         } catch (IOException e) {
   333             throw new Error(e);
   334         }
   335         throw new IllegalStateException("Cannot find javac");
   336     }
   338     /**
   339      * Get the set of strings from a class file.
   340      * Only strings that look like they might be a resource key are returned.
   341      */
   342     void scan(JavaFileObject fo, Set<String> results) throws IOException {
   343         InputStream in = fo.openInputStream();
   344         try {
   345             ClassFile cf = ClassFile.read(in);
   346             for (ConstantPool.CPInfo cpinfo: cf.constant_pool.entries()) {
   347                 if (cpinfo.getTag() == ConstantPool.CONSTANT_Utf8) {
   348                     String v = ((ConstantPool.CONSTANT_Utf8_info) cpinfo).value;
   349                     if (v.matches("[A-Za-z0-9-_.]+"))
   350                         results.add(v);
   351                 }
   352             }
   353         } catch (ConstantPoolException ignore) {
   354         } finally {
   355             in.close();
   356         }
   357     }
   359     /**
   360      * Get the set of keys from the javac resource bundles.
   361      */
   362     Set<String> getResourceKeys() {
   363         Set<String> results = new TreeSet<String>();
   364         for (String name : new String[]{"javac", "compiler"}) {
   365             ResourceBundle b =
   366                     ResourceBundle.getBundle("com.sun.tools.javac.resources." + name);
   367             results.addAll(b.keySet());
   368         }
   369         return results;
   370     }
   372     /**
   373      * Report an error.
   374      */
   375     void error(String msg) {
   376         System.err.println("Error: " + msg);
   377         errors++;
   378     }
   380     int errors;
   381 }

mercurial