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