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 @Test upgradeableModule()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.compiler", "m1"); 251 ht.makeModule("m2", "java.compiler"); 252 253 ht.makeJmod("m1"); 254 ht.makeJmod("m2"); 255 ht.makeJmod("java.compiler", 256 "--module-path", 257 ht.lib.toString() + File.pathSeparator + mpath, 258 "--hash-modules", "java\\.(?!se)|^m.*"); 259 260 ht.checkHashes("java.compiler", "m2"); 261 } 262 263 @Test testImageJmods()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 testImageJmods1()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 validateImageJmodsTest(HashesTest ht, Path mpath)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 checkHashes(String mn, String... hashModules)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 hashes(String name)326 private ModuleHashes hashes(String name) { 327 ModuleFinder finder = ModulePath.of(Runtime.version(), true, lib); 328 return hashes(finder, name); 329 } 330 hashes(ModuleFinder finder, String name)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 toHex(byte[] ba)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 deleteDirectory(Path dir)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 makeModule(String mn, String... deps)382 private void makeModule(String mn, String... deps) throws IOException { 383 makeModule(mn, null, deps); 384 } 385 makeModule(String mn, ModuleDescriptor.Requires.Modifier mod, String... deps)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 jmodHashModules(String moduleName, String hashModulesPattern)413 private void jmodHashModules(String moduleName, String hashModulesPattern) { 414 makeJmod(moduleName, "--module-path", lib.toString(), 415 "--hash-modules", hashModulesPattern); 416 } 417 makeJmod(String moduleName, String... options)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 runJmod(List<String> args)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 makeJar(String moduleName, String... options)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 } 472