View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.javaflow.bytecode.transformation.bcel;
18  
19  import org.apache.bcel.Constants;
20  import org.apache.bcel.Repository;
21  import org.apache.bcel.classfile.Attribute;
22  import org.apache.bcel.classfile.ClassFormatException;
23  import org.apache.bcel.classfile.ClassParser;
24  import org.apache.bcel.classfile.ConstantCP;
25  import org.apache.bcel.classfile.ConstantNameAndType;
26  import org.apache.bcel.classfile.ConstantPool;
27  import org.apache.bcel.classfile.ConstantUtf8;
28  import org.apache.bcel.classfile.JavaClass;
29  import org.apache.bcel.classfile.Method;
30  import org.apache.bcel.generic.ACONST_NULL;
31  import org.apache.bcel.generic.ASTORE;
32  import org.apache.bcel.generic.BasicType;
33  import org.apache.bcel.generic.ClassGen;
34  import org.apache.bcel.generic.ConstantPoolGen;
35  import org.apache.bcel.generic.GOTO;
36  import org.apache.bcel.generic.IFEQ;
37  import org.apache.bcel.generic.IFNULL;
38  import org.apache.bcel.generic.INVOKESTATIC;
39  import org.apache.bcel.generic.Instruction;
40  import org.apache.bcel.generic.InstructionConstants;
41  import org.apache.bcel.generic.InstructionFactory;
42  import org.apache.bcel.generic.InstructionHandle;
43  import org.apache.bcel.generic.InstructionList;
44  import org.apache.bcel.generic.InstructionTargeter;
45  import org.apache.bcel.generic.InvokeInstruction;
46  import org.apache.bcel.generic.MethodGen;
47  import org.apache.bcel.generic.ObjectType;
48  import org.apache.bcel.generic.PUSH;
49  import org.apache.bcel.generic.RET;
50  import org.apache.bcel.generic.ReferenceType;
51  import org.apache.bcel.generic.TABLESWITCH;
52  import org.apache.bcel.generic.TargetLostException;
53  import org.apache.bcel.generic.Type;
54  import org.apache.bcel.verifier.exc.AssertionViolatedException;
55  import org.apache.commons.javaflow.bytecode.Continuable;
56  import org.apache.commons.javaflow.bytecode.StackRecorder;
57  import org.apache.commons.javaflow.bytecode.transformation.ResourceTransformer;
58  import org.apache.commons.javaflow.bytecode.transformation.bcel.analyser.ControlFlowGraph;
59  import org.apache.commons.javaflow.bytecode.transformation.bcel.analyser.ExceptionHandler;
60  import org.apache.commons.javaflow.bytecode.transformation.bcel.analyser.ExecutionPath;
61  import org.apache.commons.javaflow.bytecode.transformation.bcel.analyser.ExecutionVisitor;
62  import org.apache.commons.javaflow.bytecode.transformation.bcel.analyser.Frame;
63  import org.apache.commons.javaflow.bytecode.transformation.bcel.analyser.InstructionContext;
64  import org.apache.commons.javaflow.bytecode.transformation.bcel.analyser.LocalVariables;
65  import org.apache.commons.javaflow.bytecode.transformation.bcel.analyser.OperandStack;
66  import org.apache.commons.javaflow.bytecode.transformation.bcel.analyser.UninitializedObjectType;
67  import org.apache.commons.logging.Log;
68  import org.apache.commons.logging.LogFactory;
69  
70  import java.io.ByteArrayInputStream;
71  import java.io.FileOutputStream;
72  import java.io.IOException;
73  import java.io.InputStream;
74  import java.util.Vector;
75  
76  
77  /**
78   * {@link ResourceTransformer} that uses BCEL.
79   *
80   * @author tcurdt
81   * @author Kohsuke Kawaguchi
82   */
83  public final class BcelClassTransformer implements ResourceTransformer {
84  
85      private final static Log log = LogFactory.getLog(BcelClassTransformer.class);
86  
87      private static final String STACK_RECORDER_CLASS = StackRecorder.class.getName();
88      private static final ObjectType STACK_RECORDER_TYPE = new ObjectType(STACK_RECORDER_CLASS);
89      private static final String CONTINUABLE_CLASS = Continuable.class.getName();
90      private static final String STACK_METHOD = "get";
91      private static final String POP_METHOD = "pop";
92      private static final String PUSH_METHOD = "push";
93      private static final String RESTORING_FIELD = "isRestoring";
94      private static final String CAPTURING_FIELD = "isCapturing";
95  
96      private boolean currentMethodStatic = false;
97      public static boolean debug = false;
98  
99      /**
100      * BCEL uses a repository object to load/store information related to other classes,
101      * which are sometimes necessary to perform bytecode instrumentation.
102      * <p>
103      * Can be null, in which case the global BCEL repository is assumed to work.
104      *
105      * <p>
106      * repository is a singleton instance in BCEL, so we have to be careful when changing it.
107      * See BugZilla 38057. This is fundamentally broken.
108      */
109     private org.apache.bcel.util.Repository repository;
110 
111     /**
112      * Since BCEL repository is a global resource, access needs to be controlled among
113      * {@link BcelClassTransformer} instances.
114      */
115     private static final Object repositoryLock = new Object();
116 
117     static {
118         try {
119             debug = System.getProperty(BcelClassTransformer.class.getName()+".debug")!=null;
120         } catch (SecurityException e) {
121             // assume no debugging
122         }
123     }
124 
125     public BcelClassTransformer() {
126     }
127 
128     public BcelClassTransformer(org.apache.bcel.util.Repository repository) {
129         this.repository = repository;
130     }
131 
132     public byte[] transform(final byte[] original) {
133         if(repository==null) {
134             return doTransform(original);
135         } else {
136             synchronized(repositoryLock) {
137                 org.apache.bcel.util.Repository old = Repository.getRepository();
138                 Repository.setRepository(repository);
139                 try {
140                     return doTransform(original);
141                 } finally {
142                     Repository.setRepository(old);
143                 }
144             }
145         }
146     }
147 
148     private byte[] doTransform(final byte[] original) {
149         
150         final InputStream is = new ByteArrayInputStream(original);
151         
152         final ClassParser parser = new ClassParser(is, null);
153         JavaClass javaClazz = null;
154         try {
155             javaClazz = parser.parse();
156         } catch (ClassFormatException e2) {
157             e2.printStackTrace();
158         } catch (IOException e2) {
159             e2.printStackTrace();
160         }
161         
162         Repository.addClass(javaClazz);
163         
164         log.debug("transforming class " + javaClazz.getClassName());
165 
166         //final JavaClass clazz = Repository.lookupClass(clazzName);
167 
168         {// check if this class is already instrumented
169             String[] intfs = javaClazz.getInterfaceNames();
170             for( int i=0; i<intfs.length; i++ )
171                 if(intfs[i].equals(CONTINUABLE_CLASS)) {
172                     // no need to instrument further
173                     log.debug(javaClazz.getClassName()+" is already instrumented. Skipping");
174                     return original;
175                 }
176         }
177 
178         final ClassGen clazzGen = new ClassGen(javaClazz);
179         final ConstantPoolGen cp = clazzGen.getConstantPool();
180 
181         if(debug) {
182             dump(javaClazz, ".orig");
183         }
184 
185         // vistor to build the frame information
186         final ExecutionVisitor ev = new ExecutionVisitor();
187         ev.setConstantPoolGen(cp);
188         // obsolete, but neccesary to execute the InvokeContext
189         //final InstConstraintVisitor icv = new InstConstraintVisitor();
190         //icv.setConstantPoolGen(cp);
191 
192         final Method[] methods = clazzGen.getMethods();
193         for (int i = 0; i < methods.length; i++) {
194             final MethodGen method = new MethodGen(methods[i], clazzGen.getClassName(), cp);
195             currentMethodStatic = methods[i].isStatic();
196             if (needsRewriting(method)) {
197                 // analyse the code of the method to create the frame
198                 // information about every instruction
199                 final ControlFlowGraph cfg = new ControlFlowGraph(method);
200 
201                 analyse(clazzGen, method, cfg, ev);
202 
203                 try {
204                     // add intercepting code
205                     rewrite(method, cfg);
206                 } catch (ClassNotFoundException e1) {
207                     e1.printStackTrace();
208                 }
209 
210                 clazzGen.replaceMethod(methods[i], method.getMethod());
211             }
212         }
213 
214         clazzGen.addInterface(CONTINUABLE_CLASS);
215         JavaClass newClass = clazzGen.getJavaClass();
216         final byte[] changed = newClass.getBytes();
217 
218         if(debug) {
219             dump(newClass, ".rewritten");
220         }
221 
222         return changed;
223     }
224 
225     /**
226      * Dumps the class file to the curent directory for debugging.
227      */
228     private void dump(JavaClass javaClazz, String suffix) {
229         String path = javaClazz.getClassName()+suffix;
230 
231         final byte[] orig = javaClazz.getBytes();
232 
233         FileOutputStream out = null;
234         try {
235             out = new FileOutputStream(path);
236             
237             log.debug("writing " + path);
238             
239             out.write(orig);
240             out.flush();
241         } catch (final IOException e) {
242             e.printStackTrace();
243 
244             try {
245                 if (out != null) {
246                     out.close();
247                 }
248             } catch (final IOException e1) {
249                 log.error(e1.getMessage(), e1);
250             } finally {
251                 out = null;
252             }
253         }
254 
255         try {
256             out = new FileOutputStream(path + ".java");
257 
258             log.debug("writing " + path + ".java");
259 
260             final DecompilingVisitor v = new DecompilingVisitor(javaClazz, out);
261             v.start();
262         } catch (final Exception e) {
263             e.printStackTrace();
264 
265             try {
266                 if (out != null) {
267                     out.close();
268                 }
269             } catch (final IOException e1) {
270                 log.error(e1.getMessage(), e1);
271             } finally {
272                 out = null;
273             }
274         }
275     }
276 
277     private boolean needsRewriting(MethodGen m) {
278         if (m.getName().equals(Constants.CONSTRUCTOR_NAME)
279                 || m.getName().equals(Constants.STATIC_INITIALIZER_NAME)
280                 || m.isNative()
281                 || m.isAbstract()) {
282             return false;
283         } else {
284             return true;
285         }
286     }
287 
288     private void analyse(ClassGen clazz, MethodGen method, ControlFlowGraph cfg, ExecutionVisitor ev) {
289         log.debug("analyse " + method.getName());
290 
291         final Frame vanillaFrame = craeteInitialFrame(method, clazz);
292 
293         final Vector ics = new Vector(); // Type: InstructionContext
294         final Vector ecs = new Vector(); // Type: ExecutionPath
295 
296         final InstructionContext start = cfg.contextOf(method.getInstructionList().getStart());
297 
298         start.execute(vanillaFrame, ExecutionPath.EMPTY, ev);
299         // new ArrayList() <=> no Instruction was executed before
300         //                  => Top-Level routine (no jsr call before)
301         ics.add(start);
302         ecs.add(ExecutionPath.EMPTY);
303 
304         while (!ics.isEmpty()) {
305             final InstructionContext u = (InstructionContext) ics.remove(0);
306             final ExecutionPath oldchain = (ExecutionPath) ecs.remove(0);
307             final ExecutionPath newchain = oldchain.append(u);
308 
309             if ((u.getInstruction().getInstruction()) instanceof RET) {
310                 // where did we come from? this JSR
311                 InstructionHandle jsr = oldchain.lastExecutionJSR().getInstruction();
312                 // so the next instruction to execute will be this
313                 final InstructionContext theSuccessor = cfg.contextOf(jsr.getNext());
314 
315                 if (theSuccessor.execute(u.getOutFrame(oldchain), newchain, ev)) {
316                     ics.add(theSuccessor);
317                     ecs.add(newchain);
318                 }
319             } else { // "not a ret"
320                 // Normal successors. Add them to the queue of successors.
321                 final InstructionContext[] succs = u.getSuccessors();
322                 for (int s = 0; s < succs.length; s++) {
323                     final InstructionContext v = succs[s];
324                     //if (v.execute(u.getOutFrame(oldchain), newchain, icv, ev)) {
325                     if (v.execute(u.getOutFrame(oldchain), newchain, ev)) {
326                         ics.add(v);
327                         ecs.add(newchain);
328                     }
329                 }
330             }
331             // Exception Handlers. Add them to the queue of successors.
332             final ExceptionHandler[] exc_hds = u.getExceptionHandlers();
333             for (int s = 0; s < exc_hds.length; s++) {
334                 final InstructionContext v = cfg.contextOf(exc_hds[s].getHandlerStart());
335                 // TODO: the "oldchain" and "newchain" is used to determine the
336                 // subroutine
337                 // we're in (by searching for the last JSR) by the
338                 // InstructionContext
339                 // implementation. Therefore, we should not use this chain
340                 // mechanism
341                 // when dealing with exception handlers.
342 
343                 final LocalVariables newLocals = u.getOutFrame(oldchain).getLocals();
344                 final OperandStack newStack = new OperandStack(
345                         u.getOutFrame(oldchain).getStack().maxStack(),
346                         (exc_hds[s].getExceptionType() == null ? Type.THROWABLE : exc_hds[s]
347                         .getExceptionType()));
348                 final Frame newFrame = new Frame(newLocals, newStack);
349 
350                 if (v.execute(newFrame, ExecutionPath.EMPTY, ev)) {
351                     ics.add(v);
352                     ecs.add(ExecutionPath.EMPTY);
353                 }
354             }
355         }
356     }
357 
358     /**
359      * Creates a {@link Frame} object that represents the state of the stack frame
360      * at the beginning of a method.
361      */
362     private Frame craeteInitialFrame(MethodGen method, ClassGen clazz) {
363         final Frame vanillaFrame = new Frame(method.getMaxLocals(), method.getMaxStack());
364         if (!method.isStatic()) {
365             if (method.getName().equals(Constants.CONSTRUCTOR_NAME)) {
366                 Frame._this = new UninitializedObjectType(new ObjectType(clazz.getClassName()));
367                 vanillaFrame.getLocals().set(0, new UninitializedObjectType(new ObjectType(clazz.getClassName())));
368             } else {
369                 Frame._this = null;
370                 vanillaFrame.getLocals().set(0, new ObjectType(clazz.getClassName()));
371             }
372         }
373         // fill local variables with parameter types
374         final Type[] argtypes = method.getArgumentTypes();
375         int twoslotoffset = 0;
376         for (int j = 0; j < argtypes.length; j++) {
377             if ((argtypes[j] == Type.SHORT) || (argtypes[j] == Type.BYTE) || (argtypes[j] == Type.CHAR) || (argtypes[j] == Type.BOOLEAN)) {
378                 argtypes[j] = Type.INT;
379             }
380             vanillaFrame.getLocals().set(twoslotoffset + j + (method.isStatic() ? 0 : 1), argtypes[j]);
381             if (argtypes[j].getSize() == 2) {
382                 twoslotoffset++;
383                 vanillaFrame.getLocals().set(twoslotoffset + j + (method.isStatic() ? 0 : 1), Type.UNKNOWN);
384             }
385         }
386 
387         return vanillaFrame;
388     }
389 
390     private void rewrite(MethodGen method, ControlFlowGraph cfg) throws ClassNotFoundException {
391         final InstructionFactory insFactory = new InstructionFactory(method.getConstantPool());
392         final Vector invokeIns = new Vector();
393         final InstructionList insList = method.getInstructionList();
394         InstructionHandle ins = insList.getStart();
395         final InstructionList restorer = new InstructionList();
396         int count = 0;  // count # of breakpoints
397 
398         // we need to expand the local variable size to store arguments for the constructor invocation.
399         // compute the necessary size in this variable.
400         int[] localVarsSize = new int[1];
401         localVarsSize[0] = method.getMaxLocals();
402 
403         while (ins != null) {
404             InstructionHandle next = ins.getNext();
405 
406             // if not traversed by the analyser, then don't rewrite
407             InstructionContext context = null;
408             Frame frame = null;
409             try {
410                 context = cfg.contextOf(ins);
411                 frame = context.getOutFrame(ExecutionPath.EMPTY);
412             } catch (AssertionViolatedException ave) {
413                 // empty
414             }
415             if (frame != null) {
416                 if (rewriteable(method, ins)) {
417                     // Add frame saver and restorer for the current breakpoint
418 
419                     // determine type of object for the method invocation
420                     final InvokeInstruction invoke = (InvokeInstruction) ins.getInstruction();
421                     final Type[] arguments = invoke.getArgumentTypes(method.getConstantPool());
422                     ReferenceType objecttype = null;
423                     if (!(invoke instanceof INVOKESTATIC)) {
424                         objecttype = (ReferenceType) context.getInFrame().getStack().peek(arguments.length);
425                     }
426                     final InstructionList rList = restoreFrame(method, ins, insFactory, frame, objecttype);
427                     insList.append(ins, saveFrame(method, ins, count++, insFactory, frame));
428                     invokeIns.addElement(rList.getStart());
429                     restorer.append(rList);
430                 }
431                 // remove all new's
432                 if (ins.getInstruction().getOpcode() == Constants.NEW) {
433                     try {
434                         // remove additional dup's
435                         while (next != null && next.getInstruction().getOpcode() == Constants.DUP) {
436                             context = cfg.contextOf(next);
437                             context.getOutFrame(ExecutionPath.EMPTY);
438                             final InstructionHandle newnext = next.getNext();
439                             insList.delete(next);
440                             next = newnext;
441                         }
442                         // if there are any dup_x2 following new and dup, replace them with dup.
443                         // some java compiler generates such bytecode for code like
444                         //  someObject.someStringField += "abc"
445                         //
446                         // which yield
447                         //   NEW StringBuffer
448                         //   DUP
449                         //   ALOAD  // someObject
450                         //   DUP_x2
451                         //   GETFIELD someStringField
452                         //   INVOKESPECIAL StringBuffer.<init>
453                         //   LDC "abc"
454                         //   INVOKEVIRTUAL StringBuffer.append
455                         //   INVOKEVIRTUAL StringBuffer.toString
456                         //   PUTFIELD someStringField
457                         //
458                         // replacing this DUP_x2 -> DUP is required for moving the new.
459                         if(next!=null && next.getNext()!=null && next.getNext().getInstruction().getOpcode() == Constants.DUP_X2) {
460                             InstructionHandle dupx2ptr = next.getNext();
461                             final InstructionHandle newnext = dupx2ptr.getNext();
462                             insList.insert(dupx2ptr, InstructionConstants.DUP);
463                             insList.delete(dupx2ptr);
464                             next = newnext;
465                         }
466                         final InstructionTargeter[] targeter = ins.getTargeters();
467                         if (targeter != null) {
468                             final InstructionHandle newnext = ins.getNext();
469                             for (int i = 0; i < targeter.length; i++) {
470                                 targeter[i].updateTarget(ins, newnext);
471                             }
472                         }
473                         insList.delete(ins);
474                     } catch (TargetLostException tle) {
475                         throw new ClassNotFoundException(tle.getMessage(), tle);
476                     }
477                 } else if (ins.getInstruction().getOpcode() == Constants.INVOKESPECIAL) {
478                     // duplicate stack before invokespecial to insert
479                     // uninitialized object
480                     frame = context.getInFrame();
481                     final InvokeInstruction invoke = (InvokeInstruction) ins.getInstruction();
482                     final Type[] arguments = invoke.getArgumentTypes(method.getConstantPool());
483 
484                     final OperandStack os = frame.getStack();
485                     final Type type = os.peek(arguments.length);
486                     if (type instanceof UninitializedObjectType) {
487                         final ObjectType objecttype = ((UninitializedObjectType) type).getInitialized();
488                         final InstructionList duplicator = duplicateStack(method, invoke, objecttype, localVarsSize);
489                         final InstructionTargeter[] targeter = ins.getTargeters();
490 
491                         if (targeter != null) {
492                             final InstructionHandle newnext = duplicator.getStart();
493                             for (int i = 0; i < targeter.length; i++) {
494                                 targeter[i].updateTarget(ins, newnext);
495                             }
496                         }
497                         insList.insert(ins, duplicator);
498                     }
499                 }
500             }
501             ins = next;
502         }
503 
504         // local variable index for storing the StackRecorder object
505         final int varStack = method.getMaxLocals();
506         // instruction to load the stack recorder
507         Instruction loadStackRecorder = InstructionFactory.createLoad(STACK_RECORDER_TYPE, varStack);
508 
509         final InstructionHandle firstIns = insList.getStart();
510         if (count > 0) {
511             final InstructionHandle[] tableTargets = new InstructionHandle[count];
512             int[] match = new int[count];
513             for (int i = 0; i < count; i++) {
514                 match[i] = i;
515             }
516             invokeIns.copyInto(tableTargets);
517             insList.insert(restorer);
518 
519             // select frame restorer
520             insList.insert(new TABLESWITCH(match, tableTargets, firstIns));
521             insList.insert(insFactory.createInvoke(STACK_RECORDER_CLASS, getPopMethod(Type.INT), Type.INT, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
522             insList.insert(loadStackRecorder);
523 
524             // test if the continuation should be restored
525             insList.insert(new IFEQ(firstIns));
526             insList.insert(insFactory.createFieldAccess(STACK_RECORDER_CLASS, RESTORING_FIELD, Type.BOOLEAN, Constants.GETFIELD));
527             insList.insert(loadStackRecorder);
528 
529             // test if no current continuation exists
530             insList.insert(new IFNULL(firstIns));
531 
532             // get current continuation and store in the next to last local variable
533             insList.insert(InstructionFactory.createStore(STACK_RECORDER_TYPE, varStack));
534             insList.insert(InstructionConstants.DUP);
535             insList.insert(insFactory.createInvoke(STACK_RECORDER_CLASS, STACK_METHOD, STACK_RECORDER_TYPE, Type.NO_ARGS, Constants.INVOKESTATIC));
536 
537             // we need one more variable for storing StackRecorder
538             localVarsSize[0] = Math.max( localVarsSize[0], method.getMaxLocals()+1 );
539 
540             // make room for additional object (StackRecorder)
541             method.setMaxStack(method.getMaxStack() + 2);
542         }
543 
544         method.setMaxLocals(localVarsSize[0]);
545         // make room for additional 'null' we push on the stack to clear
546         // the local variables that are used during the constructor invocation instrumentation.
547         // ideally we should be able to determine when we need to add +1, but for now just to be
548         // safe and let's always add this.
549         method.setMaxStack(method.getMaxStack() + 1);
550 
551         // remove LocalVariableTypeTable attribute until BCEL #33549 is fixed,
552         // so that JDK 5.0 classes can be instrumented correctly.
553         // see http://issues.apache.org/bugzilla/show_bug.cgi?id=33549
554         Attribute[] atts = method.getCodeAttributes();
555         for( int i=0; i<atts.length; i++ ) {
556             if(atts[i].getNameIndex()==method.getConstantPool().lookupUtf8("LocalVariableTypeTable"))
557                 method.removeCodeAttribute(atts[i]);
558         }
559     }
560 
561     /**
562      * Used to instrument the constructor invocation.
563      */
564     private InstructionList duplicateStack(MethodGen method, InvokeInstruction invoke, ObjectType objecttype, int[] localVarsSize) {
565         // reconstruction of an uninitialed object to call the constructor.
566         final InstructionFactory insFactory = new InstructionFactory(method.getConstantPool());
567         final InstructionList insList = new InstructionList();
568 
569         final Type[] arguments = invoke.getArgumentTypes(method.getConstantPool());
570 
571         int localVarOffset = method.getMaxLocals()+1;   // +1 for stack recorder
572 
573         // move all arguments for the constructor from the operand stack into local variables
574         for (int i = arguments.length - 1; i >= 0; i--) {
575             Type type = arguments[i];
576             insList.append(InstructionFactory.createStore(type,localVarOffset));
577             localVarOffset += type.getSize();
578         }
579 
580         localVarsSize[0] = Math.max(localVarsSize[0],localVarOffset);
581 
582         // create uninitialzed object
583         insList.append(insFactory.createNew(objecttype));
584         insList.append(InstructionConstants.DUP);
585 
586         // move the arguments back into the operand stack
587         for (int i = 0; i < arguments.length; i++) {
588             Type type = arguments[i];
589 
590             localVarOffset -= type.getSize();
591             insList.append(InstructionFactory.createLoad(type,localVarOffset));
592             if(type instanceof ReferenceType) {
593                 // erase the local variable, or else we have a potential for memory leak
594                 insList.append(InstructionConstants.ACONST_NULL);
595                 insList.append(InstructionFactory.createStore(type,localVarOffset));
596             }
597         }
598         return insList;
599     }
600 
601     private boolean rewriteable(MethodGen method, InstructionHandle handle) {
602         // check in the invocation can be a breakpoint.
603         int opcode = handle.getInstruction().getOpcode();
604 
605         if(!(handle.getInstruction() instanceof InvokeInstruction))
606             return false;   // not an INVOKE***
607 
608         if (opcode == Constants.INVOKESPECIAL) {
609             final InvokeInstruction ivs = (InvokeInstruction) handle.getInstruction();
610             final String mName = ivs.getMethodName(method.getConstantPool());
611             if(mName.equals(Constants.CONSTRUCTOR_NAME))
612                 return false;   // can't instrument the constructor invocation
613         }
614 
615         // final int index = ((InvokeInstruction) handle.getInstruction()).getIndex();
616         // final String classname = getObjectType(method.getConstantPool().getConstantPool(), index).getClassName();
617 
618         // rewrite invocation if object is continuable or a continuation
619         // object
620 
621         // FIXME:
622         //return Repository.implementationOf(classname, CONTINUABLE_CLASS) || Repository.instanceOf(classname, CONTINUATION_CLASS);
623         return true;
624     }
625 
626     private InstructionList saveFrame(MethodGen method, InstructionHandle handle, int pc, InstructionFactory insFactory, Frame frame) {
627         final InstructionList insList = new InstructionList();
628 
629         // Remove needless return type from stack
630         final InvokeInstruction inv = (InvokeInstruction) handle.getInstruction();
631         final Type returnType = getReturnType(method.getConstantPool().getConstantPool(), inv.getIndex());
632         if (returnType.getSize() > 0) {
633             insList.insert(InstructionFactory.createPop(returnType.getSize()));
634         }
635         boolean skipFirst = returnType.getSize() > 0;
636 
637         //if (debug) {
638         //    insList.append(insFactory.createPrintln("save stack"));
639         //}
640 
641         // instruction for loading StackRecorder
642         Instruction loadStackRecorder = InstructionFactory.createLoad(STACK_RECORDER_TYPE, method.getMaxLocals());
643 
644         // save stack
645         final OperandStack os = frame.getStack();
646         for (int i = skipFirst ? 1 : 0; i < os.size(); i++) {
647             Type type = os.peek(i);
648             if (type instanceof BasicType) {
649                 if (type.getSize() < 2 && !type.equals(Type.FLOAT)) {
650                     type = Type.INT;
651                 }
652 
653                 // check for types with two words on stack
654                 if (type.equals(Type.LONG) || type.equals(Type.DOUBLE)) {
655                     insList.append(InstructionConstants.ACONST_NULL); // create dummy stack entry
656                     insList.append(loadStackRecorder);
657                     insList.append(InstructionConstants.DUP2_X2); // swap Stack object and long/float
658                     insList.append(InstructionConstants.POP2);
659                 } else {
660                     insList.append(loadStackRecorder);
661                     insList.append(InstructionConstants.SWAP);
662                 }
663                 insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, getPushMethod(type), Type.VOID, new Type[] { type }, Constants.INVOKEVIRTUAL));
664                 if (type.equals(Type.LONG) || type.equals(Type.DOUBLE))
665                     insList.append(InstructionConstants.POP); // remove dummy stack entry
666             } else if (type == null) {
667                 insList.append(InstructionConstants.POP);
668             } else if (type instanceof UninitializedObjectType) {
669                 // After the remove of new, there shouldn't be a
670                 // uninitialized object on the stack
671             } else if (type instanceof ReferenceType) {
672                 if(type.equals(Type.NULL)) {
673                     // if it's guaranteed to be null, no need to store.
674                     // in fact, we can't really store this, because we'll never
675                     // be able to restore it as the 'null' type.
676                     insList.append(InstructionConstants.POP);
677                 } else {
678                     insList.append(loadStackRecorder);
679                     insList.append(InstructionConstants.SWAP);
680                     insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, getPushMethod(Type.OBJECT), Type.VOID, new Type[] { Type.OBJECT }, Constants.INVOKEVIRTUAL));
681                 }
682             }
683         }
684         // add isCapturing test
685         if (debug) {
686             insList.insert(insFactory.createPrintln("capturing invocation "+method));
687         }
688         insList.insert(new IFEQ(handle.getNext()));
689 
690         // test if the continuation should be captured after the invocation
691         insList.insert(insFactory.createFieldAccess(STACK_RECORDER_CLASS, CAPTURING_FIELD, Type.BOOLEAN, Constants.GETFIELD));
692         insList.insert(loadStackRecorder);
693 
694         // test if continuation exists
695         insList.insert(new IFNULL(handle.getNext()));
696         insList.insert(loadStackRecorder);
697 
698         // save local variables
699         //if (debug) {
700         //    insList.append(insFactory.createPrintln("save local variables"));
701         //}
702 
703         final LocalVariables lvs = frame.getLocals();
704         for (int i = 0; i < lvs.maxLocals(); i++) {
705             Type type = lvs.get(i);
706             if (type instanceof BasicType) {
707                 insList.append(loadStackRecorder);
708                 insList.append(InstructionFactory.createLoad(type, i));
709                 if (type.getSize() < 2 && !type.equals(Type.FLOAT))
710                     type = Type.INT;
711                 insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, getPushMethod(type), Type.VOID, new Type[] { type }, Constants.INVOKEVIRTUAL));
712             } else if (type == null || type==Type.NULL) {
713                 // no need to save null
714             } else if (type instanceof UninitializedObjectType) {
715                 // no need to save uninitialized objects
716             } else if (type instanceof ReferenceType) {
717                 if (i == 0 && !currentMethodStatic) {
718                     // remember current object
719                     insList.append(loadStackRecorder);
720                     insList.append(InstructionFactory.createLoad(type, i));
721                     insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, PUSH_METHOD + "Reference", Type.VOID, new Type[] { Type.OBJECT }, Constants.INVOKEVIRTUAL));
722                 }
723                 insList.append(loadStackRecorder);
724                 insList.append(InstructionFactory.createLoad(type, i));
725                 insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, getPushMethod(Type.OBJECT), Type.VOID, new Type[] { Type.OBJECT }, Constants.INVOKEVIRTUAL));
726             }
727         }
728         // save programcounter
729         insList.append(loadStackRecorder);
730         insList.append(new PUSH(method.getConstantPool(), pc));
731         insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, getPushMethod(Type.INT), Type.VOID, new Type[] { Type.INT }, Constants.INVOKEVIRTUAL));
732         // return NULL result
733         insList.append(InstructionFactory.createNull(method.getReturnType()));
734         insList.append(InstructionFactory.createReturn(method.getReturnType()));
735         return insList;
736     }
737 
738     private InstructionList restoreFrame(MethodGen method, InstructionHandle handle, InstructionFactory insFactory, Frame frame, ReferenceType objecttype) {
739         final InstructionList insList = new InstructionList();
740         // restore local variables
741         //if (debug) {
742         //    insList.append(insFactory.createPrintln("restore local variables"));
743         //}
744 
745         final LocalVariables lvs = frame.getLocals();
746         Instruction loadStackRecorder = InstructionFactory.createLoad(STACK_RECORDER_TYPE, method.getMaxLocals());
747 
748         for (int i = lvs.maxLocals() - 1; i >= 0; i--) {
749             Type type = lvs.get(i);
750             if (type instanceof BasicType) {
751                 insList.append(loadStackRecorder);
752                 if (type.getSize() < 2 && !type.equals(Type.FLOAT)) {
753                     type = Type.INT;
754                 }
755                 insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, getPopMethod(type), type, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
756                 insList.append(InstructionFactory.createStore(type, i));
757             } else if (type == null) {
758                 insList.append(InstructionConstants.ACONST_NULL);
759                 insList.append(new ASTORE(i));
760             } else if (type instanceof UninitializedObjectType) {
761                 // No uninitilaized objects should be found
762                 // in the local variables.
763                 throw new Error("assertion failure");
764             } else if (type instanceof ReferenceType) {
765                 if (type==Type.NULL) {
766                     // null type is a special type assignable to any type,
767                     // whereas popObject returns a java/lang/Object.
768                     // the saveFrame method is written so that we don't save the 'null' object
769                     insList.append(InstructionConstants.ACONST_NULL);
770                 } else {
771                     insList.append(loadStackRecorder);
772                     insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, getPopMethod(Type.OBJECT), Type.OBJECT, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
773                     if (!type.equals(Type.OBJECT)) {
774                         insList.append(insFactory.createCast(Type.OBJECT, type));
775                     }
776                 }
777                 insList.append(new ASTORE(i));
778             }
779         }
780 
781         final InvokeInstruction inv = (InvokeInstruction) handle.getInstruction();
782         final Type returnType = getReturnType(method.getConstantPool().getConstantPool(), inv.getIndex());
783         boolean skipFirst = returnType.getSize() > 0;
784 
785         // restore stack
786         //if (debug) {
787         //    insList.append(insFactory.createPrintln("restore stack"));
788         //}
789 
790         final OperandStack os = frame.getStack();
791         for (int i = os.size() - 1; i >= (skipFirst ? 1 : 0); i--) {
792             Type type = os.peek(i);
793             if (type instanceof BasicType) {
794                 if (type.getSize() < 2 && !type.equals(Type.FLOAT)) {
795                     type = Type.INT;
796                 }
797                 insList.append(loadStackRecorder);
798                 insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, getPopMethod(type), type, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
799             } else if (type == null || type==Type.NULL) {
800                 insList.append(new ACONST_NULL());
801             } else if (type instanceof UninitializedObjectType) {
802                 // After the remove of new, there shouldn't be a
803                 // uninitialized object on the stack
804             } else if (type instanceof ReferenceType) {
805                 insList.append(loadStackRecorder);
806                 insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, getPopMethod(Type.OBJECT), Type.OBJECT, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
807                 if (!type.equals(Type.OBJECT))
808                     insList.append(insFactory.createCast(Type.OBJECT, type));
809             }
810         }
811         // retrieve current object
812         if (!(inv instanceof INVOKESTATIC)) {
813             insList.append(loadStackRecorder);
814             insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, POP_METHOD + "Reference", Type.OBJECT, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
815             insList.append(insFactory.createCast(Type.OBJECT, objecttype));
816         }
817         // Create null types for the parameters of the method invocation
818         final Type[] paramTypes = getParamTypes(method.getConstantPool().getConstantPool(), inv.getIndex());
819         for (int j = 0; j < paramTypes.length; j++) {
820             insList.append(InstructionFactory.createNull(paramTypes[j]));
821         }
822         // go to last invocation
823         insList.append(new GOTO(handle));
824         return insList;
825     }
826 
827     private Type[] getParamTypes(ConstantPool cp, int index) {
828         final ConstantCP cmr = (ConstantCP) cp.getConstant(index);
829         final ConstantNameAndType cnat = (ConstantNameAndType) cp.getConstant(cmr.getNameAndTypeIndex());
830         final String sig = ((ConstantUtf8) cp.getConstant(cnat.getSignatureIndex())).getBytes();
831         return Type.getArgumentTypes(sig);
832     }
833 
834     private Type getReturnType(ConstantPool cp, int index) {
835         final ConstantCP cmr = (ConstantCP) cp.getConstant(index);
836         final ConstantNameAndType cnat = (ConstantNameAndType) cp.getConstant(cmr.getNameAndTypeIndex());
837         final String sig = ((ConstantUtf8) cp.getConstant(cnat.getSignatureIndex())).getBytes();
838         return Type.getReturnType(sig);
839     }
840 
841     private String getPopMethod(Type type) {
842         return POP_METHOD + getTypeSuffix(type);
843     }
844 
845     private String getPushMethod(Type type) {
846         return PUSH_METHOD + getTypeSuffix(type);
847     }
848 
849     private String getTypeSuffix(Type type) {
850         if (type.equals(Type.BOOLEAN))
851             return "Int";
852         else if (type.equals(Type.CHAR))
853             return "Int";
854         else if (type.equals(Type.FLOAT))
855             return "Float";
856         else if (type.equals(Type.DOUBLE))
857             return "Double";
858         else if (type.equals(Type.BYTE))
859             return "Int";
860         else if (type.equals(Type.SHORT))
861             return "Int";
862         else if (type.equals(Type.INT))
863             return "Int";
864         else if (type.equals(Type.LONG))
865             return "Long";
866         // VOID and OBJECT are "Object"
867         return "Object";
868     }
869 
870 }