Path: blob/master/src/jdk.jartool/share/classes/sun/tools/jar/Validator.java
41161 views
/*1* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.tools.jar;2627import java.io.File;28import java.io.IOException;29import java.io.InputStream;30import java.lang.module.ModuleDescriptor;31import java.lang.module.ModuleDescriptor.Exports;32import java.lang.module.ModuleDescriptor.Opens;33import java.lang.module.ModuleDescriptor.Provides;34import java.lang.module.ModuleDescriptor.Requires;35import java.util.Collections;36import java.util.HashMap;37import java.util.HashSet;38import java.util.List;39import java.util.Map;40import java.util.Set;41import java.util.TreeMap;42import java.util.function.Function;43import java.util.stream.Collectors;44import java.util.zip.ZipEntry;45import java.util.zip.ZipFile;4647import static java.util.jar.JarFile.MANIFEST_NAME;48import static sun.tools.jar.Main.VERSIONS_DIR;49import static sun.tools.jar.Main.VERSIONS_DIR_LENGTH;50import static sun.tools.jar.Main.MODULE_INFO;51import static sun.tools.jar.Main.getMsg;52import static sun.tools.jar.Main.formatMsg;53import static sun.tools.jar.Main.formatMsg2;54import static sun.tools.jar.Main.toBinaryName;5556final class Validator {5758private final Map<String,FingerPrint> classes = new HashMap<>();59private final Main main;60private final ZipFile zf;61private boolean isValid = true;62private Set<String> concealedPkgs = Collections.emptySet();63private ModuleDescriptor md;64private String mdName;6566private Validator(Main main, ZipFile zf) {67this.main = main;68this.zf = zf;69checkModuleDescriptor(MODULE_INFO);70}7172static boolean validate(Main main, ZipFile zf) throws IOException {73return new Validator(main, zf).validate();74}7576private boolean validate() {77try {78zf.stream()79.filter(e -> e.getName().endsWith(".class"))80.map(this::getFingerPrint)81.filter(FingerPrint::isClass) // skip any non-class entry82.collect(Collectors.groupingBy(83FingerPrint::mrversion,84TreeMap::new,85Collectors.toMap(FingerPrint::className,86Function.identity(),87this::sameNameFingerPrint)))88.forEach((version, entries) -> {89if (version == 0)90validateBase(entries);91else92validateVersioned(entries);93});94} catch (InvalidJarException e) {95errorAndInvalid(e.getMessage());96}97return isValid;98}99100static class InvalidJarException extends RuntimeException {101private static final long serialVersionUID = -3642329147299217726L;102InvalidJarException(String msg) {103super(msg);104}105}106107private FingerPrint sameNameFingerPrint(FingerPrint fp1, FingerPrint fp2) {108checkClassName(fp1);109checkClassName(fp2);110// entries/classes with same name, return fp2 for now ?111return fp2;112}113114private FingerPrint getFingerPrint(ZipEntry ze) {115// figure out the version and basename from the ZipEntry116String ename = ze.getName();117String bname = ename;118int version = 0;119120if (ename.startsWith(VERSIONS_DIR)) {121int n = ename.indexOf("/", VERSIONS_DIR_LENGTH);122if (n == -1) {123throw new InvalidJarException(124formatMsg("error.validator.version.notnumber", ename));125}126try {127version = Integer.parseInt(ename, VERSIONS_DIR_LENGTH, n, 10);128} catch (NumberFormatException x) {129throw new InvalidJarException(130formatMsg("error.validator.version.notnumber", ename));131}132if (n == ename.length()) {133throw new InvalidJarException(134formatMsg("error.validator.entryname.tooshort", ename));135}136bname = ename.substring(n + 1);137}138139// return the cooresponding fingerprint entry140try (InputStream is = zf.getInputStream(ze)) {141return new FingerPrint(bname, ename, version, is.readAllBytes());142} catch (IOException x) {143throw new InvalidJarException(x.getMessage());144}145}146147/*148* Validates (a) if there is any isolated nested class, and (b) if the149* class name in class file (by asm) matches the entry's basename.150*/151public void validateBase(Map<String, FingerPrint> fps) {152fps.values().forEach( fp -> {153if (!checkClassName(fp)) {154return;155}156if (fp.isNestedClass()) {157checkNestedClass(fp, fps);158}159classes.put(fp.className(), fp);160});161}162163public void validateVersioned(Map<String, FingerPrint> fps) {164165fps.values().forEach( fp -> {166167// validate the versioned module-info168if (MODULE_INFO.equals(fp.basename())) {169checkModuleDescriptor(fp.entryName());170return;171}172// process a versioned entry, look for previous entry with same name173FingerPrint matchFp = classes.get(fp.className());174if (matchFp == null) {175// no match found176if (fp.isNestedClass()) {177checkNestedClass(fp, fps);178return;179}180if (fp.isPublicClass()) {181if (!isConcealed(fp.className())) {182errorAndInvalid(formatMsg("error.validator.new.public.class",183fp.entryName()));184return;185}186// entry is a public class entry in a concealed package187warn(formatMsg("warn.validator.concealed.public.class",188fp.entryName()));189}190classes.put(fp.className(), fp);191return;192}193194// are the two classes/resources identical?195if (fp.isIdentical(matchFp)) {196warn(formatMsg("warn.validator.identical.entry", fp.entryName()));197return; // it's okay, just takes up room198}199200// ok, not identical, check for compatible class version and api201if (fp.isNestedClass()) {202checkNestedClass(fp, fps);203return; // fall through, need check nested public class??204}205if (!fp.isCompatibleVersion(matchFp)) {206errorAndInvalid(formatMsg("error.validator.incompatible.class.version",207fp.entryName()));208return;209}210if (!fp.isSameAPI(matchFp)) {211errorAndInvalid(formatMsg("error.validator.different.api",212fp.entryName()));213return;214}215if (!checkClassName(fp)) {216return;217}218classes.put(fp.className(), fp);219220return;221});222}223224/*225* Checks whether or not the given versioned module descriptor's attributes226* are valid when compared against the root/base module descriptor.227*228* A versioned module descriptor must be identical to the root/base module229* descriptor, with two exceptions:230* - A versioned descriptor can have different non-public `requires`231* clauses of platform ( `java.*` and `jdk.*` ) modules, and232* - A versioned descriptor can have different `uses` clauses, even of233* service types defined outside of the platform modules.234*/235private void checkModuleDescriptor(String miName) {236ZipEntry ze = zf.getEntry(miName);237if (ze != null) {238try (InputStream jis = zf.getInputStream(ze)) {239ModuleDescriptor md = ModuleDescriptor.read(jis);240// Initialize the base md if it's not yet. A "base" md can be either the241// root module-info.class or the first versioned module-info.class242ModuleDescriptor base = this.md;243244if (base == null) {245concealedPkgs = new HashSet<>(md.packages());246md.exports().stream().map(Exports::source).forEach(concealedPkgs::remove);247md.opens().stream().map(Opens::source).forEach(concealedPkgs::remove);248// must have the implementation class of the services it 'provides'.249if (md.provides().stream().map(Provides::providers)250.flatMap(List::stream)251.filter(p -> zf.getEntry(toBinaryName(p)) == null)252.peek(p -> error(formatMsg("error.missing.provider", p)))253.count() != 0) {254isValid = false;255return;256}257this.md = md;258this.mdName = miName;259return;260}261262if (!base.name().equals(md.name())) {263errorAndInvalid(getMsg("error.validator.info.name.notequal"));264}265if (!base.requires().equals(md.requires())) {266Set<Requires> baseRequires = base.requires();267for (Requires r : md.requires()) {268if (baseRequires.contains(r))269continue;270if (r.modifiers().contains(Requires.Modifier.TRANSITIVE)) {271errorAndInvalid(getMsg("error.validator.info.requires.transitive"));272} else if (!isPlatformModule(r.name())) {273errorAndInvalid(getMsg("error.validator.info.requires.added"));274}275}276for (Requires r : baseRequires) {277Set<Requires> mdRequires = md.requires();278if (mdRequires.contains(r))279continue;280if (!isPlatformModule(r.name())) {281errorAndInvalid(getMsg("error.validator.info.requires.dropped"));282}283}284}285if (!base.exports().equals(md.exports())) {286errorAndInvalid(getMsg("error.validator.info.exports.notequal"));287}288if (!base.opens().equals(md.opens())) {289errorAndInvalid(getMsg("error.validator.info.opens.notequal"));290}291if (!base.provides().equals(md.provides())) {292errorAndInvalid(getMsg("error.validator.info.provides.notequal"));293}294if (!base.mainClass().equals(md.mainClass())) {295errorAndInvalid(formatMsg("error.validator.info.manclass.notequal",296ze.getName()));297}298if (!base.version().equals(md.version())) {299errorAndInvalid(formatMsg("error.validator.info.version.notequal",300ze.getName()));301}302} catch (Exception x) {303errorAndInvalid(x.getMessage() + " : " + miName);304}305}306}307308private boolean checkClassName(FingerPrint fp) {309if (fp.className().equals(className(fp.basename()))) {310return true;311}312error(formatMsg2("error.validator.names.mismatch",313fp.entryName(), fp.className().replace("/", ".")));314return isValid = false;315}316317private boolean checkNestedClass(FingerPrint fp, Map<String, FingerPrint> outerClasses) {318if (outerClasses.containsKey(fp.outerClassName())) {319return true;320}321// outer class was not available322323error(formatMsg("error.validator.isolated.nested.class", fp.entryName()));324return isValid = false;325}326327private boolean isConcealed(String className) {328if (concealedPkgs.isEmpty()) {329return false;330}331int idx = className.lastIndexOf('/');332String pkgName = idx != -1 ? className.substring(0, idx).replace('/', '.') : "";333return concealedPkgs.contains(pkgName);334}335336private static boolean isPlatformModule(String name) {337return name.startsWith("java.") || name.startsWith("jdk.");338}339340private static String className(String entryName) {341return entryName.endsWith(".class") ? entryName.substring(0, entryName.length() - 6) : null;342}343344private void error(String msg) {345main.error(msg);346}347348private void errorAndInvalid(String msg) {349main.error(msg);350isValid = false;351}352353private void warn(String msg) {354main.warn(msg);355}356}357358359