org.evosuite.testcarver.instrument.Instrumenter.java Source code

Java tutorial

Introduction

Here is the source code for org.evosuite.testcarver.instrument.Instrumenter.java

Source

/**
 * Copyright (C) 2010-2016 Gordon Fraser, Andrea Arcuri and EvoSuite
 * contributors
 *
 * This file is part of EvoSuite.
 *
 * EvoSuite is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3.0 of the License, or
 * (at your option) any later version.
 *
 * EvoSuite is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with EvoSuite. If not, see <http://www.gnu.org/licenses/>.
 */
package org.evosuite.testcarver.instrument;

import java.io.PrintWriter;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

import org.evosuite.PackageInfo;
import org.evosuite.Properties;
import org.evosuite.testcarver.capture.CaptureLog;
import org.evosuite.testcarver.capture.CaptureUtil;
import org.evosuite.testcarver.capture.FieldRegistry;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.FrameNode;
import org.objectweb.asm.tree.InnerClassNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TryCatchBlockNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;
import org.objectweb.asm.util.TraceClassVisitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class Instrumenter {
    private int captureId;

    public static final int CAPTURE_ID_JAVA_UTIL_DATE = Integer.MIN_VALUE;
    public static final int CAPTURE_ID_JAVA_UTIL_CALENDAR = Integer.MIN_VALUE + 1;
    public static final int CAPTURE_ID_JAVA_TEXT_DATEFORMAT = Integer.MIN_VALUE + 2;
    public static final int CAPTURE_ID_JAVA_TEXT_SIMPLEDATEFORMAT = Integer.MIN_VALUE + 3;

    public static final String WRAP_NAME_PREFIX = "_sw_prototype_original_";

    private static final Logger logger = LoggerFactory.getLogger(Instrumenter.class);

    public Instrumenter() {
        this.captureId = CAPTURE_ID_JAVA_UTIL_DATE + 4;
    }

    public void instrument(final String className, final ClassNode cn) {
        if (!TransformerUtil.isClassConsideredForInstrumentation(className)) {
            logger.debug("Class {} has not been instrumented because its name is on the blacklist", className);
            return;
        }

        try {
            this.transformClassNode(cn, className);
        } catch (final Throwable t) {
            logger.error("An error occurred while instrumenting class {} -> returning unmodified version",
                    className, t);
        }

    }

    public byte[] instrument(final String className, final byte[] classfileBuffer)
            throws IllegalClassFormatException {
        logger.debug("Start instrumenting class {}", className);

        final ClassReader cr = new ClassReader(classfileBuffer);
        final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        final ClassNode cn = new ClassNode();
        cr.accept(cn, ClassReader.SKIP_DEBUG);

        if (!TransformerUtil.isClassConsideredForInstrumentation(className)) {
            logger.debug("Class {} has not been instrumented because its name is on the blacklist", className);
            return classfileBuffer;
        }

        try {
            this.transformClassNode(cn, className);

            cn.accept(cw);

            return cw.toByteArray();

        } catch (final Throwable t) {
            logger.error("An error occurred while instrumenting class {} -> returning unmodified version",
                    className, t);
            return classfileBuffer;
        }
    }

    @SuppressWarnings("unchecked")
    private void addFieldRegistryRegisterCall(final MethodNode methodNode) {
        AbstractInsnNode ins = null;
        ListIterator<AbstractInsnNode> iter = methodNode.instructions.iterator();

        int numInvokeSpecials = 0; // number of invokespecial calls before actual constructor call

        while (iter.hasNext()) {
            ins = iter.next();

            if (ins instanceof MethodInsnNode) {
                MethodInsnNode mins = (MethodInsnNode) ins;
                if (ins.getOpcode() == Opcodes.INVOKESPECIAL) {
                    if (mins.name.startsWith("<init>")) {
                        if (numInvokeSpecials == 0) {
                            break;
                        } else {
                            numInvokeSpecials--;
                        }
                    }
                }
            } else if (ins instanceof TypeInsnNode) {
                TypeInsnNode typeIns = (TypeInsnNode) ins;
                if (typeIns.getOpcode() == Opcodes.NEW || typeIns.getOpcode() == Opcodes.NEWARRAY) {
                    numInvokeSpecials++;
                }
            }
        }

        final InsnList instructions = new InsnList();

        instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
        instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, PackageInfo.getNameWithSlash(FieldRegistry.class),
                "register", "(Ljava/lang/Object;)V"));

        methodNode.instructions.insert(ins, instructions);
    }

    @SuppressWarnings("unchecked")
    public void transformClassNode(ClassNode cn, final String internalClassName) {
        if (!TransformerUtil.isClassConsideredForInstrumentation(internalClassName)) {
            logger.debug("Class {} has not been instrumented because its name is on the blacklist",
                    internalClassName);
            return;
        }

        // consider only public and protected classes which are not interfaces
        if ((cn.access & Opcodes.ACC_INTERFACE) != 0) {
            return;
        }

        // No private
        if ((cn.access & Opcodes.ACC_PRIVATE) != 0) {
            // TODO: Why is this not detecting $DummyIntegrator?
            logger.debug("Ignoring private class {}", cn.name);
            return;
        }

        String packageName = internalClassName.replace('/', '.');
        if (packageName.contains("."))
            packageName = packageName.substring(0, packageName.lastIndexOf('.'));

        // ASM has some problem with the access of inner classes
        // so we check if the inner class name is the current class name
        // and if so, check if the inner class is actually accessible
        List<InnerClassNode> in = cn.innerClasses;
        for (InnerClassNode inc : in) {
            if (cn.name.equals(inc.name)) {
                logger.info("ASM Bug: Inner class equals class.");
                if ((inc.access & Opcodes.ACC_PROTECTED) == Opcodes.ACC_PROTECTED) {
                    if (!Properties.CLASS_PREFIX.equals(packageName)) {
                        return;
                    }
                }
                if ((inc.access & Opcodes.ACC_PRIVATE) == Opcodes.ACC_PRIVATE) {
                    return;
                }
                logger.debug("Can use inner class {}", inc.name);
            }
        }
        logger.info("Checking package {} for class {}", packageName, cn.name);

        // Protected/default only if in same package
        if ((cn.access & Opcodes.ACC_PUBLIC) == 0) {
            if (!Properties.CLASS_PREFIX.equals(packageName)) {
                logger.info("Not using protected/default class because package name does not match");
                return;
            } else {
                logger.info("Using protected/default class because package name matches");
            }
        }
        /*
        if(   (cn.access & Opcodes.ACC_PUBLIC) == 0 && (cn.access & Opcodes.ACC_PROTECTED) == 0)
        {
           return;
        }
        */

        final ArrayList<MethodNode> wrappedMethods = new ArrayList<MethodNode>();
        MethodNode methodNode;

        final Iterator<MethodNode> methodIter = cn.methods.iterator();
        while (methodIter.hasNext()) {
            methodNode = methodIter.next();

            // consider only public methods which are not abstract or native
            if (!TransformerUtil.isPrivate(methodNode.access) && !TransformerUtil.isAbstract(methodNode.access)
                    && !TransformerUtil.isNative(methodNode.access) && !methodNode.name.equals("<clinit>")) {
                if (!TransformerUtil.isPublic(methodNode.access)) {
                    //if(!Properties.CLASS_PREFIX.equals(packageName)) {
                    transformWrapperCalls(methodNode);
                    continue;
                    //}
                }
                if (methodNode.name.equals("<init>")) {
                    if (TransformerUtil.isAbstract(cn.access)) {
                        // We cannot invoke constructors of abstract classes directly
                        continue;
                    }
                    this.addFieldRegistryRegisterCall(methodNode);
                }

                this.instrumentPUTXXXFieldAccesses(cn, internalClassName, methodNode);
                this.instrumentGETXXXFieldAccesses(cn, internalClassName, methodNode);

                this.instrumentMethod(cn, internalClassName, methodNode, wrappedMethods);
            } else {
                transformWrapperCalls(methodNode);
            }
        }

        final int numWM = wrappedMethods.size();
        for (int i = 0; i < numWM; i++) {
            cn.methods.add(wrappedMethods.get(i));
        }

        TraceClassVisitor tcv = new TraceClassVisitor(new PrintWriter(System.err));
        cn.accept(tcv);
    }

    private void instrumentGETXXXFieldAccesses(final ClassNode cn, final String internalClassName,
            final MethodNode methodNode) {
        final InsnList instructions = methodNode.instructions;

        AbstractInsnNode ins = null;
        FieldInsnNode fieldIns = null;

        for (int i = 0; i < instructions.size(); i++) {
            ins = instructions.get(i);
            if (ins instanceof FieldInsnNode) {
                fieldIns = (FieldInsnNode) ins;

                /*
                 * Is field referencing outermost instance? if yes, ignore it
                 * http://tns-www.lcs.mit.edu/manuals/java-1.1.1/guide/innerclasses/spec/innerclasses.doc10.html
                 */
                if (fieldIns.name.endsWith("$0")) {
                    continue;
                }

                final int opcode = ins.getOpcode();
                if (opcode == Opcodes.GETFIELD || opcode == Opcodes.GETSTATIC) {
                    final InsnList il = new InsnList();

                    if (opcode == Opcodes.GETFIELD) {
                        Type fieldType = Type.getType(fieldIns.desc);
                        if (fieldType.getSize() == 1) {
                            instructions.insertBefore(fieldIns, new InsnNode(Opcodes.DUP));
                            il.add(new InsnNode(Opcodes.SWAP));
                        } else if (fieldType.getSize() == 2) {
                            instructions.insertBefore(fieldIns, new InsnNode(Opcodes.DUP));
                            // v
                            // GETFIELD
                            // v, w
                            il.add(new InsnNode(Opcodes.DUP2_X1));
                            // w, v, w
                            il.add(new InsnNode(Opcodes.POP2));
                            // w, v
                            // -> Call
                            // w
                        }
                    } else
                        il.add(new InsnNode(Opcodes.ACONST_NULL));

                    il.add(new LdcInsnNode(this.captureId));
                    il.add(new LdcInsnNode(fieldIns.owner));
                    il.add(new LdcInsnNode(fieldIns.name));
                    il.add(new LdcInsnNode(fieldIns.desc));

                    il.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
                            PackageInfo.getNameWithSlash(org.evosuite.testcarver.capture.FieldRegistry.class),
                            "notifyReadAccess",
                            "(Ljava/lang/Object;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"));

                    i += il.size();

                    instructions.insert(fieldIns, il);
                    this.captureId++;
                }
            }
        }
    }

    private void instrumentPUTXXXFieldAccesses(final ClassNode cn, final String internalClassName,
            final MethodNode methodNode) {
        final InsnList instructions = methodNode.instructions;

        AbstractInsnNode ins = null;
        FieldInsnNode fieldIns = null;

        // needed get right receiver var in case of PUTFIELD

        for (int i = 0; i < instructions.size(); i++) {
            ins = instructions.get(i);
            if (ins instanceof FieldInsnNode) {
                fieldIns = (FieldInsnNode) ins;

                /*
                 * Is field referencing outermost instance? if yes, ignore it
                 * http://tns-www.lcs.mit.edu/manuals/java-1.1.1/guide/innerclasses/spec/innerclasses.doc10.html
                 */
                if (fieldIns.name.endsWith("$0")) {
                    continue;
                }

                final int opcode = ins.getOpcode();
                if (opcode == Opcodes.PUTFIELD || opcode == Opcodes.PUTSTATIC) {
                    // construction of  
                    //   Capturer.capture(final Object receiver, final String methodName, final Object[] methodParams)
                    // call
                    final InsnList il = new InsnList();

                    if (opcode == Opcodes.PUTFIELD) {
                        Type fieldType = Type.getType(fieldIns.desc);
                        if (fieldType.getSize() == 1) {
                            instructions.insertBefore(fieldIns, new InsnNode(Opcodes.DUP2));
                            il.add(new InsnNode(Opcodes.POP));
                        } else if (fieldType.getSize() == 2) {
                            InsnList uglyList = new InsnList();
                            // v, w
                            uglyList.add(new InsnNode(Opcodes.DUP2_X1));
                            // w, v, w
                            uglyList.add(new InsnNode(Opcodes.POP2));
                            // w, v
                            uglyList.add(new InsnNode(Opcodes.DUP));
                            // w, v, v
                            uglyList.add(new InsnNode(Opcodes.DUP2_X2));
                            // v, v, w, v, v
                            uglyList.add(new InsnNode(Opcodes.POP2));
                            // v, v, w
                            instructions.insertBefore(fieldIns, uglyList);
                            // PUTFIELD
                            // v
                        }
                    } else
                        il.add(new InsnNode(Opcodes.ACONST_NULL));

                    il.add(new LdcInsnNode(this.captureId));
                    il.add(new LdcInsnNode(fieldIns.owner));
                    il.add(new LdcInsnNode(fieldIns.name));
                    il.add(new LdcInsnNode(fieldIns.desc));

                    il.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
                            PackageInfo.getNameWithSlash(FieldRegistry.class), "notifyModification",
                            "(Ljava/lang/Object;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"));

                    // PUTFIELDRegistry.notifyModification also adds corresponding GETFIELD capture instructions
                    this.captureId++;
                    i += il.size();

                    instructions.insert(fieldIns, il);
                    this.captureId++;
                }
            }
        }
    }

    private void instrumentMethod(final ClassNode cn, final String internalClassName, final MethodNode methodNode,
            final List<MethodNode> wrappedMethods) {
        wrappedMethods.add(this.wrapMethod(cn, internalClassName, methodNode));
        this.captureId++;
    }

    private InsnList addCaptureCall(final boolean isStatic, final String internalClassName, final String methodName,
            final String methodDesc, final Type[] argTypes) {
        // construction of  
        //   Capturer.capture(final Object receiver, final String methodName, final Object[] methodParams)
        // call
        final InsnList il = new InsnList();

        il.add(new LdcInsnNode(this.captureId));

        // --- load receiver argument
        int varIndex;
        if (isStatic) {
            // static method invocation
            il.add(new LdcInsnNode(internalClassName));
            il.add(new MethodInsnNode(Opcodes.INVOKESTATIC, PackageInfo.getNameWithSlash(CaptureUtil.class),
                    "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"));

            varIndex = 0;
        } else {
            // non-static method call
            il.add(new VarInsnNode(Opcodes.ALOAD, 0));
            varIndex = 1;
        }

        // --- load method name argument

        il.add(new LdcInsnNode(methodName));

        // --- load method description argument

        il.add(new LdcInsnNode(methodDesc));

        // --- load methodParams arguments

        // load methodParams length
        // TODO ICONST_1 to ICONST_5 would be more efficient
        il.add(new IntInsnNode(Opcodes.BIPUSH, argTypes.length));

        // create array object
        il.add(new TypeInsnNode(Opcodes.ANEWARRAY, "java/lang/Object"));

        // fill the array

        for (int i = 0; i < argTypes.length; i++) {
            il.add(new InsnNode(Opcodes.DUP));

            // TODO ICONST_1 to ICONST_5 would be more efficient
            il.add(new IntInsnNode(Opcodes.BIPUSH, i));

            //check for primitives
            this.loadAndConvertToObject(il, argTypes[i], varIndex++);
            il.add(new InsnNode(Opcodes.AASTORE));

            // long/double take two registers
            if (argTypes[i].equals(Type.LONG_TYPE) || argTypes[i].equals(Type.DOUBLE_TYPE)) {
                varIndex++;
            }
        }

        // --- construct Capture.capture() call

        il.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
                PackageInfo.getNameWithSlash(org.evosuite.testcarver.capture.Capturer.class), "capture",
                "(ILjava/lang/Object;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V"));

        return il;
    }

    private void addCaptureEnableStatement(final String className, final MethodNode mn, final InsnList il,
            final int returnValueVar) {
        il.add(new LdcInsnNode(this.captureId));

        if (TransformerUtil.isStatic(mn.access)) {
            // static method

            il.add(new LdcInsnNode(className));
            il.add(new MethodInsnNode(Opcodes.INVOKESTATIC, PackageInfo.getNameWithSlash(CaptureUtil.class),
                    "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"));
        } else {
            // non-static method

            il.add(new VarInsnNode(Opcodes.ALOAD, 0));
        }

        final Type returnType = Type.getReturnType(mn.desc);
        if (returnType.equals(Type.VOID_TYPE)) {
            // load return value for VOID methods
            il.add(new FieldInsnNode(Opcodes.GETSTATIC, PackageInfo.getNameWithSlash(CaptureLog.class),
                    "RETURN_TYPE_VOID", Type.getDescriptor(Object.class)));
        } else {
            // load return value as object
            il.add(new VarInsnNode(Opcodes.ALOAD, returnValueVar));
        }

        il.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
                PackageInfo.getNameWithSlash(org.evosuite.testcarver.capture.Capturer.class), "enable",
                "(ILjava/lang/Object;Ljava/lang/Object;)V"));
    }

    /**
     *    public int myMethod(int i)
       {
     try
     {
        return _sw_prototype_original_myMethod(i)
     }
     finally
     {
        Capturer.enable();
     }
       }
        
     * @param classNode
     * @param className
     * @param methodNode
     */
    @SuppressWarnings("unchecked")
    private MethodNode wrapMethod(final ClassNode classNode, final String className, final MethodNode methodNode) {
        methodNode.maxStack += 4;

        // create wrapper for original method
        final MethodNode wrappingMethodNode = new MethodNode(methodNode.access, methodNode.name, methodNode.desc,
                methodNode.signature,
                (String[]) methodNode.exceptions.toArray(new String[methodNode.exceptions.size()]));
        wrappingMethodNode.maxStack = methodNode.maxStack;

        // assign annotations to wrapping method
        wrappingMethodNode.visibleAnnotations = methodNode.visibleAnnotations;
        wrappingMethodNode.visibleParameterAnnotations = methodNode.visibleParameterAnnotations;

        // remove annotations from wrapped method to avoid wrong behavior controlled by annotations
        methodNode.visibleAnnotations = null;
        methodNode.visibleParameterAnnotations = null;

        // rename original method
        methodNode.access = TransformerUtil.modifyVisibility(methodNode.access, Opcodes.ACC_PRIVATE);

        final LabelNode l0 = new LabelNode();
        final LabelNode l1 = new LabelNode();
        final LabelNode l2 = new LabelNode();

        final InsnList wInstructions = wrappingMethodNode.instructions;

        if ("<init>".equals(methodNode.name)) {
            // wrap a constructor 

            methodNode.name = WRAP_NAME_PREFIX + "init" + WRAP_NAME_PREFIX;

            // move call to other constructors to new method
            AbstractInsnNode ins = null;
            ListIterator<AbstractInsnNode> iter = methodNode.instructions.iterator();

            int numInvokeSpecials = 0; // number of invokespecial calls before actual constructor call

            while (iter.hasNext()) {
                ins = iter.next();
                iter.remove();
                wInstructions.add(ins);

                if (ins instanceof MethodInsnNode) {
                    MethodInsnNode mins = (MethodInsnNode) ins;
                    if (ins.getOpcode() == Opcodes.INVOKESPECIAL) {
                        if (mins.name.startsWith("<init>")) {
                            if (numInvokeSpecials == 0) {
                                break;
                            } else {
                                numInvokeSpecials--;
                            }
                        }
                    }
                } else if (ins instanceof TypeInsnNode) {
                    TypeInsnNode typeIns = (TypeInsnNode) ins;
                    if (typeIns.getOpcode() == Opcodes.NEW || typeIns.getOpcode() == Opcodes.NEWARRAY) {
                        numInvokeSpecials++;
                    }
                }
            }
        } else {
            methodNode.name = WRAP_NAME_PREFIX + methodNode.name;
        }

        int varReturnValue = 0;

        final Type returnType = Type.getReturnType(methodNode.desc);

        if (returnType.equals(Type.VOID_TYPE)) {
            wrappingMethodNode.tryCatchBlocks.add(new TryCatchBlockNode(l0, l1, l1, "java/lang/Throwable"));

        } else {

            wrappingMethodNode.tryCatchBlocks.add(new TryCatchBlockNode(l0, l1, l2, "java/lang/Throwable"));

            //--- create "Object returnValue = null;"

            if (!TransformerUtil.isStatic(methodNode.access)) {
                // load "this"
                varReturnValue++;
            }

            // consider method arguments to find right variable index
            final Type[] argTypes = Type.getArgumentTypes(methodNode.desc);
            for (int i = 0; i < argTypes.length; i++) {
                varReturnValue++;

                // long/double take two registers
                if (argTypes[i].equals(Type.LONG_TYPE) || argTypes[i].equals(Type.DOUBLE_TYPE)) {
                    varReturnValue++;
                }
            }

            // push NULL on the stack and initialize variable for return value for it
            wInstructions.add(new InsnNode(Opcodes.ACONST_NULL));
            wInstructions.add(new VarInsnNode(Opcodes.ASTORE, varReturnValue));
        }

        int var = 0;

        // --- L0
        wInstructions.add(l0);

        wInstructions.add(this.addCaptureCall(TransformerUtil.isStatic(methodNode.access), className,
                wrappingMethodNode.name, wrappingMethodNode.desc, Type.getArgumentTypes(methodNode.desc)));

        // --- construct call to wrapped methode

        if (!TransformerUtil.isStatic(methodNode.access)) {
            // load "this" to call method
            wInstructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
            var++;
        }

        final Type[] argTypes = Type.getArgumentTypes(methodNode.desc);
        for (int i = 0; i < argTypes.length; i++) {
            this.addLoadInsn(wInstructions, argTypes[i], var++);

            // long/double take two registers
            if (argTypes[i].equals(Type.LONG_TYPE) || argTypes[i].equals(Type.DOUBLE_TYPE)) {
                var++;
            }
        }

        if (TransformerUtil.isStatic(methodNode.access)) {
            wInstructions.add(
                    new MethodInsnNode(Opcodes.INVOKESTATIC, classNode.name, methodNode.name, methodNode.desc));
        } else {
            wInstructions.add(
                    new MethodInsnNode(Opcodes.INVOKEVIRTUAL, classNode.name, methodNode.name, methodNode.desc));
        }

        var++;

        if (returnType.equals(Type.VOID_TYPE)) {
            wInstructions.add(new JumpInsnNode(Opcodes.GOTO, l2));

            // --- L1

            wInstructions.add(l1);

            wInstructions.add(new FrameNode(Opcodes.F_SAME1, 0, null, 1, new Object[] { "java/lang/Throwable" }));

            wInstructions.add(new VarInsnNode(Opcodes.ASTORE, --var));

            this.addCaptureEnableStatement(className, methodNode, wInstructions, -1);

            wInstructions.add(new VarInsnNode(Opcodes.ALOAD, var));
            wInstructions.add(new InsnNode(Opcodes.ATHROW));

            // FIXME <--- DUPLICATE CODE

            // --- L2

            wInstructions.add(l2);
            wInstructions.add(new FrameNode(Opcodes.F_SAME, 0, null, 0, null));

            this.addCaptureEnableStatement(className, methodNode, wInstructions, -1);

            wInstructions.add(new InsnNode(Opcodes.RETURN));
        } else {
            // construct store of the wrapped method call's result

            this.addBoxingStmt(wInstructions, returnType);

            wInstructions.add(new VarInsnNode(Opcodes.ASTORE, varReturnValue));
            wInstructions.add(new VarInsnNode(Opcodes.ALOAD, varReturnValue));

            this.addUnBoxingStmt(wInstructions, returnType);

            final int storeOpcode = returnType.getOpcode(Opcodes.ISTORE);
            wInstructions.add(new VarInsnNode(storeOpcode, ++var)); // might be only var

            // --- L1

            wInstructions.add(l1);

            this.addCaptureEnableStatement(className, methodNode, wInstructions, varReturnValue);

            // construct load of the wrapped method call's result
            int loadOpcode = returnType.getOpcode(Opcodes.ILOAD);
            wInstructions.add(new VarInsnNode(loadOpcode, var));

            // construct return of the wrapped method call's result
            this.addReturnInsn(wInstructions, returnType);

            //---- L2

            wInstructions.add(l2);

            wInstructions.add(
                    new FrameNode(Opcodes.F_FULL, 2, new Object[] { className, this.getInternalName(returnType) },
                            1, new Object[] { "java/lang/Throwable" }));
            wInstructions.add(new VarInsnNode(Opcodes.ASTORE, --var));

            this.addCaptureEnableStatement(className, methodNode, wInstructions, varReturnValue);

            wInstructions.add(new VarInsnNode(Opcodes.ALOAD, var));
            wInstructions.add(new InsnNode(Opcodes.ATHROW));
        }
        transformWrapperCalls(methodNode);
        return wrappingMethodNode;
    }

    @SuppressWarnings("unchecked")
    private void transformWrapperCalls(MethodNode mn) {
        Iterator<AbstractInsnNode> iterator = mn.instructions.iterator();
        List<Class<?>> wrapperClasses = getWrapperClasses();

        while (iterator.hasNext()) {
            AbstractInsnNode insn = iterator.next();
            if (insn instanceof MethodInsnNode) {
                MethodInsnNode methodInsnNode = (MethodInsnNode) insn;
                if (methodInsnNode.name.equals("<init>")) {
                    String ownerName = methodInsnNode.owner.replace('/', '.');
                    for (Class<?> wrapperClass : wrapperClasses) {
                        if (wrapperClass.getName().equals(ownerName)) {
                            logger.debug("Replacing call " + methodInsnNode.name);
                            methodInsnNode.owner = "org/evosuite/testcarver/wrapper/" + methodInsnNode.owner;
                            break;
                        }
                    }
                } else {
                    String ownerName = methodInsnNode.owner.replace('/', '.');
                    for (Class<?> wrapperClass : wrapperClasses) {
                        if (wrapperClass.getName().equals(ownerName)) {
                            if (methodInsnNode.getOpcode() == Opcodes.INVOKESTATIC) {
                                logger.debug("Replacing call " + methodInsnNode.name);
                                methodInsnNode.owner = PackageInfo.getEvoSuitePackageWithSlash()
                                        + "/testcarver/wrapper/" + methodInsnNode.owner;
                            }
                            Type[] parameterTypes = Type.getArgumentTypes(methodInsnNode.desc);
                            try {
                                Class<?>[] parameterClasses = new Class<?>[parameterTypes.length];
                                int pos = 0;
                                for (Type parameter : parameterTypes) {
                                    switch (parameter.getSort()) {
                                    case Type.OBJECT:
                                        parameterClasses[pos++] = Class.forName(parameter.getClassName());
                                        break;
                                    case Type.BOOLEAN:
                                        parameterClasses[pos++] = boolean.class;
                                        break;
                                    case Type.BYTE:
                                        parameterClasses[pos++] = byte.class;
                                        break;
                                    case Type.CHAR:
                                        parameterClasses[pos++] = char.class;
                                        break;
                                    case Type.DOUBLE:
                                        parameterClasses[pos++] = double.class;
                                        break;
                                    case Type.FLOAT:
                                        parameterClasses[pos++] = float.class;
                                        break;
                                    case Type.INT:
                                        parameterClasses[pos++] = int.class;
                                        break;
                                    case Type.LONG:
                                        parameterClasses[pos++] = long.class;
                                        break;
                                    case Type.SHORT:
                                        parameterClasses[pos++] = short.class;
                                        break;
                                    }
                                }
                                Method method = wrapperClass.getMethod(methodInsnNode.name, parameterClasses);
                                if (Modifier.isFinal(method.getModifiers())) {
                                    if (methodInsnNode.getOpcode() != Opcodes.INVOKESTATIC) {
                                        methodInsnNode.setOpcode(Opcodes.INVOKESTATIC);
                                        Type[] args = Type.getArgumentTypes(methodInsnNode.desc);
                                        Type returnType = Type.getReturnType(methodInsnNode.desc);
                                        Type[] newargs = new Type[args.length + 1];
                                        newargs[0] = Type.getObjectType(methodInsnNode.owner);
                                        for (int i = 0; i < args.length; i++)
                                            newargs[i + 1] = args[i];
                                        methodInsnNode.desc = Type.getMethodDescriptor(returnType, newargs);
                                        methodInsnNode.owner = PackageInfo.getEvoSuitePackageWithSlash()
                                                + "/testcarver/wrapper/" + methodInsnNode.owner;
                                    } else {
                                        methodInsnNode.name += "_final";
                                    }
                                    logger.debug(
                                            "Method is final: " + methodInsnNode.owner + "." + methodInsnNode.name);
                                } else {
                                    logger.debug("Method is not final: " + methodInsnNode.owner + "."
                                            + methodInsnNode.name);
                                }
                            } catch (Exception e) {
                                logger.warn("Error while instrumenting: " + e);
                            }

                            break;
                        }
                    }
                    //            } else if(methodInsnNode.name.equals("getTime")) {
                    //               if(methodInsnNode.owner.equals("java/util/Calendar")) {
                    //                  logger.debug("Replacing call "+methodInsnNode.name);
                    //                  methodInsnNode.owner = "org/evosuite/testcarver/wrapper/java/util/Calendar";
                    //                  methodInsnNode.name = "getTime";
                    //                  methodInsnNode.desc = "(Ljava/util/Calendar;)Ljava/util/Date;";
                    //                  methodInsnNode.setOpcode(Opcodes.INVOKESTATIC);
                    //               }
                    //            }
                }
            } else if (insn.getOpcode() == Opcodes.NEW || insn.getOpcode() == Opcodes.CHECKCAST) {
                TypeInsnNode typeInsnNode = (TypeInsnNode) insn;
                Type generatedType = Type.getType(typeInsnNode.desc);
                String name = generatedType.getInternalName().replace('/', '.');
                logger.debug("Checking for replacement of " + name);
                for (Class<?> wrapperClass : wrapperClasses) {
                    if (wrapperClass.getName().equals(name)) {
                        logger.debug("Replacing new " + name);
                        typeInsnNode.desc = PackageInfo.getEvoSuitePackageWithSlash() + "/testcarver/wrapper/"
                                + generatedType.getInternalName();
                        break;
                    }
                }

            }
        }
    }

    private void addReturnInsn(final InsnList il, final Type type) {
        if (type.equals(Type.BOOLEAN_TYPE)) {
            il.add(new InsnNode(Opcodes.IRETURN));
        } else if (type.equals(Type.CHAR_TYPE)) {
            il.add(new InsnNode(Opcodes.IRETURN));
        } else if (type.equals(Type.BYTE_TYPE)) {
            il.add(new InsnNode(Opcodes.IRETURN));
        } else if (type.equals(Type.SHORT_TYPE)) {
            il.add(new InsnNode(Opcodes.IRETURN));
        } else if (type.equals(Type.INT_TYPE)) {
            il.add(new InsnNode(Opcodes.IRETURN));
        } else if (type.equals(Type.FLOAT_TYPE)) {
            il.add(new InsnNode(Opcodes.FRETURN));
        } else if (type.equals(Type.LONG_TYPE)) {
            il.add(new InsnNode(Opcodes.LRETURN));
        } else if (type.equals(Type.DOUBLE_TYPE)) {
            il.add(new InsnNode(Opcodes.DRETURN));
        } else {
            il.add(new InsnNode(Opcodes.ARETURN));
        }
    }

    private void addLoadInsn(final InsnList il, final Type type, final int argLocation) {
        if (type.equals(Type.BOOLEAN_TYPE)) {
            il.add(new VarInsnNode(Opcodes.ILOAD, argLocation));
        } else if (type.equals(Type.CHAR_TYPE)) {
            il.add(new VarInsnNode(Opcodes.ILOAD, argLocation));
        } else if (type.equals(Type.BYTE_TYPE)) {
            il.add(new VarInsnNode(Opcodes.ILOAD, argLocation));
        } else if (type.equals(Type.SHORT_TYPE)) {
            il.add(new VarInsnNode(Opcodes.ILOAD, argLocation));
        } else if (type.equals(Type.INT_TYPE)) {
            il.add(new VarInsnNode(Opcodes.ILOAD, argLocation));
        } else if (type.equals(Type.FLOAT_TYPE)) {
            il.add(new VarInsnNode(Opcodes.FLOAD, argLocation));
        } else if (type.equals(Type.LONG_TYPE)) {
            il.add(new VarInsnNode(Opcodes.LLOAD, argLocation));
        } else if (type.equals(Type.DOUBLE_TYPE)) {
            il.add(new VarInsnNode(Opcodes.DLOAD, argLocation));
        } else {
            il.add(new VarInsnNode(Opcodes.ALOAD, argLocation));
        }
    }

    private String getInternalName(final Type type) {
        if (type.equals(Type.BOOLEAN_TYPE)) {
            return "java/lang/Boolean";
        } else if (type.equals(Type.CHAR_TYPE)) {
            return "java/lang/Character";
        } else if (type.equals(Type.BYTE_TYPE)) {
            return "java/lang/Byte";
        } else if (type.equals(Type.SHORT_TYPE)) {
            return "java/lang/Short";
        } else if (type.equals(Type.INT_TYPE)) {
            return "java/lang/Integer";
        } else if (type.equals(Type.FLOAT_TYPE)) {
            return "java/lang/Float";
        } else if (type.equals(Type.LONG_TYPE)) {
            return "java/lang/Long";
        } else if (type.equals(Type.DOUBLE_TYPE)) {
            return "java/lang/Double";
        } else {
            return type.getInternalName();
        }
    }

    private void loadAndConvertToObject(final InsnList il, final Type type, final int argLocation) {
        if (type.equals(Type.BOOLEAN_TYPE)) {
            il.add(new VarInsnNode(Opcodes.ILOAD, argLocation));
            il.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Boolean", "valueOf",
                    "(Z)Ljava/lang/Boolean;"));
        } else if (type.equals(Type.CHAR_TYPE)) {
            il.add(new VarInsnNode(Opcodes.ILOAD, argLocation));
            il.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Character", "valueOf",
                    "(C)Ljava/lang/Character;"));
        } else if (type.equals(Type.BYTE_TYPE)) {
            il.add(new VarInsnNode(Opcodes.ILOAD, argLocation));
            il.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;"));
        } else if (type.equals(Type.SHORT_TYPE)) {
            il.add(new VarInsnNode(Opcodes.ILOAD, argLocation));
            il.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;"));
        } else if (type.equals(Type.INT_TYPE)) {
            il.add(new VarInsnNode(Opcodes.ILOAD, argLocation));
            il.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Integer", "valueOf",
                    "(I)Ljava/lang/Integer;"));
        } else if (type.equals(Type.FLOAT_TYPE)) {
            il.add(new VarInsnNode(Opcodes.FLOAD, argLocation));
            il.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;"));
        } else if (type.equals(Type.LONG_TYPE)) {
            il.add(new VarInsnNode(Opcodes.LLOAD, argLocation));
            il.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;"));
        } else if (type.equals(Type.DOUBLE_TYPE)) {
            il.add(new VarInsnNode(Opcodes.DLOAD, argLocation));
            il.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Double", "valueOf",
                    "(D)Ljava/lang/Double;"));
        } else {
            il.add(new VarInsnNode(Opcodes.ALOAD, argLocation));
        }
    }

    private void addBoxingStmt(final InsnList il, final Type type) {
        if (type.equals(Type.BOOLEAN_TYPE)) {
            il.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Boolean", "valueOf",
                    "(Z)Ljava/lang/Boolean;"));
        } else if (type.equals(Type.CHAR_TYPE)) {
            il.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Character", "valueOf",
                    "(C)Ljava/lang/Character;"));
        } else if (type.equals(Type.BYTE_TYPE)) {
            il.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;"));
        } else if (type.equals(Type.SHORT_TYPE)) {
            il.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;"));
        } else if (type.equals(Type.INT_TYPE)) {
            il.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Integer", "valueOf",
                    "(I)Ljava/lang/Integer;"));
        } else if (type.equals(Type.FLOAT_TYPE)) {
            il.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;"));
        } else if (type.equals(Type.LONG_TYPE)) {
            il.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;"));
        } else if (type.equals(Type.DOUBLE_TYPE)) {
            il.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Double", "valueOf",
                    "(D)Ljava/lang/Double;"));
        }
    }

    private void addUnBoxingStmt(final InsnList il, final Type type) {
        if (type.equals(Type.BOOLEAN_TYPE)) {
            il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z"));
        } else if (type.equals(Type.CHAR_TYPE)) {
            il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C"));
        } else if (type.equals(Type.BYTE_TYPE)) {
            il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B"));
        } else if (type.equals(Type.SHORT_TYPE)) {
            il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S"));
        } else if (type.equals(Type.INT_TYPE)) {
            il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I"));
        } else if (type.equals(Type.FLOAT_TYPE)) {
            il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F"));
        } else if (type.equals(Type.LONG_TYPE)) {
            il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J"));
        } else if (type.equals(Type.DOUBLE_TYPE)) {
            il.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D"));
        }
    }

    private List<Class<?>> getWrapperClasses() {
        return Arrays.asList(new Class<?>[] { java.util.Date.class, java.util.Calendar.class,
                java.text.DateFormat.class, java.text.SimpleDateFormat.class });
    }
}