001// ASM: a very small and fast Java bytecode manipulation framework
002// Copyright (c) 2000-2011 INRIA, France Telecom
003// All rights reserved.
004//
005// Redistribution and use in source and binary forms, with or without
006// modification, are permitted provided that the following conditions
007// are met:
008// 1. Redistributions of source code must retain the above copyright
009//    notice, this list of conditions and the following disclaimer.
010// 2. Redistributions in binary form must reproduce the above copyright
011//    notice, this list of conditions and the following disclaimer in the
012//    documentation and/or other materials provided with the distribution.
013// 3. Neither the name of the copyright holders nor the names of its
014//    contributors may be used to endorse or promote products derived from
015//    this software without specific prior written permission.
016//
017// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
018// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
019// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
020// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
021// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
022// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
023// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
024// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
025// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
026// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
027// THE POSSIBILITY OF SUCH DAMAGE.
028package io.ebean.enhance.asm.commons;
029
030import java.io.ByteArrayOutputStream;
031import java.io.DataOutput;
032import java.io.DataOutputStream;
033import java.io.IOException;
034import java.security.MessageDigest;
035import java.security.NoSuchAlgorithmException;
036import java.util.ArrayList;
037import java.util.Arrays;
038import java.util.Collection;
039import io.ebean.enhance.asm.ClassVisitor;
040import io.ebean.enhance.asm.FieldVisitor;
041import io.ebean.enhance.asm.MethodVisitor;
042import io.ebean.enhance.asm.Opcodes;
043
044/**
045 * A {@link ClassVisitor} that adds a serial version unique identifier to a class if missing. A
046 * typical usage of this class is:
047 *
048 * <pre>
049 *   ClassWriter classWriter = new ClassWriter(...);
050 *   ClassVisitor svuidAdder = new SerialVersionUIDAdder(classWriter);
051 *   ClassVisitor classVisitor = new MyClassAdapter(svuidAdder);
052 *   new ClassReader(orginalClass).accept(classVisitor, 0);
053 * </pre>
054 *
055 * <p>The SVUID algorithm can be found at <a href=
056 * "https://docs.oracle.com/javase/10/docs/specs/serialization/class.html#stream-unique-identifiers"
057 * >https://docs.oracle.com/javase/10/docs/specs/serialization/class.html#stream-unique-identifiers</a>:
058 *
059 * <p>The serialVersionUID is computed using the signature of a stream of bytes that reflect the
060 * class definition. The National Institute of Standards and Technology (NIST) Secure Hash Algorithm
061 * (SHA-1) is used to compute a signature for the stream. The first two 32-bit quantities are used
062 * to form a 64-bit hash. A java.lang.DataOutputStream is used to convert primitive data types to a
063 * sequence of bytes. The values input to the stream are defined by the Java Virtual Machine (VM)
064 * specification for classes.
065 *
066 * <p>The sequence of items in the stream is as follows:
067 *
068 * <ol>
069 *   <li>The class name written using UTF encoding.
070 *   <li>The class modifiers written as a 32-bit integer.
071 *   <li>The name of each interface sorted by name written using UTF encoding.
072 *   <li>For each field of the class sorted by field name (except private static and private
073 *       transient fields):
074 *       <ol>
075 *         <li>The name of the field in UTF encoding.
076 *         <li>The modifiers of the field written as a 32-bit integer.
077 *         <li>The descriptor of the field in UTF encoding
078 *       </ol>
079 *   <li>If a class initializer exists, write out the following:
080 *       <ol>
081 *         <li>The name of the method, &lt;clinit&gt;, in UTF encoding.
082 *         <li>The modifier of the method, STATIC, written as a 32-bit integer.
083 *         <li>The descriptor of the method, ()V, in UTF encoding.
084 *       </ol>
085 *   <li>For each non-private constructor sorted by method name and signature:
086 *       <ol>
087 *         <li>The name of the method, &lt;init&gt;, in UTF encoding.
088 *         <li>The modifiers of the method written as a 32-bit integer.
089 *         <li>The descriptor of the method in UTF encoding.
090 *       </ol>
091 *   <li>For each non-private method sorted by method name and signature:
092 *       <ol>
093 *         <li>The name of the method in UTF encoding.
094 *         <li>The modifiers of the method written as a 32-bit integer.
095 *         <li>The descriptor of the method in UTF encoding.
096 *       </ol>
097 *   <li>The SHA-1 algorithm is executed on the stream of bytes produced by DataOutputStream and
098 *       produces five 32-bit values sha[0..4].
099 *   <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).
112public 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   */
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   */
166  protected SerialVersionUIDAdder(final int api, final ClassVisitor classVisitor) {
167    super(api, classVisitor);
168  }
169
170  // -----------------------------------------------------------------------------------------------
171  // Overridden methods
172  // -----------------------------------------------------------------------------------------------
173
174  @Override
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
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
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
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
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).
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).
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).
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).
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   */
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
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
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
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
488    public int hashCode() {
489      return name.hashCode() ^ descriptor.hashCode();
490    }
491  }
492}