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