Path: blob/master/src/jdk.jartool/share/classes/sun/tools/jar/FingerPrint.java
41161 views
/*1* Copyright (c) 2016, 2018, 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 jdk.internal.org.objectweb.asm.*;2829import java.io.IOException;30import java.io.InputStream;31import java.security.MessageDigest;32import java.security.NoSuchAlgorithmException;33import java.util.HashMap;34import java.util.HashSet;35import java.util.Map;36import java.util.Set;3738/**39* A FingerPrint is an abstract representation of a JarFile entry that contains40* information to determine if the entry represents a class or a41* resource, and whether two entries are identical. If the FingerPrint represents42* a class, it also contains information to (1) describe the public API;43* (2) compare the public API of this class with another class; (3) determine44* whether or not it's a nested class and, if so, the name of the associated45* outer class; and (4) for an canonically ordered set of classes determine46* if the class versions are compatible. A set of classes is canonically47* ordered if the classes in the set have the same name, and the base class48* precedes the versioned classes and if each versioned class with version49* {@code n} precedes classes with versions {@code > n} for all versions50* {@code n}.51*/52final class FingerPrint {53private static final MessageDigest MD;5455private final String basename;56private final String entryName;57private final int mrversion;5859private final byte[] sha1;60private final ClassAttributes attrs;61private final boolean isClassEntry;6263static {64try {65MD = MessageDigest.getInstance("SHA-1");66} catch (NoSuchAlgorithmException x) {67// log big problem?68throw new RuntimeException(x);69}70}7172public FingerPrint(String basename, String entryName, int mrversion, byte[] bytes)73throws IOException {74this.basename = basename;75this.entryName = entryName;76this.mrversion = mrversion;77if (isCafeBabe(bytes)) {78isClassEntry = true;79sha1 = sha1(bytes, 8); // skip magic number and major/minor version80attrs = getClassAttributes(bytes);81} else {82isClassEntry = false;83sha1 = null;84attrs = null;85}86}8788public boolean isClass() {89return isClassEntry;90}9192public boolean isNestedClass() {93return attrs.nestedClass;94}9596public boolean isPublicClass() {97return attrs.publicClass;98}99100public boolean isIdentical(FingerPrint that) {101if (that == null) return false;102if (this == that) return true;103return isEqual(this.sha1, that.sha1);104}105106public boolean isCompatibleVersion(FingerPrint that) {107return attrs.version >= that.attrs.version;108}109110public boolean isSameAPI(FingerPrint that) {111if (that == null) return false;112return attrs.equals(that.attrs);113}114115public String basename() {116return basename;117}118119public String entryName() {120return entryName;121}122123public String className() {124return attrs.name;125}126127public int mrversion() {128return mrversion;129}130131public String outerClassName() {132return attrs.outerClassName;133}134135private byte[] sha1(byte[] entry) {136MD.update(entry);137return MD.digest();138}139140private byte[] sha1(byte[] entry, int offset) {141MD.update(entry, offset, entry.length - offset);142return MD.digest();143}144145private boolean isEqual(byte[] sha1_1, byte[] sha1_2) {146return MessageDigest.isEqual(sha1_1, sha1_2);147}148149private static final byte[] cafeBabe = {(byte)0xca, (byte)0xfe, (byte)0xba, (byte)0xbe};150151private boolean isCafeBabe(byte[] bytes) {152if (bytes.length < 4) return false;153for (int i = 0; i < 4; i++) {154if (bytes[i] != cafeBabe[i]) {155return false;156}157}158return true;159}160161private ClassAttributes getClassAttributes(byte[] bytes) {162ClassReader rdr = new ClassReader(bytes);163ClassAttributes attrs = new ClassAttributes();164rdr.accept(attrs, 0);165return attrs;166}167168private static final class Field {169private final int access;170private final String name;171private final String desc;172173Field(int access, String name, String desc) {174this.access = access;175this.name = name;176this.desc = desc;177}178179@Override180public boolean equals(Object that) {181if (that == null) return false;182if (this == that) return true;183if (!(that instanceof Field)) return false;184Field field = (Field)that;185return (access == field.access) && name.equals(field.name)186&& desc.equals(field.desc);187}188189@Override190public int hashCode() {191int result = 17;192result = 37 * result + access;193result = 37 * result + name.hashCode();194result = 37 * result + desc.hashCode();195return result;196}197}198199private static final class Method {200private final int access;201private final String name;202private final String desc;203private final Set<String> exceptions;204205Method(int access, String name, String desc, Set<String> exceptions) {206this.access = access;207this.name = name;208this.desc = desc;209this.exceptions = exceptions;210}211212@Override213public boolean equals(Object that) {214if (that == null) return false;215if (this == that) return true;216if (!(that instanceof Method)) return false;217Method method = (Method)that;218return (access == method.access) && name.equals(method.name)219&& desc.equals(method.desc)220&& exceptions.equals(method.exceptions);221}222223@Override224public int hashCode() {225int result = 17;226result = 37 * result + access;227result = 37 * result + name.hashCode();228result = 37 * result + desc.hashCode();229result = 37 * result + exceptions.hashCode();230return result;231}232}233234private static final class ClassAttributes extends ClassVisitor {235private String name;236private String outerClassName;237private String superName;238private int version;239private int access;240private boolean publicClass;241private boolean nestedClass;242private final Set<Field> fields = new HashSet<>();243private final Set<Method> methods = new HashSet<>();244245public ClassAttributes() {246super(Opcodes.ASM7);247}248249private boolean isPublic(int access) {250return ((access & Opcodes.ACC_PUBLIC) == Opcodes.ACC_PUBLIC)251|| ((access & Opcodes.ACC_PROTECTED) == Opcodes.ACC_PROTECTED);252}253254@Override255public void visit(int version, int access, String name, String signature,256String superName, String[] interfaces) {257this.version = version;258this.access = access;259this.name = name;260this.nestedClass = name.contains("$");261this.superName = superName;262this.publicClass = isPublic(access);263}264265@Override266public void visitOuterClass(String owner, String name, String desc) {267if (!this.nestedClass) return;268this.outerClassName = owner;269}270271@Override272public void visitInnerClass(String name, String outerName, String innerName,273int access) {274if (!this.nestedClass) return;275if (outerName == null) return;276if (!this.name.equals(name)) return;277if (this.outerClassName == null) this.outerClassName = outerName;278}279280@Override281public FieldVisitor visitField(int access, String name, String desc,282String signature, Object value) {283if (isPublic(access)) {284fields.add(new Field(access, name, desc));285}286return null;287}288289@Override290public MethodVisitor visitMethod(int access, String name, String desc,291String signature, String[] exceptions) {292if (isPublic(access)) {293Set<String> exceptionSet = new HashSet<>();294if (exceptions != null) {295for (String e : exceptions) {296exceptionSet.add(e);297}298}299// treat type descriptor as a proxy for signature because signature300// is usually null, need to strip off the return type though301int n;302if (desc != null && (n = desc.lastIndexOf(')')) != -1) {303desc = desc.substring(0, n + 1);304methods.add(new Method(access, name, desc, exceptionSet));305}306}307return null;308}309310@Override311public void visitEnd() {312this.nestedClass = this.outerClassName != null;313}314315@Override316public boolean equals(Object that) {317if (that == null) return false;318if (this == that) return true;319if (!(that instanceof ClassAttributes)) return false;320ClassAttributes clsAttrs = (ClassAttributes)that;321boolean superNameOkay = superName != null322? superName.equals(clsAttrs.superName) : true;323return access == clsAttrs.access324&& superNameOkay325&& fields.equals(clsAttrs.fields)326&& methods.equals(clsAttrs.methods);327}328329@Override330public int hashCode() {331int result = 17;332result = 37 * result + access;333result = 37 * result + superName != null ? superName.hashCode() : 0;334result = 37 * result + fields.hashCode();335result = 37 * result + methods.hashCode();336return result;337}338}339}340341342