8147491: module graph consistency checks after jlink plugins operate on module pool
authorsundar
Fri, 26 Aug 2016 11:50:36 +0530
changeset 40556 27f68990ba9d
parent 40555 face0a3ae86c
child 40557 7aff3e34365e
8147491: module graph consistency checks after jlink plugins operate on module pool Reviewed-by: jlaskey, mchung, psandoz
jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImagePluginStack.java
jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ResourcePoolConfiguration.java
jdk/test/tools/jlink/CustomPluginTest.java
jdk/test/tools/jlink/ImageFileCreatorTest.java
jdk/test/tools/jlink/customplugin/module-info.java
jdk/test/tools/jlink/customplugin/plugin/RogueAdderPlugin.java
jdk/test/tools/jlink/customplugin/plugin/RogueFilterPlugin.java
--- 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);
+    }
+}