1 /** 2 * Copyright (c) 2015, 2017, 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; 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 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 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 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 @Test 241 public static void upgradeableModule() throws IOException { 242 Path mpath = Paths.get(System.getProperty("java.home"), "jmods"); 243 if (!Files.exists(mpath)) { 244 return; 245 } 246 247 Path dest = Paths.get("test4"); 248 HashesTest ht = new HashesTest(dest); 249 ht.makeModule("m1"); 250 ht.makeModule("java.xml.bind", "m1"); 251 ht.makeModule("java.xml.ws", "java.xml.bind"); 252 ht.makeModule("m2", "java.xml.ws"); 253 254 ht.makeJmod("m1"); 255 ht.makeJmod("m2"); 256 ht.makeJmod("java.xml.ws"); 257 ht.makeJmod("java.xml.bind", 258 "--module-path", 259 ht.lib.toString() + File.pathSeparator + mpath, 260 "--hash-modules", "^java.xml.*|^m.*"); 261 262 ht.checkHashes("java.xml.bind", "java.xml.ws", "m2"); 263 } 264 265 @Test 266 public static void testImageJmods() 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("test5"); 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.makeJmod("m1"); 279 ht.makeJmod("m2"); 280 281 runJmod(List.of("hash", 282 "--module-path", 283 mpath.toString() + File.pathSeparator + ht.lib.toString(), 284 "--hash-modules", ".*")); 285 286 validateImageJmodsTest(ht, mpath); 287 } 288 289 @Test 290 public static void testImageJmods1() throws IOException { 291 Path mpath = Paths.get(System.getProperty("java.home"), "jmods"); 292 if (!Files.exists(mpath)) { 293 return; 294 } 295 296 Path dest = Paths.get("test6"); 297 HashesTest ht = new HashesTest(dest); 298 ht.makeModule("m1", "jdk.compiler", "jdk.attach"); 299 ht.makeModule("m2", "m1"); 300 ht.makeModule("m3", "java.compiler"); 301 302 ht.makeJar("m2"); 303 ht.makeJar("m1", 304 "--module-path", 305 mpath.toString() + File.pathSeparator + ht.lib.toString(), 306 "--hash-modules", ".*"); 307 validateImageJmodsTest(ht, mpath); 308 } 309 310 private static void validateImageJmodsTest(HashesTest ht, Path mpath) 311 throws IOException 312 { 313 // hash is recorded in m1 and not any other packaged modules on module path 314 ht.checkHashes("m1", "m2"); 315 assertTrue(ht.hashes("m2") == null); 316 317 // should not override any JDK packaged modules 318 ModuleFinder finder = ModulePath.of(Runtime.version(), true, mpath); 319 assertTrue(ht.hashes(finder,"jdk.compiler") == null); 320 assertTrue(ht.hashes(finder,"jdk.attach") == null); 321 } 322 323 private void checkHashes(String mn, String... hashModules) throws IOException { 324 ModuleHashes hashes = hashes(mn); 325 assertTrue(hashes.names().equals(Set.of(hashModules))); 326 } 327 328 private ModuleHashes hashes(String name) { 329 ModuleFinder finder = ModulePath.of(Runtime.version(), true, lib); 330 return hashes(finder, name); 331 } 332 333 private ModuleHashes hashes(ModuleFinder finder, String name) { 334 ModuleReference mref = finder.find(name).orElseThrow(RuntimeException::new); 335 try { 336 ModuleReader reader = mref.open(); 337 try (InputStream in = reader.open("module-info.class").get()) { 338 ModuleHashes hashes = ModuleInfo.read(in, null).recordedHashes(); 339 System.out.format("hashes in module %s %s%n", name, 340 (hashes != null) ? "present" : "absent"); 341 if (hashes != null) { 342 hashes.names().stream().sorted().forEach(n -> 343 System.out.format(" %s %s%n", n, toHex(hashes.hashFor(n))) 344 ); 345 } 346 return hashes; 347 } finally { 348 reader.close(); 349 } 350 } catch (IOException e) { 351 throw new UncheckedIOException(e); 352 } 353 } 354 355 private String toHex(byte[] ba) { 356 StringBuilder sb = new StringBuilder(ba.length); 357 for (byte b: ba) { 358 sb.append(String.format("%02x", b & 0xff)); 359 } 360 return sb.toString(); 361 } 362 363 private void deleteDirectory(Path dir) throws IOException { 364 Files.walkFileTree(dir, new SimpleFileVisitor<Path>() { 365 @Override 366 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 367 throws IOException 368 { 369 Files.delete(file); 370 return FileVisitResult.CONTINUE; 371 } 372 373 @Override 374 public FileVisitResult postVisitDirectory(Path dir, IOException exc) 375 throws IOException 376 { 377 Files.delete(dir); 378 return FileVisitResult.CONTINUE; 379 } 380 }); 381 } 382 383 384 private void makeModule(String mn, String... deps) throws IOException { 385 makeModule(mn, null, deps); 386 } 387 388 private void makeModule(String mn, ModuleDescriptor.Requires.Modifier mod, String... deps) 389 throws IOException 390 { 391 if (mod != null && mod != TRANSITIVE && mod != STATIC) { 392 throw new IllegalArgumentException(mod.toString()); 393 } 394 395 StringBuilder sb = new StringBuilder(); 396 sb.append("module ") 397 .append(mn) 398 .append(" {") 399 .append("\n"); 400 Arrays.stream(deps) 401 .forEach(req -> { 402 sb.append(" requires "); 403 if (mod != null) { 404 sb.append(mod.toString().toLowerCase()) 405 .append(" "); 406 } 407 sb.append(req) 408 .append(";\n"); 409 }); 410 sb.append("}\n"); 411 builder.writeJavaFiles(mn, sb.toString()); 412 builder.compile(mn, mods); 413 } 414 415 private void jmodHashModules(String moduleName, String hashModulesPattern) { 416 makeJmod(moduleName, "--module-path", lib.toString(), 417 "--hash-modules", hashModulesPattern); 418 } 419 420 private void makeJmod(String moduleName, String... options) { 421 Path mclasses = mods.resolve(moduleName); 422 Path outfile = lib.resolve(moduleName + ".jmod"); 423 List<String> args = new ArrayList<>(); 424 args.add("create"); 425 Collections.addAll(args, options); 426 Collections.addAll(args, "--class-path", mclasses.toString(), 427 outfile.toString()); 428 429 if (Files.exists(outfile)) { 430 try { 431 Files.delete(outfile); 432 } catch (IOException e) { 433 throw new UncheckedIOException(e); 434 } 435 } 436 runJmod(args); 437 } 438 439 private static void runJmod(List<String> args) { 440 int rc = JMOD_TOOL.run(System.out, System.out, args.toArray(new String[args.size()])); 441 System.out.println("jmod " + args.stream().collect(Collectors.joining(" "))); 442 if (rc != 0) { 443 throw new AssertionError("jmod failed: rc = " + rc); 444 } 445 } 446 447 private void makeJar(String moduleName, String... options) { 448 Path mclasses = mods.resolve(moduleName); 449 Path outfile = lib.resolve(moduleName + ".jar"); 450 List<String> args = new ArrayList<>(); 451 Stream.concat(Stream.of("--create", 452 "--file=" + outfile.toString()), 453 Arrays.stream(options)) 454 .forEach(args::add); 455 args.add("-C"); 456 args.add(mclasses.toString()); 457 args.add("."); 458 459 if (Files.exists(outfile)) { 460 try { 461 Files.delete(outfile); 462 } catch (IOException e) { 463 throw new UncheckedIOException(e); 464 } 465 } 466 467 int rc = JAR_TOOL.run(System.out, System.out, args.toArray(new String[args.size()])); 468 System.out.println("jar " + args.stream().collect(Collectors.joining(" "))); 469 if (rc != 0) { 470 throw new AssertionError("jar failed: rc = " + rc); 471 } 472 } 473 }