001: /* ClassAction.java */
002: package org.quilt.cover.stmt;
003:
004: import java.util.List;
005: import java.util.Vector;
006:
007: import org.apache.bcel.classfile.Field;
008: import org.apache.bcel.classfile.Method;
009: import org.apache.bcel.generic.*;
010: import org.quilt.cl.ClassTransformer;
011: import org.quilt.cl.CodeVertex;
012: import org.apache.bcel.Constants;
013:
014: /**
015: * Add instrumentation at the class level, creating <clinit>
016: * if necessary. Three fields are added and initialized:
017: * <ul>
018: * <li><b>q$$q</b>, the int[] hit counts array
019: * <li><b>q$$qID</b>, a class identifier unique within this run
020: * <li><b>q$$qStmtReg</b>, reference to the StmtRegistry
021: * <li><b>q$$qVer</b>, a Quilt class file format version
022: * </ul>
023: *
024: * All of these fields are <em>public final static</em>. They are
025: * initialized by <code>clinit</code> when the class is loaded,
026: * running bytecode inserted by Quilt.
027: *
028: * @author <a href="mailto:jddixon@users.sourceforge.net">Jim Dixon</a>
029: */
030:
031: public class ClassAction implements org.quilt.cl.ClassXformer {
032:
033: // XFORMER VARIABLES ////////////////////////////////////////////
034: /** Name of the processor for use in reports. */
035: private static String name_ = null;
036:
037: /** The ClassTransformer that invoked this Xformer */
038: private ClassTransformer classTrans;
039:
040: /** The ClassGen we are working on. */
041: private ClassGen clazz_;
042:
043: /** its name */
044: private String className;
045:
046: /** the class name prefixed with "class$" XXX UNNECESSARY */
047: private String prefixedClassName;
048:
049: /** Its constant pool */
050: private ConstantPoolGen cpGen_;
051:
052: /** */
053: private InstructionFactory factory;
054:
055: /** Does a static initializer class exist? */
056: boolean clinitExists = false;
057:
058: /** Its index in the method array. */
059: int clinitIndex = -1;
060:
061: // COVERAGE-RELATED VARIABLES ///////////////////////////////////
062: /** Current statement coverage registry */
063: private static StmtRegistry stmtReg = null;
064:
065: /** temporary data shared between xformers */
066: private Ephemera eph;
067:
068: // CONSTRUCTORS /////////////////////////////////////////////////
069: public ClassAction() {
070: }
071:
072: public ClassAction(StmtRegistry reg) {
073: stmtReg = reg;
074: setName(this .getClass().getName()); // default name
075: }
076:
077: /**
078: * Passes a reference to the controlling ClassTransformer.
079: * XXX Inelegant - and unnecessary. XXX REWORK TO USED stmtReg
080: */
081: public void setClassTransformer(ClassTransformer ct) {
082: classTrans = ct;
083: }
084:
085: // PRE- AND POST-PROCESSING /////////////////////////////////////
086: /**
087: * Add a q$$q hit count field to the class using
088: * public static int [] q$$q;
089: * If there is already a field of this name, do not instrument
090: * the class.
091: *
092: * This is a preprocessor applied to the class before looking at
093: * methods. Any such preprocessors will be applied in the order of
094: * the ClassXformer vector.
095: *
096: * @param clazz ClassGen for the class being transformed.
097: */
098: public void preMethods(ClassGen clazz) {
099: clazz_ = clazz;
100: cpGen_ = clazz.getConstantPool();
101: className = clazz_.getClassName();
102: // I have tried this with class_ ; still isn't found
103: prefixedClassName = "class$QIC";
104: eph = new Ephemera(className);
105: if (!stmtReg.putEphemera(className, eph)) {
106: // XXX should throw exception
107: System.out
108: .println("ClassAction.preMethods INTERNAL ERRROR - "
109: + " couldn't register ephemeral data");
110: }
111: FieldGen field;
112: if (clazz.containsField("q$$q") != null) {
113: System.out.println("ClassAction.preMethods WARNING - "
114: + className + " already has q$$q field, aborting");
115: classTrans.abort();
116: } else {
117: // ADD: public static int [] q$$q
118: field = new FieldGen(Constants.ACC_PUBLIC
119: | Constants.ACC_STATIC, new ArrayType(Type.INT, 1),
120: "q$$q", cpGen_);
121: clazz.addField(field.getField());
122: // ADD: public static int q$$qID
123: field = new FieldGen(Constants.ACC_PUBLIC
124: | Constants.ACC_STATIC, Type.INT, "q$$qID", cpGen_);
125: clazz.addField(field.getField());
126: // ADD: public static final org.quilt.cover.stmt.StmtRegistry
127: field = new FieldGen(
128: Constants.ACC_PUBLIC | Constants.ACC_STATIC
129: | Constants.ACC_FINAL,
130: new ObjectType("org.quilt.cover.stmt.StmtRegistry"),
131: "q$$qStmtReg", cpGen_);
132: clazz.addField(field.getField());
133:
134: // ADD: public static int q$$qVer
135: field = new FieldGen(Constants.ACC_PUBLIC
136: | Constants.ACC_STATIC, Type.INT, "q$$qVer", cpGen_);
137: clazz.addField(field.getField());
138:
139: // ADD: public static class class$QIC (for QIC.class value)
140: field = new FieldGen(Constants.ACC_PUBLIC
141: | Constants.ACC_STATIC, new ObjectType(
142: "java.lang.Class"), "class$QIC", cpGen_);
143: clazz.addField(field.getField());
144:
145: // do we have a <clinit> ?
146: Method[] m = clazz.getMethods();
147: for (int i = 0; i < m.length; i++) {
148: if (m[i].getName().equals("<clinit>")) {
149: clinitExists = true;
150: clinitIndex = i;
151: break;
152: }
153: }
154: }
155: }
156:
157: private void dumpIList(InstructionList ilist, String where) {
158: if (ilist != null) {
159: System.out.println(where + ": instruction list");
160: int i = 0;
161: for (InstructionHandle ih = ilist.getStart(); ih != null; ih = ih
162: .getNext()) {
163: System.out.println(" " + (i++) + " " + ih);
164: }
165: }
166: }
167:
168: // the class$ method added by the Java compiler do deal with
169: // NAME.class constructs
170: // VIRTUALLY IDENTICAL TO BCEL DUMP /////////////////////////////
171: private void addClass$Method() {
172: InstructionList il = new InstructionList();
173: MethodGen method = new MethodGen(Constants.ACC_STATIC,
174: new ObjectType("java.lang.Class"),
175: new Type[] { Type.STRING }, new String[] { "arg0" },
176: "class$", className, il, cpGen_);
177:
178: // TRY BLOCK
179: InstructionHandle ih_0 = il.append(factory.createLoad(
180: Type.OBJECT, 0));
181: InstructionHandle ih_1 = il.append(factory.createInvoke(
182: "java.lang.Class", "forName", new ObjectType(
183: "java.lang.Class"), new Type[] { Type.STRING },
184: Constants.INVOKESTATIC));
185: il.append(factory.createReturn(Type.OBJECT));
186:
187: // CATCH BLOCK
188: InstructionHandle ih_5 = il.append(factory.createStore(
189: Type.OBJECT, 1));
190: InstructionHandle ih_6 = il.append(factory
191: .createNew("java.lang.NoClassDefFoundError"));
192: il.append(InstructionConstants.DUP);
193: il.append(factory.createLoad(Type.OBJECT, 1));
194: il.append(factory.createInvoke(
195: "java.lang.ClassNotFoundException", "getMessage",
196: Type.STRING, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
197: il.append(factory.createInvoke(
198: "java.lang.NoClassDefFoundError", "<init>", Type.VOID,
199: new Type[] { Type.STRING }, Constants.INVOKESPECIAL));
200: InstructionHandle ih_17 = il
201: .append(InstructionConstants.ATHROW);
202:
203: // EXCEPTION HANDLERS
204: method.addExceptionHandler(ih_0, ih_1, ih_5, new ObjectType(
205: "java.lang.ClassNotFoundException"));
206: method.setMaxStack();
207: method.setMaxLocals();
208: clazz_.addMethod(method.getMethod());
209: il.dispose();
210: } // END class$
211:
212: /**
213: * Postprocessor applied to the class after looking at methods.
214: * These will be applied in reverse order after completion of
215: * method processing.
216: */
217: public void postMethods(ClassGen clazz) {
218: int counterCount = eph.getCounterCount();
219: List methodNames = eph.getMethodNames();
220: List methodEnds = eph.getMethodEnds();
221: if (clazz != clazz_) {
222: // XXX modify to throw exception
223: System.out
224: .println("ClassAction.postMethods: INTERNAL ERROR:"
225: + " preMethods class different from postMethods");
226: }
227: factory = new InstructionFactory(clazz_, cpGen_);
228: addClass$Method(); // uses factory
229:
230: MethodGen mg;
231: InstructionList ilist;
232: InstructionHandle ih;
233: if (clinitExists) {
234: mg = new MethodGen(clazz_.getMethodAt(clinitIndex),
235: className, clazz_.getConstantPool());
236: ilist = mg.getInstructionList();
237: } else {
238: ilist = new InstructionList();
239: mg = new MethodGen(Constants.ACC_STATIC, Type.VOID,
240: Type.NO_ARGS, new String[] {}, "<clinit>",
241: className, ilist, clazz_.getConstantPool());
242: }
243: // //////////////////////////////////////////////////////////
244: // q$$q = new int[counterCount];
245: // //////////////////////////////////////////////////////////
246:
247: // the first instruction MUST be insert
248: ih = ilist.insert(new PUSH(cpGen_, counterCount));
249:
250: // dunno why, but the following produces an array of references; also
251: // // the cast should not be necessary, according to the Javadocs,
252: // // but there is a compilation error without it
253: // ih = ilist.append(ih, (Instruction)factory.createNewArray(
254: // new ArrayType(Type.INT, 1), (short)1));
255:
256: ih = ilist.append(ih, new NEWARRAY(Type.INT));
257:
258: ih = ilist.append(ih, factory
259: .createFieldAccess(className, "q$$q", new ArrayType(
260: Type.INT, 1), Constants.PUTSTATIC));
261:
262: /////////////////////////////////////////////////////////////
263: // q$$qVer = 0;
264: /////////////////////////////////////////////////////////////
265: ih = ilist.append(ih, new PUSH(cpGen_, 0));
266: ih = ilist.append(ih, factory.createFieldAccess(className,
267: "q$$qVer", Type.INT, Constants.PUTSTATIC));
268:
269: // //////////////////////////////////////////////////////////
270: // public final static StmtRegistry q$$qStmtRegistry
271: // = (StmtRegistry)
272: // (org.quilt.cl.QuiltClassLoader)QIC.class.getClassLoader())
273: // .getRegistry("org.quilt.cover.stmt.StmtRegistry");
274: // //////////////////////////////////////////////////////////
275:
276: // GET QIC.class //////////////////////////////////////
277: ih = ilist.append(ih, new PUSH(cpGen_, "org.quilt.QIC"));
278: ih = ilist.append(ih, factory.createInvoke(className, "class$",
279: new ObjectType("java.lang.Class"),
280: new Type[] { Type.STRING }, Constants.INVOKESTATIC));
281: // this two instructions are unnecessary
282: ih = ilist.append(ih, InstructionConstants.DUP);
283: ih = ilist.append(ih, factory.createFieldAccess(className,
284: "class$QIC", new ObjectType("java.lang.Class"),
285: Constants.PUTSTATIC));
286:
287: // get the class loader
288: ih = ilist.append(ih, factory.createInvoke("java.lang.Class",
289: "getClassLoader", new ObjectType(
290: "java.lang.ClassLoader"), Type.NO_ARGS,
291: Constants.INVOKEVIRTUAL));
292: // cast to QuiltClassLoader
293: ih = ilist.append(ih, factory.createCheckCast(new ObjectType(
294: "org.quilt.cl.QuiltClassLoader")));
295: // put method name on stack ...
296: ih = ilist.append(ih, new PUSH(cpGen_,
297: "org.quilt.cover.stmt.StmtRegistry"));
298: // invoke QuiltClassLoader.getRegistry("org.quilt.cover.stmt.S...")
299: ih = ilist.append(ih, factory.createInvoke(
300: "org.quilt.cl.QuiltClassLoader", "getRegistry",
301: new ObjectType("org.quilt.reg.QuiltRegistry"),
302: new Type[] { Type.STRING }, Constants.INVOKEVIRTUAL));
303: // cast to StmtRegistry
304: ih = ilist.append(ih, factory.createCheckCast(new ObjectType(
305: "org.quilt.cover.stmt.StmtRegistry")));
306: // save to q$$qStmtReg
307: ih = ilist.append(ih, factory.createFieldAccess(className,
308: "q$$qStmtReg", new ObjectType(
309: "org.quilt.cover.stmt.StmtRegistry"),
310: Constants.PUTSTATIC));
311:
312: /////////////////////////////////////////////////////////////
313: // q$$qID = q$$qStmtRegistry.registerCounts(className, q$$q);
314: /////////////////////////////////////////////////////////////
315:
316: ih = ilist.append(ih, factory.createFieldAccess(className,
317: "q$$qStmtReg", new ObjectType(
318: "org.quilt.cover.stmt.StmtRegistry"),
319: Constants.GETSTATIC));
320: ih = ilist.append(ih, new PUSH(cpGen_, className));
321: ih = ilist.append(ih, factory
322: .createFieldAccess(className, "q$$q", new ArrayType(
323: Type.INT, 1), Constants.GETSTATIC));
324: ih = ilist.append(ih, factory.createInvoke(
325: "org.quilt.cover.stmt.StmtRegistry", "registerCounts",
326: Type.INT, new Type[] { Type.STRING,
327: new ArrayType(Type.INT, 1) },
328: Constants.INVOKEVIRTUAL));
329: ih = ilist.append(ih, factory.createFieldAccess(className,
330: "q$$qID", Type.INT, Constants.PUTSTATIC));
331:
332: /////////////////////////////////////////////////////////////
333: // return;
334: /////////////////////////////////////////////////////////////
335: if (!clinitExists) {
336: ih = ilist.append(ih, factory.createReturn(Type.VOID));
337: }
338: ilist.setPositions();
339: mg.setMaxStack();
340: mg.setMaxLocals();
341:
342: boolean aborting = false;
343: if (clinitExists) {
344: /////////////////////////////////////////////////////////
345: // XXX KNOWN PROBLEM: error in setMethod if clinitExists
346: // probably because line number table not corrected
347: /////////////////////////////////////////////////////////
348: // aborting = true;
349: // classTrans.abort();
350: clazz_.setMethodAt(mg.getMethod(), clinitIndex);
351: } else {
352: clazz_.addMethod(mg.getMethod());
353: }
354: // ilist.dispose(); // when things are more stable ;-)
355:
356: // REGISTER method names and ends ///////////////////////////
357: if (!aborting) {
358: int len = methodNames.size();
359: String[] myNames = new String[len];
360: int[] myEndCounts = new int[len];
361:
362: for (int k = 0; k < len; k++) {
363: myNames[k] = (String) methodNames.get(k);
364: myEndCounts[k] = ((Integer) methodEnds.get(k))
365: .intValue();
366: }
367: stmtReg.registerMethods(className, myNames, myEndCounts);
368: } // if not aborting
369: stmtReg.removeEphemera(className);
370: }
371:
372: // OTHER METHODS ////////////////////////////////////////////////
373: /** Get the preprocessor's report name. */
374: public String getName() {
375: return name_;
376: }
377:
378: /** Set the preprocessor's name for reports. */
379: public void setName(String name) {
380: name_ = name;
381: }
382: }
|