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