1 /*** 2 * ASM: a very small and fast Java bytecode manipulation framework 3 * Copyright (c) 2000-2005 INRIA, France Telecom 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. Neither the name of the copyright holders nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 28 * THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 package org.objectweb.asm.commons; 31 32 import java.io.ByteArrayOutputStream; 33 import java.io.DataOutputStream; 34 import java.io.IOException; 35 import java.security.MessageDigest; 36 import java.util.ArrayList; 37 import java.util.Arrays; 38 import java.util.Collection; 39 40 import org.objectweb.asm.ClassAdapter; 41 import org.objectweb.asm.ClassVisitor; 42 import org.objectweb.asm.FieldVisitor; 43 import org.objectweb.asm.MethodVisitor; 44 import org.objectweb.asm.Opcodes; 45 46 /** 47 * A {@link ClassAdapter} that adds a serial version unique identifier to a 48 * class if missing. Here is typical usage of this class: 49 * 50 * <pre> 51 * ClassWriter cw = new ClassWriter(...); 52 * ClassVisitor sv = new SerialVersionUIDAdder(cw); 53 * ClassVisitor ca = new MyClassAdapter(sv); 54 * new ClassReader(orginalClass).accept(ca, false); 55 * </pre> 56 * 57 * The SVUID algorithm can be found <a href= 58 * "http://java.sun.com/j2se/1.4.2/docs/guide/serialization/spec/class.html" 59 * >http://java.sun.com/j2se/1.4.2/docs/guide/serialization/spec/class.html</a>: 60 * 61 * <pre> 62 * The serialVersionUID is computed using the signature of a stream of bytes 63 * that reflect the class definition. The National Institute of Standards and 64 * Technology (NIST) Secure Hash Algorithm (SHA-1) is used to compute a 65 * signature for the stream. The first two 32-bit quantities are used to form a 66 * 64-bit hash. A java.lang.DataOutputStream is used to convert primitive data 67 * types to a sequence of bytes. The values input to the stream are defined by 68 * the Java Virtual Machine (VM) specification for classes. 69 * 70 * The sequence of items in the stream is as follows: 71 * 72 * 1. The class name written using UTF encoding. 73 * 2. The class modifiers written as a 32-bit integer. 74 * 3. The name of each interface sorted by name written using UTF encoding. 75 * 4. For each field of the class sorted by field name (except private static 76 * and private transient fields): 77 * 1. The name of the field in UTF encoding. 78 * 2. The modifiers of the field written as a 32-bit integer. 79 * 3. The descriptor of the field in UTF encoding 80 * 5. If a class initializer exists, write out the following: 81 * 1. The name of the method, <clinit>, in UTF encoding. 82 * 2. The modifier of the method, java.lang.reflect.Modifier.STATIC, 83 * written as a 32-bit integer. 84 * 3. The descriptor of the method, ()V, in UTF encoding. 85 * 6. For each non-private constructor sorted by method name and signature: 86 * 1. The name of the method, <init>, in UTF encoding. 87 * 2. The modifiers of the method written as a 32-bit integer. 88 * 3. The descriptor of the method in UTF encoding. 89 * 7. For each non-private method sorted by method name and signature: 90 * 1. The name of the method in UTF encoding. 91 * 2. The modifiers of the method written as a 32-bit integer. 92 * 3. The descriptor of the method in UTF encoding. 93 * 8. The SHA-1 algorithm is executed on the stream of bytes produced by 94 * DataOutputStream and produces five 32-bit values sha[0..4]. 95 * 96 * 9. The hash value is assembled from the first and second 32-bit values of 97 * the SHA-1 message digest. If the result of the message digest, the five 98 * 32-bit words H0 H1 H2 H3 H4, is in an array of five int values named 99 * sha, the hash value would be computed as follows: 100 * 101 * long hash = ((sha[0] >>> 24) & 0xFF) | 102 * ((sha[0] >>> 16) & 0xFF) << 8 | 103 * ((sha[0] >>> 8) & 0xFF) << 16 | 104 * ((sha[0] >>> 0) & 0xFF) << 24 | 105 * ((sha[1] >>> 24) & 0xFF) << 32 | 106 * ((sha[1] >>> 16) & 0xFF) << 40 | 107 * ((sha[1] >>> 8) & 0xFF) << 48 | 108 * ((sha[1] >>> 0) & 0xFF) << 56; 109 * </pre> 110 * 111 * @author Rajendra Inamdar, Vishal Vishnoi 112 */ 113 public class SerialVersionUIDAdder extends ClassAdapter { 114 115 /** 116 * Flag that indicates if we need to compute SVUID. 117 */ 118 protected boolean computeSVUID; 119 120 /** 121 * Set to true if the class already has SVUID. 122 */ 123 protected boolean hasSVUID; 124 125 /** 126 * Classes access flags. 127 */ 128 protected int access; 129 130 /** 131 * Internal name of the class 132 */ 133 protected String name; 134 135 /** 136 * Interfaces implemented by the class. 137 */ 138 protected String[] interfaces; 139 140 /** 141 * Collection of fields. (except private static and private transient 142 * fields) 143 */ 144 protected Collection svuidFields; 145 146 /** 147 * Set to true if the class has static initializer. 148 */ 149 protected boolean hasStaticInitializer; 150 151 /** 152 * Collection of non-private constructors. 153 */ 154 protected Collection svuidConstructors; 155 156 /** 157 * Collection of non-private methods. 158 */ 159 protected Collection svuidMethods; 160 161 /** 162 * Creates a new {@link SerialVersionUIDAdder}. 163 * 164 * @param cv a {@link ClassVisitor} to which this visitor will delegate 165 * calls. 166 */ SerialVersionUIDAdder(final ClassVisitor cv)167 public SerialVersionUIDAdder(final ClassVisitor cv) { 168 super(cv); 169 svuidFields = new ArrayList(); 170 svuidConstructors = new ArrayList(); 171 svuidMethods = new ArrayList(); 172 } 173 174 // ------------------------------------------------------------------------ 175 // Overriden methods 176 // ------------------------------------------------------------------------ 177 178 /* 179 * Visit class header and get class name, access , and intefraces 180 * informatoin (step 1,2, and 3) for SVUID computation. 181 */ visit( final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces)182 public void visit( 183 final int version, 184 final int access, 185 final String name, 186 final String signature, 187 final String superName, 188 final String[] interfaces) 189 { 190 computeSVUID = (access & Opcodes.ACC_INTERFACE) == 0; 191 192 if (computeSVUID) { 193 this.name = name; 194 this.access = access; 195 this.interfaces = interfaces; 196 } 197 198 super.visit(version, access, name, signature, superName, interfaces); 199 } 200 201 /* 202 * Visit the methods and get constructor and method information (step 5 and 203 * 7). Also determince if there is a class initializer (step 6). 204 */ visitMethod( final int access, final String name, final String desc, final String signature, final String[] exceptions)205 public MethodVisitor visitMethod( 206 final int access, 207 final String name, 208 final String desc, 209 final String signature, 210 final String[] exceptions) 211 { 212 if (computeSVUID) { 213 if (name.equals("<clinit>")) { 214 hasStaticInitializer = true; 215 } 216 /* 217 * Remembers non private constructors and methods for SVUID 218 * computation For constructor and method modifiers, only the 219 * ACC_PUBLIC, ACC_PRIVATE, ACC_PROTECTED, ACC_STATIC, ACC_FINAL, 220 * ACC_SYNCHRONIZED, ACC_NATIVE, ACC_ABSTRACT and ACC_STRICT flags 221 * are used. 222 */ 223 int mods = access 224 & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PRIVATE 225 | Opcodes.ACC_PROTECTED | Opcodes.ACC_STATIC 226 | Opcodes.ACC_FINAL | Opcodes.ACC_SYNCHRONIZED 227 | Opcodes.ACC_NATIVE | Opcodes.ACC_ABSTRACT | Opcodes.ACC_STRICT); 228 229 // all non private methods 230 if ((access & Opcodes.ACC_PRIVATE) == 0) { 231 if (name.equals("<init>")) { 232 svuidConstructors.add(new Item(name, mods, desc)); 233 } else if (!name.equals("<clinit>")) { 234 svuidMethods.add(new Item(name, mods, desc)); 235 } 236 } 237 } 238 239 return cv.visitMethod(access, name, desc, signature, exceptions); 240 } 241 242 /* 243 * Gets class field information for step 4 of the alogrithm. Also determines 244 * if the class already has a SVUID. 245 */ visitField( final int access, final String name, final String desc, final String signature, final Object value)246 public FieldVisitor visitField( 247 final int access, 248 final String name, 249 final String desc, 250 final String signature, 251 final Object value) 252 { 253 if (computeSVUID) { 254 if (name.equals("serialVersionUID")) { 255 // since the class already has SVUID, we won't be computing it. 256 computeSVUID = false; 257 hasSVUID = true; 258 } 259 /* 260 * Remember field for SVUID computation For field modifiers, only 261 * the ACC_PUBLIC, ACC_PRIVATE, ACC_PROTECTED, ACC_STATIC, 262 * ACC_FINAL, ACC_VOLATILE, and ACC_TRANSIENT flags are used when 263 * computing serialVersionUID values. 264 */ 265 int mods = access 266 & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PRIVATE 267 | Opcodes.ACC_PROTECTED | Opcodes.ACC_STATIC 268 | Opcodes.ACC_FINAL | Opcodes.ACC_VOLATILE | Opcodes.ACC_TRANSIENT); 269 270 if (((access & Opcodes.ACC_PRIVATE) == 0) 271 || ((access & (Opcodes.ACC_STATIC | Opcodes.ACC_TRANSIENT)) == 0)) 272 { 273 svuidFields.add(new Item(name, mods, desc)); 274 } 275 } 276 277 return super.visitField(access, name, desc, signature, value); 278 } 279 280 /* 281 * Add the SVUID if class doesn't have one 282 */ visitEnd()283 public void visitEnd() { 284 // compute SVUID and add it to the class 285 if (computeSVUID && !hasSVUID) { 286 try { 287 cv.visitField(Opcodes.ACC_FINAL + Opcodes.ACC_STATIC, 288 "serialVersionUID", 289 "J", 290 null, 291 new Long(computeSVUID())); 292 } catch (Throwable e) { 293 throw new RuntimeException("Error while computing SVUID for " 294 + name, e); 295 } 296 } 297 298 super.visitEnd(); 299 } 300 301 // ------------------------------------------------------------------------ 302 // Utility methods 303 // ------------------------------------------------------------------------ 304 305 /** 306 * Returns the value of SVUID if the class doesn't have one already. Please 307 * note that 0 is returned if the class already has SVUID, thus use 308 * <code>isHasSVUID</code> to determine if the class already had an SVUID. 309 * 310 * @return Returns the serial version UID 311 * @throws IOException 312 */ computeSVUID()313 protected long computeSVUID() throws IOException { 314 if (hasSVUID) { 315 return 0; 316 } 317 318 ByteArrayOutputStream bos = null; 319 DataOutputStream dos = null; 320 long svuid = 0; 321 322 try { 323 bos = new ByteArrayOutputStream(); 324 dos = new DataOutputStream(bos); 325 326 /* 327 * 1. The class name written using UTF encoding. 328 */ 329 dos.writeUTF(name.replace('/', '.')); 330 331 /* 332 * 2. The class modifiers written as a 32-bit integer. 333 */ 334 dos.writeInt(access 335 & (Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL 336 | Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT)); 337 338 /* 339 * 3. The name of each interface sorted by name written using UTF 340 * encoding. 341 */ 342 Arrays.sort(interfaces); 343 for (int i = 0; i < interfaces.length; i++) { 344 dos.writeUTF(interfaces[i].replace('/', '.')); 345 } 346 347 /* 348 * 4. For each field of the class sorted by field name (except 349 * private static and private transient fields): 350 * 351 * 1. The name of the field in UTF encoding. 2. The modifiers of the 352 * field written as a 32-bit integer. 3. The descriptor of the field 353 * in UTF encoding 354 * 355 * Note that field signatutes are not dot separated. Method and 356 * constructor signatures are dot separated. Go figure... 357 */ 358 writeItems(svuidFields, dos, false); 359 360 /* 361 * 5. If a class initializer exists, write out the following: 1. The 362 * name of the method, <clinit>, in UTF encoding. 2. The modifier of 363 * the method, java.lang.reflect.Modifier.STATIC, written as a 364 * 32-bit integer. 3. The descriptor of the method, ()V, in UTF 365 * encoding. 366 */ 367 if (hasStaticInitializer) { 368 dos.writeUTF("<clinit>"); 369 dos.writeInt(Opcodes.ACC_STATIC); 370 dos.writeUTF("()V"); 371 } // if.. 372 373 /* 374 * 6. For each non-private constructor sorted by method name and 375 * signature: 1. The name of the method, <init>, in UTF encoding. 2. 376 * The modifiers of the method written as a 32-bit integer. 3. The 377 * descriptor of the method in UTF encoding. 378 */ 379 writeItems(svuidConstructors, dos, true); 380 381 /* 382 * 7. For each non-private method sorted by method name and 383 * signature: 1. The name of the method in UTF encoding. 2. The 384 * modifiers of the method written as a 32-bit integer. 3. The 385 * descriptor of the method in UTF encoding. 386 */ 387 writeItems(svuidMethods, dos, true); 388 389 dos.flush(); 390 391 /* 392 * 8. The SHA-1 algorithm is executed on the stream of bytes 393 * produced by DataOutputStream and produces five 32-bit values 394 * sha[0..4]. 395 */ 396 byte[] hashBytes = computeSHAdigest(bos.toByteArray()); 397 398 /* 399 * 9. The hash value is assembled from the first and second 32-bit 400 * values of the SHA-1 message digest. If the result of the message 401 * digest, the five 32-bit words H0 H1 H2 H3 H4, is in an array of 402 * five int values named sha, the hash value would be computed as 403 * follows: 404 * 405 * long hash = ((sha[0] >>> 24) & 0xFF) | ((sha[0] >>> 16) & 0xFF) << 406 * 8 | ((sha[0] >>> 8) & 0xFF) << 16 | ((sha[0] >>> 0) & 0xFF) << 407 * 24 | ((sha[1] >>> 24) & 0xFF) << 32 | ((sha[1] >>> 16) & 0xFF) << 408 * 40 | ((sha[1] >>> 8) & 0xFF) << 48 | ((sha[1] >>> 0) & 0xFF) << 409 * 56; 410 */ 411 for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) { 412 svuid = (svuid << 8) | (hashBytes[i] & 0xFF); 413 } 414 } finally { 415 // close the stream (if open) 416 if (dos != null) { 417 dos.close(); 418 } 419 } 420 421 return svuid; 422 } 423 424 /** 425 * Returns the SHA-1 message digest of the given value. 426 * 427 * @param value the value whose SHA message digest must be computed. 428 * @return the SHA-1 message digest of the given value. 429 */ computeSHAdigest(byte[] value)430 protected byte[] computeSHAdigest(byte[] value) { 431 try { 432 return MessageDigest.getInstance("SHA").digest(value); 433 } catch (Exception e) { 434 throw new UnsupportedOperationException(e); 435 } 436 } 437 438 /** 439 * Sorts the items in the collection and writes it to the data output stream 440 * 441 * @param itemCollection collection of items 442 * @param dos a <code>DataOutputStream</code> value 443 * @param dotted a <code>boolean</code> value 444 * @exception IOException if an error occurs 445 */ writeItems( final Collection itemCollection, final DataOutputStream dos, final boolean dotted)446 private void writeItems( 447 final Collection itemCollection, 448 final DataOutputStream dos, 449 final boolean dotted) throws IOException 450 { 451 int size = itemCollection.size(); 452 Item items[] = (Item[]) itemCollection.toArray(new Item[size]); 453 Arrays.sort(items); 454 for (int i = 0; i < size; i++) { 455 dos.writeUTF(items[i].name); 456 dos.writeInt(items[i].access); 457 dos.writeUTF(dotted 458 ? items[i].desc.replace('/', '.') 459 : items[i].desc); 460 } 461 } 462 463 // ------------------------------------------------------------------------ 464 // Inner classes 465 // ------------------------------------------------------------------------ 466 467 static class Item implements Comparable { 468 469 String name; 470 471 int access; 472 473 String desc; 474 Item(final String name, final int access, final String desc)475 Item(final String name, final int access, final String desc) { 476 this.name = name; 477 this.access = access; 478 this.desc = desc; 479 } 480 compareTo(final Object o)481 public int compareTo(final Object o) { 482 Item other = (Item) o; 483 int retVal = name.compareTo(other.name); 484 if (retVal == 0) { 485 retVal = desc.compareTo(other.desc); 486 } 487 return retVal; 488 } 489 } 490 } 491