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 }