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 
  26 package com.sun.corba.se.impl.orbutil;
  27 
  28 // for computing the structural UID
  29 import java.security.MessageDigest;
  30 import java.security.NoSuchAlgorithmException;
  31 import java.security.DigestOutputStream;
  32 import java.security.AccessController;
  33 import java.security.PrivilegedExceptionAction;
  34 import java.security.PrivilegedActionException;
  35 import java.security.PrivilegedAction;
  36 import java.io.DataOutputStream;
  37 import java.io.ByteArrayOutputStream;
  38 import java.io.IOException;
  39 
  40 import java.util.Arrays;
  41 import java.util.Comparator;
  42 import java.lang.reflect.Field;
  43 import java.lang.reflect.Modifier;
  44 import java.lang.reflect.Array;
  45 import java.lang.reflect.Member;
  46 import java.lang.reflect.Method;
  47 import java.lang.reflect.Constructor;
  48 
  49 
  50 import com.sun.corba.se.impl.io.ObjectStreamClass;
  51 
  52 public 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 }