001: //
002: // This file is part of the prose package.
003: //
004: // The contents of this file are subject to the Mozilla Public License
005: // Version 1.1 (the "License"); you may not use this file except in
006: // compliance with the License. You may obtain a copy of the License at
007: // http://www.mozilla.org/MPL/
008: //
009: // Software distributed under the License is distributed on an "AS IS" basis,
010: // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
011: // for the specific language governing rights and limitations under the
012: // License.
013: //
014: // The Original Code is prose.
015: //
016: // The Initial Developer of the Original Code is Angela Nicoara. Portions
017: // created by Angela Nicoara are Copyright (C) 2002 Angela Nicoara.
018: // All Rights Reserved.
019: //
020: // Contributor(s):
021: // $Id$
022: // =====================================================================
023: //
024: // (history at end)
025: //
026:
027: package ch.ethz.prose.jvmai.jikesrvm.advice_weaver;
028:
029: import java.io.*;
030: import java.lang.reflect.Field;
031: import java.lang.reflect.Method;
032: import java.util.*;
033:
034: import org.apache.bcel.Constants;
035: import org.apache.bcel.Repository;
036: import org.apache.bcel.classfile.*;
037: import org.apache.bcel.generic.*;
038: import org.apache.bcel.verifier.*;
039:
040: import com.ibm.JikesRVM.classloader.VM_ClassEvolution;
041: import com.ibm.JikesRVM.classloader.VM_Field;
042:
043: /**
044: * Modifies a given method to support advice execution. For each activated join
045: * point an advice is woven into the bytecode of the method and then installed
046: * in the VM using the services of VM_ClassEvolution.
047: *
048: * @version $Revision$
049: * @author Johann Gyger
050: * @author Angela Nicoara
051: */
052: public class MethodWeaver {
053:
054: /**
055: * String representation of the JVMAI class.
056: */
057: public static final String JVMAI_CLASS = "ch.ethz.prose.jvmai.jikesrvm.advice_weaver.AdviceJVMAI";
058:
059: /**
060: * Should the bytecodes be verified before installing them into the VM?
061: */
062: protected static final boolean verifyBytecodes;
063:
064: static {
065: String p = System.getProperty(
066: "ch.ethz.prose.jikesrvm.MethodWeaver.verifyBytecodes",
067: "");
068: verifyBytecodes = p.toLowerCase().equals("true");
069: }
070:
071: /**
072: * Collection of all method weavers. For each method there exists exactly one
073: * method weaver in the system.
074: */
075: protected static Map weavers = new HashMap();
076:
077: /**
078: * Get a unique method weaver for `target'.
079: *
080: * @param target method that will be woven
081: */
082: public static synchronized MethodWeaver getWeaver(Method target) {
083: if (target == null)
084: throw new NullPointerException(
085: "Parameter `target' must not be null.");
086:
087: MethodWeaver result = (MethodWeaver) weavers.get(target
088: .toString());
089: if (result == null) {
090: result = new MethodWeaver(target);
091: weavers.put(target.toString(), result);
092: }
093:
094: return result;
095: };
096:
097: /**
098: * Re-weave all modified methods and activate them in the VM.
099: */
100: public static synchronized void commit() {
101: Iterator it = weavers.values().iterator();
102: while (it.hasNext()) {
103: MethodWeaver mw = (MethodWeaver) it.next();
104: if (mw.modified) {
105: //System.out.println("MethodWeaver.commit(): " + mw.target);
106: mw.weave();
107: }
108: }
109:
110: VM_ClassEvolution.commit();
111: }
112:
113: /**
114: * Reset all woven methods.
115: */
116: public static synchronized void resetAll() {
117: Iterator it = weavers.values().iterator();
118: while (it.hasNext()) {
119: MethodWeaver mw = (MethodWeaver) it.next();
120: if (mw.woven) {
121: //System.out.println("MethodWeaver.resetAll(): " + mw.target);
122: VM_ClassEvolution.redefineMethod(mw.target,
123: mw.originalCode);
124: }
125: }
126:
127: VM_ClassEvolution.commit();
128: weavers = new HashMap();
129: }
130:
131: /**
132: * Method bound to this weaver.
133: */
134: protected Method target;
135:
136: /**
137: * Method identifier;
138: */
139: protected int targetId;
140:
141: /**
142: * Declaring class of `target'.
143: */
144: protected Class targetClass;
145:
146: /**
147: * Original bytecode of `target'.
148: */
149: protected byte[] originalCode;
150:
151: /**
152: * Redefine advice method.
153: */
154: protected Method redefineAdvice;
155:
156: /**
157: * Is method entry join point activated?
158: */
159: protected boolean methodEntryEnabled;
160:
161: /**
162: * Is method exit join point activated?
163: */
164: protected boolean methodExitEnabled;
165:
166: /**
167: * Watched field accesses.
168: */
169: protected Map fieldAccessors = new HashMap();
170:
171: /**
172: * Watched field modifications.
173: */
174: protected Map fieldModifiers = new HashMap();
175:
176: /**
177: * Is a re-weaving of the method in this weaver necessary?
178: */
179: protected boolean modified;
180:
181: /**
182: * Was the method in this weaver woven at least once?
183: */
184: protected boolean woven;
185:
186: /**
187: * BCEL constant pool generator used during the weaving process.
188: */
189: protected ConstantPoolGen cpGen;
190:
191: /**
192: * BCEL instruction factory used during the weaving process.
193: */
194: protected InstructionFactory instructionFactory;
195:
196: /**
197: * BCEL method generator used during the weaving process.
198: */
199: protected MethodGen methodGen;
200:
201: /**
202: * BCEL instructions that make up the method body (used during weaving
203: * process).
204: */
205: protected InstructionList instructions;
206:
207: /**
208: * Create a new method weaver. Use the static method
209: * {@link #getWeaver(Method)}to obtain a weaver.
210: *
211: * @param target method that will be woven
212: */
213: protected MethodWeaver(Method target) {
214: this .target = target;
215: targetClass = target.getDeclaringClass();
216: targetId = java.lang.reflect.JikesRVMSupport
217: .getMethodOf(target).getId();
218: originalCode = VM_ClassEvolution.getMethodCode(target);
219: }
220:
221: /**
222: * Redefine the method in this weaver.
223: *
224: * @param advice method that will be called instead
225: */
226: public void setRedefineAdvice(Method advice) {
227: redefineAdvice = advice;
228: modified = true;
229: }
230:
231: /**
232: * Enable method entry join point.
233: *
234: * @param flag enable/disable
235: */
236: public void setMethodEntryEnabled(boolean flag) {
237: methodEntryEnabled = flag;
238: modified = true;
239: }
240:
241: /**
242: * Enable method entry join point.
243: *
244: * @param flag enable/disable
245: */
246: public void setMethodExitEnabled(boolean flag) {
247: methodExitEnabled = flag;
248: modified = true;
249: }
250:
251: /**
252: * Add a field for which a callback should be woven (for each access).
253: *
254: * @param f watched field
255: */
256: public void addFieldAccessor(java.lang.reflect.Field f) {
257: String key = f.getDeclaringClass().getName() + "#"
258: + f.getName();
259:
260: if (!fieldAccessors.containsKey(key)) {
261: //System.out.println("MethodWeaver.addFieldAccessor(): " + target + " / " + f);
262: fieldAccessors.put(key, f);
263: modified = true;
264: }
265: }
266:
267: /**
268: * Remove a field for which a callback was woven (for each access).
269: *
270: * @param f watched field
271: */
272: public void removeFieldAccessor(java.lang.reflect.Field f) {
273: String key = f.getDeclaringClass().getName() + "#"
274: + f.getName();
275:
276: if (fieldAccessors.containsKey(key)) {
277: fieldAccessors.remove(key);
278: modified = true;
279: }
280: }
281:
282: /**
283: * Add a field for which a callback should be woven (for each modification).
284: *
285: * @param f watched field
286: */
287: public void addFieldModifier(java.lang.reflect.Field f) {
288: String key = f.getDeclaringClass().getName() + "#"
289: + f.getName();
290:
291: if (!fieldModifiers.containsKey(key)) {
292: //System.out.println("MethodWeaver.addFieldModifier(): " + target + " / " + f);
293: fieldModifiers.put(key, f);
294: modified = true;
295: }
296: }
297:
298: /**
299: * Remove a field for which a callback was woven (for each modification).
300: *
301: * @param f watched field
302: */
303: public void removeFieldModifier(java.lang.reflect.Field f) {
304: String key = f.getDeclaringClass().getName() + "#"
305: + f.getName();
306:
307: if (fieldModifiers.containsKey(key)) {
308: fieldModifiers.remove(key);
309: modified = true;
310: }
311: }
312:
313: public String debugString() {
314: StringBuffer sb = new StringBuffer();
315:
316: sb.append("MethodWeaver for: ");
317: sb.append(target);
318: sb.append("\n\tredefineAdvice: ");
319: sb.append(redefineAdvice);
320: sb.append("\n\tmethodEntryEnabled: ");
321: sb.append(methodEntryEnabled);
322: sb.append("\n\tmethodExitEnabled: ");
323: sb.append(methodExitEnabled);
324: sb.append("\n\tfieldAccessors: ");
325: sb.append(fieldAccessors.values());
326: sb.append("\n\tfieldModifiers: ");
327: sb.append(fieldModifiers.values());
328: sb.append("\n\tmodified: ");
329: sb.append(modified);
330:
331: return sb.toString();
332: }
333:
334: /**
335: * Weave advice that are associated with the method in this weaver.
336: */
337: protected void weave() {
338: initWeaving();
339:
340: if (redefineAdvice != null)
341: weaveRedefineAdvice();
342:
343: weaveFieldAdvice();
344:
345: if (methodEntryEnabled)
346: weaveMethodEntryAdvice();
347:
348: if (methodExitEnabled)
349: weaveMethodExitAdvice();
350:
351: installBytecode();
352:
353: finishWeaving();
354: }
355:
356: /**
357: * Prepare for weaving by initializing the corresponding fields.
358: */
359: protected void initWeaving() {
360: byte[] code = VM_ClassEvolution
361: .getConstantPoolCode(targetClass);
362: ConstantPool target_cp = ProseSupport.getConstantPool(code);
363: cpGen = new ConstantPoolGen(target_cp);
364: instructionFactory = new InstructionFactory(cpGen);
365: //System.out.println("MethodWever.initWeaving(): " + cpGen.getSize() + " entries in constant pool.");
366:
367: org.apache.bcel.classfile.Method bm = ProseSupport.getMethod(
368: originalCode, target_cp);
369: //System.out.println("MethodWeaver.initWeaving(): Redefining method: " + target);
370: //System.out.println(bm);
371: //System.out.println(bm.getCode());
372: methodGen = new MethodGen(bm, targetClass.getName(), cpGen);
373: instructions = methodGen.getInstructionList();
374: }
375:
376: /**
377: * Clean up weaving process.
378: */
379: protected void finishWeaving() {
380: modified = false;
381: woven = true;
382: cpGen = null;
383: methodGen = null;
384: instructionFactory = null;
385: instructions = null;
386: }
387:
388: /**
389: * Weave all registered field accesses/modifications.
390: */
391: protected void weaveFieldAdvice() {
392: for (InstructionHandle h = instructions.getStart(); h != null; h = h
393: .getNext()) {
394: Instruction instr = h.getInstruction();
395:
396: if (instr instanceof FieldInstruction) {
397: FieldInstruction fi = (FieldInstruction) instr;
398: String key = fi.getClassName(cpGen) + "#"
399: + fi.getName(cpGen);
400:
401: if ((instr instanceof GETSTATIC || instr instanceof GETFIELD)
402: && fieldAccessors.containsKey(key)) {
403: Field field = (Field) fieldAccessors.get(key);
404: VM_Field vm_field = java.lang.reflect.JikesRVMSupport
405: .getFieldOf(field);
406:
407: //System.out.println("MethodWeaver.weaveFieldAdvice(): " + target + " GET " + field);
408: instructions.insert(h, createFieldAdviceCallback(
409: "onFieldAccess", vm_field));
410: } else if ((instr instanceof PUTSTATIC || instr instanceof PUTFIELD)
411: && fieldModifiers.containsKey(key)) {
412: Field field = (Field) fieldModifiers.get(key);
413: VM_Field vm_field = java.lang.reflect.JikesRVMSupport
414: .getFieldOf(field);
415:
416: // Store new value
417: Type field_type = fi.getFieldType(cpGen);
418: int local_index = methodGen.getMaxLocals();
419: instructions.insert(h, InstructionFactory
420: .createStore(field_type, local_index));
421: methodGen.setMaxLocals(local_index
422: + field_type.getSize());
423:
424: //System.out.println("MethodWeaver.weaveFieldAdvice(): " + target + " PUT " + field);
425: instructions.insert(h, createFieldAdviceCallback(
426: "onFieldModification", vm_field));
427:
428: // Load new value
429: instructions.insert(h, InstructionFactory
430: .createLoad(field_type, local_index));
431: }
432: }
433: }
434: }
435:
436: /**
437: * Weave method redefine advice callback into method body and remove original
438: * code.
439: */
440: protected void weaveRedefineAdvice() {
441: //System.out.println("MethodWeaver.weaveRedefineAdvice(): " + target + " / " + redefineAdvice);
442: Class advice_class = redefineAdvice.getDeclaringClass();
443: byte[] cp_code = VM_ClassEvolution
444: .getConstantPoolCode(advice_class);
445: ConstantPool advice_cp = ProseSupport.getConstantPool(cp_code);
446: ConstantPoolGen advice_cpg = new ConstantPoolGen(advice_cp);
447:
448: byte[] advice_code = VM_ClassEvolution
449: .getMethodCode(redefineAdvice);
450: org.apache.bcel.classfile.Method bm = ProseSupport.getMethod(
451: advice_code, advice_cp);
452: MethodGen advice_mg = new MethodGen(bm, advice_class.getName(),
453: advice_cpg);
454: instructions = advice_mg.getInstructionList();
455: methodGen.setInstructionList(instructions);
456: methodGen.setMaxLocals(advice_mg.getMaxLocals());
457:
458: for (InstructionHandle h = instructions.getStart(); h != null; h = h
459: .getNext()) {
460: Instruction instr = h.getInstruction();
461:
462: // Transform local variable (and parameter) access.
463: // Decrement each index by 1 because the first parameter was stripped.
464: if (instr instanceof LocalVariableInstruction) {
465: LocalVariableInstruction lv_instr = (LocalVariableInstruction) instr;
466: if (lv_instr.getIndex() == 0)
467: throw new RuntimeException(
468: "No (implicit) `this' usage allowed in advice method: "
469: + redefineAdvice);
470: lv_instr.setIndex(lv_instr.getIndex() - 1);
471: }
472:
473: // Transform constant pool references to constant pool of target class.
474: // Add missing constant pool entries in target class.
475: if (instr instanceof CPInstruction) {
476: CPInstruction cp_instr = (CPInstruction) instr;
477: Constant c = advice_cp.getConstant(cp_instr.getIndex());
478: int new_index = cpGen.addConstant(c, advice_cpg);
479: cp_instr.setIndex(new_index);
480: }
481: }
482:
483: // Curb exception handlers
484: methodGen.removeExceptionHandlers();
485: CodeExceptionGen[] cegs = advice_mg.getExceptionHandlers();
486: for (int i = 0; i < cegs.length; i++) {
487: CodeExceptionGen ceg = cegs[i];
488: methodGen
489: .addExceptionHandler(ceg.getStartPC(), ceg
490: .getEndPC(), ceg.getHandlerPC(), ceg
491: .getCatchType());
492: }
493: }
494:
495: /**
496: * Weave method entry advice callback into method body.
497: */
498: protected void weaveMethodEntryAdvice() {
499: //System.out.println("MethodWeaver.weaveMethodEntryAdvice(): " + target);
500: instructions
501: .insert(createMethodAdviceCallback("onMethodEntry"));
502: }
503:
504: /**
505: * Weave method exit advice callback into method body.
506: */
507: protected void weaveMethodExitAdvice() {
508: //System.out.println("MethodWeaver.weaveMethodExitAdvice(): " + target);
509: InstructionHandle try_start = instructions.getStart();
510: InstructionHandle try_end = instructions.getEnd();
511:
512: // Weave method exit advice (in a finally block)
513: int local_index = methodGen.getMaxLocals();
514: InstructionHandle finally_start = instructions
515: .append(new ASTORE(local_index));
516: instructions.append(createMethodAdviceCallback("onMethodExit"));
517: instructions.append(new RET(local_index++));
518:
519: // Insert exception handler before finally block
520: InstructionHandle catch_start = instructions.insert(
521: finally_start, new ASTORE(local_index));
522: instructions.insert(finally_start, new JSR(finally_start));
523: instructions.insert(finally_start, new ALOAD(local_index++));
524: instructions.insert(finally_start, new ATHROW());
525:
526: // Jump to finally block before each return
527: JumpFinallyVisitor visitor = new JumpFinallyVisitor(
528: instructions, try_start, try_end, finally_start,
529: local_index);
530: visitor.go();
531: local_index += visitor.getLocalSize();
532:
533: methodGen.setMaxLocals(local_index);
534: methodGen.addExceptionHandler(try_start, try_end, catch_start,
535: null);
536: }
537:
538: /**
539: * Create a method advice callback that can be woven.
540: *
541: * @param callbackMethod method that will be called
542: * @return instructions that make up the callback
543: */
544: protected InstructionList createMethodAdviceCallback(
545: String callbackMethod) {
546: InstructionList il = new InstructionList();
547:
548: // Push parameter: int methodId
549: il.append(new PUSH(cpGen, targetId));
550:
551: // Push parameter: this0
552: if (methodGen.isStatic())
553: il.append(InstructionConstants.ACONST_NULL);
554: else
555: il.append(InstructionFactory.createThis());
556:
557: // Push parameter: args
558: loadArgs(il);
559:
560: // Call advice
561: il.append(instructionFactory.createInvoke(JVMAI_CLASS,
562: callbackMethod, Type.VOID, new Type[] { Type.INT,
563: Type.OBJECT, new ArrayType(Type.OBJECT, 1) },
564: Constants.INVOKESTATIC));
565:
566: return il;
567: }
568:
569: /**
570: * Create a field advice callback that can be woven.
571: *
572: * @param callbackMethod method that will be called
573: * @param fieldId id of field
574: * @return instructions that make up the callback
575: */
576: protected InstructionList createFieldAdviceCallback(
577: String callbackMethod, VM_Field field) {
578: InstructionList il = new InstructionList();
579:
580: // Push parameter: Object owner
581: if (field.isStatic())
582: il.append(InstructionConstants.ACONST_NULL);
583: else
584: il.append(InstructionConstants.DUP);
585:
586: // Push parameter: int fieldId
587: il.append(new PUSH(cpGen, field.getId()));
588:
589: // Push parameter: int methodId
590: il.append(new PUSH(cpGen, targetId));
591:
592: // Push parameter: Object this0
593: if (methodGen.isStatic())
594: il.append(InstructionConstants.ACONST_NULL);
595: else
596: il.append(InstructionFactory.createThis());
597:
598: // Push parameter: Object[] args
599: loadArgs(il);
600:
601: // Call advice
602: il.append(instructionFactory.createInvoke(JVMAI_CLASS,
603: callbackMethod, Type.VOID, new Type[] { Type.OBJECT,
604: Type.INT, Type.INT, Type.OBJECT,
605: new ArrayType(Type.OBJECT, 1) },
606: Constants.INVOKESTATIC));
607:
608: return il;
609: }
610:
611: /**
612: * Load all arguments.
613: *
614: * @param il list where instructions are appended
615: */
616: protected void loadArgs(InstructionList il) {
617: Type[] arg_types = methodGen.getArgumentTypes();
618:
619: il.append(new PUSH(cpGen, arg_types.length));
620: il.append(instructionFactory.createNewArray(Type.OBJECT,
621: (short) 1));
622:
623: int varnum = methodGen.isStatic() ? 0 : 1;
624: for (int i = 0; i < methodGen.getArgumentTypes().length; i++) {
625: il.append(InstructionConstants.DUP);
626: il.append(new PUSH(cpGen, i));
627: loadArg(il, arg_types[i], varnum);
628: il.append(InstructionConstants.AASTORE);
629: varnum += arg_types[i].getSize();
630: }
631: }
632:
633: /**
634: * Load argument at `index' of type `type'. Basic types are wrapped.
635: *
636: * @param il list where instructions are appended
637: * @param type argument type
638: * @param index argument index
639: */
640: protected void loadArg(InstructionList il, Type type, int index) {
641: if (type instanceof BasicType) {
642: String wrapper = Constants.CLASS_TYPE_NAMES[type.getType()];
643: il.append(instructionFactory.createNew(wrapper));
644: il.append(InstructionConstants.DUP);
645: il.append(InstructionFactory.createLoad(type, index));
646: il.append(instructionFactory.createInvoke(wrapper,
647: Constants.CONSTRUCTOR_NAME, Type.VOID,
648: new Type[] { type }, Constants.INVOKESPECIAL));
649: } else if (type instanceof ReferenceType) {
650: il.append(InstructionFactory.createLoad(type, index));
651: } else {
652: throw new RuntimeException("Invalid type: " + type);
653: }
654: }
655:
656: /**
657: * Install bytecode of woven method into the VM.
658: */
659: protected void installBytecode() {
660: try {
661: // Create new method. This will add missing constant pool entries.
662: methodGen.setMaxStack();
663: methodGen.setMaxLocals();
664: org.apache.bcel.classfile.Method bm = methodGen.getMethod();
665: instructions.dispose();
666: bm.getCode().setAttributes(null);
667:
668: // Set new constant pool of target method's declaring class.
669: ByteArrayOutputStream bout = new ByteArrayOutputStream();
670: DataOutputStream out = new DataOutputStream(bout);
671: ConstantPool pool = cpGen.getFinalConstantPool();
672: if (verifyBytecodes)
673: verify(pool, bm);
674: pool.dump(out);
675: //System.out.println("MethodWeaver.installBytecode(): Extending constant pool: " + targetClass.getName() + " / " + pool.getLength() + " entries");
676: //System.out.println(pool);
677: VM_ClassEvolution.extendConstantPool(targetClass, bout
678: .toByteArray());
679:
680: // Set target method's new code.
681: bout = new ByteArrayOutputStream();
682: out = new DataOutputStream(bout);
683: bm.dump(out);
684: //System.out.println("MethodWeaver.installBytecode(): Redefining method: " + target);
685: //System.out.println(bm);
686: //System.out.println(bm.getCode());
687: VM_ClassEvolution
688: .redefineMethod(target, bout.toByteArray());
689: } catch (IOException e) {
690: throw new RuntimeException("Oops.", e);
691: }
692: }
693:
694: /**
695: * Verify bytecodes with the BCEL verifier.
696: * <p>
697: * NOTE: Can't be used since even `normal' classes are rejected with Jikes
698: * RVM 2.3.0.1.
699: *
700: * @param cp constant pool that is used for verification
701: * @param bm method that is verified
702: */
703: protected void verify(ConstantPool cp,
704: org.apache.bcel.classfile.Method bm) {
705: String class_name = target.getDeclaringClass().getName();
706: //System.out.println("MethodWeaver.verify(): " + target);
707:
708: try {
709: Repository.clearCache();
710: JavaClass jc = Repository.lookupClass(class_name);
711: jc.setConstantPool(cp);
712:
713: org.apache.bcel.classfile.Method[] methods = jc
714: .getMethods();
715: int method_index = 0;
716: for (method_index = 0; method_index < methods.length; method_index++)
717: if (methods[method_index].toString().equals(
718: bm.toString()))
719: break;
720: if (method_index == methods.length)
721: throw new RuntimeException("Method not found!");
722: methods[method_index] = bm;
723:
724: Verifier v = VerifierFactory.getVerifier(class_name);
725: checkVerificationResult(v.doPass1(), "1");
726: checkVerificationResult(v.doPass2(), "2");
727: checkVerificationResult(v.doPass3a(method_index), "3a");
728: checkVerificationResult(v.doPass3b(method_index), "3b");
729:
730: String[] warnings = v.getMessages();
731: if (warnings.length != 0)
732: System.err.println("Messages:");
733: for (int j = 0; j < warnings.length; j++)
734: System.err.println(warnings[j]);
735: } catch (Exception e) {
736: System.out.println("Exception: " + e.toString());
737: }
738: }
739:
740: /**
741: * Check bytecode verification result.
742: *
743: * @param vr verification result which must be checked
744: * @param pass verification pass
745: */
746: protected void checkVerificationResult(VerificationResult vr,
747: String pass) {
748: if (vr != VerificationResult.VR_OK) {
749: System.err.println("Verification failed in pass " + pass
750: + " for " + target + ".");
751: System.err.println(vr);
752: }
753: }
754:
755: }
756:
757: //======================================================================
758: //
759: // $Log$
760: //
|