cl.inria.stiq.instrumenter.BCIUtils.java Source code

Java tutorial

Introduction

Here is the source code for cl.inria.stiq.instrumenter.BCIUtils.java

Source

/*
TOD - Trace Oriented Debugger.
Copyright (c) 2006-2008, Guillaume Pothier
All rights reserved.
    
This program is free software; you can redistribute it and/or 
modify it under the terms of the GNU General Public License 
version 2 as published by the Free Software Foundation.
    
This program 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 
General Public License for more details.
    
You should have received a copy of the GNU General Public License 
along with this program; if not, write to the Free Software 
Foundation, Inc., 59 Temple Place, Suite 330, Boston, 
MA 02111-1307 USA
    
Parts of this work rely on the MD5 algorithm "derived from the 
RSA Data Security, Inc. MD5 Message-Digest Algorithm".
 */
package cl.inria.stiq.instrumenter;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;

import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
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.LocalVariableNode;
import org.objectweb.asm.tree.LookupSwitchInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TableSwitchInsnNode;
import org.objectweb.asm.tree.TryCatchBlockNode;
import org.objectweb.asm.tree.analysis.Analyzer;
import org.objectweb.asm.tree.analysis.AnalyzerException;
import org.objectweb.asm.tree.analysis.BasicInterpreter;
import org.objectweb.asm.tree.analysis.Frame;
import org.objectweb.asm.tree.analysis.Interpreter;
import org.objectweb.asm.util.TraceMethodVisitor;

import tod2.access.Intrinsics;
import zz.utils.Utils;
import cl.inria.stiq.config.ClassSelector;
import cl.inria.stiq.db.structure.ObjectId;
import cl.inria.stiq.db.structure.impl.PrimitiveTypeInfo;
import cl.inria.stiq.instrumenter.MethodInfo.BCIFrame;
import cl.inria.stiq.replayer.BehaviorExitException;
import cl.inria.stiq.replayer.EventCollector;
import cl.inria.stiq.replayer.HandlerReachedException;
import cl.inria.stiq.replayer.InScopeReplayerFrame;
import cl.inria.stiq.replayer.LocalsSnapshot;
import cl.inria.stiq.replayer.ThreadReplayer;
import cl.inria.stiq.replayer.TmpObjectId;
import cl.inria.stiq.replayer.UnmonitoredBehaviorCallException;

public class BCIUtils implements Opcodes {
    public static final Type TYPE_OBJECTID = Type.getType(ObjectId.class);
    public static final Type TYPE_OBJECT = Type.getType(Object.class);
    public static final Type TYPE_THREADREPLAYER = Type.getType(ThreadReplayer.class);

    public static final String CLS_EVENTCOLLECTOR_AGENT = "java/tod/EventCollector";
    public static final String DSC_EVENTCOLLECTOR_AGENT = "L" + CLS_EVENTCOLLECTOR_AGENT + ";";
    public static final String CLS_AGENTREADY = "java/tod/AgentReady";
    public static final String CLS_EXCEPTIONGENERATEDRECEIVER = "java/tod/ExceptionGeneratedReceiver";
    public static final String CLS_THREADDATA = "java/tod/ThreadData";
    public static final String DSC_THREADDATA = "L" + CLS_THREADDATA + ";";
    public static final String CLS_INTRINSICS = getJvmClassName(Intrinsics.class);
    public static final String CLS_OBJECT = getJvmClassName(Object.class);
    public static final String DSC_OBJECT = "L" + CLS_OBJECT + ";";
    public static final String CLS_THROWABLE = getJvmClassName(Throwable.class);
    public static final String DSC_THROWABLE = "L" + CLS_THROWABLE + ";";
    public static final String CLS_STRING = getJvmClassName(String.class);
    public static final String DSC_STRING = "L" + CLS_STRING + ";";
    public static final String CLS_CLASS = getJvmClassName(Class.class);
    public static final String DSC_CLASS = "L" + CLS_CLASS + ";";
    public static final String CLS_CLASSLOADER = getJvmClassName(ClassLoader.class);
    public static final String DSC_CLASSLOADER = "L" + CLS_CLASSLOADER + ";";
    public static final String CLS_OBJECTID = getJvmClassName(ObjectId.class);
    public static final String DSC_OBJECTID = "L" + CLS_OBJECTID + ";";
    public static final String CLS_TMPOBJECTID = getJvmClassName(TmpObjectId.class);
    public static final String DSC_TMPOBJECTID = "L" + CLS_TMPOBJECTID + ";";
    public static final String CLS_THREAD = getJvmClassName(Thread.class);
    public static final String CLS_LOCALSSNAPSHOT = getJvmClassName(LocalsSnapshot.class);
    public static final String DSC_LOCALSSNAPSHOT = "L" + CLS_LOCALSSNAPSHOT + ";";
    public static final String CLS_HANDLERREACHED = BCIUtils.getJvmClassName(HandlerReachedException.class);
    public static final String CLS_BEHAVIOREXITEXCEPTION = BCIUtils.getJvmClassName(BehaviorExitException.class);
    public static final String CLS_UNMONITOREDCALLEXCEPTION = BCIUtils
            .getJvmClassName(UnmonitoredBehaviorCallException.class);
    public static final String CLS_INSCOPEREPLAYERFRAME = BCIUtils.getJvmClassName(InScopeReplayerFrame.class);
    public static final String CLS_EVENTCOLLECTOR_REPLAY = BCIUtils.getJvmClassName(EventCollector.class);
    public static final String DSC_EVENTCOLLECTOR_REPLAY = "L" + CLS_EVENTCOLLECTOR_REPLAY + ";";
    public static final String CLS_THREADREPLAYER = BCIUtils.getJvmClassName(ThreadReplayer.class);
    public static final String DSC_THREADREPLAYER = "L" + CLS_THREADREPLAYER + ";";

    private static final Type[] TYPE_FOR_SORT = new Type[11];
    static {
        TYPE_FOR_SORT[Type.OBJECT] = null;
        TYPE_FOR_SORT[Type.ARRAY] = null;
        TYPE_FOR_SORT[Type.BOOLEAN] = Type.BOOLEAN_TYPE;
        TYPE_FOR_SORT[Type.BYTE] = Type.BYTE_TYPE;
        TYPE_FOR_SORT[Type.CHAR] = Type.CHAR_TYPE;
        TYPE_FOR_SORT[Type.DOUBLE] = Type.DOUBLE_TYPE;
        TYPE_FOR_SORT[Type.FLOAT] = Type.FLOAT_TYPE;
        TYPE_FOR_SORT[Type.INT] = Type.INT_TYPE;
        TYPE_FOR_SORT[Type.LONG] = Type.LONG_TYPE;
        TYPE_FOR_SORT[Type.SHORT] = Type.SHORT_TYPE;
        TYPE_FOR_SORT[Type.VOID] = Type.VOID_TYPE;
    }

    public static String getJvmClassName(Class<?> aClass) {
        String theName = aClass.getName();
        return theName.replace('.', '/');
    }

    /**
     * Return the normal Java class name corresponding to the given internal
     * name
     */
    public static String getClassName(String aJVMClassName) {
        return Type.getType("L" + aJVMClassName + ";").getClassName();
    }

    public static boolean isInterface(int access) {
        return (access & Opcodes.ACC_INTERFACE) != 0;
    }

    public static boolean isStatic(int access) {
        return (access & Opcodes.ACC_STATIC) != 0;
    }

    public static boolean isFinal(int access) {
        return (access & Opcodes.ACC_FINAL) != 0;
    }

    public static boolean isNative(int access) {
        return (access & Opcodes.ACC_NATIVE) != 0;
    }

    public static boolean isPrivate(int access) {
        return (access & Opcodes.ACC_PRIVATE) != 0;
    }

    public static boolean isAbstract(int access) {
        return (access & Opcodes.ACC_ABSTRACT) != 0;
    }

    public static boolean isConstructorCall(AbstractInsnNode aNode) {
        if (aNode.getOpcode() == Opcodes.INVOKESPECIAL) {
            MethodInsnNode theMethodNode = (MethodInsnNode) aNode;
            if ("<init>".equals(theMethodNode.name))
                return true;
        }
        return false;
    }

    /**
     * Generates the bytecode that pushes the given value onto the stack
     */
    public static AbstractInsnNode pushInt(int aValue) {
        switch (aValue) {
        case -1:
            return new InsnNode(ICONST_M1);
        case 0:
            return new InsnNode(ICONST_0);
        case 1:
            return new InsnNode(ICONST_1);
        case 2:
            return new InsnNode(ICONST_2);
        case 3:
            return new InsnNode(ICONST_3);
        case 4:
            return new InsnNode(ICONST_4);
        case 5:
            return new InsnNode(ICONST_5);
        }

        if (aValue >= Byte.MIN_VALUE && aValue <= Byte.MAX_VALUE)
            return new IntInsnNode(BIPUSH, aValue);
        else if (aValue >= Short.MIN_VALUE && aValue <= Short.MAX_VALUE)
            return new IntInsnNode(SIPUSH, aValue);
        else
            return new LdcInsnNode(new Integer(aValue));
    }

    /**
     * Generates the bytecode that pushes the given value onto the stack
     */
    public static AbstractInsnNode pushLong(long aValue) {
        if (aValue == 0)
            return new InsnNode(LCONST_0);
        else if (aValue == 1)
            return new InsnNode(LCONST_1);
        else
            return new LdcInsnNode(new Long(aValue));
    }

    /**
     * Returns the sort of data (as defined by {@link Type} that correponds to
     * the given opcode.
     */
    public static int getSort(int aOpcode) {
        switch (aOpcode) {
        case AALOAD:
        case AASTORE:
        case ACONST_NULL:
        case ALOAD:
        case ANEWARRAY:
        case ARETURN:
        case ASTORE:
        case IF_ACMPEQ:
        case IF_ACMPNE:
            return Type.OBJECT;

        case BALOAD:
        case BASTORE:
            return Type.BYTE;

        case CALOAD:
        case CASTORE:
            return Type.CHAR;

        case DADD:
        case DALOAD:
        case DASTORE:
        case DCMPG:
        case DCMPL:
        case DCONST_0:
        case DCONST_1:
        case DDIV:
        case DLOAD:
        case DMUL:
        case DNEG:
        case DREM:
        case DRETURN:
        case DSTORE:
        case DSUB:
            return Type.DOUBLE;

        case FADD:
        case FALOAD:
        case FASTORE:
        case FCMPG:
        case FCMPL:
        case FCONST_0:
        case FCONST_1:
        case FCONST_2:
        case FDIV:
        case FLOAD:
        case FMUL:
        case FNEG:
        case FREM:
        case FRETURN:
        case FSTORE:
        case FSUB:
            return Type.FLOAT;

        case BIPUSH:
        case IADD:
        case IALOAD:
        case IAND:
        case IASTORE:
        case ICONST_0:
        case ICONST_1:
        case ICONST_2:
        case ICONST_3:
        case ICONST_4:
        case ICONST_5:
        case ICONST_M1:
        case IDIV:
        case IF_ICMPEQ:
        case IF_ICMPGE:
        case IF_ICMPGT:
        case IF_ICMPLE:
        case IF_ICMPLT:
        case IF_ICMPNE:
        case IINC:
        case ILOAD:
        case IMUL:
        case INEG:
        case IOR:
        case IREM:
        case IRETURN:
        case ISHL:
        case ISHR:
        case ISTORE:
        case ISUB:
        case IUSHR:
        case IXOR:
            return Type.INT;

        case LADD:
        case LALOAD:
        case LAND:
        case LASTORE:
        case LCMP:
        case LCONST_0:
        case LCONST_1:
        case LDIV:
        case LLOAD:
        case LMUL:
        case LNEG:
        case LOR:
        case LREM:
        case LRETURN:
        case LSHL:
        case LSHR:
        case LSTORE:
        case LSUB:
        case LUSHR:
        case LXOR:
            return Type.LONG;

        case SALOAD:
        case SASTORE:
        case SIPUSH:
            return Type.SHORT;

        case RETURN:
            return Type.VOID;

        default:
            return -1;
        }
    }

    public static Type getType(int aSort, Type aReferenceType) {
        Type theType = TYPE_FOR_SORT[aSort];
        return theType != null ? theType : aReferenceType;
    }

    /**
     * Returns the primitive type that corresponds to the given operand
     * 
     * @param aOperand
     *            {@link Opcodes#T_BOOLEAN} etc.
     */
    public static PrimitiveTypeInfo getPrimitiveType(int aOperand) {
        switch (aOperand) {
        case Opcodes.T_BOOLEAN:
            return PrimitiveTypeInfo.BOOLEAN;
        case Opcodes.T_BYTE:
            return PrimitiveTypeInfo.BYTE;
        case Opcodes.T_CHAR:
            return PrimitiveTypeInfo.CHAR;
        case Opcodes.T_DOUBLE:
            return PrimitiveTypeInfo.DOUBLE;
        case Opcodes.T_FLOAT:
            return PrimitiveTypeInfo.FLOAT;
        case Opcodes.T_INT:
            return PrimitiveTypeInfo.INT;
        case Opcodes.T_LONG:
            return PrimitiveTypeInfo.LONG;
        case Opcodes.T_SHORT:
            return PrimitiveTypeInfo.SHORT;
        default:
            return null;
        }
    }

    public static boolean acceptClass(String aClassName, ClassSelector aSelector) {
        return aSelector.accept(getClassName(aClassName));
    }

    /**
     * Checks that a method is correct (ie. likely to pass the JVM verifier).
     */
    public static void checkMethod(ClassNode aClassNode, MethodNode aNode) {
        checkMethod(aClassNode, aNode, new BasicInterpreter(), false);
    }

    public static void checkMethod(ClassNode aClassNode, MethodNode aNode, boolean aAlwaysPrint) {
        checkMethod(aClassNode, aNode, new BasicInterpreter(), aAlwaysPrint);
    }

    private static void checkLabel(LabelNode aLabelNode) {
        assert aLabelNode.getLabel().info == null || aLabelNode.getLabel().info == aLabelNode;
    }

    public static void checkLabels(MethodNode aNode) {
        for (int i = 0; i < aNode.instructions.size(); i++) {
            AbstractInsnNode theNode = aNode.instructions.get(i);

            if (theNode instanceof LabelNode) {
                LabelNode theLabelNode = (LabelNode) theNode;
                checkLabel(theLabelNode);
            } else if (theNode instanceof JumpInsnNode) {
                JumpInsnNode theJumpNode = (JumpInsnNode) theNode;
                checkLabel(theJumpNode.label);
            } else if (theNode instanceof LookupSwitchInsnNode) {
                LookupSwitchInsnNode theLookupSwitchNode = (LookupSwitchInsnNode) theNode;
                for (LabelNode theLabel : (List<LabelNode>) theLookupSwitchNode.labels)
                    checkLabel(theLabel);
                checkLabel(theLookupSwitchNode.dflt);
            } else if (theNode instanceof TableSwitchInsnNode) {
                TableSwitchInsnNode theTableSwitchNode = (TableSwitchInsnNode) theNode;
                for (LabelNode theLabel : (List<LabelNode>) theTableSwitchNode.labels)
                    checkLabel(theLabel);
                checkLabel(theTableSwitchNode.dflt);
            }
            theNode = theNode.getNext();
        }
    }

    public static void checkMethod(ClassNode aClassNode, MethodNode aNode, Interpreter aInterpreter,
            boolean aAlwaysPrint) {
        checkLabels(aNode);
        Analyzer theAnalyzer = new Analyzer(aInterpreter);
        try {
            theAnalyzer.analyze(aClassNode.name, aNode);
            if (aAlwaysPrint) {
                Frame[] theFrames = theAnalyzer.getFrames();
                printFrames(aNode, theFrames);
            }
        } catch (AnalyzerException e) {
            Frame[] theFrames = theAnalyzer.getFrames();
            printFrames(aNode, theFrames);

            Utils.rtex(e, "Error in %s.%s%s at instructions #(%s): %s", aClassNode.name, aNode.name, aNode.desc,
                    "?", //e.nodes != null ? getBytecodeRanks(aNode, e.nodes) : "?",
                    e.getMessage());
        } catch (Exception e) {
            Utils.rtex(e, "Exception while analyzing %s.%s%s", aClassNode.name, aNode.name, aNode.desc);
        }
    }

    private static void printFrames(MethodNode aNode, Frame[] aFrames) {
        int bcIndex = 1;

        for (int i = 0; i < aFrames.length; i++) {
            Frame theFrame = aFrames[i];
            AbstractInsnNode theInsn = aNode.instructions.get(i);

            switch (theInsn.getType()) {
            case AbstractInsnNode.INSN:
            case AbstractInsnNode.INT_INSN:
            case AbstractInsnNode.VAR_INSN:
            case AbstractInsnNode.TYPE_INSN:
            case AbstractInsnNode.FIELD_INSN:
            case AbstractInsnNode.METHOD_INSN:
            case AbstractInsnNode.JUMP_INSN:
            case AbstractInsnNode.LDC_INSN:
            case AbstractInsnNode.IINC_INSN:
            case AbstractInsnNode.TABLESWITCH_INSN:
            case AbstractInsnNode.LOOKUPSWITCH_INSN:
            case AbstractInsnNode.MULTIANEWARRAY_INSN:
                TraceMethodVisitor theTraceVisitor = new TraceMethodVisitor();
                theInsn.accept(theTraceVisitor);
                StringWriter theWriter = new StringWriter();
                theTraceVisitor.print(new PrintWriter(theWriter));
                String theTraced = theWriter.toString().replace("\n", "");
                System.out.println(bcIndex + "\t" + frameString(theFrame) + " |\t" + theTraced);
                bcIndex++;
                break;

            case AbstractInsnNode.FRAME:
            case AbstractInsnNode.LINE:
            case AbstractInsnNode.LABEL:
                break;
            }
        }
    }

    private static String frameString(Frame aFrame) {
        if (aFrame == null)
            return "null";
        String s = aFrame.toString();
        s = s.replace("Ltod/core/database/structure/ObjectId;", "o");
        s = s.replace("Ltod/impl/replay2/ThreadReplayer;", "r");
        s = s.replace("Ltod/impl/replay2/TmpObjectId;", "t");
        s = s.replace("Lnull;", "L");
        s = s.replace("Ltod/impl/replay2/HandlerReachedException;", "h");
        s = s.replace("Ltod/impl/replay2/EventCollector;", "c");
        s = s.replace("Ltod/impl/replay2/LocalsSnapshot;", "s");
        s = s.replace("Ljava/lang/Throwable;", "T");
        return s;
    }

    public static int getBytecodeRank(MethodNode aNode, AbstractInsnNode aInstruction) {
        if (aInstruction == null)
            return -1;
        int theRank = 1;
        ListIterator<AbstractInsnNode> theIterator = aNode.instructions.iterator();
        while (theIterator.hasNext()) {
            AbstractInsnNode theNode = theIterator.next();
            if (theNode == aInstruction)
                return theRank;

            int theOpcode = theNode.getOpcode();
            if (theOpcode >= 0)
                theRank++;
        }

        return -1;
    }

    public static String getBytecodeRanks(MethodNode aNode, AbstractInsnNode[] aInstructions) {
        StringBuilder theBuilder = new StringBuilder();
        for (AbstractInsnNode theNode : aInstructions) {
            theBuilder.append(getBytecodeRank(aNode, theNode));
            theBuilder.append(", ");
        }
        return theBuilder.toString();
    }

    public static void checkClass(byte[] aBytecode) {
        // StringWriter sw = new StringWriter();
        // PrintWriter pw = new PrintWriter(sw);
        // CheckClassAdapter.verify(new ClassReader(aBytecode), false, pw);
        //      
        // String theResult = sw.toString();
        // if (theResult.length() != 0)
        // {
        // Utils.rtex(theResult);
        // }
    }

    public static void writeClass(String aRoot, String aName, byte[] aData) {
        try {
            File theDir = new File(aRoot);
            theDir.mkdirs();

            File theFile = new File(theDir, aName.replace('/', '.') + ".class");
            theFile.getParentFile().mkdirs();

            String theName = theFile.getName();
            if (theName.length() > 255) {
                String theMaimedName = maimFileName(theName);
                theFile = new File(theFile.getParentFile(), theMaimedName);
                System.out.println("[BCIUtils.writeClass] Warning: changed " + theName + " to " + theMaimedName);
            }

            theFile.createNewFile();
            FileOutputStream theFileOutputStream = new FileOutputStream(theFile);
            theFileOutputStream.write(aData);
            theFileOutputStream.flush();
            theFileOutputStream.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * If file name is longer than 255 chars, make it shorter by force...
     */
    private static String maimFileName(String aName) {
        int l = aName.length();
        int r = (l + 254) / 255;
        StringBuilder theBuilder = new StringBuilder();
        for (int i = 0; i < l; i += r)
            theBuilder.append(aName.charAt(i));
        return theBuilder.toString();
    }

    public static void throwRTEx(SyntaxInsnList s, String aMessage) {
        s.NEW("java/lang/RuntimeException");
        s.DUP();
        s.LDC(aMessage);
        s.INVOKESPECIAL("java/lang/RuntimeException", "<init>", "(Ljava/lang/String;)V");
        s.ATHROW();
    }

    public static MethodNode cloneMethod(MethodNode aNode) {
        MethodNode theClone = new MethodNode();

        if (aNode.annotationDefault != null)
            throw new UnsupportedOperationException();

        theClone.name = aNode.name;
        theClone.signature = aNode.signature;
        theClone.access = aNode.access;
        theClone.desc = aNode.desc;
        theClone.exceptions = cloneList(aNode.exceptions);
        theClone.attrs = cloneList(aNode.attrs);
        theClone.maxLocals = aNode.maxLocals;
        theClone.maxStack = aNode.maxStack;

        ClonerMap theMap = new ClonerMap();
        theClone.instructions = cloneInstructions(theMap, aNode.instructions);
        theClone.localVariables = cloneLocalVariables(theMap, aNode.localVariables);
        theClone.tryCatchBlocks = cloneTryCatchBlocks(theMap, aNode.tryCatchBlocks);

        theClone.invisibleAnnotations = cloneList(aNode.invisibleAnnotations);
        theClone.invisibleParameterAnnotations = cloneAnnotations(aNode.invisibleParameterAnnotations);
        theClone.visibleAnnotations = cloneList(aNode.visibleAnnotations);
        theClone.visibleParameterAnnotations = cloneAnnotations(aNode.visibleParameterAnnotations);

        checkLabels(theClone);

        return theClone;
    }

    private static List cloneList(List aList) {
        if (aList == null)
            return null;
        if (aList instanceof ArrayList) {
            ArrayList theList = (ArrayList) aList;
            return (List) theList.clone();
        } else
            throw new IllegalArgumentException();
    }

    private static InsnList cloneInstructions(ClonerMap aMap, InsnList aList) {
        InsnList theInsnListClone = new InsnList();
        AbstractInsnNode theNode = aList.getFirst();
        while (theNode != null) {
            AbstractInsnNode theClone = theNode.clone(aMap);
            if (theClone instanceof LabelNode) {
                LabelNode theLabelNode = (LabelNode) theClone;
            }
            theInsnListClone.add(theClone);
            theNode = theNode.getNext();
        }

        return theInsnListClone;
    }

    private static List<AnnotationNode>[] cloneAnnotations(List<AnnotationNode>[] aLists) {
        if (aLists == null)
            return null;
        List<AnnotationNode>[] theClone = new List[aLists.length];
        for (int i = 0; i < theClone.length; i++)
            theClone[i] = cloneList(aLists[i]);
        return theClone;
    }

    private static List<LocalVariableNode> cloneLocalVariables(ClonerMap aMap, List<LocalVariableNode> aList) {
        List<LocalVariableNode> theListClone = new ArrayList<LocalVariableNode>();
        for (LocalVariableNode theNode : aList) {
            theListClone.add(new LocalVariableNode(theNode.name, theNode.desc, theNode.signature,
                    aMap.get(theNode.start), aMap.get(theNode.end), theNode.index));
        }

        return theListClone;
    }

    private static List<TryCatchBlockNode> cloneTryCatchBlocks(ClonerMap aMap, List<TryCatchBlockNode> aList) {
        List<TryCatchBlockNode> theListClone = new ArrayList<TryCatchBlockNode>();
        for (TryCatchBlockNode theNode : aList) {
            theListClone.add(new TryCatchBlockNode(aMap.get(theNode.start), aMap.get(theNode.end),
                    aMap.get(theNode.handler), theNode.type));
        }

        return theListClone;
    }

    private static class ClonerMap extends HashMap<LabelNode, LabelNode> {
        @Override
        public LabelNode get(Object aKey) {
            LabelNode theNode = super.get(aKey);

            if (theNode == null) {
                theNode = new LabelNode();
                put((LabelNode) aKey, theNode);
            }

            return theNode;
        }
    }

    public static Type getTypeForSig(char aSig) {
        switch (aSig) {
        case 'I':
            return Type.INT_TYPE;
        case 'J':
            return Type.LONG_TYPE;
        case 'F':
            return Type.FLOAT_TYPE;
        case 'D':
            return Type.DOUBLE_TYPE;
        case 'L':
            return TYPE_OBJECTID;
        default:
            throw new RuntimeException("Not handled: " + aSig);
        }
    }

    public static String getSnapshotSig(BCIFrame aFrame, boolean aIncludeStack) {
        StringBuilder theBuilder = new StringBuilder();

        // Locals
        int theLocals = aFrame.getLocals();
        for (int i = 0; i < theLocals; i++) {
            Type theType = aFrame.getLocal(i).getType();
            if (theType != null)
                theBuilder.append(getSigForType(theType));
        }

        // Stack
        if (aIncludeStack) {
            // Stack is passed in reverse order for simper resuming from snapshot
            int theStack = aFrame.getStackSize();
            for (int i = theStack - 1; i >= 0; i--) {
                Type theType = aFrame.getStack(i).getType();
                theBuilder.append(getSigForType(theType));
            }
        }

        return theBuilder.toString();
    }

    /**
     * Returns a one-character descriptor for the given type, folding all the types
     * that are represented as an int in bytecode into int.
     */
    public static char getSigForType(Type aType) {
        return getSigForSort(aType.getSort());
    }

    /**
     * Returns a one-character descriptor for the given type, folding all the types
     * that are represented as an int in bytecode into int.
     */
    public static char getSigForSort(int aSort) {
        switch (aSort) {
        case Type.ARRAY:
        case Type.OBJECT:
            return 'L';

        case Type.BOOLEAN:
        case Type.BYTE:
        case Type.CHAR:
        case Type.INT:
        case Type.SHORT:
            return 'I';

        case Type.DOUBLE:
            return 'D';

        case Type.FLOAT:
            return 'F';

        case Type.LONG:
            return 'J';

        default:
            throw new RuntimeException("Not handled: " + aSort);
        }
    }

    /**
     * Returns a one-character descriptor for the given type. These are the classical
     * JVM descriptor characters, with just L for reference types.
     */
    public static char getCompleteSigForType(Type aType) {
        return getCompleteSigForSort(aType.getSort());
    }

    /**
     * Returns a one-character descriptor for the given type. These are the classical
     * JVM descriptor characters, with just L for reference types.
     */
    public static char getCompleteSigForSort(int aSort) {
        switch (aSort) {
        case Type.ARRAY:
        case Type.OBJECT:
            return 'L';

        case Type.BOOLEAN:
            return 'Z';
        case Type.BYTE:
            return 'B';
        case Type.CHAR:
            return 'C';
        case Type.INT:
            return 'I';
        case Type.SHORT:
            return 'S';
        case Type.DOUBLE:
            return 'D';
        case Type.FLOAT:
            return 'F';
        case Type.LONG:
            return 'J';
        case Type.VOID:
            return 'V';

        default:
            throw new RuntimeException("Not handled: " + aSort);
        }
    }

    public static Type getReplayDispatchType(Type aType) {
        return getType(aType.getSort(), TYPE_OBJECTID);
    }

    /**
     * Returns the actual type to use for the given type (all refs are folded into ObjectId, and scalars to int).
     */
    public static Type getActualReplayType(Type aType) {
        return ACTUALTYPE_FOR_SORT[aType.getSort()];
    }

    public static Type getActualReplayType(int aSort) {
        return ACTUALTYPE_FOR_SORT[aSort];
    }

    private static final Type[] ACTUALTYPE_FOR_SORT = new Type[11];
    static {
        ACTUALTYPE_FOR_SORT[Type.OBJECT] = TYPE_OBJECTID;
        ACTUALTYPE_FOR_SORT[Type.ARRAY] = TYPE_OBJECTID;
        ACTUALTYPE_FOR_SORT[Type.BOOLEAN] = Type.INT_TYPE;
        ACTUALTYPE_FOR_SORT[Type.BYTE] = Type.INT_TYPE;
        ACTUALTYPE_FOR_SORT[Type.CHAR] = Type.INT_TYPE;
        ACTUALTYPE_FOR_SORT[Type.DOUBLE] = Type.DOUBLE_TYPE;
        ACTUALTYPE_FOR_SORT[Type.FLOAT] = Type.FLOAT_TYPE;
        ACTUALTYPE_FOR_SORT[Type.INT] = Type.INT_TYPE;
        ACTUALTYPE_FOR_SORT[Type.LONG] = Type.LONG_TYPE;
        ACTUALTYPE_FOR_SORT[Type.SHORT] = Type.INT_TYPE;
        ACTUALTYPE_FOR_SORT[Type.VOID] = Type.VOID_TYPE;
    }

}