ObjectStreamClassUtil_1_3.java revision 608:7e06bf1dcb09
1/*
2 * Copyright (c) 2000, 2014, 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 com.sun.corba.se.impl.orbutil;
27
28// for computing the structural UID
29import java.security.MessageDigest;
30import java.security.NoSuchAlgorithmException;
31import java.security.DigestOutputStream;
32import java.security.AccessController;
33import java.security.PrivilegedExceptionAction;
34import java.security.PrivilegedActionException;
35import java.security.PrivilegedAction;
36import java.io.DataOutputStream;
37import java.io.ByteArrayOutputStream;
38import java.io.IOException;
39
40import java.util.Arrays;
41import java.util.Comparator;
42import java.lang.reflect.Field;
43import java.lang.reflect.Modifier;
44import java.lang.reflect.Array;
45import java.lang.reflect.Member;
46import java.lang.reflect.Method;
47import java.lang.reflect.Constructor;
48
49
50import com.sun.corba.se.impl.io.ObjectStreamClass;
51
52public final class ObjectStreamClassUtil_1_3 {
53
54    // maintained here for backward compatability with JDK 1.3, where
55    // writeObject method was not being checked at all, so there is
56    // no need to lookup the ObjectStreamClass
57
58    public static long computeSerialVersionUID(final Class cl) {
59
60        long csuid = ObjectStreamClass.getSerialVersionUID(cl);
61        if (csuid == 0)
62            return csuid; // for non-serializable/proxy classes
63
64        csuid = (ObjectStreamClassUtil_1_3.getSerialVersion(csuid, cl).longValue());
65        return csuid;
66    }
67
68
69    // to maintain same suid as the JDK 1.3, we pick
70    // up suid only for classes with private,static,final
71    // declarations, and compute it for all others
72
73    private static Long getSerialVersion(final long csuid, final Class cl)
74    {
75        return (Long) AccessController.doPrivileged(new PrivilegedAction() {
76          public Object run() {
77            long suid;
78            try {
79                final Field f = cl.getDeclaredField("serialVersionUID");
80                int mods = f.getModifiers();
81                if (Modifier.isStatic(mods) &&
82                    Modifier.isFinal(mods) && Modifier.isPrivate(mods)) {
83                    suid = csuid;
84                 } else {
85                    suid = _computeSerialVersionUID(cl);
86                 }
87              } catch (NoSuchFieldException ex) {
88                  suid = _computeSerialVersionUID(cl);
89              //} catch (IllegalAccessException ex) {
90              //     suid = _computeSerialVersionUID(cl);
91              }
92              return new Long(suid);
93           }
94        });
95    }
96
97    public static long computeStructuralUID(boolean hasWriteObject, Class<?> cl) {
98        ByteArrayOutputStream devnull = new ByteArrayOutputStream(512);
99
100        long h = 0;
101        try {
102
103            if ((!java.io.Serializable.class.isAssignableFrom(cl)) ||
104                (cl.isInterface())){
105                return 0;
106            }
107
108            if (java.io.Externalizable.class.isAssignableFrom(cl)) {
109                return 1;
110            }
111
112            MessageDigest md = MessageDigest.getInstance("SHA");
113            DigestOutputStream mdo = new DigestOutputStream(devnull, md);
114            DataOutputStream data = new DataOutputStream(mdo);
115
116            //In the old case, for the caller class, the write Method wasn't considered
117            // for rep-id calculations correctly, but for parent classes it was taken
118            // into account.  That is the reason there is the klude of getting the write
119            // Object method in there
120
121            // Get SUID of parent
122            Class<?> parent = cl.getSuperclass();
123            if ((parent != null) && (parent != java.lang.Object.class)) {
124                boolean hasWriteObjectFlag = false;
125                Class [] args = {java.io.ObjectOutputStream.class};
126                Method hasWriteObjectMethod = ObjectStreamClassUtil_1_3.getDeclaredMethod(parent, "writeObject", args,
127                       Modifier.PRIVATE, Modifier.STATIC);
128                if (hasWriteObjectMethod != null)
129                    hasWriteObjectFlag = true;
130                data.writeLong(ObjectStreamClassUtil_1_3.computeStructuralUID(hasWriteObjectFlag, parent));
131            }
132
133            if (hasWriteObject)
134                data.writeInt(2);
135            else
136                data.writeInt(1);
137
138            /* Sort the field names to get a deterministic order */
139            Field[] field = ObjectStreamClassUtil_1_3.getDeclaredFields(cl);
140            Arrays.sort(field, compareMemberByName);
141
142            for (int i = 0; i < field.length; i++) {
143                Field f = field[i];
144
145                                /* Include in the hash all fields except those that are
146                                 * transient or static.
147                                 */
148                int m = f.getModifiers();
149                if (Modifier.isTransient(m) || Modifier.isStatic(m))
150                    continue;
151
152                data.writeUTF(f.getName());
153                data.writeUTF(getSignature(f.getType()));
154            }
155
156            /* Compute the hash value for this class.
157             * Use only the first 64 bits of the hash.
158             */
159            data.flush();
160            byte hasharray[] = md.digest();
161            int minimum = Math.min(8, hasharray.length);
162            for (int i = minimum; i > 0; i--) {
163                h += (long)(hasharray[i] & 255) << (i * 8);
164            }
165        } catch (IOException ignore) {
166            /* can't happen, but be deterministic anyway. */
167            h = -1;
168        } catch (NoSuchAlgorithmException complain) {
169            throw new SecurityException(complain.getMessage());
170        }
171        return h;
172    }
173
174    /*
175     * Compute a hash for the specified class.  Incrementally add
176     * items to the hash accumulating in the digest stream.
177     * Fold the hash into a long.  Use the SHA secure hash function.
178     */
179    private static long _computeSerialVersionUID(Class cl) {
180        ByteArrayOutputStream devnull = new ByteArrayOutputStream(512);
181
182        long h = 0;
183        try {
184            MessageDigest md = MessageDigest.getInstance("SHA");
185            DigestOutputStream mdo = new DigestOutputStream(devnull, md);
186            DataOutputStream data = new DataOutputStream(mdo);
187
188
189            data.writeUTF(cl.getName());
190
191            int classaccess = cl.getModifiers();
192            classaccess &= (Modifier.PUBLIC | Modifier.FINAL |
193                            Modifier.INTERFACE | Modifier.ABSTRACT);
194
195            /* Workaround for javac bug that only set ABSTRACT for
196             * interfaces if the interface had some methods.
197             * The ABSTRACT bit reflects that the number of methods > 0.
198             * This is required so correct hashes can be computed
199             * for existing class files.
200             * Previously this hack was previously present in the VM.
201             */
202            Method[] method = cl.getDeclaredMethods();
203            if ((classaccess & Modifier.INTERFACE) != 0) {
204                classaccess &= (~Modifier.ABSTRACT);
205                if (method.length > 0) {
206                    classaccess |= Modifier.ABSTRACT;
207                }
208            }
209
210            data.writeInt(classaccess);
211
212            /*
213             * Get the list of interfaces supported,
214             * Accumulate their names their names in Lexical order
215             * and add them to the hash
216             */
217            if (!cl.isArray()) {
218                /* In 1.2fcs, getInterfaces() was modified to return
219                 * {java.lang.Cloneable, java.io.Serializable} when
220                 * called on array classes.  These values would upset
221                 * the computation of the hash, so we explicitly omit
222                 * them from its computation.
223                 */
224
225                Class interfaces[] = cl.getInterfaces();
226                Arrays.sort(interfaces, compareClassByName);
227
228                for (int i = 0; i < interfaces.length; i++) {
229                    data.writeUTF(interfaces[i].getName());
230                }
231            }
232
233            /* Sort the field names to get a deterministic order */
234            Field[] field = cl.getDeclaredFields();
235            Arrays.sort(field, compareMemberByName);
236
237            for (int i = 0; i < field.length; i++) {
238                Field f = field[i];
239
240                /* Include in the hash all fields except those that are
241                 * private transient and private static.
242                 */
243                int m = f.getModifiers();
244                if (Modifier.isPrivate(m) &&
245                    (Modifier.isTransient(m) || Modifier.isStatic(m)))
246                    continue;
247
248                data.writeUTF(f.getName());
249                data.writeInt(m);
250                data.writeUTF(getSignature(f.getType()));
251            }
252
253            // need to find the java replacement for hasStaticInitializer
254            if (hasStaticInitializer(cl)) {
255                data.writeUTF("<clinit>");
256                data.writeInt(Modifier.STATIC); // TBD: what modifiers does it have
257                data.writeUTF("()V");
258            }
259
260            /*
261             * Get the list of constructors including name and signature
262             * Sort lexically, add all except the private constructors
263             * to the hash with their access flags
264             */
265
266            MethodSignature[] constructors =
267                MethodSignature.removePrivateAndSort(cl.getDeclaredConstructors());
268            for (int i = 0; i < constructors.length; i++) {
269                MethodSignature c = constructors[i];
270                String mname = "<init>";
271                String desc = c.signature;
272                desc = desc.replace('/', '.');
273                data.writeUTF(mname);
274                data.writeInt(c.member.getModifiers());
275                data.writeUTF(desc);
276            }
277
278            /* Include in the hash all methods except those that are
279             * private transient and private static.
280             */
281            MethodSignature[] methods =
282                MethodSignature.removePrivateAndSort(method);
283            for (int i = 0; i < methods.length; i++ ) {
284                MethodSignature m = methods[i];
285                String desc = m.signature;
286                desc = desc.replace('/', '.');
287                data.writeUTF(m.member.getName());
288                data.writeInt(m.member.getModifiers());
289                data.writeUTF(desc);
290            }
291
292            /* Compute the hash value for this class.
293             * Use only the first 64 bits of the hash.
294             */
295            data.flush();
296            byte hasharray[] = md.digest();
297            for (int i = 0; i < Math.min(8, hasharray.length); i++) {
298                h += (long)(hasharray[i] & 255) << (i * 8);
299            }
300        } catch (IOException ignore) {
301            /* can't happen, but be deterministic anyway. */
302            h = -1;
303        } catch (NoSuchAlgorithmException complain) {
304            throw new SecurityException(complain.getMessage());
305        }
306        return h;
307    }
308
309    /*
310     * Comparator object for Classes and Interfaces
311     */
312    private static Comparator compareClassByName =
313        new CompareClassByName();
314
315    private static class CompareClassByName implements Comparator {
316        public int compare(Object o1, Object o2) {
317            Class c1 = (Class)o1;
318            Class c2 = (Class)o2;
319            return (c1.getName()).compareTo(c2.getName());
320        }
321    }
322
323    /*
324     * Comparator object for Members, Fields, and Methods
325     */
326    private static Comparator compareMemberByName =
327        new CompareMemberByName();
328
329    private static class CompareMemberByName implements Comparator {
330        public int compare(Object o1, Object o2) {
331            String s1 = ((Member)o1).getName();
332            String s2 = ((Member)o2).getName();
333
334            if (o1 instanceof Method) {
335                s1 += getSignature((Method)o1);
336                s2 += getSignature((Method)o2);
337            } else if (o1 instanceof Constructor) {
338                s1 += getSignature((Constructor)o1);
339                s2 += getSignature((Constructor)o2);
340            }
341            return s1.compareTo(s2);
342        }
343    }
344
345    /**
346     * Compute the JVM signature for the class.
347     */
348    private static String getSignature(Class clazz) {
349        String type = null;
350        if (clazz.isArray()) {
351            Class cl = clazz;
352            int dimensions = 0;
353            while (cl.isArray()) {
354                dimensions++;
355                cl = cl.getComponentType();
356            }
357            StringBuffer sb = new StringBuffer();
358            for (int i = 0; i < dimensions; i++) {
359                sb.append("[");
360            }
361            sb.append(getSignature(cl));
362            type = sb.toString();
363        } else if (clazz.isPrimitive()) {
364            if (clazz == Integer.TYPE) {
365                type = "I";
366            } else if (clazz == Byte.TYPE) {
367                type = "B";
368            } else if (clazz == Long.TYPE) {
369                type = "J";
370            } else if (clazz == Float.TYPE) {
371                type = "F";
372            } else if (clazz == Double.TYPE) {
373                type = "D";
374            } else if (clazz == Short.TYPE) {
375                type = "S";
376            } else if (clazz == Character.TYPE) {
377                type = "C";
378            } else if (clazz == Boolean.TYPE) {
379                type = "Z";
380            } else if (clazz == Void.TYPE) {
381                type = "V";
382            }
383        } else {
384            type = "L" + clazz.getName().replace('.', '/') + ";";
385        }
386        return type;
387    }
388
389    /*
390     * Compute the JVM method descriptor for the method.
391     */
392    private static String getSignature(Method meth) {
393        StringBuffer sb = new StringBuffer();
394
395        sb.append("(");
396
397        Class[] params = meth.getParameterTypes(); // avoid clone
398        for (int j = 0; j < params.length; j++) {
399            sb.append(getSignature(params[j]));
400        }
401        sb.append(")");
402        sb.append(getSignature(meth.getReturnType()));
403        return sb.toString();
404    }
405
406    /*
407     * Compute the JVM constructor descriptor for the constructor.
408     */
409    private static String getSignature(Constructor cons) {
410        StringBuffer sb = new StringBuffer();
411
412        sb.append("(");
413
414        Class[] params = cons.getParameterTypes(); // avoid clone
415        for (int j = 0; j < params.length; j++) {
416            sb.append(getSignature(params[j]));
417        }
418        sb.append(")V");
419        return sb.toString();
420    }
421
422    private static Field[] getDeclaredFields(final Class clz) {
423        return (Field[]) AccessController.doPrivileged(new PrivilegedAction() {
424            public Object run() {
425                return clz.getDeclaredFields();
426            }
427        });
428    }
429
430    private static class MethodSignature implements Comparator {
431        Member member;
432        String signature;      // cached parameter signature
433
434        /* Given an array of Method or Constructor members,
435           return a sorted array of the non-private members.*/
436        /* A better implementation would be to implement the returned data
437           structure as an insertion sorted link list.*/
438        static MethodSignature[] removePrivateAndSort(Member[] m) {
439            int numNonPrivate = 0;
440            for (int i = 0; i < m.length; i++) {
441                if (! Modifier.isPrivate(m[i].getModifiers())) {
442                    numNonPrivate++;
443                }
444            }
445            MethodSignature[] cm = new MethodSignature[numNonPrivate];
446            int cmi = 0;
447            for (int i = 0; i < m.length; i++) {
448                if (! Modifier.isPrivate(m[i].getModifiers())) {
449                    cm[cmi] = new MethodSignature(m[i]);
450                    cmi++;
451                }
452            }
453            if (cmi > 0)
454                Arrays.sort(cm, cm[0]);
455            return cm;
456        }
457
458        /* Assumes that o1 and o2 are either both methods
459           or both constructors.*/
460        public int compare(Object o1, Object o2) {
461            /* Arrays.sort calls compare when o1 and o2 are equal.*/
462            if (o1 == o2)
463                return 0;
464
465            MethodSignature c1 = (MethodSignature)o1;
466            MethodSignature c2 = (MethodSignature)o2;
467
468            int result;
469            if (isConstructor()) {
470                result = c1.signature.compareTo(c2.signature);
471            } else { // is a Method.
472                result = c1.member.getName().compareTo(c2.member.getName());
473                if (result == 0)
474                    result = c1.signature.compareTo(c2.signature);
475            }
476            return result;
477        }
478
479        final private boolean isConstructor() {
480            return member instanceof Constructor;
481        }
482        private MethodSignature(Member m) {
483            member = m;
484            if (isConstructor()) {
485                signature = ObjectStreamClassUtil_1_3.getSignature((Constructor)m);
486            } else {
487                signature = ObjectStreamClassUtil_1_3.getSignature((Method)m);
488            }
489        }
490    }
491
492    /* Find out if the class has a static class initializer <clinit> */
493    // use java.io.ObjectStream's hasStaticInitializer method
494    // private static native boolean hasStaticInitializer(Class cl);
495
496    private static Method hasStaticInitializerMethod = null;
497    /**
498     * Returns true if the given class defines a static initializer method,
499     * false otherwise.
500     */
501    private static boolean hasStaticInitializer(Class cl) {
502        if (hasStaticInitializerMethod == null) {
503            Class classWithThisMethod = null;
504
505            try {
506                if (classWithThisMethod == null)
507                    classWithThisMethod = java.io.ObjectStreamClass.class;
508
509                hasStaticInitializerMethod =
510                    classWithThisMethod.getDeclaredMethod("hasStaticInitializer",
511                                                          new Class[] { Class.class });
512            } catch (NoSuchMethodException ex) {
513            }
514
515            if (hasStaticInitializerMethod == null) {
516                throw new InternalError("Can't find hasStaticInitializer method on "
517                                        + classWithThisMethod.getName());
518            }
519            hasStaticInitializerMethod.setAccessible(true);
520        }
521        try {
522            Boolean retval = (Boolean)
523                hasStaticInitializerMethod.invoke(null, new Object[] { cl });
524            return retval.booleanValue();
525        } catch (Exception ex) {
526            throw new InternalError("Error invoking hasStaticInitializer: "
527                                    + ex);
528        }
529    }
530
531    private static Method getDeclaredMethod(final Class cl, final String methodName, final Class[] args,
532                                     final int requiredModifierMask,
533                                     final int disallowedModifierMask) {
534        return (Method) AccessController.doPrivileged(new PrivilegedAction() {
535            public Object run() {
536                Method method = null;
537                try {
538                    method =
539                        cl.getDeclaredMethod(methodName, args);
540                        int mods = method.getModifiers();
541                        if ((mods & disallowedModifierMask) != 0 ||
542                            (mods & requiredModifierMask) != requiredModifierMask) {
543                            method = null;
544                        }
545                        //if (!Modifier.isPrivate(mods) ||
546                        //    Modifier.isStatic(mods)) {
547                        //    method = null;
548                        //}
549                } catch (NoSuchMethodException e) {
550                // Since it is alright if methodName does not exist,
551                // no need to do anything special here.
552                }
553                return method;
554            }
555        });
556    }
557
558}
559