jdk/make/src/classes/build/tools/deps/CheckDeps.java
changeset 28766 b2569bf771f1
parent 28763 4a400033227b
parent 28758 343c4ddd1520
child 28767 40a8ae3dab56
equal deleted inserted replaced
28763:4a400033227b 28766:b2569bf771f1
     1 /*
       
     2  * Copyright (c) 2013, 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.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 
       
    26 package build.tools.deps;
       
    27 
       
    28 import java.nio.file.DirectoryStream;
       
    29 import java.nio.file.Files;
       
    30 import java.nio.file.Path;
       
    31 import java.nio.file.Paths;
       
    32 import java.nio.charset.StandardCharsets;
       
    33 import java.util.Set;
       
    34 import java.util.HashSet;
       
    35 import java.util.Map;
       
    36 import java.util.HashMap;
       
    37 import java.util.Enumeration;
       
    38 import java.util.Properties;
       
    39 import java.util.jar.JarEntry;
       
    40 import java.util.jar.JarFile;
       
    41 import java.io.InputStream;
       
    42 import java.io.InputStreamReader;
       
    43 import java.net.URL;
       
    44 
       
    45 import com.sun.tools.classfile.ClassFile;
       
    46 import com.sun.tools.classfile.Dependencies;
       
    47 import com.sun.tools.classfile.Dependency;
       
    48 
       
    49 /**
       
    50  * A simple tool to check the JAR files in a JRE image to ensure that there
       
    51  * aren't any references to types that do not exist. The tool is intended to
       
    52  * be used in the JDK "profiles" build to help ensure that the profile
       
    53  * definitions are kept up to date.
       
    54  */
       
    55 
       
    56 public class CheckDeps {
       
    57 
       
    58     // classfile API for finding dependencies
       
    59     static final Dependency.Finder finder = Dependencies.getClassDependencyFinder();
       
    60 
       
    61     // "known types", found in rt.jar or other JAR files
       
    62     static final Set<String> knownTypes = new HashSet<>();
       
    63 
       
    64     // References to unknown types. The map key is the unknown type, the
       
    65     // map value is the set of classes that reference it.
       
    66     static final Map<String,Set<String>> unknownRefs = new HashMap<>();
       
    67 
       
    68     // The property name is the name of an unknown type that is allowed to be
       
    69     // references. The property value is a comma separated list of the types
       
    70     // that are allowed to reference it. The list also includes the names of
       
    71     // the profiles that the reference is allowed.
       
    72     static final Properties allowedBadRefs = new Properties();
       
    73 
       
    74     /**
       
    75      * Returns the class name for the given class file. In the case of inner
       
    76      * classes then the enclosing class is returned in order to keep the
       
    77      * rules simple.
       
    78      */
       
    79     static String toClassName(String s) {
       
    80         int i = s.indexOf('$');
       
    81         if (i > 0)
       
    82             s = s.substring(0, i);
       
    83         return s.replace("/", ".");
       
    84     }
       
    85 
       
    86     /**
       
    87      * Analyze the dependencies of all classes in the given JAR file. The
       
    88      * method updates knownTypes and unknownRefs as part of the analysis.
       
    89      */
       
    90     static void analyzeDependencies(Path jarpath) throws Exception {
       
    91         System.out.format("Analyzing %s%n", jarpath);
       
    92         try (JarFile jf = new JarFile(jarpath.toFile())) {
       
    93             Enumeration<JarEntry> entries = jf.entries();
       
    94             while (entries.hasMoreElements()) {
       
    95                 JarEntry e = entries.nextElement();
       
    96                 String name = e.getName();
       
    97                 if (name.endsWith(".class")) {
       
    98                     ClassFile cf = ClassFile.read(jf.getInputStream(e));
       
    99                     for (Dependency d : finder.findDependencies(cf)) {
       
   100                         String origin = toClassName(d.getOrigin().getName());
       
   101                         String target = toClassName(d.getTarget().getName());
       
   102 
       
   103                         // origin is now known
       
   104                         unknownRefs.remove(origin);
       
   105                         knownTypes.add(origin);
       
   106 
       
   107                         // if the target is not known then record the reference
       
   108                         if (!knownTypes.contains(target)) {
       
   109                             Set<String> refs = unknownRefs.get(target);
       
   110                             if (refs == null) {
       
   111                                 // first time seeing this unknown type
       
   112                                 refs = new HashSet<>();
       
   113                                 unknownRefs.put(target, refs);
       
   114                             }
       
   115                             refs.add(origin);
       
   116                         }
       
   117                     }
       
   118                 }
       
   119             }
       
   120         }
       
   121     }
       
   122 
       
   123     /**
       
   124      * We have closure (no references to types that do not exist) if
       
   125      * unknownRefs is empty. When unknownRefs is not empty then it should
       
   126      * only contain references that are allowed to be present (these are
       
   127      * loaded from the refs.allowed properties file).
       
   128      *
       
   129      * @param the profile that is being tested, this determines the exceptions
       
   130      *   in {@code allowedBadRefs} that apply.
       
   131      *
       
   132      * @return {@code true} if there are no missing types or the only references
       
   133      *   to missing types are described by {@code allowedBadRefs}.
       
   134      */
       
   135     static boolean checkClosure(String profile) {
       
   136         // process the references to types that do not exist.
       
   137         boolean fail = false;
       
   138         for (Map.Entry<String,Set<String>> entry: unknownRefs.entrySet()) {
       
   139             String target = entry.getKey();
       
   140             for (String origin: entry.getValue()) {
       
   141                 // check if origin -> target allowed
       
   142                 String value = allowedBadRefs.getProperty(target);
       
   143                 if (value == null) {
       
   144                     System.err.format("%s -> %s (unknown type)%n", origin, target);
       
   145                     fail = true;
       
   146                 } else {
       
   147                     // target is known, check if the origin is one that we
       
   148                     // expect and that the exception applies to the profile.
       
   149                     boolean found = false;
       
   150                     boolean applicable = false;
       
   151                     for (String s: value.split(",")) {
       
   152                         s = s.trim();
       
   153                         if (s.equals(origin))
       
   154                             found = true;
       
   155                         if (s.equals(profile))
       
   156                             applicable = true;
       
   157                     }
       
   158                     if (!found || !applicable) {
       
   159                         if (!found) {
       
   160                             System.err.format("%s -> %s (not allowed)%n", origin, target);
       
   161                         } else {
       
   162                             System.err.format("%s -> %s (reference not applicable to %s)%n",
       
   163                                 origin, target, profile);
       
   164                         }
       
   165                         fail = true;
       
   166                     }
       
   167                 }
       
   168 
       
   169             }
       
   170         }
       
   171 
       
   172         return !fail;
       
   173     }
       
   174 
       
   175     static void fail(URL url) throws Exception {
       
   176         System.err.println("One or more unexpected references encountered");
       
   177         if (url != null)
       
   178             System.err.format("Check %s is up to date%n", Paths.get(url.toURI()));
       
   179         System.exit(-1);
       
   180     }
       
   181 
       
   182     public static void main(String[] args) throws Exception {
       
   183         // load properties file so that we know what missing types that are
       
   184         // allowed to be referenced.
       
   185         URL url = CheckDeps.class.getResource("refs.allowed");
       
   186         if (url != null) {
       
   187             try (InputStream in = url.openStream()) {
       
   188                 allowedBadRefs.load(new InputStreamReader(in, StandardCharsets.UTF_8));
       
   189             }
       
   190         }
       
   191 
       
   192         if (args.length != 2) {
       
   193             System.err.println("Usage: java CheckDeps <image> <profile>");
       
   194             System.exit(-1);
       
   195         }
       
   196 
       
   197         String image = args[0];
       
   198         String profile = args[1];
       
   199 
       
   200         // process JAR files on boot class path
       
   201         Path lib = Paths.get(image, "lib");
       
   202         try (DirectoryStream<Path> stream = Files.newDirectoryStream(lib, "*.jar")) {
       
   203             for (Path jarpath: stream) {
       
   204                 analyzeDependencies(jarpath);
       
   205             }
       
   206         }
       
   207 
       
   208         // classes on boot class path should not reference other types
       
   209         boolean okay = checkClosure(profile);
       
   210         if (!okay)
       
   211             fail(url);
       
   212 
       
   213         // process JAR files in the extensions directory
       
   214         try (DirectoryStream<Path> stream = Files.newDirectoryStream(lib.resolve("ext"), "*.jar")) {
       
   215             for (Path jarpath: stream) {
       
   216                 analyzeDependencies(jarpath);
       
   217             }
       
   218         }
       
   219 
       
   220         // re-check to ensure that the extensions doesn't reference types that
       
   221         // do not exist.
       
   222         okay = checkClosure(profile);
       
   223         if (!okay)
       
   224             fail(url);
       
   225     }
       
   226 }