8158456: ModuleDescriptor.read does not verify dependence on java.base in module-info.class
authoralanb
Fri, 17 Jun 2016 08:41:39 +0100
changeset 39050 9de41b79ec7e
parent 39049 1108f7f41940
child 39051 1b0204c55914
8158456: ModuleDescriptor.read does not verify dependence on java.base in module-info.class 8159248: ModuleFinder.of not clear that FindException thrown if module descriptor cannot be derived for automatic module Reviewed-by: chegar, mchung
jdk/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java
jdk/src/java.base/share/classes/java/lang/module/ModuleFinder.java
jdk/src/java.base/share/classes/java/lang/module/ModuleInfo.java
jdk/src/java.base/share/classes/java/lang/module/ModulePath.java
jdk/src/java.base/share/classes/java/lang/module/Resolver.java
jdk/test/java/lang/module/AutomaticModulesTest.java
jdk/test/java/lang/module/ModuleDescriptorTest.java
--- a/jdk/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java	Fri Jun 17 10:53:13 2016 +0800
+++ b/jdk/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java	Fri Jun 17 08:41:39 2016 +0100
@@ -1095,7 +1095,7 @@
     public static final class Builder {
 
         final String name;
-        final boolean automatic;
+        boolean automatic;
         boolean synthetic;
         final Map<String, Requires> requires = new HashMap<>();
         final Set<String> uses = new HashSet<>();
@@ -1120,12 +1120,19 @@
          *         identifier
          */
         public Builder(String name) {
-            this(name, false);
+            this.name = requireModuleName(name);
         }
 
-        /* package */ Builder(String name, boolean automatic) {
-            this.name = requireModuleName(name);
-            this.automatic = automatic;
+        /**
+         * Updates the builder so that it builds an automatic module.
+         *
+         * @return This builder
+         *
+         * @see ModuleDescriptor#isAutomatic()
+         */
+        /* package */ Builder automatic() {
+            this.automatic = true;
+            return this;
         }
 
         /**
--- a/jdk/src/java.base/share/classes/java/lang/module/ModuleFinder.java	Fri Jun 17 10:53:13 2016 +0800
+++ b/jdk/src/java.base/share/classes/java/lang/module/ModuleFinder.java	Fri Jun 17 08:41:39 2016 +0100
@@ -42,6 +42,7 @@
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+
 import sun.security.action.GetPropertyAction;
 
 /**
@@ -267,6 +268,13 @@
      *
      * </ul>
      *
+     * <p> If a {@code ModuleDescriptor} cannot be created (by means of the
+     * {@link ModuleDescriptor.Builder ModuleDescriptor.Builder} API) for an
+     * automatic module then {@code FindException} is thrown. This can arise,
+     * for example, when a legal Java identifier name cannot be derived from
+     * the file name of the JAR file or where a package name derived from an
+     * entry ending with {@code .class} is not a legal Java identifier. </p>
+     *
      * <p> In addition to JAR files, an implementation may also support modules
      * that are packaged in other implementation specific module formats. When
      * a file is encountered that is not recognized as a packaged module then
--- a/jdk/src/java.base/share/classes/java/lang/module/ModuleInfo.java	Fri Jun 17 10:53:13 2016 +0800
+++ b/jdk/src/java.base/share/classes/java/lang/module/ModuleInfo.java	Fri Jun 17 08:41:39 2016 +0100
@@ -276,10 +276,7 @@
         throws IOException
     {
         int requires_count = in.readUnsignedShort();
-        if (requires_count == 0 && !mn.equals("java.base")) {
-            throw invalidModuleDescriptor("The requires table must have"
-                                          + " at least one entry");
-        }
+        boolean requiresJavaBase = false;
         for (int i=0; i<requires_count; i++) {
             int index = in.readUnsignedShort();
             int flags = in.readUnsignedShort();
@@ -297,6 +294,17 @@
                     mods.add(Modifier.MANDATED);
             }
             builder.requires(mods, dn);
+            if (dn.equals("java.base"))
+                requiresJavaBase = true;
+        }
+        if (mn.equals("java.base")) {
+            if (requires_count > 0) {
+                throw invalidModuleDescriptor("The requires table for java.base"
+                                              + " must be 0 length");
+            }
+        } else if (!requiresJavaBase) {
+            throw invalidModuleDescriptor("The requires table must have"
+                                          + " an entry for java.base");
         }
 
         int exports_count = in.readUnsignedShort();
--- a/jdk/src/java.base/share/classes/java/lang/module/ModulePath.java	Fri Jun 17 10:53:13 2016 +0800
+++ b/jdk/src/java.base/share/classes/java/lang/module/ModulePath.java	Fri Jun 17 08:41:39 2016 +0100
@@ -55,7 +55,6 @@
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
-import jdk.internal.module.Checks;
 import jdk.internal.module.ConfigurableModuleFinder;
 import jdk.internal.perf.PerfCounter;
 
@@ -343,8 +342,7 @@
             String prefix = cf.substring(0, index);
             if (prefix.equals(SERVICES_PREFIX)) {
                 String sn = cf.substring(index);
-                if (Checks.isJavaIdentifier(sn))
-                    return Optional.of(sn);
+                return Optional.of(sn);
             }
         }
         return Optional.empty();
@@ -378,9 +376,6 @@
      *    to "provides" declarations
      * 5. The Main-Class attribute in the main attributes of the JAR manifest
      *    is mapped to the module descriptor mainClass
-     *
-     * @apiNote This needs to move to somewhere where it can be used by tools,
-     * maybe even a standard API if automatic modules are a Java SE feature.
      */
     private ModuleDescriptor deriveModuleDescriptor(JarFile jf)
         throws IOException
@@ -397,7 +392,7 @@
         String vs = null;
 
         // find first occurrence of -${NUMBER}. or -${NUMBER}$
-        Matcher matcher = Pattern.compile("-(\\d+(\\.|$))").matcher(mn);
+        Matcher matcher = Patterns.DASH_VERSION.matcher(mn);
         if (matcher.find()) {
             int start = matcher.start();
 
@@ -412,16 +407,13 @@
         }
 
         // finally clean up the module name
-        mn =  mn.replaceAll("[^A-Za-z0-9]", ".")  // replace non-alphanumeric
-                .replaceAll("(\\.)(\\1)+", ".")   // collapse repeating dots
-                .replaceAll("^\\.", "")           // drop leading dots
-                .replaceAll("\\.$", "");          // drop trailing dots
-
+        mn = cleanModuleName(mn);
 
         // Builder throws IAE if module name is empty or invalid
         ModuleDescriptor.Builder builder
-            = new ModuleDescriptor.Builder(mn, true)
-                .requires(Requires.Modifier.MANDATED, "java.base");
+            = new ModuleDescriptor.Builder(mn)
+                .automatic()
+                .requires(Set.of(Requires.Modifier.MANDATED), "java.base");
         if (vs != null)
             builder.version(vs);
 
@@ -457,7 +449,7 @@
                     = new BufferedReader(new InputStreamReader(in, "UTF-8"));
                 String cn;
                 while ((cn = nextLine(reader)) != null) {
-                    if (Checks.isJavaIdentifier(cn)) {
+                    if (cn.length() > 0) {
                         providerClasses.add(cn);
                     }
                 }
@@ -478,6 +470,39 @@
         return builder.build();
     }
 
+    /**
+     * Patterns used to derive the module name from a JAR file name.
+     */
+    private static class Patterns {
+        static final Pattern DASH_VERSION = Pattern.compile("-(\\d+(\\.|$))");
+        static final Pattern NON_ALPHANUM = Pattern.compile("[^A-Za-z0-9]");
+        static final Pattern REPEATING_DOTS = Pattern.compile("(\\.)(\\1)+");
+        static final Pattern LEADING_DOTS = Pattern.compile("^\\.");
+        static final Pattern TRAILING_DOTS = Pattern.compile("\\.$");
+    }
+
+    /**
+     * Clean up candidate module name derived from a JAR file name.
+     */
+    private static String cleanModuleName(String mn) {
+        // replace non-alphanumeric
+        mn = Patterns.NON_ALPHANUM.matcher(mn).replaceAll(".");
+
+        // collapse repeating dots
+        mn = Patterns.REPEATING_DOTS.matcher(mn).replaceAll(".");
+
+        // drop leading dots
+        if (mn.length() > 0 && mn.charAt(0) == '.')
+            mn = Patterns.LEADING_DOTS.matcher(mn).replaceAll("");
+
+        // drop trailing dots
+        int len = mn.length();
+        if (len > 0 && mn.charAt(len-1) == '.')
+            mn = Patterns.TRAILING_DOTS.matcher(mn).replaceAll("");
+
+        return mn;
+    }
+
     private Set<String> jarPackages(JarFile jf) {
         return jf.stream()
             .filter(e -> e.getName().endsWith(".class"))
--- a/jdk/src/java.base/share/classes/java/lang/module/Resolver.java	Fri Jun 17 10:53:13 2016 +0800
+++ b/jdk/src/java.base/share/classes/java/lang/module/Resolver.java	Fri Jun 17 08:41:39 2016 +0100
@@ -36,7 +36,6 @@
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
@@ -454,17 +453,13 @@
 
 
     /**
-     * Computes and sets the readability graph for the modules in the given
-     * Resolution object.
+     * Computes the readability graph for the modules in the given Configuration.
      *
      * The readability graph is created by propagating "requires" through the
      * "public requires" edges of the module dependence graph. So if the module
      * dependence graph has m1 requires m2 && m2 requires public m3 then the
-     * resulting readability graph will contain m1 reads m2, m1
-     * reads m3, and m2 reads m3.
-     *
-     * TODO: Use a more efficient algorithm, maybe cache the requires public
-     *       in parent configurations.
+     * resulting readability graph will contain m1 reads m2, m1 reads m3, and
+     * m2 reads m3.
      */
     private Map<ResolvedModule, Set<ResolvedModule>> makeGraph(Configuration cf) {
 
@@ -483,16 +478,15 @@
         Configuration p = parent;
         while (p != null) {
             for (ModuleDescriptor descriptor : p.descriptors()) {
-                ResolvedModule x = p.findModule(descriptor.name()).orElse(null);
-                if (x == null)
-                    throw new InternalError();
+                String name = descriptor.name();
+                ResolvedModule m1 = p.findModule(name)
+                    .orElseThrow(() -> new InternalError(name + " not found"));
                 for (ModuleDescriptor.Requires requires : descriptor.requires()) {
                     if (requires.modifiers().contains(Modifier.PUBLIC)) {
                         String dn = requires.name();
-                        ResolvedModule y = p.findModule(dn).orElse(null);
-                        if (y == null)
-                            throw new InternalError(dn + " not found");
-                        g2.computeIfAbsent(x, k -> new HashSet<>()).add(y);
+                        ResolvedModule m2 = p.findModule(dn)
+                            .orElseThrow(() -> new InternalError(dn + " not found"));
+                        g2.computeIfAbsent(m1, k -> new HashSet<>()).add(m2);
                     }
                 }
             }
@@ -501,53 +495,60 @@
         }
 
         // populate g1 and g2 with the dependences from the selected modules
+
+        Map<String, ResolvedModule> nameToResolved = new HashMap<>();
+
         for (ModuleReference mref : nameToReference.values()) {
             ModuleDescriptor descriptor = mref.descriptor();
-            ResolvedModule x = new ResolvedModule(cf, mref);
+            String name = descriptor.name();
+
+            ResolvedModule m1 = computeIfAbsent(nameToResolved, name, cf, mref);
 
             Set<ResolvedModule> reads = new HashSet<>();
-            g1.put(x, reads);
-
             Set<ResolvedModule> requiresPublic = new HashSet<>();
-            g2.put(x, requiresPublic);
 
             for (ModuleDescriptor.Requires requires : descriptor.requires()) {
                 String dn = requires.name();
 
-                ResolvedModule y;
-                ModuleReference other = nameToReference.get(dn);
-                if (other != null) {
-                    y = new ResolvedModule(cf, other);  // cache?
+                ResolvedModule m2;
+                ModuleReference mref2 = nameToReference.get(dn);
+                if (mref2 != null) {
+                    // same configuration
+                    m2 = computeIfAbsent(nameToResolved, dn, cf, mref2);
                 } else {
-                    y = parent.findModule(dn).orElse(null);
-                    if (y == null)
-                        throw new InternalError("unable to find " + dn);
+                    // parent configuration
+                    m2 = parent.findModule(dn).orElse(null);
+                    if (m2 == null) {
+                        continue;
+                    }
                 }
 
-                // m requires other => m reads other
-                reads.add(y);
+                // m1 requires m2 => m1 reads m2
+                reads.add(m2);
 
-                // m requires public other
+                // m1 requires public m2
                 if (requires.modifiers().contains(Modifier.PUBLIC)) {
-                    requiresPublic.add(y);
+                    requiresPublic.add(m2);
                 }
 
             }
 
-            // automatic modules reads all selected modules and all modules
+            // automatic modules read all selected modules and all modules
             // in parent configurations
             if (descriptor.isAutomatic()) {
-                String name = descriptor.name();
 
                 // reads all selected modules
-                // requires public` all selected automatic modules
+                // `requires public` all selected automatic modules
                 for (ModuleReference mref2 : nameToReference.values()) {
                     ModuleDescriptor descriptor2 = mref2.descriptor();
-                    if (!name.equals(descriptor2.name())) {
-                        ResolvedModule m = new ResolvedModule(cf, mref2);
-                        reads.add(m);
+                    String name2 = descriptor2.name();
+
+                    if (!name.equals(name2)) {
+                        ResolvedModule m2
+                            = computeIfAbsent(nameToResolved, name2, cf, mref2);
+                        reads.add(m2);
                         if (descriptor2.isAutomatic())
-                            requiresPublic.add(m);
+                            requiresPublic.add(m2);
                     }
                 }
 
@@ -565,52 +566,56 @@
 
             }
 
+            g1.put(m1, reads);
+            g2.put(m1, requiresPublic);
         }
 
         // Iteratively update g1 until there are no more requires public to propagate
         boolean changed;
-        Map<ResolvedModule, Set<ResolvedModule>> changes = new HashMap<>();
+        Set<ResolvedModule> toAdd = new HashSet<>();
         do {
             changed = false;
-            for (Entry<ResolvedModule, Set<ResolvedModule>> entry : g1.entrySet()) {
-
-                ResolvedModule m1 = entry.getKey();
-                Set<ResolvedModule> m1Reads = entry.getValue();
-
+            for (Set<ResolvedModule> m1Reads : g1.values()) {
                 for (ResolvedModule m2 : m1Reads) {
                     Set<ResolvedModule> m2RequiresPublic = g2.get(m2);
                     if (m2RequiresPublic != null) {
                         for (ResolvedModule m3 : m2RequiresPublic) {
                             if (!m1Reads.contains(m3)) {
-
-                                // computeIfAbsent
-                                Set<ResolvedModule> s = changes.get(m1);
-                                if (s == null) {
-                                    s = new HashSet<>();
-                                    changes.put(m1, s);
-                                }
-                                s.add(m3);
-                                changed = true;
-
+                                // m1 reads m2, m2 requires public m3
+                                // => need to add m1 reads m3
+                                toAdd.add(m3);
                             }
                         }
                     }
                 }
+                if (!toAdd.isEmpty()) {
+                    m1Reads.addAll(toAdd);
+                    toAdd.clear();
+                    changed = true;
+                }
             }
-
-            if (changed) {
-                for (Map.Entry<ResolvedModule, Set<ResolvedModule>> e :
-                        changes.entrySet()) {
-                    ResolvedModule m = e.getKey();
-                    g1.get(m).addAll(e.getValue());
-                }
-                changes.clear();
-            }
-
         } while (changed);
 
+        return g1;
+    }
 
-        return g1;
+    /**
+     * Equivalent to
+     * <pre>{@code
+     *     map.computeIfAbsent(name, k -> new ResolvedModule(cf, mref))
+     * </pre>}
+     */
+    private ResolvedModule computeIfAbsent(Map<String, ResolvedModule> map,
+                                           String name,
+                                           Configuration cf,
+                                           ModuleReference mref)
+    {
+        ResolvedModule m = map.get(name);
+        if (m == null) {
+            m = new ResolvedModule(cf, mref);
+            map.put(name, m);
+        }
+        return m;
     }
 
 
--- a/jdk/test/java/lang/module/AutomaticModulesTest.java	Fri Jun 17 10:53:13 2016 +0800
+++ b/jdk/test/java/lang/module/AutomaticModulesTest.java	Fri Jun 17 08:41:39 2016 +0100
@@ -24,14 +24,14 @@
 /**
  * @test
  * @library /lib/testlibrary
- * @build AutomaticModulesTest ModuleUtils
+ * @build AutomaticModulesTest ModuleUtils JarUtils
  * @run testng AutomaticModulesTest
  * @summary Basic tests for automatic modules
  */
 
 import java.io.IOException;
-import java.io.OutputStream;
 import java.lang.module.Configuration;
+import java.lang.module.FindException;
 import java.lang.module.ModuleDescriptor;
 import java.lang.module.ModuleDescriptor.Exports;
 import java.lang.module.ModuleDescriptor.Requires.Modifier;
@@ -46,11 +46,9 @@
 import java.util.Optional;
 import java.util.Set;
 import java.util.jar.Attributes;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
-import java.util.jar.JarOutputStream;
 import java.util.jar.Manifest;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
@@ -62,10 +60,8 @@
     private static final Path USER_DIR
          = Paths.get(System.getProperty("user.dir"));
 
-
     @DataProvider(name = "names")
     public Object[][] createNames() {
-
         return new Object[][] {
 
             // JAR file name                module-name[/version]
@@ -98,12 +94,22 @@
         };
     }
 
+    // JAR file names that do not map to a legal module name
+    @DataProvider(name = "badnames")
+    public Object[][] createBadNames() {
+        return new Object[][]{
+
+            { ".jar",     null },
+            { "_.jar",    null }
+
+        };
+    }
+
     /**
      * Test mapping of JAR file names to module names
      */
     @Test(dataProvider = "names")
     public void testNames(String fn, String mid) throws IOException {
-
         String[] s = mid.split("/");
         String mn = s[0];
         String vs = (s.length == 2) ? s[1] : null;
@@ -112,7 +118,7 @@
         Path jf = dir.resolve(fn);
 
         // create empty JAR file
-        createJarFile(jf);
+        createDummyJarFile(jf);
 
         // create a ModuleFinder to find modules in the directory
         ModuleFinder finder = ModuleFinder.of(dir);
@@ -128,17 +134,32 @@
         } else {
             assertEquals(descriptor.version().get().toString(), vs);
         }
+    }
 
+
+    /**
+     * Test impossible mapping of JAR files to modules names
+     */
+    @Test(dataProvider = "badnames", expectedExceptions = FindException.class)
+    public void testBadNames(String fn, String ignore) throws IOException {
+        Path dir = Files.createTempDirectory(USER_DIR, "mods");
+        Path jf = dir.resolve(fn);
+
+        // create empty JAR file
+        createDummyJarFile(jf);
+
+        // should throw FindException
+        ModuleFinder.of(dir).findAll();
     }
 
 
     /**
      * Test all packages are exported
      */
-    public void testExports() throws IOException {
+    public void testPackages() throws IOException {
         Path dir = Files.createTempDirectory(USER_DIR, "mods");
-        createJarFile(dir.resolve("m1.jar"),
-                      "p/C1.class", "p/C2.class", "q/C1.class");
+        createDummyJarFile(dir.resolve("m1.jar"),
+                           "p/C1.class", "p/C2.class", "q/C1.class");
 
         ModuleFinder finder = ModuleFinder.of(dir);
 
@@ -158,6 +179,84 @@
 
 
     /**
+     * Test class file in JAR file where the entry does not correspond to a
+     * legal package name.
+     */
+    @Test(expectedExceptions = FindException.class)
+    public void testBadPackage() throws IOException {
+        Path dir = Files.createTempDirectory(USER_DIR, "mods");
+        createDummyJarFile(dir.resolve("m1.jar"), "p-/T.class");
+
+        // should throw FindException
+        ModuleFinder.of(dir).findAll();
+    }
+
+
+    /**
+     * Test JAR file with META-INF/services configuration file
+     */
+    public void testServicesConfiguration() throws IOException {
+        String service = "p.S";
+        String provider = "p.S1";
+
+        Path tmpdir = Files.createTempDirectory(USER_DIR, "tmp");
+        Path services = tmpdir.resolve("META-INF").resolve("services");
+        Files.createDirectories(services);
+        Files.write(services.resolve(service), Set.of(provider));
+        Path dir = Files.createTempDirectory(USER_DIR, "mods");
+        JarUtils.createJarFile(dir.resolve("m1.jar"), tmpdir);
+
+        ModuleFinder finder = ModuleFinder.of(dir);
+
+        Optional<ModuleReference> mref = finder.find("m1");
+        assertTrue(mref.isPresent(), "m1 not found");
+
+        ModuleDescriptor descriptor = mref.get().descriptor();
+        assertTrue(descriptor.provides().size() == 1);
+        assertTrue(descriptor.provides().containsKey(service));
+        ModuleDescriptor.Provides provides = descriptor.provides().get(service);
+        assertTrue(provides.providers().size() == 1);
+        assertTrue(provides.providers().contains((provider)));
+    }
+
+
+    // META-INF/services configuration file/entries that are not legal
+    @DataProvider(name = "badproviders")
+    public Object[][] createProviders() {
+        return new Object[][] {
+
+                // service type         provider type
+
+                { "p.S",                "-" },
+                { "p.S",                ".S1" },
+                { "p.S",                "S1." },
+
+                { "-",                  "p.S1" },
+                { ".S",                 "p.S1" },
+        };
+    }
+
+    /**
+     * Test JAR file with META-INF/services configuration file with bad
+     * values or names.
+     */
+    @Test(dataProvider = "badproviders", expectedExceptions = FindException.class)
+    public void testBadServicesConfiguration(String service, String provider)
+        throws IOException
+    {
+        Path tmpdir = Files.createTempDirectory(USER_DIR, "tmp");
+        Path services = tmpdir.resolve("META-INF").resolve("services");
+        Files.createDirectories(services);
+        Files.write(services.resolve(service), Set.of(provider));
+        Path dir = Files.createTempDirectory(USER_DIR, "mods");
+        JarUtils.createJarFile(dir.resolve("m1.jar"), tmpdir);
+
+        // should throw FindException
+        ModuleFinder.of(dir).findAll();
+    }
+
+
+    /**
      * Test that a JAR file with a Main-Class attribute results
      * in a module with a main class.
      */
@@ -170,7 +269,7 @@
         attrs.put(Attributes.Name.MAIN_CLASS, mainClass);
 
         Path dir = Files.createTempDirectory(USER_DIR, "mods");
-        createJarFile(dir.resolve("m1.jar"), man);
+        createDummyJarFile(dir.resolve("m1.jar"), man);
 
         ModuleFinder finder = ModuleFinder.of(dir);
 
@@ -184,6 +283,36 @@
     }
 
 
+    // Main-Class files that do not map to a legal Java identifier
+    @DataProvider(name = "badmainclass")
+    public Object[][] createBadMainClass() {
+        return new Object[][]{
+
+            { "p-.Main",     null },
+            { ".Main",       null }
+
+        };
+    }
+
+    /**
+     * Test that a JAR file with a Main-Class attribute that is not a valid
+     * Java identifier
+     */
+    @Test(dataProvider = "badmainclass", expectedExceptions = FindException.class)
+    public void testBadMainClass(String mainClass, String ignore) throws IOException {
+        Manifest man = new Manifest();
+        Attributes attrs = man.getMainAttributes();
+        attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0.0");
+        attrs.put(Attributes.Name.MAIN_CLASS, mainClass);
+
+        Path dir = Files.createTempDirectory(USER_DIR, "mods");
+        createDummyJarFile(dir.resolve("m1.jar"), man);
+
+        // should throw FindException
+        ModuleFinder.of(dir).findAll();
+    }
+
+
     /**
      * Basic test of a configuration created with automatic modules.
      *   m1 requires m2*
@@ -201,8 +330,8 @@
 
         // m2 and m3 are automatic modules
         Path dir = Files.createTempDirectory(USER_DIR, "mods");
-        createJarFile(dir.resolve("m2.jar"), "p/T.class");
-        createJarFile(dir.resolve("m3.jar"), "q/T.class");
+        createDummyJarFile(dir.resolve("m2.jar"), "p/T.class");
+        createDummyJarFile(dir.resolve("m3.jar"), "q/T.class");
 
         // module finder locates m1 and the modules in the directory
         ModuleFinder finder
@@ -252,7 +381,6 @@
      *   m4*
      */
     public void testInConfiguration2() throws IOException {
-
         ModuleDescriptor descriptor1
             =  new ModuleDescriptor.Builder("m1")
                 .requires("m2")
@@ -267,8 +395,8 @@
 
         // m3 and m4 are automatic modules
         Path dir = Files.createTempDirectory(USER_DIR, "mods");
-        createJarFile(dir.resolve("m3.jar"), "p/T.class");
-        createJarFile(dir.resolve("m4.jar"), "q/T.class");
+        createDummyJarFile(dir.resolve("m3.jar"), "p/T.class");
+        createDummyJarFile(dir.resolve("m4.jar"), "q/T.class");
 
         // module finder locates m1 and the modules in the directory
         ModuleFinder finder
@@ -315,7 +443,6 @@
         assertTrue(m4.reads().contains(m2));
         assertTrue(m4.reads().contains(m3));
         testReadAllBootModules(cf, "m4");    // m4 reads all modules in boot layer
-
     }
 
 
@@ -327,7 +454,6 @@
      *   m4*
      */
     public void testInConfiguration3() throws IOException {
-
         ModuleDescriptor descriptor1
             =  new ModuleDescriptor.Builder("m1")
                 .requires("m2")
@@ -342,8 +468,8 @@
 
         // m3 and m4 are automatic modules
         Path dir = Files.createTempDirectory(USER_DIR, "mods");
-        createJarFile(dir.resolve("m3.jar"), "p/T.class");
-        createJarFile(dir.resolve("m4.jar"), "q/T.class");
+        createDummyJarFile(dir.resolve("m3.jar"), "p/T.class");
+        createDummyJarFile(dir.resolve("m4.jar"), "q/T.class");
 
         // module finder locates m1 and the modules in the directory
         ModuleFinder finder
@@ -396,7 +522,6 @@
         assertTrue(m4.reads().contains(m2));
         assertTrue(m4.reads().contains(m3));
         testReadAllBootModules(cf, "m4");    // m4 reads all modules in boot layer
-
     }
 
 
@@ -412,8 +537,8 @@
 
         // m2 and m3 are simple JAR files
         Path dir = Files.createTempDirectory(USER_DIR, "mods");
-        createJarFile(dir.resolve("m2.jar"), "p/T.class");
-        createJarFile(dir.resolve("m3.jar"), "q/T2.class");
+        createDummyJarFile(dir.resolve("m2.jar"), "p/T.class");
+        createDummyJarFile(dir.resolve("m3.jar"), "q/T2.class");
 
         // module finder locates m1 and the modules in the directory
         ModuleFinder finder
@@ -447,7 +572,7 @@
      */
     public void testMisc() throws IOException {
         Path dir = Files.createTempDirectory(USER_DIR, "mods");
-        Path m1_jar = createJarFile(dir.resolve("m1.jar"), "p/T.class");
+        Path m1_jar = createDummyJarFile(dir.resolve("m1.jar"), "p/T.class");
 
         ModuleFinder finder = ModuleFinder.of(m1_jar);
 
@@ -535,38 +660,32 @@
      * Creates a JAR file, optionally with a manifest, and with the given
      * entries. The entries will be empty in the resulting JAR file.
      */
-    static Path createJarFile(Path file, Manifest man, String... entries)
+    static Path createDummyJarFile(Path jarfile, Manifest man, String... entries)
         throws IOException
     {
-        try (OutputStream out = Files.newOutputStream(file)) {
-            try (JarOutputStream jos = new JarOutputStream(out)) {
-
-                if (man != null) {
-                    JarEntry je = new JarEntry(JarFile.MANIFEST_NAME);
-                    jos.putNextEntry(je);
-                    man.write(jos);
-                    jos.closeEntry();
-                }
+        Path dir = Files.createTempDirectory(USER_DIR, "tmp");
 
-                for (String entry : entries) {
-                    JarEntry je = new JarEntry(entry);
-                    jos.putNextEntry(je);
-                    jos.closeEntry();
-                }
+        for (String entry : entries) {
+            Path file = dir.resolve(entry);
+            Path parent = file.getParent();
+            if (parent != null)
+                Files.createDirectories(parent);
+            Files.createFile(file);
+        }
 
-            }
-        }
-        return file;
+        Path[] paths = Stream.of(entries).map(Paths::get).toArray(Path[]::new);
+        JarUtils.createJarFile(jarfile, man, dir, paths);
+        return jarfile;
     }
 
     /**
      * Creates a JAR file and with the given entries. The entries will be empty
      * in the resulting JAR file.
      */
-    static Path createJarFile(Path file, String... entries)
+    static Path createDummyJarFile(Path jarfile, String... entries)
         throws IOException
     {
-        return createJarFile(file, null, entries);
+        return createDummyJarFile(jarfile, null, entries);
     }
 
 }
--- a/jdk/test/java/lang/module/ModuleDescriptorTest.java	Fri Jun 17 10:53:13 2016 +0800
+++ b/jdk/test/java/lang/module/ModuleDescriptorTest.java	Fri Jun 17 08:41:39 2016 +0100
@@ -23,6 +23,7 @@
 
 /**
  * @test
+ * @modules java.base/jdk.internal.module
  * @run testng ModuleDescriptorTest
  * @summary Basic test for java.lang.module.ModuleDescriptor and its builder
  */
@@ -47,6 +48,7 @@
 
 import static java.lang.module.ModuleDescriptor.Requires.Modifier.*;
 
+import jdk.internal.module.ModuleInfoWriter;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 import static org.testng.Assert.*;
@@ -668,6 +670,37 @@
         ModuleDescriptor.read(bb);
     }
 
+    // The requires table for java.base must be 0 length
+    @Test(expectedExceptions = InvalidModuleDescriptorException.class)
+    public void testReadOfJavaBaseWithRequires() {
+        ModuleDescriptor descriptor
+            = new ModuleDescriptor.Builder("java.base")
+                .requires("other")
+                .build();
+        ByteBuffer bb = ModuleInfoWriter.toByteBuffer(descriptor);
+        ModuleDescriptor.read(bb);
+    }
+
+    // The requires table must have an entry for java.base
+    @Test(expectedExceptions = InvalidModuleDescriptorException.class)
+    public void testReadWithEmptyRequires() {
+        ModuleDescriptor descriptor = new ModuleDescriptor.Builder("m1").build();
+        ByteBuffer bb = ModuleInfoWriter.toByteBuffer(descriptor);
+        ModuleDescriptor.read(bb);
+    }
+
+    // The requires table must have an entry for java.base
+    @Test(expectedExceptions = InvalidModuleDescriptorException.class)
+    public void testReadWithNoRequiresBase() {
+        ModuleDescriptor descriptor
+            = new ModuleDescriptor.Builder("m1")
+                .requires("m2")
+                .build();
+        ByteBuffer bb = ModuleInfoWriter.toByteBuffer(descriptor);
+        ModuleDescriptor.read(bb);
+    }
+
+
     public void testReadWithNull() throws Exception {
         Module base = Object.class.getModule();