View Javadoc
1 /* ClassAction.java */ 2 package org.quilt.cover.stmt; 3 4 import java.util.List; 5 import java.util.Vector; 6 7 import org.apache.bcel.classfile.Field; 8 import org.apache.bcel.classfile.Method; 9 import org.apache.bcel.generic.*; 10 import org.quilt.cl.ClassTransformer; 11 import org.quilt.cl.CodeVertex; 12 import org.apache.bcel.Constants; 13 14 /*** 15 * Add instrumentation at the class level, creating <clinit> 16 * if necessary. Three fields are added and initialized: 17 * <ul> 18 * <li><b>q$$q</b>, the int[] hit counts array 19 * <li><b>q$$qID</b>, a class identifier unique within this run 20 * <li><b>q$$qStmtReg</b>, reference to the StmtRegistry 21 * <li><b>q$$qVer</b>, a Quilt class file format version 22 * </ul> 23 * 24 * All of these fields are <em>public final static</em>. They are 25 * initialized by <code>clinit</code> when the class is loaded, 26 * running bytecode inserted by Quilt. 27 * 28 * @author <a href="mailto:jddixon@users.sourceforge.net">Jim Dixon</a> 29 */ 30 31 public class ClassAction implements org.quilt.cl.ClassXformer { 32 33 // XFORMER VARIABLES //////////////////////////////////////////// 34 /*** Name of the processor for use in reports. */ 35 private static String name_ = null; 36 37 /*** The ClassTransformer that invoked this Xformer */ 38 private ClassTransformer classTrans; 39 40 /*** The ClassGen we are working on. */ 41 private ClassGen clazz_; 42 43 /*** its name */ 44 private String className; 45 46 /*** the class name prefixed with "class$" XXX UNNECESSARY */ 47 private String prefixedClassName; 48 49 /*** Its constant pool */ 50 private ConstantPoolGen cpGen_; 51 52 /*** */ 53 private InstructionFactory factory; 54 55 /*** Does a static initializer class exist? */ 56 boolean clinitExists = false; 57 58 /*** Its index in the method array. */ 59 int clinitIndex = -1; 60 61 // COVERAGE-RELATED VARIABLES /////////////////////////////////// 62 /*** Current statement coverage registry */ 63 private static StmtRegistry stmtReg = null; 64 65 /*** temporary data shared between xformers */ 66 private Ephemera eph; 67 68 // CONSTRUCTORS ///////////////////////////////////////////////// 69 public ClassAction() { 70 } 71 public ClassAction (StmtRegistry reg) { 72 stmtReg = reg; 73 setName(this.getClass().getName()); // default name 74 } 75 76 /*** 77 * Passes a reference to the controlling ClassTransformer. 78 * XXX Inelegant - and unnecessary. XXX REWORK TO USED stmtReg 79 */ 80 public void setClassTransformer (ClassTransformer ct) { 81 classTrans = ct; 82 } 83 // PRE- AND POST-PROCESSING ///////////////////////////////////// 84 /*** 85 * Add a q$$q hit count field to the class using 86 * public static int [] q$$q; 87 * If there is already a field of this name, do not instrument 88 * the class. 89 * 90 * This is a preprocessor applied to the class before looking at 91 * methods. Any such preprocessors will be applied in the order of 92 * the ClassXformer vector. 93 * 94 * @param clazz ClassGen for the class being transformed. 95 */ 96 public void preMethods( ClassGen clazz ) { 97 clazz_ = clazz; 98 cpGen_ = clazz.getConstantPool(); 99 className = clazz_.getClassName(); 100 // I have tried this with class_ ; still isn't found 101 prefixedClassName = "class$QIC"; 102 eph = new Ephemera(className); 103 if (!stmtReg.putEphemera(className, eph)) { 104 // XXX should throw exception 105 System.out.println("ClassAction.preMethods INTERNAL ERRROR - " 106 + " couldn't register ephemeral data"); 107 } 108 FieldGen field; 109 if (clazz.containsField("q$$q") != null) { 110 System.out.println("ClassAction.preMethods WARNING - " 111 + className + " already has q$$q field, aborting"); 112 classTrans.abort(); 113 } else { 114 // ADD: public static int [] q$$q 115 field = new FieldGen(Constants.ACC_PUBLIC | Constants.ACC_STATIC, 116 new ArrayType(Type.INT, 1), "q$$q", cpGen_); 117 clazz.addField(field.getField()); 118 // ADD: public static int q$$qID 119 field = new FieldGen(Constants.ACC_PUBLIC | Constants.ACC_STATIC, 120 Type.INT, "q$$qID", cpGen_); 121 clazz.addField(field.getField()); 122 // ADD: public static final org.quilt.cover.stmt.StmtRegistry 123 field = new FieldGen(Constants.ACC_PUBLIC | Constants.ACC_STATIC 124 | Constants.ACC_FINAL, 125 new ObjectType("org.quilt.cover.stmt.StmtRegistry"), 126 "q$$qStmtReg", cpGen_); 127 clazz.addField(field.getField()); 128 129 // ADD: public static int q$$qVer 130 field = new FieldGen(Constants.ACC_PUBLIC | Constants.ACC_STATIC, 131 Type.INT, "q$$qVer", cpGen_); 132 clazz.addField(field.getField()); 133 134 // ADD: public static class class$QIC (for QIC.class value) 135 field = new FieldGen(Constants.ACC_PUBLIC |Constants.ACC_STATIC, 136 new ObjectType("java.lang.Class"), 137 "class$QIC", cpGen_); 138 clazz.addField(field.getField()); 139 140 // do we have a <clinit> ? 141 Method[] m = clazz.getMethods(); 142 for (int i = 0; i < m.length; i++) { 143 if (m[i].getName().equals("<clinit>")) { 144 clinitExists = true; 145 clinitIndex = i; 146 break; 147 } 148 } 149 } 150 } 151 152 private void dumpIList(InstructionList ilist, String where) { 153 if (ilist != null) { 154 System.out.println(where + ": instruction list"); 155 int i=0; 156 for (InstructionHandle ih = ilist.getStart(); ih != null; 157 ih = ih.getNext()) { 158 System.out.println(" " + (i++) + " " + ih); 159 } 160 } 161 } 162 163 // the class$ method added by the Java compiler do deal with 164 // NAME.class constructs 165 // VIRTUALLY IDENTICAL TO BCEL DUMP ///////////////////////////// 166 private void addClass$Method() { 167 InstructionList il = new InstructionList(); 168 MethodGen method = new MethodGen(Constants.ACC_STATIC, 169 new ObjectType("java.lang.Class"), new Type[] { Type.STRING }, 170 new String[] { "arg0" }, "class$", className, il, cpGen_); 171 172 // TRY BLOCK 173 InstructionHandle ih_0 = il.append(factory.createLoad(Type.OBJECT, 0)); 174 InstructionHandle ih_1 = il.append(factory 175 .createInvoke("java.lang.Class", "forName", 176 new ObjectType("java.lang.Class"), new Type[] { Type.STRING }, 177 Constants.INVOKESTATIC)); 178 il.append(factory.createReturn(Type.OBJECT)); 179 180 // CATCH BLOCK 181 InstructionHandle ih_5 = il.append(factory 182 .createStore(Type.OBJECT, 1)); 183 InstructionHandle ih_6 = il.append(factory 184 .createNew("java.lang.NoClassDefFoundError")); 185 il.append(InstructionConstants.DUP); 186 il.append(factory.createLoad(Type.OBJECT, 1)); 187 il.append(factory 188 .createInvoke("java.lang.ClassNotFoundException", "getMessage", 189 Type.STRING, Type.NO_ARGS, Constants.INVOKEVIRTUAL)); 190 il.append(factory 191 .createInvoke("java.lang.NoClassDefFoundError", "<init>", 192 Type.VOID, new Type[] { Type.STRING }, 193 Constants.INVOKESPECIAL)); 194 InstructionHandle ih_17 = il.append(InstructionConstants.ATHROW); 195 196 // EXCEPTION HANDLERS 197 method.addExceptionHandler(ih_0, ih_1, ih_5, 198 new ObjectType("java.lang.ClassNotFoundException")); 199 method.setMaxStack(); 200 method.setMaxLocals(); 201 clazz_.addMethod(method.getMethod()); 202 il.dispose(); 203 } // END class$ 204 /*** 205 * Postprocessor applied to the class after looking at methods. 206 * These will be applied in reverse order after completion of 207 * method processing. 208 */ 209 public void postMethods (ClassGen clazz ) { 210 int counterCount = eph.getCounterCount(); 211 List methodNames = eph.getMethodNames(); 212 List methodEnds = eph.getMethodEnds(); 213 if (clazz != clazz_) { 214 // XXX modify to throw exception 215 System.out.println("ClassAction.postMethods: INTERNAL ERROR:" 216 + " preMethods class different from postMethods"); 217 } 218 factory = new InstructionFactory (clazz_, cpGen_); 219 addClass$Method(); // uses factory 220 221 MethodGen mg; 222 InstructionList ilist; 223 InstructionHandle ih; 224 if (clinitExists) { 225 mg = new MethodGen (clazz_.getMethodAt(clinitIndex), 226 className, clazz_.getConstantPool() ); 227 ilist = mg.getInstructionList(); 228 } else { 229 ilist = new InstructionList(); 230 mg = new MethodGen ( Constants.ACC_STATIC, Type.VOID, Type.NO_ARGS, 231 new String[] {}, "<clinit>", className, 232 ilist, clazz_.getConstantPool() ); 233 } 234 // ////////////////////////////////////////////////////////// 235 // q$$q = new int[counterCount]; 236 // ////////////////////////////////////////////////////////// 237 238 // the first instruction MUST be insert 239 ih = ilist.insert(new PUSH (cpGen_, counterCount)); 240 241 // dunno why, but the following produces an array of references; also 242 // // the cast should not be necessary, according to the Javadocs, 243 // // but there is a compilation error without it 244 // ih = ilist.append(ih, (Instruction)factory.createNewArray( 245 // new ArrayType(Type.INT, 1), (short)1)); 246 247 ih = ilist.append(ih, new NEWARRAY(Type.INT)); 248 249 ih = ilist.append(ih, factory.createFieldAccess ( 250 className, "q$$q", 251 new ArrayType (Type.INT, 1), Constants.PUTSTATIC)); 252 253 ///////////////////////////////////////////////////////////// 254 // q$$qVer = 0; 255 ///////////////////////////////////////////////////////////// 256 ih = ilist.append(ih, new PUSH(cpGen_, 0)); 257 ih = ilist.append(ih, factory.createFieldAccess( 258 className, "q$$qVer", Type.INT, 259 Constants.PUTSTATIC)); 260 261 // ////////////////////////////////////////////////////////// 262 // public final static StmtRegistry q$$qStmtRegistry 263 // = (StmtRegistry) 264 // (org.quilt.cl.QuiltClassLoader)QIC.class.getClassLoader()) 265 // .getRegistry("org.quilt.cover.stmt.StmtRegistry"); 266 // ////////////////////////////////////////////////////////// 267 268 // GET QIC.class ////////////////////////////////////// 269 ih = ilist.append(ih, new PUSH(cpGen_, "org.quilt.QIC")); 270 ih = ilist.append(ih, factory.createInvoke(className, "class$", 271 new ObjectType("java.lang.Class"), new Type[] { Type.STRING }, 272 Constants.INVOKESTATIC)); 273 // this two instructions are unnecessary 274 ih = ilist.append(ih, InstructionConstants.DUP); 275 ih = ilist.append(ih, factory.createFieldAccess(className, 276 "class$QIC", new ObjectType("java.lang.Class"), 277 Constants.PUTSTATIC)); 278 279 // get the class loader 280 ih = ilist.append(ih, factory 281 .createInvoke("java.lang.Class", "getClassLoader", 282 new ObjectType("java.lang.ClassLoader"), Type.NO_ARGS, 283 Constants.INVOKEVIRTUAL)); 284 // cast to QuiltClassLoader 285 ih = ilist.append(ih, factory 286 .createCheckCast(new ObjectType("org.quilt.cl.QuiltClassLoader"))); 287 // put method name on stack ... 288 ih = ilist.append(ih, 289 new PUSH(cpGen_, "org.quilt.cover.stmt.StmtRegistry")); 290 // invoke QuiltClassLoader.getRegistry("org.quilt.cover.stmt.S...") 291 ih = ilist.append(ih, factory 292 .createInvoke("org.quilt.cl.QuiltClassLoader", "getRegistry", 293 new ObjectType("org.quilt.reg.QuiltRegistry"), 294 new Type[] { Type.STRING }, Constants.INVOKEVIRTUAL)); 295 // cast to StmtRegistry 296 ih = ilist.append(ih, factory 297 .createCheckCast( 298 new ObjectType("org.quilt.cover.stmt.StmtRegistry"))); 299 // save to q$$qStmtReg 300 ih = ilist.append(ih, factory 301 .createFieldAccess(className, "q$$qStmtReg", 302 new ObjectType("org.quilt.cover.stmt.StmtRegistry"), 303 Constants.PUTSTATIC)); 304 305 ///////////////////////////////////////////////////////////// 306 // q$$qID = q$$qStmtRegistry.registerCounts(className, q$$q); 307 ///////////////////////////////////////////////////////////// 308 309 ih = ilist.append(ih, factory.createFieldAccess( 310 className, "q$$qStmtReg", 311 new ObjectType("org.quilt.cover.stmt.StmtRegistry"), 312 Constants.GETSTATIC)); 313 ih = ilist.append(ih, new PUSH(cpGen_, className)); 314 ih = ilist.append(ih, factory.createFieldAccess ( 315 className, "q$$q", 316 new ArrayType (Type.INT, 1), Constants.GETSTATIC)); 317 ih = ilist.append(ih, factory.createInvoke( 318 "org.quilt.cover.stmt.StmtRegistry", "registerCounts", 319 Type.INT, 320 new Type[] { Type.STRING, new ArrayType(Type.INT,1) }, 321 Constants.INVOKEVIRTUAL)); 322 ih = ilist.append(ih, factory.createFieldAccess ( 323 className, "q$$qID", Type.INT, 324 Constants.PUTSTATIC)); 325 326 ///////////////////////////////////////////////////////////// 327 // return; 328 ///////////////////////////////////////////////////////////// 329 if (!clinitExists) { 330 ih = ilist.append(ih, factory.createReturn (Type.VOID)); 331 } 332 ilist.setPositions(); 333 mg.setMaxStack(); 334 mg.setMaxLocals(); 335 336 boolean aborting = false; 337 if (clinitExists) { 338 ///////////////////////////////////////////////////////// 339 // XXX KNOWN PROBLEM: error in setMethod if clinitExists 340 // probably because line number table not corrected 341 ///////////////////////////////////////////////////////// 342 // aborting = true; 343 // classTrans.abort(); 344 clazz_.setMethodAt(mg.getMethod(), clinitIndex); 345 } else { 346 clazz_.addMethod(mg.getMethod()); 347 } 348 // ilist.dispose(); // when things are more stable ;-) 349 350 // REGISTER method names and ends /////////////////////////// 351 if (!aborting) { 352 int len = methodNames.size(); 353 String [] myNames = new String [len]; 354 int [] myEndCounts = new int[len]; 355 356 for (int k = 0; k < len; k++) { 357 myNames[k] = (String) methodNames.get(k); 358 myEndCounts[k] = ((Integer)methodEnds. get(k)).intValue(); 359 } 360 stmtReg.registerMethods(className, myNames, myEndCounts); 361 } // if not aborting 362 stmtReg.removeEphemera(className); 363 } 364 // OTHER METHODS //////////////////////////////////////////////// 365 /*** Get the preprocessor's report name. */ 366 public String getName() { 367 return name_; 368 } 369 370 /*** Set the preprocessor's name for reports. */ 371 public void setName(String name) { 372 name_ = name; 373 } 374 }

This page was automatically generated by Maven