|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
package jdk.internal.org.objectweb.asm.commons; |
|
|
|
import java.io.ByteArrayOutputStream; |
|
import java.io.DataOutput; |
|
import java.io.DataOutputStream; |
|
import java.io.IOException; |
|
import java.security.MessageDigest; |
|
import java.security.NoSuchAlgorithmException; |
|
import java.util.ArrayList; |
|
import java.util.Arrays; |
|
import java.util.Collection; |
|
import jdk.internal.org.objectweb.asm.ClassVisitor; |
|
import jdk.internal.org.objectweb.asm.FieldVisitor; |
|
import jdk.internal.org.objectweb.asm.MethodVisitor; |
|
import jdk.internal.org.objectweb.asm.Opcodes; |
|
|
|
/** |
|
* A {@link ClassVisitor} that adds a serial version unique identifier to a class if missing. A |
|
* typical usage of this class is: |
|
* |
|
* <pre> |
|
* ClassWriter classWriter = new ClassWriter(...); |
|
* ClassVisitor svuidAdder = new SerialVersionUIDAdder(classWriter); |
|
* ClassVisitor classVisitor = new MyClassAdapter(svuidAdder); |
|
* new ClassReader(orginalClass).accept(classVisitor, 0); |
|
* </pre> |
|
* |
|
* <p>The SVUID algorithm can be found at <a href= |
|
* "https://docs.oracle.com/javase/10/docs/specs/serialization/class.html#stream-unique-identifiers" |
|
* >https://docs.oracle.com/javase/10/docs/specs/serialization/class.html#stream-unique-identifiers</a>: |
|
* |
|
* <p>The serialVersionUID is computed using the signature of a stream of bytes that reflect the |
|
* class definition. The National Institute of Standards and Technology (NIST) Secure Hash Algorithm |
|
* (SHA-1) is used to compute a signature for the stream. The first two 32-bit quantities are used |
|
* to form a 64-bit hash. A java.lang.DataOutputStream is used to convert primitive data types to a |
|
* sequence of bytes. The values input to the stream are defined by the Java Virtual Machine (VM) |
|
* specification for classes. |
|
* |
|
* <p>The sequence of items in the stream is as follows: |
|
* |
|
* <ol> |
|
* <li>The class name written using UTF encoding. |
|
* <li>The class modifiers written as a 32-bit integer. |
|
* <li>The name of each interface sorted by name written using UTF encoding. |
|
* <li>For each field of the class sorted by field name (except private static and private |
|
* transient fields): |
|
* <ol> |
|
* <li>The name of the field in UTF encoding. |
|
* <li>The modifiers of the field written as a 32-bit integer. |
|
* <li>The descriptor of the field in UTF encoding |
|
* </ol> |
|
* <li>If a class initializer exists, write out the following: |
|
* <ol> |
|
* <li>The name of the method, <clinit>, in UTF encoding. |
|
* <li>The modifier of the method, STATIC, written as a 32-bit integer. |
|
* <li>The descriptor of the method, ()V, in UTF encoding. |
|
* </ol> |
|
* <li>For each non-private constructor sorted by method name and signature: |
|
* <ol> |
|
* <li>The name of the method, <init>, in UTF encoding. |
|
* <li>The modifiers of the method written as a 32-bit integer. |
|
* <li>The descriptor of the method in UTF encoding. |
|
* </ol> |
|
* <li>For each non-private method sorted by method name and signature: |
|
* <ol> |
|
* <li>The name of the method in UTF encoding. |
|
* <li>The modifiers of the method written as a 32-bit integer. |
|
* <li>The descriptor of the method in UTF encoding. |
|
* </ol> |
|
* <li>The SHA-1 algorithm is executed on the stream of bytes produced by DataOutputStream and |
|
* produces five 32-bit values sha[0..4]. |
|
* <li>The hash value is assembled from the first and second 32-bit values of the SHA-1 message |
|
* digest. If the result of the message digest, the five 32-bit words H0 H1 H2 H3 H4, is in an |
|
* array of five int values named sha, the hash value would be computed as follows: long hash |
|
* = ((sha[0] >>> 24) & 0xFF) | ((sha[0] >>> 16) & 0xFF) << 8 |
|
* | ((sha[0] >>> 8) & 0xFF) << 16 | ((sha[0] >>> 0) & 0xFF) |
|
* << 24 | ((sha[1] >>> 24) & 0xFF) << 32 | ((sha[1] >>> 16) |
|
* & 0xFF) << 40 | ((sha[1] >>> 8) & 0xFF) << 48 | ((sha[1] |
|
* >>> 0) & 0xFF) << 56; |
|
* </ol> |
|
* |
|
* @author Rajendra Inamdar, Vishal Vishnoi |
|
*/ |
|
|
|
public class SerialVersionUIDAdder extends ClassVisitor { |
|
|
|
|
|
private static final String CLINIT = "<clinit>"; |
|
|
|
|
|
private boolean computeSvuid; |
|
|
|
|
|
private boolean hasSvuid; |
|
|
|
|
|
private int access; |
|
|
|
|
|
private String name; |
|
|
|
|
|
private String[] interfaces; |
|
|
|
|
|
private Collection<Item> svuidFields; |
|
|
|
|
|
private boolean hasStaticInitializer; |
|
|
|
|
|
private Collection<Item> svuidConstructors; |
|
|
|
|
|
private Collection<Item> svuidMethods; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public SerialVersionUIDAdder(final ClassVisitor classVisitor) { |
|
this( Opcodes.ASM8, classVisitor); |
|
if (getClass() != SerialVersionUIDAdder.class) { |
|
throw new IllegalStateException(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
protected SerialVersionUIDAdder(final int api, final ClassVisitor classVisitor) { |
|
super(api, classVisitor); |
|
} |
|
|
|
// ----------------------------------------------------------------------------------------------- |
|
// Overridden methods |
|
// ----------------------------------------------------------------------------------------------- |
|
|
|
@Override |
|
public void visit( |
|
final int version, |
|
final int access, |
|
final String name, |
|
final String signature, |
|
final String superName, |
|
final String[] interfaces) { |
|
// Get the class name, access flags, and interfaces information (step 1, 2 and 3) for SVUID |
|
|
|
computeSvuid = (access & Opcodes.ACC_ENUM) == 0; |
|
|
|
if (computeSvuid) { |
|
this.name = name; |
|
this.access = access; |
|
this.interfaces = interfaces.clone(); |
|
this.svuidFields = new ArrayList<>(); |
|
this.svuidConstructors = new ArrayList<>(); |
|
this.svuidMethods = new ArrayList<>(); |
|
} |
|
|
|
super.visit(version, access, name, signature, superName, interfaces); |
|
} |
|
|
|
@Override |
|
public MethodVisitor visitMethod( |
|
final int access, |
|
final String name, |
|
final String descriptor, |
|
final String signature, |
|
final String[] exceptions) { |
|
// Get constructor and method information (step 5 and 7). Also determine if there is a class |
|
|
|
if (computeSvuid) { |
|
if (CLINIT.equals(name)) { |
|
hasStaticInitializer = true; |
|
} |
|
// Collect the non private constructors and methods. Only the ACC_PUBLIC, ACC_PRIVATE, |
|
// ACC_PROTECTED, ACC_STATIC, ACC_FINAL, ACC_SYNCHRONIZED, ACC_NATIVE, ACC_ABSTRACT and |
|
|
|
int mods = |
|
access |
|
& (Opcodes.ACC_PUBLIC |
|
| Opcodes.ACC_PRIVATE |
|
| Opcodes.ACC_PROTECTED |
|
| Opcodes.ACC_STATIC |
|
| Opcodes.ACC_FINAL |
|
| Opcodes.ACC_SYNCHRONIZED |
|
| Opcodes.ACC_NATIVE |
|
| Opcodes.ACC_ABSTRACT |
|
| Opcodes.ACC_STRICT); |
|
|
|
if ((access & Opcodes.ACC_PRIVATE) == 0) { |
|
if ("<init>".equals(name)) { |
|
svuidConstructors.add(new Item(name, mods, descriptor)); |
|
} else if (!CLINIT.equals(name)) { |
|
svuidMethods.add(new Item(name, mods, descriptor)); |
|
} |
|
} |
|
} |
|
|
|
return super.visitMethod(access, name, descriptor, signature, exceptions); |
|
} |
|
|
|
@Override |
|
public FieldVisitor visitField( |
|
final int access, |
|
final String name, |
|
final String desc, |
|
final String signature, |
|
final Object value) { |
|
// Get the class field information for step 4 of the algorithm. Also determine if the class |
|
|
|
if (computeSvuid) { |
|
if ("serialVersionUID".equals(name)) { |
|
|
|
computeSvuid = false; |
|
hasSvuid = true; |
|
} |
|
// Collect the non private fields. Only the ACC_PUBLIC, ACC_PRIVATE, ACC_PROTECTED, |
|
// ACC_STATIC, ACC_FINAL, ACC_VOLATILE, and ACC_TRANSIENT flags are used when computing |
|
|
|
if ((access & Opcodes.ACC_PRIVATE) == 0 |
|
|| (access & (Opcodes.ACC_STATIC | Opcodes.ACC_TRANSIENT)) == 0) { |
|
int mods = |
|
access |
|
& (Opcodes.ACC_PUBLIC |
|
| Opcodes.ACC_PRIVATE |
|
| Opcodes.ACC_PROTECTED |
|
| Opcodes.ACC_STATIC |
|
| Opcodes.ACC_FINAL |
|
| Opcodes.ACC_VOLATILE |
|
| Opcodes.ACC_TRANSIENT); |
|
svuidFields.add(new Item(name, mods, desc)); |
|
} |
|
} |
|
|
|
return super.visitField(access, name, desc, signature, value); |
|
} |
|
|
|
@Override |
|
public void visitInnerClass( |
|
final String innerClassName, |
|
final String outerName, |
|
final String innerName, |
|
final int innerClassAccess) { |
|
// Handles a bizarre special case. Nested classes (static classes declared inside another class) |
|
// that are protected have their access bit set to public in their class files to deal with some |
|
// odd reflection situation. Our SVUID computation must do as the JVM does and ignore access |
|
|
|
if ((name != null) && name.equals(innerClassName)) { |
|
this.access = innerClassAccess; |
|
} |
|
super.visitInnerClass(innerClassName, outerName, innerName, innerClassAccess); |
|
} |
|
|
|
@Override |
|
public void visitEnd() { |
|
|
|
if (computeSvuid && !hasSvuid) { |
|
try { |
|
addSVUID(computeSVUID()); |
|
} catch (IOException e) { |
|
throw new IllegalStateException("Error while computing SVUID for " + name, e); |
|
} |
|
} |
|
|
|
super.visitEnd(); |
|
} |
|
|
|
// ----------------------------------------------------------------------------------------------- |
|
// Utility methods |
|
// ----------------------------------------------------------------------------------------------- |
|
|
|
/** |
|
* Returns true if the class already has a SVUID field. The result of this method is only valid |
|
* when visitEnd has been called. |
|
* |
|
* @return true if the class already has a SVUID field. |
|
*/ |
|
|
|
public boolean hasSVUID() { |
|
return hasSvuid; |
|
} |
|
|
|
/** |
|
* Adds a static final serialVersionUID field to the class, with the given value. |
|
* |
|
* @param svuid the serialVersionUID field value. |
|
*/ |
|
|
|
protected void addSVUID(final long svuid) { |
|
FieldVisitor fieldVisitor = |
|
super.visitField( |
|
Opcodes.ACC_FINAL + Opcodes.ACC_STATIC, "serialVersionUID", "J", null, svuid); |
|
if (fieldVisitor != null) { |
|
fieldVisitor.visitEnd(); |
|
} |
|
} |
|
|
|
/** |
|
* Computes and returns the value of SVUID. |
|
* |
|
* @return the serial version UID. |
|
* @throws IOException if an I/O error occurs. |
|
*/ |
|
|
|
protected long computeSVUID() throws IOException { |
|
long svuid = 0; |
|
|
|
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); |
|
DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream)) { |
|
|
|
|
|
dataOutputStream.writeUTF(name.replace('/', '.')); |
|
|
|
|
|
int mods = access; |
|
if ((mods & Opcodes.ACC_INTERFACE) != 0) { |
|
mods = |
|
svuidMethods.isEmpty() ? (mods & ~Opcodes.ACC_ABSTRACT) : (mods | Opcodes.ACC_ABSTRACT); |
|
} |
|
dataOutputStream.writeInt( |
|
mods |
|
& (Opcodes.ACC_PUBLIC |
|
| Opcodes.ACC_FINAL |
|
| Opcodes.ACC_INTERFACE |
|
| Opcodes.ACC_ABSTRACT)); |
|
|
|
|
|
Arrays.sort(interfaces); |
|
for (String interfaceName : interfaces) { |
|
dataOutputStream.writeUTF(interfaceName.replace('/', '.')); |
|
} |
|
|
|
// 4. For each field of the class sorted by field name (except private static and private |
|
// transient fields): |
|
// 1. The name of the field in UTF encoding. |
|
// 2. The modifiers of the field written as a 32-bit integer. |
|
// 3. The descriptor of the field in UTF encoding. |
|
// Note that field signatures are not dot separated. Method and constructor signatures are dot |
|
|
|
writeItems(svuidFields, dataOutputStream, false); |
|
|
|
// 5. If a class initializer exists, write out the following: |
|
// 1. The name of the method, <clinit>, in UTF encoding. |
|
// 2. The modifier of the method, ACC_STATIC, written as a 32-bit integer. |
|
|
|
if (hasStaticInitializer) { |
|
dataOutputStream.writeUTF(CLINIT); |
|
dataOutputStream.writeInt(Opcodes.ACC_STATIC); |
|
dataOutputStream.writeUTF("()V"); |
|
} |
|
|
|
// 6. For each non-private constructor sorted by method name and signature: |
|
// 1. The name of the method, <init>, in UTF encoding. |
|
// 2. The modifiers of the method written as a 32-bit integer. |
|
|
|
writeItems(svuidConstructors, dataOutputStream, true); |
|
|
|
// 7. For each non-private method sorted by method name and signature: |
|
// 1. The name of the method in UTF encoding. |
|
// 2. The modifiers of the method written as a 32-bit integer. |
|
|
|
writeItems(svuidMethods, dataOutputStream, true); |
|
|
|
dataOutputStream.flush(); |
|
|
|
// 8. The SHA-1 algorithm is executed on the stream of bytes produced by DataOutputStream and |
|
|
|
byte[] hashBytes = computeSHAdigest(byteArrayOutputStream.toByteArray()); |
|
|
|
// 9. The hash value is assembled from the first and second 32-bit values of the SHA-1 message |
|
// digest. If the result of the message digest, the five 32-bit words H0 H1 H2 H3 H4, is in an |
|
|
|
for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) { |
|
svuid = (svuid << 8) | (hashBytes[i] & 0xFF); |
|
} |
|
} |
|
|
|
return svuid; |
|
} |
|
|
|
/** |
|
* Returns the SHA-1 message digest of the given value. |
|
* |
|
* @param value the value whose SHA message digest must be computed. |
|
* @return the SHA-1 message digest of the given value. |
|
*/ |
|
|
|
protected byte[] computeSHAdigest(final byte[] value) { |
|
try { |
|
return MessageDigest.getInstance("SHA").digest(value); |
|
} catch (NoSuchAlgorithmException e) { |
|
throw new UnsupportedOperationException(e); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static void writeItems( |
|
final Collection<Item> itemCollection, |
|
final DataOutput dataOutputStream, |
|
final boolean dotted) |
|
throws IOException { |
|
Item[] items = itemCollection.toArray(new Item[0]); |
|
Arrays.sort(items); |
|
for (Item item : items) { |
|
dataOutputStream.writeUTF(item.name); |
|
dataOutputStream.writeInt(item.access); |
|
dataOutputStream.writeUTF(dotted ? item.descriptor.replace('/', '.') : item.descriptor); |
|
} |
|
} |
|
|
|
// ----------------------------------------------------------------------------------------------- |
|
// Inner classes |
|
// ----------------------------------------------------------------------------------------------- |
|
|
|
private static final class Item implements Comparable<Item> { |
|
|
|
final String name; |
|
final int access; |
|
final String descriptor; |
|
|
|
Item(final String name, final int access, final String descriptor) { |
|
this.name = name; |
|
this.access = access; |
|
this.descriptor = descriptor; |
|
} |
|
|
|
@Override |
|
public int compareTo(final Item item) { |
|
int result = name.compareTo(item.name); |
|
if (result == 0) { |
|
result = descriptor.compareTo(item.descriptor); |
|
} |
|
return result; |
|
} |
|
|
|
@Override |
|
public boolean equals(final Object other) { |
|
if (other instanceof Item) { |
|
return compareTo((Item) other) == 0; |
|
} |
|
return false; |
|
} |
|
|
|
@Override |
|
public int hashCode() { |
|
return name.hashCode() ^ descriptor.hashCode(); |
|
} |
|
} |
|
} |