6901992: InvalidJarIndexException due to bug in sun.misc.JarIndex.merge()
authorchegar
Tue, 19 Jun 2012 10:20:45 +0100
changeset 13030 d4cdee9d5740
parent 13029 dfd934f86768
child 13031 a410394dc617
6901992: InvalidJarIndexException due to bug in sun.misc.JarIndex.merge() Reviewed-by: chegar Contributed-by: dbelfer@gmail.com
jdk/src/share/classes/sun/misc/JarIndex.java
jdk/test/sun/misc/JarIndex/JarIndexMergeForClassLoaderTest.java
jdk/test/sun/misc/JarIndex/JarIndexMergeTest.java
--- a/jdk/src/share/classes/sun/misc/JarIndex.java	Tue Jun 19 16:21:17 2012 +0900
+++ b/jdk/src/share/classes/sun/misc/JarIndex.java	Tue Jun 19 10:20:45 2012 +0100
@@ -201,23 +201,20 @@
             packageName = fileName;
         }
 
-        // add the mapping to indexMap
-        addToList(packageName, jarName, indexMap);
-
-        // add the mapping to jarMap
-        addToList(jarName, packageName, jarMap);
+        addMapping(packageName, jarName);
     }
 
     /**
      * Same as add(String,String) except that it doesn't strip off from the
-     * last index of '/'. It just adds the filename.
+     * last index of '/'. It just adds the jarItem (filename or package)
+     * as it is received.
      */
-    private void addExplicit(String fileName, String jarName) {
+    private void addMapping(String jarItem, String jarName) {
         // add the mapping to indexMap
-        addToList(fileName, jarName, indexMap);
+        addToList(jarItem, jarName, indexMap);
 
         // add the mapping to jarMap
-        addToList(jarName, fileName, jarMap);
+        addToList(jarName, jarItem, jarMap);
      }
 
     /**
@@ -248,18 +245,14 @@
                     fileName.equals(JarFile.MANIFEST_NAME))
                     continue;
 
-                if (!metaInfFilenames) {
+                if (!metaInfFilenames || !fileName.startsWith("META-INF/")) {
                     add(fileName, currentJar);
-                } else {
-                    if (!fileName.startsWith("META-INF/")) {
-                        add(fileName, currentJar);
-                    } else if (!entry.isDirectory()) {
+                } else if (!entry.isDirectory()) {
                         // Add files under META-INF explicitly so that certain
                         // services, like ServiceLoader, etc, can be located
                         // with greater accuracy. Directories can be skipped
                         // since each file will be added explicitly.
-                        addExplicit(fileName, currentJar);
-                    }
+                        addMapping(fileName, currentJar);
                 }
             }
 
@@ -324,8 +317,7 @@
                 jars.add(currentJar);
             } else {
                 String name = line;
-                addToList(name, currentJar, indexMap);
-                addToList(currentJar, name, jarMap);
+                addMapping(name, currentJar);
             }
         }
 
@@ -354,7 +346,7 @@
                 if (path != null) {
                     jarName = path.concat(jarName);
                 }
-                toIndex.add(packageName, jarName);
+                toIndex.addMapping(packageName, jarName);
             }
         }
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/misc/JarIndex/JarIndexMergeForClassLoaderTest.java	Tue Jun 19 10:20:45 2012 +0100
@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) 2012, 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.
+ */
+
+/*
+ * @test
+ * @bug     6901992
+ * @summary InvalidJarIndexException due to bug in sun.misc.JarIndex.merge()
+ *          Test URLClassLoader usage of the merge method when using indexes
+ * @author  Diego Belfer
+ */
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+
+public class JarIndexMergeForClassLoaderTest {
+    static final String slash = File.separator;
+    static final String testClassesDir = System.getProperty("test.classes", ".");
+    static final String jar;
+    static final boolean debug = true;
+    static final File tmpFolder = new File(testClassesDir);
+
+    static {
+        String javaHome = System.getProperty("java.home");
+        if (javaHome.endsWith("jre")) {
+            int index = javaHome.lastIndexOf(slash);
+            if (index != -1)
+                javaHome = javaHome.substring(0, index);
+        }
+
+        jar = javaHome + slash + "bin" + slash + "jar";
+    }
+
+    public static void main(String[] args) throws Exception {
+        // Create the jars file
+        File jar1 = buildJar1();
+        File jar2 = buildJar2();
+        File jar3 = buildJar3();
+
+        // Index jar files in two levels: jar1 -> jar2 -> jar3
+        createIndex(jar2.getName(), jar3.getName());
+        createIndex(jar1.getName(), jar2.getName());
+
+        // Get root jar of the URLClassLoader
+        URL url = jar1.toURI().toURL();
+
+        URLClassLoader classLoader = new URLClassLoader(new URL[] { url });
+
+        assertResource(classLoader, "com/jar1/resource.file", "jar1");
+        assertResource(classLoader, "com/test/resource1.file", "resource1");
+        assertResource(classLoader, "com/jar2/resource.file", "jar2");
+        assertResource(classLoader, "com/test/resource2.file", "resource2");
+        assertResource(classLoader, "com/test/resource3.file", "resource3");
+
+        /*
+         * The following two asserts failed before the fix of the bug 6901992
+         */
+        // Check that an existing file is found using the merged index
+        assertResource(classLoader, "com/missing/jar3/resource.file", "jar3");
+        // Check that a non existent file in directory which does not contain
+        // any file is not found and it does not throw InvalidJarIndexException
+        assertResource(classLoader, "com/missing/nofile", null);
+    }
+
+    private static File buildJar3() throws FileNotFoundException, IOException {
+        JarBuilder jar3Builder = new JarBuilder(tmpFolder, "jar3.jar");
+        jar3Builder.addResourceFile("com/test/resource3.file", "resource3");
+        jar3Builder.addResourceFile("com/missing/jar3/resource.file", "jar3");
+        return jar3Builder.build();
+    }
+
+    private static File buildJar2() throws FileNotFoundException, IOException {
+        JarBuilder jar2Builder = new JarBuilder(tmpFolder, "jar2.jar");
+        jar2Builder.addResourceFile("com/jar2/resource.file", "jar2");
+        jar2Builder.addResourceFile("com/test/resource2.file", "resource2");
+        return jar2Builder.build();
+    }
+
+    private static File buildJar1() throws FileNotFoundException, IOException {
+        JarBuilder jar1Builder = new JarBuilder(tmpFolder, "jar1.jar");
+        jar1Builder.addResourceFile("com/jar1/resource.file", "jar1");
+        jar1Builder.addResourceFile("com/test/resource1.file", "resource1");
+        return jar1Builder.build();
+    }
+
+    /* create the index */
+    static void createIndex(String parentJar, String childJar) {
+        // ProcessBuilder is used so that the current directory can be set
+        // to the directory that directly contains the jars.
+        debug("Running jar to create the index for: " + parentJar + " and "
+                + childJar);
+        ProcessBuilder pb = new ProcessBuilder(jar, "-i", parentJar, childJar);
+
+        pb.directory(tmpFolder);
+        // pd.inheritIO();
+        try {
+            Process p = pb.start();
+            if (p.waitFor() != 0)
+                throw new RuntimeException("jar indexing failed");
+
+            if (debug && p != null) {
+                debugStream(p.getInputStream());
+                debugStream(p.getErrorStream());
+            }
+        } catch (InterruptedException | IOException x) {
+            throw new RuntimeException(x);
+        }
+    }
+
+    private static void debugStream(InputStream is) throws IOException {
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                debug(line);
+            }
+        }
+    }
+
+    private static void assertResource(URLClassLoader classLoader, String file,
+            String expectedContent) throws IOException {
+        InputStream fileStream = classLoader.getResourceAsStream(file);
+
+        if (fileStream == null && expectedContent == null) {
+            return;
+        }
+        if (fileStream == null && expectedContent != null) {
+            throw new RuntimeException(
+                    buildMessage(file, expectedContent, null));
+        }
+        try {
+            String actualContent = readAsString(fileStream);
+
+            if (fileStream != null && expectedContent == null) {
+                throw new RuntimeException(buildMessage(file, null,
+                        actualContent));
+            }
+            if (!expectedContent.equals(actualContent)) {
+                throw new RuntimeException(buildMessage(file, expectedContent,
+                        actualContent));
+            }
+        } finally {
+            fileStream.close();
+        }
+    }
+
+    private static String buildMessage(String file, String expectedContent,
+            String actualContent) {
+        return "Expected: " + expectedContent + " for: " + file + " was: "
+                + actualContent;
+    }
+
+    private static String readAsString(InputStream fileStream)
+            throws IOException {
+        byte[] buffer = new byte[1024];
+        int count, len = 0;
+        while ((count = fileStream.read(buffer, len, buffer.length-len)) != -1)
+                len += count;
+        return new String(buffer, 0, len, "ASCII");
+    }
+
+    static void debug(Object message) {
+        if (debug)
+            System.out.println(message);
+    }
+
+    /*
+     * Helper class for building jar files
+     */
+    public static class JarBuilder {
+        private JarOutputStream os;
+        private File jarFile;
+
+        public JarBuilder(File tmpFolder, String jarName)
+            throws FileNotFoundException, IOException
+        {
+            this.jarFile = new File(tmpFolder, jarName);
+            this.os = new JarOutputStream(new FileOutputStream(jarFile));
+        }
+
+        public void addResourceFile(String pathFromRoot, String content)
+            throws IOException
+        {
+            JarEntry entry = new JarEntry(pathFromRoot);
+            os.putNextEntry(entry);
+            os.write(content.getBytes("ASCII"));
+            os.closeEntry();
+        }
+
+        public File build() throws IOException {
+            os.close();
+            return jarFile;
+        }
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/misc/JarIndex/JarIndexMergeTest.java	Tue Jun 19 10:20:45 2012 +0100
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2012, 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.
+ */
+
+/*
+ * @test
+ * @bug 6901992
+ * @compile -XDignore.symbol.file JarIndexMergeTest.java
+ * @run main JarIndexMergeTest
+ * @summary InvalidJarIndexException due to bug in sun.misc.JarIndex.merge()
+ * @author  Diego Belfer
+ */
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+// implementation specific API
+import sun.misc.JarIndex;
+
+public class JarIndexMergeTest {
+    static final String slash = File.separator;
+    static final String testClassesDir = System.getProperty("test.classes", ".");
+    static final File tmpFolder = new File(testClassesDir);
+
+    public static void main(String[] args) throws Exception {
+        File jar1 = buildJar1();
+        File jar2 = buildJar2();
+
+        JarIndex jarIndex1 = new JarIndex(new String[] { jar1.getAbsolutePath() });
+        JarIndex jarIndex2 = new JarIndex(new String[] { jar2.getAbsolutePath() });
+
+        jarIndex1.merge(jarIndex2, null);
+
+        assertFileResolved(jarIndex2, "com/test1/resource1.file",
+                           jar1.getAbsolutePath());
+        assertFileResolved(jarIndex2, "com/test2/resource2.file",
+                           jar2.getAbsolutePath());
+    }
+
+    static void assertFileResolved(JarIndex jarIndex2, String file,
+                                   String jarName) {
+        @SuppressWarnings("unchecked")
+        LinkedList<String> jarLists = (LinkedList<String>)jarIndex2.get(file);
+        if (jarLists == null || jarLists.size() == 0 ||
+            !jarName.equals(jarLists.get(0))) {
+            throw new RuntimeException(
+                "Unexpected result: the merged index must resolve file: "
+                + file);
+        }
+    }
+
+    private static File buildJar1() throws FileNotFoundException, IOException {
+        JarBuilder jar1Builder = new JarBuilder(tmpFolder, "jar1-merge.jar");
+        jar1Builder.addResourceFile("com/test1/resource1.file", "resource1");
+        return jar1Builder.build();
+    }
+
+    private static File buildJar2() throws FileNotFoundException, IOException {
+        JarBuilder jar2Builder = new JarBuilder(tmpFolder, "jar2-merge.jar");
+        jar2Builder.addResourceFile("com/test2/resource2.file", "resource2");
+        return jar2Builder.build();
+    }
+
+    /*
+     * Helper class for building jar files
+     */
+    public static class JarBuilder {
+        private JarOutputStream os;
+        private File jarFile;
+
+        public JarBuilder(File tmpFolder, String jarName)
+            throws FileNotFoundException, IOException
+        {
+            this.jarFile = new File(tmpFolder, jarName);
+            this.os = new JarOutputStream(new FileOutputStream(jarFile));
+        }
+
+        public void addResourceFile(String pathFromRoot, String content)
+            throws IOException
+        {
+            JarEntry entry = new JarEntry(pathFromRoot);
+            os.putNextEntry(entry);
+            os.write(content.getBytes("ASCII"));
+            os.closeEntry();
+        }
+
+        public File build() throws IOException {
+            os.close();
+            return jarFile;
+        }
+    }
+}
+