Skip to content

8217527: jmod hash does not work if --hash-module does not include the target module #3039

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 41 additions & 19 deletions src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -282,19 +282,35 @@ private boolean extract() throws IOException {
}
}

private boolean hashModules() {
private boolean hashModules() throws IOException {
String moduleName = null;
if (options.jmodFile != null) {
try (JmodFile jf = new JmodFile(options.jmodFile)) {
try (InputStream in = jf.getInputStream(Section.CLASSES, MODULE_INFO)) {
ModuleInfo.Attributes attrs = ModuleInfo.read(in, null);
moduleName = attrs.descriptor().name();
} catch (IOException e) {
throw new CommandException("err.module.descriptor.not.found");
}
}
}
Hasher hasher = new Hasher(moduleName, options.moduleFinder);

if (options.dryrun) {
out.println("Dry run:");
}

Hasher hasher = new Hasher(options.moduleFinder);
hasher.computeHashes().forEach((mn, hashes) -> {
Map<String, ModuleHashes> moduleHashes = hasher.computeHashes();
if (moduleHashes.isEmpty()) {
throw new CommandException("err.no.moduleToHash", "\"" + options.modulesToHash + "\"");
}
moduleHashes.forEach((mn, hashes) -> {
if (options.dryrun) {
out.format("%s%n", mn);
hashes.names().stream()
.sorted()
.forEach(name -> out.format(" hashes %s %s %s%n",
name, hashes.algorithm(), toHex(hashes.hashFor(name))));
.sorted()
.forEach(name -> out.format(" hashes %s %s %s%n",
name, hashes.algorithm(), toHex(hashes.hashFor(name))));
} else {
try {
hasher.updateModuleInfo(mn, hashes);
Expand Down Expand Up @@ -845,19 +861,6 @@ private class Hasher {
final Set<String> modules;
final String moduleName; // a specific module to record hashes, if set

/**
* This constructor is for jmod hash command.
*
* This Hasher will determine which modules to record hashes, i.e.
* the module in a subgraph of modules to be hashed and that
* has no outgoing edges. It will record in each of these modules,
* say `M`, with the the hashes of modules that depend upon M
* directly or indirectly matching the specified --hash-modules pattern.
*/
Hasher(ModuleFinder finder) {
this(null, finder);
}

/**
* Constructs a Hasher to compute hashes.
*
Expand All @@ -866,6 +869,13 @@ private class Hasher {
* specified --hash-modules pattern and record in the ModuleHashes
* attribute in M's module-info.class.
*
* If name is null, this Hasher will determine which modules to
* record hashes, i.e. the module in a subgraph of modules to be
* hashed and that has no outgoing edges. It will record in each
* of these modules, say `M`, with the hashes of modules that
* depend upon M directly or indirectly matching the specified
* --hash-modules pattern.
*
* @param name name of the module to record hashes
* @param finder module finder for the specified --module-path
*/
Expand Down Expand Up @@ -1446,6 +1456,18 @@ private void handleOptions(String[] args) {
if (options.moduleFinder == null || options.modulesToHash == null)
throw new CommandException("err.modulepath.must.be.specified")
.showUsage(true);
// It's optional to specify jmod-file. If not specified, then
// it will find all the modules that have no outgoing read edges
if (words.size() >= 2) {
Path path = Paths.get(words.get(1));
if (Files.notExists(path))
throw new CommandException("err.jmod.not.found", path);

options.jmodFile = path;
}
if (words.size() > 2)
throw new CommandException("err.unknown.option",
words.subList(2, words.size())).showUsage(true);
} else {
if (words.size() <= 1)
throw new CommandException("err.jmod.must.be.specified").showUsage(true);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -105,6 +105,7 @@ err.invalid.dryrun.option=--dry-run can only be used with hash mode
err.module.descriptor.not.found=Module descriptor not found
err.missing.export.or.open.packages=Packages that are exported or open in {0} are not present: {1}
err.module.resolution.fail=Resolution failed: {0}
err.no.moduleToHash=No hashes recorded: no module matching {0} found to record hashes
warn.invalid.arg=Invalid classname or pathname not exist: {0}
warn.no.module.hashes=No hashes recorded: no module specified for hashing depends on {0}
warn.ignore.entry=ignoring entry {0}, in section {1}
Expand Down
30 changes: 29 additions & 1 deletion test/jdk/tools/jmod/JmodNegativeTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -506,6 +506,34 @@ public void testPathIsFile(Supplier<JmodResult> supplier,
});
}

@Test
public void testNoMatchingHashModule() throws IOException {
Path lib = Paths.get("hashes");
Files.createDirectories(lib);
// create jmod file with no module depending on it
Path jmod = lib.resolve("foo.jmod");
jmod("create",
"--class-path", EXPLODED_DIR.resolve("foo").resolve("classes").toString(),
jmod.toString());

// jmod hash command should report no module found to record hashes
jmod("hash",
"--module-path", lib.toString(),
"--hash-modules", ".*",
jmod.toString())
.resultChecker(r ->
assertContains(r.output, "No hashes recorded: " +
"no module matching \".*\" found to record hashes")
);
jmod("hash",
"--module-path", lib.toString(),
"--hash-modules", "foo")
.resultChecker(r ->
assertContains(r.output, "No hashes recorded: " +
"no module matching \"foo\" found to record hashes")
);
}

// ---

static boolean compileModule(String name, Path dest) throws IOException {
Expand Down
67 changes: 58 additions & 9 deletions test/jdk/tools/jmod/hashes/HashesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

/*
* @test
* @bug 8160286 8243666
* @bug 8160286 8243666 8217527
* @summary Test the recording and checking of module hashes
* @library /test/lib
* @modules java.base/jdk.internal.misc
Expand Down Expand Up @@ -276,7 +276,7 @@ public void testImageJmods1() throws IOException {
}

@Test
private void testReproducibibleHash() throws Exception {
public void testReproducibibleHash() throws Exception {
makeModule("m4");
makeModule("m3", "m4");
makeModule("m2");
Expand All @@ -297,6 +297,48 @@ private void testReproducibibleHash() throws Exception {
assertEquals(hashes1, hashes2);
}

@Test
public void testHashModulesPattern() throws IOException {
// create modules for test cases
makeModule("m1");
makeModule("m2", "m1");
makeModule("m3");
makeModule("m4", "m1", "m3");
List.of("m1", "m2", "m3", "m4").forEach(this::makeJmod);

// compute hash for the target jmod (m1.jmod) with different regex
// 1) --hash-module "m2"
Path jmod = lib.resolve("m1.jmod");
runJmod("hash",
"--module-path", lib.toString(),
"--hash-modules", "m2", jmod.toString());
assertEquals(moduleHashes().keySet(), Set.of("m1"));
checkHashes("m1", Set.of("m2"));

// 2) --hash-module "m2|m4"
runJmod("hash",
"--module-path", lib.toString(),
"--hash-modules", "m2|m4", jmod.toString());
assertEquals(moduleHashes().keySet(), Set.of("m1"));
checkHashes("m1", Set.of("m2", "m4"));

// 3) --hash-module ".*"
runJmod("hash",
"--module-path", lib.toString(),
"--hash-modules", ".*", jmod.toString());
assertEquals(moduleHashes().keySet(), Set.of("m1"));
checkHashes("m1", Set.of("m2", "m4"));

// target jmod is not specified
// compute hash for all modules in the library
runJmod("hash",
"--module-path", lib.toString(),
"--hash-modules", ".*");
assertEquals(moduleHashes().keySet(), Set.of("m1", "m3"));
checkHashes("m1", Set.of("m2", "m4"));
checkHashes("m3", Set.of("m4"));
}

private void validateImageJmodsTest(Path mpath)
throws IOException
{
Expand Down Expand Up @@ -450,22 +492,29 @@ private void makeJmod(String moduleName, String... options) {
* a ModuleHashes class file attribute.
*/
private Map<String, ModuleHashes> runJmodHash() {
runJmod(List.of("hash",
runJmod("hash",
"--module-path", lib.toString(),
"--hash-modules", ".*"));
HashesTest ht = this;
"--hash-modules", ".*");
return moduleHashes();
}

private Map<String, ModuleHashes> moduleHashes() {
return ModulePath.of(Runtime.version(), true, lib)
.findAll()
.stream()
.map(ModuleReference::descriptor)
.map(ModuleDescriptor::name)
.filter(mn -> ht.hashes(mn) != null)
.collect(Collectors.toMap(mn -> mn, ht::hashes));
.filter(mn -> hashes(mn) != null)
.collect(Collectors.toMap(mn -> mn, this::hashes));
}

private static void runJmod(List<String> args) {
int rc = JMOD_TOOL.run(System.out, System.out, args.toArray(new String[args.size()]));
System.out.println("jmod " + args.stream().collect(Collectors.joining(" ")));
runJmod(args.toArray(new String[args.size()]));
}

private static void runJmod(String... args) {
int rc = JMOD_TOOL.run(System.out, System.out, args);
System.out.println("jmod " + Arrays.stream(args).collect(Collectors.joining(" ")));
if (rc != 0) {
throw new AssertionError("jmod failed: rc = " + rc);
}
Expand Down