8147491: module graph consistency checks after jlink plugins operate on module pool
Reviewed-by: jlaskey, mchung, psandoz
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImagePluginStack.java Thu Aug 25 21:58:38 2016 -0700
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImagePluginStack.java Fri Aug 26 11:50:36 2016 +0530
@@ -172,7 +172,7 @@
private final Plugin lastSorter;
private final List<Plugin> plugins = new ArrayList<>();
private final List<ResourcePrevisitor> resourcePrevisitors = new ArrayList<>();
-
+ private final boolean validate;
public ImagePluginStack() {
this(null, Collections.emptyList(), null);
@@ -181,6 +181,13 @@
public ImagePluginStack(ImageBuilder imageBuilder,
List<Plugin> plugins,
Plugin lastSorter) {
+ this(imageBuilder, plugins, lastSorter, true);
+ }
+
+ public ImagePluginStack(ImageBuilder imageBuilder,
+ List<Plugin> plugins,
+ Plugin lastSorter,
+ boolean validate) {
this.imageBuilder = Objects.requireNonNull(imageBuilder);
this.lastSorter = lastSorter;
this.plugins.addAll(Objects.requireNonNull(plugins));
@@ -190,6 +197,7 @@
resourcePrevisitors.add((ResourcePrevisitor) p);
}
});
+ this.validate = validate;
}
public void operate(ImageProvider provider) throws Exception {
@@ -268,6 +276,7 @@
frozenOrder = ((OrderedResourcePoolManager.OrderedResourcePool)resPool).getOrderedList();
}
}
+
return resPool;
}
@@ -458,7 +467,11 @@
throws Exception {
Objects.requireNonNull(original);
Objects.requireNonNull(transformed);
- imageBuilder.storeFiles(new LastPoolManager(transformed).resourcePool());
+ ResourcePool lastPool = new LastPoolManager(transformed).resourcePool();
+ if (validate) {
+ ResourcePoolConfiguration.validate(lastPool);
+ }
+ imageBuilder.storeFiles(lastPool);
}
public ExecutableImage getExecutableImage() throws IOException {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ResourcePoolConfiguration.java Fri Aug 26 11:50:36 2016 +0530
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2016, 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
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.tools.jlink.internal;
+
+import java.lang.module.Configuration;
+import java.lang.module.ModuleDescriptor;
+import java.lang.module.ModuleFinder;
+import java.lang.module.ModuleReference;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import jdk.tools.jlink.plugin.PluginException;
+import jdk.tools.jlink.plugin.ResourcePool;
+import jdk.tools.jlink.plugin.ResourcePoolEntry;
+import jdk.tools.jlink.plugin.ResourcePoolModule;
+
+final class ResourcePoolConfiguration {
+ private ResourcePoolConfiguration() {}
+
+ private static ModuleDescriptor descriptorOf(ResourcePoolModule mod) {
+ ModuleDescriptor md = mod.descriptor();
+
+ // drop hashes
+ ModuleDescriptor.Builder builder = new ModuleDescriptor.Builder(md.name());
+ md.requires().stream()
+ .forEach(builder::requires);
+ md.exports().stream()
+ .forEach(builder::exports);
+ md.uses().stream()
+ .forEach(builder::uses);
+ md.provides().values().stream()
+ .forEach(builder::provides);
+
+ // build the proper concealed packages
+ Set<String> exps = md.exports().stream()
+ .map(ModuleDescriptor.Exports::source)
+ .collect(Collectors.toSet());
+
+ mod.packages().stream()
+ .filter(pn -> !exps.contains(pn))
+ .forEach(builder::conceals);
+
+ md.version().ifPresent(builder::version);
+ md.mainClass().ifPresent(builder::mainClass);
+ md.osName().ifPresent(builder::osName);
+ md.osArch().ifPresent(builder::osArch);
+ md.osVersion().ifPresent(builder::osVersion);
+
+ return builder.build();
+ }
+
+ private static ModuleReference moduleReference(ModuleDescriptor desc) {
+ return new ModuleReference(desc, null, () -> {
+ IOException ioe = new IOException("<module reader unsupported>");
+ throw new UncheckedIOException(ioe);
+ });
+ }
+
+ private static Map<String, ModuleReference> allModRefs(ResourcePool pool) {
+ return pool.moduleView().modules().
+ collect(Collectors.toMap(ResourcePoolModule::name,
+ m -> moduleReference(descriptorOf(m))));
+ }
+
+ private static void checkPackages(ResourcePool pool) {
+ // check that each resource pool module's packages()
+ // returns a set that is consistent with the module
+ // descriptor of that module.
+
+ pool.moduleView().modules().forEach(m -> {
+ ModuleDescriptor desc = m.descriptor();
+ if (!desc.packages().equals(m.packages())) {
+ throw new RuntimeException("Module " + m.name() +
+ "'s descriptor returns inconsistent package set");
+ }
+ });
+ }
+
+ static Configuration validate(ResourcePool pool) {
+ checkPackages(pool);
+ final Map<String, ModuleReference> nameToModRef = allModRefs(pool);
+ final Set<ModuleReference> allRefs = new HashSet<>(nameToModRef.values());
+
+ final ModuleFinder finder = new ModuleFinder() {
+ @Override
+ public Optional<ModuleReference> find(String name) {
+ return Optional.ofNullable(nameToModRef.get(name));
+ }
+
+ @Override
+ public Set<ModuleReference> findAll() {
+ return allRefs;
+ }
+ };
+
+ return Configuration.empty().resolveRequires(
+ finder, ModuleFinder.of(), nameToModRef.keySet());
+ }
+}
--- a/jdk/test/tools/jlink/CustomPluginTest.java Thu Aug 25 21:58:38 2016 -0700
+++ b/jdk/test/tools/jlink/CustomPluginTest.java Fri Aug 26 11:50:36 2016 +0530
@@ -67,6 +67,7 @@
testHelloProvider(helper, pluginModulePath);
testCustomPlugins(helper, pluginModulePath);
+ testModuleVerification(helper, pluginModulePath);
}
private void testCustomPlugins(Helper helper, Path pluginModulePath) {
@@ -93,8 +94,7 @@
String name = "customplugin";
Path src = Paths.get(System.getProperty("test.src")).resolve(name);
Path classes = helper.getJmodClassesDir().resolve(name);
- JImageGenerator.compile(src, classes,
- "--add-exports", "jdk.jlink/jdk.tools.jlink.internal=customplugin");
+ JImageGenerator.compile(src, classes);
return JImageGenerator.getJModTask()
.addClassPath(classes)
.jmod(helper.getJmodDir().resolve(name + ".jmod"))
@@ -136,4 +136,44 @@
throw new AssertionError("Custom plugin not called");
}
}
+
+ private void testModuleVerification(Helper helper, Path pluginModulePath) throws IOException {
+ {
+ // dependent module missing check
+ String moduleName = "bar"; // 8147491
+ Path jmodFoo = helper.generateDefaultJModule("foo").assertSuccess();
+ Path jmodBar = helper.generateDefaultJModule(moduleName, "foo").assertSuccess();
+ // rogue filter removes "foo" module resources which are
+ // required by "bar" module. Module checks after plugin
+ // application should detect and report error.
+ JImageGenerator.getJLinkTask()
+ .modulePath(helper.defaultModulePath())
+ .pluginModulePath(pluginModulePath)
+ .output(helper.createNewImageDir(moduleName))
+ .addMods(moduleName)
+ .option("--rogue-filter")
+ .option("/foo/")
+ .call()
+ .assertFailure("java.lang.module.ResolutionException");
+ }
+
+ {
+ // package exported by one module used as concealed package
+ // in another module. But, module-info.class is not updated!
+ String moduleName = "jdk.scripting.nashorn";
+ JImageGenerator.getJLinkTask()
+ .modulePath(helper.defaultModulePath())
+ .pluginModulePath(pluginModulePath)
+ .output(helper.createNewImageDir(moduleName))
+ .addMods(moduleName)
+ // "java.logging" includes a package 'javax.script'
+ // which is an exported package from "java.scripting" module!
+ // module-info.class of java.logging left "as is".
+ .option("--rogue-adder")
+ .option("/java.logging/javax/script/Foo.class")
+ .call()
+ .assertFailure(
+ "Module java.logging's descriptor returns inconsistent package set");
+ }
+ }
}
--- a/jdk/test/tools/jlink/ImageFileCreatorTest.java Thu Aug 25 21:58:38 2016 -0700
+++ b/jdk/test/tools/jlink/ImageFileCreatorTest.java Fri Aug 26 11:50:36 2016 +0530
@@ -220,7 +220,7 @@
};
ImagePluginStack stack = new ImagePluginStack(noopBuilder, Collections.emptyList(),
- null);
+ null, false);
ImageFileCreator.create(archives, ByteOrder.nativeOrder(), stack);
}
--- a/jdk/test/tools/jlink/customplugin/module-info.java Thu Aug 25 21:58:38 2016 -0700
+++ b/jdk/test/tools/jlink/customplugin/module-info.java Fri Aug 26 11:50:36 2016 +0530
@@ -25,4 +25,6 @@
requires jdk.jlink;
provides jdk.tools.jlink.plugin.Plugin with plugin.HelloPlugin;
provides jdk.tools.jlink.plugin.Plugin with plugin.CustomPlugin;
+ provides jdk.tools.jlink.plugin.Plugin with plugin.RogueAdderPlugin;
+ provides jdk.tools.jlink.plugin.Plugin with plugin.RogueFilterPlugin;
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/jlink/customplugin/plugin/RogueAdderPlugin.java Fri Aug 26 11:50:36 2016 +0530
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2016, 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
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package plugin;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.lang.module.ModuleDescriptor;
+import java.util.Collections;
+import java.util.Map;
+import java.util.function.Function;
+import jdk.tools.jlink.plugin.ResourcePool;
+import jdk.tools.jlink.plugin.ResourcePoolBuilder;
+import jdk.tools.jlink.plugin.ResourcePoolEntry;
+import jdk.tools.jlink.plugin.ResourcePoolModule;
+import jdk.tools.jlink.plugin.Plugin;
+
+/**
+ * Rogue adder plugin
+ */
+public final class RogueAdderPlugin implements Plugin {
+ public static final String NAME = "rogue-adder";
+ private String resName;
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) {
+ in.transformAndCopy(Function.identity(), out);
+ out.add(ResourcePoolEntry.create(resName, new byte[1]));
+ return out.build();
+ }
+
+ @Override
+ public String getDescription() {
+ return NAME + "-description";
+ }
+
+ @Override
+ public Category getType() {
+ return Category.FILTER;
+ }
+
+ @Override
+ public boolean hasArguments() {
+ return true;
+ }
+
+ @Override
+ public void configure(Map<String, String> config) {
+ resName = config.get(NAME);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/jlink/customplugin/plugin/RogueFilterPlugin.java Fri Aug 26 11:50:36 2016 +0530
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2016, 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
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package plugin;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.Collections;
+import java.util.Map;
+import jdk.tools.jlink.plugin.ResourcePoolEntry;
+import jdk.tools.jlink.plugin.ResourcePool;
+import jdk.tools.jlink.plugin.ResourcePoolBuilder;
+import jdk.tools.jlink.plugin.Plugin;
+
+/**
+ * Rogue filter plugin
+ */
+public final class RogueFilterPlugin implements Plugin {
+ public static final String NAME = "rogue-filter";
+ private String prefix;
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) {
+ in.transformAndCopy((file) -> {
+ return file.path().startsWith(prefix)? null : file;
+ }, out);
+ return out.build();
+ }
+
+ @Override
+ public String getDescription() {
+ return NAME + "-description";
+ }
+
+ @Override
+ public Category getType() {
+ return Category.FILTER;
+ }
+
+ @Override
+ public boolean hasArguments() {
+ return true;
+ }
+
+ @Override
+ public void configure(Map<String, String> config) {
+ prefix = config.get(NAME);
+ }
+}