1 /** 2 * Copyright (c) 2015, 2018, 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 /* 25 * @test 26 * @bug 8160286 27 * @summary Test the recording and checking of module hashes 28 * @library /test/lib 29 * @modules java.base/jdk.internal.misc 30 * java.base/jdk.internal.module 31 * jdk.compiler 32 * jdk.jartool 33 * jdk.jlink 34 * @build jdk.test.lib.compiler.ModuleInfoMaker 35 * jdk.test.lib.compiler.CompilerUtils 36 * @run testng HashesTest 37 */ 38 39 import java.io.File; 40 import java.io.IOException; 41 import java.io.InputStream; 42 import java.io.UncheckedIOException; 43 import java.lang.module.ModuleDescriptor; 44 import java.lang.module.ModuleFinder; 45 import java.lang.module.ModuleReader; 46 import java.lang.module.ModuleReference; 47 import java.nio.file.FileVisitResult; 48 import java.nio.file.Files; 49 import java.nio.file.Path; 50 import java.nio.file.Paths; 51 import java.nio.file.SimpleFileVisitor; 52 import java.nio.file.attribute.BasicFileAttributes; 53 import java.util.ArrayList; 54 import java.util.Arrays; 55 import java.util.Collections; 56 import java.util.List; 57 import java.util.Set; 58 import java.util.spi.ToolProvider; 59 import java.util.stream.Collectors; 60 import java.util.stream.Stream; 61 62 import jdk.internal.module.ModuleInfo; 63 import jdk.internal.module.ModuleHashes; 64 import jdk.internal.module.ModulePath; 65 66 import jdk.test.lib.compiler.ModuleInfoMaker; 67 68 import org.testng.annotations.Test; 69 70 import static org.testng.Assert.*; 71 import static java.lang.module.ModuleDescriptor.Requires.Modifier.*; 72 73 public class HashesTest { 74 static final ToolProvider JMOD_TOOL = ToolProvider.findFirst("jmod") 75 .orElseThrow(() -> 76 new RuntimeException("jmod tool not found") 77 ); 78 static final ToolProvider JAR_TOOL = ToolProvider.findFirst("jar") 79 .orElseThrow(() -> 80 new RuntimeException("jar tool not found") 81 ); 82 83 private final Path mods; 84 private final Path srcDir; 85 private final Path lib; 86 private final ModuleInfoMaker builder; HashesTest(Path dest)87 HashesTest(Path dest) throws IOException { 88 if (Files.exists(dest)) { 89 deleteDirectory(dest); 90 } 91 this.mods = dest.resolve("mods"); 92 this.srcDir = dest.resolve("src"); 93 this.lib = dest.resolve("lib"); 94 this.builder = new ModuleInfoMaker(srcDir); 95 96 Files.createDirectories(lib); 97 Files.createDirectories(mods); 98 } 99 100 @Test test()101 public static void test() throws IOException { 102 Path dest = Paths.get("test"); 103 HashesTest ht = new HashesTest(dest); 104 105 // create modules for test cases 106 ht.makeModule("m2"); 107 ht.makeModule("m3"); 108 ht.makeModule("m1", "m2", "m3"); 109 110 ht.makeModule("org.bar", TRANSITIVE, "m1"); 111 ht.makeModule("org.foo", TRANSITIVE, "org.bar"); 112 113 // create JMOD for m1, m2, m3 114 ht.makeJmod("m2"); 115 ht.makeJmod("m3"); 116 117 // no hash is recorded since m1 has outgoing edges 118 ht.jmodHashModules("m1", ".*"); 119 120 // no hash is recorded in m1, m2, m3 121 assertTrue(ht.hashes("m1") == null); 122 assertTrue(ht.hashes("m2") == null); 123 assertTrue(ht.hashes("m3") == null); 124 125 // hash m1 in m2 126 ht.jmodHashModules("m2", "m1"); 127 ht.checkHashes("m2", "m1"); 128 129 // hash m1 in m2 130 ht.jmodHashModules("m2", ".*"); 131 ht.checkHashes("m2", "m1"); 132 133 // create m2.jmod with no hash 134 ht.makeJmod("m2"); 135 // run jmod hash command to hash m1 in m2 and m3 136 runJmod(List.of("hash", "--module-path", ht.lib.toString(), 137 "--hash-modules", ".*")); 138 ht.checkHashes("m2", "m1"); 139 ht.checkHashes("m3", "m1"); 140 141 // check transitive requires 142 ht.makeJmod("org.bar"); 143 ht.makeJmod("org.foo"); 144 145 ht.jmodHashModules("org.bar", "org.*"); 146 ht.checkHashes("org.bar", "org.foo"); 147 148 ht.jmodHashModules( "m3", ".*"); 149 ht.checkHashes("m3", "org.foo", "org.bar", "m1"); 150 } 151 152 @Test multiBaseModules()153 public static void multiBaseModules() throws IOException { 154 Path dest = Paths.get("test2"); 155 HashesTest ht = new HashesTest(dest); 156 157 /* 158 * y2 -----------> y1 159 * |______ 160 * | | 161 * V V 162 * z3 -> z2 163 * | | 164 * | V 165 * |---> z1 166 */ 167 168 ht.makeModule("z1"); 169 ht.makeModule("z2", "z1"); 170 ht.makeModule("z3", "z1", "z2"); 171 172 ht.makeModule("y1"); 173 ht.makeModule("y2", "y1", "z2", "z3"); 174 175 Set<String> ys = Set.of("y1", "y2"); 176 Set<String> zs = Set.of("z1", "z2", "z3"); 177 178 // create JMOD files 179 Stream.concat(ys.stream(), zs.stream()).forEach(ht::makeJmod); 180 181 // run jmod hash command 182 runJmod(List.of("hash", "--module-path", ht.lib.toString(), 183 "--hash-modules", ".*")); 184 185 /* 186 * z1 and y1 are the modules with hashes recorded. 187 */ 188 ht.checkHashes("y1", "y2"); 189 ht.checkHashes("z1", "z2", "z3", "y2"); 190 Stream.concat(ys.stream(), zs.stream()) 191 .filter(mn -> !mn.equals("y1") && !mn.equals("z1")) 192 .forEach(mn -> assertTrue(ht.hashes(mn) == null)); 193 } 194 195 @Test mixJmodAndJarFile()196 public static void mixJmodAndJarFile() throws IOException { 197 Path dest = Paths.get("test3"); 198 HashesTest ht = new HashesTest(dest); 199 200 /* 201 * j3 -----------> j2 202 * |______ 203 * | | 204 * V V 205 * m3 -> m2 206 * | | 207 * | V 208 * |---> m1 -> j1 -> jdk.jlink 209 */ 210 211 ht.makeModule("j1"); 212 ht.makeModule("j2"); 213 ht.makeModule("m1", "j1"); 214 ht.makeModule("m2", "m1"); 215 ht.makeModule("m3", "m1", "m2"); 216 217 ht.makeModule("j3", "j2", "m2", "m3"); 218 219 Set<String> jars = Set.of("j1", "j2", "j3"); 220 Set<String> jmods = Set.of("m1", "m2", "m3"); 221 222 // create JMOD and JAR files 223 jars.forEach(ht::makeJar); 224 jmods.forEach(ht::makeJmod); 225 226 // run jmod hash command 227 runJmod(List.of("hash", "--module-path", ht.lib.toString(), 228 "--hash-modules", "^j.*|^m.*")); 229 230 /* 231 * j1 and j2 are the modules with hashes recorded. 232 */ 233 ht.checkHashes("j2", "j3"); 234 ht.checkHashes("j1", "m1", "m2", "m3", "j3"); 235 Stream.concat(jars.stream(), jmods.stream()) 236 .filter(mn -> !mn.equals("j1") && !mn.equals("j2")) 237 .forEach(mn -> assertTrue(ht.hashes(mn) == null)); 238 } 239 240 241 @Test testImageJmods()242 public static void testImageJmods() throws IOException { 243 Path mpath = Paths.get(System.getProperty("java.home"), "jmods"); 244 if (!Files.exists(mpath)) { 245 return; 246 } 247 248 Path dest = Paths.get("test5"); 249 HashesTest ht = new HashesTest(dest); 250 ht.makeModule("m1", "jdk.compiler", "jdk.attach"); 251 ht.makeModule("m2", "m1"); 252 ht.makeModule("m3", "java.compiler"); 253 254 ht.makeJmod("m1"); 255 ht.makeJmod("m2"); 256 257 runJmod(List.of("hash", 258 "--module-path", 259 mpath.toString() + File.pathSeparator + ht.lib.toString(), 260 "--hash-modules", ".*")); 261 262 validateImageJmodsTest(ht, mpath); 263 } 264 265 @Test testImageJmods1()266 public static void testImageJmods1() throws IOException { 267 Path mpath = Paths.get(System.getProperty("java.home"), "jmods"); 268 if (!Files.exists(mpath)) { 269 return; 270 } 271 272 Path dest = Paths.get("test6"); 273 HashesTest ht = new HashesTest(dest); 274 ht.makeModule("m1", "jdk.compiler", "jdk.attach"); 275 ht.makeModule("m2", "m1"); 276 ht.makeModule("m3", "java.compiler"); 277 278 ht.makeJar("m2"); 279 ht.makeJar("m1", 280 "--module-path", 281 mpath.toString() + File.pathSeparator + ht.lib.toString(), 282 "--hash-modules", ".*"); 283 validateImageJmodsTest(ht, mpath); 284 } 285 validateImageJmodsTest(HashesTest ht, Path mpath)286 private static void validateImageJmodsTest(HashesTest ht, Path mpath) 287 throws IOException 288 { 289 // hash is recorded in m1 and not any other packaged modules on module path 290 ht.checkHashes("m1", "m2"); 291 assertTrue(ht.hashes("m2") == null); 292 293 // should not override any JDK packaged modules 294 ModuleFinder finder = ModulePath.of(Runtime.version(), true, mpath); 295 assertTrue(ht.hashes(finder,"jdk.compiler") == null); 296 assertTrue(ht.hashes(finder,"jdk.attach") == null); 297 } 298 checkHashes(String mn, String... hashModules)299 private void checkHashes(String mn, String... hashModules) throws IOException { 300 ModuleHashes hashes = hashes(mn); 301 assertTrue(hashes.names().equals(Set.of(hashModules))); 302 } 303 hashes(String name)304 private ModuleHashes hashes(String name) { 305 ModuleFinder finder = ModulePath.of(Runtime.version(), true, lib); 306 return hashes(finder, name); 307 } 308 hashes(ModuleFinder finder, String name)309 private ModuleHashes hashes(ModuleFinder finder, String name) { 310 ModuleReference mref = finder.find(name).orElseThrow(RuntimeException::new); 311 try { 312 ModuleReader reader = mref.open(); 313 try (InputStream in = reader.open("module-info.class").get()) { 314 ModuleHashes hashes = ModuleInfo.read(in, null).recordedHashes(); 315 System.out.format("hashes in module %s %s%n", name, 316 (hashes != null) ? "present" : "absent"); 317 if (hashes != null) { 318 hashes.names().stream().sorted().forEach(n -> 319 System.out.format(" %s %s%n", n, toHex(hashes.hashFor(n))) 320 ); 321 } 322 return hashes; 323 } finally { 324 reader.close(); 325 } 326 } catch (IOException e) { 327 throw new UncheckedIOException(e); 328 } 329 } 330 toHex(byte[] ba)331 private String toHex(byte[] ba) { 332 StringBuilder sb = new StringBuilder(ba.length); 333 for (byte b: ba) { 334 sb.append(String.format("%02x", b & 0xff)); 335 } 336 return sb.toString(); 337 } 338 deleteDirectory(Path dir)339 private void deleteDirectory(Path dir) throws IOException { 340 Files.walkFileTree(dir, new SimpleFileVisitor<Path>() { 341 @Override 342 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 343 throws IOException 344 { 345 Files.delete(file); 346 return FileVisitResult.CONTINUE; 347 } 348 349 @Override 350 public FileVisitResult postVisitDirectory(Path dir, IOException exc) 351 throws IOException 352 { 353 Files.delete(dir); 354 return FileVisitResult.CONTINUE; 355 } 356 }); 357 } 358 359 makeModule(String mn, String... deps)360 private void makeModule(String mn, String... deps) throws IOException { 361 makeModule(mn, null, deps); 362 } 363 makeModule(String mn, ModuleDescriptor.Requires.Modifier mod, String... deps)364 private void makeModule(String mn, ModuleDescriptor.Requires.Modifier mod, String... deps) 365 throws IOException 366 { 367 if (mod != null && mod != TRANSITIVE && mod != STATIC) { 368 throw new IllegalArgumentException(mod.toString()); 369 } 370 371 StringBuilder sb = new StringBuilder(); 372 sb.append("module ") 373 .append(mn) 374 .append(" {") 375 .append("\n"); 376 Arrays.stream(deps) 377 .forEach(req -> { 378 sb.append(" requires "); 379 if (mod != null) { 380 sb.append(mod.toString().toLowerCase()) 381 .append(" "); 382 } 383 sb.append(req) 384 .append(";\n"); 385 }); 386 sb.append("}\n"); 387 builder.writeJavaFiles(mn, sb.toString()); 388 builder.compile(mn, mods); 389 } 390 jmodHashModules(String moduleName, String hashModulesPattern)391 private void jmodHashModules(String moduleName, String hashModulesPattern) { 392 makeJmod(moduleName, "--module-path", lib.toString(), 393 "--hash-modules", hashModulesPattern); 394 } 395 makeJmod(String moduleName, String... options)396 private void makeJmod(String moduleName, String... options) { 397 Path mclasses = mods.resolve(moduleName); 398 Path outfile = lib.resolve(moduleName + ".jmod"); 399 List<String> args = new ArrayList<>(); 400 args.add("create"); 401 Collections.addAll(args, options); 402 Collections.addAll(args, "--class-path", mclasses.toString(), 403 outfile.toString()); 404 405 if (Files.exists(outfile)) { 406 try { 407 Files.delete(outfile); 408 } catch (IOException e) { 409 throw new UncheckedIOException(e); 410 } 411 } 412 runJmod(args); 413 } 414 runJmod(List<String> args)415 private static void runJmod(List<String> args) { 416 int rc = JMOD_TOOL.run(System.out, System.out, args.toArray(new String[args.size()])); 417 System.out.println("jmod " + args.stream().collect(Collectors.joining(" "))); 418 if (rc != 0) { 419 throw new AssertionError("jmod failed: rc = " + rc); 420 } 421 } 422 makeJar(String moduleName, String... options)423 private void makeJar(String moduleName, String... options) { 424 Path mclasses = mods.resolve(moduleName); 425 Path outfile = lib.resolve(moduleName + ".jar"); 426 List<String> args = new ArrayList<>(); 427 Stream.concat(Stream.of("--create", 428 "--file=" + outfile.toString()), 429 Arrays.stream(options)) 430 .forEach(args::add); 431 args.add("-C"); 432 args.add(mclasses.toString()); 433 args.add("."); 434 435 if (Files.exists(outfile)) { 436 try { 437 Files.delete(outfile); 438 } catch (IOException e) { 439 throw new UncheckedIOException(e); 440 } 441 } 442 443 int rc = JAR_TOOL.run(System.out, System.out, args.toArray(new String[args.size()])); 444 System.out.println("jar " + args.stream().collect(Collectors.joining(" "))); 445 if (rc != 0) { 446 throw new AssertionError("jar failed: rc = " + rc); 447 } 448 } 449 } 450