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