Home | History | Annotate | Download | only in commons
      1 /***
      2  * ASM: a very small and fast Java bytecode manipulation framework
      3  * Copyright (c) 2000-2005 INRIA, France Telecom
      4  * All rights reserved.
      5  *
      6  * Redistribution and use in source and binary forms, with or without
      7  * modification, are permitted provided that the following conditions
      8  * are met:
      9  * 1. Redistributions of source code must retain the above copyright
     10  *    notice, this list of conditions and the following disclaimer.
     11  * 2. Redistributions in binary form must reproduce the above copyright
     12  *    notice, this list of conditions and the following disclaimer in the
     13  *    documentation and/or other materials provided with the distribution.
     14  * 3. Neither the name of the copyright holders nor the names of its
     15  *    contributors may be used to endorse or promote products derived from
     16  *    this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     19  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     21  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
     22  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     23  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     24  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     25  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     26  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     27  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
     28  * THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 package org.objectweb.asm.commons;
     31 
     32 import java.io.ByteArrayOutputStream;
     33 import java.io.DataOutputStream;
     34 import java.io.IOException;
     35 import java.security.MessageDigest;
     36 import java.util.ArrayList;
     37 import java.util.Arrays;
     38 import java.util.Collection;
     39 
     40 import org.objectweb.asm.ClassAdapter;
     41 import org.objectweb.asm.ClassVisitor;
     42 import org.objectweb.asm.FieldVisitor;
     43 import org.objectweb.asm.MethodVisitor;
     44 import org.objectweb.asm.Opcodes;
     45 
     46 /**
     47  * A {@link ClassAdapter} that adds a serial version unique identifier to a
     48  * class if missing. Here is typical usage of this class:
     49  *
     50  * <pre>
     51  *   ClassWriter cw = new ClassWriter(...);
     52  *   ClassVisitor sv = new SerialVersionUIDAdder(cw);
     53  *   ClassVisitor ca = new MyClassAdapter(sv);
     54  *   new ClassReader(orginalClass).accept(ca, false);
     55  * </pre>
     56  *
     57  * The SVUID algorithm can be found <a href=
     58  * "http://java.sun.com/j2se/1.4.2/docs/guide/serialization/spec/class.html"
     59  * >http://java.sun.com/j2se/1.4.2/docs/guide/serialization/spec/class.html</a>:
     60  *
     61  * <pre>
     62  * The serialVersionUID is computed using the signature of a stream of bytes
     63  * that reflect the class definition. The National Institute of Standards and
     64  * Technology (NIST) Secure Hash Algorithm (SHA-1) is used to compute a
     65  * signature for the stream. The first two 32-bit quantities are used to form a
     66  * 64-bit hash. A java.lang.DataOutputStream is used to convert primitive data
     67  * types to a sequence of bytes. The values input to the stream are defined by
     68  * the Java Virtual Machine (VM) specification for classes.
     69  *
     70  * The sequence of items in the stream is as follows:
     71  *
     72  * 1. The class name written using UTF encoding.
     73  * 2. The class modifiers written as a 32-bit integer.
     74  * 3. The name of each interface sorted by name written using UTF encoding.
     75  * 4. For each field of the class sorted by field name (except private static
     76  * and private transient fields):
     77  * 1. The name of the field in UTF encoding.
     78  * 2. The modifiers of the field written as a 32-bit integer.
     79  * 3. The descriptor of the field in UTF encoding
     80  * 5. If a class initializer exists, write out the following:
     81  * 1. The name of the method, &lt;clinit&gt;, in UTF encoding.
     82  * 2. The modifier of the method, java.lang.reflect.Modifier.STATIC,
     83  * written as a 32-bit integer.
     84  * 3. The descriptor of the method, ()V, in UTF encoding.
     85  * 6. For each non-private constructor sorted by method name and signature:
     86  * 1. The name of the method, &lt;init&gt;, in UTF encoding.
     87  * 2. The modifiers of the method written as a 32-bit integer.
     88  * 3. The descriptor of the method in UTF encoding.
     89  * 7. For each non-private method sorted by method name and signature:
     90  * 1. The name of the method in UTF encoding.
     91  * 2. The modifiers of the method written as a 32-bit integer.
     92  * 3. The descriptor of the method in UTF encoding.
     93  * 8. The SHA-1 algorithm is executed on the stream of bytes produced by
     94  * DataOutputStream and produces five 32-bit values sha[0..4].
     95  *
     96  * 9. The hash value is assembled from the first and second 32-bit values of
     97  * the SHA-1 message digest. If the result of the message digest, the five
     98  * 32-bit words H0 H1 H2 H3 H4, is in an array of five int values named
     99  * sha, the hash value would be computed as follows:
    100  *
    101  * long hash = ((sha[0] &gt;&gt;&gt; 24) &amp; 0xFF) |
    102  * ((sha[0] &gt;&gt;&gt; 16) &amp; 0xFF) &lt;&lt; 8 |
    103  * ((sha[0] &gt;&gt;&gt; 8) &amp; 0xFF) &lt;&lt; 16 |
    104  * ((sha[0] &gt;&gt;&gt; 0) &amp; 0xFF) &lt;&lt; 24 |
    105  * ((sha[1] &gt;&gt;&gt; 24) &amp; 0xFF) &lt;&lt; 32 |
    106  * ((sha[1] &gt;&gt;&gt; 16) &amp; 0xFF) &lt;&lt; 40 |
    107  * ((sha[1] &gt;&gt;&gt; 8) &amp; 0xFF) &lt;&lt; 48 |
    108  * ((sha[1] &gt;&gt;&gt; 0) &amp; 0xFF) &lt;&lt; 56;
    109  * </pre>
    110  *
    111  * @author Rajendra Inamdar, Vishal Vishnoi
    112  */
    113 public class SerialVersionUIDAdder extends ClassAdapter {
    114 
    115     /**
    116      * Flag that indicates if we need to compute SVUID.
    117      */
    118     protected boolean computeSVUID;
    119 
    120     /**
    121      * Set to true if the class already has SVUID.
    122      */
    123     protected boolean hasSVUID;
    124 
    125     /**
    126      * Classes access flags.
    127      */
    128     protected int access;
    129 
    130     /**
    131      * Internal name of the class
    132      */
    133     protected String name;
    134 
    135     /**
    136      * Interfaces implemented by the class.
    137      */
    138     protected String[] interfaces;
    139 
    140     /**
    141      * Collection of fields. (except private static and private transient
    142      * fields)
    143      */
    144     protected Collection svuidFields;
    145 
    146     /**
    147      * Set to true if the class has static initializer.
    148      */
    149     protected boolean hasStaticInitializer;
    150 
    151     /**
    152      * Collection of non-private constructors.
    153      */
    154     protected Collection svuidConstructors;
    155 
    156     /**
    157      * Collection of non-private methods.
    158      */
    159     protected Collection svuidMethods;
    160 
    161     /**
    162      * Creates a new {@link SerialVersionUIDAdder}.
    163      *
    164      * @param cv a {@link ClassVisitor} to which this visitor will delegate
    165      *        calls.
    166      */
    167     public SerialVersionUIDAdder(final ClassVisitor cv) {
    168         super(cv);
    169         svuidFields = new ArrayList();
    170         svuidConstructors = new ArrayList();
    171         svuidMethods = new ArrayList();
    172     }
    173 
    174     // ------------------------------------------------------------------------
    175     // Overriden methods
    176     // ------------------------------------------------------------------------
    177 
    178     /*
    179      * Visit class header and get class name, access , and intefraces
    180      * informatoin (step 1,2, and 3) for SVUID computation.
    181      */
    182     public void visit(
    183         final int version,
    184         final int access,
    185         final String name,
    186         final String signature,
    187         final String superName,
    188         final String[] interfaces)
    189     {
    190         computeSVUID = (access & Opcodes.ACC_INTERFACE) == 0;
    191 
    192         if (computeSVUID) {
    193             this.name = name;
    194             this.access = access;
    195             this.interfaces = interfaces;
    196         }
    197 
    198         super.visit(version, access, name, signature, superName, interfaces);
    199     }
    200 
    201     /*
    202      * Visit the methods and get constructor and method information (step 5 and
    203      * 7). Also determince if there is a class initializer (step 6).
    204      */
    205     public MethodVisitor visitMethod(
    206         final int access,
    207         final String name,
    208         final String desc,
    209         final String signature,
    210         final String[] exceptions)
    211     {
    212         if (computeSVUID) {
    213             if (name.equals("<clinit>")) {
    214                 hasStaticInitializer = true;
    215             }
    216             /*
    217              * Remembers non private constructors and methods for SVUID
    218              * computation For constructor and method modifiers, only the
    219              * ACC_PUBLIC, ACC_PRIVATE, ACC_PROTECTED, ACC_STATIC, ACC_FINAL,
    220              * ACC_SYNCHRONIZED, ACC_NATIVE, ACC_ABSTRACT and ACC_STRICT flags
    221              * are used.
    222              */
    223             int mods = access
    224                     & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PRIVATE
    225                             | Opcodes.ACC_PROTECTED | Opcodes.ACC_STATIC
    226                             | Opcodes.ACC_FINAL | Opcodes.ACC_SYNCHRONIZED
    227                             | Opcodes.ACC_NATIVE | Opcodes.ACC_ABSTRACT | Opcodes.ACC_STRICT);
    228 
    229             // all non private methods
    230             if ((access & Opcodes.ACC_PRIVATE) == 0) {
    231                 if (name.equals("<init>")) {
    232                     svuidConstructors.add(new Item(name, mods, desc));
    233                 } else if (!name.equals("<clinit>")) {
    234                     svuidMethods.add(new Item(name, mods, desc));
    235                 }
    236             }
    237         }
    238 
    239         return cv.visitMethod(access, name, desc, signature, exceptions);
    240     }
    241 
    242     /*
    243      * Gets class field information for step 4 of the alogrithm. Also determines
    244      * if the class already has a SVUID.
    245      */
    246     public FieldVisitor visitField(
    247         final int access,
    248         final String name,
    249         final String desc,
    250         final String signature,
    251         final Object value)
    252     {
    253         if (computeSVUID) {
    254             if (name.equals("serialVersionUID")) {
    255                 // since the class already has SVUID, we won't be computing it.
    256                 computeSVUID = false;
    257                 hasSVUID = true;
    258             }
    259             /*
    260              * Remember field for SVUID computation For field modifiers, only
    261              * the ACC_PUBLIC, ACC_PRIVATE, ACC_PROTECTED, ACC_STATIC,
    262              * ACC_FINAL, ACC_VOLATILE, and ACC_TRANSIENT flags are used when
    263              * computing serialVersionUID values.
    264              */
    265             int mods = access
    266                     & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PRIVATE
    267                             | Opcodes.ACC_PROTECTED | Opcodes.ACC_STATIC
    268                             | Opcodes.ACC_FINAL | Opcodes.ACC_VOLATILE | Opcodes.ACC_TRANSIENT);
    269 
    270             if (((access & Opcodes.ACC_PRIVATE) == 0)
    271                     || ((access & (Opcodes.ACC_STATIC | Opcodes.ACC_TRANSIENT)) == 0))
    272             {
    273                 svuidFields.add(new Item(name, mods, desc));
    274             }
    275         }
    276 
    277         return super.visitField(access, name, desc, signature, value);
    278     }
    279 
    280     /*
    281      * Add the SVUID if class doesn't have one
    282      */
    283     public void visitEnd() {
    284         // compute SVUID and add it to the class
    285         if (computeSVUID && !hasSVUID) {
    286             try {
    287                 cv.visitField(Opcodes.ACC_FINAL + Opcodes.ACC_STATIC,
    288                         "serialVersionUID",
    289                         "J",
    290                         null,
    291                         new Long(computeSVUID()));
    292             } catch (Throwable e) {
    293                 throw new RuntimeException("Error while computing SVUID for "
    294                         + name, e);
    295             }
    296         }
    297 
    298         super.visitEnd();
    299     }
    300 
    301     // ------------------------------------------------------------------------
    302     // Utility methods
    303     // ------------------------------------------------------------------------
    304 
    305     /**
    306      * Returns the value of SVUID if the class doesn't have one already. Please
    307      * note that 0 is returned if the class already has SVUID, thus use
    308      * <code>isHasSVUID</code> to determine if the class already had an SVUID.
    309      *
    310      * @return Returns the serial version UID
    311      * @throws IOException
    312      */
    313     protected long computeSVUID() throws IOException {
    314         if (hasSVUID) {
    315             return 0;
    316         }
    317 
    318         ByteArrayOutputStream bos = null;
    319         DataOutputStream dos = null;
    320         long svuid = 0;
    321 
    322         try {
    323             bos = new ByteArrayOutputStream();
    324             dos = new DataOutputStream(bos);
    325 
    326             /*
    327              * 1. The class name written using UTF encoding.
    328              */
    329             dos.writeUTF(name.replace('/', '.'));
    330 
    331             /*
    332              * 2. The class modifiers written as a 32-bit integer.
    333              */
    334             dos.writeInt(access
    335                     & (Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL
    336                             | Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT));
    337 
    338             /*
    339              * 3. The name of each interface sorted by name written using UTF
    340              * encoding.
    341              */
    342             Arrays.sort(interfaces);
    343             for (int i = 0; i < interfaces.length; i++) {
    344                 dos.writeUTF(interfaces[i].replace('/', '.'));
    345             }
    346 
    347             /*
    348              * 4. For each field of the class sorted by field name (except
    349              * private static and private transient fields):
    350              *
    351              * 1. The name of the field in UTF encoding. 2. The modifiers of the
    352              * field written as a 32-bit integer. 3. The descriptor of the field
    353              * in UTF encoding
    354              *
    355              * Note that field signatutes are not dot separated. Method and
    356              * constructor signatures are dot separated. Go figure...
    357              */
    358             writeItems(svuidFields, dos, false);
    359 
    360             /*
    361              * 5. If a class initializer exists, write out the following: 1. The
    362              * name of the method, <clinit>, in UTF encoding. 2. The modifier of
    363              * the method, java.lang.reflect.Modifier.STATIC, written as a
    364              * 32-bit integer. 3. The descriptor of the method, ()V, in UTF
    365              * encoding.
    366              */
    367             if (hasStaticInitializer) {
    368                 dos.writeUTF("<clinit>");
    369                 dos.writeInt(Opcodes.ACC_STATIC);
    370                 dos.writeUTF("()V");
    371             } // if..
    372 
    373             /*
    374              * 6. For each non-private constructor sorted by method name and
    375              * signature: 1. The name of the method, <init>, in UTF encoding. 2.
    376              * The modifiers of the method written as a 32-bit integer. 3. The
    377              * descriptor of the method in UTF encoding.
    378              */
    379             writeItems(svuidConstructors, dos, true);
    380 
    381             /*
    382              * 7. For each non-private method sorted by method name and
    383              * signature: 1. The name of the method in UTF encoding. 2. The
    384              * modifiers of the method written as a 32-bit integer. 3. The
    385              * descriptor of the method in UTF encoding.
    386              */
    387             writeItems(svuidMethods, dos, true);
    388 
    389             dos.flush();
    390 
    391             /*
    392              * 8. The SHA-1 algorithm is executed on the stream of bytes
    393              * produced by DataOutputStream and produces five 32-bit values
    394              * sha[0..4].
    395              */
    396             byte[] hashBytes = computeSHAdigest(bos.toByteArray());
    397 
    398             /*
    399              * 9. The hash value is assembled from the first and second 32-bit
    400              * values of the SHA-1 message digest. If the result of the message
    401              * digest, the five 32-bit words H0 H1 H2 H3 H4, is in an array of
    402              * five int values named sha, the hash value would be computed as
    403              * follows:
    404              *
    405              * long hash = ((sha[0] >>> 24) & 0xFF) | ((sha[0] >>> 16) & 0xFF) <<
    406              * 8 | ((sha[0] >>> 8) & 0xFF) << 16 | ((sha[0] >>> 0) & 0xFF) <<
    407              * 24 | ((sha[1] >>> 24) & 0xFF) << 32 | ((sha[1] >>> 16) & 0xFF) <<
    408              * 40 | ((sha[1] >>> 8) & 0xFF) << 48 | ((sha[1] >>> 0) & 0xFF) <<
    409              * 56;
    410              */
    411             for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) {
    412                 svuid = (svuid << 8) | (hashBytes[i] & 0xFF);
    413             }
    414         } finally {
    415             // close the stream (if open)
    416             if (dos != null) {
    417                 dos.close();
    418             }
    419         }
    420 
    421         return svuid;
    422     }
    423 
    424     /**
    425      * Returns the SHA-1 message digest of the given value.
    426      *
    427      * @param value the value whose SHA message digest must be computed.
    428      * @return the SHA-1 message digest of the given value.
    429      */
    430     protected byte[] computeSHAdigest(byte[] value) {
    431         try {
    432             return MessageDigest.getInstance("SHA").digest(value);
    433         } catch (Exception e) {
    434             throw new UnsupportedOperationException(e);
    435         }
    436     }
    437 
    438     /**
    439      * Sorts the items in the collection and writes it to the data output stream
    440      *
    441      * @param itemCollection collection of items
    442      * @param dos a <code>DataOutputStream</code> value
    443      * @param dotted a <code>boolean</code> value
    444      * @exception IOException if an error occurs
    445      */
    446     private void writeItems(
    447         final Collection itemCollection,
    448         final DataOutputStream dos,
    449         final boolean dotted) throws IOException
    450     {
    451         int size = itemCollection.size();
    452         Item items[] = (Item[]) itemCollection.toArray(new Item[size]);
    453         Arrays.sort(items);
    454         for (int i = 0; i < size; i++) {
    455             dos.writeUTF(items[i].name);
    456             dos.writeInt(items[i].access);
    457             dos.writeUTF(dotted
    458                     ? items[i].desc.replace('/', '.')
    459                     : items[i].desc);
    460         }
    461     }
    462 
    463     // ------------------------------------------------------------------------
    464     // Inner classes
    465     // ------------------------------------------------------------------------
    466 
    467     static class Item implements Comparable {
    468 
    469         String name;
    470 
    471         int access;
    472 
    473         String desc;
    474 
    475         Item(final String name, final int access, final String desc) {
    476             this.name = name;
    477             this.access = access;
    478             this.desc = desc;
    479         }
    480 
    481         public int compareTo(final Object o) {
    482             Item other = (Item) o;
    483             int retVal = name.compareTo(other.name);
    484             if (retVal == 0) {
    485                 retVal = desc.compareTo(other.desc);
    486             }
    487             return retVal;
    488         }
    489     }
    490 }
    491