Hi Chris,

Here is a new patch containing new version of the tests. The new versions
generate a the Jar on the fly as we discussed.

Let me know if there is anything else you think should be improved.

Best,
Diego

On Thu, Jun 14, 2012 at 7:32 AM, Chris Hegarty <chris.hega...@oracle.com>wrote:

> Diego,
>
> It's not too difficult to create jars on the fly using the Jar API. Here
> is a small example that I think would work nice in this case. Files created
> ( and paths are relative to the jtreg scratch, or working dir if running
> outside of jtreg ).
>
> Do you think you could use similar to create the jars for your test?
>
>    createJar("a.jar", jarAList);
>    createJar("b.jar", jarBList);
>    .......
>
>    static void createJar(String jarName, Map<String,String> contents)
>        throws Exception
>    {
>        try (FileOutputStream aJar = new FileOutputStream(jarName);
>             JarOutputStream jos = new JarOutputStream(aJar)) {
>            Set<Entry<String,String>> entries = contents.entrySet();
>            for (Entry<String,String> entry : entries)
>                writeJarEntry(jos, entry.getKey(),
>                              entry.getValue().getBytes("**ASCII"));
>        }
>    }
>
>    static void writeJarEntry(JarOutputStream jos, String name, byte[] data)
>        throws Exception
>    {
>        JarEntry entry = new JarEntry(name);
>        jos.putNextEntry(entry);
>        jos.write(data);
>    }
>
>    static final Map<String,String> jarAList = new HashMap<>();
>    static final Map<String,String> jarBList = new HashMap<>();
>    static {
>        jarAList.put("com/foo/**resource1.txt", "some random data");
>        jarAList.put("com/bar/**resource2.txt", "some more random data!");
>        jarAList.put("com/baz/**resource3.txt", "even more random
> data!!!");
>        jarBList.put("x/y/resourceA.**dat", "Hello there");
>        jarBList.put("x/y/resourceB.**dat", "Goodbye");
>        jarBList.put("x/y/resourceC.**dat", "Hello\nfrom\nb\ndot\njar");
>    }
>
> Thanks,
> -Chris.
>
>
> On 14/06/2012 03:20, Diego Belfer wrote:
>
>> Hi Chris,
>>
>> There is no way to generate a jar without directory entries using the
>> jar tool; there is no option for that. What do you think is the best way
>> to handle this ?
>> I don't want to re-implement the logic for creating a jar using the
>> JarOutputStream class.
>>
>> Do you think it is possible to add a new option to the Jar tool Main
>> class to exclude directory entries? The option does not need to be
>> exposed by the command line tool to final users if this an issue,
>> although I think it may be useful for them too.
>>
>> Best,
>> Diego
>>
>>
>> On Wed, Jun 13, 2012 at 7:12 PM, Diego Belfer <dbel...@gmail.com
>> <mailto:dbel...@gmail.com>> wrote:
>>
>>    Chris,
>>
>>    I was thinking of something similar. I will create the jars and
>>    their contents using Java code. There is no need of creating real
>>    class files, using a few resource files and some directories will be
>>    enough.
>>
>>    Best,
>>    Diego
>>
>>
>>    On Wed, Jun 13, 2012 at 6:46 PM, chris hegarty
>>    <chris.hega...@oracle.com 
>> <mailto:chris.hegarty@oracle.**com<chris.hega...@oracle.com>>>
>> wrote:
>>
>>        Diego,
>>
>>        I'm thinking that some of the trivial source files, to compile
>>        and built into the jars, could be simply created and written by
>>        the test itself, rather than checking them all in. If this makes
>>        it cleaner. I really don't like all the file in
>>        test/sun/misc/JarIndex/__**metaInfFilenames, but at least it is
>>
>>        quite understandable.
>>
>>        -Chris.
>>
>>
>>        On 13/06/2012 20:36, Diego Belfer wrote:
>>
>>            Hi Chris,
>>
>>            That's right. The only non-cleanup change is the one in the
>>            merge.
>>
>>            Regarding the test case,  I will re-write them in order to
>>            generate the
>>            jars on fly. I'd scanned the jdk/test folder and found a few
>>            jars,
>>            that's why I included them.  I have seen your test case, I
>>            will use it
>>            as a sample.
>>
>>            I had not seen your comment in the bug report. Maybe there
>>            are other
>>            cases which trigger the InvalidJarIndexException, but, as
>>            far as I could
>>            see, the validIndex method checks that at least one entry of
>>            the jar
>>            matches the target path found in the index. If directory
>>            entries are not
>>            present in the jar, stripped paths generated during the
>>            merge and used
>>            by the index will return jars which may not contain entries
>>            for them,
>>            triggering the exception.
>>            When all directory entries are present, if a jar contains an
>>            entry for
>>            "xxx/yyy/resource.file", it  will contain entries for "xxx",
>>            "xxx/yyy"
>>            and "xxx/yyy/resource.file".
>>
>>
>>            Best,
>>            Diego
>>
>>
>>            On Wed, Jun 13, 2012 at 12:05 PM, Chris Hegarty
>>            <chris.hega...@oracle.com 
>> <mailto:chris.hegarty@oracle.**com<chris.hega...@oracle.com>
>> >
>>             <mailto:chris.hegarty@oracle._**_com
>>
>>            <mailto:chris.hegarty@oracle.**com <chris.hega...@oracle.com>>>>
>> wrote:
>>
>>                Hi Diego,
>>
>>                Thanks for picking up this bug.
>>
>>                I think your changes look fine. Mainly cleanup except
>>            for add ->
>>                addExplicit/addMapping in merge, right? BTW the cleanup
>>            makes this
>>                more readable.
>>
>>                Unfortunately, the tests you created require checking in
>>            a binary
>>                jar file. This is a real no no for the OpenJDK, we
>>            really need to
>>                create these jars on the fly. I did similar for
>>                test/sun/misc/JarIndex/____**metaInfFilenames/, but I
>>
>>            really wish I
>>
>>                generated the source files for these tests rather than
>>            checking in
>>                so many pointless files.
>>
>>                I can look at helping with writing suitable tests for this.
>>
>>
>>             > That's because I was using jars containing "directory
>>            entries"
>>             > (I was unaware that jar files may not include them)
>>
>>                Strangely I added the comment "Remove directories from
>>            jar files
>>                being indexed." to the workaround section of the bug.
>>            You seem to be
>>                seeing the opposite, right?
>>
>>                -Chris.
>>
>>
>>
>>                On 13/06/2012 06:11, Diego Belfer wrote:
>>
>>                    Hi,
>>
>>                    I have finally reproduced the
>>            InvalidJarIndexException bug as
>>                    reported in
>>                    the ticket. I mentioned in a previous email, that
>>            the only way
>>                    I'd found
>>                    for getting the error was to use an invalid index file
>>                    (INDEX.LIST), which
>>                    did not have any sense. That's because I was using
>>            jars containing
>>            "directory entries" (I was unaware that jar files may not
>>                    include them)
>>
>>                    After reviewing the URLClasspath$JarLoader class and
>> the
>>                    validIndex method,
>>                    I notice it is possible to get the exception for a
>>            Jar file
>>                    which does not
>>                    include directory entries. In order to trigger the
>>            issue, the
>>                    Jar must be
>>                    referenced by an intermediary INDEX.LIST and the
>>            intermediary
>>                    Jar index
>>                    should have been merged to its parent index.
>>            Although, jar tool
>>                    includes
>>                    directory entries in the generated jar files,
>>            Eclipse default
>>                    option for
>>                    exporting jars does not include them (AFAIK), so
>>            this might be
>>                    quite common.
>>
>>                    I have created a new PATCH which includes an
>>            additional test
>>                    case which
>>                    uses the URLClassLoader to trigger the
>>            InvalidIndexException.
>>
>>                    The patch is attached, please consider it for review.
>>
>>                    Best,
>>                    Diego Belfer [muralx]
>>
>>
>>
>>                    On Mon, Jun 11, 2012 at 4:47 PM, Diego
>>            Belfer<dbel...@gmail.com <mailto:dbel...@gmail.com>
>>            <mailto:dbel...@gmail.com <mailto:dbel...@gmail.com>>>  wrote:
>>
>>
>>
>>                        Hi,
>>
>>                        Here is a patch that fixes the merge method of
>>            the JarIndex.
>>                        This bug was
>>                        reported as the cause of the bug 6901992.
>>            Although, I was
>>                        not able to
>>                        reproduce the BUG itself
>>            (InvalidJarIndexException), I did
>>                        verified that
>>                        the method had a bug, and resources/classes
>>            where not found
>>                        in a jarIndex
>>                        with merged contents.
>>
>>                        If you think it is possible to commit this fix
>>            without actually
>>                        reproducing the original bug report, please
>>            consider this
>>                        patch for review.
>>
>>                        Thanks,
>>                        Diego Belfer [muralx]
>>
>>
>>
>>
>>
>>
# HG changeset patch
# User muralx
# Date 1339820030 10800
# Node ID 7b531b09855a099fe0e9ea06ed7f77772cb62ed0
# Parent  fc575c78f5d314fd8ccbdc86c8b2d7631d736960
[PATCH] 6901992 - Possible InvalidJarIndexException due to bug in 
sun.misc.JarIndex.merge()

diff --git a/.hgignore b/.hgignore
--- a/.hgignore
+++ b/.hgignore
@@ -5,3 +5,4 @@
 ^make/netbeans/.*/dist/
 ^.hgtip
 .DS_Store
+\.class$
diff --git a/src/share/classes/sun/misc/JarIndex.java 
b/src/share/classes/sun/misc/JarIndex.java
--- a/src/share/classes/sun/misc/JarIndex.java
+++ b/src/share/classes/sun/misc/JarIndex.java
@@ -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);
             }
         }
     }
diff --git a/test/sun/misc/JarIndex/JarIndexMergeForClassLoaderTest.java 
b/test/sun/misc/JarIndex/JarIndexMergeForClassLoaderTest.java
new file mode 100644
--- /dev/null
+++ b/test/sun/misc/JarIndex/JarIndexMergeForClassLoaderTest.java
@@ -0,0 +1,224 @@
+/*
+ * 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 Possible 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 String slash = File.separator;
+    static String testClasses = System.getProperty("test.classes");
+    static String testClassesDir = testClasses != null ? testClasses : ".";
+    static String jar;
+    static boolean debug = true;
+    static 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 ie) {
+            throw new RuntimeException(ie);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static void debugStream(InputStream is) throws IOException {
+        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+        String line;
+        while ((line = reader.readLine()) != null) {
+            debug(line);
+        }
+        reader.close();
+    }
+
+    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[1000];
+        int count = fileStream.read(buffer);
+        return new String(buffer, 0, count, "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;
+       }
+    }
+}
+
diff --git a/test/sun/misc/JarIndex/JarIndexMergeTest.java 
b/test/sun/misc/JarIndex/JarIndexMergeTest.java
new file mode 100644
--- /dev/null
+++ b/test/sun/misc/JarIndex/JarIndexMergeTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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 Possible 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;
+
+import sun.misc.JarIndex;
+
+public class JarIndexMergeTest {
+    static String slash = File.separator;
+    static String testClasses = System.getProperty("test.classes");
+    static String testClassesDir = testClasses != null ? testClasses : ".";
+    static 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) {
+
+       LinkedList jarLists = 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;
+       }
+    }
+}
+

Reply via email to