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;
039
040import io.ebean.enhance.asm.FieldVisitor;
041import io.ebean.enhance.asm.MethodVisitor;
042import io.ebean.enhance.asm.ClassVisitor;
043import io.ebean.enhance.asm.Opcodes;
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(/* latest api = */ Opcodes.ASM9, 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 the {@code
164   *     ASM}<i>x</i> values in {@link Opcodes}.
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 = interfaces.clone();
191      this.svuidFields = new ArrayList<>();
192      this.svuidConstructors = new ArrayList<>();
193      this.svuidMethods = new ArrayList<>();
194    }
195
196    super.visit(version, access, name, signature, superName, interfaces);
197  }
198
199  @Override
200  public MethodVisitor visitMethod(
201      final int access,
202      final String name,
203      final String descriptor,
204      final String signature,
205      final String[] exceptions) {
206    // Get constructor and method information (step 5 and 7). Also determine if there is a class
207    // initializer (step 6).
208    if (computeSvuid) {
209      if (CLINIT.equals(name)) {
210        hasStaticInitializer = true;
211      }
212      // Collect the non private constructors and methods. Only the ACC_PUBLIC, ACC_PRIVATE,
213      // ACC_PROTECTED, ACC_STATIC, ACC_FINAL, ACC_SYNCHRONIZED, ACC_NATIVE, ACC_ABSTRACT and
214      // ACC_STRICT flags are used.
215      int mods =
216          access
217              & (Opcodes.ACC_PUBLIC
218                  | Opcodes.ACC_PRIVATE
219                  | Opcodes.ACC_PROTECTED
220                  | Opcodes.ACC_STATIC
221                  | Opcodes.ACC_FINAL
222                  | Opcodes.ACC_SYNCHRONIZED
223                  | Opcodes.ACC_NATIVE
224                  | Opcodes.ACC_ABSTRACT
225                  | Opcodes.ACC_STRICT);
226
227      if ((access & Opcodes.ACC_PRIVATE) == 0) {
228        if ("<init>".equals(name)) {
229          svuidConstructors.add(new Item(name, mods, descriptor));
230        } else if (!CLINIT.equals(name)) {
231          svuidMethods.add(new Item(name, mods, descriptor));
232        }
233      }
234    }
235
236    return super.visitMethod(access, name, descriptor, signature, exceptions);
237  }
238
239  @Override
240  public FieldVisitor visitField(
241      final int access,
242      final String name,
243      final String desc,
244      final String signature,
245      final Object value) {
246    // Get the class field information for step 4 of the algorithm. Also determine if the class
247    // already has a SVUID.
248    if (computeSvuid) {
249      if ("serialVersionUID".equals(name)) {
250        // Since the class already has SVUID, we won't be computing it.
251        computeSvuid = false;
252        hasSvuid = true;
253      }
254      // Collect the non private fields. Only the ACC_PUBLIC, ACC_PRIVATE, ACC_PROTECTED,
255      // ACC_STATIC, ACC_FINAL, ACC_VOLATILE, and ACC_TRANSIENT flags are used when computing
256      // serialVersionUID values.
257      if ((access & Opcodes.ACC_PRIVATE) == 0
258          || (access & (Opcodes.ACC_STATIC | Opcodes.ACC_TRANSIENT)) == 0) {
259        int mods =
260            access
261                & (Opcodes.ACC_PUBLIC
262                    | Opcodes.ACC_PRIVATE
263                    | Opcodes.ACC_PROTECTED
264                    | Opcodes.ACC_STATIC
265                    | Opcodes.ACC_FINAL
266                    | Opcodes.ACC_VOLATILE
267                    | Opcodes.ACC_TRANSIENT);
268        svuidFields.add(new Item(name, mods, desc));
269      }
270    }
271
272    return super.visitField(access, name, desc, signature, value);
273  }
274
275  @Override
276  public void visitInnerClass(
277      final String innerClassName,
278      final String outerName,
279      final String innerName,
280      final int innerClassAccess) {
281    // Handles a bizarre special case. Nested classes (static classes declared inside another class)
282    // that are protected have their access bit set to public in their class files to deal with some
283    // odd reflection situation. Our SVUID computation must do as the JVM does and ignore access
284    // bits in the class file in favor of the access bits of the InnerClass attribute.
285    if ((name != null) && name.equals(innerClassName)) {
286      this.access = innerClassAccess;
287    }
288    super.visitInnerClass(innerClassName, outerName, innerName, innerClassAccess);
289  }
290
291  @Override
292  public void visitEnd() {
293    // Add the SVUID field to the class if it doesn't have one.
294    if (computeSvuid && !hasSvuid) {
295      try {
296        addSVUID(computeSVUID());
297      } catch (IOException e) {
298        throw new IllegalStateException("Error while computing SVUID for " + name, e);
299      }
300    }
301
302    super.visitEnd();
303  }
304
305  // -----------------------------------------------------------------------------------------------
306  // Utility methods
307  // -----------------------------------------------------------------------------------------------
308
309  /**
310   * Returns true if the class already has a SVUID field. The result of this method is only valid
311   * when visitEnd has been called.
312   *
313   * @return true if the class already has a SVUID field.
314   */
315  // DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility).
316  public boolean hasSVUID() {
317    return hasSvuid;
318  }
319
320  /**
321   * Adds a final static serialVersionUID field to the class, with the given value.
322   *
323   * @param svuid the serialVersionUID field value.
324   */
325  // DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility).
326  protected void addSVUID(final long svuid) {
327    FieldVisitor fieldVisitor =
328        super.visitField(
329            Opcodes.ACC_FINAL + Opcodes.ACC_STATIC, "serialVersionUID", "J", null, svuid);
330    if (fieldVisitor != null) {
331      fieldVisitor.visitEnd();
332    }
333  }
334
335  /**
336   * Computes and returns the value of SVUID.
337   *
338   * @return the serial version UID.
339   * @throws IOException if an I/O error occurs.
340   */
341  // DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility).
342  protected long computeSVUID() throws IOException {
343    long svuid = 0;
344
345    try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
346        DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream)) {
347
348      // 1. The class name written using UTF encoding.
349      dataOutputStream.writeUTF(name.replace('/', '.'));
350
351      // 2. The class modifiers written as a 32-bit integer.
352      int mods = access;
353      if ((mods & Opcodes.ACC_INTERFACE) != 0) {
354        mods =
355            svuidMethods.isEmpty() ? (mods & ~Opcodes.ACC_ABSTRACT) : (mods | Opcodes.ACC_ABSTRACT);
356      }
357      dataOutputStream.writeInt(
358          mods
359              & (Opcodes.ACC_PUBLIC
360                  | Opcodes.ACC_FINAL
361                  | Opcodes.ACC_INTERFACE
362                  | Opcodes.ACC_ABSTRACT));
363
364      // 3. The name of each interface sorted by name written using UTF encoding.
365      Arrays.sort(interfaces);
366      for (String interfaceName : interfaces) {
367        dataOutputStream.writeUTF(interfaceName.replace('/', '.'));
368      }
369
370      // 4. For each field of the class sorted by field name (except private static and private
371      // transient fields):
372      //   1. The name of the field in UTF encoding.
373      //   2. The modifiers of the field written as a 32-bit integer.
374      //   3. The descriptor of the field in UTF encoding.
375      // Note that field signatures are not dot separated. Method and constructor signatures are dot
376      // separated. Go figure...
377      writeItems(svuidFields, dataOutputStream, false);
378
379      // 5. If a class initializer exists, write out the following:
380      //   1. The name of the method, <clinit>, in UTF encoding.
381      //   2. The modifier of the method, ACC_STATIC, written as a 32-bit integer.
382      //   3. The descriptor of the method, ()V, in UTF encoding.
383      if (hasStaticInitializer) {
384        dataOutputStream.writeUTF(CLINIT);
385        dataOutputStream.writeInt(Opcodes.ACC_STATIC);
386        dataOutputStream.writeUTF("()V");
387      }
388
389      // 6. For each non-private constructor sorted by method name and signature:
390      //   1. The name of the method, <init>, in UTF encoding.
391      //   2. The modifiers of the method written as a 32-bit integer.
392      //   3. The descriptor of the method in UTF encoding.
393      writeItems(svuidConstructors, dataOutputStream, true);
394
395      // 7. For each non-private method sorted by method name and signature:
396      //   1. The name of the method in UTF encoding.
397      //   2. The modifiers of the method written as a 32-bit integer.
398      //   3. The descriptor of the method in UTF encoding.
399      writeItems(svuidMethods, dataOutputStream, true);
400
401      dataOutputStream.flush();
402
403      // 8. The SHA-1 algorithm is executed on the stream of bytes produced by DataOutputStream and
404      // produces five 32-bit values sha[0..4].
405      byte[] hashBytes = computeSHAdigest(byteArrayOutputStream.toByteArray());
406
407      // 9. The hash value is assembled from the first and second 32-bit values of the SHA-1 message
408      // digest. If the result of the message digest, the five 32-bit words H0 H1 H2 H3 H4, is in an
409      // array of five int values named sha, the hash value would be computed as follows:
410      for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) {
411        svuid = (svuid << 8) | (hashBytes[i] & 0xFF);
412      }
413    }
414
415    return svuid;
416  }
417
418  /**
419   * Returns the SHA-1 message digest of the given value.
420   *
421   * @param value the value whose SHA message digest must be computed.
422   * @return the SHA-1 message digest of the given value.
423   */
424  // DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility).
425  protected byte[] computeSHAdigest(final byte[] value) {
426    try {
427      return MessageDigest.getInstance("SHA").digest(value);
428    } catch (NoSuchAlgorithmException e) {
429      throw new UnsupportedOperationException(e);
430    }
431  }
432
433  /**
434   * Sorts the items in the collection and writes it to the given output stream.
435   *
436   * @param itemCollection a collection of items.
437   * @param dataOutputStream where the items must be written.
438   * @param dotted whether package names must use dots, instead of slashes.
439   * @exception IOException if an error occurs.
440   */
441  private static void writeItems(
442      final Collection<Item> itemCollection,
443      final DataOutput dataOutputStream,
444      final boolean dotted)
445      throws IOException {
446    Item[] items = itemCollection.toArray(new Item[0]);
447    Arrays.sort(items);
448    for (Item item : items) {
449      dataOutputStream.writeUTF(item.name);
450      dataOutputStream.writeInt(item.access);
451      dataOutputStream.writeUTF(dotted ? item.descriptor.replace('/', '.') : item.descriptor);
452    }
453  }
454
455  // -----------------------------------------------------------------------------------------------
456  // Inner classes
457  // -----------------------------------------------------------------------------------------------
458
459  private static final class Item implements Comparable<Item> {
460
461    final String name;
462    final int access;
463    final String descriptor;
464
465    Item(final String name, final int access, final String descriptor) {
466      this.name = name;
467      this.access = access;
468      this.descriptor = descriptor;
469    }
470
471    @Override
472    public int compareTo(final Item item) {
473      int result = name.compareTo(item.name);
474      if (result == 0) {
475        result = descriptor.compareTo(item.descriptor);
476      }
477      return result;
478    }
479
480    @Override
481    public boolean equals(final Object other) {
482      if (other instanceof Item) {
483        return compareTo((Item) other) == 0;
484      }
485      return false;
486    }
487
488    @Override
489    public int hashCode() {
490      return name.hashCode() ^ descriptor.hashCode();
491    }
492  }
493}