8164805: Fail to create a MR modular JAR with a versioned entry of a concealed package

Reviewed-by: chegar, mchung
This commit is contained in:
Steve Drach 2016-10-25 13:51:08 -07:00
parent 1552f3ddbb
commit 5530ec2227
8 changed files with 402 additions and 30 deletions

View File

@ -80,6 +80,7 @@ class Main {
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 @@ class Main {
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 @@ 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<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.");
}

View File

@ -152,9 +152,13 @@ final class Validator implements Consumer<JarEntry> {
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<JarEntry> {
// 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<JarEntry> {
}
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<JarEntry> {
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);
}

View File

@ -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=\

View File

@ -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);
}
}

View File

@ -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");
}
}

View File

@ -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;
}

View File

@ -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");
}
}

View File

@ -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";
}
}