KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > ch > ethz > prose > jvmai > jikesrvm > advice_weaver > MethodWeaver


1 package ch.ethz.prose.jvmai.jikesrvm.advice_weaver;
2
3 import java.io.*;
4 import java.lang.reflect.Field JavaDoc;
5 import java.lang.reflect.Method JavaDoc;
6 import java.util.*;
7
8 import org.apache.bcel.Constants;
9 import org.apache.bcel.Repository;
10 import org.apache.bcel.classfile.*;
11 import org.apache.bcel.generic.*;
12 import org.apache.bcel.verifier.*;
13
14 import com.ibm.JikesRVM.classloader.VM_ClassEvolution;
15 import com.ibm.JikesRVM.classloader.VM_Field;
16
17 /**
18  * Modifies a given method to support advice execution. For each activated join
19  * point an advice is woven into the bytecode of the method and then installed
20  * in the VM using the services of VM_ClassEvolution.
21  *
22  * @author Johann Gyger
23  */

24 public class MethodWeaver {
25
26   /**
27    * String representation of the JVMAI class.
28    */

29   public static final String JavaDoc JVMAI_CLASS = "ch.ethz.prose.jvmai.jikesrvm.advice_weaver.AdviceJVMAI";
30
31   /**
32    * Should the bytecodes be verified before installing them into the VM?
33    */

34   protected static final boolean verifyBytecodes;
35
36   static {
37     String JavaDoc p = System.getProperty("ch.ethz.prose.jikesrvm.MethodWeaver.verifyBytecodes", "");
38     verifyBytecodes = p.toLowerCase().equals("true");
39   }
40
41   /**
42    * Collection of all method weavers. For each method there exists exactly one
43    * method weaver in the system.
44    */

45   protected static Map weavers = new HashMap();
46
47   /**
48    * Get a unique method weaver for `target'.
49    *
50    * @param target method that will be woven
51    */

52   public static synchronized MethodWeaver getWeaver(Method JavaDoc target) {
53     if (target == null)
54       throw new NullPointerException JavaDoc("Parameter `target' must not be null.");
55
56     MethodWeaver result = (MethodWeaver) weavers.get(target.toString());
57     if (result == null) {
58       result = new MethodWeaver(target);
59       weavers.put(target.toString(), result);
60     }
61
62     return result;
63   };
64
65   /**
66    * Re-weave all modified methods and activate them in the VM.
67    */

68   public static synchronized void commit() {
69     Iterator it = weavers.values().iterator();
70     while (it.hasNext()) {
71       MethodWeaver mw = (MethodWeaver) it.next();
72       if (mw.modified) {
73         //System.out.println("MethodWeaver.commit(): " + mw.target);
74
mw.weave();
75       }
76     }
77
78     VM_ClassEvolution.commit();
79   }
80
81   /**
82    * Reset all woven methods.
83    */

84   public static synchronized void resetAll() {
85     Iterator it = weavers.values().iterator();
86     while (it.hasNext()) {
87       MethodWeaver mw = (MethodWeaver) it.next();
88       if (mw.woven) {
89         //System.out.println("MethodWeaver.resetAll(): " + mw.target);
90
VM_ClassEvolution.redefineMethod(mw.target, mw.originalCode);
91       }
92     }
93
94     VM_ClassEvolution.commit();
95     weavers = new HashMap();
96   }
97
98   /**
99    * Method bound to this weaver.
100    */

101   protected Method JavaDoc target;
102   
103   /**
104    * Method identifier;
105    */

106   protected int targetId;
107
108   /**
109    * Declaring class of `target'.
110    */

111   protected Class JavaDoc targetClass;
112
113   /**
114    * Original bytecode of `target'.
115    */

116   protected byte[] originalCode;
117
118   /**
119    * Redefine advice method.
120    */

121   protected Method JavaDoc redefineAdvice;
122
123   /**
124    * Is method entry join point activated?
125    */

126   protected boolean methodEntryEnabled;
127
128   /**
129    * Is method exit join point activated?
130    */

131   protected boolean methodExitEnabled;
132
133   /**
134    * Watched field accesses.
135    */

136   protected Map fieldAccessors = new HashMap();
137
138   /**
139    * Watched field modifications.
140    */

141   protected Map fieldModifiers = new HashMap();
142
143   /**
144    * Is a re-weaving of the method in this weaver necessary?
145    */

146   protected boolean modified;
147
148   /**
149    * Was the method in this weaver woven at least once?
150    */

151   protected boolean woven;
152
153   /**
154    * BCEL constant pool generator used during the weaving process.
155    */

156   protected ConstantPoolGen cpGen;
157
158   /**
159    * BCEL instruction factory used during the weaving process.
160    */

161   protected InstructionFactory instructionFactory;
162
163   /**
164    * BCEL method generator used during the weaving process.
165    */

166   protected MethodGen methodGen;
167
168   /**
169    * BCEL instructions that make up the method body (used during weaving
170    * process).
171    */

172   protected InstructionList instructions;
173
174   /**
175    * Create a new method weaver. Use the static method
176    * {@link #getWeaver(Method)}to obtain a weaver.
177    *
178    * @param target method that will be woven
179    */

180   protected MethodWeaver(Method JavaDoc target) {
181     this.target = target;
182     targetClass = target.getDeclaringClass();
183     targetId = java.lang.reflect.JikesRVMSupport.getMethodOf(target).getId();
184     originalCode = VM_ClassEvolution.getMethodCode(target);
185   }
186
187   /**
188    * Redefine the method in this weaver.
189    *
190    * @param advice method that will be called instead
191    */

192   public void setRedefineAdvice(Method JavaDoc advice) {
193     redefineAdvice = advice;
194     modified = true;
195   }
196
197   /**
198    * Enable method entry join point.
199    *
200    * @param flag enable/disable
201    */

202   public void setMethodEntryEnabled(boolean flag) {
203     methodEntryEnabled = flag;
204     modified = true;
205   }
206
207   /**
208    * Enable method entry join point.
209    *
210    * @param flag enable/disable
211    */

212   public void setMethodExitEnabled(boolean flag) {
213     methodExitEnabled = flag;
214     modified = true;
215   }
216
217   /**
218    * Add a field for which a callback should be woven (for each access).
219    *
220    * @param f watched field
221    */

222   public void addFieldAccessor(java.lang.reflect.Field JavaDoc f) {
223     String JavaDoc key = f.getDeclaringClass().getName() + "#" + f.getName();
224
225     if (!fieldAccessors.containsKey(key)) {
226       //System.out.println("MethodWeaver.addFieldAccessor(): " + target + " / " + f);
227
fieldAccessors.put(key, f);
228       modified = true;
229     }
230   }
231
232   /**
233    * Remove a field for which a callback was woven (for each access).
234    *
235    * @param f watched field
236    */

237   public void removeFieldAccessor(java.lang.reflect.Field JavaDoc f) {
238     String JavaDoc key = f.getDeclaringClass().getName() + "#" + f.getName();
239
240     if (fieldAccessors.containsKey(key)) {
241       fieldAccessors.remove(key);
242       modified = true;
243     }
244   }
245
246   /**
247    * Add a field for which a callback should be woven (for each modification).
248    *
249    * @param f watched field
250    */

251   public void addFieldModifier(java.lang.reflect.Field JavaDoc f) {
252     String JavaDoc key = f.getDeclaringClass().getName() + "#" + f.getName();
253
254     if (!fieldModifiers.containsKey(key)) {
255       //System.out.println("MethodWeaver.addFieldModifier(): " + target + " / " + f);
256
fieldModifiers.put(key, f);
257       modified = true;
258     }
259   }
260
261   /**
262    * Remove a field for which a callback was woven (for each modification).
263    *
264    * @param f watched field
265    */

266   public void removeFieldModifier(java.lang.reflect.Field JavaDoc f) {
267     String JavaDoc key = f.getDeclaringClass().getName() + "#" + f.getName();
268
269     if (fieldModifiers.containsKey(key)) {
270       fieldModifiers.remove(key);
271       modified = true;
272     }
273   }
274
275   public String JavaDoc debugString() {
276     StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
277
278     sb.append("MethodWeaver for: ");
279     sb.append(target);
280     sb.append("\n\tredefineAdvice: ");
281     sb.append(redefineAdvice);
282     sb.append("\n\tmethodEntryEnabled: ");
283     sb.append(methodEntryEnabled);
284     sb.append("\n\tmethodExitEnabled: ");
285     sb.append(methodExitEnabled);
286     sb.append("\n\tfieldAccessors: ");
287     sb.append(fieldAccessors.values());
288     sb.append("\n\tfieldModifiers: ");
289     sb.append(fieldModifiers.values());
290     sb.append("\n\tmodified: ");
291     sb.append(modified);
292
293     return sb.toString();
294   }
295
296   /**
297    * Weave advice that are associated with the method in this weaver.
298    */

299   protected void weave() {
300     initWeaving();
301
302     if (redefineAdvice != null)
303       weaveRedefineAdvice();
304
305     weaveFieldAdvice();
306
307     if (methodEntryEnabled)
308       weaveMethodEntryAdvice();
309
310     if (methodExitEnabled)
311       weaveMethodExitAdvice();
312
313     installBytecode();
314
315     finishWeaving();
316   }
317
318   /**
319    * Prepare for weaving by initializing the corresponding fields.
320    */

321   protected void initWeaving() {
322     byte[] code = VM_ClassEvolution.getConstantPoolCode(targetClass);
323     ConstantPool target_cp = ProseSupport.getConstantPool(code);
324     cpGen = new ConstantPoolGen(target_cp);
325     instructionFactory = new InstructionFactory(cpGen);
326     //System.out.println("MethodWever.initWeaving(): " + cpGen.getSize() + " entries in constant pool.");
327

328     org.apache.bcel.classfile.Method bm = ProseSupport.getMethod(originalCode, target_cp);
329     //System.out.println("MethodWeaver.initWeaving(): Redefining method: " + target);
330
//System.out.println(bm);
331
//System.out.println(bm.getCode());
332
methodGen = new MethodGen(bm, targetClass.getName(), cpGen);
333     instructions = methodGen.getInstructionList();
334   }
335
336   /**
337    * Clean up weaving process.
338    */

339   protected void finishWeaving() {
340     modified = false;
341     woven = true;
342     cpGen = null;
343     methodGen = null;
344     instructionFactory = null;
345     instructions = null;
346   }
347
348   /**
349    * Weave all registered field accesses/modifications.
350    */

351   protected void weaveFieldAdvice() {
352     for (InstructionHandle h = instructions.getStart(); h != null; h = h.getNext()) {
353       Instruction instr = h.getInstruction();
354
355       if (instr instanceof FieldInstruction) {
356         FieldInstruction fi = (FieldInstruction) instr;
357         String JavaDoc key = fi.getClassName(cpGen) + "#" + fi.getName(cpGen);
358
359         if ((instr instanceof GETSTATIC || instr instanceof GETFIELD) && fieldAccessors.containsKey(key)) {
360           Field JavaDoc field = (Field JavaDoc) fieldAccessors.get(key);
361           VM_Field vm_field = java.lang.reflect.JikesRVMSupport.getFieldOf(field);
362
363           //System.out.println("MethodWeaver.weaveFieldAdvice(): " + target + " GET " + field);
364
instructions.insert(h, createFieldAdviceCallback("onFieldAccess", vm_field));
365         } else if ((instr instanceof PUTSTATIC || instr instanceof PUTFIELD) && fieldModifiers.containsKey(key)) {
366           Field JavaDoc field = (Field JavaDoc) fieldModifiers.get(key);
367           VM_Field vm_field = java.lang.reflect.JikesRVMSupport.getFieldOf(field);
368
369           // Store new value
370
Type field_type = fi.getFieldType(cpGen);
371           int local_index = methodGen.getMaxLocals();
372           instructions.insert(h, InstructionFactory.createStore(field_type, local_index));
373           methodGen.setMaxLocals(local_index + field_type.getSize());
374
375           //System.out.println("MethodWeaver.weaveFieldAdvice(): " + target + " PUT " + field);
376
instructions.insert(h, createFieldAdviceCallback("onFieldModification", vm_field));
377
378           // Load new value
379
instructions.insert(h, InstructionFactory.createLoad(field_type, local_index));
380         }
381       }
382     }
383   }
384
385   /**
386    * Weave method redefine advice callback into method body and remove original
387    * code.
388    */

389   protected void weaveRedefineAdvice() {
390     //System.out.println("MethodWeaver.weaveRedefineAdvice(): " + target + " / " + redefineAdvice);
391
Class JavaDoc advice_class = redefineAdvice.getDeclaringClass();
392     byte[] cp_code = VM_ClassEvolution.getConstantPoolCode(advice_class);
393     ConstantPool advice_cp = ProseSupport.getConstantPool(cp_code);
394     ConstantPoolGen advice_cpg = new ConstantPoolGen(advice_cp);
395
396     byte[] advice_code = VM_ClassEvolution.getMethodCode(redefineAdvice);
397     org.apache.bcel.classfile.Method bm = ProseSupport.getMethod(advice_code, advice_cp);
398     MethodGen advice_mg = new MethodGen(bm, advice_class.getName(), advice_cpg);
399     instructions = advice_mg.getInstructionList();
400     methodGen.setInstructionList(instructions);
401     methodGen.setMaxLocals(advice_mg.getMaxLocals());
402
403     for (InstructionHandle h = instructions.getStart(); h != null; h = h.getNext()) {
404       Instruction instr = h.getInstruction();
405
406       // Transform local variable (and parameter) access.
407
// Decrement each index by 1 because the first parameter was stripped.
408
if (instr instanceof LocalVariableInstruction) {
409         LocalVariableInstruction lv_instr = (LocalVariableInstruction) instr;
410         if (lv_instr.getIndex() == 0)
411           throw new RuntimeException JavaDoc("No (implicit) `this' usage allowed in advice method: " + redefineAdvice);
412         lv_instr.setIndex(lv_instr.getIndex() - 1);
413       }
414
415       // Transform constant pool references to constant pool of target class.
416
// Add missing constant pool entries in target class.
417
if (instr instanceof CPInstruction) {
418         CPInstruction cp_instr = (CPInstruction) instr;
419         Constant c = advice_cp.getConstant(cp_instr.getIndex());
420         int new_index = cpGen.addConstant(c, advice_cpg);
421         cp_instr.setIndex(new_index);
422       }
423     }
424
425     // Curb exception handlers
426
methodGen.removeExceptionHandlers();
427     CodeExceptionGen[] cegs = advice_mg.getExceptionHandlers();
428     for (int i = 0; i < cegs.length; i++) {
429       CodeExceptionGen ceg = cegs[i];
430       methodGen.addExceptionHandler(ceg.getStartPC(), ceg.getEndPC(), ceg.getHandlerPC(), ceg.getCatchType());
431     }
432   }
433
434   /**
435    * Weave method entry advice callback into method body.
436    */

437   protected void weaveMethodEntryAdvice() {
438     //System.out.println("MethodWeaver.weaveMethodEntryAdvice(): " + target);
439
instructions.insert(createMethodAdviceCallback("onMethodEntry"));
440   }
441
442   /**
443    * Weave method exit advice callback into method body.
444    */

445   protected void weaveMethodExitAdvice() {
446     //System.out.println("MethodWeaver.weaveMethodExitAdvice(): " + target);
447
InstructionHandle try_start = instructions.getStart();
448     InstructionHandle try_end = instructions.getEnd();
449
450     // Weave method exit advice (in a finally block)
451
int local_index = methodGen.getMaxLocals();
452     InstructionHandle finally_start = instructions.append(new ASTORE(local_index));
453     instructions.append(createMethodAdviceCallback("onMethodExit"));
454     instructions.append(new RET(local_index++));
455
456     // Insert exception handler before finally block
457
InstructionHandle catch_start = instructions.insert(finally_start, new ASTORE(local_index));
458     instructions.insert(finally_start, new JSR(finally_start));
459     instructions.insert(finally_start, new ALOAD(local_index++));
460     instructions.insert(finally_start, new ATHROW());
461
462     // Jump to finally block before each return
463
JumpFinallyVisitor visitor = new JumpFinallyVisitor(instructions, try_start, try_end, finally_start, local_index);
464     visitor.go();
465     local_index += visitor.getLocalSize();
466
467     methodGen.setMaxLocals(local_index);
468     methodGen.addExceptionHandler(try_start, try_end, catch_start, null);
469   }
470
471   /**
472    * Create a method advice callback that can be woven.
473    *
474    * @param callbackMethod method that will be called
475    * @return instructions that make up the callback
476    */

477   protected InstructionList createMethodAdviceCallback(String JavaDoc callbackMethod) {
478     InstructionList il = new InstructionList();
479
480     // Push parameter: int methodId
481
il.append(new PUSH(cpGen, targetId));
482
483     // Push parameter: this0
484
if (methodGen.isStatic())
485       il.append(InstructionConstants.ACONST_NULL);
486     else
487       il.append(InstructionFactory.createThis());
488
489     // Push parameter: args
490
loadArgs(il);
491
492     // Call advice
493
il.append(
494       instructionFactory.createInvoke(
495         JVMAI_CLASS,
496         callbackMethod,
497         Type.VOID,
498         new Type[] { Type.INT, Type.OBJECT, new ArrayType(Type.OBJECT, 1)},
499         Constants.INVOKESTATIC));
500
501     return il;
502   }
503
504   /**
505    * Create a field advice callback that can be woven.
506    *
507    * @param callbackMethod method that will be called
508    * @param fieldId id of field
509    * @return instructions that make up the callback
510    */

511   protected InstructionList createFieldAdviceCallback(String JavaDoc callbackMethod, VM_Field field) {
512     InstructionList il = new InstructionList();
513
514     // Push parameter: Object owner
515
if (field.isStatic())
516       il.append(InstructionConstants.ACONST_NULL);
517     else
518       il.append(InstructionConstants.DUP);
519
520     // Push parameter: int fieldId
521
il.append(new PUSH(cpGen, field.getId()));
522
523     // Push parameter: int methodId
524
il.append(new PUSH(cpGen, targetId));
525
526     // Push parameter: Object this0
527
if (methodGen.isStatic())
528       il.append(InstructionConstants.ACONST_NULL);
529     else
530       il.append(InstructionFactory.createThis());
531
532     // Push parameter: Object[] args
533
loadArgs(il);
534
535     // Call advice
536
il.append(
537       instructionFactory.createInvoke(
538         JVMAI_CLASS,
539         callbackMethod,
540         Type.VOID,
541         new Type[] { Type.OBJECT, Type.INT, Type.INT, Type.OBJECT, new ArrayType(Type.OBJECT, 1)},
542         Constants.INVOKESTATIC));
543
544     return il;
545   }
546
547   /**
548    * Load all arguments.
549    *
550    * @param il list where instructions are appended
551    */

552   protected void loadArgs(InstructionList il) {
553     Type[] arg_types = methodGen.getArgumentTypes();
554
555     il.append(new PUSH(cpGen, arg_types.length));
556     il.append(instructionFactory.createNewArray(Type.OBJECT, (short) 1));
557
558     int varnum = methodGen.isStatic()? 0: 1;
559     for (int i = 0; i < methodGen.getArgumentTypes().length; i++) {
560       il.append(InstructionConstants.DUP);
561       il.append(new PUSH(cpGen, i));
562       loadArg(il, arg_types[i], varnum);
563       il.append(InstructionConstants.AASTORE);
564       varnum += arg_types[i].getSize();
565     }
566   }
567
568   /**
569    * Load argument at `index' of type `type'. Basic types are wrapped.
570    *
571    * @param il list where instructions are appended
572    * @param type argument type
573    * @param index argument index
574    */

575   protected void loadArg(InstructionList il, Type type, int index) {
576     if (type instanceof BasicType) {
577       String JavaDoc wrapper = Constants.CLASS_TYPE_NAMES[type.getType()];
578       il.append(instructionFactory.createNew(wrapper));
579       il.append(InstructionConstants.DUP);
580       il.append(InstructionFactory.createLoad(type, index));
581       il.append(
582         instructionFactory.createInvoke(
583           wrapper,
584           Constants.CONSTRUCTOR_NAME,
585           Type.VOID,
586           new Type[] { type },
587           Constants.INVOKESPECIAL));
588     } else if (type instanceof ReferenceType) {
589       il.append(InstructionFactory.createLoad(type, index));
590     } else {
591       throw new RuntimeException JavaDoc("Invalid type: " + type);
592     }
593   }
594
595   /**
596    * Install bytecode of woven method into the VM.
597    */

598   protected void installBytecode() {
599     try {
600       // Create new method. This will add missing constant pool entries.
601
methodGen.setMaxStack();
602       methodGen.setMaxLocals();
603       org.apache.bcel.classfile.Method bm = methodGen.getMethod();
604       instructions.dispose();
605       bm.getCode().setAttributes(null);
606
607       // Set new constant pool of target method's declaring class.
608
ByteArrayOutputStream bout = new ByteArrayOutputStream();
609       DataOutputStream out = new DataOutputStream(bout);
610       ConstantPool pool = cpGen.getFinalConstantPool();
611       if (verifyBytecodes)
612         verify(pool, bm);
613       pool.dump(out);
614       //System.out.println("MethodWeaver.installBytecode(): Extending constant pool: " + targetClass.getName() + " / " + pool.getLength() + " entries");
615
//System.out.println(pool);
616
VM_ClassEvolution.extendConstantPool(targetClass, bout.toByteArray());
617
618       // Set target method's new code.
619
bout = new ByteArrayOutputStream();
620       out = new DataOutputStream(bout);
621       bm.dump(out);
622       //System.out.println("MethodWeaver.installBytecode(): Redefining method: " + target);
623
//System.out.println(bm);
624
//System.out.println(bm.getCode());
625
VM_ClassEvolution.redefineMethod(target, bout.toByteArray());
626     } catch (IOException e) {
627       throw new RuntimeException JavaDoc("Oops.", e);
628     }
629   }
630   
631   /**
632    * Verify bytecodes with the BCEL verifier.
633    * <p>
634    * NOTE: Can't be used since even `normal' classes are rejected with Jikes
635    * RVM 2.3.0.1.
636    *
637    * @param cp constant pool that is used for verification
638    * @param bm method that is verified
639    */

640   protected void verify(ConstantPool cp, org.apache.bcel.classfile.Method bm) {
641     String JavaDoc class_name = target.getDeclaringClass().getName();
642     //System.out.println("MethodWeaver.verify(): " + target);
643

644     Repository.clearCache();
645     JavaClass jc = Repository.lookupClass(class_name);
646     jc.setConstantPool(cp);
647
648     org.apache.bcel.classfile.Method[] methods = jc.getMethods();
649     int method_index = 0;
650     for (method_index = 0; method_index < methods.length; method_index++)
651       if (methods[method_index].toString().equals(bm.toString()))
652         break;
653     if (method_index == methods.length)
654       throw new RuntimeException JavaDoc("Method not found!");
655     methods[method_index] = bm;
656
657     Verifier v = VerifierFactory.getVerifier(class_name);
658     checkVerificationResult(v.doPass1(), "1");
659     checkVerificationResult(v.doPass2(), "2");
660     checkVerificationResult(v.doPass3a(method_index), "3a");
661     checkVerificationResult(v.doPass3b(method_index), "3b");
662
663     String JavaDoc[] warnings = v.getMessages();
664     if (warnings.length != 0)
665       System.err.println("Messages:");
666     for (int j = 0; j < warnings.length; j++)
667       System.err.println(warnings[j]);
668   }
669
670   /**
671    * Check bytecode verification result.
672    *
673    * @param vr verification result which must be checked
674    * @param pass verification pass
675    */

676   protected void checkVerificationResult(VerificationResult vr, String JavaDoc pass) {
677     if (vr != VerificationResult.VR_OK) {
678       System.err.println("Verification failed in pass " + pass + " for " + target + ".");
679       System.err.println(vr);
680     }
681   }
682
683 }
684
Popular Tags