8186087: jar tool fails to create a multi-release jar when validating nested classes
Reviewed-by: psandoz
--- a/src/jdk.jartool/share/classes/sun/tools/jar/FingerPrint.java Wed Nov 29 14:41:09 2017 +0100
+++ b/src/jdk.jartool/share/classes/sun/tools/jar/FingerPrint.java Wed Nov 29 09:25:25 2017 -0800
@@ -43,7 +43,7 @@
* a class, it also contains information to (1) describe the public API;
* (2) compare the public API of this class with another class; (3) determine
* whether or not it's a nested class and, if so, the name of the associated
- * top level class; and (4) for an canonically ordered set of classes determine
+ * outer class; and (4) for an canonically ordered set of classes determine
* if the class versions are compatible. A set of classes is canonically
* ordered if the classes in the set have the same name, and the base class
* precedes the versioned classes and if each versioned class with version
@@ -53,10 +53,13 @@
final class FingerPrint {
private static final MessageDigest MD;
+ private final String basename;
+ private final String entryName;
+ private final int mrversion;
+
private final byte[] sha1;
private final ClassAttributes attrs;
private final boolean isClassEntry;
- private final String entryName;
static {
try {
@@ -67,16 +70,19 @@
}
}
- public FingerPrint(String entryName,byte[] bytes) throws IOException {
+ public FingerPrint(String basename, String entryName, int mrversion, byte[] bytes)
+ throws IOException {
+ this.basename = basename;
this.entryName = entryName;
- if (entryName.endsWith(".class") && isCafeBabe(bytes)) {
+ this.mrversion = mrversion;
+ if (isCafeBabe(bytes)) {
isClassEntry = true;
sha1 = sha1(bytes, 8); // skip magic number and major/minor version
attrs = getClassAttributes(bytes);
} else {
isClassEntry = false;
- sha1 = sha1(bytes);
- attrs = new ClassAttributes(); // empty class
+ sha1 = null;
+ attrs = null;
}
}
@@ -107,14 +113,24 @@
return attrs.equals(that.attrs);
}
- public String name() {
- String name = attrs.name;
- return name == null ? entryName : name;
+ public String basename() {
+ return basename;
+ }
+
+ public String entryName() {
+ return entryName;
}
- public String topLevelName() {
- String name = attrs.topLevelName;
- return name == null ? name() : name;
+ public String className() {
+ return attrs.name;
+ }
+
+ public int mrversion() {
+ return mrversion;
+ }
+
+ public String outerClassName() {
+ return attrs.outerClassName;
}
private byte[] sha1(byte[] entry) {
@@ -218,7 +234,7 @@
private static final class ClassAttributes extends ClassVisitor {
private String name;
- private String topLevelName;
+ private String outerClassName;
private String superName;
private int version;
private int access;
@@ -228,7 +244,7 @@
private final Set<Method> methods = new HashSet<>();
public ClassAttributes() {
- super(Opcodes.ASM5);
+ super(Opcodes.ASM6);
}
private boolean isPublic(int access) {
@@ -250,7 +266,7 @@
@Override
public void visitOuterClass(String owner, String name, String desc) {
if (!this.nestedClass) return;
- this.topLevelName = owner;
+ this.outerClassName = owner;
}
@Override
@@ -259,7 +275,7 @@
if (!this.nestedClass) return;
if (outerName == null) return;
if (!this.name.equals(name)) return;
- if (this.topLevelName == null) this.topLevelName = outerName;
+ if (this.outerClassName == null) this.outerClassName = outerName;
}
@Override
@@ -294,7 +310,7 @@
@Override
public void visitEnd() {
- this.nestedClass = this.topLevelName != null;
+ this.nestedClass = this.outerClassName != null;
}
@Override
--- a/src/jdk.jartool/share/classes/sun/tools/jar/Main.java Wed Nov 29 14:41:09 2017 +0100
+++ b/src/jdk.jartool/share/classes/sun/tools/jar/Main.java Wed Nov 29 09:25:25 2017 -0800
@@ -71,7 +71,6 @@
import static java.util.jar.JarFile.MANIFEST_NAME;
import static java.util.stream.Collectors.joining;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
-import static sun.tools.jar.Validator.ENTRYNAME_COMPARATOR;
/**
* This class implements a simple utility for creating files in the JAR
@@ -444,8 +443,8 @@
private void validateAndClose(File tmpfile) throws IOException {
if (ok && isMultiRelease) {
- try (JarFile jf = new JarFile(tmpfile)) {
- ok = Validator.validate(this, jf);
+ try (ZipFile zf = new ZipFile(tmpfile)) {
+ ok = Validator.validate(this, zf);
if (!ok) {
error(formatMsg("error.validator.jarfile.invalid", fname));
}
@@ -1784,7 +1783,7 @@
private boolean describeModule(ZipFile zipFile) throws IOException {
ZipFileModuleInfoEntry[] infos = zipFile.stream()
.filter(e -> isModuleInfoEntry(e.getName()))
- .sorted(Validator.ENTRY_COMPARATOR)
+ .sorted(ENTRY_COMPARATOR)
.map(e -> new ZipFileModuleInfoEntry(zipFile, e))
.toArray(ZipFileModuleInfoEntry[]::new);
@@ -2216,4 +2215,48 @@
return hashesBuilder.computeHashes(Set.of(name)).get(name);
}
}
+
+ // sort base entries before versioned entries, and sort entry classes with
+ // nested classes so that the outter class appears before the associated
+ // nested class
+ static Comparator<String> ENTRYNAME_COMPARATOR = (s1, s2) -> {
+
+ if (s1.equals(s2)) return 0;
+ boolean b1 = s1.startsWith(VERSIONS_DIR);
+ boolean b2 = s2.startsWith(VERSIONS_DIR);
+ if (b1 && !b2) return 1;
+ if (!b1 && b2) return -1;
+ int n = 0; // starting char for String compare
+ if (b1 && b2) {
+ // normally strings would be sorted so "10" goes before "9", but
+ // version number strings need to be sorted numerically
+ n = VERSIONS_DIR.length(); // skip the common prefix
+ int i1 = s1.indexOf('/', n);
+ int i2 = s2.indexOf('/', n);
+ if (i1 == -1) throw new Validator.InvalidJarException(s1);
+ if (i2 == -1) throw new Validator.InvalidJarException(s2);
+ // shorter version numbers go first
+ if (i1 != i2) return i1 - i2;
+ // otherwise, handle equal length numbers below
+ }
+ int l1 = s1.length();
+ int l2 = s2.length();
+ int lim = Math.min(l1, l2);
+ for (int k = n; k < lim; k++) {
+ char c1 = s1.charAt(k);
+ char c2 = s2.charAt(k);
+ if (c1 != c2) {
+ // change natural ordering so '.' comes before '$'
+ // i.e. outer classes come before nested classes
+ if (c1 == '$' && c2 == '.') return 1;
+ if (c1 == '.' && c2 == '$') return -1;
+ return c1 - c2;
+ }
+ }
+ return l1 - l2;
+ };
+
+ static Comparator<ZipEntry> ENTRY_COMPARATOR =
+ Comparator.comparing(ZipEntry::getName, ENTRYNAME_COMPARATOR);
+
}
--- a/src/jdk.jartool/share/classes/sun/tools/jar/Validator.java Wed Nov 29 14:41:09 2017 +0100
+++ b/src/jdk.jartool/share/classes/sun/tools/jar/Validator.java Wed Nov 29 09:25:25 2017 -0800
@@ -28,24 +28,22 @@
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
-import java.lang.module.InvalidModuleDescriptorException;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleDescriptor.Exports;
-import java.lang.module.InvalidModuleDescriptorException;
import java.lang.module.ModuleDescriptor.Opens;
import java.lang.module.ModuleDescriptor.Provides;
import java.lang.module.ModuleDescriptor.Requires;
import java.util.Collections;
-import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.function.Consumer;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
+import java.util.TreeMap;
+import java.util.function.Function;
+import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
import static java.util.jar.JarFile.MANIFEST_NAME;
import static sun.tools.jar.Main.VERSIONS_DIR;
@@ -55,269 +53,184 @@
import static sun.tools.jar.Main.formatMsg;
import static sun.tools.jar.Main.formatMsg2;
import static sun.tools.jar.Main.toBinaryName;
-import static sun.tools.jar.Main.isModuleInfoEntry;
final class Validator {
- private final static boolean DEBUG = Boolean.getBoolean("jar.debug");
- private final Map<String,FingerPrint> fps = new HashMap<>();
+
+ private final Map<String,FingerPrint> classes = new HashMap<>();
private final Main main;
- private final JarFile jf;
- private int oldVersion = -1;
- private String currentTopLevelName;
+ private final ZipFile zf;
private boolean isValid = true;
private Set<String> concealedPkgs = Collections.emptySet();
private ModuleDescriptor md;
private String mdName;
- private Validator(Main main, JarFile jf) {
+ private Validator(Main main, ZipFile zf) {
this.main = main;
- this.jf = jf;
+ this.zf = zf;
checkModuleDescriptor(MODULE_INFO);
}
- static boolean validate(Main main, JarFile jf) throws IOException {
- return new Validator(main, jf).validate();
+ static boolean validate(Main main, ZipFile zf) throws IOException {
+ return new Validator(main, zf).validate();
}
private boolean validate() {
try {
- jf.stream()
- .filter(e -> !e.isDirectory() &&
- !e.getName().equals(MANIFEST_NAME))
- .sorted(ENTRY_COMPARATOR)
- .forEachOrdered(e -> validate(e));
- return isValid;
+ zf.stream()
+ .filter(e -> e.getName().endsWith(".class"))
+ .map(this::getFingerPrint)
+ .filter(FingerPrint::isClass) // skip any non-class entry
+ .collect(Collectors.groupingBy(
+ FingerPrint::mrversion,
+ TreeMap::new,
+ Collectors.toMap(FingerPrint::className,
+ Function.identity(),
+ this::sameNameFingerPrint)))
+ .forEach((version, entries) -> {
+ if (version == 0)
+ validateBase(entries);
+ else
+ validateVersioned(entries);
+ });
} catch (InvalidJarException e) {
- error(formatMsg("error.validator.bad.entry.name", e.getMessage()));
+ errorAndInvalid(e.getMessage());
}
- return false;
+ return isValid;
}
- private static class InvalidJarException extends RuntimeException {
+ static class InvalidJarException extends RuntimeException {
private static final long serialVersionUID = -3642329147299217726L;
InvalidJarException(String msg) {
super(msg);
}
}
- // sort base entries before versioned entries, and sort entry classes with
- // nested classes so that the top level class appears before the associated
- // nested class
- static Comparator<String> ENTRYNAME_COMPARATOR = (s1, s2) -> {
+ private FingerPrint sameNameFingerPrint(FingerPrint fp1, FingerPrint fp2) {
+ checkClassName(fp1);
+ checkClassName(fp2);
+ // entries/classes with same name, return fp2 for now ?
+ return fp2;
+ }
+
+ private FingerPrint getFingerPrint(ZipEntry ze) {
+ // figure out the version and basename from the ZipEntry
+ String ename = ze.getName();
+ String bname = ename;
+ int version = 0;
- if (s1.equals(s2)) return 0;
- boolean b1 = s1.startsWith(VERSIONS_DIR);
- boolean b2 = s2.startsWith(VERSIONS_DIR);
- if (b1 && !b2) return 1;
- if (!b1 && b2) return -1;
- int n = 0; // starting char for String compare
- if (b1 && b2) {
- // normally strings would be sorted so "10" goes before "9", but
- // version number strings need to be sorted numerically
- n = VERSIONS_DIR.length(); // skip the common prefix
- int i1 = s1.indexOf('/', n);
- int i2 = s2.indexOf('/', n);
- if (i1 == -1) throw new InvalidJarException(s1);
- if (i2 == -1) throw new InvalidJarException(s2);
- // shorter version numbers go first
- if (i1 != i2) return i1 - i2;
- // otherwise, handle equal length numbers below
+ if (ename.startsWith(VERSIONS_DIR)) {
+ int n = ename.indexOf("/", VERSIONS_DIR_LENGTH);
+ if (n == -1) {
+ throw new InvalidJarException(
+ formatMsg("error.validator.version.notnumber", ename));
+ }
+ try {
+ version = Integer.parseInt(ename, VERSIONS_DIR_LENGTH, n, 10);
+ } catch (NumberFormatException x) {
+ throw new InvalidJarException(
+ formatMsg("error.validator.version.notnumber", ename));
+ }
+ if (n == ename.length()) {
+ throw new InvalidJarException(
+ formatMsg("error.validator.entryname.tooshort", ename));
+ }
+ bname = ename.substring(n + 1);
}
- int l1 = s1.length();
- int l2 = s2.length();
- int lim = Math.min(l1, l2);
- for (int k = n; k < lim; k++) {
- char c1 = s1.charAt(k);
- char c2 = s2.charAt(k);
- if (c1 != c2) {
- // change natural ordering so '.' comes before '$'
- // i.e. top level classes come before nested classes
- if (c1 == '$' && c2 == '.') return 1;
- if (c1 == '.' && c2 == '$') return -1;
- return c1 - c2;
- }
+
+ // return the cooresponding fingerprint entry
+ try (InputStream is = zf.getInputStream(ze)) {
+ return new FingerPrint(bname, ename, version, is.readAllBytes());
+ } catch (IOException x) {
+ throw new InvalidJarException(x.getMessage());
}
- return l1 - l2;
- };
-
- static Comparator<ZipEntry> ENTRY_COMPARATOR =
- Comparator.comparing(ZipEntry::getName, ENTRYNAME_COMPARATOR);
+ }
/*
- * Validator has state and assumes entries provided to accept are ordered
- * from base entries first and then through the versioned entries in
- * ascending version order. Also, to find isolated nested classes,
- * classes must be ordered so that the top level class is before the associated
- * nested class(es).
- */
- public void validate(JarEntry je) {
- String entryName = je.getName();
-
- // directories are always accepted
- if (entryName.endsWith("/")) {
- debug("%s is a directory", entryName);
- return;
- }
-
- // validate the versioned module-info
- if (isModuleInfoEntry(entryName)) {
- if (!entryName.equals(mdName))
- checkModuleDescriptor(entryName);
- return;
- }
-
- // figure out the version and basename from the JarEntry
- int version;
- String basename;
- String versionStr = null;;
- if (entryName.startsWith(VERSIONS_DIR)) {
- int n = entryName.indexOf("/", VERSIONS_DIR_LENGTH);
- if (n == -1) {
- error(formatMsg("error.validator.version.notnumber", entryName));
+ * Validates (a) if there is any isolated nested class, and (b) if the
+ * class name in class file (by asm) matches the entry's basename.
+ */
+ public void validateBase(Map<String, FingerPrint> fps) {
+ fps.values().forEach( fp -> {
+ if (!checkClassName(fp)) {
isValid = false;
return;
}
- versionStr = entryName.substring(VERSIONS_DIR_LENGTH, n);
- try {
- version = Integer.parseInt(versionStr);
- } catch (NumberFormatException x) {
- error(formatMsg("error.validator.version.notnumber", entryName));
- isValid = false;
- return;
+ if (fp.isNestedClass()) {
+ if (!checkNestedClass(fp, fps)) {
+ isValid = false;
+ }
}
- if (n == entryName.length()) {
- error(formatMsg("error.validator.entryname.tooshort", entryName));
- isValid = false;
+ classes.put(fp.className(), fp);
+ });
+ }
+
+ public void validateVersioned(Map<String, FingerPrint> fps) {
+
+ fps.values().forEach( fp -> {
+
+ // validate the versioned module-info
+ if (MODULE_INFO.equals(fp.basename())) {
+ checkModuleDescriptor(fp.entryName());
return;
}
- basename = entryName.substring(n + 1);
- } else {
- version = 0;
- basename = entryName;
- }
- debug("\n===================\nversion %d %s", version, entryName);
-
- if (oldVersion != version) {
- oldVersion = version;
- currentTopLevelName = null;
- if (md == null && versionStr != null) {
- // don't have a base module-info.class yet, try to see if
- // a versioned one exists
- checkModuleDescriptor(VERSIONS_DIR + versionStr + "/" + MODULE_INFO);
- }
- }
-
- // analyze the entry, keeping key attributes
- FingerPrint fp;
- try (InputStream is = jf.getInputStream(je)) {
- fp = new FingerPrint(basename, is.readAllBytes());
- } catch (IOException x) {
- error(x.getMessage());
- isValid = false;
- return;
- }
- String internalName = fp.name();
-
- // process a base entry paying attention to nested classes
- if (version == 0) {
- debug("base entry found");
- if (fp.isNestedClass()) {
- debug("nested class found");
- if (fp.topLevelName().equals(currentTopLevelName)) {
- fps.put(internalName, fp);
- return;
- }
- error(formatMsg("error.validator.isolated.nested.class", entryName));
- isValid = false;
- return;
- }
- // top level class or resource entry
- if (fp.isClass()) {
- currentTopLevelName = fp.topLevelName();
- if (!checkInternalName(entryName, basename, internalName)) {
- isValid = false;
- return;
- }
- }
- fps.put(internalName, fp);
- return;
- }
-
- // process a versioned entry, look for previous entry with same name
- FingerPrint matchFp = fps.get(internalName);
- debug("looking for match");
- if (matchFp == null) {
- debug("no match found");
- if (fp.isClass()) {
+ // process a versioned entry, look for previous entry with same name
+ FingerPrint matchFp = classes.get(fp.className());
+ if (matchFp == null) {
+ // no match found
if (fp.isNestedClass()) {
- if (!checkNestedClass(version, entryName, internalName, fp)) {
+ if (!checkNestedClass(fp, fps)) {
isValid = false;
}
return;
}
if (fp.isPublicClass()) {
- if (!isConcealed(internalName)) {
- error(Main.formatMsg("error.validator.new.public.class", entryName));
- isValid = false;
+ if (!isConcealed(fp.className())) {
+ errorAndInvalid(formatMsg("error.validator.new.public.class",
+ fp.entryName()));
return;
- }
- warn(formatMsg("warn.validator.concealed.public.class", entryName));
- debug("%s is a public class entry in a concealed package", entryName);
+ }
+ // entry is a public class entry in a concealed package
+ warn(formatMsg("warn.validator.concealed.public.class",
+ fp.entryName()));
}
- debug("%s is a non-public class entry", entryName);
- fps.put(internalName, fp);
- currentTopLevelName = fp.topLevelName();
+ classes.put(fp.className(), fp);
return;
}
- debug("%s is a resource entry");
- fps.put(internalName, fp);
- return;
- }
- debug("match found");
- // are the two classes/resources identical?
- if (fp.isIdentical(matchFp)) {
- warn(formatMsg("warn.validator.identical.entry", entryName));
- return; // it's okay, just takes up room
- }
- debug("sha1 not equal -- different bytes");
+ // are the two classes/resources identical?
+ if (fp.isIdentical(matchFp)) {
+ warn(formatMsg("warn.validator.identical.entry", fp.entryName()));
+ return; // it's okay, just takes up room
+ }
- // ok, not identical, check for compatible class version and api
- if (fp.isClass()) {
+ // ok, not identical, check for compatible class version and api
if (fp.isNestedClass()) {
- if (!checkNestedClass(version, entryName, internalName, fp)) {
+ if (!checkNestedClass(fp, fps)) {
isValid = false;
}
+ return; // fall through, need check nested public class??
+ }
+ if (!fp.isCompatibleVersion(matchFp)) {
+ errorAndInvalid(formatMsg("error.validator.incompatible.class.version",
+ fp.entryName()));
return;
}
- debug("%s is a class entry", entryName);
- if (!fp.isCompatibleVersion(matchFp)) {
- error(formatMsg("error.validator.incompatible.class.version", entryName));
+ if (!fp.isSameAPI(matchFp)) {
+ errorAndInvalid(formatMsg("error.validator.different.api",
+ fp.entryName()));
+ return;
+ }
+ if (!checkClassName(fp)) {
isValid = false;
return;
}
- if (!fp.isSameAPI(matchFp)) {
- error(formatMsg("error.validator.different.api", entryName));
- isValid = false;
- return;
- }
- if (!checkInternalName(entryName, basename, internalName)) {
- isValid = false;
- return;
- }
- debug("fingerprints same -- same api");
- fps.put(internalName, fp);
- currentTopLevelName = fp.topLevelName();
+ classes.put(fp.className(), fp);
+
return;
- }
- debug("%s is a resource", entryName);
-
- warn(formatMsg("warn.validator.resources.with.same.name", entryName));
- fps.put(internalName, fp);
- return;
+ });
}
- /**
+ /*
* Checks whether or not the given versioned module descriptor's attributes
* are valid when compared against the root/base module descriptor.
*
@@ -326,12 +239,12 @@
* - A versioned descriptor can have different non-public `requires`
* clauses of platform ( `java.*` and `jdk.*` ) modules, and
* - A versioned descriptor can have different `uses` clauses, even of
- * service types defined outside of the platform modules.
+ * service types defined outside of the platform modules.
*/
private void checkModuleDescriptor(String miName) {
- ZipEntry je = jf.getEntry(miName);
- if (je != null) {
- try (InputStream jis = jf.getInputStream(je)) {
+ ZipEntry ze = zf.getEntry(miName);
+ if (ze != null) {
+ try (InputStream jis = zf.getInputStream(ze)) {
ModuleDescriptor md = ModuleDescriptor.read(jis);
// Initialize the base md if it's not yet. A "base" md can be either the
// root module-info.class or the first versioned module-info.class
@@ -344,7 +257,7 @@
// must have the implementation class of the services it 'provides'.
if (md.provides().stream().map(Provides::providers)
.flatMap(List::stream)
- .filter(p -> jf.getEntry(toBinaryName(p)) == null)
+ .filter(p -> zf.getEntry(toBinaryName(p)) == null)
.peek(p -> error(formatMsg("error.missing.provider", p)))
.count() != 0) {
isValid = false;
@@ -356,8 +269,7 @@
}
if (!base.name().equals(md.name())) {
- error(getMsg("error.validator.info.name.notequal"));
- isValid = false;
+ errorAndInvalid(getMsg("error.validator.info.name.notequal"));
}
if (!base.requires().equals(md.requires())) {
Set<Requires> baseRequires = base.requires();
@@ -365,11 +277,9 @@
if (baseRequires.contains(r))
continue;
if (r.modifiers().contains(Requires.Modifier.TRANSITIVE)) {
- error(getMsg("error.validator.info.requires.transitive"));
- isValid = false;
+ errorAndInvalid(getMsg("error.validator.info.requires.transitive"));
} else if (!isPlatformModule(r.name())) {
- error(getMsg("error.validator.info.requires.added"));
- isValid = false;
+ errorAndInvalid(getMsg("error.validator.info.requires.added"));
}
}
for (Requires r : baseRequires) {
@@ -377,87 +287,78 @@
if (mdRequires.contains(r))
continue;
if (!isPlatformModule(r.name())) {
- error(getMsg("error.validator.info.requires.dropped"));
- isValid = false;
+ errorAndInvalid(getMsg("error.validator.info.requires.dropped"));
}
}
}
if (!base.exports().equals(md.exports())) {
- error(getMsg("error.validator.info.exports.notequal"));
- isValid = false;
+ errorAndInvalid(getMsg("error.validator.info.exports.notequal"));
}
if (!base.opens().equals(md.opens())) {
- error(getMsg("error.validator.info.opens.notequal"));
- isValid = false;
+ errorAndInvalid(getMsg("error.validator.info.opens.notequal"));
}
if (!base.provides().equals(md.provides())) {
- error(getMsg("error.validator.info.provides.notequal"));
- isValid = false;
+ errorAndInvalid(getMsg("error.validator.info.provides.notequal"));
}
if (!base.mainClass().equals(md.mainClass())) {
- error(formatMsg("error.validator.info.manclass.notequal", je.getName()));
- isValid = false;
+ errorAndInvalid(formatMsg("error.validator.info.manclass.notequal",
+ ze.getName()));
}
if (!base.version().equals(md.version())) {
- error(formatMsg("error.validator.info.version.notequal", je.getName()));
- isValid = false;
+ errorAndInvalid(formatMsg("error.validator.info.version.notequal",
+ ze.getName()));
}
} catch (Exception x) {
- error(x.getMessage() + " : " + miName);
- this.isValid = false;
+ errorAndInvalid(x.getMessage() + " : " + miName);
}
}
}
+ private boolean checkClassName(FingerPrint fp) {
+ if (fp.className().equals(className(fp.basename()))) {
+ return true;
+ }
+ error(formatMsg2("error.validator.names.mismatch",
+ fp.entryName(), fp.className().replace("/", ".")));
+ return false;
+ }
+
+ private boolean checkNestedClass(FingerPrint fp, Map<String, FingerPrint> outerClasses) {
+ if (outerClasses.containsKey(fp.outerClassName())) {
+ return true;
+ }
+ // outer class was not available
+ error(formatMsg("error.validator.isolated.nested.class", fp.entryName()));
+ return false;
+ }
+
+ private boolean isConcealed(String className) {
+ if (concealedPkgs.isEmpty()) {
+ return false;
+ }
+ int idx = className.lastIndexOf('/');
+ String pkgName = idx != -1 ? className.substring(0, idx).replace('/', '.') : "";
+ return concealedPkgs.contains(pkgName);
+ }
+
private static boolean isPlatformModule(String name) {
return name.startsWith("java.") || name.startsWith("jdk.");
}
- private boolean checkInternalName(String entryName, String basename, String internalName) {
- String className = className(basename);
- if (internalName.equals(className)) {
- return true;
- }
- error(formatMsg2("error.validator.names.mismatch",
- entryName, internalName.replace("/", ".")));
- return false;
- }
-
- private boolean checkNestedClass(int version, String entryName, String internalName, FingerPrint fp) {
- debug("%s is a nested class entry in top level class %s", entryName, fp.topLevelName());
- if (fp.topLevelName().equals(currentTopLevelName)) {
- debug("%s (top level class) was accepted", fp.topLevelName());
- fps.put(internalName, fp);
- return true;
- }
- debug("top level class was not accepted");
- error(formatMsg("error.validator.isolated.nested.class", entryName));
- return false;
- }
-
- private String className(String entryName) {
+ private static String className(String entryName) {
return entryName.endsWith(".class") ? entryName.substring(0, entryName.length() - 6) : null;
}
- private boolean isConcealed(String internalName) {
- if (concealedPkgs.isEmpty()) {
- return false;
- }
- int idx = internalName.lastIndexOf('/');
- String pkgName = idx != -1 ? internalName.substring(0, idx).replace('/', '.') : "";
- return concealedPkgs.contains(pkgName);
- }
-
- private void debug(String fmt, Object... args) {
- if (DEBUG) System.err.format(fmt, args);
- }
-
private void error(String msg) {
main.error(msg);
}
+ private void errorAndInvalid(String msg) {
+ main.error(msg);
+ isValid = false;
+ }
+
private void warn(String msg) {
main.warn(msg);
}
-
}
--- a/test/jdk/tools/jar/multiRelease/Basic.java Wed Nov 29 14:41:09 2017 +0100
+++ b/test/jdk/tools/jar/multiRelease/Basic.java Wed Nov 29 09:25:25 2017 -0800
@@ -23,6 +23,7 @@
/*
* @test
+ # @bug 8186087
* @library /test/lib
* @modules java.base/jdk.internal.misc
* jdk.compiler
@@ -346,39 +347,6 @@
}
@Test
- // resources with same name in different versions
- // this is okay but produces warning
- public void test08() throws Throwable {
- String jarfile = "test.jar";
-
- compile("test01"); //use same data as test01
-
- Path classes = Paths.get("classes");
-
- // add a resource to the base
- Path source = Paths.get(src, "data", "test01", "base", "version");
- Files.copy(source.resolve("Version.java"), classes.resolve("base")
- .resolve("version").resolve("Version.java"));
-
- jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",
- "--release", "9", "-C", classes.resolve("v9").toString(), ".")
- .shouldHaveExitValue(SUCCESS)
- .shouldBeEmpty();
-
- // now add a different resource with same name to META-INF/version/9
- Files.copy(source.resolve("Main.java"), classes.resolve("v9")
- .resolve("version").resolve("Version.java"));
-
- jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",
- "--release", "9", "-C", classes.resolve("v9").toString(), ".")
- .shouldHaveExitValue(SUCCESS)
- .shouldContain("multiple resources with same name");
-
- FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile));
- FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes"));
- }
-
- @Test
// a class with an internal name different from the external name
public void test09() throws Throwable {
String jarfile = "test.jar";
@@ -451,7 +419,9 @@
.shouldNotHaveExitValue(SUCCESS)
.asLines();
+ /* "META-INF/versions/9/version/Nested$nested.class" is really NOT isolated
assertTrue(output.size() == 4);
+ assertTrue(output.size() == 3);
assertTrue(output.get(0).contains("an isolated nested class"),
output.get(0));
assertTrue(output.get(1).contains("contains a new public class"),
@@ -460,6 +430,17 @@
output.get(2));
assertTrue(output.get(3).contains("invalid multi-release jar file"),
output.get(3));
+ assertTrue(output.get(2).contains("invalid multi-release jar file"),
+ output.get(2));
+ */
+
+ assertTrue(output.size() == 3);
+ assertTrue(output.get(0).contains("an isolated nested class"),
+ output.get(0));
+ assertTrue(output.get(1).contains("contains a new public class"),
+ output.get(1));
+ assertTrue(output.get(2).contains("invalid multi-release jar file"),
+ output.get(2));
FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile));
FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes"));
@@ -495,6 +476,31 @@
}
@Test
+ // assure the nested-nested classes are acceptable
+ public void test13() throws Throwable {
+ String jarfile = "test.jar";
+
+ compile("test01"); //use same data as test01
+
+ Path classes = Paths.get("classes");
+
+ // add a base class with a nested and nested-nested class
+ Path source = Paths.get(src, "data", "test13", "base", "version");
+ javac(classes.resolve("base"), source.resolve("Nested.java"));
+
+ // add a versioned class with a nested and nested-nested class
+ source = Paths.get(src, "data", "test13", "v10", "version");
+ javac(classes.resolve("v10"), source.resolve("Nested.java"));
+
+ jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",
+ "--release", "10", "-C", classes.resolve("v10").toString(), ".")
+ .shouldHaveExitValue(SUCCESS);
+
+ FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile));
+ FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes"));
+ }
+
+ @Test
public void testCustomManifest() throws Throwable {
String jarfile = "test.jar";
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jar/multiRelease/data/test13/base/version/Nested.java Wed Nov 29 09:25:25 2017 -0800
@@ -0,0 +1,22 @@
+package version;
+
+public class Nested {
+ public int getVersion() {
+ return 9;
+ }
+
+ protected void doNothing() {
+ }
+
+ private void anyName() {
+ }
+
+ class nested {
+ int save;
+
+ class nestnested {
+ int save;
+ }
+
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jar/multiRelease/data/test13/v10/version/Nested.java Wed Nov 29 09:25:25 2017 -0800
@@ -0,0 +1,18 @@
+package version;
+
+public class Nested {
+ public int getVersion() {
+ return 10;
+ }
+
+ protected void doNothing() {
+ }
+
+ class nested {
+ int save = getVersion();
+
+ class nestnested {
+ int save = getVersion();;
+ }
+ }
+}
--- a/test/jdk/tools/jar/multiRelease/whitebox/jdk.jartool/sun/tools/jar/ValidatorComparatorTest.java Wed Nov 29 14:41:09 2017 +0100
+++ b/test/jdk/tools/jar/multiRelease/whitebox/jdk.jartool/sun/tools/jar/ValidatorComparatorTest.java Wed Nov 29 09:25:25 2017 -0800
@@ -29,7 +29,7 @@
import java.util.List;
import static java.util.stream.Collectors.toList;
-import static sun.tools.jar.Validator.ENTRYNAME_COMPARATOR;
+import static sun.tools.jar.Main.ENTRYNAME_COMPARATOR;
import org.testng.Assert;
import org.testng.annotations.Test;