1 /*
   2  * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.tools.jar;
  27 
  28 import jdk.internal.org.objectweb.asm.*;
  29 
  30 import java.io.IOException;
  31 import java.io.InputStream;
  32 import java.security.MessageDigest;
  33 import java.security.NoSuchAlgorithmException;
  34 import java.util.HashMap;
  35 import java.util.HashSet;
  36 import java.util.Map;
  37 import java.util.Set;
  38 
  39 /**
  40  * A FingerPrint is an abstract representation of a JarFile entry that contains
  41  * information to determine if the entry represents a class or a
  42  * resource, and whether two entries are identical.  If the FingerPrint represents
  43  * a class, it also contains information to (1) describe the public API;
  44  * (2) compare the public API of this class with another class;  (3) determine
  45  * whether or not it's a nested class and, if so, the name of the associated
  46  * outer class; and (4) for an canonically ordered set of classes determine
  47  * if the class versions are compatible.  A set of classes is canonically
  48  * ordered if the classes in the set have the same name, and the base class
  49  * precedes the versioned classes and if each versioned class with version
  50  * {@code n} precedes classes with versions {@code > n} for all versions
  51  * {@code n}.
  52  */
  53 final class FingerPrint {
  54     private static final MessageDigest MD;
  55 
  56     private final String basename;
  57     private final String entryName;
  58     private final int mrversion;
  59 
  60     private final byte[] sha1;
  61     private final ClassAttributes attrs;
  62     private final boolean isClassEntry;
  63 
  64     static {
  65         try {
  66             MD = MessageDigest.getInstance("SHA-1");
  67         } catch (NoSuchAlgorithmException x) {
  68             // log big problem?
  69             throw new RuntimeException(x);
  70         }
  71     }
  72 
  73     public FingerPrint(String basename, String entryName, int mrversion, byte[] bytes)
  74             throws IOException {
  75         this.basename = basename;
  76         this.entryName = entryName;
  77         this.mrversion = mrversion;
  78         if (isCafeBabe(bytes)) {
  79             isClassEntry = true;
  80             sha1 = sha1(bytes, 8);  // skip magic number and major/minor version
  81             attrs = getClassAttributes(bytes);
  82         } else {
  83             isClassEntry = false;
  84             sha1 = null;
  85             attrs = null;
  86         }
  87     }
  88 
  89     public boolean isClass() {
  90         return isClassEntry;
  91     }
  92 
  93     public boolean isNestedClass() {
  94         return attrs.nestedClass;
  95     }
  96 
  97     public boolean isPublicClass() {
  98         return attrs.publicClass;
  99     }
 100 
 101     public boolean isIdentical(FingerPrint that) {
 102         if (that == null) return false;
 103         if (this == that) return true;
 104         return isEqual(this.sha1, that.sha1);
 105     }
 106 
 107     public boolean isCompatibleVersion(FingerPrint that) {
 108         return attrs.version >= that.attrs.version;
 109     }
 110 
 111     public boolean isSameAPI(FingerPrint that) {
 112         if (that == null) return false;
 113         return attrs.equals(that.attrs);
 114     }
 115 
 116     public String basename() {
 117         return basename;
 118     }
 119 
 120     public String entryName() {
 121         return entryName;
 122     }
 123 
 124     public String className() {
 125         return attrs.name;
 126     }
 127 
 128     public int mrversion() {
 129         return mrversion;
 130     }
 131 
 132     public String outerClassName() {
 133         return attrs.outerClassName;
 134     }
 135 
 136     private byte[] sha1(byte[] entry) {
 137         MD.update(entry);
 138         return MD.digest();
 139     }
 140 
 141     private byte[] sha1(byte[] entry, int offset) {
 142         MD.update(entry, offset, entry.length - offset);
 143         return MD.digest();
 144     }
 145 
 146     private boolean isEqual(byte[] sha1_1, byte[] sha1_2) {
 147         return MessageDigest.isEqual(sha1_1, sha1_2);
 148     }
 149 
 150     private static final byte[] cafeBabe = {(byte)0xca, (byte)0xfe, (byte)0xba, (byte)0xbe};
 151 
 152     private boolean isCafeBabe(byte[] bytes) {
 153         if (bytes.length < 4) return false;
 154         for (int i = 0; i < 4; i++) {
 155             if (bytes[i] != cafeBabe[i]) {
 156                 return false;
 157             }
 158         }
 159         return true;
 160     }
 161 
 162     private ClassAttributes getClassAttributes(byte[] bytes) {
 163         ClassReader rdr = new ClassReader(bytes);
 164         ClassAttributes attrs = new ClassAttributes();
 165         rdr.accept(attrs, 0);
 166         return attrs;
 167     }
 168 
 169     private static final class Field {
 170         private final int access;
 171         private final String name;
 172         private final String desc;
 173 
 174         Field(int access, String name, String desc) {
 175             this.access = access;
 176             this.name = name;
 177             this.desc = desc;
 178         }
 179 
 180         @Override
 181         public boolean equals(Object that) {
 182             if (that == null) return false;
 183             if (this == that) return true;
 184             if (!(that instanceof Field)) return false;
 185             Field field = (Field)that;
 186             return (access == field.access) && name.equals(field.name)
 187                     && desc.equals(field.desc);
 188         }
 189 
 190         @Override
 191         public int hashCode() {
 192             int result = 17;
 193             result = 37 * result + access;
 194             result = 37 * result + name.hashCode();
 195             result = 37 * result + desc.hashCode();
 196             return result;
 197         }
 198     }
 199 
 200     private static final class Method {
 201         private final int access;
 202         private final String name;
 203         private final String desc;
 204         private final Set<String> exceptions;
 205 
 206         Method(int access, String name, String desc, Set<String> exceptions) {
 207             this.access = access;
 208             this.name = name;
 209             this.desc = desc;
 210             this.exceptions = exceptions;
 211         }
 212 
 213         @Override
 214         public boolean equals(Object that) {
 215             if (that == null) return false;
 216             if (this == that) return true;
 217             if (!(that instanceof Method)) return false;
 218             Method method = (Method)that;
 219             return (access == method.access) && name.equals(method.name)
 220                     && desc.equals(method.desc)
 221                     && exceptions.equals(method.exceptions);
 222         }
 223 
 224         @Override
 225         public int hashCode() {
 226             int result = 17;
 227             result = 37 * result + access;
 228             result = 37 * result + name.hashCode();
 229             result = 37 * result + desc.hashCode();
 230             result = 37 * result + exceptions.hashCode();
 231             return result;
 232         }
 233     }
 234 
 235     private static final class ClassAttributes extends ClassVisitor {
 236         private String name;
 237         private String outerClassName;
 238         private String superName;
 239         private int version;
 240         private int access;
 241         private boolean publicClass;
 242         private boolean nestedClass;
 243         private final Set<Field> fields = new HashSet<>();
 244         private final Set<Method> methods = new HashSet<>();
 245 
 246         public ClassAttributes() {
 247             super(Opcodes.ASM7);
 248         }
 249 
 250         private boolean isPublic(int access) {
 251             return ((access & Opcodes.ACC_PUBLIC) == Opcodes.ACC_PUBLIC)
 252                     || ((access & Opcodes.ACC_PROTECTED) == Opcodes.ACC_PROTECTED);
 253         }
 254 
 255         @Override
 256         public void visit(int version, int access, String name, String signature,
 257                           String superName, String[] interfaces) {
 258             this.version = version;
 259             this.access = access;
 260             this.name = name;
 261             this.nestedClass = name.contains("$");
 262             this.superName = superName;
 263             this.publicClass = isPublic(access);
 264         }
 265 
 266         @Override
 267         public void visitOuterClass(String owner, String name, String desc) {
 268             if (!this.nestedClass) return;
 269             this.outerClassName = owner;
 270         }
 271 
 272         @Override
 273         public void visitInnerClass(String name, String outerName, String innerName,
 274                                     int access) {
 275             if (!this.nestedClass) return;
 276             if (outerName == null) return;
 277             if (!this.name.equals(name)) return;
 278             if (this.outerClassName == null) this.outerClassName = outerName;
 279         }
 280 
 281         @Override
 282         public FieldVisitor visitField(int access, String name, String desc,
 283                                        String signature, Object value) {
 284             if (isPublic(access)) {
 285                 fields.add(new Field(access, name, desc));
 286             }
 287             return null;
 288         }
 289 
 290         @Override
 291         public MethodVisitor visitMethod(int access, String name, String desc,
 292                                          String signature, String[] exceptions) {
 293             if (isPublic(access)) {
 294                 Set<String> exceptionSet = new HashSet<>();
 295                 if (exceptions != null) {
 296                     for (String e : exceptions) {
 297                         exceptionSet.add(e);
 298                     }
 299                 }
 300                 // treat type descriptor as a proxy for signature because signature
 301                 // is usually null, need to strip off the return type though
 302                 int n;
 303                 if (desc != null && (n = desc.lastIndexOf(')')) != -1) {
 304                     desc = desc.substring(0, n + 1);
 305                     methods.add(new Method(access, name, desc, exceptionSet));
 306                 }
 307             }
 308             return null;
 309         }
 310 
 311         @Override
 312         public void visitEnd() {
 313             this.nestedClass = this.outerClassName != null;
 314         }
 315 
 316         @Override
 317         public boolean equals(Object that) {
 318             if (that == null) return false;
 319             if (this == that) return true;
 320             if (!(that instanceof ClassAttributes)) return false;
 321             ClassAttributes clsAttrs = (ClassAttributes)that;
 322             boolean superNameOkay = superName != null
 323                     ? superName.equals(clsAttrs.superName) : true;
 324             return access == clsAttrs.access
 325                     && superNameOkay
 326                     && fields.equals(clsAttrs.fields)
 327                     && methods.equals(clsAttrs.methods);
 328         }
 329 
 330         @Override
 331         public int hashCode() {
 332             int result = 17;
 333             result = 37 * result + access;
 334             result = 37 * result + superName != null ? superName.hashCode() : 0;
 335             result = 37 * result + fields.hashCode();
 336             result = 37 * result + methods.hashCode();
 337             return result;
 338         }
 339     }
 340 }