1 /* ObjectStreamClass.java -- Class used to write class information
2    about serialized objects.
3    Copyright (C) 1998, 1999, 2000, 2001, 2003, 2005  Free Software Foundation, Inc.
4 
5 This file is part of GNU Classpath.
6 
7 GNU Classpath is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2, or (at your option)
10 any later version.
11 
12 GNU Classpath is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 General Public License for more details.
16 
17 You should have received a copy of the GNU General Public License
18 along with GNU Classpath; see the file COPYING.  If not, write to the
19 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20 02110-1301 USA.
21 
22 Linking this library statically or dynamically with other modules is
23 making a combined work based on this library.  Thus, the terms and
24 conditions of the GNU General Public License cover the whole
25 combination.
26 
27 As a special exception, the copyright holders of this library give you
28 permission to link this library with independent modules to produce an
29 executable, regardless of the license terms of these independent
30 modules, and to copy and distribute the resulting executable under
31 terms of your choice, provided that you also meet, for each linked
32 independent module, the terms and conditions of the license of that
33 module.  An independent module is a module which is not derived from
34 or based on this library.  If you modify this library, you may extend
35 this exception to your version of the library, but you are not
36 obligated to do so.  If you do not wish to do so, delete this
37 exception statement from your version. */
38 
39 
40 package java.io;
41 
42 import gnu.java.io.NullOutputStream;
43 import gnu.java.lang.reflect.TypeSignature;
44 import gnu.java.security.action.SetAccessibleAction;
45 import gnu.java.security.provider.Gnu;
46 
47 import java.lang.reflect.Constructor;
48 import java.lang.reflect.Field;
49 import java.lang.reflect.Member;
50 import java.lang.reflect.Method;
51 import java.lang.reflect.Modifier;
52 import java.lang.reflect.Proxy;
53 import java.security.AccessController;
54 import java.security.DigestOutputStream;
55 import java.security.MessageDigest;
56 import java.security.NoSuchAlgorithmException;
57 import java.security.PrivilegedAction;
58 import java.security.Security;
59 import java.util.Arrays;
60 import java.util.Comparator;
61 import java.util.Hashtable;
62 
63 /**
64  * @author Tom Tromey (tromey@redhat.com)
65  * @author Jeroen Frijters (jeroen@frijters.net)
66  * @author Guilhem Lavaux (guilhem@kaffe.org)
67  * @author Michael Koch (konqueror@gmx.de)
68  * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
69  */
70 public class ObjectStreamClass implements Serializable
71 {
72   static final ObjectStreamField[] INVALID_FIELDS = new ObjectStreamField[0];
73 
74   /**
75    * Returns the <code>ObjectStreamClass</code> for <code>cl</code>.
76    * If <code>cl</code> is null, or is not <code>Serializable</code>,
77    * null is returned.  <code>ObjectStreamClass</code>'s are memorized;
78    * later calls to this method with the same class will return the
79    * same <code>ObjectStreamClass</code> object and no recalculation
80    * will be done.
81    *
82    * Warning: If this class contains an invalid serialPersistentField arrays
83    * lookup will not throw anything. However {@link #getFields()} will return
84    * an empty array and {@link java.io.ObjectOutputStream#writeObject} will throw an
85    * {@link java.io.InvalidClassException}.
86    *
87    * @see java.io.Serializable
88    */
lookup(Class<?> cl)89   public static ObjectStreamClass lookup(Class<?> cl)
90   {
91     if (cl == null)
92       return null;
93     if (! (Serializable.class).isAssignableFrom(cl))
94       return null;
95 
96     return lookupForClassObject(cl);
97   }
98 
99   /**
100    * This lookup for internal use by ObjectOutputStream.  Suppose
101    * we have a java.lang.Class object C for class A, though A is not
102    * serializable, but it's okay to serialize C.
103    */
lookupForClassObject(Class cl)104   static ObjectStreamClass lookupForClassObject(Class cl)
105   {
106     if (cl == null)
107       return null;
108 
109     ObjectStreamClass osc = classLookupTable.get(cl);
110 
111     if (osc != null)
112       return osc;
113     else
114       {
115         osc = new ObjectStreamClass(cl);
116         classLookupTable.put(cl, osc);
117         return osc;
118       }
119   }
120 
121   /**
122    * Returns the name of the class that this
123    * <code>ObjectStreamClass</code> represents.
124    *
125    * @return the name of the class.
126    */
getName()127   public String getName()
128   {
129     return name;
130   }
131 
132   /**
133    * Returns the class that this <code>ObjectStreamClass</code>
134    * represents.  Null could be returned if this
135    * <code>ObjectStreamClass</code> was read from an
136    * <code>ObjectInputStream</code> and the class it represents cannot
137    * be found or loaded.
138    *
139    * @see java.io.ObjectInputStream
140    */
forClass()141   public Class<?> forClass()
142   {
143     return clazz;
144   }
145 
146   /**
147    * Returns the serial version stream-unique identifier for the class
148    * represented by this <code>ObjectStreamClass</code>.  This SUID is
149    * either defined by the class as <code>static final long
150    * serialVersionUID</code> or is calculated as specified in
151    * Javasoft's "Object Serialization Specification" XXX: add reference
152    *
153    * @return the serial version UID.
154    */
getSerialVersionUID()155   public long getSerialVersionUID()
156   {
157     return uid;
158   }
159 
160   /**
161    * Returns the serializable (non-static and non-transient) Fields
162    * of the class represented by this ObjectStreamClass.  The Fields
163    * are sorted by name.
164    * If fields were obtained using serialPersistentFields and this array
165    * is faulty then the returned array of this method will be empty.
166    *
167    * @return the fields.
168    */
getFields()169   public ObjectStreamField[] getFields()
170   {
171     ObjectStreamField[] copy = new ObjectStreamField[ fields.length ];
172     System.arraycopy(fields, 0, copy, 0, fields.length);
173     return copy;
174   }
175 
176   // XXX doc
177   // Can't do binary search since fields is sorted by name and
178   // primitiveness.
getField(String name)179   public ObjectStreamField getField (String name)
180   {
181     for (int i = 0; i < fields.length; i++)
182       if (fields[i].getName().equals(name))
183         return fields[i];
184     return null;
185   }
186 
187   /**
188    * Returns a textual representation of this
189    * <code>ObjectStreamClass</code> object including the name of the
190    * class it represents as well as that class's serial version
191    * stream-unique identifier.
192    *
193    * @see #getSerialVersionUID()
194    * @see #getName()
195    */
toString()196   public String toString()
197   {
198     return "java.io.ObjectStreamClass< " + name + ", " + uid + " >";
199   }
200 
201   // Returns true iff the class that this ObjectStreamClass represents
202   // has the following method:
203   //
204   // private void writeObject (ObjectOutputStream)
205   //
206   // This method is used by the class to override default
207   // serialization behavior.
hasWriteMethod()208   boolean hasWriteMethod()
209   {
210     return (flags & ObjectStreamConstants.SC_WRITE_METHOD) != 0;
211   }
212 
213   // Returns true iff the class that this ObjectStreamClass represents
214   // implements Serializable but does *not* implement Externalizable.
isSerializable()215   boolean isSerializable()
216   {
217     return (flags & ObjectStreamConstants.SC_SERIALIZABLE) != 0;
218   }
219 
220 
221   // Returns true iff the class that this ObjectStreamClass represents
222   // implements Externalizable.
isExternalizable()223   boolean isExternalizable()
224   {
225     return (flags & ObjectStreamConstants.SC_EXTERNALIZABLE) != 0;
226   }
227 
228   // Returns true iff the class that this ObjectStreamClass represents
229   // implements Externalizable.
isEnum()230   boolean isEnum()
231   {
232     return (flags & ObjectStreamConstants.SC_ENUM) != 0;
233   }
234 
235   // Returns the <code>ObjectStreamClass</code> that represents the
236   // class that is the superclass of the class this
237   // <code>ObjectStreamClass</code> represents.  If the superclass is
238   // not Serializable, null is returned.
getSuper()239   ObjectStreamClass getSuper()
240   {
241     return superClass;
242   }
243 
244   /**
245    * returns an array of ObjectStreamClasses that represent the super
246    * classes of the class represented by this and the class
247    * represented by this itself in order from most super to this.
248    * ObjectStreamClass[0] is the highest superclass of this that is
249    * serializable.
250    *
251    * The result of consecutive calls this hierarchy() will be the same
252    * array instance.
253    *
254    * @return an array of ObjectStreamClass representing the
255    * super-class hierarchy of serializable classes.
256    */
hierarchy()257   ObjectStreamClass[] hierarchy()
258   {
259     ObjectStreamClass[] result = hierarchy;
260     if (result == null)
261         {
262         int d = 0;
263 
264         for(ObjectStreamClass osc = this; osc != null; osc = osc.getSuper())
265           d++;
266 
267         result = new ObjectStreamClass[d];
268 
269         for (ObjectStreamClass osc = this; osc != null; osc = osc.getSuper())
270           {
271             result[--d] = osc;
272           }
273 
274         hierarchy = result;
275       }
276     return result;
277   }
278 
279   /**
280    * Cache for hierarchy() result.
281    */
282   private ObjectStreamClass[] hierarchy = null;
283 
284   // Returns an integer that consists of bit-flags that indicate
285   // properties of the class represented by this ObjectStreamClass.
286   // The bit-flags that could be present are those defined in
287   // ObjectStreamConstants that begin with `SC_'
getFlags()288   int getFlags()
289   {
290     return flags;
291   }
292 
293 
ObjectStreamClass(String name, long uid, byte flags, ObjectStreamField[] fields)294   ObjectStreamClass(String name, long uid, byte flags,
295                     ObjectStreamField[] fields)
296   {
297     this.name = name;
298     this.uid = uid;
299     this.flags = flags;
300     this.fields = fields;
301   }
302 
303   /**
304    * This method builds the internal description corresponding to a Java Class.
305    * As the constructor only assign a name to the current ObjectStreamClass instance,
306    * that method sets the serial UID, chose the fields which will be serialized,
307    * and compute the position of the fields in the serialized stream.
308    *
309    * @param cl The Java class which is used as a reference for building the descriptor.
310    * @param superClass The descriptor of the super class for this class descriptor.
311    * @throws InvalidClassException if an incompatibility between computed UID and
312    * already set UID is found.
313    */
setClass(Class cl, ObjectStreamClass superClass)314   void setClass(Class cl, ObjectStreamClass superClass) throws InvalidClassException
315   {hierarchy = null;
316     this.clazz = cl;
317 
318     cacheMethods();
319 
320     long class_uid = getClassUID(cl);
321     if (uid == 0)
322       uid = class_uid;
323     else
324       {
325         // Check that the actual UID of the resolved class matches the UID from
326         // the stream. Mismatches for array classes are ignored.
327         if (!cl.isArray() && uid != class_uid)
328           {
329             String msg = cl +
330               ": Local class not compatible: stream serialVersionUID="
331               + uid + ", local serialVersionUID=" + class_uid;
332             throw new InvalidClassException (msg);
333           }
334       }
335 
336     isProxyClass = clazz != null && Proxy.isProxyClass(clazz);
337     this.superClass = superClass;
338     calculateOffsets();
339 
340     try
341       {
342         ObjectStreamField[] exportedFields = getSerialPersistentFields (clazz);
343 
344         if (exportedFields == null)
345           return;
346 
347         ObjectStreamField[] newFieldList = new ObjectStreamField[exportedFields.length + fields.length];
348         int i, j, k;
349 
350         /* We now check the import fields against the exported fields.
351          * There should not be contradiction (e.g. int x and String x)
352          * but extra virtual fields can be added to the class.
353          */
354 
355         Arrays.sort(exportedFields);
356 
357         i = 0; j = 0; k = 0;
358         while (i < fields.length && j < exportedFields.length)
359           {
360             int comp = fields[i].compareTo(exportedFields[j]);
361 
362             if (comp < 0)
363               {
364                 newFieldList[k] = fields[i];
365                 fields[i].setPersistent(false);
366                 fields[i].setToSet(false);
367                 i++;
368               }
369             else if (comp > 0)
370               {
371                 /* field not found in imported fields. We add it
372                  * in the list of supported fields.
373                  */
374                 newFieldList[k] = exportedFields[j];
375                 newFieldList[k].setPersistent(true);
376                 newFieldList[k].setToSet(false);
377                 try
378                   {
379                     newFieldList[k].lookupField(clazz);
380                     newFieldList[k].checkFieldType();
381                   }
382                 catch (NoSuchFieldException _)
383                   {
384                   }
385                 j++;
386               }
387             else
388               {
389                 try
390                   {
391                     exportedFields[j].lookupField(clazz);
392                     exportedFields[j].checkFieldType();
393                   }
394                 catch (NoSuchFieldException _)
395                   {
396                   }
397 
398                 if (!fields[i].getType().equals(exportedFields[j].getType()))
399                   throw new InvalidClassException
400                     ("serialPersistentFields must be compatible with" +
401                      " imported fields (about " + fields[i].getName() + ")");
402                 newFieldList[k] = fields[i];
403                 fields[i].setPersistent(true);
404                 i++;
405                 j++;
406               }
407             k++;
408           }
409 
410         if (i < fields.length)
411           for (;i<fields.length;i++,k++)
412             {
413               fields[i].setPersistent(false);
414               fields[i].setToSet(false);
415               newFieldList[k] = fields[i];
416             }
417         else
418           if (j < exportedFields.length)
419             for (;j<exportedFields.length;j++,k++)
420               {
421                 exportedFields[j].setPersistent(true);
422                 exportedFields[j].setToSet(false);
423                 newFieldList[k] = exportedFields[j];
424               }
425 
426         fields = new ObjectStreamField[k];
427         System.arraycopy(newFieldList, 0, fields, 0, k);
428       }
429     catch (NoSuchFieldException ignore)
430       {
431         return;
432       }
433     catch (IllegalAccessException ignore)
434       {
435         return;
436       }
437   }
438 
setSuperclass(ObjectStreamClass osc)439   void setSuperclass (ObjectStreamClass osc)
440   {
441     superClass = osc;
442     hierarchy = null;
443   }
444 
calculateOffsets()445   void calculateOffsets()
446   {
447     int i;
448     ObjectStreamField field;
449     primFieldSize = 0;
450     int fcount = fields.length;
451     for (i = 0; i < fcount; ++ i)
452       {
453         field = fields[i];
454 
455         if (! field.isPrimitive())
456           break;
457 
458         field.setOffset(primFieldSize);
459         switch (field.getTypeCode())
460           {
461           case 'B':
462           case 'Z':
463             ++ primFieldSize;
464             break;
465           case 'C':
466           case 'S':
467             primFieldSize += 2;
468             break;
469           case 'I':
470           case 'F':
471             primFieldSize += 4;
472             break;
473           case 'D':
474           case 'J':
475             primFieldSize += 8;
476             break;
477           }
478       }
479 
480     for (objectFieldCount = 0; i < fcount; ++ i)
481       fields[i].setOffset(objectFieldCount++);
482   }
483 
findMethod(Method[] methods, String name, Class[] params, Class returnType, boolean mustBePrivate)484   private Method findMethod(Method[] methods, String name, Class[] params,
485                             Class returnType, boolean mustBePrivate)
486   {
487 outer:
488     for (int i = 0; i < methods.length; i++)
489     {
490         final Method m = methods[i];
491         int mods = m.getModifiers();
492         if (Modifier.isStatic(mods)
493             || (mustBePrivate && !Modifier.isPrivate(mods)))
494         {
495             continue;
496         }
497 
498         if (m.getName().equals(name)
499            && m.getReturnType() == returnType)
500         {
501             Class[] mp = m.getParameterTypes();
502             if (mp.length == params.length)
503             {
504                 for (int j = 0; j < mp.length; j++)
505                 {
506                     if (mp[j] != params[j])
507                     {
508                         continue outer;
509                     }
510                 }
511                 AccessController.doPrivileged(new SetAccessibleAction(m));
512                 return m;
513             }
514         }
515     }
516     return null;
517   }
518 
inSamePackage(Class c1, Class c2)519   private static boolean inSamePackage(Class c1, Class c2)
520   {
521     String name1 = c1.getName();
522     String name2 = c2.getName();
523 
524     int id1 = name1.lastIndexOf('.');
525     int id2 = name2.lastIndexOf('.');
526 
527     // Handle the default package
528     if (id1 == -1 || id2 == -1)
529       return id1 == id2;
530 
531     String package1 = name1.substring(0, id1);
532     String package2 = name2.substring(0, id2);
533 
534     return package1.equals(package2);
535   }
536 
537   final static Class[] noArgs = new Class[0];
538 
findAccessibleMethod(String name, Class from)539   private static Method findAccessibleMethod(String name, Class from)
540   {
541     for (Class c = from; c != null; c = c.getSuperclass())
542       {
543         try
544           {
545             Method res = c.getDeclaredMethod(name, noArgs);
546             int mods = res.getModifiers();
547 
548             if (c == from
549                 || Modifier.isProtected(mods)
550                 || Modifier.isPublic(mods)
551                 || (! Modifier.isPrivate(mods) && inSamePackage(c, from)))
552               {
553                 AccessController.doPrivileged(new SetAccessibleAction(res));
554                 return res;
555               }
556           }
557         catch (NoSuchMethodException e)
558           {
559           }
560       }
561 
562     return null;
563   }
564 
565   /**
566    * Helper routine to check if a class was loaded by boot or
567    * application class loader.  Classes for which this is not the case
568    * should not be cached since caching prevent class file garbage
569    * collection.
570    *
571    * @param cl a class
572    *
573    * @return true if cl was loaded by boot or application class loader,
574    *         false if cl was loaded by a user class loader.
575    */
loadedByBootOrApplicationClassLoader(Class cl)576   private static boolean loadedByBootOrApplicationClassLoader(Class cl)
577   {
578     ClassLoader l = cl.getClassLoader();
579     return
580       (   l == null                             /* boot loader */       )
581       || (l == ClassLoader.getSystemClassLoader() /* application loader */);
582   }
583 
584   static Hashtable methodCache = new Hashtable();
585 
586   static final Class[] readObjectSignature  = { ObjectInputStream.class };
587   static final Class[] writeObjectSignature = { ObjectOutputStream.class };
588 
cacheMethods()589   private void cacheMethods()
590   {
591     Class cl = forClass();
592     Method[] cached = (Method[]) methodCache.get(cl);
593     if (cached == null)
594       {
595         cached = new Method[4];
596         Method[] methods = cl.getDeclaredMethods();
597 
598         cached[0] = findMethod(methods, "readObject",
599                                readObjectSignature,
600                                Void.TYPE, true);
601         cached[1] = findMethod(methods, "writeObject",
602                                writeObjectSignature,
603                                Void.TYPE, true);
604 
605         // readResolve and writeReplace can be in parent classes, as long as they
606         // are accessible from this class.
607         cached[2] = findAccessibleMethod("readResolve", cl);
608         cached[3] = findAccessibleMethod("writeReplace", cl);
609 
610         /* put in cache if classes not loaded by user class loader.
611          * For a user class loader, the cache may otherwise grow
612          * without limit.
613          */
614         if (loadedByBootOrApplicationClassLoader(cl))
615           methodCache.put(cl,cached);
616       }
617     readObjectMethod   = cached[0];
618     writeObjectMethod  = cached[1];
619     readResolveMethod  = cached[2];
620     writeReplaceMethod = cached[3];
621   }
622 
ObjectStreamClass(Class cl)623   private ObjectStreamClass(Class cl)
624   {
625     uid = 0;
626     flags = 0;
627     isProxyClass = Proxy.isProxyClass(cl);
628 
629     clazz = cl;
630     cacheMethods();
631     name = cl.getName();
632     setFlags(cl);
633     setFields(cl);
634     // to those class nonserializable, its uid field is 0
635     if ( (Serializable.class).isAssignableFrom(cl) && !isProxyClass)
636       uid = getClassUID(cl);
637     superClass = lookup(cl.getSuperclass());
638   }
639 
640 
641   // Sets bits in flags according to features of CL.
setFlags(Class cl)642   private void setFlags(Class cl)
643   {
644     if ((java.io.Externalizable.class).isAssignableFrom(cl))
645       flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
646     else if ((java.io.Serializable.class).isAssignableFrom(cl))
647       // only set this bit if CL is NOT Externalizable
648       flags |= ObjectStreamConstants.SC_SERIALIZABLE;
649 
650     if (writeObjectMethod != null)
651       flags |= ObjectStreamConstants.SC_WRITE_METHOD;
652 
653     if (cl.isEnum() || cl == Enum.class)
654       flags |= ObjectStreamConstants.SC_ENUM;
655   }
656 
657 /* GCJ LOCAL */
658   // FIXME: This is a workaround for a fairly obscure bug that happens
659   // when reading a Proxy and then writing it back out again.  The
660   // result is that the ObjectStreamClass doesn't have its fields set,
661   // generating a NullPointerException.  Rather than this kludge we
662   // should probably fix the real bug, but it would require a fairly
663   // radical reorganization to do so.
ensureFieldsSet(Class cl)664   final void ensureFieldsSet(Class cl)
665   {
666     if (! fieldsSet)
667       setFields(cl);
668   }
669 /* END GCJ LOCAL */
670 
671 
672   // Sets fields to be a sorted array of the serializable fields of
673   // clazz.
setFields(Class cl)674   private void setFields(Class cl)
675   {
676 /* GCJ LOCAL */
677     fieldsSet = true;
678 /* END GCJ LOCAL */
679 
680     SetAccessibleAction setAccessible = new SetAccessibleAction();
681 
682     if (!isSerializable() || isExternalizable() || isEnum())
683       {
684         fields = NO_FIELDS;
685         return;
686       }
687 
688     try
689       {
690         final Field f =
691           cl.getDeclaredField("serialPersistentFields");
692         setAccessible.setMember(f);
693         AccessController.doPrivileged(setAccessible);
694         int modifiers = f.getModifiers();
695 
696         if (Modifier.isStatic(modifiers)
697             && Modifier.isFinal(modifiers)
698             && Modifier.isPrivate(modifiers))
699           {
700             fields = getSerialPersistentFields(cl);
701             if (fields != null)
702               {
703                 ObjectStreamField[] fieldsName = new ObjectStreamField[fields.length];
704                 System.arraycopy(fields, 0, fieldsName, 0, fields.length);
705 
706                 Arrays.sort (fieldsName, new Comparator() {
707                         public int compare(Object o1, Object o2)
708                         {
709                           ObjectStreamField f1 = (ObjectStreamField)o1;
710                           ObjectStreamField f2 = (ObjectStreamField)o2;
711 
712                           return f1.getName().compareTo(f2.getName());
713                         }
714                     });
715 
716                 for (int i=1; i < fields.length; i++)
717                   {
718                     if (fieldsName[i-1].getName().equals(fieldsName[i].getName()))
719                         {
720                             fields = INVALID_FIELDS;
721                             return;
722                         }
723                   }
724 
725                 Arrays.sort (fields);
726                 // Retrieve field reference.
727                 for (int i=0; i < fields.length; i++)
728                   {
729                     try
730                       {
731                         fields[i].lookupField(cl);
732                       }
733                     catch (NoSuchFieldException _)
734                       {
735                         fields[i].setToSet(false);
736                       }
737                   }
738 
739                 calculateOffsets();
740                 return;
741               }
742           }
743       }
744     catch (NoSuchFieldException ignore)
745       {
746       }
747     catch (IllegalAccessException ignore)
748       {
749       }
750 
751     int num_good_fields = 0;
752     Field[] all_fields = cl.getDeclaredFields();
753 
754     int modifiers;
755     // set non-serializable fields to null in all_fields
756     for (int i = 0; i < all_fields.length; i++)
757       {
758         modifiers = all_fields[i].getModifiers();
759         if (Modifier.isTransient(modifiers)
760             || Modifier.isStatic(modifiers))
761           all_fields[i] = null;
762         else
763           num_good_fields++;
764       }
765 
766     // make a copy of serializable (non-null) fields
767     fields = new ObjectStreamField[ num_good_fields ];
768     for (int from = 0, to = 0; from < all_fields.length; from++)
769       if (all_fields[from] != null)
770         {
771           final Field f = all_fields[from];
772           setAccessible.setMember(f);
773           AccessController.doPrivileged(setAccessible);
774           fields[to] = new ObjectStreamField(all_fields[from]);
775           to++;
776         }
777 
778     Arrays.sort(fields);
779     // Make sure we don't have any duplicate field names
780     // (Sun JDK 1.4.1. throws an Internal Error as well)
781     for (int i = 1; i < fields.length; i++)
782       {
783         if(fields[i - 1].getName().equals(fields[i].getName()))
784             throw new InternalError("Duplicate field " +
785                         fields[i].getName() + " in class " + cl.getName());
786       }
787     calculateOffsets();
788   }
789 
790   static Hashtable uidCache = new Hashtable();
791 
792   // Returns the serial version UID defined by class, or if that
793   // isn't present, calculates value of serial version UID.
getClassUID(Class cl)794   private long getClassUID(Class cl)
795   {
796     long result = 0;
797     Long cache = (Long) uidCache.get(cl);
798     if (cache != null)
799       result = cache.longValue();
800     else
801       {
802         // Note that we can't use Class.isEnum() here, because that returns
803         // false for java.lang.Enum and enum value sub classes.
804         if (Enum.class.isAssignableFrom(cl) || Proxy.isProxyClass(cl))
805           {
806             // Spec says that enums and dynamic proxies have
807             // a serialVersionUID of 0L.
808             return 0L;
809           }
810         try
811           {
812             result = getClassUIDFromField(cl);
813           }
814         catch (NoSuchFieldException ignore)
815           {
816             try
817               {
818                 result = calculateClassUID(cl);
819               }
820             catch (NoSuchAlgorithmException e)
821               {
822                 throw new RuntimeException
823                   ("The SHA algorithm was not found to use in computing the Serial Version UID for class "
824                    + cl.getName(), e);
825               }
826             catch (IOException ioe)
827               {
828                 throw new RuntimeException(ioe);
829               }
830           }
831 
832         if (loadedByBootOrApplicationClassLoader(cl))
833           uidCache.put(cl,Long.valueOf(result));
834       }
835     return result;
836   }
837 
838   /**
839    * Search for a serialVersionUID field in the given class and read
840    * its value.
841    *
842    * @return the contents of the serialVersionUID field
843    *
844    * @throws NoSuchFieldException if such a field does not exist or is
845    * not static, not final, not of type Long or not accessible.
846    */
getClassUIDFromField(Class cl)847   long getClassUIDFromField(Class cl)
848     throws NoSuchFieldException
849   {
850     long result;
851 
852     try
853       {
854         // Use getDeclaredField rather than getField, since serialVersionUID
855         // may not be public AND we only want the serialVersionUID of this
856         // class, not a superclass or interface.
857         final Field suid = cl.getDeclaredField("serialVersionUID");
858         SetAccessibleAction setAccessible = new SetAccessibleAction(suid);
859         AccessController.doPrivileged(setAccessible);
860         int modifiers = suid.getModifiers();
861 
862         if (Modifier.isStatic(modifiers)
863             && Modifier.isFinal(modifiers)
864             && suid.getType() == Long.TYPE)
865           result = suid.getLong(null);
866         else
867           throw new NoSuchFieldException();
868       }
869     catch (IllegalAccessException ignore)
870       {
871         throw new NoSuchFieldException();
872       }
873 
874     return result;
875   }
876 
877   /**
878    * Calculate class serial version UID for a class that does not
879    * define serialVersionUID:
880    *
881    * @param cl a class
882    *
883    * @return the calculated serial varsion UID.
884    *
885    * @throws NoSuchAlgorithmException if SHA algorithm not found
886    *
887    * @throws IOException if writing to the DigestOutputStream causes
888    * an IOException.
889    */
calculateClassUID(Class cl)890   long calculateClassUID(Class cl)
891     throws NoSuchAlgorithmException, IOException
892   {
893     long result;
894     MessageDigest md;
895     try
896       {
897         md = MessageDigest.getInstance("SHA");
898       }
899     catch (NoSuchAlgorithmException e)
900       {
901         // If a provider already provides SHA, use it; otherwise, use this.
902         Gnu gnuProvider = new Gnu();
903         Security.addProvider(gnuProvider);
904         md = MessageDigest.getInstance("SHA");
905       }
906 
907     DigestOutputStream digest_out =
908       new DigestOutputStream(nullOutputStream, md);
909     DataOutputStream data_out = new DataOutputStream(digest_out);
910 
911     data_out.writeUTF(cl.getName());
912 
913     int modifiers = cl.getModifiers();
914     // just look at interesting bits
915     modifiers = modifiers & (Modifier.ABSTRACT | Modifier.FINAL
916                              | Modifier.INTERFACE | Modifier.PUBLIC);
917     data_out.writeInt(modifiers);
918 
919     // Pretend that an array has no interfaces, because when array
920     // serialization was defined (JDK 1.1), arrays didn't have it.
921     if (! cl.isArray())
922       {
923         Class[] interfaces = cl.getInterfaces();
924         Arrays.sort(interfaces, interfaceComparator);
925         for (int i = 0; i < interfaces.length; i++)
926           data_out.writeUTF(interfaces[i].getName());
927       }
928 
929     Field field;
930     Field[] fields = cl.getDeclaredFields();
931     Arrays.sort(fields, memberComparator);
932     for (int i = 0; i < fields.length; i++)
933       {
934         field = fields[i];
935         modifiers = field.getModifiers();
936         if (Modifier.isPrivate(modifiers)
937             && (Modifier.isStatic(modifiers)
938                 || Modifier.isTransient(modifiers)))
939           continue;
940 
941         data_out.writeUTF(field.getName());
942         data_out.writeInt(modifiers);
943         data_out.writeUTF(TypeSignature.getEncodingOfClass (field.getType()));
944       }
945 
946     // write class initializer method if present
947     if (VMObjectStreamClass.hasClassInitializer(cl))
948       {
949         data_out.writeUTF("<clinit>");
950         data_out.writeInt(Modifier.STATIC);
951         data_out.writeUTF("()V");
952       }
953 
954     Constructor constructor;
955     Constructor[] constructors = cl.getDeclaredConstructors();
956     Arrays.sort (constructors, memberComparator);
957     for (int i = 0; i < constructors.length; i++)
958       {
959         constructor = constructors[i];
960         modifiers = constructor.getModifiers();
961         if (Modifier.isPrivate(modifiers))
962           continue;
963 
964         data_out.writeUTF("<init>");
965         data_out.writeInt(modifiers);
966 
967         // the replacement of '/' with '.' was needed to make computed
968         // SUID's agree with those computed by JDK
969         data_out.writeUTF
970           (TypeSignature.getEncodingOfConstructor(constructor).replace('/','.'));
971       }
972 
973     Method method;
974     Method[] methods = cl.getDeclaredMethods();
975     Arrays.sort(methods, memberComparator);
976     for (int i = 0; i < methods.length; i++)
977       {
978         method = methods[i];
979         modifiers = method.getModifiers();
980         if (Modifier.isPrivate(modifiers))
981           continue;
982 
983         data_out.writeUTF(method.getName());
984         data_out.writeInt(modifiers);
985 
986         // the replacement of '/' with '.' was needed to make computed
987         // SUID's agree with those computed by JDK
988         data_out.writeUTF
989           (TypeSignature.getEncodingOfMethod(method).replace('/', '.'));
990       }
991 
992     data_out.close();
993     byte[] sha = md.digest();
994     result = 0;
995     int len = sha.length < 8 ? sha.length : 8;
996     for (int i = 0; i < len; i++)
997       result += (long) (sha[i] & 0xFF) << (8 * i);
998 
999     return result;
1000   }
1001 
1002   /**
1003    * Returns the value of CLAZZ's private static final field named
1004    * `serialPersistentFields'. It performs some sanity checks before
1005    * returning the real array. Besides, the returned array is a clean
1006    * copy of the original. So it can be modified.
1007    *
1008    * @param clazz Class to retrieve 'serialPersistentFields' from.
1009    * @return The content of 'serialPersistentFields'.
1010    */
1011   private ObjectStreamField[] getSerialPersistentFields(Class clazz)
1012     throws NoSuchFieldException, IllegalAccessException
1013   {
1014     ObjectStreamField[] fieldsArray = null;
1015     ObjectStreamField[] o;
1016 
1017     // Use getDeclaredField rather than getField for the same reason
1018     // as above in getDefinedSUID.
1019     Field f = clazz.getDeclaredField("serialPersistentFields");
1020     f.setAccessible(true);
1021 
1022     int modifiers = f.getModifiers();
1023     if (!(Modifier.isStatic(modifiers) &&
1024           Modifier.isFinal(modifiers) &&
1025           Modifier.isPrivate(modifiers)))
1026       return null;
1027 
1028     o = (ObjectStreamField[]) f.get(null);
1029 
1030     if (o == null)
1031       return null;
1032 
1033     fieldsArray = new ObjectStreamField[ o.length ];
1034     System.arraycopy(o, 0, fieldsArray, 0, o.length);
1035 
1036     return fieldsArray;
1037   }
1038 
1039   /**
1040    * Returns a new instance of the Class this ObjectStreamClass corresponds
1041    * to.
1042    * Note that this should only be used for Externalizable classes.
1043    *
1044    * @return A new instance.
1045    */
1046   Externalizable newInstance() throws InvalidClassException
1047   {
1048     synchronized(this)
1049     {
1050         if (constructor == null)
1051         {
1052             try
1053             {
1054                 final Constructor c = clazz.getConstructor(new Class[0]);
1055 
1056                 AccessController.doPrivileged(new PrivilegedAction()
1057                 {
1058                     public Object run()
1059                     {
1060                         c.setAccessible(true);
1061                         return null;
1062                     }
1063                 });
1064 
1065                 constructor = c;
1066             }
1067             catch(NoSuchMethodException x)
1068             {
1069                 throw new InvalidClassException(clazz.getName(),
1070                     "No public zero-argument constructor");
1071             }
1072         }
1073     }
1074 
1075     try
1076     {
1077         return (Externalizable)constructor.newInstance();
1078     }
1079     catch(Exception x)
1080     {
1081         throw (InvalidClassException)
1082             new InvalidClassException(clazz.getName(),
1083                      "Unable to instantiate").initCause(x);
1084     }
1085   }
1086 
1087   public static final ObjectStreamField[] NO_FIELDS = {};
1088 
1089   private static Hashtable<Class,ObjectStreamClass> classLookupTable
1090     = new Hashtable<Class,ObjectStreamClass>();
1091   private static final NullOutputStream nullOutputStream = new NullOutputStream();
1092   private static final Comparator interfaceComparator = new InterfaceComparator();
1093   private static final Comparator memberComparator = new MemberComparator();
1094   private static final
1095     Class[] writeMethodArgTypes = { java.io.ObjectOutputStream.class };
1096 
1097   private ObjectStreamClass superClass;
1098   private Class<?> clazz;
1099   private String name;
1100   private long uid;
1101   private byte flags;
1102 
1103   // this field is package protected so that ObjectInputStream and
1104   // ObjectOutputStream can access it directly
1105   ObjectStreamField[] fields;
1106 
1107   // these are accessed by ObjectIn/OutputStream
1108   int primFieldSize = -1;  // -1 if not yet calculated
1109   int objectFieldCount;
1110 
1111   Method readObjectMethod;
1112   Method readResolveMethod;
1113   Method writeReplaceMethod;
1114   Method writeObjectMethod;
1115   boolean realClassIsSerializable;
1116   boolean realClassIsExternalizable;
1117   ObjectStreamField[] fieldMapping;
1118   Constructor firstNonSerializableParentConstructor;
1119   private Constructor constructor;  // default constructor for Externalizable
1120 
1121   boolean isProxyClass = false;
1122 
1123 /* GCJ LOCAL */
1124   // True after setFields() has been called
1125   private boolean fieldsSet = false;
1126 /* END GCJ LOCAL */
1127 
1128   // This is probably not necessary because this class is special cased already
1129   // but it will avoid showing up as a discrepancy when comparing SUIDs.
1130   private static final long serialVersionUID = -6120832682080437368L;
1131 
1132 
1133   // interfaces are compared only by name
1134   private static final class InterfaceComparator implements Comparator
1135   {
1136     public int compare(Object o1, Object o2)
1137     {
1138       return ((Class) o1).getName().compareTo(((Class) o2).getName());
1139     }
1140   }
1141 
1142 
1143   // Members (Methods and Constructors) are compared first by name,
1144   // conflicts are resolved by comparing type signatures
1145   private static final class MemberComparator implements Comparator
1146   {
1147     public int compare(Object o1, Object o2)
1148     {
1149       Member m1 = (Member) o1;
1150       Member m2 = (Member) o2;
1151 
1152       int comp = m1.getName().compareTo(m2.getName());
1153 
1154       if (comp == 0)
1155         return TypeSignature.getEncodingOfMember(m1).
1156           compareTo(TypeSignature.getEncodingOfMember(m2));
1157       else
1158         return comp;
1159     }
1160   }
1161 }
1162