• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
SerialVersionUIDAdder(final ClassVisitor cv)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      */
visit( final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces)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      */
visitMethod( final int access, final String name, final String desc, final String signature, final String[] exceptions)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      */
visitField( final int access, final String name, final String desc, final String signature, final Object value)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      */
visitEnd()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      */
computeSVUID()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      */
computeSHAdigest(byte[] value)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      */
writeItems( final Collection itemCollection, final DataOutputStream dos, final boolean dotted)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 
Item(final String name, final int access, final String desc)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 
compareTo(final Object o)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