src/jdk.jartool/share/classes/sun/tools/jar/Validator.java
Print this page
*** 26,325 ****
package sun.tools.jar;
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.zip.ZipEntry;
import static java.util.jar.JarFile.MANIFEST_NAME;
import static sun.tools.jar.Main.VERSIONS_DIR;
import static sun.tools.jar.Main.VERSIONS_DIR_LENGTH;
import static sun.tools.jar.Main.MODULE_INFO;
import static sun.tools.jar.Main.getMsg;
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 Main main;
! private final JarFile jf;
! private int oldVersion = -1;
! private String currentTopLevelName;
private boolean isValid = true;
private Set<String> concealedPkgs = Collections.emptySet();
private ModuleDescriptor md;
private String mdName;
! private Validator(Main main, JarFile jf) {
this.main = main;
! this.jf = jf;
checkModuleDescriptor(MODULE_INFO);
}
! static boolean validate(Main main, JarFile jf) throws IOException {
! return new Validator(main, jf).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;
} catch (InvalidJarException e) {
! error(formatMsg("error.validator.bad.entry.name", e.getMessage()));
}
! return false;
}
! private 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) -> {
!
! 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
! }
! 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 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));
! 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 (n == entryName.length()) {
! error(formatMsg("error.validator.entryname.tooshort", entryName));
! isValid = false;
! 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()) {
if (fp.isNestedClass()) {
! if (!checkNestedClass(version, entryName, internalName, fp)) {
isValid = false;
}
return;
}
if (fp.isPublicClass()) {
! if (!isConcealed(internalName)) {
! error(Main.formatMsg("error.validator.new.public.class", entryName));
! isValid = false;
return;
}
! warn(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);
! currentTopLevelName = fp.topLevelName();
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");
// ok, not identical, check for compatible class version and api
- if (fp.isClass()) {
if (fp.isNestedClass()) {
! if (!checkNestedClass(version, entryName, internalName, fp)) {
isValid = false;
}
! return;
}
- debug("%s is a class entry", entryName);
if (!fp.isCompatibleVersion(matchFp)) {
! error(formatMsg("error.validator.incompatible.class.version", entryName));
! 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();
! 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.
*
* A versioned module descriptor must be identical to the root/base module
* descriptor, with two exceptions:
--- 26,238 ----
package sun.tools.jar;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleDescriptor.Exports;
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.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
! 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;
import static sun.tools.jar.Main.VERSIONS_DIR_LENGTH;
import static sun.tools.jar.Main.MODULE_INFO;
import static sun.tools.jar.Main.getMsg;
import static sun.tools.jar.Main.formatMsg;
import static sun.tools.jar.Main.formatMsg2;
import static sun.tools.jar.Main.toBinaryName;
final class Validator {
!
! private final Map<String,FingerPrint> classes = new HashMap<>();
private final Main main;
! private final ZipFile zf;
private boolean isValid = true;
private Set<String> concealedPkgs = Collections.emptySet();
private ModuleDescriptor md;
private String mdName;
! private Validator(Main main, ZipFile zf) {
this.main = main;
! this.zf = zf;
checkModuleDescriptor(MODULE_INFO);
}
! static boolean validate(Main main, ZipFile zf) throws IOException {
! return new Validator(main, zf).validate();
}
private boolean validate() {
try {
! 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) {
! errorAndInvalid(e.getMessage());
}
! return isValid;
}
! static class InvalidJarException extends RuntimeException {
private static final long serialVersionUID = -3642329147299217726L;
InvalidJarException(String msg) {
super(msg);
}
}
! 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 (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);
}
! // 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());
}
}
!
! /*
! * 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;
}
! if (fp.isNestedClass()) {
! if (!checkNestedClass(fp, fps)) {
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;
+ }
// 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(fp, fps)) {
isValid = false;
}
return;
}
if (fp.isPublicClass()) {
! if (!isConcealed(fp.className())) {
! errorAndInvalid(formatMsg("error.validator.new.public.class",
! fp.entryName()));
return;
}
! // entry is a public class entry in a concealed package
! warn(formatMsg("warn.validator.concealed.public.class",
! fp.entryName()));
}
! classes.put(fp.className(), fp);
return;
}
// 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.isNestedClass()) {
! 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;
}
if (!fp.isSameAPI(matchFp)) {
! errorAndInvalid(formatMsg("error.validator.different.api",
! fp.entryName()));
return;
}
! if (!checkClassName(fp)) {
isValid = false;
return;
}
! classes.put(fp.className(), fp);
return;
+ });
}
! /*
* Checks whether or not the given versioned module descriptor's attributes
* are valid when compared against the root/base module descriptor.
*
* A versioned module descriptor must be identical to the root/base module
* descriptor, with two exceptions:
*** 327,339 ****
* 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.
*/
private void checkModuleDescriptor(String miName) {
! ZipEntry je = jf.getEntry(miName);
! if (je != null) {
! try (InputStream jis = jf.getInputStream(je)) {
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
ModuleDescriptor base = this.md;
--- 240,252 ----
* 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.
*/
private void checkModuleDescriptor(String miName) {
! 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
ModuleDescriptor base = this.md;
*** 342,352 ****
md.exports().stream().map(Exports::source).forEach(concealedPkgs::remove);
md.opens().stream().map(Opens::source).forEach(concealedPkgs::remove);
// 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)
.peek(p -> error(formatMsg("error.missing.provider", p)))
.count() != 0) {
isValid = false;
return;
}
--- 255,265 ----
md.exports().stream().map(Exports::source).forEach(concealedPkgs::remove);
md.opens().stream().map(Opens::source).forEach(concealedPkgs::remove);
// must have the implementation class of the services it 'provides'.
if (md.provides().stream().map(Provides::providers)
.flatMap(List::stream)
! .filter(p -> zf.getEntry(toBinaryName(p)) == null)
.peek(p -> error(formatMsg("error.missing.provider", p)))
.count() != 0) {
isValid = false;
return;
}
*** 354,463 ****
this.mdName = miName;
return;
}
if (!base.name().equals(md.name())) {
! error(getMsg("error.validator.info.name.notequal"));
! isValid = false;
}
if (!base.requires().equals(md.requires())) {
Set<Requires> baseRequires = base.requires();
for (Requires r : md.requires()) {
if (baseRequires.contains(r))
continue;
if (r.modifiers().contains(Requires.Modifier.TRANSITIVE)) {
! error(getMsg("error.validator.info.requires.transitive"));
! isValid = false;
} else if (!isPlatformModule(r.name())) {
! error(getMsg("error.validator.info.requires.added"));
! isValid = false;
}
}
for (Requires r : baseRequires) {
Set<Requires> mdRequires = md.requires();
if (mdRequires.contains(r))
continue;
if (!isPlatformModule(r.name())) {
! error(getMsg("error.validator.info.requires.dropped"));
! isValid = false;
}
}
}
if (!base.exports().equals(md.exports())) {
! error(getMsg("error.validator.info.exports.notequal"));
! isValid = false;
}
if (!base.opens().equals(md.opens())) {
! error(getMsg("error.validator.info.opens.notequal"));
! isValid = false;
}
if (!base.provides().equals(md.provides())) {
! error(getMsg("error.validator.info.provides.notequal"));
! isValid = false;
}
if (!base.mainClass().equals(md.mainClass())) {
! error(formatMsg("error.validator.info.manclass.notequal", je.getName()));
! isValid = false;
}
if (!base.version().equals(md.version())) {
! error(formatMsg("error.validator.info.version.notequal", je.getName()));
! isValid = false;
}
} catch (Exception x) {
! error(x.getMessage() + " : " + miName);
! this.isValid = false;
! }
}
}
-
- 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) {
! 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 warn(String msg) {
main.warn(msg);
}
-
}
--- 267,364 ----
this.mdName = miName;
return;
}
if (!base.name().equals(md.name())) {
! errorAndInvalid(getMsg("error.validator.info.name.notequal"));
}
if (!base.requires().equals(md.requires())) {
Set<Requires> baseRequires = base.requires();
for (Requires r : md.requires()) {
if (baseRequires.contains(r))
continue;
if (r.modifiers().contains(Requires.Modifier.TRANSITIVE)) {
! errorAndInvalid(getMsg("error.validator.info.requires.transitive"));
} else if (!isPlatformModule(r.name())) {
! errorAndInvalid(getMsg("error.validator.info.requires.added"));
}
}
for (Requires r : baseRequires) {
Set<Requires> mdRequires = md.requires();
if (mdRequires.contains(r))
continue;
if (!isPlatformModule(r.name())) {
! errorAndInvalid(getMsg("error.validator.info.requires.dropped"));
}
}
}
if (!base.exports().equals(md.exports())) {
! errorAndInvalid(getMsg("error.validator.info.exports.notequal"));
}
if (!base.opens().equals(md.opens())) {
! errorAndInvalid(getMsg("error.validator.info.opens.notequal"));
}
if (!base.provides().equals(md.provides())) {
! errorAndInvalid(getMsg("error.validator.info.provides.notequal"));
}
if (!base.mainClass().equals(md.mainClass())) {
! errorAndInvalid(formatMsg("error.validator.info.manclass.notequal",
! ze.getName()));
}
if (!base.version().equals(md.version())) {
! errorAndInvalid(formatMsg("error.validator.info.version.notequal",
! ze.getName()));
}
} catch (Exception x) {
! 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 static String className(String entryName) {
! return entryName.endsWith(".class") ? entryName.substring(0, entryName.length() - 6) : null;
}
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);
}
}