1 /*
   2  * Copyright (c) 2014, 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 import java.io.File;
  25 import java.io.IOException;
  26 import java.io.UncheckedIOException;
  27 import java.nio.file.DirectoryStream;
  28 import java.nio.file.Files;
  29 import java.nio.file.Path;
  30 import java.nio.file.Paths;
  31 import java.nio.file.attribute.BasicFileAttributes;
  32 import java.util.ArrayList;
  33 import java.util.Arrays;
  34 import java.util.Deque;
  35 import java.util.List;
  36 import java.util.Set;
  37 import java.util.concurrent.ConcurrentLinkedDeque;
  38 import java.util.concurrent.ExecutorService;
  39 import java.util.concurrent.Executors;
  40 import java.util.concurrent.TimeUnit;
  41 import java.util.concurrent.atomic.AtomicInteger;
  42 import java.util.stream.Collectors;
  43 import java.util.stream.Stream;
  44 
  45 import jdk.internal.jimage.BasicImageReader;
  46 import jdk.internal.jimage.ImageLocation;
  47 
  48 /*
  49  * @test
  50  * @summary Verify jimage
  51  * @modules java.base/jdk.internal.jimage
  52  * @run main/othervm -Djdk.launcher.addmods=ALL-SYSTEM VerifyJimage
  53  */
  54 
  55 /**
  56  * This test runs in two modes:
  57  * (1) No argument: it verifies the jimage by loading all classes in the runtime
  58  * (2) path of exploded modules: it compares bytes of each file in the exploded
  59  *     module with the entry in jimage
  60  *
  61  * FIXME: exception thrown when findLocation from jimage by multiple threads
  62  * -Djdk.test.threads=<n> to specify the number of threads.
  63  */
  64 public class VerifyJimage {
  65     private static final String MODULE_INFO = "module-info.class";
  66     private static final Deque<String> failed = new ConcurrentLinkedDeque<>();
  67 
  68     public static void main(String... args) throws Exception {
  69 
  70         String home = System.getProperty("java.home");
  71         Path bootimagePath = Paths.get(home, "lib", "modules");
  72         if (Files.notExists(bootimagePath)) {
  73              System.out.println("Test skipped, not an images build");
  74              return;
  75         }
  76 
  77         long start = System.nanoTime();
  78         int numThreads = Integer.getInteger("jdk.test.threads", 1);
  79         List<JImageReader> readers = newJImageReaders();
  80         VerifyJimage verify = new VerifyJimage(readers, numThreads);
  81         if (args.length == 0) {
  82             // load classes from jimage
  83             verify.loadClasses();
  84         } else {
  85             Path dir = Paths.get(args[0]);
  86             if (Files.notExists(dir) || !Files.isDirectory(dir)) {
  87                 throw new RuntimeException("Invalid argument: " + dir);
  88             }
  89             verify.compareExplodedModules(dir);
  90         }
  91         verify.waitForCompletion();
  92         long end = System.nanoTime();
  93         int entries = readers.stream()
  94                              .mapToInt(JImageReader::entries)
  95                              .sum();
  96         System.out.format("%d entries %d files verified: %d ms %d errors%n",
  97                           entries, verify.count.get(),
  98                           TimeUnit.NANOSECONDS.toMillis(end - start), failed.size());
  99         for (String f : failed) {
 100             System.err.println(f);
 101         }
 102         if (!failed.isEmpty()) {
 103             throw new AssertionError("Test failed");
 104         }
 105     }
 106 
 107     private final AtomicInteger count = new AtomicInteger(0);
 108     private final List<JImageReader> readers;
 109     private final ExecutorService pool;
 110 
 111     VerifyJimage(List<JImageReader> readers, int numThreads) {
 112         this.readers = readers;
 113         this.pool = Executors.newFixedThreadPool(numThreads);
 114     }
 115 
 116     private void waitForCompletion() throws InterruptedException {
 117         pool.shutdown();
 118         pool.awaitTermination(20, TimeUnit.SECONDS);
 119     }
 120 
 121     private void compareExplodedModules(Path dir) throws IOException {
 122         System.out.println("comparing jimage with " + dir);
 123 
 124         try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
 125             for (Path mdir : stream) {
 126                 if (Files.isDirectory(mdir)) {
 127                     pool.execute(new Runnable() {
 128                         @Override
 129                         public void run() {
 130                             try {
 131                                 Files.find(mdir, Integer.MAX_VALUE, (Path p, BasicFileAttributes attr)
 132                                            -> !Files.isDirectory(p) &&
 133                                               !mdir.relativize(p).toString().startsWith("_") &&
 134                                               !p.getFileName().toString().equals("MANIFEST.MF"))
 135                                      .forEach(p -> compare(mdir, p, readers));
 136                             } catch (IOException e) {
 137                                 throw new UncheckedIOException(e);
 138                             }
 139                         }
 140                     });
 141                 }
 142             }
 143         }
 144     }
 145 
 146     private final List<String> BOOT_RESOURCES = Arrays.asList(
 147         "java.base/META-INF/services/java.nio.file.spi.FileSystemProvider"
 148     );
 149     private final List<String> EXT_RESOURCES = Arrays.asList(
 150         "jdk.zipfs/META-INF/services/java.nio.file.spi.FileSystemProvider"
 151     );
 152     private final List<String> APP_RESOURCES = Arrays.asList(
 153         "jdk.hotspot.agent/META-INF/services/com.sun.jdi.connect.Connector",
 154         "jdk.jdi/META-INF/services/com.sun.jdi.connect.Connector"
 155     );
 156 
 157     private void compare(Path mdir, Path p, List<JImageReader> readers) {
 158         String entry = p.getFileName().toString().equals(MODULE_INFO)
 159                 ? mdir.getFileName().toString() + "/" + MODULE_INFO
 160                 : mdir.relativize(p).toString().replace(File.separatorChar, '/');
 161 
 162         count.incrementAndGet();
 163         String file = mdir.getFileName().toString() + "/" + entry;
 164         if (APP_RESOURCES.contains(file)) {
 165             // skip until the service config file is merged
 166             System.out.println("Skipped " + file);
 167             return;
 168         }
 169 
 170         String jimage = "modules";
 171         JImageReader reader = readers.stream()
 172                 .filter(r -> r.findLocation(entry) != null)
 173                 .filter(r -> jimage.isEmpty() || r.imageName().equals(jimage))
 174                 .findFirst().orElse(null);
 175         if (reader == null) {
 176             failed.add(entry + " not found: " + p.getFileName().toString());
 177         } else {
 178             reader.compare(entry, p);
 179         }
 180     }
 181 
 182     private void loadClasses() {
 183         ClassLoader loader = ClassLoader.getSystemClassLoader();
 184         for (JImageReader reader : readers) {
 185             Arrays.stream(reader.getEntryNames())
 186                     .filter(n -> n.endsWith(".class") && !n.endsWith(MODULE_INFO))
 187                     .forEach(n -> {
 188                         String cn = removeModule(n).replaceAll("\\.class$", "").replace('/', '.');
 189                         count.incrementAndGet();
 190                         try {
 191                             System.out.println("Loading " + cn);
 192                             Class.forName(cn, false, loader);
 193                         } catch (VerifyError ve) {
 194                             System.err.println("VerifyError for " + cn);
 195                             failed.add(reader.imageName() + ": " + cn + " not verified: " + ve.getMessage());
 196                         } catch (ClassNotFoundException e) {
 197                             failed.add(reader.imageName() + ": " + cn + " not found");
 198                         }
 199                     });
 200         }
 201     }
 202 
 203     private String removeModule(String path) {
 204         int index = path.indexOf('/', 1);
 205         return path.substring(index + 1, path.length());
 206     }
 207 
 208     private static List<JImageReader> newJImageReaders() throws IOException {
 209         String home = System.getProperty("java.home");
 210         Path jimage = Paths.get(home, "lib", "modules");
 211         JImageReader reader = new JImageReader(jimage);
 212         List<JImageReader> result = new ArrayList<>();
 213         System.out.println("opened " + jimage);
 214         result.add(reader);
 215         return result;
 216     }
 217 
 218     static class JImageReader extends BasicImageReader {
 219         final Path jimage;
 220         JImageReader(Path p) throws IOException {
 221             super(p);
 222             this.jimage = p;
 223         }
 224 
 225         String imageName() {
 226             return jimage.getFileName().toString();
 227         }
 228 
 229         int entries() {
 230             return getHeader().getTableLength();
 231         }
 232 
 233         void compare(String entry, Path p) {
 234             try {
 235                 byte[] bytes = Files.readAllBytes(p);
 236                 byte[] imagebytes = getResource(entry);
 237                 if (!Arrays.equals(bytes, imagebytes)) {
 238                     failed.add(imageName() + ": bytes differs than " + p.toString());
 239                 }
 240             } catch (IOException e) {
 241                 throw new UncheckedIOException(e);
 242             }
 243         }
 244     }
 245 }