All Downloads are FREE. Search and download functionalities are using the official Maven repository.

co.paralleluniverse.fibers.instrument.InstrumentMethod Maven / Gradle / Ivy

Go to download

The core library for Fibers on Java, compatible with Java 11-16. Forked from puniverse/quasar

There is a newer version: 10.0.6
Show newest version
/*
 * Quasar: lightweight threads and actors for the JVM.
 * Copyright (c) 2013-2015, Parallel Universe Software Co. All rights reserved.
 * 
 * This program and the accompanying materials are dual-licensed under
 * either the terms of the Eclipse Public License v1.0 as published by
 * the Eclipse Foundation
 *  
 *   or (per the licensee's choosing)
 *  
 * under the terms of the GNU Lesser General Public License version 3.0
 * as published by the Free Software Foundation.
 */
 /*
 * Copyright (c) 2008-2013, Matthias Mann
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *     * Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Matthias Mann nor the names of its
 *       contributors may be used to endorse or promote products derived from
 *       this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package co.paralleluniverse.fibers.instrument;

// import co.paralleluniverse.common.util.SystemProperties;
import co.paralleluniverse.fibers.Instrumented;
import co.paralleluniverse.fibers.Stack;
import static co.paralleluniverse.fibers.instrument.Classes.INSTRUMENTED_DESC;
import static co.paralleluniverse.fibers.instrument.Classes.EXCEPTION_NAME;
import static co.paralleluniverse.fibers.instrument.Classes.THROWABLE_NAME;
import static co.paralleluniverse.fibers.instrument.Classes.RUNTIME_EXCEPTION_NAME;
import static co.paralleluniverse.fibers.instrument.Classes.SUSPEND_EXECUTION_NAME;
import static co.paralleluniverse.fibers.instrument.Classes.RUNTIME_SUSPEND_EXECUTION_NAME;
import static co.paralleluniverse.fibers.instrument.Classes.STACK_NAME;
import static co.paralleluniverse.fibers.instrument.Classes.UNDECLARED_THROWABLE_NAME;
import static co.paralleluniverse.fibers.instrument.Classes.isAllowedToBlock;
import static co.paralleluniverse.fibers.instrument.Classes.blockingCallIdx;
import static co.paralleluniverse.fibers.instrument.Classes.isYieldMethod;
import co.paralleluniverse.fibers.instrument.MethodDatabase.SuspendableType;
import static co.paralleluniverse.fibers.instrument.MethodDatabase.isInvocationHandlerInvocation;
import static co.paralleluniverse.fibers.instrument.MethodDatabase.isMethodHandleInvocation;
import static co.paralleluniverse.fibers.instrument.MethodDatabase.isReflectInvocation;
import static co.paralleluniverse.fibers.instrument.MethodDatabase.isSyntheticAccess;

import java.util.*;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;
import org.objectweb.asm.tree.analysis.Analyzer;
import org.objectweb.asm.tree.analysis.AnalyzerException;
import org.objectweb.asm.tree.analysis.BasicValue;
import org.objectweb.asm.tree.analysis.Frame;
import org.objectweb.asm.tree.analysis.Value;

/**
 * Instrument a method to allow suspension
 *
 * @author Matthias Mann
 * @author pron
 */
class InstrumentMethod {
    private static final boolean optimizationDisabled = false; // SystemProperties.isEmptyOrTrue("co.paralleluniverse.fibers.disableInstrumentationOptimization");
    private static final boolean HANDLE_PROXY_INVOCATIONS = true;

    // private final boolean verifyInstrumentation; //
    // private static final int PREEMPTION_BACKBRANCH = 0;
    // private static final int PREEMPTION_CALL = 1;
    private static final int NUM_LOCALS = 3; // = 3 + (verifyInstrumentation ? 1 : 0); // lvarStack, lvarResumed, lvarInvocationReturnValue
    private static final int ADD_OPERANDS = 6; // 4;

    // private static final String INTERRUPTED_EXCEPTION_NAME = Type.getInternalName(InterruptedException.class);
    // private static final boolean DUAL = true; // true if suspendable methods can be called from regular threads in addition to fibers
    private final MethodDatabase db;

    private final String sourceName;
    private final String className;

    private final MethodNode mn;
    private final Frame[] frames;

    private final int lvarStack; // ref to Stack
    private final int lvarResumed; // boolean indicating if we've been resumed
    private final int lvarInvocationReturnValue;
    // private final int lvarSuspendableCalled; // true iff we've called another suspendable method (used when VERIFY_INSTRUMENTATION)

    private final int firstLocal;

    private FrameInfo[] codeBlocks = new FrameInfo[32];
    private int numCodeBlocks;

    private int additionalLocals;
    private int maxRefSlots;

    private boolean warnedAboutMonitors;
    private int warnedAboutBlocking;

    private boolean callsSuspendableSupers;

    private int startSourceLine = -1;
    private int endSourceLine = -1;
    private int[] suspCallsSourceLines = new int[8];
    private String[] suspCallsNames = new String[0];
    private int[] suspCallsBcis = null;

    InstrumentMethod(MethodDatabase db, String sourceName, String className, MethodNode mn) throws AnalyzerException {
        this.db = db;
        this.sourceName = sourceName;
        this.className = className;
        this.mn = mn;

        try {
            upgradeForeach(mn);
            Analyzer a = new TypeAnalyzer(db);
            this.frames = a.analyze(className, mn);
            this.lvarStack = mn.maxLocals;
            this.lvarResumed = mn.maxLocals + 1;
            this.lvarInvocationReturnValue = mn.maxLocals + 2;
            // this.lvarSuspendableCalled = (verifyInstrumentation ? mn.maxLocals + 3 : -1);
            this.firstLocal = ((mn.access & Opcodes.ACC_STATIC) == Opcodes.ACC_STATIC) ? 0 : 1;
            this.maxRefSlots = 0;
        } catch (UnsupportedOperationException ex) {
            throw new AnalyzerException(null, ex.getMessage(), ex);
        }
    }

    public void upgradeForeach(MethodNode mn) {
        ListIterator it = mn.instructions.iterator();
        int i = 0;
        while (it.hasNext()) {
            AbstractInsnNode instr = it.next();
            if (instr.getType() == AbstractInsnNode.METHOD_INSN
                && (instr.getOpcode() == Opcodes.INVOKEVIRTUAL
                || instr.getOpcode() == Opcodes.INVOKEINTERFACE)) {
                MethodInsnNode mCall = (MethodInsnNode) instr;
                if (mCall.name.equals("iterator")
                    // we can't check the return type here because Eclipse makes it Iterator
                    // but javac respects subtypes (but only here)
//                        && mCall.desc.equals("()Ljava/util/Iterator;")
                ) {
                    checkForeach(mCall);
                }
            }
        }
    }

    private void checkForeach(MethodInsnNode iteratorCall) {
        // iterable.iterator(): invoke iterator(), store, jump to test [only Eclipse]
        AbstractInsnNode mCallPlus1 = iteratorCall.getNext();
        if (mCallPlus1 == null
            || mCallPlus1.getType() != AbstractInsnNode.VAR_INSN
            || mCallPlus1.getOpcode() != Opcodes.ASTORE)
            return;
        VarInsnNode mCallStore = (VarInsnNode) mCallPlus1;
        int iteratorVarIndex = mCallStore.var;
        AbstractInsnNode mCallPlus2 = mCallPlus1.getNext();
        if (mCallPlus2 == null)
            return;

        boolean testBeforeNext;
        AbstractInsnNode testInstr;
        if (mCallPlus2.getType() == AbstractInsnNode.JUMP_INSN
            && mCallPlus2.getOpcode() == Opcodes.GOTO) {
            testBeforeNext = true;
            // jump to the hasNext() test: load, invoke hasNext(), ifne to body
            JumpInsnNode jumpToTest = (JumpInsnNode) mCallPlus2;
            testInstr = getJumpTarget(jumpToTest.label);
        } else {
            // continue hasNext() test: label, load, invoke hasNext(), ifeq to end
            testBeforeNext = false;
            if (mCallPlus2.getType() != AbstractInsnNode.LABEL)
                return;
            testInstr = mCallPlus2.getNext();
        }

        if (testInstr == null
            || testInstr.getType() != AbstractInsnNode.VAR_INSN
            || testInstr.getOpcode() != Opcodes.ALOAD)
            return;
        VarInsnNode testLoad = (VarInsnNode) testInstr;
        if (testLoad.var != iteratorVarIndex)
            return;
        AbstractInsnNode testLoadPlus1 = testLoad.getNext();
        if (testLoadPlus1 == null
            || testLoadPlus1.getType() != AbstractInsnNode.METHOD_INSN
            || testLoadPlus1.getOpcode() != Opcodes.INVOKEINTERFACE)
            return;
        MethodInsnNode hasNextCall = (MethodInsnNode) testLoadPlus1;
        if (!hasNextCall.name.equals("hasNext")
            || !hasNextCall.owner.equals("java/util/Iterator")
            || !hasNextCall.desc.equals("()Z"))
            return;
        AbstractInsnNode testLoadPlus2 = hasNextCall.getNext();
        if (testLoadPlus2 == null
            || testLoadPlus2.getType() != AbstractInsnNode.JUMP_INSN)
            return;
        if (testBeforeNext && testLoadPlus2.getOpcode() != Opcodes.IFNE)
            return;
        if (!testBeforeNext && testLoadPlus2.getOpcode() != Opcodes.IFEQ)
            return;

        // Now check body: load, invoke next()
        JumpInsnNode jumpToBody = (JumpInsnNode) testLoadPlus2;
        AbstractInsnNode bodyInstr = testBeforeNext ? getJumpTarget(jumpToBody.label) : jumpToBody.getNext();
        if (bodyInstr == null
            || bodyInstr.getType() != AbstractInsnNode.VAR_INSN
            || bodyInstr.getOpcode() != Opcodes.ALOAD)
            return;
        VarInsnNode bodyLoad = (VarInsnNode) bodyInstr;
        if (bodyLoad.var != iteratorVarIndex)
            return;
        AbstractInsnNode bodyLoadPlus1 = bodyLoad.getNext();
        if (bodyLoadPlus1 == null
            || bodyLoadPlus1.getType() != AbstractInsnNode.METHOD_INSN
            || bodyLoadPlus1.getOpcode() != Opcodes.INVOKEINTERFACE)
            return;
        MethodInsnNode nextCall = (MethodInsnNode) bodyLoadPlus1;
        if (!nextCall.name.equals("next")
            || !nextCall.owner.equals("java/util/Iterator")
            || !nextCall.desc.equals("()Ljava/lang/Object;"))
            return;

        MethodDatabase.ClassEntry iterableClassEntry = db.getOrLoadClassEntry(iteratorCall.owner);
        if (iterableClassEntry == null)
            return;
        if (!iterableClassEntry.implementsInterface("java/lang/Iterable", db))
            return;
        MethodDatabase.ClassEntry methodOwnerClass = iterableClassEntry.getClassImplementingMethod("iterator()", db);
        // iteratorType contains the "L...;" parts
        String iteratorType = methodOwnerClass.getReturnType("iterator()");
        if (iteratorType == null || iteratorType.equals("Ljava/util/Iterator;"))
            return;

        MethodDatabase.ClassEntry iteratorClass =
            db.getOrLoadClassEntry(iteratorType.substring(1, iteratorType.length() - 1));
        if (iteratorClass == null)
            return;
        MethodDatabase.ClassEntry nextOwnerClass = iteratorClass.getClassImplementingMethod("next()", db);
        if (nextOwnerClass == null)
            return;
        String nextMethodOwner = nextOwnerClass.getName();
        boolean nextMethodInterface = nextOwnerClass.isInterface();

        MethodDatabase.ClassEntry hasNextOwnerClass = iteratorClass.getClassImplementingMethod("hasNext()", db);
        if (hasNextOwnerClass == null)
            return;
        String hasNextMethodOwner = hasNextOwnerClass.getName();
        boolean hasNextMethodInterface = hasNextOwnerClass.isInterface();

        iteratorCall.desc = "()" + iteratorType;
        hasNextCall.owner = hasNextMethodOwner;
        hasNextCall.setOpcode(hasNextMethodInterface ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL);
        hasNextCall.itf = hasNextMethodInterface;
        nextCall.owner = nextMethodOwner;
        nextCall.setOpcode(nextMethodInterface ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL);
        nextCall.itf = nextMethodInterface;
    }

    private AbstractInsnNode getJumpTarget(LabelNode label) {
        AbstractInsnNode next = label.getNext();
        while (next.getType() == AbstractInsnNode.FRAME
            || next.getType() == AbstractInsnNode.LINE) {
            next = next.getNext();
        }
        return next;
    }

    private void collectCallsites() {
        if (suspCallsBcis == null) {
            suspCallsBcis = new int[8];
            final int numIns = mn.instructions.size();
            int currSourceLine = -1;
            int count = 0;
            final Set callSiteNames = new HashSet<>();
            for (int i = 0; i < numIns; i++) {
                final Frame f = frames[i];
                if (f != null) { // reachable ?
                    final AbstractInsnNode in = mn.instructions.get(i);
                    if (in.getType() == AbstractInsnNode.LINE) {
                        final LineNumberNode lnn = (LineNumberNode) in;
                        currSourceLine = lnn.line;
                        if (startSourceLine == -1 || currSourceLine < startSourceLine)
                            startSourceLine = currSourceLine;
                        if (endSourceLine == -1 || currSourceLine > endSourceLine)
                            endSourceLine = currSourceLine;
                    } else if (in.getType() == AbstractInsnNode.METHOD_INSN || in.getType() == AbstractInsnNode.INVOKE_DYNAMIC_INSN) {
                        if (isSuspendableCall(db, in)) {
                            if (count >= suspCallsBcis.length)
                                suspCallsBcis = Arrays.copyOf(suspCallsBcis, suspCallsBcis.length * 2);
                            if (count >= suspCallsSourceLines.length)
                                suspCallsSourceLines = Arrays.copyOf(suspCallsSourceLines, suspCallsSourceLines.length * 2);
                            suspCallsBcis[count] = i;
                            suspCallsSourceLines[count] = currSourceLine;
                            callSiteNames.add(getSuspendableCallName(in));
                            count++;
                        } else
                            possiblyWarnAboutBlocking(in);
                    }
                }
            }

            if (count < suspCallsSourceLines.length)
                suspCallsSourceLines = Arrays.copyOf(suspCallsSourceLines, count);
            if (count < suspCallsBcis.length)
                suspCallsBcis = Arrays.copyOf(suspCallsBcis, count);
            suspCallsNames = callSiteNames.toArray(new String[0]);
        }
    }

    private static boolean isSuspendableCall(MethodDatabase db, AbstractInsnNode in) {
        final int type = in.getType();
        String owner;
        String name;
        String desc;
        if (type == AbstractInsnNode.METHOD_INSN) {
            final MethodInsnNode min = (MethodInsnNode) in;
            owner = min.owner;
            name = min.name;
            desc = min.desc;
        } else if (type == AbstractInsnNode.INVOKE_DYNAMIC_INSN) { // invoke dynamic
            final InvokeDynamicInsnNode idd = (InvokeDynamicInsnNode) in;
            owner = idd.bsm.getOwner();
            name = idd.name;
            desc = idd.desc;
        } else {
            throw new RuntimeException("Not a call: " + in);
        }

        return isSuspendableCall(db, type, in.getOpcode(), owner, name, desc);
    }

    static boolean isSuspendableCall(MethodDatabase db, int type, int opcode, String owner, String name, String desc) {
        boolean susp = true;
        if (type == AbstractInsnNode.METHOD_INSN) {
            if (!isSyntheticAccess(owner, name)
                && !isReflectInvocation(owner, name)
                && !isMethodHandleInvocation(owner, name)
                && !isInvocationHandlerInvocation(owner, name)) {
                SuspendableType st = db.isMethodSuspendable(owner, name, desc, opcode);

                if (st == SuspendableType.NON_SUSPENDABLE)
                    susp = false;
            }
        } else if (type == AbstractInsnNode.INVOKE_DYNAMIC_INSN) { // invoke dynamic
            if (owner.equals("java/lang/invoke/LambdaMetafactory")) // lambda
                susp = false;
        } else
            susp = false;

        return susp;
    }

    private static String getSuspendableCallName(AbstractInsnNode in) {
        return (in.getType() == AbstractInsnNode.INVOKE_DYNAMIC_INSN ? "-" : getMethodOwner1(in))
               + "." + getMethodName(in) + getMethodDesc(in);
    }

    private void collectCodeBlocks() {
        final int numIns = mn.instructions.size();

        codeBlocks[0] = FrameInfo.FIRST;
        for (int i = 0; i < numIns; i++) {
            final Frame f = frames[i];
            if (f != null) { // reachable ?
                final AbstractInsnNode in = mn.instructions.get(i);
                if (in.getType() == AbstractInsnNode.METHOD_INSN || in.getType() == AbstractInsnNode.INVOKE_DYNAMIC_INSN) {
                    boolean susp = true;
                    if (in.getType() == AbstractInsnNode.METHOD_INSN) {
                        final MethodInsnNode min = (MethodInsnNode) in;
                        int opcode = min.getOpcode();
                        if (isSyntheticAccess(min.owner, min.name))
                            db.log(LogLevel.DEBUG, "Synthetic accessor method call at instruction %d is assumed suspendable", i);
                        else if (isReflectInvocation(min.owner, min.name))
                            db.log(LogLevel.DEBUG, "Reflective method call at instruction %d is assumed suspendable", i);
                        else if (isMethodHandleInvocation(min.owner, min.name))
                            db.log(LogLevel.DEBUG, "MethodHandle invocation at instruction %d is assumed suspendable", i);
                        else if (isInvocationHandlerInvocation(min.owner, min.name))
                            db.log(LogLevel.DEBUG, "InvocationHandler invocation at instruction %d is assumed suspendable", i);
                        else {
                            SuspendableType st = db.isMethodSuspendable(min.owner, min.name, min.desc, opcode);
                            if (st == SuspendableType.NON_SUSPENDABLE) {
                                susp = false;
                            } else if (st == null) {
                                db.log(LogLevel.WARNING, "Method not found in class - assuming suspendable: %s#%s%s (at %s:%s#%s)", min.owner, min.name, min.desc, sourceName, className, mn.name);
                                susp = true;
                            } else if (st != SuspendableType.SUSPENDABLE_SUPER) {
                                db.log(LogLevel.DEBUG, "Method call at instruction %d to %s#%s%s is suspendable", i, min.owner, min.name, min.desc);
                            }
                            if (st == SuspendableType.SUSPENDABLE_SUPER) {
                                db.log(LogLevel.DEBUG, "Method call at instruction %d to %s#%s%s to suspendable-super (instrumentation for proxy support will be enabled)", i, min.owner, min.name, min.desc);
                                this.callsSuspendableSupers = true;
                            }
                        }
                    } else if (in.getType() == AbstractInsnNode.INVOKE_DYNAMIC_INSN) {
                        // invoke dynamic
                        final InvokeDynamicInsnNode idin = (InvokeDynamicInsnNode) in;
                        if (idin.bsm.getOwner().equals("java/lang/invoke/LambdaMetafactory")) {
                            // lambda
                            db.log(LogLevel.DEBUG, "Lambda at instruction %d", i);
                            susp = false;
                        } else
                            db.log(LogLevel.DEBUG, "InvokeDynamic Method call at instruction %d to is assumed suspendable", i);
                    }

                    if (susp) {
                        FrameInfo fi = addCodeBlock(f, i);
                        splitTryCatch(fi);
                    } else if (in.getType() == AbstractInsnNode.METHOD_INSN) {// not invokedynamic
                        //noinspection ConstantConditions
                        final MethodInsnNode min = (MethodInsnNode) in;
                        db.log(LogLevel.DEBUG, "Method call at instruction %d to %s#%s%s is not suspendable", i, min.owner, min.name, min.desc);
                        possiblyWarnAboutBlocking(min);
                    }
                }
            }
        }
        addCodeBlock(null, numIns);
    }

    private void possiblyWarnAboutBlocking(final AbstractInsnNode ain) throws UnableToInstrumentException {
        if (ain instanceof MethodInsnNode) {
            final MethodInsnNode min = (MethodInsnNode) ain;
            int blockingId = blockingCallIdx(min);
            if (blockingId >= 0 && !isAllowedToBlock(className, mn.name)) {
                int mask = 1 << blockingId;
                if (!db.isAllowBlocking()) {
                    throw new UnableToInstrumentException("blocking call to " + min.owner + "#" + min.name + min.desc, className, mn.name, mn.desc);
                } else if ((warnedAboutBlocking & mask) == 0) {
                    warnedAboutBlocking |= mask;
                    db.log(LogLevel.WARNING, "Method %s:%s#%s%s contains potentially blocking call to " + min.owner + "#" + min.name + min.desc, sourceName, className, mn.name, mn.desc);
                }
            }
        }
    }

    public void accept(MethodVisitor mv, boolean hasAnnotation) {
        db.log(LogLevel.INFO, "Instrumenting method %s:%s#%s%s", sourceName, className, mn.name, mn.desc);

        if (mn.name.charAt(0) == '<')
            throw new UnableToInstrumentException("special method", className, mn.name, mn.desc);

        collectCallsites();
        final boolean skipInstrumentation = canInstrumentationBeSkipped(suspCallsBcis);
        emitInstrumentedAnn(db, mv, mn, sourceName, className, skipInstrumentation,
                startSourceLine, endSourceLine, suspCallsSourceLines, suspCallsNames, null);

        if (skipInstrumentation) {
            db.log(LogLevel.INFO, "[OPTIMIZE] Skipping instrumentation for method %s:%s#%s%s", sourceName, className, mn.name, mn.desc);
            mn.accept(mv); // Dump
            return;
        }

        // Else instrument
        collectCodeBlocks(); // Must be called first, sets flags & state used below

        //noinspection ConstantConditions
        final boolean handleProxyInvocations = HANDLE_PROXY_INVOCATIONS && callsSuspendableSupers;

        mv.visitCode();

        Label lMethodStart = new Label();
        Label lMethodStart2 = new Label();
        Label lMethodEnd = new Label();
        Label lCatchSEE = new Label();
        Label lCatchUTE = new Label();
        Label lCatchAll = new Label();
        Label[] lMethodCalls = new Label[numCodeBlocks - 1];
        Label[][] refInvokeTryCatch;

        for (int i = 1; i < numCodeBlocks; i++)
            lMethodCalls[i - 1] = new Label();

        mv.visitInsn(Opcodes.ACONST_NULL);
        mv.visitVarInsn(Opcodes.ASTORE, lvarInvocationReturnValue);

//        if (verifyInstrumentation) {
//            mv.visitInsn(Opcodes.ICONST_0);
//            mv.visitVarInsn(Opcodes.ISTORE, lvarSuspendableCalled);
//        }
        mv.visitTryCatchBlock(lMethodStart, lMethodEnd, lCatchSEE, SUSPEND_EXECUTION_NAME);
        mv.visitTryCatchBlock(lMethodStart, lMethodEnd, lCatchSEE, RUNTIME_SUSPEND_EXECUTION_NAME);
        if (handleProxyInvocations)
            mv.visitTryCatchBlock(lMethodStart, lMethodEnd, lCatchUTE, UNDECLARED_THROWABLE_NAME);

        // Prepare visitTryCatchBlocks for InvocationTargetException.
        // With reflective invocations, the SuspendExecution exception will be wrapped in InvocationTargetException. We need to catch it and unwrap it.
        // Note that the InvocationTargetException will be regenrated on every park, adding further overhead on top of the reflective call.
        // This must be done here, before all other visitTryCatchBlock, because the exception's handler
        // will be matched according to the order of in which visitTryCatchBlock has been called. Earlier calls take precedence.
        refInvokeTryCatch = new Label[numCodeBlocks - 1][];
        for (int i = 1; i < numCodeBlocks; i++) {
            final FrameInfo fi = codeBlocks[i];
            final AbstractInsnNode in = mn.instructions.get(fi.endInstruction);
            if (mn.instructions.get(fi.endInstruction) instanceof MethodInsnNode) {
                MethodInsnNode min = (MethodInsnNode) in;
                if (isReflectInvocation(min.owner, min.name)) {
                    Label[] ls = new Label[3];
                    for (int k = 0; k < 3; k++)
                        ls[k] = new Label();
                    refInvokeTryCatch[i - 1] = ls;
                    mv.visitTryCatchBlock(ls[0], ls[1], ls[2], "java/lang/reflect/InvocationTargetException");
                }
            }
        }

        // Output try-catch blocks
        for (final Object o : mn.tryCatchBlocks) {
            final TryCatchBlockNode tcb = (TryCatchBlockNode) o;

            if (SUSPEND_EXECUTION_NAME.equals(tcb.type) && !hasAnnotation && !mn.name.startsWith(Classes.LAMBDA_METHOD_PREFIX)) // we allow catch of SuspendExecution only in methods annotated with @Suspendable and in lambda-generated ones.
                throw new UnableToInstrumentException("catch for SuspendExecution", className, mn.name, mn.desc);
            if (handleProxyInvocations && UNDECLARED_THROWABLE_NAME.equals(tcb.type)) // we allow catch of SuspendExecution in method annotated with @Suspendable.
                throw new UnableToInstrumentException("catch for UndeclaredThrowableException", className, mn.name, mn.desc);
//          if (INTERRUPTED_EXCEPTION_NAME.equals(tcb.type))
//              throw new UnableToInstrumentException("catch for " + InterruptedException.class.getSimpleName(), className, mn.name, mn.desc);

            tcb.accept(mv);
        }

        // Output parameter annotations
        if (mn.visibleParameterAnnotations != null)
            dumpParameterAnnotations(mv, mn.visibleParameterAnnotations, true);
        if (mn.invisibleParameterAnnotations != null)
            dumpParameterAnnotations(mv, mn.invisibleParameterAnnotations, false);

        // Output method annotations
        if (mn.visibleAnnotations != null) {
            for (Object o : mn.visibleAnnotations) {
                AnnotationNode an = (AnnotationNode) o;
                an.accept(mv.visitAnnotation(an.desc, true));
            }
        }

        mv.visitTryCatchBlock(lMethodStart, lMethodEnd, lCatchAll, null);

        mv.visitMethodInsn(Opcodes.INVOKESTATIC, STACK_NAME, "getStack", "()L" + STACK_NAME + ";", false);
        mv.visitInsn(Opcodes.DUP);
        mv.visitVarInsn(Opcodes.ASTORE, lvarStack);

        // println(mv, "STACK: ", lvarStack);
        // dumpStack(mv);
        // DUAL
        mv.visitJumpInsn(Opcodes.IFNULL, lMethodStart);
        mv.visitVarInsn(Opcodes.ALOAD, lvarStack);

        emitStoreResumed(mv, true); // we'll assume we have been resumed

        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "nextMethodEntry", "()I", false);
        mv.visitTableSwitchInsn(1, numCodeBlocks - 1, lMethodStart2, lMethodCalls);

        mv.visitLabel(lMethodStart2);

        // the following code handles the case of an instrumented method called not as part of a suspendable code path
        // isFirstInStack will return false in that case.
        mv.visitVarInsn(Opcodes.ALOAD, lvarStack);
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "isFirstInStackOrPushed", "()Z", false);
        mv.visitJumpInsn(Opcodes.IFNE, lMethodStart); // if true

        // This will reset the fiber stack local if isFirstStack returns false.
        mv.visitInsn(Opcodes.ACONST_NULL);
        mv.visitVarInsn(Opcodes.ASTORE, lvarStack);

        mv.visitLabel(lMethodStart);

        emitStoreResumed(mv, false); // no, we have not been resumed

        dumpCodeBlock(mv, 0, 0);

        // Blocks leading to suspendable calls
        for (int i = 1; i < numCodeBlocks; i++) {
            final FrameInfo fi = codeBlocks[i];

            // Emit instrumented call
            final AbstractInsnNode min = mn.instructions.get(fi.endInstruction);
            final String owner = getMethodOwner(min), name = getMethodName(min), desc = getMethodDesc(min);
            if (isYieldMethod(owner, name)) { // special case - call to yield
                if (min.getOpcode() != Opcodes.INVOKESTATIC)
                    throw new UnableToInstrumentException("invalid call to suspending method.", className, mn.name, mn.desc);

                final int numYieldArgs = TypeAnalyzer.getNumArguments(desc);
                final boolean yieldReturnsValue = (Type.getReturnType(desc) != Type.VOID_TYPE);

                emitStoreState(mv, i, fi, numYieldArgs); // we preserve the arguments for the call to yield on the operand stack
                emitStoreResumed(mv, false); // we have not been resumed
                // emitSuspendableCalled(mv);

                min.accept(mv);                              // we call the yield method
                if (yieldReturnsValue)
                    mv.visitInsn(Opcodes.POP);               // we ignore the returned value...
                mv.visitLabel(lMethodCalls[i - 1]);          // we resume AFTER the call

                final Label afterPostRestore = new Label();
                mv.visitVarInsn(Opcodes.ILOAD, lvarResumed);
                mv.visitJumpInsn(Opcodes.IFEQ, afterPostRestore);
                emitPostRestore(mv);
                mv.visitLabel(afterPostRestore);

                emitRestoreState(mv, i, fi, numYieldArgs);
                if (yieldReturnsValue)
                    mv.visitVarInsn(Opcodes.ILOAD, lvarResumed); // ... and replace the returned value with the value of resumed

                // See #211: if Fiber.park() is the last call before catch, ASM generates
                // empty handlers (start_pc = end_pc) that won't pass ASM's nor JVM's bytecode checker because of
                // exception_table's spec here: https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.3
                mv.visitInsn(Opcodes.NOP);

                dumpCodeBlock(mv, i, 1 /* skip the call */);
            } else {
                final Label lbl = new Label();

                // DUAL
                mv.visitVarInsn(Opcodes.ALOAD, lvarStack);
                mv.visitJumpInsn(Opcodes.IFNULL, lbl);

                // normal case - call to a suspendable method - resume before the call
                emitStoreState(mv, i, fi, 0);
                emitStoreResumed(mv, false); // we have not been resumed
                // emitPreemptionPoint(mv, PREEMPTION_CALL);

                mv.visitLabel(lMethodCalls[i - 1]);
                emitRestoreState(mv, i, fi, 0);

                // DUAL
                mv.visitLabel(lbl);

                if (isReflectInvocation(owner, name)) {
                    // We catch the InvocationTargetException and unwrap it if it wraps a SuspendExecution exception.
                    Label[] ls = refInvokeTryCatch[i - 1];
                    final Label startTry = ls[0];
                    final Label endTry = ls[1];
                    final Label startCatch = ls[2];
                    final Label endCatch = new Label();
                    final Label notSuspendExecution = new Label();

                    // mv.visitTryCatchBlock(startTry, endTry, startCatch, "java/lang/reflect/InvocationTargetException");
                    mv.visitLabel(startTry);   // try {
                    min.accept(mv);            //   method.invoke()
                    mv.visitVarInsn(Opcodes.ASTORE, lvarInvocationReturnValue); // save return value
                    mv.visitLabel(endTry);     // }
                    mv.visitJumpInsn(Opcodes.GOTO, endCatch);
                    mv.visitLabel(startCatch); // catch(InvocationTargetException ex) {
                    mv.visitInsn(Opcodes.DUP);
                    mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable", "getCause", "()Ljava/lang/Throwable;", false);
                    mv.visitTypeInsn(Opcodes.INSTANCEOF, SUSPEND_EXECUTION_NAME);
                    mv.visitJumpInsn(Opcodes.IFEQ, notSuspendExecution);
                    mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable", "getCause", "()Ljava/lang/Throwable;", false);
                    mv.visitLabel(notSuspendExecution);
                    mv.visitInsn(Opcodes.ATHROW);
                    mv.visitLabel(endCatch);

                    mv.visitVarInsn(Opcodes.ALOAD, lvarInvocationReturnValue); // restore return value
                    dumpCodeBlock(mv, i, 1 /* skip the call */);
                } else {
                    // emitSuspendableCalled(mv);
                    dumpCodeBlock(mv, i, 0);
                }
            }
        }

        // Emit catchall's catch section
        mv.visitLabel(lMethodEnd);

        if (handleProxyInvocations) {
            mv.visitLabel(lCatchUTE);
            mv.visitInsn(Opcodes.DUP);

            // println(mv, "CTCH: ");
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable", "getCause", "()Ljava/lang/Throwable;", false);
            // println(mv, "CAUSE: ");
            mv.visitTypeInsn(Opcodes.INSTANCEOF, SUSPEND_EXECUTION_NAME);
            mv.visitJumpInsn(Opcodes.IFEQ, lCatchAll);
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable", "getCause", "()Ljava/lang/Throwable;", false);
            mv.visitJumpInsn(Opcodes.GOTO, lCatchSEE);
        }

        mv.visitLabel(lCatchAll);
        emitPopMethod(mv);
        mv.visitLabel(lCatchSEE);

        // println(mv, "THROW: ");
        mv.visitInsn(Opcodes.ATHROW);   // rethrow shared between catchAll and catchSSE

        if (mn.localVariables != null) {
            for (Object o : mn.localVariables)
                ((LocalVariableNode) o).accept(mv);
        }
        mv.visitMaxs(mn.maxStack + ADD_OPERANDS, mn.maxLocals + NUM_LOCALS + additionalLocals); // Needed by ASM analysis
        mv.visitEnd();
    }

    private boolean canInstrumentationBeSkipped(int[] susCallsIndexes) {
        if (susCallsIndexes.length == 0) {
            db.log(LogLevel.INFO, "No callsites to instrument in method %s#%s%s", className, mn.name, mn.desc);
            return true;
        }

        if (optimizationDisabled) {
            db.log(LogLevel.DEBUG, "[OPTIMIZE] Optimization disabled, not examining method %s:%s#%s%s with susCallsIndexes=%s", sourceName, className, mn.name, mn.desc, Arrays.toString(susCallsIndexes));
            return false;
        }

        db.log(LogLevel.DEBUG, "[OPTIMIZE] Examining method %s:%s#%s%s with susCallsIndexes=%s", sourceName, className, mn.name, mn.desc, Arrays.toString(susCallsIndexes));
        return isForwardingToSuspendable(susCallsIndexes); // Fully instrumentation-transparent methods
    }

    private boolean isForwardingToSuspendable(int[] susCallsBcis) {
        if (susCallsBcis.length != 1)
            return false; // we allow exactly one suspendable call

        final int susCallBci = susCallsBcis[0];

        final AbstractInsnNode susCall = mn.instructions.get(susCallBci);
        assert isSuspendableCall(db, susCall);
        if (isYieldMethod(getMethodOwner(susCall), getMethodName(susCall)))
            return false; // yield calls require instrumentation (to skip the call when resuming)
        if (isReflectInvocation(getMethodOwner(susCall), getMethodName(susCall)))
            return false; // Reflective calls require instrumentation to handle SuspendExecution wrapped in InvocationTargetException
        if (hasSuspendableTryCatchBlocksAround(susCallBci))
            return false; // Catching `SuspendableExecution needs instrumentation in order to propagate it

        // before suspendable call:
        for (int i = 0; i < susCallBci; i++) {
            final AbstractInsnNode ins = mn.instructions.get(i);

            if (ins.getType() == AbstractInsnNode.METHOD_INSN || ins.getType() == AbstractInsnNode.INVOKE_DYNAMIC_INSN)
                return false; // methods calls might have side effects
            if (ins.getType() == AbstractInsnNode.FIELD_INSN)
                return false; // side effects
            if (ins instanceof JumpInsnNode && mn.instructions.indexOf(((JumpInsnNode) ins).label) <= i)
                return false; // back branches may be costly, so we'd rather capture state
            if (!db.isAllowMonitors() && (ins.getOpcode() == Opcodes.MONITORENTER || ins.getOpcode() == Opcodes.MONITOREXIT))
                return false;  // we need collectCodeBlocks to warn about monitors
        }

        // after suspendable call
        for (int i = susCallBci + 1; i <= mn.instructions.size() - 1; i++) {
            final AbstractInsnNode ins = mn.instructions.get(i);

            if (ins instanceof JumpInsnNode && mn.instructions.indexOf(((JumpInsnNode) ins).label) <= susCallBci)
                return false; // if we jump before the suspendable call we suspend more than once -- need instrumentation
            if (!db.isAllowMonitors() && (ins.getOpcode() == Opcodes.MONITORENTER || ins.getOpcode() == Opcodes.MONITOREXIT))
                return false;  // we need collectCodeBlocks to warn about monitors
            if (!db.isAllowBlocking() && (ins instanceof MethodInsnNode && blockingCallIdx((MethodInsnNode) ins) != -1))
                return false;  // we need collectCodeBlocks to warn about blocking calls
        }
        return true;
    }

    private boolean hasSuspendableTryCatchBlocksAround(int bci) {
        //noinspection unchecked
        for (final TryCatchBlockNode tcb : (List) mn.tryCatchBlocks) {
            if (mn.instructions.indexOf(tcb.start) <= bci && mn.instructions.indexOf(tcb.end) >= bci
                && (THROWABLE_NAME.equals(tcb.type)
                    || EXCEPTION_NAME.equals(tcb.type)
                    || RUNTIME_EXCEPTION_NAME.equals(tcb.type)
                    || RUNTIME_SUSPEND_EXECUTION_NAME.equals(tcb.type)
                    || SUSPEND_EXECUTION_NAME.equals(tcb.type)))
                return true;
        }
        return false;
    }

    static void emitInstrumentedAnn(
            MethodDatabase db, MethodVisitor mv, MethodNode mn, String sourceName, String className,
            boolean skip, int startSourceLine, int endSourceLine,
            int[] suspCallsSourceLines, String[] instrumentedCalls, int[] postInstrOffsets) {

        final StringBuilder sb = db.isDebug() ? new StringBuilder() : null;
        final AnnotationVisitor instrumentedAV = mv.visitAnnotation(INSTRUMENTED_DESC, true);
        if (sb != null)
            sb.append("@").append(Instrumented.class.getSimpleName()).append("(");

        instrumentedAV.visit(Instrumented.FIELD_NAME_METHOD_OPTIMIZED, skip);
        if (sb != null)
            sb.append(Instrumented.FIELD_NAME_METHOD_OPTIMIZED + "=").append(skip).append(", ");

        instrumentedAV.visit(Instrumented.FIELD_NAME_METHOD_START, startSourceLine);
        if (sb != null)
            sb.append(Instrumented.FIELD_NAME_METHOD_START + "=").append(startSourceLine).append(", ");

        instrumentedAV.visit(Instrumented.FIELD_NAME_METHOD_END, endSourceLine);
        if (sb != null)
            sb.append(Instrumented.FIELD_NAME_METHOD_END + "=").append(endSourceLine).append(", ");

        if (suspCallsSourceLines != null) {
            final AnnotationVisitor linesAV = instrumentedAV.visitArray(Instrumented.FIELD_NAME_SUSPENDABLE_CALL_SITES);
            if (sb != null)
                sb.append(Instrumented.FIELD_NAME_SUSPENDABLE_CALL_SITES + "=[");
            for (int i = 0; i < suspCallsSourceLines.length; i++) {
                if (sb != null && i != 0)
                    sb.append(", ");

                final int l = suspCallsSourceLines[i];
                linesAV.visit(null, l);
                if (sb != null)
                    sb.append(l);
            }
            linesAV.visitEnd();
            if (sb != null)
                sb.append("], ");
        }

        if (instrumentedCalls != null) {
            final AnnotationVisitor instrumentedCallsAV = instrumentedAV.visitArray(Instrumented.FIELD_NAME_SUSPENDABLE_CALL_SITE_NAMES);
            if (sb != null)
                sb.append(Instrumented.FIELD_NAME_SUSPENDABLE_CALL_SITE_NAMES + "=[");
            for (int i = 0; i < instrumentedCalls.length; i++) {
                if (sb != null && i != 0)
                    sb.append(", ");

                final String s = instrumentedCalls[i];
                instrumentedCallsAV.visit(null, s);
                if (sb != null)
                    sb.append(s);
            }
            instrumentedCallsAV.visitEnd();
            if (sb != null)
                sb.append("], ");
        }

        if (postInstrOffsets != null) {
            final AnnotationVisitor postInstrOffsetsAV = instrumentedAV.visitArray(Instrumented.FIELD_NAME_SUSPENDABLE_CALL_SITES_OFFSETS_AFTER_INSTR);
            if (sb != null)
                sb.append(Instrumented.FIELD_NAME_SUSPENDABLE_CALL_SITES_OFFSETS_AFTER_INSTR + "=[");
            for (int i = 0; i < postInstrOffsets.length; i++) {
                if (sb != null && i != 0)
                    sb.append(", ");

                final int l = postInstrOffsets[i];
                postInstrOffsetsAV.visit(null, l);
                if (sb != null)
                    sb.append(l);
            }
            postInstrOffsetsAV.visitEnd();
            if (sb != null)
                sb.append("]");
        }

        instrumentedAV.visitEnd();
        if (sb != null)
            sb.append(")");

        db.log(LogLevel.DEBUG, "Annotating method %s:%s#%s%s with %s", sourceName, className, mn.name, mn.desc, sb);
    }

    /*
    private void dumpStack(MethodVisitor mv) {
        mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Thread", "dumpStack", "()V", false);
    }
     */
    private FrameInfo addCodeBlock(Frame f, int end) {
        if (++numCodeBlocks == codeBlocks.length) {
            FrameInfo[] newArray = new FrameInfo[numCodeBlocks * 2];
            System.arraycopy(codeBlocks, 0, newArray, 0, codeBlocks.length);
            codeBlocks = newArray;
        }
        FrameInfo fi = new FrameInfo(f, firstLocal, end, mn.instructions, db);
        codeBlocks[numCodeBlocks] = fi;
        this.maxRefSlots = Math.max(maxRefSlots, fi.numObjSlots);
        return fi;
    }

    private void emitStoreResumed(MethodVisitor mv, boolean value) {
        mv.visitInsn(value ? Opcodes.ICONST_1 : Opcodes.ICONST_0);
        mv.visitVarInsn(Opcodes.ISTORE, lvarResumed);
    }

    private int getLabelIdx(LabelNode l) {
        int idx;
        if (l instanceof BlockLabelNode) {
            idx = ((BlockLabelNode) l).idx;
        } else {
            idx = mn.instructions.indexOf(l);
        }

        // search for the "real" instruction
        for (;;) {
            int type = mn.instructions.get(idx).getType();
            if (type != AbstractInsnNode.LABEL && type != AbstractInsnNode.LINE) {
                return idx;
            }
            idx++;
        }
    }

    @SuppressWarnings("unchecked")
    private void splitTryCatch(FrameInfo fi) {
        for (int i = 0; i < mn.tryCatchBlocks.size(); i++) {
            TryCatchBlockNode tcb = (TryCatchBlockNode) mn.tryCatchBlocks.get(i);

            int start = getLabelIdx(tcb.start);
            int end = getLabelIdx(tcb.end);

            if (start <= fi.endInstruction && end >= fi.endInstruction) {
                db.log(LogLevel.DEBUG, "Splitting try-catch in %s, block %d call at instruction %d", mn.name, i, fi.endInstruction);
                //System.out.println("i="+i+" start="+start+" end="+end+" split="+splitIdx+
                //        " start="+mn.instructions.get(start)+" end="+mn.instructions.get(end));

                // need to split try/catch around the suspendable call
                if (start == fi.endInstruction) {
                    // Starts exactly at the suspendable call => just make it start after it
                    tcb.start = fi.createAfterLabel();
                } else {
                    if (end > fi.endInstruction) {
                        TryCatchBlockNode tcb2 = new TryCatchBlockNode(
                                fi.createAfterLabel(),
                                tcb.end, tcb.handler, tcb.type);
                        mn.tryCatchBlocks.add(i + 1, tcb2);
                    }

                    // Make it end before the suspendable call
                    tcb.end = fi.createBeforeLabel();
                }
            }
        }
    }

    private void dumpCodeBlock(MethodVisitor mv, int idx, int skip) {
        int start = codeBlocks[idx].endInstruction;
        int end = codeBlocks[idx + 1].endInstruction;

        for (int i = start + skip; i < end; i++) {
            AbstractInsnNode ins = mn.instructions.get(i);

            switch (ins.getOpcode()) {
                case Opcodes.RETURN:
                case Opcodes.ARETURN:
                case Opcodes.IRETURN:
                case Opcodes.LRETURN:
                case Opcodes.FRETURN:
                case Opcodes.DRETURN:
                    emitPopMethod(mv);
                    break;

                case Opcodes.MONITORENTER:
                case Opcodes.MONITOREXIT:
                    if (!db.isAllowMonitors()) {
                        if (!className.equals("clojure/lang/LazySeq"))
                            throw new UnableToInstrumentException("synchronization", className, mn.name, mn.desc);
                    } else if (!warnedAboutMonitors) {
                        warnedAboutMonitors = true;
                        db.log(LogLevel.WARNING, "Method %s#%s%s contains synchronization", className, mn.name, mn.desc);
                    }
                    break;

                case Opcodes.INVOKESPECIAL:
                    MethodInsnNode min = (MethodInsnNode) ins;
                    if ("".equals(min.name)) {
                        int argSize = TypeAnalyzer.getNumArguments(min.desc);
                        Frame frame = frames[i];
                        int stackIndex = frame.getStackSize() - argSize - 1;
                        Value thisValue = frame.getStack(stackIndex);
                        if (stackIndex >= 1
                            && isNewValue(thisValue, true)
                            && isNewValue(frame.getStack(stackIndex - 1), false)) {
                            if (isOmitted((NewValue) thisValue))
                                emitNewAndDup(mv, frame, stackIndex, min); // explanation in emitNewAndDup
                        } else {
                            db.log(LogLevel.WARNING, "Expected to find a NewValue on stack index %d: %s", stackIndex, frame);
                        }
                    }
                    break;
            }

            ins.accept(mv);
        }
    }

    private static void dumpParameterAnnotations(MethodVisitor mv, List[] parameterAnnotations, boolean visible) {
        for (int i = 0; i < parameterAnnotations.length; i++) {
            if (parameterAnnotations[i] != null) {
                for (Object o : parameterAnnotations[i]) {
                    AnnotationNode an = (AnnotationNode) o;
                    an.accept(mv.visitParameterAnnotation(i, an.desc, visible));
                }
            }
        }
    }

    private static void emitConst(MethodVisitor mv, int value) {
        if (value >= -1 && value <= 5)
            mv.visitInsn(Opcodes.ICONST_0 + value);
        else if ((byte) value == value)
            mv.visitIntInsn(Opcodes.BIPUSH, value);
        else if ((short) value == value)
            mv.visitIntInsn(Opcodes.SIPUSH, value);
        else
            mv.visitLdcInsn(value);
    }

    /*
    private static void emitConst(MethodVisitor mv, String value) {
        mv.visitLdcInsn(value);
    }
     */
    private void emitNewAndDup(MethodVisitor mv, Frame frame, int stackIndex, MethodInsnNode min) {
        /*
         * This method, and the entire NewValue business has to do with dealing with the following case:
         * 
         *   new Foo(suspendableCall())
         *
         * I.e. when the suspendable call is passed as an argument to the constructor. The emitted code may be:
         *
         *   NEW Foo
         *   DUP
         *   INVOKEVIRTUAL suspendableCall
         *   INVOKESPECIAL Foo.
         *
         * Which means that the suspension points is after NEW, leaving the object in an uninitialized state which the verifier rejects.
         * This method rewrites it to be:
         *
         *   INVOKEVIRTUAL suspendableCall
         *   ASTORE X
         *   NEW Foo
         *   DUP
         *   ALOAD X
         *   INVOKESPECIAL Foo.
         *
         */
        int arguments = frame.getStackSize() - stackIndex - 1;
        int neededLocals = 0;
        for (int i = arguments; i >= 1; i--) {
            BasicValue v = (BasicValue) frame.getStack(stackIndex + i);
            mv.visitVarInsn(v.getType().getOpcode(Opcodes.ISTORE), lvarStack + NUM_LOCALS + neededLocals);
            neededLocals += v.getSize();
        }
        if (additionalLocals < neededLocals)
            additionalLocals = neededLocals;

        db.log(LogLevel.DEBUG, "Inserting NEW & DUP for constructor call %s%s with %d arguments (%d locals)", min.owner, min.desc, arguments, neededLocals);
        ((NewValue) frame.getStack(stackIndex - 1)).insn.accept(mv);
        ((NewValue) frame.getStack(stackIndex)).insn.accept(mv);

        for (int i = 1; i <= arguments; i++) {
            BasicValue v = (BasicValue) frame.getStack(stackIndex + i);
            neededLocals -= v.getSize();
            mv.visitVarInsn(v.getType().getOpcode(Opcodes.ILOAD), lvarStack + NUM_LOCALS + neededLocals);
        }
    }

    private void emitPopMethod(MethodVisitor mv) {
//        emitVerifyInstrumentation(mv);

        final Label lbl = new Label();
        // DUAL
        mv.visitVarInsn(Opcodes.ALOAD, lvarStack);
        mv.visitJumpInsn(Opcodes.IFNULL, lbl);

        mv.visitVarInsn(Opcodes.ALOAD, lvarStack);
        emitConst(mv, maxRefSlots);
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "popMethod", "(I)V", false);

        // DUAL
        mv.visitLabel(lbl);
    }

    private void emitStoreState(MethodVisitor mv, int idx, FrameInfo fi, int numArgsToPreserve) {
        if (idx > Stack.MAX_ENTRY)
            throw new IllegalArgumentException("Entry index (PC) " + idx + " greater than maximum of " + Stack.MAX_ENTRY + " in " + className + "." + mn.name + mn.desc);
        if (fi.numSlots > Stack.MAX_SLOTS)
            throw new IllegalArgumentException("Number of slots required " + fi.numSlots + " greater than maximum of " + Stack.MAX_SLOTS + " in " + className + "." + mn.name + mn.desc);

        Frame f = frames[fi.endInstruction];

        if (fi.lBefore != null)
            fi.lBefore.accept(mv);

        mv.visitVarInsn(Opcodes.ALOAD, lvarStack);
        emitConst(mv, idx);
        emitConst(mv, fi.numSlots);
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "pushMethod", "(II)V", false);

        // store operand stack
        for (int i = f.getStackSize(); i-- > 0;) {
            BasicValue v = (BasicValue) f.getStack(i);
            if (!isOmitted(v)) {
                if (!isNullType(v)) {
                    int slotIdx = fi.stackSlotIndices[i];
                    assert slotIdx >= 0 && slotIdx < fi.numSlots;
                    emitStoreValue(mv, v, lvarStack, slotIdx, -1);
                } else {
                    db.log(LogLevel.DEBUG, "NULL stack entry: type=%s size=%d", v.getType(), v.getSize());
                    mv.visitInsn(Opcodes.POP);
                }
            }
        }

        // store local vars
        for (int i = firstLocal; i < f.getLocals(); i++) {
            BasicValue v = (BasicValue) f.getLocal(i);
            if (!isNullType(v)) {
                mv.visitVarInsn(v.getType().getOpcode(Opcodes.ILOAD), i);
                int slotIdx = fi.localSlotIndices[i];
                assert slotIdx >= 0 && slotIdx < fi.numSlots;
                emitStoreValue(mv, v, lvarStack, slotIdx, i);
            }
        }

        // restore last numArgsToPreserve operands
        for (int i = f.getStackSize() - numArgsToPreserve; i < f.getStackSize(); i++) {
            BasicValue v = (BasicValue) f.getStack(i);
            if (!isOmitted(v)) {
                if (!isNullType(v)) {
                    int slotIdx = fi.stackSlotIndices[i];
                    assert slotIdx >= 0 && slotIdx < fi.numSlots;
                    emitRestoreValue(mv, v, lvarStack, slotIdx, -1);
                } else
                    mv.visitInsn(Opcodes.ACONST_NULL);
            }
        }
    }

    private void emitRestoreState(MethodVisitor mv, @SuppressWarnings("UnusedParameters") int idx, FrameInfo fi, int numArgsPreserved) {
        Frame f = frames[fi.endInstruction];

        // restore local vars
        for (int i = firstLocal; i < f.getLocals(); i++) {
            BasicValue v = (BasicValue) f.getLocal(i);
            if (!isNullType(v)) {
                int slotIdx = fi.localSlotIndices[i];
                assert slotIdx >= 0 && slotIdx < fi.numSlots;
                emitRestoreValue(mv, v, lvarStack, slotIdx, i);
                mv.visitVarInsn(v.getType().getOpcode(Opcodes.ISTORE), i);
            } else if (v != BasicValue.UNINITIALIZED_VALUE) {
                mv.visitInsn(Opcodes.ACONST_NULL);
                mv.visitVarInsn(Opcodes.ASTORE, i);
            }
        }

        // restore operand stack
        for (int i = 0; i < f.getStackSize() - numArgsPreserved; i++) {
            BasicValue v = (BasicValue) f.getStack(i);
            if (!isOmitted(v)) {
                if (!isNullType(v)) {
                    int slotIdx = fi.stackSlotIndices[i];
                    assert slotIdx >= 0 && slotIdx < fi.numSlots;
                    emitRestoreValue(mv, v, lvarStack, slotIdx, -1);
                } else
                    mv.visitInsn(Opcodes.ACONST_NULL);
            }
        }

        if (fi.lAfter != null)
            fi.lAfter.accept(mv);
    }

    private void emitPostRestore(MethodVisitor mv) {
        mv.visitVarInsn(Opcodes.ALOAD, lvarStack);
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "postRestore", "()V", false);
    }

    /*
    private void emitPreemptionPoint(MethodVisitor mv, int type) {
        mv.visitVarInsn(Opcodes.ALOAD, lvarStack);
        switch (type) {
            case 0:
                mv.visitInsn(Opcodes.ICONST_0);
                break;
            case 1:
                mv.visitInsn(Opcodes.ICONST_1);
                break;
            case 2:
                mv.visitInsn(Opcodes.ICONST_2);
                break;
            case 3:
                mv.visitInsn(Opcodes.ICONST_3);
                break;
            default:
                throw new AssertionError("Unsupported type: " + type);
        }
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "preemptionPoint", "(I)V", false);
    }
     */
    private void emitStoreValue(MethodVisitor mv, BasicValue v, int lvarStack, int idx, @SuppressWarnings("UnusedParameters") int lvar) throws InternalError, IndexOutOfBoundsException {
        String desc;

        switch (v.getType().getSort()) {
            case Type.OBJECT:
            case Type.ARRAY:
                desc = "(Ljava/lang/Object;L" + STACK_NAME + ";I)V";
                break;
            case Type.BOOLEAN:
            case Type.BYTE:
            case Type.SHORT:
            case Type.CHAR:
            case Type.INT:
                desc = "(IL" + STACK_NAME + ";I)V";
                break;
            case Type.FLOAT:
                desc = "(FL" + STACK_NAME + ";I)V";
                break;
            case Type.LONG:
                desc = "(JL" + STACK_NAME + ";I)V";
                break;
            case Type.DOUBLE:
                desc = "(DL" + STACK_NAME + ";I)V";
                break;
            default:
                throw new InternalError("Unexpected type: " + v.getType());
        }

        mv.visitVarInsn(Opcodes.ALOAD, lvarStack);
//        if (v.getType().getSort() == Type.OBJECT || v.getType().getSort() == Type.ARRAY)
//            println(mv, "STORE " + (lvar >= 0 ? ("VAR " + lvar + ": ") : "OPRND: "));
        emitConst(mv, idx);
        mv.visitMethodInsn(Opcodes.INVOKESTATIC, STACK_NAME, "push", desc, false);
    }

    private void emitRestoreValue(MethodVisitor mv, BasicValue v, int lvarStack, int idx, @SuppressWarnings("UnusedParameters") int lvar) {
        mv.visitVarInsn(Opcodes.ALOAD, lvarStack);
        emitConst(mv, idx);

        switch (v.getType().getSort()) {
            case Type.OBJECT:
                String internalName = v.getType().getInternalName();
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "getObject", "(I)Ljava/lang/Object;", false);
                if (!internalName.equals("java/lang/Object"))  // don't cast to Object ;)
                    mv.visitTypeInsn(Opcodes.CHECKCAST, internalName);
//                println(mv, "RESTORE " + (lvar >= 0 ? ("VAR " + lvar + ": ") : "OPRND: "));
                break;
            case Type.ARRAY:
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "getObject", "(I)Ljava/lang/Object;", false);
                mv.visitTypeInsn(Opcodes.CHECKCAST, v.getType().getDescriptor());
                break;
            case Type.BYTE:
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "getInt", "(I)I", false);
                mv.visitInsn(Opcodes.I2B);
                break;
            case Type.SHORT:
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "getInt", "(I)I", false);
                mv.visitInsn(Opcodes.I2S);
                break;
            case Type.CHAR:
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "getInt", "(I)I", false);
                mv.visitInsn(Opcodes.I2C);
                break;
            case Type.BOOLEAN:
            case Type.INT:
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "getInt", "(I)I", false);
                break;
            case Type.FLOAT:
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "getFloat", "(I)F", false);
                break;
            case Type.LONG:
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "getLong", "(I)J", false);
                break;
            case Type.DOUBLE:
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "getDouble", "(I)D", false);
                break;
            default:
                throw new InternalError("Unexpected type: " + v.getType());
        }
    }

    private static boolean isNullType(BasicValue v) {
        return (v == BasicValue.UNINITIALIZED_VALUE)
               || (v.isReference() && v.getType().getInternalName().equals("null"));
    }

    private static boolean isOmitted(BasicValue v) {
        return v instanceof NewValue && ((NewValue) v).omitted;
    }

    private static boolean isNewValue(Value v, boolean dupped) {
        return v instanceof NewValue && ((NewValue) v).isDupped == dupped;
    }

    private static String getMethodOwner(AbstractInsnNode min) {
        return min instanceof MethodInsnNode ? ((MethodInsnNode) min).owner : null;
    }

    private static String getMethodOwner1(AbstractInsnNode min) {
        return min instanceof MethodInsnNode ? ((MethodInsnNode) min).owner
                : min instanceof InvokeDynamicInsnNode ? ((InvokeDynamicInsnNode) min).bsm.getOwner()
                        : null;
    }

    private static String getMethodName(AbstractInsnNode min) {
        return min instanceof MethodInsnNode ? ((MethodInsnNode) min).name
                : min instanceof InvokeDynamicInsnNode ? ((InvokeDynamicInsnNode) min).name
                        : null;
    }

    private static String getMethodDesc(AbstractInsnNode min) {
        return min instanceof MethodInsnNode ? ((MethodInsnNode) min).desc
                : min instanceof InvokeDynamicInsnNode ? ((InvokeDynamicInsnNode) min).desc
                        : null;
    }

    private static class OmittedInstruction extends AbstractInsnNode {
        private final AbstractInsnNode orgInsn;

        OmittedInstruction(AbstractInsnNode orgInsn) {
            super(orgInsn.getOpcode());
            this.orgInsn = orgInsn;
        }

        @Override
        public int getType() {
            return orgInsn.getType();
        }

        @Override
        public void accept(MethodVisitor cv) {
        }

        @Override
        public AbstractInsnNode clone(Map labels) {
            return new OmittedInstruction(orgInsn.clone(labels));
        }
    }

    private static class BlockLabelNode extends LabelNode {
        final int idx;

        BlockLabelNode(int idx) {
            this.idx = idx;
        }
    }

    private static class FrameInfo {
        static final FrameInfo FIRST = new FrameInfo(null, 0, 0, null, null);
        final int endInstruction;
        final int numSlots;
        final int numObjSlots;
        final int[] localSlotIndices;
        final int[] stackSlotIndices;
        BlockLabelNode lBefore;
        BlockLabelNode lAfter;

        FrameInfo(Frame f, int firstLocal, int endInstruction, InsnList insnList, MethodDatabase db) {
            this.endInstruction = endInstruction;

            int idxObj = 0;
            int idxPrim = 0;

            if (f != null) {
                stackSlotIndices = new int[f.getStackSize()];
                for (int i = 0; i < f.getStackSize(); i++) {
                    BasicValue v = (BasicValue) f.getStack(i);
                    if (v instanceof NewValue) {
                        // explanation in emitNewAndDup
                        NewValue newValue = (NewValue) v;
                        if (db.isDebug())
                            db.log(LogLevel.DEBUG, "Omit value from stack idx %d at instruction %d with type %s generated by %s", i, endInstruction, v, newValue.formatInsn());
                        if (!newValue.omitted) {
                            newValue.omitted = true;
                            if (db.isDebug())
                                db.log(LogLevel.DEBUG, "Omitting instruction %d: %s", insnList.indexOf(newValue.insn), newValue.formatInsn()); // // log index before replacing instruction
                            insnList.set(newValue.insn, new OmittedInstruction(newValue.insn));
                        }
                        stackSlotIndices[i] = -666; // an invalid index ;)
                    } else if (!isNullType(v)) {
                        if (v.isReference())
                            stackSlotIndices[i] = idxObj++;
                        else
                            stackSlotIndices[i] = idxPrim++;
                    } else {
                        stackSlotIndices[i] = -666; // an invalid index ;)
                    }
                }

                localSlotIndices = new int[f.getLocals()];
                for (int i = firstLocal; i < f.getLocals(); i++) {
                    BasicValue v = (BasicValue) f.getLocal(i);
                    if (!isNullType(v)) {
                        if (v.isReference())
                            localSlotIndices[i] = idxObj++;
                        else
                            localSlotIndices[i] = idxPrim++;
                    } else
                        localSlotIndices[i] = -666; // an invalid index ;)
                }
            } else {
                stackSlotIndices = null;
                localSlotIndices = null;
            }

            numSlots = Math.max(idxPrim, idxObj);
            numObjSlots = idxObj;
        }

        LabelNode createBeforeLabel() {
            if (lBefore == null)
                lBefore = new BlockLabelNode(endInstruction);
            return lBefore;
        }

        LabelNode createAfterLabel() {
            if (lAfter == null)
                lAfter = new BlockLabelNode(endInstruction);
            return lAfter;
        }
    }

    /*
    // prints a local var
    private void println(MethodVisitor mv, String prefix, int refVar) {
        mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;");
        mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
        mv.visitInsn(Opcodes.DUP);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "", "()V", false);
        mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Thread", "currentThread", "()Ljava/lang/Thread;", false);
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Thread", "getName", "()Ljava/lang/String;", false);
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
        mv.visitLdcInsn(" " + prefix);
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
        mv.visitLdcInsn(" var " + refVar + ":");
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);

        mv.visitVarInsn(Opcodes.ALOAD, refVar);

        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/Object;)Ljava/lang/StringBuilder;", false);
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
    }

    // prints the value at the top of the operand stack
    private void println(MethodVisitor mv, String prefix) {
        mv.visitInsn(Opcodes.DUP); // S1 S1

        mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;"); // PrintStream S1 S1

        mv.visitInsn(Opcodes.SWAP); // S1 PrintStream S1

        mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder"); // StringBuilder S1 PrintStream S1
        mv.visitInsn(Opcodes.DUP); // StringBuilder StringBuilder S1 PrintStream S1
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "", "()V", false); // StringBuilder S1 PrintStream S1
        mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Thread", "currentThread", "()Ljava/lang/Thread;", false);
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Thread", "getName", "()Ljava/lang/String;", false);
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
        mv.visitLdcInsn(" " + prefix); // prefix StringBuilder S1 PrintStream S1
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false); // StringBuilder S1 PrintStream S1

        mv.visitInsn(Opcodes.SWAP); // S1 StringBuilder PrintStream S1

        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/Object;)Ljava/lang/StringBuilder;", false); // StringBuilder PrintStream S1
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false); // PrintStream S1
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); // S1
    }
     */
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy