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