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