public static InsnList call(Method method, InsnList ... args) {
    Validate.notNull(method);
    Validate.notNull(args);
    Validate.noNullElements(args);
        
        
    InsnList ret = new InsnList();
        
    for (InsnList arg : args) {
        ret.add(arg);
    }
        
    Type clsType = Type.getType(method.getDeclaringClass());
    Type methodType = Type.getType(method);
        
    if ((method.getModifiers() & Modifier.STATIC) == Modifier.STATIC) {
        Validate.isTrue(method.getParameterCount() == args.length);
        ret.add(new MethodInsnNode(Opcodes.INVOKESTATIC, clsType.getInternalName(), method.getName(), methodType.getDescriptor(),
                false));
    } else if (method.getDeclaringClass().isInterface()) {
        Validate.isTrue(method.getParameterCount() + 1 == args.length);
        ret.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, clsType.getInternalName(), method.getName(), methodType.getDescriptor(),
                true));
    } else {
        Validate.isTrue(method.getParameterCount() + 1 == args.length);
        ret.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, clsType.getInternalName(), method.getName(), methodType.getDescriptor(),
                false));
    }
        
    return ret;
} public static InsnList construct(Constructor<?> constructor, InsnList ... args) {
    Validate.notNull(constructor);
    Validate.notNull(args);
    Validate.noNullElements(args);
    Validate.isTrue(constructor.getParameterCount() == args.length);
        
        
    InsnList ret = new InsnList();
        
    Type clsType = Type.getType(constructor.getDeclaringClass());
    Type methodType = Type.getType(constructor);
        
    ret.add(new TypeInsnNode(Opcodes.NEW, clsType.getInternalName()));
    ret.add(new InsnNode(Opcodes.DUP));
    for (InsnList arg : args) {
        ret.add(arg);
    }
    ret.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, clsType.getInternalName(), "<init>", methodType.getDescriptor(), false));
        
    return ret;
} public static InsnList forEach(Variable counterVar, Variable arrayLenVar, InsnList array, InsnList action) {
    Validate.notNull(counterVar);
    Validate.notNull(arrayLenVar);
    Validate.notNull(array);
    Validate.notNull(action);
    Validate.isTrue(counterVar.getType().equals(Type.INT_TYPE));
    Validate.isTrue(arrayLenVar.getType().equals(Type.INT_TYPE));
        
        
    InsnList ret = new InsnList();
        
    LabelNode doneLabelNode = new LabelNode();
    LabelNode loopLabelNode = new LabelNode();
        
    // put zero in to counterVar
    ret.add(new LdcInsnNode(0)); // int
    ret.add(new VarInsnNode(Opcodes.ISTORE, counterVar.getIndex())); //
        
    // load array we'll be traversing over
    ret.add(array); // object[]
        
    // put array length in to arrayLenVar
    ret.add(new InsnNode(Opcodes.DUP)); // object[], object[]
    ret.add(new InsnNode(Opcodes.ARRAYLENGTH)); // object[], int
    ret.add(new VarInsnNode(Opcodes.ISTORE, arrayLenVar.getIndex())); // object[]
        
    // loopLabelNode: test if counterVar == arrayLenVar, if it does then jump to doneLabelNode
    ret.add(loopLabelNode);
    ret.add(new VarInsnNode(Opcodes.ILOAD, counterVar.getIndex())); // object[], int
    ret.add(new VarInsnNode(Opcodes.ILOAD, arrayLenVar.getIndex())); // object[], int, int
    ret.add(new JumpInsnNode(Opcodes.IF_ICMPEQ, doneLabelNode)); // object[]
        
    // load object from object[]
    ret.add(new InsnNode(Opcodes.DUP)); // object[], object[]
    ret.add(new VarInsnNode(Opcodes.ILOAD, counterVar.getIndex())); // object[], object[], int
    ret.add(new InsnNode(Opcodes.AALOAD)); // object[], object
        
    // call action
    ret.add(action); // object[]
        
    // increment counter var and goto loopLabelNode
    ret.add(new IincInsnNode(counterVar.getIndex(), 1)); // object[]
    ret.add(new JumpInsnNode(Opcodes.GOTO, loopLabelNode)); // object[]
        
    // doneLabelNode: pop object[] off of stack
    ret.add(doneLabelNode);
    ret.add(new InsnNode(Opcodes.POP)); //
        
    return ret;
}     public static InsnList loadVar(Variable variable) {
        Validate.notNull(variable);

        InsnList ret = new InsnList();
        switch (variable.getType().getSort()) {
            case Type.BOOLEAN:
            case Type.BYTE:
            case Type.CHAR:
            case Type.SHORT:
            case Type.INT:
                ret.add(new VarInsnNode(Opcodes.ILOAD, variable.getIndex()));
                break;
            case Type.LONG:
                ret.add(new VarInsnNode(Opcodes.LLOAD, variable.getIndex()));
                break;
            case Type.FLOAT:
                ret.add(new VarInsnNode(Opcodes.FLOAD, variable.getIndex()));
                break;
            case Type.DOUBLE:
                ret.add(new VarInsnNode(Opcodes.DLOAD, variable.getIndex()));
                break;
            case Type.OBJECT:
            case Type.ARRAY:
                ret.add(new VarInsnNode(Opcodes.ALOAD, variable.getIndex()));
                // If required, do it outside this method
//                ret.add(new TypeInsnNode(Opcodes.CHECKCAST, variable.getType().getInternalName()));
                break;
            default:
                throw new IllegalStateException(); // should never happen, there is code in Variable/VariableTable to make sure invalid
                                                   // types aren't set
        }

        return ret;
    } public static InsnList merge(Object... insns) {
    Validate.notNull(insns);
    Validate.noNullElements(insns);

    InsnList ret = new InsnList();
    for (Object insn : insns) {
        if (insn instanceof AbstractInsnNode) {
            // add single instruction
            AbstractInsnNode insnNode = (AbstractInsnNode) insn;
            ret.add(insnNode);
        } else if (insn instanceof InsnList) {
            // add instruction list
            InsnList insnList = (InsnList) insn;
            for (int i = 0; i < insnList.size(); i++) {
                Validate.notNull(insnList.get(i));
            }
            ret.add((InsnList) insn);
        } else if (insn instanceof InstructionGenerator) {
            // generate conditional merger instruction list and add
            InsnList insnList = ((InstructionGenerator) insn).generate();
            for (int i = 0; i < insnList.size(); i++) {
                Validate.notNull(insnList.get(i));
            }
            ret.add(insnList);
        } else {
            // unrecognized
            throw new IllegalArgumentException();
        }
    }

    return ret;
} public static InsnList saveVar(Variable variable) {
    Validate.notNull(variable);

    InsnList ret = new InsnList();
    switch (variable.getType().getSort()) {
        case Type.BOOLEAN:
        case Type.BYTE:
        case Type.CHAR:
        case Type.SHORT:
        case Type.INT:
            ret.add(new VarInsnNode(Opcodes.ISTORE, variable.getIndex()));
            break;
        case Type.LONG:
            ret.add(new VarInsnNode(Opcodes.LSTORE, variable.getIndex()));
            break;
        case Type.FLOAT:
            ret.add(new VarInsnNode(Opcodes.FSTORE, variable.getIndex()));
            break;
        case Type.DOUBLE:
            ret.add(new VarInsnNode(Opcodes.DSTORE, variable.getIndex()));
            break;
        case Type.OBJECT:
        case Type.ARRAY:
            ret.add(new VarInsnNode(Opcodes.ASTORE, variable.getIndex()));
            break;
        default:
            throw new IllegalStateException(); // should never happen, there is code in Variable/VariableTable to make sure invalid
                                               // types aren't set
    }

    return ret;
} public final class Variable {
    private Type type;
    private int index;
    private boolean used;

    private Variable(Type type, int index, boolean used) {
        this.type = type;
        this.index = index;
        this.used = used;
    }

    /**
     * Get the type of this local variable table entry.
     * @return type of this entry
     * @throws IllegalArgumentException if this {@link Variable} has been released
     */
    public Type getType() {
        Validate.isTrue(used);
        return type;
    }

    /**
     * Get the index of this entry within the local variable table.
     * @return index of this entry
     * @throws IllegalArgumentException if this {@link Variable} has been released
     */
    public int getIndex() {
        Validate.isTrue(used);
        return index;
    }

    /**
     * Returns {@code true} if this object hasn't been released.
     * @see VariableTable#releaseExtra(com.offbynull.coroutines.instrumenter.asm.VariableTable.Variable) 
    
     * @return index of this entry
     * @throws IllegalArgumentException if this {@link Variable} has been released
     */
    public boolean isUsed() {
        return used;
    }
        
    private VariableTable getParent() {
        return VariableTable.this;
    }
} public enum MarkerType {
    /**
     * Generate no instructions.
     */
    NONE,
    /**
     * Generate instructions to load text as string constant and immediately pop it off the stack.
     */
    CONSTANT,
    /**
     * Generate instructions to print text to standard out.
     */
    STDOUT
} public static InsnList debugMarker(MarkerType markerType, String text) {
    Validate.notNull(markerType);
    Validate.notNull(text);
        
    InsnList ret = new InsnList();
        
    switch (markerType) {
        case NONE:
            break;
        case CONSTANT:
            ret.add(new LdcInsnNode(text));
            ret.add(new InsnNode(Opcodes.POP));
            break;
        case STDOUT:
            ret.add(new FieldInsnNode(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"));
            ret.add(new LdcInsnNode(text));
            ret.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false));
            break;
        default:
            throw new IllegalStateException();
    }

    return ret;
} public final class LockState implements Serializable {
    private static final long serialVersionUID = 6L;

    // We use a linkedlist to make sure that we retain the order of monitors as they come in. Otherwise we're going to deal with deadlock
    // issues if we have code structured with double locks. For example, imagine the following scenario...
    //
    // Method 1:
    // synchronized(a) {
    //    synchronized(b) {
    //        continuation.suspend();
    //    }
    // }
    //
    // Method 2 (same as method1):
    // synchronized(a) {
    //    synchronized(b) {
    //        continuation.suspend();
    //    }
    // }
    //
    //
    // If we weren't ordered, we may end up coming back after a suspend and re-locking the monitors in the wrong order. For example,
    // we may lock b before we lock a. That has the potential for a deadlock because another thread could execute the locking logic
    // correctly (first a and then b). Dual locking without retaining the same order = a deadlock waiting to happen.
    //
    // Long story short: it's vital that we keep the order which locks happen
    private LinkedList monitors = new LinkedList();

    /**
     * Do not use -- for internal use only.
     * <p>
     * Should be called after a MONITORENTER instruction has been executed. Tracks the object that MONITORENTER was used on.
     * @param monitor object the MONITORENTER instruction was used on
     */
    public void enter(Object monitor) {
        if (monitor == null) {
            throw new NullPointerException();
        }

        monitors.add(monitor);
    }

    /**
     * Do not use -- for internal use only.
     * <p>
     * Should be called after a MONITOREXIT instruction has been executed. Untracks the object that MONITOREXIT was used on.
     * @param monitor object the MONITOREXIT instruction was used on
     */
    public void exit(Object monitor) {
        if (monitor == null) {
            throw new NullPointerException();
        }

        // remove last
        ListIterator it = monitors.listIterator(monitors.size());
        while (it.hasPrevious()) {
            Object prev = it.previous();
            if (monitor == prev) { // Never use equals() to test equality. We always need to make sure that the objects are the same, we
                                   // don't care if they're the objects are logically equivalent
                it.remove();
                return;
            }
        }
        
        throw new IllegalArgumentException(); // not found
    }
    
    /**
     * Dumps monitors out as an array. Order is retained.
     * @return monitors
     */
    public Object[] toArray() {
        return monitors.toArray();
    }
}
import static com.offbynull.coroutines.instrumenter.generators.GenericGenerators.call;
import static com.offbynull.coroutines.instrumenter.generators.GenericGenerators.construct;
import static com.offbynull.coroutines.instrumenter.generators.GenericGenerators.forEach;
import static com.offbynull.coroutines.instrumenter.generators.GenericGenerators.loadVar;
import static com.offbynull.coroutines.instrumenter.generators.GenericGenerators.merge;
import static com.offbynull.coroutines.instrumenter.generators.GenericGenerators.saveVar;
import com.offbynull.coroutines.instrumenter.asm.VariableTable.Variable;
import com.offbynull.coroutines.instrumenter.generators.DebugGenerators.MarkerType;
import static com.offbynull.coroutines.instrumenter.generators.DebugGenerators.debugMarker;
import com.offbynull.coroutines.user.LockState;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.reflect.ConstructorUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.VarInsnNode;
/*
 * Copyright (c) 2016, Kasra Faghihi, All rights reserved.
 * 
 * This library 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.
 * 
 * This library 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.
 */
package com.offbynull.coroutines.instrumenter;


final class SynchronizationGenerators {

    private static final Constructor<LockState> LOCKSTATE_INIT_METHOD
            = ConstructorUtils.getAccessibleConstructor(LockState.class);
    private static final Method LOCKSTATE_ENTER_METHOD
            = MethodUtils.getAccessibleMethod(LockState.class, "enter", Object.class);
    private static final Method LOCKSTATE_EXIT_METHOD
            = MethodUtils.getAccessibleMethod(LockState.class, "exit", Object.class);
    private static final Method LOCKSTATE_TOARRAY_METHOD
            = MethodUtils.getAccessibleMethod(LockState.class, "toArray");

    private SynchronizationGenerators() {
        // do nothing
    }
    
    /**
     * Generates instruction to that creates a new {@link LockState} object and saves it to the lockstate variable.
     * @param markerType debug marker type
     * @param lockVars variables for lock/synchpoint functionality
     * @return instructions to push a new {@link LockState} object
     * @throws NullPointerException if any argument is {@code null}
     * @throws IllegalArgumentException if lock variables aren't set (the method doesn't contain any monitorenter/monitorexit instructions)
     */
    public static InsnList createMonitorContainer(MarkerType markerType, LockVariables lockVars) {
        Validate.notNull(markerType);
        Validate.notNull(lockVars);

        Variable lockStateVar = lockVars.getLockStateVar();
        Validate.isTrue(lockStateVar != null);  // extra sanity check, if no synch points this should be null

        return merge(
                debugMarker(markerType, "Creating lockstate"),
                construct(LOCKSTATE_INIT_METHOD),
                saveVar(lockStateVar)
        );
    }

    /**
     * Generates instruction to enter all monitors in the {@link LockState} object sitting in the lockstate variable.
     * @param markerType debug marker type
     * @param lockVars variables for lock/synchpoint functionality
     * @return instructions to enter all monitors in the {@link LockState} object
     * @throws NullPointerException if any argument is {@code null}
     * @throws IllegalArgumentException if lock variables aren't set (the method doesn't contain any monitorenter/monitorexit instructions)
     */
    public static InsnList enterStoredMonitors(MarkerType markerType, LockVariables lockVars) {
        Validate.notNull(markerType);
        Validate.notNull(lockVars);

        Variable lockStateVar = lockVars.getLockStateVar();
        Variable counterVar = lockVars.getCounterVar();
        Variable arrayLenVar = lockVars.getArrayLenVar();
        Validate.isTrue(lockStateVar != null);
        Validate.isTrue(counterVar != null);
        Validate.isTrue(arrayLenVar != null);

        return forEach(counterVar, arrayLenVar,
                merge(
                        debugMarker(markerType, "Loading monitors to enter"),
                        call(LOCKSTATE_TOARRAY_METHOD, loadVar(lockStateVar))