/** * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * @test * @bug 8160286 * @summary Test the recording and checking of module hashes * @library /lib/testlibrary * @modules java.base/jdk.internal.misc * java.base/jdk.internal.module * jdk.compiler * jdk.jartool * jdk.jlink * @build CompilerUtils ModuleInfoMaker * @run testng HashesTest */ import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import java.lang.module.ModuleDescriptor; import java.lang.module.ModuleFinder; import java.lang.module.ModuleReader; import java.lang.module.ModuleReference; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.spi.ToolProvider; import java.util.stream.Collectors; import java.util.stream.Stream; import jdk.internal.module.ModuleInfo; import jdk.internal.module.ModuleHashes; import jdk.internal.module.ModulePath; import org.testng.annotations.Test; import static org.testng.Assert.*; import static java.lang.module.ModuleDescriptor.Requires.Modifier.*; public class HashesTest { static final ToolProvider JMOD_TOOL = ToolProvider.findFirst("jmod") .orElseThrow(() -> new RuntimeException("jmod tool not found") ); static final ToolProvider JAR_TOOL = ToolProvider.findFirst("jar") .orElseThrow(() -> new RuntimeException("jar tool not found") ); private final Path mods; private final Path srcDir; private final Path lib; private final ModuleInfoMaker builder; HashesTest(Path dest) throws IOException { if (Files.exists(dest)) { deleteDirectory(dest); } this.mods = dest.resolve("mods"); this.srcDir = dest.resolve("src"); this.lib = dest.resolve("lib"); this.builder = new ModuleInfoMaker(srcDir); Files.createDirectories(lib); Files.createDirectories(mods); } @Test public static void test() throws IOException { Path dest = Paths.get("test"); HashesTest ht = new HashesTest(dest); // create modules for test cases ht.makeModule("m2"); ht.makeModule("m3"); ht.makeModule("m1", "m2", "m3"); ht.makeModule("org.bar", TRANSITIVE, "m1"); ht.makeModule("org.foo", TRANSITIVE, "org.bar"); // create JMOD for m1, m2, m3 ht.makeJmod("m2"); ht.makeJmod("m3"); // no hash is recorded since m1 has outgoing edges ht.jmodHashModules("m1", ".*"); // no hash is recorded in m1, m2, m3 assertTrue(ht.hashes("m1") == null); assertTrue(ht.hashes("m2") == null); assertTrue(ht.hashes("m3") == null); // hash m1 in m2 ht.jmodHashModules("m2", "m1"); ht.checkHashes("m2", "m1"); // hash m1 in m2 ht.jmodHashModules("m2", ".*"); ht.checkHashes("m2", "m1"); // create m2.jmod with no hash ht.makeJmod("m2"); // run jmod hash command to hash m1 in m2 and m3 runJmod(List.of("hash", "--module-path", ht.lib.toString(), "--hash-modules", ".*")); ht.checkHashes("m2", "m1"); ht.checkHashes("m3", "m1"); // check transitive requires ht.makeJmod("org.bar"); ht.makeJmod("org.foo"); ht.jmodHashModules("org.bar", "org.*"); ht.checkHashes("org.bar", "org.foo"); ht.jmodHashModules( "m3", ".*"); ht.checkHashes("m3", "org.foo", "org.bar", "m1"); } @Test public static void multiBaseModules() throws IOException { Path dest = Paths.get("test2"); HashesTest ht = new HashesTest(dest); /* * y2 -----------> y1 * |______ * | | * V V * z3 -> z2 * | | * | V * |---> z1 */ ht.makeModule("z1"); ht.makeModule("z2", "z1"); ht.makeModule("z3", "z1", "z2"); ht.makeModule("y1"); ht.makeModule("y2", "y1", "z2", "z3"); Set ys = Set.of("y1", "y2"); Set zs = Set.of("z1", "z2", "z3"); // create JMOD files Stream.concat(ys.stream(), zs.stream()).forEach(ht::makeJmod); // run jmod hash command to runJmod(List.of("hash", "--module-path", ht.lib.toString(), "--hash-modules", ".*")); /* * z1 and y1 are the base modules with hashes recorded. */ ht.checkHashes("y1", "y2"); ht.checkHashes("z1", "z2", "z3", "y2"); } @Test public static void mixJmodAndJarFile() throws IOException { Path dest = Paths.get("test3"); HashesTest ht = new HashesTest(dest); /* * j3 -----------> j2 * |______ * | | * V V * m3 -> m2 * | | * | V * |---> m1 -> j1 */ ht.makeModule("j1"); ht.makeModule("j2"); ht.makeModule("m1", "j1"); ht.makeModule("m2", "m1"); ht.makeModule("m3", "m1", "m2"); ht.makeModule("j3", "j2", "m2", "m3"); Set jars = Set.of("j1", "j2", "j3"); Set jmods = Set.of("m1", "m2", "m3"); // create JMOD and JAR files jars.forEach(ht::makeJar); jmods.forEach(ht::makeJmod); // run jmod hash command to runJmod(List.of("hash", "--module-path", ht.lib.toString(), "--hash-modules", ".*")); /* * j1 and j2 are the base modules with hashes recorded. */ ht.checkHashes("j2", "j3"); ht.checkHashes("j1", "m1", "m2", "m3", "j3"); } private void checkHashes(String mn, String... hashModules) throws IOException { ModuleHashes hashes = hashes(mn); assertTrue(hashes.names().equals(Set.of(hashModules))); } private ModuleHashes hashes(String name) throws IOException { ModuleFinder finder = new ModulePath(Runtime.version(), true, lib); ModuleReference mref = finder.find(name).orElseThrow(RuntimeException::new); ModuleReader reader = mref.open(); try (InputStream in = reader.open("module-info.class").get()) { ModuleHashes hashes = ModuleInfo.read(in, null).recordedHashes(); System.out.format("hashes in module %s %s%n", name, (hashes != null) ? "present" : "absent"); if (hashes != null) { hashes.names().stream() .sorted() .forEach(n -> System.out.format(" %s %s%n", n, toHex(hashes.hashFor(n)))); } return hashes; } finally { reader.close(); } } private String toHex(byte[] ba) { StringBuilder sb = new StringBuilder(ba.length); for (byte b: ba) { sb.append(String.format("%02x", b & 0xff)); } return sb.toString(); } private void deleteDirectory(Path dir) throws IOException { Files.walkFileTree(dir, new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Files.delete(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { Files.delete(dir); return FileVisitResult.CONTINUE; } }); } private void makeModule(String mn, String... deps) throws IOException { makeModule(mn, null, deps); } private void makeModule(String mn, ModuleDescriptor.Requires.Modifier mod, String... deps) throws IOException { if (mod != null && mod != TRANSITIVE && mod != STATIC) { throw new IllegalArgumentException(mod.toString()); } StringBuilder sb = new StringBuilder(); sb.append("module " + mn + " {").append("\n"); Arrays.stream(deps).forEach(req -> { sb.append(" requires "); if (mod != null) { sb.append(mod.toString().toLowerCase()).append(" "); } sb.append(req + ";\n"); }); sb.append("}\n"); builder.writeJavaFiles(mn, sb.toString()); compileModule(mn, srcDir); } private void compileModule(String moduleName, Path src) throws IOException { Path msrc = src.resolve(moduleName); assertTrue(CompilerUtils.compile(msrc, mods, "--module-source-path", src.toString())); } private void jmodHashModules(String moduleName, String hashModulesPattern) { makeJmod(moduleName, "--module-path", lib.toString(), "--hash-modules", hashModulesPattern); } private void makeJmod(String moduleName, String... options) { Path mclasses = mods.resolve(moduleName); Path outfile = lib.resolve(moduleName + ".jmod"); List args = new ArrayList<>(); args.add("create"); Collections.addAll(args, options); Collections.addAll(args, "--class-path", mclasses.toString(), outfile.toString()); if (Files.exists(outfile)) { try { Files.delete(outfile); } catch (IOException e) { throw new UncheckedIOException(e); } } runJmod(args); } private static void runJmod(List args) { int rc = JMOD_TOOL.run(System.out, System.out, args.toArray(new String[args.size()])); System.out.println("jmod " + args.stream().collect(Collectors.joining(" "))); if (rc != 0) { throw new AssertionError("jmod failed: rc = " + rc); } } private void makeJar(String moduleName) { Path mclasses = mods.resolve(moduleName); Path outfile = lib.resolve(moduleName + ".jar"); List args = List.of( "--create", "--file=" + outfile.toString(), "-C", mclasses.toString(), "." ); if (Files.exists(outfile)) { try { Files.delete(outfile); } catch (IOException e) { throw new UncheckedIOException(e); } } int rc = JAR_TOOL.run(System.out, System.out, args.toArray(new String[args.size()])); System.out.println("jar " + args.stream().collect(Collectors.joining(" "))); if (rc != 0) { throw new AssertionError("jar failed: rc = " + rc); } } }