Coverage Report - org.apache.commons.javaflow.bytecode.transformation.bcel.BcelClassTransformer
 
Classes in this File Line Coverage Branch Coverage Complexity
BcelClassTransformer
0%
0/422
0%
0/226
7.278
 
 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  0
     private final static Log log = LogFactory.getLog(BcelClassTransformer.class);
 86  
 
 87  0
     private static final String STACK_RECORDER_CLASS = StackRecorder.class.getName();
 88  0
     private static final ObjectType STACK_RECORDER_TYPE = new ObjectType(STACK_RECORDER_CLASS);
 89  0
     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  0
     private boolean currentMethodStatic = false;
 97  0
     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  0
     private static final Object repositoryLock = new Object();
 116  
 
 117  
     static {
 118  
         try {
 119  0
             debug = System.getProperty(BcelClassTransformer.class.getName()+".debug")!=null;
 120  0
         } catch (SecurityException e) {
 121  
             // assume no debugging
 122  0
         }
 123  0
     }
 124  
 
 125  0
     public BcelClassTransformer() {
 126  0
     }
 127  
 
 128  0
     public BcelClassTransformer(org.apache.bcel.util.Repository repository) {
 129  0
         this.repository = repository;
 130  0
     }
 131  
 
 132  
     public byte[] transform(final byte[] original) {
 133  0
         if(repository==null) {
 134  0
             return doTransform(original);
 135  
         } else {
 136  0
             synchronized(repositoryLock) {
 137  0
                 org.apache.bcel.util.Repository old = Repository.getRepository();
 138  0
                 Repository.setRepository(repository);
 139  
                 try {
 140  0
                     return doTransform(original);
 141  
                 } finally {
 142  0
                     Repository.setRepository(old);
 143  0
                 }
 144  0
             }
 145  
         }
 146  
     }
 147  
 
 148  
     private byte[] doTransform(final byte[] original) {
 149  
         
 150  0
         final InputStream is = new ByteArrayInputStream(original);
 151  
         
 152  0
         final ClassParser parser = new ClassParser(is, null);
 153  0
         JavaClass javaClazz = null;
 154  
         try {
 155  0
             javaClazz = parser.parse();
 156  0
         } catch (ClassFormatException e2) {
 157  0
             e2.printStackTrace();
 158  0
         } catch (IOException e2) {
 159  0
             e2.printStackTrace();
 160  0
         }
 161  
         
 162  0
         Repository.addClass(javaClazz);
 163  
         
 164  0
         log.debug("transforming class " + javaClazz.getClassName());
 165  
 
 166  
         //final JavaClass clazz = Repository.lookupClass(clazzName);
 167  
 
 168  
         {// check if this class is already instrumented
 169  0
             String[] intfs = javaClazz.getInterfaceNames();
 170  0
             for( int i=0; i<intfs.length; i++ )
 171  0
                 if(intfs[i].equals(CONTINUABLE_CLASS)) {
 172  
                     // no need to instrument further
 173  0
                     log.debug(javaClazz.getClassName()+" is already instrumented. Skipping");
 174  0
                     return original;
 175  
                 }
 176  
         }
 177  
 
 178  0
         final ClassGen clazzGen = new ClassGen(javaClazz);
 179  0
         final ConstantPoolGen cp = clazzGen.getConstantPool();
 180  
 
 181  0
         if(debug) {
 182  0
             dump(javaClazz, ".orig");
 183  
         }
 184  
 
 185  
         // vistor to build the frame information
 186  0
         final ExecutionVisitor ev = new ExecutionVisitor();
 187  0
         ev.setConstantPoolGen(cp);
 188  
         // obsolete, but neccesary to execute the InvokeContext
 189  
         //final InstConstraintVisitor icv = new InstConstraintVisitor();
 190  
         //icv.setConstantPoolGen(cp);
 191  
 
 192  0
         final Method[] methods = clazzGen.getMethods();
 193  0
         for (int i = 0; i < methods.length; i++) {
 194  0
             final MethodGen method = new MethodGen(methods[i], clazzGen.getClassName(), cp);
 195  0
             currentMethodStatic = methods[i].isStatic();
 196  0
             if (needsRewriting(method)) {
 197  
                 // analyse the code of the method to create the frame
 198  
                 // information about every instruction
 199  0
                 final ControlFlowGraph cfg = new ControlFlowGraph(method);
 200  
 
 201  0
                 analyse(clazzGen, method, cfg, ev);
 202  
 
 203  
                 try {
 204  
                     // add intercepting code
 205  0
                     rewrite(method, cfg);
 206  0
                 } catch (ClassNotFoundException e1) {
 207  0
                     e1.printStackTrace();
 208  0
                 }
 209  
 
 210  0
                 clazzGen.replaceMethod(methods[i], method.getMethod());
 211  
             }
 212  
         }
 213  
 
 214  0
         clazzGen.addInterface(CONTINUABLE_CLASS);
 215  0
         JavaClass newClass = clazzGen.getJavaClass();
 216  0
         final byte[] changed = newClass.getBytes();
 217  
 
 218  0
         if(debug) {
 219  0
             dump(newClass, ".rewritten");
 220  
         }
 221  
 
 222  0
         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  0
         String path = javaClazz.getClassName()+suffix;
 230  
 
 231  0
         final byte[] orig = javaClazz.getBytes();
 232  
 
 233  0
         FileOutputStream out = null;
 234  
         try {
 235  0
             out = new FileOutputStream(path);
 236  
             
 237  0
             log.debug("writing " + path);
 238  
             
 239  0
             out.write(orig);
 240  0
             out.flush();
 241  0
         } catch (final IOException e) {
 242  0
             e.printStackTrace();
 243  
 
 244  
             try {
 245  0
                 if (out != null) {
 246  0
                     out.close();
 247  
                 }
 248  0
             } catch (final IOException e1) {
 249  0
                 log.error(e1.getMessage(), e1);
 250  
             } finally {
 251  0
                 out = null;
 252  0
             }
 253  0
         }
 254  
 
 255  
         try {
 256  0
             out = new FileOutputStream(path + ".java");
 257  
 
 258  0
             log.debug("writing " + path + ".java");
 259  
 
 260  0
             final DecompilingVisitor v = new DecompilingVisitor(javaClazz, out);
 261  0
             v.start();
 262  0
         } catch (final Exception e) {
 263  0
             e.printStackTrace();
 264  
 
 265  
             try {
 266  0
                 if (out != null) {
 267  0
                     out.close();
 268  
                 }
 269  0
             } catch (final IOException e1) {
 270  0
                 log.error(e1.getMessage(), e1);
 271  
             } finally {
 272  0
                 out = null;
 273  0
             }
 274  0
         }
 275  0
     }
 276  
 
 277  
     private boolean needsRewriting(MethodGen m) {
 278  0
         if (m.getName().equals(Constants.CONSTRUCTOR_NAME)
 279  
                 || m.getName().equals(Constants.STATIC_INITIALIZER_NAME)
 280  
                 || m.isNative()
 281  
                 || m.isAbstract()) {
 282  0
             return false;
 283  
         } else {
 284  0
             return true;
 285  
         }
 286  
     }
 287  
 
 288  
     private void analyse(ClassGen clazz, MethodGen method, ControlFlowGraph cfg, ExecutionVisitor ev) {
 289  0
         log.debug("analyse " + method.getName());
 290  
 
 291  0
         final Frame vanillaFrame = craeteInitialFrame(method, clazz);
 292  
 
 293  0
         final Vector ics = new Vector(); // Type: InstructionContext
 294  0
         final Vector ecs = new Vector(); // Type: ExecutionPath
 295  
 
 296  0
         final InstructionContext start = cfg.contextOf(method.getInstructionList().getStart());
 297  
 
 298  0
         start.execute(vanillaFrame, ExecutionPath.EMPTY, ev);
 299  
         // new ArrayList() <=> no Instruction was executed before
 300  
         //                  => Top-Level routine (no jsr call before)
 301  0
         ics.add(start);
 302  0
         ecs.add(ExecutionPath.EMPTY);
 303  
 
 304  0
         while (!ics.isEmpty()) {
 305  0
             final InstructionContext u = (InstructionContext) ics.remove(0);
 306  0
             final ExecutionPath oldchain = (ExecutionPath) ecs.remove(0);
 307  0
             final ExecutionPath newchain = oldchain.append(u);
 308  
 
 309  0
             if ((u.getInstruction().getInstruction()) instanceof RET) {
 310  
                 // where did we come from? this JSR
 311  0
                 InstructionHandle jsr = oldchain.lastExecutionJSR().getInstruction();
 312  
                 // so the next instruction to execute will be this
 313  0
                 final InstructionContext theSuccessor = cfg.contextOf(jsr.getNext());
 314  
 
 315  0
                 if (theSuccessor.execute(u.getOutFrame(oldchain), newchain, ev)) {
 316  0
                     ics.add(theSuccessor);
 317  0
                     ecs.add(newchain);
 318  
                 }
 319  0
             } else { // "not a ret"
 320  
                 // Normal successors. Add them to the queue of successors.
 321  0
                 final InstructionContext[] succs = u.getSuccessors();
 322  0
                 for (int s = 0; s < succs.length; s++) {
 323  0
                     final InstructionContext v = succs[s];
 324  
                     //if (v.execute(u.getOutFrame(oldchain), newchain, icv, ev)) {
 325  0
                     if (v.execute(u.getOutFrame(oldchain), newchain, ev)) {
 326  0
                         ics.add(v);
 327  0
                         ecs.add(newchain);
 328  
                     }
 329  
                 }
 330  
             }
 331  
             // Exception Handlers. Add them to the queue of successors.
 332  0
             final ExceptionHandler[] exc_hds = u.getExceptionHandlers();
 333  0
             for (int s = 0; s < exc_hds.length; s++) {
 334  0
                 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  0
                 final LocalVariables newLocals = u.getOutFrame(oldchain).getLocals();
 344  0
                 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  0
                 final Frame newFrame = new Frame(newLocals, newStack);
 349  
 
 350  0
                 if (v.execute(newFrame, ExecutionPath.EMPTY, ev)) {
 351  0
                     ics.add(v);
 352  0
                     ecs.add(ExecutionPath.EMPTY);
 353  
                 }
 354  
             }
 355  0
         }
 356  0
     }
 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  0
         final Frame vanillaFrame = new Frame(method.getMaxLocals(), method.getMaxStack());
 364  0
         if (!method.isStatic()) {
 365  0
             if (method.getName().equals(Constants.CONSTRUCTOR_NAME)) {
 366  0
                 Frame._this = new UninitializedObjectType(new ObjectType(clazz.getClassName()));
 367  0
                 vanillaFrame.getLocals().set(0, new UninitializedObjectType(new ObjectType(clazz.getClassName())));
 368  0
             } else {
 369  0
                 Frame._this = null;
 370  0
                 vanillaFrame.getLocals().set(0, new ObjectType(clazz.getClassName()));
 371  
             }
 372  
         }
 373  
         // fill local variables with parameter types
 374  0
         final Type[] argtypes = method.getArgumentTypes();
 375  0
         int twoslotoffset = 0;
 376  0
         for (int j = 0; j < argtypes.length; j++) {
 377  0
             if ((argtypes[j] == Type.SHORT) || (argtypes[j] == Type.BYTE) || (argtypes[j] == Type.CHAR) || (argtypes[j] == Type.BOOLEAN)) {
 378  0
                 argtypes[j] = Type.INT;
 379  
             }
 380  0
             vanillaFrame.getLocals().set(twoslotoffset + j + (method.isStatic() ? 0 : 1), argtypes[j]);
 381  0
             if (argtypes[j].getSize() == 2) {
 382  0
                 twoslotoffset++;
 383  0
                 vanillaFrame.getLocals().set(twoslotoffset + j + (method.isStatic() ? 0 : 1), Type.UNKNOWN);
 384  
             }
 385  
         }
 386  
 
 387  0
         return vanillaFrame;
 388  
     }
 389  
 
 390  
     private void rewrite(MethodGen method, ControlFlowGraph cfg) throws ClassNotFoundException {
 391  0
         final InstructionFactory insFactory = new InstructionFactory(method.getConstantPool());
 392  0
         final Vector invokeIns = new Vector();
 393  0
         final InstructionList insList = method.getInstructionList();
 394  0
         InstructionHandle ins = insList.getStart();
 395  0
         final InstructionList restorer = new InstructionList();
 396  0
         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  0
         int[] localVarsSize = new int[1];
 401  0
         localVarsSize[0] = method.getMaxLocals();
 402  
 
 403  0
         while (ins != null) {
 404  0
             InstructionHandle next = ins.getNext();
 405  
 
 406  
             // if not traversed by the analyser, then don't rewrite
 407  0
             InstructionContext context = null;
 408  0
             Frame frame = null;
 409  
             try {
 410  0
                 context = cfg.contextOf(ins);
 411  0
                 frame = context.getOutFrame(ExecutionPath.EMPTY);
 412  0
             } catch (AssertionViolatedException ave) {
 413  
                 // empty
 414  0
             }
 415  0
             if (frame != null) {
 416  0
                 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  0
                     final InvokeInstruction invoke = (InvokeInstruction) ins.getInstruction();
 421  0
                     final Type[] arguments = invoke.getArgumentTypes(method.getConstantPool());
 422  0
                     ReferenceType objecttype = null;
 423  0
                     if (!(invoke instanceof INVOKESTATIC)) {
 424  0
                         objecttype = (ReferenceType) context.getInFrame().getStack().peek(arguments.length);
 425  
                     }
 426  0
                     final InstructionList rList = restoreFrame(method, ins, insFactory, frame, objecttype);
 427  0
                     insList.append(ins, saveFrame(method, ins, count++, insFactory, frame));
 428  0
                     invokeIns.addElement(rList.getStart());
 429  0
                     restorer.append(rList);
 430  
                 }
 431  
                 // remove all new's
 432  0
                 if (ins.getInstruction().getOpcode() == Constants.NEW) {
 433  
                     try {
 434  
                         // remove additional dup's
 435  0
                         while (next != null && next.getInstruction().getOpcode() == Constants.DUP) {
 436  0
                             context = cfg.contextOf(next);
 437  0
                             context.getOutFrame(ExecutionPath.EMPTY);
 438  0
                             final InstructionHandle newnext = next.getNext();
 439  0
                             insList.delete(next);
 440  0
                             next = newnext;
 441  0
                         }
 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  0
                         if(next!=null && next.getNext()!=null && next.getNext().getInstruction().getOpcode() == Constants.DUP_X2) {
 460  0
                             InstructionHandle dupx2ptr = next.getNext();
 461  0
                             final InstructionHandle newnext = dupx2ptr.getNext();
 462  0
                             insList.insert(dupx2ptr, InstructionConstants.DUP);
 463  0
                             insList.delete(dupx2ptr);
 464  0
                             next = newnext;
 465  
                         }
 466  0
                         final InstructionTargeter[] targeter = ins.getTargeters();
 467  0
                         if (targeter != null) {
 468  0
                             final InstructionHandle newnext = ins.getNext();
 469  0
                             for (int i = 0; i < targeter.length; i++) {
 470  0
                                 targeter[i].updateTarget(ins, newnext);
 471  
                             }
 472  
                         }
 473  0
                         insList.delete(ins);
 474  0
                     } catch (TargetLostException tle) {
 475  0
                         throw new ClassNotFoundException(tle.getMessage(), tle);
 476  0
                     }
 477  0
                 } else if (ins.getInstruction().getOpcode() == Constants.INVOKESPECIAL) {
 478  
                     // duplicate stack before invokespecial to insert
 479  
                     // uninitialized object
 480  0
                     frame = context.getInFrame();
 481  0
                     final InvokeInstruction invoke = (InvokeInstruction) ins.getInstruction();
 482  0
                     final Type[] arguments = invoke.getArgumentTypes(method.getConstantPool());
 483  
 
 484  0
                     final OperandStack os = frame.getStack();
 485  0
                     final Type type = os.peek(arguments.length);
 486  0
                     if (type instanceof UninitializedObjectType) {
 487  0
                         final ObjectType objecttype = ((UninitializedObjectType) type).getInitialized();
 488  0
                         final InstructionList duplicator = duplicateStack(method, invoke, objecttype, localVarsSize);
 489  0
                         final InstructionTargeter[] targeter = ins.getTargeters();
 490  
 
 491  0
                         if (targeter != null) {
 492  0
                             final InstructionHandle newnext = duplicator.getStart();
 493  0
                             for (int i = 0; i < targeter.length; i++) {
 494  0
                                 targeter[i].updateTarget(ins, newnext);
 495  
                             }
 496  
                         }
 497  0
                         insList.insert(ins, duplicator);
 498  
                     }
 499  
                 }
 500  
             }
 501  0
             ins = next;
 502  0
         }
 503  
 
 504  
         // local variable index for storing the StackRecorder object
 505  0
         final int varStack = method.getMaxLocals();
 506  
         // instruction to load the stack recorder
 507  0
         Instruction loadStackRecorder = InstructionFactory.createLoad(STACK_RECORDER_TYPE, varStack);
 508  
 
 509  0
         final InstructionHandle firstIns = insList.getStart();
 510  0
         if (count > 0) {
 511  0
             final InstructionHandle[] tableTargets = new InstructionHandle[count];
 512  0
             int[] match = new int[count];
 513  0
             for (int i = 0; i < count; i++) {
 514  0
                 match[i] = i;
 515  
             }
 516  0
             invokeIns.copyInto(tableTargets);
 517  0
             insList.insert(restorer);
 518  
 
 519  
             // select frame restorer
 520  0
             insList.insert(new TABLESWITCH(match, tableTargets, firstIns));
 521  0
             insList.insert(insFactory.createInvoke(STACK_RECORDER_CLASS, getPopMethod(Type.INT), Type.INT, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
 522  0
             insList.insert(loadStackRecorder);
 523  
 
 524  
             // test if the continuation should be restored
 525  0
             insList.insert(new IFEQ(firstIns));
 526  0
             insList.insert(insFactory.createFieldAccess(STACK_RECORDER_CLASS, RESTORING_FIELD, Type.BOOLEAN, Constants.GETFIELD));
 527  0
             insList.insert(loadStackRecorder);
 528  
 
 529  
             // test if no current continuation exists
 530  0
             insList.insert(new IFNULL(firstIns));
 531  
 
 532  
             // get current continuation and store in the next to last local variable
 533  0
             insList.insert(InstructionFactory.createStore(STACK_RECORDER_TYPE, varStack));
 534  0
             insList.insert(InstructionConstants.DUP);
 535  0
             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  0
             localVarsSize[0] = Math.max( localVarsSize[0], method.getMaxLocals()+1 );
 539  
 
 540  
             // make room for additional object (StackRecorder)
 541  0
             method.setMaxStack(method.getMaxStack() + 2);
 542  
         }
 543  
 
 544  0
         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  0
         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  0
         Attribute[] atts = method.getCodeAttributes();
 555  0
         for( int i=0; i<atts.length; i++ ) {
 556  0
             if(atts[i].getNameIndex()==method.getConstantPool().lookupUtf8("LocalVariableTypeTable"))
 557  0
                 method.removeCodeAttribute(atts[i]);
 558  
         }
 559  0
     }
 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  0
         final InstructionFactory insFactory = new InstructionFactory(method.getConstantPool());
 567  0
         final InstructionList insList = new InstructionList();
 568  
 
 569  0
         final Type[] arguments = invoke.getArgumentTypes(method.getConstantPool());
 570  
 
 571  0
         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  0
         for (int i = arguments.length - 1; i >= 0; i--) {
 575  0
             Type type = arguments[i];
 576  0
             insList.append(InstructionFactory.createStore(type,localVarOffset));
 577  0
             localVarOffset += type.getSize();
 578  
         }
 579  
 
 580  0
         localVarsSize[0] = Math.max(localVarsSize[0],localVarOffset);
 581  
 
 582  
         // create uninitialzed object
 583  0
         insList.append(insFactory.createNew(objecttype));
 584  0
         insList.append(InstructionConstants.DUP);
 585  
 
 586  
         // move the arguments back into the operand stack
 587  0
         for (int i = 0; i < arguments.length; i++) {
 588  0
             Type type = arguments[i];
 589  
 
 590  0
             localVarOffset -= type.getSize();
 591  0
             insList.append(InstructionFactory.createLoad(type,localVarOffset));
 592  0
             if(type instanceof ReferenceType) {
 593  
                 // erase the local variable, or else we have a potential for memory leak
 594  0
                 insList.append(InstructionConstants.ACONST_NULL);
 595  0
                 insList.append(InstructionFactory.createStore(type,localVarOffset));
 596  
             }
 597  
         }
 598  0
         return insList;
 599  
     }
 600  
 
 601  
     private boolean rewriteable(MethodGen method, InstructionHandle handle) {
 602  
         // check in the invocation can be a breakpoint.
 603  0
         int opcode = handle.getInstruction().getOpcode();
 604  
 
 605  0
         if(!(handle.getInstruction() instanceof InvokeInstruction))
 606  0
             return false;   // not an INVOKE***
 607  
 
 608  0
         if (opcode == Constants.INVOKESPECIAL) {
 609  0
             final InvokeInstruction ivs = (InvokeInstruction) handle.getInstruction();
 610  0
             final String mName = ivs.getMethodName(method.getConstantPool());
 611  0
             if(mName.equals(Constants.CONSTRUCTOR_NAME))
 612  0
                 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  0
         return true;
 624  
     }
 625  
 
 626  
     private InstructionList saveFrame(MethodGen method, InstructionHandle handle, int pc, InstructionFactory insFactory, Frame frame) {
 627  0
         final InstructionList insList = new InstructionList();
 628  
 
 629  
         // Remove needless return type from stack
 630  0
         final InvokeInstruction inv = (InvokeInstruction) handle.getInstruction();
 631  0
         final Type returnType = getReturnType(method.getConstantPool().getConstantPool(), inv.getIndex());
 632  0
         if (returnType.getSize() > 0) {
 633  0
             insList.insert(InstructionFactory.createPop(returnType.getSize()));
 634  
         }
 635  0
         boolean skipFirst = returnType.getSize() > 0;
 636  
 
 637  
         //if (debug) {
 638  
         //    insList.append(insFactory.createPrintln("save stack"));
 639  
         //}
 640  
 
 641  
         // instruction for loading StackRecorder
 642  0
         Instruction loadStackRecorder = InstructionFactory.createLoad(STACK_RECORDER_TYPE, method.getMaxLocals());
 643  
 
 644  
         // save stack
 645  0
         final OperandStack os = frame.getStack();
 646  0
         for (int i = skipFirst ? 1 : 0; i < os.size(); i++) {
 647  0
             Type type = os.peek(i);
 648  0
             if (type instanceof BasicType) {
 649  0
                 if (type.getSize() < 2 && !type.equals(Type.FLOAT)) {
 650  0
                     type = Type.INT;
 651  
                 }
 652  
 
 653  
                 // check for types with two words on stack
 654  0
                 if (type.equals(Type.LONG) || type.equals(Type.DOUBLE)) {
 655  0
                     insList.append(InstructionConstants.ACONST_NULL); // create dummy stack entry
 656  0
                     insList.append(loadStackRecorder);
 657  0
                     insList.append(InstructionConstants.DUP2_X2); // swap Stack object and long/float
 658  0
                     insList.append(InstructionConstants.POP2);
 659  0
                 } else {
 660  0
                     insList.append(loadStackRecorder);
 661  0
                     insList.append(InstructionConstants.SWAP);
 662  
                 }
 663  0
                 insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, getPushMethod(type), Type.VOID, new Type[] { type }, Constants.INVOKEVIRTUAL));
 664  0
                 if (type.equals(Type.LONG) || type.equals(Type.DOUBLE))
 665  0
                     insList.append(InstructionConstants.POP); // remove dummy stack entry
 666  0
             } else if (type == null) {
 667  0
                 insList.append(InstructionConstants.POP);
 668  0
             } else if (type instanceof UninitializedObjectType) {
 669  
                 // After the remove of new, there shouldn't be a
 670  
                 // uninitialized object on the stack
 671  0
             } else if (type instanceof ReferenceType) {
 672  0
                 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  0
                     insList.append(InstructionConstants.POP);
 677  0
                 } else {
 678  0
                     insList.append(loadStackRecorder);
 679  0
                     insList.append(InstructionConstants.SWAP);
 680  0
                     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  0
         if (debug) {
 686  0
             insList.insert(insFactory.createPrintln("capturing invocation "+method));
 687  
         }
 688  0
         insList.insert(new IFEQ(handle.getNext()));
 689  
 
 690  
         // test if the continuation should be captured after the invocation
 691  0
         insList.insert(insFactory.createFieldAccess(STACK_RECORDER_CLASS, CAPTURING_FIELD, Type.BOOLEAN, Constants.GETFIELD));
 692  0
         insList.insert(loadStackRecorder);
 693  
 
 694  
         // test if continuation exists
 695  0
         insList.insert(new IFNULL(handle.getNext()));
 696  0
         insList.insert(loadStackRecorder);
 697  
 
 698  
         // save local variables
 699  
         //if (debug) {
 700  
         //    insList.append(insFactory.createPrintln("save local variables"));
 701  
         //}
 702  
 
 703  0
         final LocalVariables lvs = frame.getLocals();
 704  0
         for (int i = 0; i < lvs.maxLocals(); i++) {
 705  0
             Type type = lvs.get(i);
 706  0
             if (type instanceof BasicType) {
 707  0
                 insList.append(loadStackRecorder);
 708  0
                 insList.append(InstructionFactory.createLoad(type, i));
 709  0
                 if (type.getSize() < 2 && !type.equals(Type.FLOAT))
 710  0
                     type = Type.INT;
 711  0
                 insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, getPushMethod(type), Type.VOID, new Type[] { type }, Constants.INVOKEVIRTUAL));
 712  0
             } else if (type == null || type==Type.NULL) {
 713  
                 // no need to save null
 714  0
             } else if (type instanceof UninitializedObjectType) {
 715  
                 // no need to save uninitialized objects
 716  0
             } else if (type instanceof ReferenceType) {
 717  0
                 if (i == 0 && !currentMethodStatic) {
 718  
                     // remember current object
 719  0
                     insList.append(loadStackRecorder);
 720  0
                     insList.append(InstructionFactory.createLoad(type, i));
 721  0
                     insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, PUSH_METHOD + "Reference", Type.VOID, new Type[] { Type.OBJECT }, Constants.INVOKEVIRTUAL));
 722  
                 }
 723  0
                 insList.append(loadStackRecorder);
 724  0
                 insList.append(InstructionFactory.createLoad(type, i));
 725  0
                 insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, getPushMethod(Type.OBJECT), Type.VOID, new Type[] { Type.OBJECT }, Constants.INVOKEVIRTUAL));
 726  
             }
 727  
         }
 728  
         // save programcounter
 729  0
         insList.append(loadStackRecorder);
 730  0
         insList.append(new PUSH(method.getConstantPool(), pc));
 731  0
         insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, getPushMethod(Type.INT), Type.VOID, new Type[] { Type.INT }, Constants.INVOKEVIRTUAL));
 732  
         // return NULL result
 733  0
         insList.append(InstructionFactory.createNull(method.getReturnType()));
 734  0
         insList.append(InstructionFactory.createReturn(method.getReturnType()));
 735  0
         return insList;
 736  
     }
 737  
 
 738  
     private InstructionList restoreFrame(MethodGen method, InstructionHandle handle, InstructionFactory insFactory, Frame frame, ReferenceType objecttype) {
 739  0
         final InstructionList insList = new InstructionList();
 740  
         // restore local variables
 741  
         //if (debug) {
 742  
         //    insList.append(insFactory.createPrintln("restore local variables"));
 743  
         //}
 744  
 
 745  0
         final LocalVariables lvs = frame.getLocals();
 746  0
         Instruction loadStackRecorder = InstructionFactory.createLoad(STACK_RECORDER_TYPE, method.getMaxLocals());
 747  
 
 748  0
         for (int i = lvs.maxLocals() - 1; i >= 0; i--) {
 749  0
             Type type = lvs.get(i);
 750  0
             if (type instanceof BasicType) {
 751  0
                 insList.append(loadStackRecorder);
 752  0
                 if (type.getSize() < 2 && !type.equals(Type.FLOAT)) {
 753  0
                     type = Type.INT;
 754  
                 }
 755  0
                 insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, getPopMethod(type), type, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
 756  0
                 insList.append(InstructionFactory.createStore(type, i));
 757  0
             } else if (type == null) {
 758  0
                 insList.append(InstructionConstants.ACONST_NULL);
 759  0
                 insList.append(new ASTORE(i));
 760  0
             } else if (type instanceof UninitializedObjectType) {
 761  
                 // No uninitilaized objects should be found
 762  
                 // in the local variables.
 763  0
                 throw new Error("assertion failure");
 764  0
             } else if (type instanceof ReferenceType) {
 765  0
                 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  0
                     insList.append(InstructionConstants.ACONST_NULL);
 770  0
                 } else {
 771  0
                     insList.append(loadStackRecorder);
 772  0
                     insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, getPopMethod(Type.OBJECT), Type.OBJECT, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
 773  0
                     if (!type.equals(Type.OBJECT)) {
 774  0
                         insList.append(insFactory.createCast(Type.OBJECT, type));
 775  
                     }
 776  
                 }
 777  0
                 insList.append(new ASTORE(i));
 778  
             }
 779  
         }
 780  
 
 781  0
         final InvokeInstruction inv = (InvokeInstruction) handle.getInstruction();
 782  0
         final Type returnType = getReturnType(method.getConstantPool().getConstantPool(), inv.getIndex());
 783  0
         boolean skipFirst = returnType.getSize() > 0;
 784  
 
 785  
         // restore stack
 786  
         //if (debug) {
 787  
         //    insList.append(insFactory.createPrintln("restore stack"));
 788  
         //}
 789  
 
 790  0
         final OperandStack os = frame.getStack();
 791  0
         for (int i = os.size() - 1; i >= (skipFirst ? 1 : 0); i--) {
 792  0
             Type type = os.peek(i);
 793  0
             if (type instanceof BasicType) {
 794  0
                 if (type.getSize() < 2 && !type.equals(Type.FLOAT)) {
 795  0
                     type = Type.INT;
 796  
                 }
 797  0
                 insList.append(loadStackRecorder);
 798  0
                 insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, getPopMethod(type), type, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
 799  0
             } else if (type == null || type==Type.NULL) {
 800  0
                 insList.append(new ACONST_NULL());
 801  0
             } else if (type instanceof UninitializedObjectType) {
 802  
                 // After the remove of new, there shouldn't be a
 803  
                 // uninitialized object on the stack
 804  0
             } else if (type instanceof ReferenceType) {
 805  0
                 insList.append(loadStackRecorder);
 806  0
                 insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, getPopMethod(Type.OBJECT), Type.OBJECT, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
 807  0
                 if (!type.equals(Type.OBJECT))
 808  0
                     insList.append(insFactory.createCast(Type.OBJECT, type));
 809  
             }
 810  
         }
 811  
         // retrieve current object
 812  0
         if (!(inv instanceof INVOKESTATIC)) {
 813  0
             insList.append(loadStackRecorder);
 814  0
             insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, POP_METHOD + "Reference", Type.OBJECT, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
 815  0
             insList.append(insFactory.createCast(Type.OBJECT, objecttype));
 816  
         }
 817  
         // Create null types for the parameters of the method invocation
 818  0
         final Type[] paramTypes = getParamTypes(method.getConstantPool().getConstantPool(), inv.getIndex());
 819  0
         for (int j = 0; j < paramTypes.length; j++) {
 820  0
             insList.append(InstructionFactory.createNull(paramTypes[j]));
 821  
         }
 822  
         // go to last invocation
 823  0
         insList.append(new GOTO(handle));
 824  0
         return insList;
 825  
     }
 826  
 
 827  
     private Type[] getParamTypes(ConstantPool cp, int index) {
 828  0
         final ConstantCP cmr = (ConstantCP) cp.getConstant(index);
 829  0
         final ConstantNameAndType cnat = (ConstantNameAndType) cp.getConstant(cmr.getNameAndTypeIndex());
 830  0
         final String sig = ((ConstantUtf8) cp.getConstant(cnat.getSignatureIndex())).getBytes();
 831  0
         return Type.getArgumentTypes(sig);
 832  
     }
 833  
 
 834  
     private Type getReturnType(ConstantPool cp, int index) {
 835  0
         final ConstantCP cmr = (ConstantCP) cp.getConstant(index);
 836  0
         final ConstantNameAndType cnat = (ConstantNameAndType) cp.getConstant(cmr.getNameAndTypeIndex());
 837  0
         final String sig = ((ConstantUtf8) cp.getConstant(cnat.getSignatureIndex())).getBytes();
 838  0
         return Type.getReturnType(sig);
 839  
     }
 840  
 
 841  
     private String getPopMethod(Type type) {
 842  0
         return POP_METHOD + getTypeSuffix(type);
 843  
     }
 844  
 
 845  
     private String getPushMethod(Type type) {
 846  0
         return PUSH_METHOD + getTypeSuffix(type);
 847  
     }
 848  
 
 849  
     private String getTypeSuffix(Type type) {
 850  0
         if (type.equals(Type.BOOLEAN))
 851  0
             return "Int";
 852  0
         else if (type.equals(Type.CHAR))
 853  0
             return "Int";
 854  0
         else if (type.equals(Type.FLOAT))
 855  0
             return "Float";
 856  0
         else if (type.equals(Type.DOUBLE))
 857  0
             return "Double";
 858  0
         else if (type.equals(Type.BYTE))
 859  0
             return "Int";
 860  0
         else if (type.equals(Type.SHORT))
 861  0
             return "Int";
 862  0
         else if (type.equals(Type.INT))
 863  0
             return "Int";
 864  0
         else if (type.equals(Type.LONG))
 865  0
             return "Long";
 866  
         // VOID and OBJECT are "Object"
 867  0
         return "Object";
 868  
     }
 869  
 
 870  
 }