CheckClassAdapter.java revision 674060f01e9090cd21b3c5656cc3204912ad17a6
1/*** 2 * ASM: a very small and fast Java bytecode manipulation framework 3 * Copyright (c) 2000-2007 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 */ 30package org.mockito.asm.util; 31 32import java.io.FileInputStream; 33import java.io.PrintWriter; 34import java.util.List; 35 36import org.mockito.asm.AnnotationVisitor; 37import org.mockito.asm.Attribute; 38import org.mockito.asm.ClassAdapter; 39import org.mockito.asm.ClassReader; 40import org.mockito.asm.ClassVisitor; 41import org.mockito.asm.FieldVisitor; 42import org.mockito.asm.MethodVisitor; 43import org.mockito.asm.Opcodes; 44import org.mockito.asm.Type; 45import org.mockito.asm.tree.ClassNode; 46import org.mockito.asm.tree.MethodNode; 47import org.mockito.asm.tree.TryCatchBlockNode; 48import org.mockito.asm.tree.analysis.Analyzer; 49import org.mockito.asm.tree.analysis.Frame; 50import org.mockito.asm.tree.analysis.SimpleVerifier; 51 52/** 53 * A {@link ClassAdapter} that checks that its methods are properly used. More 54 * precisely this class adapter checks each method call individually, based 55 * <i>only</i> on its arguments, but does <i>not</i> check the <i>sequence</i> 56 * of method calls. For example, the invalid sequence 57 * <tt>visitField(ACC_PUBLIC, "i", "I", null)</tt> <tt>visitField(ACC_PUBLIC, 58 * "i", "D", null)</tt> 59 * will <i>not</i> be detected by this class adapter. 60 * 61 * <p><code>CheckClassAdapter</code> can be also used to verify bytecode 62 * transformations in order to make sure transformed bytecode is sane. For 63 * example: 64 * 65 * <pre> 66 * InputStream is = ...; // get bytes for the source class 67 * ClassReader cr = new ClassReader(is); 68 * ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS); 69 * ClassVisitor cv = new <b>MyClassAdapter</b>(new CheckClassAdapter(cw)); 70 * cr.accept(cv, 0); 71 * 72 * StringWriter sw = new StringWriter(); 73 * PrintWriter pw = new PrintWriter(sw); 74 * CheckClassAdapter.verify(new ClassReader(cw.toByteArray()), false, pw); 75 * assertTrue(sw.toString(), sw.toString().length()==0); 76 * </pre> 77 * 78 * Above code runs transformed bytecode trough the 79 * <code>CheckClassAdapter</code>. It won't be exactly the same verification 80 * as JVM does, but it run data flow analysis for the code of each method and 81 * checks that expectations are met for each method instruction. 82 * 83 * <p>If method bytecode has errors, assertion text will show the erroneous 84 * instruction number and dump of the failed method with information about 85 * locals and stack slot for each instruction. For example (format is - 86 * insnNumber locals : stack): 87 * 88 * <pre> 89 * org.mockito.asm.tree.analysis.AnalyzerException: Error at instruction 71: Expected I, but found . 90 * at org.mockito.asm.tree.analysis.Analyzer.analyze(Analyzer.java:289) 91 * at org.mockito.asm.util.CheckClassAdapter.verify(CheckClassAdapter.java:135) 92 * ... 93 * remove()V 94 * 00000 LinkedBlockingQueue$Itr . . . . . . . . : 95 * ICONST_0 96 * 00001 LinkedBlockingQueue$Itr . . . . . . . . : I 97 * ISTORE 2 98 * 00001 LinkedBlockingQueue$Itr <b>.</b> I . . . . . . : 99 * ... 100 * 101 * 00071 LinkedBlockingQueue$Itr <b>.</b> I . . . . . . : 102 * ILOAD 1 103 * 00072 <b>?</b> 104 * INVOKESPECIAL java/lang/Integer.<init> (I)V 105 * ... 106 * </pre> 107 * 108 * In the above output you can see that variable 1 loaded by 109 * <code>ILOAD 1</code> instruction at position <code>00071</code> is not 110 * initialized. You can also see that at the beginning of the method (code 111 * inserted by the transformation) variable 2 is initialized. 112 * 113 * <p>Note that when used like that, <code>CheckClassAdapter.verify()</code> 114 * can trigger additional class loading, because it is using 115 * <code>SimpleVerifier</code>. 116 * 117 * @author Eric Bruneton 118 */ 119public class CheckClassAdapter extends ClassAdapter { 120 121 /** 122 * <tt>true</tt> if the visit method has been called. 123 */ 124 private boolean start; 125 126 /** 127 * <tt>true</tt> if the visitSource method has been called. 128 */ 129 private boolean source; 130 131 /** 132 * <tt>true</tt> if the visitOuterClass method has been called. 133 */ 134 private boolean outer; 135 136 /** 137 * <tt>true</tt> if the visitEnd method has been called. 138 */ 139 private boolean end; 140 141 /** 142 * Checks a given class. <p> Usage: CheckClassAdapter <fully qualified 143 * class name or class file name> 144 * 145 * @param args the command line arguments. 146 * 147 * @throws Exception if the class cannot be found, or if an IO exception 148 * occurs. 149 */ 150 public static void main(final String[] args) throws Exception { 151 if (args.length != 1) { 152 System.err.println("Verifies the given class."); 153 System.err.println("Usage: CheckClassAdapter " 154 + "<fully qualified class name or class file name>"); 155 return; 156 } 157 ClassReader cr; 158 if (args[0].endsWith(".class")) { 159 cr = new ClassReader(new FileInputStream(args[0])); 160 } else { 161 cr = new ClassReader(args[0]); 162 } 163 164 verify(cr, false, new PrintWriter(System.err)); 165 } 166 167 /** 168 * Checks a given class 169 * 170 * @param cr a <code>ClassReader</code> that contains bytecode for the 171 * analysis. 172 * @param dump true if bytecode should be printed out not only when errors 173 * are found. 174 * @param pw write where results going to be printed 175 */ 176 public static void verify( 177 final ClassReader cr, 178 final boolean dump, 179 final PrintWriter pw) 180 { 181 ClassNode cn = new ClassNode(); 182 cr.accept(new CheckClassAdapter(cn), ClassReader.SKIP_DEBUG); 183 184 Type syperType = cn.superName == null 185 ? null 186 : Type.getObjectType(cn.superName); 187 List methods = cn.methods; 188 for (int i = 0; i < methods.size(); ++i) { 189 MethodNode method = (MethodNode) methods.get(i); 190 Analyzer a = new Analyzer(new SimpleVerifier(Type.getObjectType(cn.name), 191 syperType, 192 false)); 193 try { 194 a.analyze(cn.name, method); 195 if (!dump) { 196 continue; 197 } 198 } catch (Exception e) { 199 e.printStackTrace(pw); 200 } 201 Frame[] frames = a.getFrames(); 202 203 TraceMethodVisitor mv = new TraceMethodVisitor(); 204 205 pw.println(method.name + method.desc); 206 for (int j = 0; j < method.instructions.size(); ++j) { 207 method.instructions.get(j).accept(mv); 208 209 StringBuffer s = new StringBuffer(); 210 Frame f = frames[j]; 211 if (f == null) { 212 s.append('?'); 213 } else { 214 for (int k = 0; k < f.getLocals(); ++k) { 215 s.append(getShortName(f.getLocal(k).toString())) 216 .append(' '); 217 } 218 s.append(" : "); 219 for (int k = 0; k < f.getStackSize(); ++k) { 220 s.append(getShortName(f.getStack(k).toString())) 221 .append(' '); 222 } 223 } 224 while (s.length() < method.maxStack + method.maxLocals + 1) { 225 s.append(' '); 226 } 227 pw.print(Integer.toString(j + 100000).substring(1)); 228 pw.print(" " + s + " : " + mv.buf); // mv.text.get(j)); 229 } 230 for (int j = 0; j < method.tryCatchBlocks.size(); ++j) { 231 ((TryCatchBlockNode) method.tryCatchBlocks.get(j)).accept(mv); 232 pw.print(" " + mv.buf); 233 } 234 pw.println(); 235 } 236 pw.flush(); 237 } 238 239 private static String getShortName(final String name) { 240 int n = name.lastIndexOf('/'); 241 int k = name.length(); 242 if (name.charAt(k - 1) == ';') { 243 k--; 244 } 245 return n == -1 ? name : name.substring(n + 1, k); 246 } 247 248 /** 249 * Constructs a new {@link CheckClassAdapter}. 250 * 251 * @param cv the class visitor to which this adapter must delegate calls. 252 */ 253 public CheckClassAdapter(final ClassVisitor cv) { 254 super(cv); 255 } 256 257 // ------------------------------------------------------------------------ 258 // Implementation of the ClassVisitor interface 259 // ------------------------------------------------------------------------ 260 261 public void visit( 262 final int version, 263 final int access, 264 final String name, 265 final String signature, 266 final String superName, 267 final String[] interfaces) 268 { 269 if (start) { 270 throw new IllegalStateException("visit must be called only once"); 271 } 272 start = true; 273 checkState(); 274 checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL 275 + Opcodes.ACC_SUPER + Opcodes.ACC_INTERFACE 276 + Opcodes.ACC_ABSTRACT + Opcodes.ACC_SYNTHETIC 277 + Opcodes.ACC_ANNOTATION + Opcodes.ACC_ENUM 278 + Opcodes.ACC_DEPRECATED); 279 if (name == null || !name.endsWith("package-info")) { 280 CheckMethodAdapter.checkInternalName(name, "class name"); 281 } 282 if ("java/lang/Object".equals(name)) { 283 if (superName != null) { 284 throw new IllegalArgumentException("The super class name of the Object class must be 'null'"); 285 } 286 } else { 287 CheckMethodAdapter.checkInternalName(superName, "super class name"); 288 } 289 if (signature != null) { 290 CheckMethodAdapter.checkClassSignature(signature); 291 } 292 if ((access & Opcodes.ACC_INTERFACE) != 0) { 293 if (!"java/lang/Object".equals(superName)) { 294 throw new IllegalArgumentException("The super class name of interfaces must be 'java/lang/Object'"); 295 } 296 } 297 if (interfaces != null) { 298 for (int i = 0; i < interfaces.length; ++i) { 299 CheckMethodAdapter.checkInternalName(interfaces[i], 300 "interface name at index " + i); 301 } 302 } 303 cv.visit(version, access, name, signature, superName, interfaces); 304 } 305 306 public void visitSource(final String file, final String debug) { 307 checkState(); 308 if (source) { 309 throw new IllegalStateException("visitSource can be called only once."); 310 } 311 source = true; 312 cv.visitSource(file, debug); 313 } 314 315 public void visitOuterClass( 316 final String owner, 317 final String name, 318 final String desc) 319 { 320 checkState(); 321 if (outer) { 322 throw new IllegalStateException("visitOuterClass can be called only once."); 323 } 324 outer = true; 325 if (owner == null) { 326 throw new IllegalArgumentException("Illegal outer class owner"); 327 } 328 if (desc != null) { 329 CheckMethodAdapter.checkMethodDesc(desc); 330 } 331 cv.visitOuterClass(owner, name, desc); 332 } 333 334 public void visitInnerClass( 335 final String name, 336 final String outerName, 337 final String innerName, 338 final int access) 339 { 340 checkState(); 341 CheckMethodAdapter.checkInternalName(name, "class name"); 342 if (outerName != null) { 343 CheckMethodAdapter.checkInternalName(outerName, "outer class name"); 344 } 345 if (innerName != null) { 346 CheckMethodAdapter.checkIdentifier(innerName, "inner class name"); 347 } 348 checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_PRIVATE 349 + Opcodes.ACC_PROTECTED + Opcodes.ACC_STATIC 350 + Opcodes.ACC_FINAL + Opcodes.ACC_INTERFACE 351 + Opcodes.ACC_ABSTRACT + Opcodes.ACC_SYNTHETIC 352 + Opcodes.ACC_ANNOTATION + Opcodes.ACC_ENUM); 353 cv.visitInnerClass(name, outerName, innerName, access); 354 } 355 356 public FieldVisitor visitField( 357 final int access, 358 final String name, 359 final String desc, 360 final String signature, 361 final Object value) 362 { 363 checkState(); 364 checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_PRIVATE 365 + Opcodes.ACC_PROTECTED + Opcodes.ACC_STATIC 366 + Opcodes.ACC_FINAL + Opcodes.ACC_VOLATILE 367 + Opcodes.ACC_TRANSIENT + Opcodes.ACC_SYNTHETIC 368 + Opcodes.ACC_ENUM + Opcodes.ACC_DEPRECATED); 369 CheckMethodAdapter.checkIdentifier(name, "field name"); 370 CheckMethodAdapter.checkDesc(desc, false); 371 if (signature != null) { 372 CheckMethodAdapter.checkFieldSignature(signature); 373 } 374 if (value != null) { 375 CheckMethodAdapter.checkConstant(value); 376 } 377 FieldVisitor av = cv.visitField(access, name, desc, signature, value); 378 return new CheckFieldAdapter(av); 379 } 380 381 public MethodVisitor visitMethod( 382 final int access, 383 final String name, 384 final String desc, 385 final String signature, 386 final String[] exceptions) 387 { 388 checkState(); 389 checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_PRIVATE 390 + Opcodes.ACC_PROTECTED + Opcodes.ACC_STATIC 391 + Opcodes.ACC_FINAL + Opcodes.ACC_SYNCHRONIZED 392 + Opcodes.ACC_BRIDGE + Opcodes.ACC_VARARGS + Opcodes.ACC_NATIVE 393 + Opcodes.ACC_ABSTRACT + Opcodes.ACC_STRICT 394 + Opcodes.ACC_SYNTHETIC + Opcodes.ACC_DEPRECATED); 395 CheckMethodAdapter.checkMethodIdentifier(name, "method name"); 396 CheckMethodAdapter.checkMethodDesc(desc); 397 if (signature != null) { 398 CheckMethodAdapter.checkMethodSignature(signature); 399 } 400 if (exceptions != null) { 401 for (int i = 0; i < exceptions.length; ++i) { 402 CheckMethodAdapter.checkInternalName(exceptions[i], 403 "exception name at index " + i); 404 } 405 } 406 return new CheckMethodAdapter(cv.visitMethod(access, 407 name, 408 desc, 409 signature, 410 exceptions)); 411 } 412 413 public AnnotationVisitor visitAnnotation( 414 final String desc, 415 final boolean visible) 416 { 417 checkState(); 418 CheckMethodAdapter.checkDesc(desc, false); 419 return new CheckAnnotationAdapter(cv.visitAnnotation(desc, visible)); 420 } 421 422 public void visitAttribute(final Attribute attr) { 423 checkState(); 424 if (attr == null) { 425 throw new IllegalArgumentException("Invalid attribute (must not be null)"); 426 } 427 cv.visitAttribute(attr); 428 } 429 430 public void visitEnd() { 431 checkState(); 432 end = true; 433 cv.visitEnd(); 434 } 435 436 // ------------------------------------------------------------------------ 437 // Utility methods 438 // ------------------------------------------------------------------------ 439 440 /** 441 * Checks that the visit method has been called and that visitEnd has not 442 * been called. 443 */ 444 private void checkState() { 445 if (!start) { 446 throw new IllegalStateException("Cannot visit member before visit has been called."); 447 } 448 if (end) { 449 throw new IllegalStateException("Cannot visit member after visitEnd has been called."); 450 } 451 } 452 453 /** 454 * Checks that the given access flags do not contain invalid flags. This 455 * method also checks that mutually incompatible flags are not set 456 * simultaneously. 457 * 458 * @param access the access flags to be checked 459 * @param possibleAccess the valid access flags. 460 */ 461 static void checkAccess(final int access, final int possibleAccess) { 462 if ((access & ~possibleAccess) != 0) { 463 throw new IllegalArgumentException("Invalid access flags: " 464 + access); 465 } 466 int pub = (access & Opcodes.ACC_PUBLIC) == 0 ? 0 : 1; 467 int pri = (access & Opcodes.ACC_PRIVATE) == 0 ? 0 : 1; 468 int pro = (access & Opcodes.ACC_PROTECTED) == 0 ? 0 : 1; 469 if (pub + pri + pro > 1) { 470 throw new IllegalArgumentException("public private and protected are mutually exclusive: " 471 + access); 472 } 473 int fin = (access & Opcodes.ACC_FINAL) == 0 ? 0 : 1; 474 int abs = (access & Opcodes.ACC_ABSTRACT) == 0 ? 0 : 1; 475 if (fin + abs > 1) { 476 throw new IllegalArgumentException("final and abstract are mutually exclusive: " 477 + access); 478 } 479 } 480} 481