diff --git a/jdk/src/jdk.jartool/share/classes/sun/tools/jar/Main.java b/jdk/src/jdk.jartool/share/classes/sun/tools/jar/Main.java index 6aa65550140..28e0752fe0a 100644 --- a/jdk/src/jdk.jartool/share/classes/sun/tools/jar/Main.java +++ b/jdk/src/jdk.jartool/share/classes/sun/tools/jar/Main.java @@ -80,6 +80,7 @@ class Main { String fname, mname, ename; String zname = ""; String rootjar = null; + Set concealedPackages = new HashSet<>(); private static final int BASE_VERSION = 0; @@ -821,22 +822,21 @@ class Main { return true; } - private static Set 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 @@ class Main { 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 @@ class Main { } jarEntries.add(name); - if (name.endsWith(".class") && !(name.startsWith(VERSIONS_DIR))) + if (name.endsWith(".class")) packages.add(toPackageName(name)); } } @@ -1761,6 +1761,13 @@ class Main { err.println(s); } + /** + * Print a warning message + */ + void warn(String s) { + err.println(s); + } + /** * Main routine to start program. */ @@ -1975,24 +1982,30 @@ class Main { ByteBuffer bb = ByteBuffer.wrap(moduleInfos.get(MODULE_INFO)); ModuleDescriptor rd = ModuleDescriptor.read(bb); - Set exports = rd.exports() - .stream() - .map(Exports::source) - .collect(toSet()); - - Set conceals = packages.stream() - .filter(p -> !exports.contains(p)) - .collect(toSet()); + concealedPackages = findConcealedPackages(rd); for (Map.Entry 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 findConcealedPackages(ModuleDescriptor md){ + Objects.requireNonNull(md); + + Set 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."); } diff --git a/jdk/src/jdk.jartool/share/classes/sun/tools/jar/Validator.java b/jdk/src/jdk.jartool/share/classes/sun/tools/jar/Validator.java index bf4fa8bf702..c3307e05613 100644 --- a/jdk/src/jdk.jartool/share/classes/sun/tools/jar/Validator.java +++ b/jdk/src/jdk.jartool/share/classes/sun/tools/jar/Validator.java @@ -152,9 +152,13 @@ final class Validator implements Consumer { 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 @@ final class Validator implements Consumer { // 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 @@ final class Validator implements Consumer { } 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 @@ final class Validator implements Consumer { 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); } diff --git a/jdk/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar.properties b/jdk/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar.properties index 78c81048b96..d43231e21d2 100644 --- a/jdk/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar.properties +++ b/jdk/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar.properties @@ -99,16 +99,19 @@ error.validator.isolated.nested.class=\ 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=\ diff --git a/jdk/test/tools/jar/mmrjar/ConcealedPackage.java b/jdk/test/tools/jar/mmrjar/ConcealedPackage.java new file mode 100644 index 00000000000..cbe449ad4ed --- /dev/null +++ b/jdk/test/tools/jar/mmrjar/ConcealedPackage.java @@ -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 actual = Arrays.stream(s.split(linesep)).collect(Collectors.toSet()); + Set 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 actual = Arrays.stream(s.split(linesep)).collect(Collectors.toSet()); + Set 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 actual = Arrays.stream(s.split(linesep)).collect(Collectors.toSet()); + Set 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); + } +} diff --git a/jdk/test/tools/jar/mmrjar/src/classes/p/Hi.java b/jdk/test/tools/jar/mmrjar/src/classes/p/Hi.java new file mode 100644 index 00000000000..954961c95eb --- /dev/null +++ b/jdk/test/tools/jar/mmrjar/src/classes/p/Hi.java @@ -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"); + } +} diff --git a/jdk/test/tools/jar/mmrjar/src/mr9/module-info.java b/jdk/test/tools/jar/mmrjar/src/mr9/module-info.java new file mode 100644 index 00000000000..386ac9932df --- /dev/null +++ b/jdk/test/tools/jar/mmrjar/src/mr9/module-info.java @@ -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; +} + diff --git a/jdk/test/tools/jar/mmrjar/src/mr9/p/Hi.java b/jdk/test/tools/jar/mmrjar/src/mr9/p/Hi.java new file mode 100644 index 00000000000..2c4eca1b92d --- /dev/null +++ b/jdk/test/tools/jar/mmrjar/src/mr9/p/Hi.java @@ -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"); + } +} diff --git a/jdk/test/tools/jar/mmrjar/src/mr9/p/internal/Bar.java b/jdk/test/tools/jar/mmrjar/src/mr9/p/internal/Bar.java new file mode 100644 index 00000000000..f962af3abab --- /dev/null +++ b/jdk/test/tools/jar/mmrjar/src/mr9/p/internal/Bar.java @@ -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"; + } +}