< prev index next >

test/jdk/tools/jimage/VerifyJimage.java

Print this page

  1 /*
  2  * Copyright (c) 2014, 2023, 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 --add-modules 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         JImageReader reader = newJImageReader();
 80         VerifyJimage verify = new VerifyJimage(reader, 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 = reader.entries();
 94         System.out.format("%d entries %d files verified: %d ms %d errors%n",
 95                           entries, verify.count.get(),
 96                           TimeUnit.NANOSECONDS.toMillis(end - start), failed.size());
 97         for (String f : failed) {
 98             System.err.println(f);
 99         }
100         if (!failed.isEmpty()) {

101             throw new AssertionError("Test failed");
102         }
103     }
104 
105     private final AtomicInteger count = new AtomicInteger(0);
106     private final JImageReader reader;
107     private final ExecutorService pool;

108 
109     VerifyJimage(JImageReader reader, int numThreads) {
110         this.reader = reader;
111         this.pool = Executors.newFixedThreadPool(numThreads);
112     }
113 
114     private void waitForCompletion() throws InterruptedException {
115         pool.shutdown();
116         pool.awaitTermination(20, TimeUnit.SECONDS);
117     }
118 
119     private void compareExplodedModules(Path dir) throws IOException {
120         System.out.println("comparing jimage with " + dir);
121 
122         try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
123             for (Path mdir : stream) {
124                 if (Files.isDirectory(mdir)) {
125                     pool.execute(new Runnable() {
126                         @Override
127                         public void run() {
128                             try {
129                                 Files.find(mdir, Integer.MAX_VALUE, (Path p, BasicFileAttributes attr)
130                                            -> !Files.isDirectory(p) &&
131                                               !mdir.relativize(p).toString().startsWith("_") &&
132                                               !p.getFileName().toString().equals("MANIFEST.MF"))
133                                      .forEach(p -> compare(mdir, p, reader));
134                             } catch (IOException e) {
135                                 throw new UncheckedIOException(e);
136                             }
137                         }
138                     });
139                 }





140             }
141         }
142     }
143 
144     private final List<String> BOOT_RESOURCES = Arrays.asList(
145         "java.base/META-INF/services/java.nio.file.spi.FileSystemProvider"
146     );
147     private final List<String> EXT_RESOURCES = Arrays.asList(
148         "jdk.zipfs/META-INF/services/java.nio.file.spi.FileSystemProvider"
149     );
150     private final List<String> APP_RESOURCES = Arrays.asList(
151         "jdk.hotspot.agent/META-INF/services/com.sun.jdi.connect.Connector",
152         "jdk.jdi/META-INF/services/com.sun.jdi.connect.Connector"
153     );
154 
155     private void compare(Path mdir, Path p, JImageReader reader) {
156         String entry = p.getFileName().toString().equals(MODULE_INFO)
157                 ? mdir.getFileName().toString() + "/" + MODULE_INFO
158                 : mdir.relativize(p).toString().replace(File.separatorChar, '/');
159 
160         count.incrementAndGet();
161         String file = mdir.getFileName().toString() + "/" + entry;
162         if (APP_RESOURCES.contains(file)) {
163             // skip until the service config file is merged
164             System.out.println("Skipped " + file);
165             return;
166         }
167 
168         if (reader.findLocation(entry) != null) {
169             reader.compare(entry, p);
170         }
171     }








172 
173     private void loadClasses() {
174         ClassLoader loader = ClassLoader.getSystemClassLoader();
175         Stream.of(reader.getEntryNames())
176               .filter(this::accept)
177               .map(this::toClassName)
178               .forEach(cn -> {
179                   count.incrementAndGet();
180                   try {
181                       System.out.println("Loading " + cn);
182                       Class.forName(cn, false, loader);
183                   } catch (VerifyError ve) {
184                       System.err.println("VerifyError for " + cn);
185                       failed.add(reader.imageName() + ": " + cn + " not verified: " + ve.getMessage());
186                   } catch (ClassNotFoundException e) {
187                       failed.add(reader.imageName() + ": " + cn + " not found");
188                   }
189               });
190     }
191 
192     private String toClassName(String entry) {
193         int index = entry.indexOf('/', 1);
194         return entry.substring(index + 1, entry.length())
195                     .replaceAll("\\.class$", "").replace('/', '.');
196     }
























197 
198     // All JVMCI packages other than jdk.vm.ci.services are dynamically
199     // exported to jdk.graal.compiler
200     private static Set<String> EXCLUDED_MODULES = Set.of("jdk.graal.compiler");


























201 
202     private boolean accept(String entry) {
203         int index = entry.indexOf('/', 1);
204         String mn = index > 1 ? entry.substring(1, index) : "";
205         if (mn.isEmpty() || EXCLUDED_MODULES.contains(mn)) {
206             return false;












207         }
208         return entry.endsWith(".class") && !entry.endsWith(MODULE_INFO);
209     }
210 
211     private static JImageReader newJImageReader() throws IOException {
212         String home = System.getProperty("java.home");
213         Path jimage = Paths.get(home, "lib", "modules");
214         System.out.println("opened " + jimage);
215         return new JImageReader(jimage);
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 }

  1 /*
  2  * Copyright (c) 2014, 2025, 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 jdk.internal.jimage.BasicImageReader;
 25 import jtreg.SkippedException;
 26 
 27 import java.io.IOException;
 28 import java.io.UncheckedIOException;
 29 import java.net.URI;
 30 import java.nio.file.FileSystem;
 31 import java.nio.file.FileSystems;
 32 import java.nio.file.Files;
 33 import java.nio.file.Path;



 34 import java.util.Arrays;
 35 import java.util.Deque;
 36 import java.util.HashSet;
 37 import java.util.List;
 38 import java.util.Set;
 39 import java.util.concurrent.ConcurrentLinkedDeque;
 40 import java.util.concurrent.ExecutorService;
 41 import java.util.concurrent.Executors;
 42 import java.util.concurrent.TimeUnit;
 43 import java.util.concurrent.atomic.AtomicInteger;
 44 import java.util.stream.Collectors;
 45 import java.util.stream.Stream;
 46 import java.util.stream.StreamSupport;
 47 
 48 import static java.util.stream.Collectors.joining;

 49 
 50 /*
 51  * @test id=load
 52  * @summary Load all classes defined in JRT file system.
 53  * @library /test/lib
 54  * @modules java.base/jdk.internal.jimage
 55  * @run main/othervm --add-modules ALL-SYSTEM VerifyJimage
 56  */
 57 
 58 /*
 59  * @test id=compare
 60  * @summary Compare an exploded directory of module classes with the system jimage.
 61  * @library /test/lib
 62  * @modules java.base/jdk.internal.jimage
 63  * @run main/othervm --add-modules ALL-SYSTEM -Djdk.test.threads=10 VerifyJimage ../../jdk/modules


 64  */
 65 public abstract class VerifyJimage implements Runnable {
 66     private static final String MODULE_INFO = "module-info.class";

 67 
 68     public static void main(String... args) throws Exception {
 69         // Best practice is to read "test.jdk" in preference to "java.home".
 70         String testJdk = System.getProperty("test.jdk", System.getProperty("java.home"));
 71         Path jdkRoot = Path.of(testJdk);
 72         Path bootimagePath = jdkRoot.resolve("lib", "modules");
 73         if (Files.notExists(bootimagePath)) {
 74             throw new SkippedException("No boot image: " + bootimagePath);

 75         }
 76 
 77         FileSystem jrtFs = FileSystems.getFileSystem(URI.create("jrt:/"));
 78         Path modulesRoot = jrtFs.getPath("/").resolve("modules");
 79         List<String> modules;
 80         try (Stream<Path> moduleDirs = Files.list(modulesRoot)) {
 81             modules = moduleDirs.map(Path::getFileName).map(Object::toString).toList();
 82         }
 83         VerifyJimage verifier;
 84         if (args.length == 0) {
 85             verifier = new ClassLoadingVerifier(modules, modulesRoot);

 86         } else {
 87             Path pathArg = Path.of(args[0].replace("/", FileSystems.getDefault().getSeparator()));
 88             // The path argument may be relative.
 89             Path rootDir = jdkRoot.resolve(pathArg);
 90             if (!Files.isDirectory(rootDir)) {
 91                 throw new SkippedException("No modules directory found: " + rootDir);
 92             }
 93             int maxThreads = Integer.getInteger("jdk.test.threads", 1);
 94             verifier = new DirectoryContentVerifier(modules, rootDir, maxThreads, bootimagePath);
 95         }
 96         verifier.verify();
 97     }
 98 
 99     final List<String> modules;
100     // Count of items which have passed verification.
101     final AtomicInteger verifiedCount = new AtomicInteger(0);
102     // Error messages for verification failures.
103     final Deque<String> failed = new ConcurrentLinkedDeque<>();
104 
105     private VerifyJimage(List<String> modules) {
106         this.modules = modules;
107     }
108 
109     void verify() {
110         long start = System.nanoTime();
111         run();
112         long end = System.nanoTime();
113 
114         System.out.format("Verified %d entries: %d ms, %d errors%n",
115                 verifiedCount.get(),
116                 TimeUnit.NANOSECONDS.toMillis(end - start),
117                 failed.size());


118         if (!failed.isEmpty()) {
119             failed.forEach(System.err::println);
120             throw new AssertionError("Test failed");
121         }
122     }
123 
124     private static final class DirectoryContentVerifier extends VerifyJimage {
125         private final Path rootDir;
126         private final ExecutorService pool;
127         private final Path jimagePath;
128 
129         DirectoryContentVerifier(List<String> modules, Path rootDir, int maxThreads, Path jimagePath) {
130             super(modules);
131             this.rootDir = rootDir;
132             this.pool = Executors.newFixedThreadPool(maxThreads);
133             this.jimagePath = jimagePath;
134         }



135 
136         @Override
137         public void run() {
138             System.out.println("Comparing jimage with: " + rootDir);
139             try (BasicImageReader jimage = BasicImageReader.open(jimagePath)) {
140                 for (String modName : modules) {
141                     Path modDir = rootDir.resolve(modName);
142                     if (!Files.isDirectory(modDir)) {
143                         failed.add("Missing module directory: " + modDir);
144                     } else {
145                         pool.execute(new ModuleResourceComparator(rootDir, modName, jimage));
146                     }
147                 }
148                 pool.shutdown();
149                 if (!pool.awaitTermination(20, TimeUnit.SECONDS)) {
150                     failed.add("Directory verification timed out");





151                 }
152             } catch (IOException ex) {
153                 throw new UncheckedIOException(ex);
154             } catch (InterruptedException e) {
155                 failed.add("Directory verification was interrupted");
156                 Thread.currentThread().interrupt();
157             }
158         }

159 
160         /**
161          * Verifies the contents of the current runtime jimage file by comparing
162          * entries with the on-disk resources in a given directory.
163          */
164         private class ModuleResourceComparator implements Runnable {
165             private final Path rootDir;
166             private final String moduleName;
167             private final BasicImageReader jimage;
168             private final String moduleInfoName;
169             // Entries we expect to find in the jimage module.
170             private final Set<String> moduleEntries;
171             private final Set<String> handledEntries = new HashSet<>();











172 
173             public ModuleResourceComparator(Path rootDir, String moduleName, BasicImageReader jimage) {
174                 this.rootDir = rootDir;
175                 this.moduleName = moduleName;
176                 this.jimage = jimage;
177                 String moduleEntryPrefix = "/" + moduleName + "/";
178                 this.moduleInfoName = moduleEntryPrefix + MODULE_INFO;
179                 this.moduleEntries =
180                         Arrays.stream(jimage.getEntryNames())
181                                 .filter(n -> n.startsWith(moduleEntryPrefix))
182                                 .filter(n -> !isJimageOnly(n))
183                                 .collect(Collectors.toSet());
184             }
185 
186             @Override
187             public void run() {
188                 try (Stream<Path> files = Files.walk(rootDir.resolve(moduleName))) {
189                     files.filter(this::shouldVerify).forEach(this::compareEntry);
190                 } catch (IOException e) {
191                     throw new UncheckedIOException(e);
192                 }
193                 moduleEntries.stream()
194                         .filter(n -> !handledEntries.contains(n))
195                         .sorted()
196                         .forEach(n -> failed.add("Untested jimage entry: " + n));
197             }






198 
199             void compareEntry(Path path) {
200                 String entryName = getEntryName(path);
201                 if (!moduleEntries.contains(entryName)) {
202                     // Corresponds to an on-disk file which is not expected to
203                     // be present in the jimage. This is normal and is skipped.
204                     return;
205                 }
206                 // Mark valid entries as "handled" to track if we've seen them
207                 // (even if we don't test their content).
208                 if (!handledEntries.add(entryName)) {
209                     failed.add("Duplicate entry name: " + entryName);
210                     return;
211                 }
212                 if (isExpectedToDiffer(entryName)) {
213                     return;
214                 }
215                 try {
216                     int mismatch = Arrays.mismatch(
217                             Files.readAllBytes(path),
218                             jimage.getResource(entryName));
219                     if (mismatch == -1) {
220                         verifiedCount.incrementAndGet();
221                     } else {
222                         failed.add("Content diff (byte offset " + mismatch + "): " + entryName);
223                     }
224                 } catch (IOException e) {
225                     throw new UncheckedIOException(e);
226                 }
227             }
228 
229             /**
230              * Predicate for files which correspond to entries in the jimage.
231              *
232              * <p>This should be a narrow test with minimal chance of
233              * false-negative matching, primarily focusing on excluding build
234              * artifacts.
235              */
236             boolean shouldVerify(Path path) {
237                 // Use the entry name because we know it uses the '/' separator.
238                 String entryName = getEntryName(path);
239                 return Files.isRegularFile(path)
240                         && !entryName.contains("/_the.")
241                         && !entryName.contains("/_element_lists.");
242             }
243 
244             /**
245              * Predicate for the limited subset of entries which are expected to
246              * exist in the file system, but are not expected to have the same
247              * content as the associated jimage entry. This is to handle files
248              * which are modified/patched by jlink plugins.
249              *
250              * <p>This should be a narrow test with minimal chance of
251              * false-positive matching.
252              */
253             private boolean isExpectedToDiffer(String entryName) {
254                 return entryName.equals(moduleInfoName)
255                         || (entryName.startsWith("/java.base/java/lang/invoke/") && entryName.endsWith("$Holder.class"))
256                         || entryName.equals("/java.base/jdk/internal/module/SystemModulesMap.class");
257             }
258 
259             /**
260              * Predicate for the limited subset of entries which are not expected
261              * to exist in the file system, such as those created synthetically
262              * by jlink plugins.
263              *
264              * <p>This should be a narrow test with minimal chance of
265              * false-positive matching.
266              */
267             private boolean isJimageOnly(String entryName) {
268                 return entryName.startsWith("/java.base/jdk/internal/module/SystemModules$")
269                         || entryName.startsWith("/java.base/java/lang/invoke/BoundMethodHandle$Species_");
270             }
271 
272             private String getEntryName(Path path) {
273                 return StreamSupport.stream(rootDir.relativize(path).spliterator(), false)
274                         .map(Object::toString).collect(joining("/", "/", ""));
275             }
276         }

277     }
278 
279     /**
280      * Verifies the contents of the current runtime jimage file by attempting to
281      * load every available class based on the content of the JRT file system.
282      */
283     static final class ClassLoadingVerifier extends VerifyJimage {
284         private static final String CLASS_SUFFIX = ".class";
285 
286         private final Path modulesRoot;





287 
288         ClassLoadingVerifier(List<String> modules, Path modulesRoot) {
289             super(modules);
290             this.modulesRoot = modulesRoot;
291         }
292 
293         @Override
294         public void run() {
295             ClassLoader loader = ClassLoader.getSystemClassLoader();
296             for (String modName : modules) {
297                 Path modDir = modulesRoot.resolve(modName);
298                 try (Stream<Path> files = Files.walk(modDir)) {
299                     files.map(modDir::relativize)
300                             .filter(ClassLoadingVerifier::isClassFile)
301                             .map(ClassLoadingVerifier::toClassName)
302                             .forEach(cn -> loadClass(cn, loader));
303                 } catch (IOException ex) {
304                     throw new UncheckedIOException(ex);
305                 }
306             }
307         }
308 
309         private void loadClass(String cn, ClassLoader loader) {
310             try {
311                 Class.forName(cn, false, loader);
312                 verifiedCount.incrementAndGet();
313             } catch (VerifyError ve) {
314                 System.err.println("VerifyError for " + cn);
315                 failed.add("Class: " + cn + " not verified: " + ve.getMessage());
316             } catch (ClassNotFoundException e) {
317                 failed.add("Class: " + cn + " not found");
318             }
319         }
320 
321         /**
322          * Maps a module-relative JRT path of a class file to its corresponding
323          * fully-qualified class name.
324          */
325         private static String toClassName(Path path) {
326             // JRT uses '/' as the separator, and relative paths don't start with '/'.
327             String s = path.toString();
328             return s.substring(0, s.length() - CLASS_SUFFIX.length()).replace('/', '.');
329         }
330 
331         /** Whether a module-relative JRT file system path is a class file. */
332         private static boolean isClassFile(Path path) {
333             String classFileName = path.getFileName().toString();
334             return classFileName.endsWith(CLASS_SUFFIX)
335                     && !classFileName.equals(MODULE_INFO);
336         }
337     }
338 }
< prev index next >