6901992: InvalidJarIndexException due to bug in sun.misc.JarIndex.merge()
Reviewed-by: chegar
Contributed-by: dbelfer@gmail.com
--- 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;
+ }
+ }
+}
+