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 io.ebean.enhance.asm.ClassVisitor;
031import io.ebean.enhance.asm.FieldVisitor;
032import io.ebean.enhance.asm.MethodVisitor;
033import io.ebean.enhance.asm.Opcodes;
034
035import java.io.ByteArrayOutputStream;
036import java.io.DataOutput;
037import java.io.DataOutputStream;
038import java.io.IOException;
039import java.security.MessageDigest;
040import java.security.NoSuchAlgorithmException;
041import java.util.ArrayList;
042import java.util.Arrays;
043import java.util.Collection;
044
045/**
046 * A {@link ClassVisitor} that adds a serial version unique identifier to a class if missing. A
047 * typical usage of this class is:
048 *
049 * <pre>
050 *   ClassWriter classWriter = new ClassWriter(...);
051 *   ClassVisitor svuidAdder = new SerialVersionUIDAdder(classWriter);
052 *   ClassVisitor classVisitor = new MyClassAdapter(svuidAdder);
053 *   new ClassReader(orginalClass).accept(classVisitor, 0);
054 * </pre>
055 *
056 * <p>The SVUID algorithm can be found at <a href=
057 * "https://docs.oracle.com/javase/10/docs/specs/serialization/class.html#stream-unique-identifiers"
058 * >https://docs.oracle.com/javase/10/docs/specs/serialization/class.html#stream-unique-identifiers</a>:
059 *
060 * <p>The serialVersionUID is computed using the signature of a stream of bytes that reflect the
061 * class definition. The National Institute of Standards and Technology (NIST) Secure Hash Algorithm
062 * (SHA-1) is used to compute a signature for the stream. The first two 32-bit quantities are used
063 * to form a 64-bit hash. A java.lang.DataOutputStream is used to convert primitive data types to a
064 * sequence of bytes. The values input to the stream are defined by the Java Virtual Machine (VM)
065 * specification for classes.
066 *
067 * <p>The sequence of items in the stream is as follows:
068 *
069 * <ol>
070 *   <li>The class name written using UTF encoding.
071 *   <li>The class modifiers written as a 32-bit integer.
072 *   <li>The name of each interface sorted by name written using UTF encoding.
073 *   <li>For each field of the class sorted by field name (except private static and private
074 *       transient fields):
075 *       <ol>
076 *         <li>The name of the field in UTF encoding.
077 *         <li>The modifiers of the field written as a 32-bit integer.
078 *         <li>The descriptor of the field in UTF encoding
079 *       </ol>
080 *   <li>If a class initializer exists, write out the following:
081 *       <ol>
082 *         <li>The name of the method, &lt;clinit&gt;, in UTF encoding.
083 *         <li>The modifier of the method, STATIC, written as a 32-bit integer.
084 *         <li>The descriptor of the method, ()V, in UTF encoding.
085 *       </ol>
086 *   <li>For each non-private constructor sorted by method name and signature:
087 *       <ol>
088 *         <li>The name of the method, &lt;init&gt;, in UTF encoding.
089 *         <li>The modifiers of the method written as a 32-bit integer.
090 *         <li>The descriptor of the method in UTF encoding.
091 *       </ol>
092 *   <li>For each non-private method sorted by method name and signature:
093 *       <ol>
094 *         <li>The name of the method in UTF encoding.
095 *         <li>The modifiers of the method written as a 32-bit integer.
096 *         <li>The descriptor of the method in UTF encoding.
097 *       </ol>
098 *   <li>The SHA-1 algorithm is executed on the stream of bytes produced by DataOutputStream and
099 *       produces five 32-bit values sha[0..4].
100 *   <li>The hash value is assembled from the first and second 32-bit values of the SHA-1 message
101 *       digest. If the result of the message digest, the five 32-bit words H0 H1 H2 H3 H4, is in an
102 *       array of five int values named sha, the hash value would be computed as follows: long hash
103 *       = ((sha[0] &gt;&gt;&gt; 24) &amp; 0xFF) | ((sha[0] &gt;&gt;&gt; 16) &amp; 0xFF) &lt;&lt; 8
104 *       | ((sha[0] &gt;&gt;&gt; 8) &amp; 0xFF) &lt;&lt; 16 | ((sha[0] &gt;&gt;&gt; 0) &amp; 0xFF)
105 *       &lt;&lt; 24 | ((sha[1] &gt;&gt;&gt; 24) &amp; 0xFF) &lt;&lt; 32 | ((sha[1] &gt;&gt;&gt; 16)
106 *       &amp; 0xFF) &lt;&lt; 40 | ((sha[1] &gt;&gt;&gt; 8) &amp; 0xFF) &lt;&lt; 48 | ((sha[1]
107 *       &gt;&gt;&gt; 0) &amp; 0xFF) &lt;&lt; 56;
108 * </ol>
109 *
110 * @author Rajendra Inamdar, Vishal Vishnoi
111 */
112// DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility).
113public class SerialVersionUIDAdder extends ClassVisitor {
114
115  /** The JVM name of static initializer methods. */
116  private static final String CLINIT = "<clinit>";
117
118  /** A flag that indicates if we need to compute SVUID. */
119  private boolean computeSvuid;
120
121  /** Whether the class already has a SVUID. */
122  private boolean hasSvuid;
123
124  /** The class access flags. */
125  private int access;
126
127  /** The internal name of the class. */
128  private String name;
129
130  /** The interfaces implemented by the class. */
131  private String[] interfaces;
132
133  /** The fields of the class that are needed to compute the SVUID. */
134  private Collection<Item> svuidFields;
135
136  /** Whether the class has a static initializer. */
137  private boolean hasStaticInitializer;
138
139  /** The constructors of the class that are needed to compute the SVUID. */
140  private Collection<Item> svuidConstructors;
141
142  /** The methods of the class that are needed to compute the SVUID. */
143  private Collection<Item> svuidMethods;
144
145  /**
146   * Constructs a new {@link SerialVersionUIDAdder}. <i>Subclasses must not use this
147   * constructor</i>. Instead, they must use the {@link #SerialVersionUIDAdder(int, ClassVisitor)}
148   * version.
149   *
150   * @param classVisitor a {@link ClassVisitor} to which this visitor will delegate calls.
151   * @throws IllegalStateException If a subclass calls this constructor.
152   */
153  public SerialVersionUIDAdder(final ClassVisitor classVisitor) {
154    this(Opcodes.ASM7, classVisitor);
155    if (getClass() != SerialVersionUIDAdder.class) {
156      throw new IllegalStateException();
157    }
158  }
159
160  /**
161   * Constructs a new {@link SerialVersionUIDAdder}.
162   *
163   * @param api the ASM API version implemented by this visitor. Must be one of {@link
164   *     Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
165   * @param classVisitor a {@link ClassVisitor} to which this visitor will delegate calls.
166   */
167  protected SerialVersionUIDAdder(final int api, final ClassVisitor classVisitor) {
168    super(api, classVisitor);
169  }
170
171  // -----------------------------------------------------------------------------------------------
172  // Overridden methods
173  // -----------------------------------------------------------------------------------------------
174
175  @Override
176  public void visit(
177      final int version,
178      final int access,
179      final String name,
180      final String signature,
181      final String superName,
182      final String[] interfaces) {
183    // Get the class name, access flags, and interfaces information (step 1, 2 and 3) for SVUID
184    // computation.
185    computeSvuid = (access & Opcodes.ACC_ENUM) == 0;
186
187    if (computeSvuid) {
188      this.name = name;
189      this.access = access;
190      this.interfaces = new String[interfaces.length];
191      this.svuidFields = new ArrayList<>();
192      this.svuidConstructors = new ArrayList<>();
193      this.svuidMethods = new ArrayList<>();
194      System.arraycopy(interfaces, 0, this.interfaces, 0, interfaces.length);
195    }
196
197    super.visit(version, access, name, signature, superName, interfaces);
198  }
199
200  @Override
201  public MethodVisitor visitMethod(
202      final int access,
203      final String name,
204      final String descriptor,
205      final String signature,
206      final String[] exceptions) {
207    // Get constructor and method information (step 5 and 7). Also determine if there is a class
208    // initializer (step 6).
209    if (computeSvuid) {
210      if (CLINIT.equals(name)) {
211        hasStaticInitializer = true;
212      }
213      // Collect the non private constructors and methods. Only the ACC_PUBLIC, ACC_PRIVATE,
214      // ACC_PROTECTED, ACC_STATIC, ACC_FINAL, ACC_SYNCHRONIZED, ACC_NATIVE, ACC_ABSTRACT and
215      // ACC_STRICT flags are used.
216      int mods =
217          access
218              & (Opcodes.ACC_PUBLIC
219                  | Opcodes.ACC_PRIVATE
220                  | Opcodes.ACC_PROTECTED
221                  | Opcodes.ACC_STATIC
222                  | Opcodes.ACC_FINAL
223                  | Opcodes.ACC_SYNCHRONIZED
224                  | Opcodes.ACC_NATIVE
225                  | Opcodes.ACC_ABSTRACT
226                  | Opcodes.ACC_STRICT);
227
228      if ((access & Opcodes.ACC_PRIVATE) == 0) {
229        if ("<init>".equals(name)) {
230          svuidConstructors.add(new Item(name, mods, descriptor));
231        } else if (!CLINIT.equals(name)) {
232          svuidMethods.add(new Item(name, mods, descriptor));
233        }
234      }
235    }
236
237    return super.visitMethod(access, name, descriptor, signature, exceptions);
238  }
239
240  @Override
241  public FieldVisitor visitField(
242      final int access,
243      final String name,
244      final String desc,
245      final String signature,
246      final Object value) {
247    // Get the class field information for step 4 of the algorithm. Also determine if the class
248    // already has a SVUID.
249    if (computeSvuid) {
250      if ("serialVersionUID".equals(name)) {
251        // Since the class already has SVUID, we won't be computing it.
252        computeSvuid = false;
253        hasSvuid = true;
254      }
255      // Collect the non private fields. Only the ACC_PUBLIC, ACC_PRIVATE, ACC_PROTECTED,
256      // ACC_STATIC, ACC_FINAL, ACC_VOLATILE, and ACC_TRANSIENT flags are used when computing
257      // serialVersionUID values.
258      if ((access & Opcodes.ACC_PRIVATE) == 0
259          || (access & (Opcodes.ACC_STATIC | Opcodes.ACC_TRANSIENT)) == 0) {
260        int mods =
261            access
262                & (Opcodes.ACC_PUBLIC
263                    | Opcodes.ACC_PRIVATE
264                    | Opcodes.ACC_PROTECTED
265                    | Opcodes.ACC_STATIC
266                    | Opcodes.ACC_FINAL
267                    | Opcodes.ACC_VOLATILE
268                    | Opcodes.ACC_TRANSIENT);
269        svuidFields.add(new Item(name, mods, desc));
270      }
271    }
272
273    return super.visitField(access, name, desc, signature, value);
274  }
275
276  @Override
277  public void visitInnerClass(
278      final String innerClassName,
279      final String outerName,
280      final String innerName,
281      final int innerClassAccess) {
282    // Handles a bizarre special case. Nested classes (static classes declared inside another class)
283    // that are protected have their access bit set to public in their class files to deal with some
284    // odd reflection situation. Our SVUID computation must do as the JVM does and ignore access
285    // bits in the class file in favor of the access bits of the InnerClass attribute.
286    if ((name != null) && name.equals(innerClassName)) {
287      this.access = innerClassAccess;
288    }
289    super.visitInnerClass(innerClassName, outerName, innerName, innerClassAccess);
290  }
291
292  @Override
293  public void visitEnd() {
294    // Add the SVUID field to the class if it doesn't have one.
295    if (computeSvuid && !hasSvuid) {
296      try {
297        addSVUID(computeSVUID());
298      } catch (IOException e) {
299        throw new IllegalStateException("Error while computing SVUID for " + name, e);
300      }
301    }
302
303    super.visitEnd();
304  }
305
306  // -----------------------------------------------------------------------------------------------
307  // Utility methods
308  // -----------------------------------------------------------------------------------------------
309
310  /**
311   * Returns true if the class already has a SVUID field. The result of this method is only valid
312   * when visitEnd has been called.
313   *
314   * @return true if the class already has a SVUID field.
315   */
316  // DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility).
317  public boolean hasSVUID() {
318    return hasSvuid;
319  }
320
321  /**
322   * Adds a final static serialVersionUID field to the class, with the given value.
323   *
324   * @param svuid the serialVersionUID field value.
325   */
326  // DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility).
327  protected void addSVUID(final long svuid) {
328    FieldVisitor fieldVisitor =
329        super.visitField(
330            Opcodes.ACC_FINAL + Opcodes.ACC_STATIC, "serialVersionUID", "J", null, svuid);
331    if (fieldVisitor != null) {
332      fieldVisitor.visitEnd();
333    }
334  }
335
336  /**
337   * Computes and returns the value of SVUID.
338   *
339   * @return the serial version UID.
340   * @throws IOException if an I/O error occurs.
341   */
342  // DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility).
343  protected long computeSVUID() throws IOException {
344    long svuid = 0;
345
346    try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
347        DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream)) {
348
349      // 1. The class name written using UTF encoding.
350      dataOutputStream.writeUTF(name.replace('/', '.'));
351
352      // 2. The class modifiers written as a 32-bit integer.
353      int mods = access;
354      if ((mods & Opcodes.ACC_INTERFACE) != 0) {
355        mods =
356            svuidMethods.isEmpty() ? (mods & ~Opcodes.ACC_ABSTRACT) : (mods | Opcodes.ACC_ABSTRACT);
357      }
358      dataOutputStream.writeInt(
359          mods
360              & (Opcodes.ACC_PUBLIC
361                  | Opcodes.ACC_FINAL
362                  | Opcodes.ACC_INTERFACE
363                  | Opcodes.ACC_ABSTRACT));
364
365      // 3. The name of each interface sorted by name written using UTF encoding.
366      Arrays.sort(interfaces);
367      for (String interfaceName : interfaces) {
368        dataOutputStream.writeUTF(interfaceName.replace('/', '.'));
369      }
370
371      // 4. For each field of the class sorted by field name (except private static and private
372      // transient fields):
373      //   1. The name of the field in UTF encoding.
374      //   2. The modifiers of the field written as a 32-bit integer.
375      //   3. The descriptor of the field in UTF encoding.
376      // Note that field signatures are not dot separated. Method and constructor signatures are dot
377      // separated. Go figure...
378      writeItems(svuidFields, dataOutputStream, false);
379
380      // 5. If a class initializer exists, write out the following:
381      //   1. The name of the method, <clinit>, in UTF encoding.
382      //   2. The modifier of the method, ACC_STATIC, written as a 32-bit integer.
383      //   3. The descriptor of the method, ()V, in UTF encoding.
384      if (hasStaticInitializer) {
385        dataOutputStream.writeUTF(CLINIT);
386        dataOutputStream.writeInt(Opcodes.ACC_STATIC);
387        dataOutputStream.writeUTF("()V");
388      }
389
390      // 6. For each non-private constructor sorted by method name and signature:
391      //   1. The name of the method, <init>, in UTF encoding.
392      //   2. The modifiers of the method written as a 32-bit integer.
393      //   3. The descriptor of the method in UTF encoding.
394      writeItems(svuidConstructors, dataOutputStream, true);
395
396      // 7. For each non-private method sorted by method name and signature:
397      //   1. The name of the method in UTF encoding.
398      //   2. The modifiers of the method written as a 32-bit integer.
399      //   3. The descriptor of the method in UTF encoding.
400      writeItems(svuidMethods, dataOutputStream, true);
401
402      dataOutputStream.flush();
403
404      // 8. The SHA-1 algorithm is executed on the stream of bytes produced by DataOutputStream and
405      // produces five 32-bit values sha[0..4].
406      byte[] hashBytes = computeSHAdigest(byteArrayOutputStream.toByteArray());
407
408      // 9. The hash value is assembled from the first and second 32-bit values of the SHA-1 message
409      // digest. If the result of the message digest, the five 32-bit words H0 H1 H2 H3 H4, is in an
410      // array of five int values named sha, the hash value would be computed as follows:
411      for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) {
412        svuid = (svuid << 8) | (hashBytes[i] & 0xFF);
413      }
414    }
415
416    return svuid;
417  }
418
419  /**
420   * Returns the SHA-1 message digest of the given value.
421   *
422   * @param value the value whose SHA message digest must be computed.
423   * @return the SHA-1 message digest of the given value.
424   */
425  // DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility).
426  protected byte[] computeSHAdigest(final byte[] value) {
427    try {
428      return MessageDigest.getInstance("SHA").digest(value);
429    } catch (NoSuchAlgorithmException e) {
430      throw new UnsupportedOperationException(e);
431    }
432  }
433
434  /**
435   * Sorts the items in the collection and writes it to the given output stream.
436   *
437   * @param itemCollection a collection of items.
438   * @param dataOutputStream where the items must be written.
439   * @param dotted whether package names must use dots, instead of slashes.
440   * @exception IOException if an error occurs.
441   */
442  private static void writeItems(
443      final Collection<Item> itemCollection,
444      final DataOutput dataOutputStream,
445      final boolean dotted)
446      throws IOException {
447    Item[] items = itemCollection.toArray(new Item[0]);
448    Arrays.sort(items);
449    for (Item item : items) {
450      dataOutputStream.writeUTF(item.name);
451      dataOutputStream.writeInt(item.access);
452      dataOutputStream.writeUTF(dotted ? item.descriptor.replace('/', '.') : item.descriptor);
453    }
454  }
455
456  // -----------------------------------------------------------------------------------------------
457  // Inner classes
458  // -----------------------------------------------------------------------------------------------
459
460  private static final class Item implements Comparable<Item> {
461
462    final String name;
463    final int access;
464    final String descriptor;
465
466    Item(final String name, final int access, final String descriptor) {
467      this.name = name;
468      this.access = access;
469      this.descriptor = descriptor;
470    }
471
472    @Override
473    public int compareTo(final Item item) {
474      int result = name.compareTo(item.name);
475      if (result == 0) {
476        result = descriptor.compareTo(item.descriptor);
477      }
478      return result;
479    }
480
481    @Override
482    public boolean equals(final Object other) {
483      if (other instanceof Item) {
484        return compareTo((Item) other) == 0;
485      }
486      return false;
487    }
488
489    @Override
490    public int hashCode() {
491      return name.hashCode() ^ descriptor.hashCode();
492    }
493  }
494}