8164805: Fail to create a MR modular JAR with a versioned entry of a concealed package
Reviewed-by: chegar, mchung
--- a/jdk/src/jdk.jartool/share/classes/sun/tools/jar/Main.java Tue Oct 25 12:58:34 2016 -0700
+++ b/jdk/src/jdk.jartool/share/classes/sun/tools/jar/Main.java Tue Oct 25 13:51:08 2016 -0700
@@ -80,6 +80,7 @@
String fname, mname, ename;
String zname = "";
String rootjar = null;
+ Set<String> concealedPackages = new HashSet<>();
private static final int BASE_VERSION = 0;
@@ -821,22 +822,21 @@
return true;
}
- private static Set<String> findPackages(ZipFile zf) {
- return zf.stream()
- .filter(e -> e.getName().endsWith(".class"))
- .map(e -> toPackageName(e))
- .filter(pkg -> pkg.length() > 0)
- .distinct()
- .collect(Collectors.toSet());
- }
-
private static String toPackageName(ZipEntry entry) {
return toPackageName(entry.getName());
}
private static String toPackageName(String path) {
assert path.endsWith(".class");
- int index = path.lastIndexOf('/');
+ int index;
+ if (path.startsWith(VERSIONS_DIR)) {
+ index = path.indexOf('/', VERSIONS_DIR.length());
+ if (index <= 0) {
+ return "";
+ }
+ path = path.substring(index + 1);
+ }
+ index = path.lastIndexOf('/');
if (index != -1) {
return path.substring(0, index).replace('/', '.');
} else {
@@ -875,7 +875,7 @@
entryMap.put(entryName, entry);
} else if (entries.add(entry)) {
jarEntries.add(entryName);
- if (entry.basename.endsWith(".class") && !entryName.startsWith(VERSIONS_DIR))
+ if (entry.basename.endsWith(".class"))
packages.add(toPackageName(entry.basename));
if (isUpdate)
entryMap.put(entryName, entry);
@@ -1068,7 +1068,7 @@
}
jarEntries.add(name);
- if (name.endsWith(".class") && !(name.startsWith(VERSIONS_DIR)))
+ if (name.endsWith(".class"))
packages.add(toPackageName(name));
}
}
@@ -1762,6 +1762,13 @@
}
/**
+ * Print a warning message
+ */
+ void warn(String s) {
+ err.println(s);
+ }
+
+ /**
* Main routine to start program.
*/
public static void main(String args[]) {
@@ -1975,24 +1982,30 @@
ByteBuffer bb = ByteBuffer.wrap(moduleInfos.get(MODULE_INFO));
ModuleDescriptor rd = ModuleDescriptor.read(bb);
- Set<String> exports = rd.exports()
- .stream()
- .map(Exports::source)
- .collect(toSet());
-
- Set<String> conceals = packages.stream()
- .filter(p -> !exports.contains(p))
- .collect(toSet());
+ concealedPackages = findConcealedPackages(rd);
for (Map.Entry<String,byte[]> e: moduleInfos.entrySet()) {
ModuleDescriptor vd = ModuleDescriptor.read(ByteBuffer.wrap(e.getValue()));
if (!(isValidVersionedDescriptor(vd, rd)))
return false;
- e.setValue(extendedInfoBytes(rd, vd, e.getValue(), conceals));
+ e.setValue(extendedInfoBytes(rd, vd, e.getValue(), concealedPackages));
}
return true;
}
+ private Set<String> findConcealedPackages(ModuleDescriptor md){
+ Objects.requireNonNull(md);
+
+ Set<String> exports = md.exports()
+ .stream()
+ .map(Exports::source)
+ .collect(toSet());
+
+ return packages.stream()
+ .filter(p -> !exports.contains(p))
+ .collect(toSet());
+ }
+
private static boolean isPlatformModule(String name) {
return name.startsWith("java.") || name.startsWith("jdk.");
}
--- a/jdk/src/jdk.jartool/share/classes/sun/tools/jar/Validator.java Tue Oct 25 12:58:34 2016 -0700
+++ b/jdk/src/jdk.jartool/share/classes/sun/tools/jar/Validator.java Tue Oct 25 13:51:08 2016 -0700
@@ -152,9 +152,13 @@
return;
}
if (fp.isPublicClass()) {
- main.error(Main.formatMsg("error.validator.new.public.class", entryName));
- isValid = false;
- return;
+ if (!isConcealed(internalName)) {
+ main.error(Main.formatMsg("error.validator.new.public.class", entryName));
+ isValid = false;
+ return;
+ }
+ main.warn(Main.formatMsg("warn.validator.concealed.public.class", entryName));
+ debug("%s is a public class entry in a concealed package", entryName);
}
debug("%s is a non-public class entry", entryName);
fps.put(internalName, fp);
@@ -169,7 +173,7 @@
// are the two classes/resources identical?
if (fp.isIdentical(matchFp)) {
- main.error(Main.formatMsg("error.validator.identical.entry", entryName));
+ main.warn(Main.formatMsg("warn.validator.identical.entry", entryName));
return; // it's okay, just takes up room
}
debug("sha1 not equal -- different bytes");
@@ -204,7 +208,7 @@
}
debug("%s is a resource", entryName);
- main.error(Main.formatMsg("error.validator.resources.with.same.name", entryName));
+ main.warn(Main.formatMsg("warn.validator.resources.with.same.name", entryName));
fps.put(internalName, fp);
return;
}
@@ -235,6 +239,15 @@
return entryName.endsWith(".class") ? entryName.substring(0, entryName.length() - 6) : null;
}
+ private boolean isConcealed(String internalName) {
+ if (main.concealedPackages.isEmpty()) {
+ return false;
+ }
+ int idx = internalName.lastIndexOf('/');
+ String pkgName = idx != -1 ? internalName.substring(0, idx).replace('/', '.') : "";
+ return main.concealedPackages.contains(pkgName);
+ }
+
private void debug(String fmt, Object... args) {
if (DEBUG) System.err.format(fmt, args);
}
--- a/jdk/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar.properties Tue Oct 25 12:58:34 2016 -0700
+++ b/jdk/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar.properties Tue Oct 25 13:51:08 2016 -0700
@@ -99,16 +99,19 @@
entry: {0}, is an isolated nested class
error.validator.new.public.class=\
entry: {0}, contains a new public class not found in base entries
-error.validator.identical.entry=\
- warning - entry: {0} contains a class that is identical to an entry already in the jar
error.validator.incompatible.class.version=\
entry: {0}, has a class version incompatible with an earlier version
error.validator.different.api=\
entry: {0}, contains a class with different api from earlier version
-error.validator.resources.with.same.name=\
- warning - entry: {0}, multiple resources with same name
error.validator.names.mismatch=\
entry: {0}, contains a class with internal name {1}, names do not match
+warn.validator.identical.entry=\
+ warning - entry: {0} contains a class that is identical to an entry already in the jar
+warn.validator.resources.with.same.name=\
+ warning - entry: {0}, multiple resources with same name
+warn.validator.concealed.public.class=\
+ warning - entry {0} is a public class in a concealed package, \n\
+ placing this jar on the class path will result in incompatible public interfaces
out.added.manifest=\
added manifest
out.added.module-info=\
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/jar/mmrjar/ConcealedPackage.java Tue Oct 25 13:51:08 2016 -0700
@@ -0,0 +1,225 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 8146486
+ * @summary Fail to create a MR modular JAR with a versioned entry in
+ * base-versioned empty package
+ * @library /lib/testlibrary
+ * @build jdk.testlibrary.FileUtils
+ * @run testng ConcealedPackage
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Set;
+import java.util.spi.ToolProvider;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import jdk.testlibrary.FileUtils;
+
+public class ConcealedPackage {
+ private static final ToolProvider JAR_TOOL = ToolProvider.findFirst("jar")
+ .orElseThrow(() -> new RuntimeException("jar tool not found"));
+ private static final ToolProvider JAVAC_TOOL = ToolProvider.findFirst("javac")
+ .orElseThrow(() -> new RuntimeException("javac tool not found"));
+ private final String linesep = System.lineSeparator();
+ private final Path userdir;
+ private final ByteArrayOutputStream outbytes = new ByteArrayOutputStream();
+ private final PrintStream out = new PrintStream(outbytes, true);
+ private final ByteArrayOutputStream errbytes = new ByteArrayOutputStream();
+ private final PrintStream err = new PrintStream(errbytes, true);
+
+ public ConcealedPackage() throws IOException {
+ Path testsrc = Paths.get(System.getProperty("test.src"));
+ userdir = Paths.get(System.getProperty("user.dir", "."));
+
+ // compile the classes directory
+ Path source = testsrc.resolve("src").resolve("classes");
+ Path destination = Paths.get("classes");
+ javac(source, destination);
+
+ // compile the mr9 directory including module-info.java
+ source = testsrc.resolve("src").resolve("mr9");
+ destination = Paths.get("mr9");
+ javac(source, destination);
+
+ // move module-info.class for later use
+ Files.move(destination.resolve("module-info.class"),
+ Paths.get("module-info.class"));
+ }
+
+ private void javac(Path source, Path destination) throws IOException {
+ String[] args = Stream.concat(
+ Stream.of("-d", destination.toString()),
+ Files.walk(source)
+ .map(Path::toString)
+ .filter(s -> s.endsWith(".java"))
+ ).toArray(String[]::new);
+ JAVAC_TOOL.run(System.out, System.err, args);
+ }
+
+ private int jar(String cmd) {
+ outbytes.reset();
+ errbytes.reset();
+ return JAR_TOOL.run(out, err, cmd.split(" +"));
+ }
+
+ @AfterClass
+ public void cleanup() throws IOException {
+ Files.walk(userdir, 1)
+ .filter(p -> !p.equals(userdir))
+ .forEach(p -> {
+ try {
+ if (Files.isDirectory(p)) {
+ FileUtils.deleteFileTreeWithRetry(p);
+ } else {
+ FileUtils.deleteFileIfExistsWithRetry(p);
+ }
+ } catch (IOException x) {
+ throw new UncheckedIOException(x);
+ }
+ });
+ }
+
+ // updates a valid multi-release jar with a new public class in
+ // versioned section and fails
+ @Test
+ public void test1() {
+ // successful build of multi-release jar
+ int rc = jar("-cf mmr.jar -C classes . --release 9 -C mr9 p/Hi.class");
+ Assert.assertEquals(rc, 0);
+
+ jar("-tf mmr.jar");
+
+ String s = new String(outbytes.toByteArray());
+ Set<String> actual = Arrays.stream(s.split(linesep)).collect(Collectors.toSet());
+ Set<String> expected = Set.of(
+ "META-INF/",
+ "META-INF/MANIFEST.MF",
+ "p/",
+ "p/Hi.class",
+ "META-INF/versions/9/p/Hi.class"
+ );
+ Assert.assertEquals(actual, expected);
+
+ // failed build because of new public class
+ rc = jar("-uf mmr.jar --release 9 -C mr9 p/internal/Bar.class");
+ Assert.assertEquals(rc, 1);
+
+ s = new String(errbytes.toByteArray());
+ Assert.assertTrue(s.contains("p/internal/Bar.class, contains a new public "
+ + "class not found in base entries")
+ );
+ }
+
+ // updates a valid multi-release jar with a module-info class and new
+ // concealed public class in versioned section and succeeds
+ @Test
+ public void test2() {
+ // successful build of multi-release jar
+ int rc = jar("-cf mmr.jar -C classes . --release 9 -C mr9 p/Hi.class");
+ Assert.assertEquals(rc, 0);
+
+ // successful build because of module-info and new public class
+ rc = jar("-uf mmr.jar module-info.class --release 9 -C mr9 p/internal/Bar.class");
+ Assert.assertEquals(rc, 0);
+
+ String s = new String(errbytes.toByteArray());
+ Assert.assertTrue(s.contains("p/internal/Bar.class is a public class in a "
+ + "concealed package, \nplacing this jar on the class path "
+ + "will result in incompatible public interfaces")
+ );
+
+ jar("-tf mmr.jar");
+
+ s = new String(outbytes.toByteArray());
+ Set<String> actual = Arrays.stream(s.split(linesep)).collect(Collectors.toSet());
+ Set<String> expected = Set.of(
+ "META-INF/",
+ "META-INF/MANIFEST.MF",
+ "p/",
+ "p/Hi.class",
+ "META-INF/versions/9/p/Hi.class",
+ "META-INF/versions/9/p/internal/Bar.class",
+ "module-info.class"
+ );
+ Assert.assertEquals(actual, expected);
+ }
+
+ // jar tool fails building mmr.jar because of new public class
+ @Test
+ public void test3() {
+ int rc = jar("-cf mmr.jar -C classes . --release 9 -C mr9 .");
+ Assert.assertEquals(rc, 1);
+
+ String s = new String(errbytes.toByteArray());
+ Assert.assertTrue(s.contains("p/internal/Bar.class, contains a new public "
+ + "class not found in base entries")
+ );
+ }
+
+ // jar tool succeeds building mmr.jar because of concealed package
+ @Test
+ public void test4() {
+ int rc = jar("-cf mmr.jar module-info.class -C classes . " +
+ "--release 9 module-info.class -C mr9 .");
+ Assert.assertEquals(rc, 0);
+
+ String s = new String(errbytes.toByteArray());
+ Assert.assertTrue(s.contains("p/internal/Bar.class is a public class in a "
+ + "concealed package, \nplacing this jar on the class path "
+ + "will result in incompatible public interfaces")
+ );
+
+ jar("-tf mmr.jar");
+
+ s = new String(outbytes.toByteArray());
+ Set<String> actual = Arrays.stream(s.split(linesep)).collect(Collectors.toSet());
+ Set<String> expected = Set.of(
+ "META-INF/",
+ "META-INF/MANIFEST.MF",
+ "module-info.class",
+ "META-INF/versions/9/module-info.class",
+ "p/",
+ "p/Hi.class",
+ "META-INF/versions/9/p/",
+ "META-INF/versions/9/p/Hi.class",
+ "META-INF/versions/9/p/internal/",
+ "META-INF/versions/9/p/internal/Bar.class"
+ );
+ Assert.assertEquals(actual, expected);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/jar/mmrjar/src/classes/p/Hi.java Tue Oct 25 13:51:08 2016 -0700
@@ -0,0 +1,30 @@
+/*
+ * 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 p;
+
+public class Hi {
+ public void sayHi() {
+ System.out.println("Hi");
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/jar/mmrjar/src/mr9/module-info.java Tue Oct 25 13:51:08 2016 -0700
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+module m1 {
+ exports p;
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/jar/mmrjar/src/mr9/p/Hi.java Tue Oct 25 13:51:08 2016 -0700
@@ -0,0 +1,30 @@
+/*
+ * 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 p;
+
+public class Hi {
+ public void sayHi() {
+ System.out.println("Hello");
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/jar/mmrjar/src/mr9/p/internal/Bar.java Tue Oct 25 13:51:08 2016 -0700
@@ -0,0 +1,31 @@
+/*
+ * 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 p.internal;
+
+public class Bar {
+ @Override
+ public String toString() {
+ return "p.internal.Bar";
+ }
+}