Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.commons.javaflow.bytecode.transformation.bcel; import org.apache.bcel.Constants; import org.apache.bcel.Repository; import org.apache.bcel.classfile.Attribute; import org.apache.bcel.classfile.ClassFormatException; import org.apache.bcel.classfile.ClassParser; import org.apache.bcel.classfile.ConstantCP; import org.apache.bcel.classfile.ConstantNameAndType; import org.apache.bcel.classfile.ConstantPool; import org.apache.bcel.classfile.ConstantUtf8; import org.apache.bcel.classfile.JavaClass; import org.apache.bcel.classfile.Method; import org.apache.bcel.generic.ACONST_NULL; import org.apache.bcel.generic.ASTORE; import org.apache.bcel.generic.BasicType; import org.apache.bcel.generic.ClassGen; import org.apache.bcel.generic.ConstantPoolGen; import org.apache.bcel.generic.GOTO; import org.apache.bcel.generic.IFEQ; import org.apache.bcel.generic.IFNULL; import org.apache.bcel.generic.INVOKESTATIC; import org.apache.bcel.generic.Instruction; import org.apache.bcel.generic.InstructionConstants; import org.apache.bcel.generic.InstructionFactory; import org.apache.bcel.generic.InstructionHandle; import org.apache.bcel.generic.InstructionList; import org.apache.bcel.generic.InstructionTargeter; import org.apache.bcel.generic.InvokeInstruction; import org.apache.bcel.generic.MethodGen; import org.apache.bcel.generic.ObjectType; import org.apache.bcel.generic.PUSH; import org.apache.bcel.generic.RET; import org.apache.bcel.generic.ReferenceType; import org.apache.bcel.generic.TABLESWITCH; import org.apache.bcel.generic.TargetLostException; import org.apache.bcel.generic.Type; import org.apache.bcel.util.ClassPath; import org.apache.bcel.util.SyntheticRepository; import org.apache.bcel.verifier.exc.AssertionViolatedException; import org.apache.commons.javaflow.bytecode.Continuable; import org.apache.commons.javaflow.bytecode.StackRecorder; import org.apache.commons.javaflow.bytecode.transformation.ResourceTransformer; import org.apache.commons.javaflow.bytecode.transformation.bcel.analyser.ControlFlowGraph; import org.apache.commons.javaflow.bytecode.transformation.bcel.analyser.ExceptionHandler; import org.apache.commons.javaflow.bytecode.transformation.bcel.analyser.ExecutionPath; import org.apache.commons.javaflow.bytecode.transformation.bcel.analyser.ExecutionVisitor; import org.apache.commons.javaflow.bytecode.transformation.bcel.analyser.Frame; import org.apache.commons.javaflow.bytecode.transformation.bcel.analyser.InstructionContext; import org.apache.commons.javaflow.bytecode.transformation.bcel.analyser.LocalVariables; import org.apache.commons.javaflow.bytecode.transformation.bcel.analyser.OperandStack; import org.apache.commons.javaflow.bytecode.transformation.bcel.analyser.UninitializedObjectType; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; /** * {@link ResourceTransformer} that uses BCEL. * * @deprecated use the AsmClassTransformer instead */ public final class BcelClassTransformer implements ResourceTransformer { private final static Log log = LogFactory.getLog(BcelClassTransformer.class); private static final String STACK_RECORDER_CLASS = StackRecorder.class.getName(); private static final ObjectType STACK_RECORDER_TYPE = new ObjectType(STACK_RECORDER_CLASS); private static final String CONTINUABLE_CLASS = Continuable.class.getName(); private static final String STACK_METHOD = "get"; private static final String POP_METHOD = "pop"; private static final String PUSH_METHOD = "push"; private static final String RESTORING_FIELD = "isRestoring"; private static final String CAPTURING_FIELD = "isCapturing"; private boolean currentMethodStatic = false; public static boolean debug = false; /** * BCEL uses a repository object to load/store information related to other classes, * which are sometimes necessary to perform bytecode instrumentation. * <p> * Can be null, in which case the global BCEL repository is assumed to work. * * <p> * repository is a singleton instance in BCEL, so we have to be careful when changing it. * See BugZilla 38057. This is fundamentally broken. */ private org.apache.bcel.util.Repository repository; /** * Since BCEL repository is a global resource, access needs to be controlled among * {@link BcelClassTransformer} instances. */ private static final Object repositoryLock = new Object(); static { try { debug = System.getProperty(BcelClassTransformer.class.getName() + ".debug") != null; } catch (SecurityException e) { // assume no debugging } } public BcelClassTransformer() { } /** * Creates an instance that uses the passed classpath for the resolution * of referenced classes. * * @param classPathFile The classpath to use for class file resolution. */ public BcelClassTransformer(File classPathFile) { ClassPath classPath = new ClassPath(classPathFile.getAbsolutePath()); this.repository = SyntheticRepository.getInstance(classPath); } public BcelClassTransformer(org.apache.bcel.util.Repository repository) { this.repository = repository; } public byte[] transform(final byte[] original) { if (repository == null) { return doTransform(original); } else { synchronized (repositoryLock) { org.apache.bcel.util.Repository old = Repository.getRepository(); Repository.setRepository(repository); try { return doTransform(original); } finally { Repository.setRepository(old); } } } } private byte[] doTransform(final byte[] original) { final InputStream is = new ByteArrayInputStream(original); final ClassParser parser = new ClassParser(is, null); JavaClass javaClazz = null; try { javaClazz = parser.parse(); } catch (ClassFormatException e2) { e2.printStackTrace(); } catch (IOException e2) { e2.printStackTrace(); } finally { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } if (javaClazz == null) { return null; } Repository.addClass(javaClazz); log.debug("transforming class " + javaClazz.getClassName()); //final JavaClass clazz = Repository.lookupClass(clazzName); {// check if this class is already instrumented String[] intfs = javaClazz.getInterfaceNames(); for (int i = 0; i < intfs.length; i++) if (intfs[i].equals(CONTINUABLE_CLASS)) { // no need to instrument further log.debug(javaClazz.getClassName() + " is already instrumented. Skipping"); return original; } } final ClassGen clazzGen = new ClassGen(javaClazz); final ConstantPoolGen cp = clazzGen.getConstantPool(); if (debug) { dump(javaClazz, ".orig"); } // vistor to build the frame information final ExecutionVisitor ev = new ExecutionVisitor(); ev.setConstantPoolGen(cp); // obsolete, but neccesary to execute the InvokeContext //final InstConstraintVisitor icv = new InstConstraintVisitor(); //icv.setConstantPoolGen(cp); final Method[] methods = clazzGen.getMethods(); for (int i = 0; i < methods.length; i++) { final MethodGen method = new MethodGen(methods[i], clazzGen.getClassName(), cp); currentMethodStatic = methods[i].isStatic(); if (needsRewriting(method)) { // analyse the code of the method to create the frame // information about every instruction final ControlFlowGraph cfg = new ControlFlowGraph(method); analyse(clazzGen, method, cfg, ev); try { // add intercepting code rewrite(method, cfg); } catch (ClassNotFoundException e1) { e1.printStackTrace(); } clazzGen.replaceMethod(methods[i], method.getMethod()); } } clazzGen.addInterface(CONTINUABLE_CLASS); JavaClass newClass = clazzGen.getJavaClass(); final byte[] changed = newClass.getBytes(); if (debug) { dump(newClass, ".rewritten"); } return changed; } /** * Dumps the class file to the curent directory for debugging. */ private void dump(JavaClass javaClazz, String suffix) { String path = javaClazz.getClassName() + suffix; final byte[] orig = javaClazz.getBytes(); FileOutputStream out = null; try { out = new FileOutputStream(path); log.debug("writing " + path); out.write(orig); out.flush(); } catch (final IOException e) { e.printStackTrace(); } finally { try { if (out != null) { out.close(); } } catch (final IOException e1) { log.error(e1.getMessage(), e1); } finally { out = null; } } try { out = new FileOutputStream(path + ".java"); log.debug("writing " + path + ".java"); final DecompilingVisitor v = new DecompilingVisitor(javaClazz, out); v.start(); } catch (final Exception e) { e.printStackTrace(); } finally { try { if (out != null) { out.close(); } } catch (final IOException e1) { log.error(e1.getMessage(), e1); } finally { out = null; } } } private boolean needsRewriting(MethodGen m) { if (m.getName().equals(Constants.CONSTRUCTOR_NAME) || m.getName().equals(Constants.STATIC_INITIALIZER_NAME) || m.isNative() || m.isAbstract()) { return false; } else { return true; } } private void analyse(ClassGen clazz, MethodGen method, ControlFlowGraph cfg, ExecutionVisitor ev) { log.debug("analyse " + method.getName()); final Frame vanillaFrame = createInitialFrame(method, clazz); final List<InstructionContext> ics = new ArrayList<InstructionContext>(); // Type: InstructionContext final List<ExecutionPath> ecs = new ArrayList<ExecutionPath>(); // Type: ExecutionPath final InstructionContext start = cfg.contextOf(method.getInstructionList().getStart()); start.execute(vanillaFrame, ExecutionPath.EMPTY, ev); // new ArrayList() <=> no Instruction was executed before // => Top-Level routine (no jsr call before) ics.add(start); ecs.add(ExecutionPath.EMPTY); while (!ics.isEmpty()) { final InstructionContext u = ics.remove(0); final ExecutionPath oldchain = ecs.remove(0); final ExecutionPath newchain = oldchain.append(u); if ((u.getInstruction().getInstruction()) instanceof RET) { // where did we come from? this JSR InstructionHandle jsr = oldchain.lastExecutionJSR().getInstruction(); // so the next instruction to execute will be this final InstructionContext theSuccessor = cfg.contextOf(jsr.getNext()); if (theSuccessor.execute(u.getOutFrame(oldchain), newchain, ev)) { ics.add(theSuccessor); ecs.add(newchain); } } else { // "not a ret" // Normal successors. Add them to the queue of successors. final InstructionContext[] succs = u.getSuccessors(); for (int s = 0; s < succs.length; s++) { final InstructionContext v = succs[s]; //if (v.execute(u.getOutFrame(oldchain), newchain, icv, ev)) { if (v.execute(u.getOutFrame(oldchain), newchain, ev)) { ics.add(v); ecs.add(newchain); } } } // Exception Handlers. Add them to the queue of successors. final ExceptionHandler[] exc_hds = u.getExceptionHandlers(); for (int s = 0; s < exc_hds.length; s++) { final InstructionContext v = cfg.contextOf(exc_hds[s].getHandlerStart()); // TODO: the "oldchain" and "newchain" is used to determine the // subroutine // we're in (by searching for the last JSR) by the // InstructionContext // implementation. Therefore, we should not use this chain // mechanism // when dealing with exception handlers. final LocalVariables newLocals = u.getOutFrame(oldchain).getLocals(); final OperandStack newStack = new OperandStack(u.getOutFrame(oldchain).getStack().maxStack(), (exc_hds[s].getExceptionType() == null ? Type.THROWABLE : exc_hds[s].getExceptionType())); final Frame newFrame = new Frame(newLocals, newStack); if (v.execute(newFrame, ExecutionPath.EMPTY, ev)) { ics.add(v); ecs.add(ExecutionPath.EMPTY); } } } } /** * Creates a {@link Frame} object that represents the state of the stack frame * at the beginning of a method. */ private Frame createInitialFrame(MethodGen method, ClassGen clazz) { final Frame vanillaFrame = new Frame(method.getMaxLocals(), method.getMaxStack()); if (!method.isStatic()) { if (method.getName().equals(Constants.CONSTRUCTOR_NAME)) { Frame._this = new UninitializedObjectType(new ObjectType(clazz.getClassName())); vanillaFrame.getLocals().set(0, new UninitializedObjectType(new ObjectType(clazz.getClassName()))); } else { Frame._this = null; vanillaFrame.getLocals().set(0, new ObjectType(clazz.getClassName())); } } // fill local variables with parameter types final Type[] argtypes = method.getArgumentTypes(); int twoslotoffset = 0; for (int j = 0; j < argtypes.length; j++) { if ((argtypes[j] == Type.SHORT) || (argtypes[j] == Type.BYTE) || (argtypes[j] == Type.CHAR) || (argtypes[j] == Type.BOOLEAN)) { argtypes[j] = Type.INT; } vanillaFrame.getLocals().set(twoslotoffset + j + (method.isStatic() ? 0 : 1), argtypes[j]); if (argtypes[j].getSize() == 2) { twoslotoffset++; vanillaFrame.getLocals().set(twoslotoffset + j + (method.isStatic() ? 0 : 1), Type.UNKNOWN); } } return vanillaFrame; } private void rewrite(MethodGen method, ControlFlowGraph cfg) throws ClassNotFoundException { final InstructionFactory insFactory = new InstructionFactory(method.getConstantPool()); final List<InstructionHandle> invokeIns = new ArrayList<InstructionHandle>(); final InstructionList insList = method.getInstructionList(); InstructionHandle ins = insList.getStart(); final InstructionList restorer = new InstructionList(); int count = 0; // count # of breakpoints // we need to expand the local variable size to store arguments for the constructor invocation. // compute the necessary size in this variable. int[] localVarsSize = new int[1]; localVarsSize[0] = method.getMaxLocals(); while (ins != null) { InstructionHandle next = ins.getNext(); // if not traversed by the analyser, then don't rewrite InstructionContext context = null; Frame frame = null; try { context = cfg.contextOf(ins); frame = context.getOutFrame(ExecutionPath.EMPTY); } catch (AssertionViolatedException ave) { // empty } if (frame != null) { if (rewriteable(method, ins)) { // Add frame saver and restorer for the current breakpoint // determine type of object for the method invocation final InvokeInstruction invoke = (InvokeInstruction) ins.getInstruction(); final Type[] arguments = invoke.getArgumentTypes(method.getConstantPool()); ReferenceType objecttype = null; if (!(invoke instanceof INVOKESTATIC)) { objecttype = (ReferenceType) context.getInFrame().getStack().peek(arguments.length); } final InstructionList rList = restoreFrame(method, ins, insFactory, frame, objecttype); insList.append(ins, saveFrame(method, ins, count++, insFactory, frame)); invokeIns.add(rList.getStart()); restorer.append(rList); } // remove all new's if (ins.getInstruction().getOpcode() == Constants.NEW) { try { // remove additional dup's while (next != null && next.getInstruction().getOpcode() == Constants.DUP) { context = cfg.contextOf(next); context.getOutFrame(ExecutionPath.EMPTY); final InstructionHandle newnext = next.getNext(); insList.delete(next); next = newnext; } // if there are any dup_x2 following new and dup, replace them with dup. // some java compiler generates such bytecode for code like // someObject.someStringField += "abc" // // which yield // NEW StringBuffer // DUP // ALOAD // someObject // DUP_x2 // GETFIELD someStringField // INVOKESPECIAL StringBuffer.<init> // LDC "abc" // INVOKEVIRTUAL StringBuffer.append // INVOKEVIRTUAL StringBuffer.toString // PUTFIELD someStringField // // replacing this DUP_x2 -> DUP is required for moving the new. if (next != null && next.getNext() != null && next.getNext().getInstruction().getOpcode() == Constants.DUP_X2) { InstructionHandle dupx2ptr = next.getNext(); final InstructionHandle newnext = dupx2ptr.getNext(); insList.insert(dupx2ptr, InstructionConstants.DUP); insList.delete(dupx2ptr); next = newnext; } final InstructionTargeter[] targeter = ins.getTargeters(); if (targeter != null) { final InstructionHandle newnext = ins.getNext(); for (int i = 0; i < targeter.length; i++) { targeter[i].updateTarget(ins, newnext); } } insList.delete(ins); } catch (TargetLostException tle) { throw new ClassNotFoundException(tle.getMessage(), tle); } } else if (ins.getInstruction().getOpcode() == Constants.INVOKESPECIAL) { // duplicate stack before invokespecial to insert // uninitialized object frame = context.getInFrame(); final InvokeInstruction invoke = (InvokeInstruction) ins.getInstruction(); final Type[] arguments = invoke.getArgumentTypes(method.getConstantPool()); final OperandStack os = frame.getStack(); final Type type = os.peek(arguments.length); if (type instanceof UninitializedObjectType) { final ObjectType objecttype = ((UninitializedObjectType) type).getInitialized(); final InstructionList duplicator = duplicateStack(method, invoke, objecttype, localVarsSize); final InstructionTargeter[] targeter = ins.getTargeters(); if (targeter != null) { final InstructionHandle newnext = duplicator.getStart(); for (int i = 0; i < targeter.length; i++) { targeter[i].updateTarget(ins, newnext); } } insList.insert(ins, duplicator); } } } ins = next; } // local variable index for storing the StackRecorder object final int varStack = method.getMaxLocals(); // instruction to load the stack recorder Instruction loadStackRecorder = InstructionFactory.createLoad(STACK_RECORDER_TYPE, varStack); final InstructionHandle firstIns = insList.getStart(); if (count > 0) { int[] match = new int[count]; for (int i = 0; i < count; i++) { match[i] = i; } InstructionHandle[] tableTargets = new InstructionHandle[count]; tableTargets = invokeIns.toArray(tableTargets); insList.insert(restorer); // select frame restorer insList.insert(new TABLESWITCH(match, tableTargets, firstIns)); insList.insert(insFactory.createInvoke(STACK_RECORDER_CLASS, getPopMethod(Type.INT), Type.INT, Type.NO_ARGS, Constants.INVOKEVIRTUAL)); insList.insert(loadStackRecorder); // test if the continuation should be restored insList.insert(new IFEQ(firstIns)); insList.insert(insFactory.createFieldAccess(STACK_RECORDER_CLASS, RESTORING_FIELD, Type.BOOLEAN, Constants.GETFIELD)); insList.insert(loadStackRecorder); // test if no current continuation exists insList.insert(new IFNULL(firstIns)); // get current continuation and store in the next to last local variable insList.insert(InstructionFactory.createStore(STACK_RECORDER_TYPE, varStack)); insList.insert(InstructionConstants.DUP); insList.insert(insFactory.createInvoke(STACK_RECORDER_CLASS, STACK_METHOD, STACK_RECORDER_TYPE, Type.NO_ARGS, Constants.INVOKESTATIC)); // we need one more variable for storing StackRecorder localVarsSize[0] = Math.max(localVarsSize[0], method.getMaxLocals() + 1); // make room for additional object (StackRecorder) method.setMaxStack(method.getMaxStack() + 2); } method.setMaxLocals(localVarsSize[0]); // make room for additional 'null' we push on the stack to clear // the local variables that are used during the constructor invocation instrumentation. // ideally we should be able to determine when we need to add +1, but for now just to be // safe and let's always add this. method.setMaxStack(method.getMaxStack() + 1); // remove LocalVariableTypeTable attribute until BCEL #33549 is fixed, // so that JDK 5.0 classes can be instrumented correctly. // see http://issues.apache.org/bugzilla/show_bug.cgi?id=33549 Attribute[] atts = method.getCodeAttributes(); for (int i = 0; i < atts.length; i++) { if (atts[i].getNameIndex() == method.getConstantPool().lookupUtf8("LocalVariableTypeTable")) method.removeCodeAttribute(atts[i]); } } /** * Used to instrument the constructor invocation. */ private InstructionList duplicateStack(MethodGen method, InvokeInstruction invoke, ObjectType objecttype, int[] localVarsSize) { // reconstruction of an uninitialed object to call the constructor. final InstructionFactory insFactory = new InstructionFactory(method.getConstantPool()); final InstructionList insList = new InstructionList(); final Type[] arguments = invoke.getArgumentTypes(method.getConstantPool()); int localVarOffset = method.getMaxLocals() + 1; // +1 for stack recorder // move all arguments for the constructor from the operand stack into local variables for (int i = arguments.length - 1; i >= 0; i--) { Type type = arguments[i]; insList.append(InstructionFactory.createStore(type, localVarOffset)); localVarOffset += type.getSize(); } localVarsSize[0] = Math.max(localVarsSize[0], localVarOffset); // create uninitialzed object insList.append(insFactory.createNew(objecttype)); insList.append(InstructionConstants.DUP); // move the arguments back into the operand stack for (int i = 0; i < arguments.length; i++) { Type type = arguments[i]; localVarOffset -= type.getSize(); insList.append(InstructionFactory.createLoad(type, localVarOffset)); if (type instanceof ReferenceType) { // erase the local variable, or else we have a potential for memory leak insList.append(InstructionConstants.ACONST_NULL); insList.append(InstructionFactory.createStore(type, localVarOffset)); } } return insList; } private boolean rewriteable(MethodGen method, InstructionHandle handle) { // check in the invocation can be a breakpoint. int opcode = handle.getInstruction().getOpcode(); if (!(handle.getInstruction() instanceof InvokeInstruction)) return false; // not an INVOKE*** if (opcode == Constants.INVOKESPECIAL) { final InvokeInstruction ivs = (InvokeInstruction) handle.getInstruction(); final String mName = ivs.getMethodName(method.getConstantPool()); if (mName.equals(Constants.CONSTRUCTOR_NAME)) return false; // can't instrument the constructor invocation } // final int index = ((InvokeInstruction) handle.getInstruction()).getIndex(); // final String classname = getObjectType(method.getConstantPool().getConstantPool(), index).getClassName(); // rewrite invocation if object is continuable or a continuation // object // FIXME: //return Repository.implementationOf(classname, CONTINUABLE_CLASS) || Repository.instanceOf(classname, CONTINUATION_CLASS); return true; } private InstructionList saveFrame(MethodGen method, InstructionHandle handle, int pc, InstructionFactory insFactory, Frame frame) { final InstructionList insList = new InstructionList(); // Remove needless return type from stack final InvokeInstruction inv = (InvokeInstruction) handle.getInstruction(); final Type returnType = getReturnType(method.getConstantPool().getConstantPool(), inv.getIndex()); if (returnType.getSize() > 0) { insList.insert(InstructionFactory.createPop(returnType.getSize())); } boolean skipFirst = returnType.getSize() > 0; //if (debug) { // insList.append(insFactory.createPrintln("save stack")); //} // instruction for loading StackRecorder Instruction loadStackRecorder = InstructionFactory.createLoad(STACK_RECORDER_TYPE, method.getMaxLocals()); // save stack final OperandStack os = frame.getStack(); for (int i = skipFirst ? 1 : 0; i < os.size(); i++) { Type type = os.peek(i); if (type instanceof BasicType) { if (type.getSize() < 2 && !type.equals(Type.FLOAT)) { type = Type.INT; } // check for types with two words on stack if (type.equals(Type.LONG) || type.equals(Type.DOUBLE)) { insList.append(InstructionConstants.ACONST_NULL); // create dummy stack entry insList.append(loadStackRecorder); insList.append(InstructionConstants.DUP2_X2); // swap Stack object and long/float insList.append(InstructionConstants.POP2); } else { insList.append(loadStackRecorder); insList.append(InstructionConstants.SWAP); } insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, getPushMethod(type), Type.VOID, new Type[] { type }, Constants.INVOKEVIRTUAL)); if (type.equals(Type.LONG) || type.equals(Type.DOUBLE)) insList.append(InstructionConstants.POP); // remove dummy stack entry } else if (type == null) { insList.append(InstructionConstants.POP); } else if (type instanceof UninitializedObjectType) { // After the remove of new, there shouldn't be a // uninitialized object on the stack } else if (type instanceof ReferenceType) { if (type.equals(Type.NULL)) { // if it's guaranteed to be null, no need to store. // in fact, we can't really store this, because we'll never // be able to restore it as the 'null' type. insList.append(InstructionConstants.POP); } else { insList.append(loadStackRecorder); insList.append(InstructionConstants.SWAP); insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, getPushMethod(Type.OBJECT), Type.VOID, new Type[] { Type.OBJECT }, Constants.INVOKEVIRTUAL)); } } } // add isCapturing test if (debug) { insList.insert(insFactory.createPrintln("capturing invocation " + method)); } insList.insert(new IFEQ(handle.getNext())); // test if the continuation should be captured after the invocation insList.insert(insFactory.createFieldAccess(STACK_RECORDER_CLASS, CAPTURING_FIELD, Type.BOOLEAN, Constants.GETFIELD)); insList.insert(loadStackRecorder); // test if continuation exists insList.insert(new IFNULL(handle.getNext())); insList.insert(loadStackRecorder); // save local variables //if (debug) { // insList.append(insFactory.createPrintln("save local variables")); //} final LocalVariables lvs = frame.getLocals(); for (int i = 0; i < lvs.maxLocals(); i++) { Type type = lvs.get(i); if (type instanceof BasicType) { insList.append(loadStackRecorder); insList.append(InstructionFactory.createLoad(type, i)); if (type.getSize() < 2 && !type.equals(Type.FLOAT)) type = Type.INT; insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, getPushMethod(type), Type.VOID, new Type[] { type }, Constants.INVOKEVIRTUAL)); } else if (type == null || type == Type.NULL) { // no need to save null } else if (type instanceof UninitializedObjectType) { // no need to save uninitialized objects } else if (type instanceof ReferenceType) { if (i == 0 && !currentMethodStatic) { // remember current object insList.append(loadStackRecorder); insList.append(InstructionFactory.createLoad(type, i)); insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, PUSH_METHOD + "Reference", Type.VOID, new Type[] { Type.OBJECT }, Constants.INVOKEVIRTUAL)); } insList.append(loadStackRecorder); insList.append(InstructionFactory.createLoad(type, i)); insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, getPushMethod(Type.OBJECT), Type.VOID, new Type[] { Type.OBJECT }, Constants.INVOKEVIRTUAL)); } } // save programcounter insList.append(loadStackRecorder); insList.append(new PUSH(method.getConstantPool(), pc)); insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, getPushMethod(Type.INT), Type.VOID, new Type[] { Type.INT }, Constants.INVOKEVIRTUAL)); // return NULL result insList.append(InstructionFactory.createNull(method.getReturnType())); insList.append(InstructionFactory.createReturn(method.getReturnType())); return insList; } private InstructionList restoreFrame(MethodGen method, InstructionHandle handle, InstructionFactory insFactory, Frame frame, ReferenceType objecttype) { final InstructionList insList = new InstructionList(); // restore local variables //if (debug) { // insList.append(insFactory.createPrintln("restore local variables")); //} final LocalVariables lvs = frame.getLocals(); Instruction loadStackRecorder = InstructionFactory.createLoad(STACK_RECORDER_TYPE, method.getMaxLocals()); for (int i = lvs.maxLocals() - 1; i >= 0; i--) { Type type = lvs.get(i); if (type instanceof BasicType) { insList.append(loadStackRecorder); if (type.getSize() < 2 && !type.equals(Type.FLOAT)) { type = Type.INT; } insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, getPopMethod(type), type, Type.NO_ARGS, Constants.INVOKEVIRTUAL)); insList.append(InstructionFactory.createStore(type, i)); } else if (type == null) { insList.append(InstructionConstants.ACONST_NULL); insList.append(new ASTORE(i)); } else if (type instanceof UninitializedObjectType) { // No uninitilaized objects should be found // in the local variables. throw new Error("assertion failure"); } else if (type instanceof ReferenceType) { if (type == Type.NULL) { // null type is a special type assignable to any type, // whereas popObject returns a java/lang/Object. // the saveFrame method is written so that we don't save the 'null' object insList.append(InstructionConstants.ACONST_NULL); } else { insList.append(loadStackRecorder); insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, getPopMethod(Type.OBJECT), Type.OBJECT, Type.NO_ARGS, Constants.INVOKEVIRTUAL)); if (!type.equals(Type.OBJECT)) { insList.append(insFactory.createCast(Type.OBJECT, type)); } } insList.append(new ASTORE(i)); } } final InvokeInstruction inv = (InvokeInstruction) handle.getInstruction(); final Type returnType = getReturnType(method.getConstantPool().getConstantPool(), inv.getIndex()); boolean skipFirst = returnType.getSize() > 0; // restore stack //if (debug) { // insList.append(insFactory.createPrintln("restore stack")); //} final OperandStack os = frame.getStack(); for (int i = os.size() - 1; i >= (skipFirst ? 1 : 0); i--) { Type type = os.peek(i); if (type instanceof BasicType) { if (type.getSize() < 2 && !type.equals(Type.FLOAT)) { type = Type.INT; } insList.append(loadStackRecorder); insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, getPopMethod(type), type, Type.NO_ARGS, Constants.INVOKEVIRTUAL)); } else if (type == null || type == Type.NULL) { insList.append(new ACONST_NULL()); } else if (type instanceof UninitializedObjectType) { // After the remove of new, there shouldn't be a // uninitialized object on the stack } else if (type instanceof ReferenceType) { insList.append(loadStackRecorder); insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, getPopMethod(Type.OBJECT), Type.OBJECT, Type.NO_ARGS, Constants.INVOKEVIRTUAL)); if (!type.equals(Type.OBJECT)) insList.append(insFactory.createCast(Type.OBJECT, type)); } } // retrieve current object if (!(inv instanceof INVOKESTATIC)) { insList.append(loadStackRecorder); insList.append(insFactory.createInvoke(STACK_RECORDER_CLASS, POP_METHOD + "Reference", Type.OBJECT, Type.NO_ARGS, Constants.INVOKEVIRTUAL)); insList.append(insFactory.createCast(Type.OBJECT, objecttype)); } // Create null types for the parameters of the method invocation final Type[] paramTypes = getParamTypes(method.getConstantPool().getConstantPool(), inv.getIndex()); for (int j = 0; j < paramTypes.length; j++) { insList.append(InstructionFactory.createNull(paramTypes[j])); } // go to last invocation insList.append(new GOTO(handle)); return insList; } private Type[] getParamTypes(ConstantPool cp, int index) { final ConstantCP cmr = (ConstantCP) cp.getConstant(index); final ConstantNameAndType cnat = (ConstantNameAndType) cp.getConstant(cmr.getNameAndTypeIndex()); final String sig = ((ConstantUtf8) cp.getConstant(cnat.getSignatureIndex())).getBytes(); return Type.getArgumentTypes(sig); } private Type getReturnType(ConstantPool cp, int index) { final ConstantCP cmr = (ConstantCP) cp.getConstant(index); final ConstantNameAndType cnat = (ConstantNameAndType) cp.getConstant(cmr.getNameAndTypeIndex()); final String sig = ((ConstantUtf8) cp.getConstant(cnat.getSignatureIndex())).getBytes(); return Type.getReturnType(sig); } private String getPopMethod(Type type) { return POP_METHOD + getTypeSuffix(type); } private String getPushMethod(Type type) { return PUSH_METHOD + getTypeSuffix(type); } private String getTypeSuffix(Type type) { if (type.equals(Type.BOOLEAN)) return "Int"; else if (type.equals(Type.CHAR)) return "Int"; else if (type.equals(Type.FLOAT)) return "Float"; else if (type.equals(Type.DOUBLE)) return "Double"; else if (type.equals(Type.BYTE)) return "Int"; else if (type.equals(Type.SHORT)) return "Int"; else if (type.equals(Type.INT)) return "Int"; else if (type.equals(Type.LONG)) return "Long"; // VOID and OBJECT are "Object" return "Object"; } }