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