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, <clinit>, 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, <init>, 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] >>> 24) & 0xFF) | ((sha[0] >>> 16) & 0xFF) << 8 103 * | ((sha[0] >>> 8) & 0xFF) << 16 | ((sha[0] >>> 0) & 0xFF) 104 * << 24 | ((sha[1] >>> 24) & 0xFF) << 32 | ((sha[1] >>> 16) 105 * & 0xFF) << 40 | ((sha[1] >>> 8) & 0xFF) << 48 | ((sha[1] 106 * >>> 0) & 0xFF) << 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