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