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 26package sun.tools.jar; 27 28import jdk.internal.org.objectweb.asm.*; 29 30import java.io.IOException; 31import java.io.InputStream; 32import java.security.MessageDigest; 33import java.security.NoSuchAlgorithmException; 34import java.util.HashMap; 35import java.util.HashSet; 36import java.util.Map; 37import 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 * top level 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 */ 53final class FingerPrint { 54 private static final MessageDigest MD; 55 56 private final byte[] sha1; 57 private final ClassAttributes attrs; 58 private final boolean isClassEntry; 59 private final String entryName; 60 61 static { 62 try { 63 MD = MessageDigest.getInstance("SHA-1"); 64 } catch (NoSuchAlgorithmException x) { 65 // log big problem? 66 throw new RuntimeException(x); 67 } 68 } 69 70 public FingerPrint(String entryName,byte[] bytes) throws IOException { 71 this.entryName = entryName; 72 if (entryName.endsWith(".class") && isCafeBabe(bytes)) { 73 isClassEntry = true; 74 sha1 = sha1(bytes, 8); // skip magic number and major/minor version 75 attrs = getClassAttributes(bytes); 76 } else { 77 isClassEntry = false; 78 sha1 = sha1(bytes); 79 attrs = new ClassAttributes(); // empty class 80 } 81 } 82 83 public boolean isClass() { 84 return isClassEntry; 85 } 86 87 public boolean isNestedClass() { 88 return attrs.nestedClass; 89 } 90 91 public boolean isPublicClass() { 92 return attrs.publicClass; 93 } 94 95 public boolean isIdentical(FingerPrint that) { 96 if (that == null) return false; 97 if (this == that) return true; 98 return isEqual(this.sha1, that.sha1); 99 } 100 101 public boolean isCompatibleVersion(FingerPrint that) { 102 return attrs.version >= that.attrs.version; 103 } 104 105 public boolean isSameAPI(FingerPrint that) { 106 if (that == null) return false; 107 return attrs.equals(that.attrs); 108 } 109 110 public String name() { 111 String name = attrs.name; 112 return name == null ? entryName : name; 113 } 114 115 public String topLevelName() { 116 String name = attrs.topLevelName; 117 return name == null ? name() : name; 118 } 119 120 private byte[] sha1(byte[] entry) { 121 MD.update(entry); 122 return MD.digest(); 123 } 124 125 private byte[] sha1(byte[] entry, int offset) { 126 MD.update(entry, offset, entry.length - offset); 127 return MD.digest(); 128 } 129 130 private boolean isEqual(byte[] sha1_1, byte[] sha1_2) { 131 return MessageDigest.isEqual(sha1_1, sha1_2); 132 } 133 134 private static final byte[] cafeBabe = {(byte)0xca, (byte)0xfe, (byte)0xba, (byte)0xbe}; 135 136 private boolean isCafeBabe(byte[] bytes) { 137 if (bytes.length < 4) return false; 138 for (int i = 0; i < 4; i++) { 139 if (bytes[i] != cafeBabe[i]) { 140 return false; 141 } 142 } 143 return true; 144 } 145 146 private ClassAttributes getClassAttributes(byte[] bytes) { 147 ClassReader rdr = new ClassReader(bytes); 148 ClassAttributes attrs = new ClassAttributes(); 149 rdr.accept(attrs, 0); 150 return attrs; 151 } 152 153 private static final class Field { 154 private final int access; 155 private final String name; 156 private final String desc; 157 158 Field(int access, String name, String desc) { 159 this.access = access; 160 this.name = name; 161 this.desc = desc; 162 } 163 164 @Override 165 public boolean equals(Object that) { 166 if (that == null) return false; 167 if (this == that) return true; 168 if (!(that instanceof Field)) return false; 169 Field field = (Field)that; 170 return (access == field.access) && name.equals(field.name) 171 && desc.equals(field.desc); 172 } 173 174 @Override 175 public int hashCode() { 176 int result = 17; 177 result = 37 * result + access; 178 result = 37 * result + name.hashCode(); 179 result = 37 * result + desc.hashCode(); 180 return result; 181 } 182 } 183 184 private static final class Method { 185 private final int access; 186 private final String name; 187 private final String desc; 188 private final Set<String> exceptions; 189 190 Method(int access, String name, String desc, Set<String> exceptions) { 191 this.access = access; 192 this.name = name; 193 this.desc = desc; 194 this.exceptions = exceptions; 195 } 196 197 @Override 198 public boolean equals(Object that) { 199 if (that == null) return false; 200 if (this == that) return true; 201 if (!(that instanceof Method)) return false; 202 Method method = (Method)that; 203 return (access == method.access) && name.equals(method.name) 204 && desc.equals(method.desc) 205 && exceptions.equals(method.exceptions); 206 } 207 208 @Override 209 public int hashCode() { 210 int result = 17; 211 result = 37 * result + access; 212 result = 37 * result + name.hashCode(); 213 result = 37 * result + desc.hashCode(); 214 result = 37 * result + exceptions.hashCode(); 215 return result; 216 } 217 } 218 219 private static final class ClassAttributes extends ClassVisitor { 220 private String name; 221 private String topLevelName; 222 private String superName; 223 private int version; 224 private int access; 225 private boolean publicClass; 226 private boolean nestedClass; 227 private final Set<Field> fields = new HashSet<>(); 228 private final Set<Method> methods = new HashSet<>(); 229 230 public ClassAttributes() { 231 super(Opcodes.ASM5); 232 } 233 234 private boolean isPublic(int access) { 235 return ((access & Opcodes.ACC_PUBLIC) == Opcodes.ACC_PUBLIC) 236 || ((access & Opcodes.ACC_PROTECTED) == Opcodes.ACC_PROTECTED); 237 } 238 239 @Override 240 public void visit(int version, int access, String name, String signature, 241 String superName, String[] interfaces) { 242 this.version = version; 243 this.access = access; 244 this.name = name; 245 this.nestedClass = name.contains("$"); 246 this.superName = superName; 247 this.publicClass = isPublic(access); 248 } 249 250 @Override 251 public void visitOuterClass(String owner, String name, String desc) { 252 if (!this.nestedClass) return; 253 this.topLevelName = owner; 254 } 255 256 @Override 257 public void visitInnerClass(String name, String outerName, String innerName, 258 int access) { 259 if (!this.nestedClass) return; 260 if (outerName == null) return; 261 if (!this.name.equals(name)) return; 262 if (this.topLevelName == null) this.topLevelName = outerName; 263 } 264 265 @Override 266 public FieldVisitor visitField(int access, String name, String desc, 267 String signature, Object value) { 268 if (isPublic(access)) { 269 fields.add(new Field(access, name, desc)); 270 } 271 return null; 272 } 273 274 @Override 275 public MethodVisitor visitMethod(int access, String name, String desc, 276 String signature, String[] exceptions) { 277 if (isPublic(access)) { 278 Set<String> exceptionSet = new HashSet<>(); 279 if (exceptions != null) { 280 for (String e : exceptions) { 281 exceptionSet.add(e); 282 } 283 } 284 // treat type descriptor as a proxy for signature because signature 285 // is usually null, need to strip off the return type though 286 int n; 287 if (desc != null && (n = desc.lastIndexOf(')')) != -1) { 288 desc = desc.substring(0, n + 1); 289 methods.add(new Method(access, name, desc, exceptionSet)); 290 } 291 } 292 return null; 293 } 294 295 @Override 296 public void visitEnd() { 297 this.nestedClass = this.topLevelName != null; 298 } 299 300 @Override 301 public boolean equals(Object that) { 302 if (that == null) return false; 303 if (this == that) return true; 304 if (!(that instanceof ClassAttributes)) return false; 305 ClassAttributes clsAttrs = (ClassAttributes)that; 306 boolean superNameOkay = superName != null 307 ? superName.equals(clsAttrs.superName) : true; 308 return access == clsAttrs.access 309 && superNameOkay 310 && fields.equals(clsAttrs.fields) 311 && methods.equals(clsAttrs.methods); 312 } 313 314 @Override 315 public int hashCode() { 316 int result = 17; 317 result = 37 * result + access; 318 result = 37 * result + superName != null ? superName.hashCode() : 0; 319 result = 37 * result + fields.hashCode(); 320 result = 37 * result + methods.hashCode(); 321 return result; 322 } 323 } 324} 325