Package nginx.clojure.wave

Source Code of nginx.clojure.wave.InstrumentMethod

/*
* Copyright (c) 2008-2013, Matthias Mann
* Copyright (C) 2014 Zhang,Yuexiang (xfeep)
* 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 nginx.clojure.wave;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import nginx.clojure.Stack;
import nginx.clojure.SuspendExecution;
import nginx.clojure.asm.Label;
import nginx.clojure.asm.MethodVisitor;
import nginx.clojure.asm.Opcodes;
import nginx.clojure.asm.Type;
import nginx.clojure.asm.tree.AbstractInsnNode;
import nginx.clojure.asm.tree.AnnotationNode;
import nginx.clojure.asm.tree.InsnList;
import nginx.clojure.asm.tree.LabelNode;
import nginx.clojure.asm.tree.LocalVariableNode;
import nginx.clojure.asm.tree.MethodInsnNode;
import nginx.clojure.asm.tree.MethodNode;
import nginx.clojure.asm.tree.TryCatchBlockNode;
import nginx.clojure.asm.tree.analysis.Analyzer;
import nginx.clojure.asm.tree.analysis.AnalyzerException;
import nginx.clojure.asm.tree.analysis.BasicValue;
import nginx.clojure.asm.tree.analysis.Frame;
import nginx.clojure.asm.tree.analysis.Value;
import nginx.clojure.wave.MethodDatabase.ClassEntry;
import nginx.clojure.wave.SuspendMethodVerifier.VerifyVarInfo;

/**
* Instrument a method to allow suspension
*
* @author Matthias Mann
* @author Zhang,Yuexiang (xfeep)
*/
public class InstrumentMethod {

    private static final String STACK_NAME = Type.getInternalName(Stack.class);

  private static final String STACK_PUSH_OBJECT_VALUE_DESC = "(Ljava/lang/Object;L"+STACK_NAME+";I)V";

  private static final String STACK_PUSHV_OBJECT_VALUE_DESC = "(Ljava/lang/Object;L"+STACK_NAME +";ILjava/lang/String;)V";

  private static final String STACK_PUSH_DOUBLE_VALUE_DESC = "(DL"+STACK_NAME+";I)V";

  private static final String STACK_PUSHV_DOUBLE_VALUE_DESC = "(DL"+STACK_NAME + ";ILjava/lang/String;)V";

  private static final String STACK_PUSH_LONG_VALUE_DESC = "(JL"+STACK_NAME+";I)V";

  private static final String STACK_PUSHV_LONG_VALUE_DESC = "(JL"+STACK_NAME  + ";ILjava/lang/String;)V";

  private static final String STACK_PUSH_FLOAT_VALUE_DESC = "(FL"+STACK_NAME+";I)V";

  private static final String STACK_PUSHV_FLOAT_VALUE_DESC = "(FL"+STACK_NAME  + ";ILjava/lang/String;)V";

  private static final String STACK_PUSH_INT_VALUE_DESC = "(IL"+STACK_NAME+";I)V";

  private static final String STACK_PUSHV_INT_VALUE_DESC = "(IL"+STACK_NAME + ";ILjava/lang/String;)V";
   
    private final MethodDatabase db;
    private final String className;
    private final String classAndMethod;
    private final MethodNode mn;
    private final Frame[] frames;
    private final int lvarStack;
    private final int firstLocal;
   
    private FrameInfo[] codeBlocks = new FrameInfo[32];
    private int numCodeBlocks;
    private int additionalLocals;
   
    private boolean warnedAboutMonitors;
    private boolean warnedAboutBlocking;
   
    private boolean hasReflectInvoke;
   
    private Set<LabelNode> reflectExceptionHandlers;
   
  private final static Set<String> REFLECT_EXCEPTION_SET = new HashSet<String>(
      Arrays.asList("java/lang/reflect/InvocationTargetException",
          "java/lang/reflect/ReflectiveOperationException",
          "java/lang/Exception",
          "java/lang/Throwable"));
 
  private VerifyVarInfo[][] verifyVarInfoss;
   
    public InstrumentMethod(MethodDatabase db, String className, MethodNode mn) throws AnalyzerException {
        this.db = db;
        this.className = className;
        this.mn = mn;
        this.classAndMethod = className + "." +  mn.name + mn.desc;
        try {
            Analyzer a = MethodDatabaseUtil.buildAnalyzer(db);
            this.frames = a.analyze(className, mn);
            this.lvarStack = mn.maxLocals;
            this.firstLocal = ((mn.access & Opcodes.ACC_STATIC) == Opcodes.ACC_STATIC) ? 0 : 1;
        } catch (UnsupportedOperationException ex) {
            throw new AnalyzerException(null, ex.getMessage(), ex);
        }
    }
   
    public boolean collectCodeBlocks() {
        int numIns = mn.instructions.size();
       
        codeBlocks[0] = FrameInfo.FIRST;
        for(int i=0 ; i<numIns ; i++) {
            Frame f = frames[i];
            if(f != null) { // reachable ?
                AbstractInsnNode in = mn.instructions.get(i);
                if(in.getType() == AbstractInsnNode.METHOD_INSN) {
                    MethodInsnNode min = (MethodInsnNode)in;
                    int opcode = min.getOpcode();
                    if (min.owner.equals("java/lang/reflect/Method") && min.name.equals("invoke")) {
                      hasReflectInvoke = true;
                    }
                    Integer st = db.checkMethodSuspendType(min.owner, ClassEntry.key(min.name, min.desc), opcode == Opcodes.INVOKEVIRTUAL || opcode == Opcodes.INVOKESTATIC || opcode == Opcodes.INVOKEINTERFACE);
                    if(st == MethodDatabase.SUSPEND_NORMAL || st == MethodDatabase.SUSPEND_FAMILY || st == MethodDatabase.SUSPEND_JUST_MARK) {
                        db.trace("Method call at instruction %d to %s#%s%s is suspendable", i, min.owner, min.name, min.desc);
                        FrameInfo fi = addCodeBlock(f, i);
                        splitTryCatch(fi);
                    } else {
                        if(st == MethodDatabase.SUSPEND_BLOCKING) {
                            if(!db.isAllowBlocking()) {
                                throw new UnableToInstrumentException("blocking call to " +
                                        min.owner + "#" + min.name + min.desc, className, mn.name, mn.desc);
                            } else  {
                                warnedAboutBlocking = true;
                                db.warn("Method %s#%s%s contains potentially blocking call to " +
                                        min.owner + "#" + min.name + min.desc, className, mn.name, mn.desc);
                            }
                        }
                    }
                }
            }
        }
        addCodeBlock(null, numIns);
       
        return numCodeBlocks > 1;
    }
   
   
    public void accept(MethodVisitor mv) {
        db.trace("Instrumenting method %s.%s%s", className, mn.name, mn.desc);
        if (db.isDebug() && db.meetTraceTargetClassMethod(classAndMethod)) {
          db.info("Instrumenting meet traced method %s.%s%s", className, mn.name, mn.desc);
        }
       
        if (db.isVerify()) {
          verifyVarInfoss = new VerifyVarInfo[numCodeBlocks-1][];
        }
       
        mv.visitCode();
       
        Label lMethodStart = new Label();
        Label lMethodEnd = new Label();
        Label lCatchSEE = new Label();
        Label lCatchAll = new Label();
        Label[] lMethodCalls = new Label[numCodeBlocks-1];
       
        for(int i=1 ; i<numCodeBlocks ; i++) {
            lMethodCalls[i-1] = new Label();
        }
       
        mv.visitTryCatchBlock(lMethodStart, lMethodEnd, lCatchSEE, CheckInstrumentationVisitor.EXCEPTION_NAME);
       
       
        for(TryCatchBlockNode tcb : mn.tryCatchBlocks) {
            if (hasReflectInvoke && REFLECT_EXCEPTION_SET.contains(tcb.type)) {
              if (reflectExceptionHandlers == null){
                reflectExceptionHandlers = new HashSet<LabelNode>();
              }
              reflectExceptionHandlers.add(tcb.handler);
            }
            if(CheckInstrumentationVisitor.EXCEPTION_NAME.equals(tcb.type)) {
                throw new UnableToInstrumentException("catch for " +
                        SuspendExecution.class.getSimpleName(), className, mn.name, mn.desc);
            }
            tcb.accept(mv);
        }
       
        if(mn.visibleParameterAnnotations != null) {
            dumpParameterAnnotations(mv, mn.visibleParameterAnnotations, true);
        }
       
        if(mn.invisibleParameterAnnotations != null) {
            dumpParameterAnnotations(mv, mn.invisibleParameterAnnotations, false);
        }
       
        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+";");
        mv.visitInsn(Opcodes.DUP);
        mv.visitVarInsn(Opcodes.ASTORE, lvarStack);
       
        if (db.isAllowOutofCoroutine()) {
            mv.visitJumpInsn(Opcodes.IFNULL, lMethodStart);
            mv.visitVarInsn(Opcodes.ALOAD, lvarStack);
        }
       
        if (verifyVarInfoss != null) {
          mv.visitLdcInsn(classAndMethod);
          mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "nextMethodEntryV", "(Ljava/lang/String;)I");
        }else {
          mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "nextMethodEntry", "()I");
        }
       
        mv.visitInsn(Opcodes.DUP);
        Label tableSwitchLabel = new Label();
        mv.visitJumpInsn(Opcodes.IFGE, tableSwitchLabel);
        mv.visitInsn(Opcodes.POP);
        mv.visitInsn(Opcodes.ACONST_NULL);
        mv.visitVarInsn(Opcodes.ASTORE, lvarStack);
        mv.visitJumpInsn(Opcodes.GOTO, lMethodStart);
       
        mv.visitLabel(tableSwitchLabel);
        mv.visitTableSwitchInsn(1, numCodeBlocks-1, lMethodStart, lMethodCalls);
       
        mv.visitLabel(lMethodStart);
        dumpCodeBlock(mv, 0, 0);
       
        for(int i=1 ; i<numCodeBlocks ; i++) {
            FrameInfo fi = codeBlocks[i];
           
            MethodInsnNode min = (MethodInsnNode)(mn.instructions.get(fi.endInstruction));
            if(InstrumentClass.COROUTINE_NAME.equals(min.owner) && "yield".equals(min.name)) {
                // special case - call to yield() - resume AFTER the call
                if(min.getOpcode() != Opcodes.INVOKESTATIC) {
                    throw new UnableToInstrumentException("invalid call to yield()", className, mn.name, mn.desc);
                }
               
                if (verifyVarInfoss != null) {
                  mv.visitMethodInsn(Opcodes.INVOKESTATIC, "nginx/clojure/wave/SuspendMethodVerifier", "onYield", "()V");
                }
               
                emitStoreState(mv, i, fi);
                mv.visitFieldInsn(Opcodes.GETSTATIC, STACK_NAME,
                        "exception_instance_not_for_user_code",
                        CheckInstrumentationVisitor.EXCEPTION_DESC);
                mv.visitInsn(Opcodes.ATHROW);
                min.accept(mv); // only the call
                mv.visitLabel(lMethodCalls[i-1]);
                emitRestoreState(mv, i, fi);
                dumpCodeBlock(mv, i, 1);    // skip the call
            } else {
             
                final Label ocl = new Label();
                if (db.isAllowOutofCoroutine()) {
                    mv.visitVarInsn(Opcodes.ALOAD, lvarStack);
                    mv.visitJumpInsn(Opcodes.IFNULL, ocl);
                }
             
                // normal case - call to a suspendable method - resume before the call
                emitStoreState(mv, i, fi);
                mv.visitLabel(lMethodCalls[i-1]);
                emitRestoreState(mv, i, fi);
               
                if (db.isAllowOutofCoroutine()) {
                  mv.visitLabel(ocl);
                }
               
                dumpCodeBlock(mv, i, 0);
            }
        }
       
        mv.visitLabel(lMethodEnd);
       
        mv.visitLabel(lCatchAll);
        emitPopMethod(mv);
        mv.visitLabel(lCatchSEE);
        if (hasReflectInvoke) {
          emitRelectExceptionHandleCode(mv, false);
        }
        mv.visitInsn(Opcodes.ATHROW);   // rethrow shared between catchAll and catchSSE
       
        if(mn.localVariables != null) {
            for(Object o : mn.localVariables) {
                ((LocalVariableNode)o).accept(mv);
            }
        }
        if (verifyVarInfoss != null) {
          mv.visitMaxs(mn.maxStack + 4, mn.maxLocals+1+additionalLocals);
          db.getVerfiyMethodInfos().put(classAndMethod, verifyVarInfoss);
        }else {
          mv.visitMaxs(mn.maxStack + 3, mn.maxLocals+1+additionalLocals);
        }
        mv.visitEnd();
    }

  private void emitRelectExceptionHandleCode(MethodVisitor mv, boolean doThrow) {
    Label lNoReflectException = new Label();
    mv.visitInsn(Opcodes.DUP);
    mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable", "getCause", "()Ljava/lang/Throwable;");
    mv.visitFieldInsn(Opcodes.GETSTATIC, "nginx/clojure/Stack", "exception_instance_not_for_user_code", "Lnginx/clojure/SuspendExecution;");
    mv.visitJumpInsn(Opcodes.IF_ACMPNE, lNoReflectException);
    mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable", "getCause", "()Ljava/lang/Throwable;");
    if (doThrow) {
      mv.visitInsn(Opcodes.ATHROW);
    }
    mv.visitLabel(lNoReflectException);
  }

    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;
        return  fi;
    }

    private int getLabelIdx(LabelNode l) {
        int idx;
        if(l instanceof BlockLabelNode) {
            idx = ((BlockLabelNode)l).idx;
        } else {
            idx = mn.instructions.indexOf(l);
        }
       
        if (idx == mn.instructions.size() -1) {
          return idx;
        }
        // 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) {
                //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) {
                    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);
                    }

                    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);
           
            if (ins instanceof LabelNode) {
              ins.accept(mv);
        LabelNode ln = (LabelNode) ins;
        if (hasReflectInvoke && reflectExceptionHandlers.contains(ln)) {
          emitRelectExceptionHandleCode(mv, true);
        }
        continue;
      }
           
            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()) {
                    throw new UnableToInstrumentException("synchronisation", className, mn.name, mn.desc);
                } else  {
                    warnedAboutMonitors = true;
                    if (true) {
                       db.warn("Method %s#%s%s contains synchronisation, we'll clear it", className, mn.name, mn.desc);
                         mv.visitInsn(Opcodes.POP);
                         continue;
                    }
                }
                break;
               
            case Opcodes.INVOKESPECIAL:
                MethodInsnNode min = (MethodInsnNode)ins;
                if("<init>".equals(min.name)) {
                    int argSize = TypeAnalyzer.getNumArguments(min.desc);
                    Frame frame = frames[i];
                    if (frame != null) {
                       int stackIndex = frame.getStackSize() - argSize - 1;
                         Value thisValue = frame.getStack(stackIndex);
                         if(stackIndex >= 1 &&
                                 isNewValue(thisValue, true) &&
                                 isNewValue(frame.getStack(stackIndex-1), false)) {
                             NewValue newValue = (NewValue)thisValue;
                             if(newValue.omitted) {
                                 emitNewAndDup(mv, frame, stackIndex, min);
                             }
                         } else {
                             db.warn("Expected to find a NewValue on stack index %d: %s", stackIndex, frame);
                         }
                    }else {
                      db.error("frame is null!!");
                    }
                }
                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 void emitNewAndDup(MethodVisitor mv, Frame frame, int stackIndex, MethodInsnNode min) {
        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+1+neededLocals);
            neededLocals += v.getSize();
        }
        db.trace("Inserting NEW & DUP for constructor call %s%s with %d arguments (%d locals)", min.owner, min.desc, arguments, neededLocals);
        if(additionalLocals < neededLocals) {
            additionalLocals = 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+1+neededLocals);
        }
    }
   
    private void emitPopMethod(MethodVisitor mv) {
     
        final Label ocl = new Label();
        if (db.isAllowOutofCoroutine()) {
            mv.visitVarInsn(Opcodes.ALOAD, lvarStack);
            mv.visitJumpInsn(Opcodes.IFNULL, ocl);
        }

     
        mv.visitVarInsn(Opcodes.ALOAD, lvarStack);
       
        if (verifyVarInfoss != null) {
          mv.visitLdcInsn(classAndMethod);
          mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "popMethodV", "(Ljava/lang/String;)V");
        }else {
          mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "popMethod", "()V");
        }
       
       
        if (db.isAllowOutofCoroutine()) {
            mv.visitLabel(ocl);
        }
    }
   
    private LocalVariableNode findVarNode(int i) {
      for (LocalVariableNode lvn : mn.localVariables) {
        if (lvn.index == i) {
          return lvn;
        }
      }
      return null;
    }

    private void emitStoreState(MethodVisitor mv, int idx, FrameInfo fi) {
        Frame f = frames[fi.endInstruction];
       
        if (verifyVarInfoss != null) {
          VerifyVarInfo[] vis = verifyVarInfoss[idx-1] = new VerifyVarInfo[fi.numSlots*2];
            for(int i=f.getStackSize() ; i-->0 ;) {
                BasicValue v = (BasicValue) f.getStack(i);
        if (!isOmitted(v) && !isNullType(v)) {
          VerifyVarInfo vi = new VerifyVarInfo();
          int slotIdx = fi.stackSlotIndices[i];
          vi.idx = i;
          vi.name = "_NGX_STACK_VAL_";
          vi.dataIdx = slotIdx;
          vi.value = v;
          if (v.isReference()) {
            vis[slotIdx] = vi;
          }else {
            vis[fi.numSlots + slotIdx] = vi;
          }
        }
            }
           
            for(int i=firstLocal ; i<f.getLocals() ; i++) {
                BasicValue v = (BasicValue) f.getLocal(i);
                if(!isNullType(v)) {
                  VerifyVarInfo vi = new VerifyVarInfo();
                    int slotIdx = fi.localSlotIndices[i];
          LocalVariableNode lvn = findVarNode(i);
          if (lvn != null) {
            vi.name = lvn.name;
            vi.idx = i;
          }
                    vi.dataIdx = slotIdx;
          vi.value = v;

          if (v.isReference()) {
            vis[slotIdx] = vi;
          }else {
            vis[fi.numSlots + slotIdx] = vi;
          }
                }
            }
        }
       
        if(fi.lBefore != null) {
            fi.lBefore.accept(mv);
        }
           
        mv.visitVarInsn(Opcodes.ALOAD,lvarStack);
        emitConst(mv, idx);
        emitConst(mv, fi.numSlots);
        if (verifyVarInfoss != null) {
          mv.visitLdcInsn(classAndMethod);
          mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "pushMethodAndReserveSpaceV", "(IILjava/lang/String;)V");
        }else {
          mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "pushMethodAndReserveSpace", "(II)V");
        }
       
               
        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);
                } else {
                    db.trace("NULL stack entry: type=%s size=%d", v.getType(), v.getSize());
                    mv.visitInsn(Opcodes.POP);
                }
            }
        }
       
        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);
            }
        }
    }

    private void emitRestoreState(MethodVisitor mv, int idx, FrameInfo fi) {
        Frame f = frames[fi.endInstruction];
       
        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);
                mv.visitVarInsn(v.getType().getOpcode(Opcodes.ISTORE), i);
            } else if(v != BasicValue.UNINITIALIZED_VALUE) {
                mv.visitInsn(Opcodes.ACONST_NULL);
                mv.visitVarInsn(Opcodes.ASTORE, i);
            }
        }
       
        for(int i=0 ; 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);
                } else {
                    mv.visitInsn(Opcodes.ACONST_NULL);
                }
            }
        }
       
        if(fi.lAfter != null) {
            fi.lAfter.accept(mv);
        }
    }

    private void emitStoreValue(MethodVisitor mv, BasicValue v, int lvarStack, int idx) throws InternalError, IndexOutOfBoundsException {
        String desc;

        switch(v.getType().getSort()) {
        case Type.OBJECT:
        case Type.ARRAY:
          if (verifyVarInfoss != null) {
            desc = STACK_PUSHV_OBJECT_VALUE_DESC;
          }else {
            desc = STACK_PUSH_OBJECT_VALUE_DESC;
          }
            break;
        case Type.BOOLEAN:
        case Type.BYTE:
        case Type.SHORT:
        case Type.CHAR:
        case Type.INT:
          if (verifyVarInfoss != null) {
            desc = STACK_PUSHV_INT_VALUE_DESC;
          }else {
            desc = STACK_PUSH_INT_VALUE_DESC;
          }
            break;
        case Type.FLOAT:
          if (verifyVarInfoss != null) {
            desc = STACK_PUSHV_FLOAT_VALUE_DESC;
          }else {
            desc = STACK_PUSH_FLOAT_VALUE_DESC;
          }
            break;
        case Type.LONG:
          if (verifyVarInfoss != null) {
            desc = STACK_PUSHV_LONG_VALUE_DESC;
          }else {
            desc = STACK_PUSH_LONG_VALUE_DESC;
          }
            break;
        case Type.DOUBLE:
          if (verifyVarInfoss != null) {
            desc = STACK_PUSHV_DOUBLE_VALUE_DESC;
          }else {
            desc = STACK_PUSH_DOUBLE_VALUE_DESC;
          }
            break;
        default:
            throw new InternalError("Unexpected type: " + v.getType());
        }

       
        mv.visitVarInsn(Opcodes.ALOAD, lvarStack);
        emitConst(mv, idx);
        if (verifyVarInfoss != null) {
          mv.visitLdcInsn(classAndMethod);
          mv.visitMethodInsn(Opcodes.INVOKESTATIC, STACK_NAME, "pushV", desc);
        }else {
          mv.visitMethodInsn(Opcodes.INVOKESTATIC, STACK_NAME, "push", desc);
        }
    }

    private void emitRestoreValue(MethodVisitor mv, BasicValue v, int lvarStack, int idx) {
        mv.visitVarInsn(Opcodes.ALOAD, lvarStack);
        emitConst(mv, idx);
        if (verifyVarInfoss != null) {
            mv.visitLdcInsn(classAndMethod);
        }
        switch(v.getType().getSort()) {
        case Type.OBJECT:
            String internalName = v.getType().getInternalName();
            if (verifyVarInfoss != null) {
              mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "getObjectV", "(ILjava/lang/String;)Ljava/lang/Object;");
            }else {
              mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "getObject", "(I)Ljava/lang/Object;");
            }
            if(!internalName.equals("java/lang/Object")) {  // don't cast to Object ;)
                mv.visitTypeInsn(Opcodes.CHECKCAST, internalName);
            }
            break;
        case Type.ARRAY:
          if (verifyVarInfoss != null) {
              mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "getObjectV", "(ILjava/lang/String;)Ljava/lang/Object;");
            }else {
              mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "getObject", "(I)Ljava/lang/Object;");
            }
            mv.visitTypeInsn(Opcodes.CHECKCAST, v.getType().getDescriptor());
            break;
        case Type.BYTE:
          if (verifyVarInfoss != null) {
              mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "getIntV", "(ILjava/lang/String;)I");
            }else {
              mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "getInt", "(I)I");
            }
            mv.visitInsn(Opcodes.I2B);
            break;
        case Type.SHORT:
          if (verifyVarInfoss != null) {
              mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "getIntV", "(ILjava/lang/String;)I");
            }else {
              mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "getInt", "(I)I");
            }
            mv.visitInsn(Opcodes.I2S);
            break;
        case Type.CHAR:
          if (verifyVarInfoss != null) {
              mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "getIntV", "(ILjava/lang/String;)I");
            }else {
              mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "getInt", "(I)I");
            }
            mv.visitInsn(Opcodes.I2C);
            break;
        case Type.BOOLEAN:
        case Type.INT:
          if (verifyVarInfoss != null) {
              mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "getIntV", "(ILjava/lang/String;)I");
            }else {
              mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "getInt", "(I)I");
            }
            break;
        case Type.FLOAT:
          if (verifyVarInfoss != null) {
              mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "getFloatV", "(ILjava/lang/String;)F");
            }else {
              mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "getFloat", "(I)F");
            }
            break;
        case Type.LONG:
          if (verifyVarInfoss != null) {
              mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "getLongV", "(ILjava/lang/String;)J");
            }else {
              mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "getLong", "(I)J");
            }
            break;
        case Type.DOUBLE:
          if (verifyVarInfoss != null) {
              mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "getLongV", "(ILjava/lang/String;)D");
            }else {
              mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STACK_NAME, "getDouble", "(I)D");
            }
            break;
        default:
            throw new InternalError("Unexpected type: " + v.getType());
        }
    }
   
    static boolean isNullType(BasicValue v) {
        return (v == BasicValue.UNINITIALIZED_VALUE) ||
                (v.isReference() && v.getType().getInternalName().equals("null"));
    }
   
    static boolean isOmitted(BasicValue v) {
        if(v instanceof NewValue) {
            return ((NewValue)v).omitted;
        }
        return false;
    }
   
    static boolean isNewValue(Value v, boolean dupped) {
        if(v instanceof NewValue) {
            return ((NewValue)v).isDupped == dupped;
        }
        return false;
    }
   
    static class BlockLabelNode extends LabelNode {
        final int idx;

        BlockLabelNode(int idx) {
            this.idx = idx;
        }
    }
   
    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) {
                        NewValue newValue = (NewValue)v;
                        if(db.isDebug()) {
                            db.trace("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()) {
                                // need to log index before replacing instruction
                                db.trace("Omitting instruction %d: %s", insnList.indexOf(newValue.insn), newValue.formatInsn());
                            }
                            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;
        }
       
        public LabelNode createBeforeLabel() {
            if(lBefore == null) {
                lBefore = new BlockLabelNode(endInstruction);
            }
            return lBefore;
        }
       
        public LabelNode createAfterLabel() {
            if(lAfter == null) {
                lAfter = new BlockLabelNode(endInstruction);
            }
            return lAfter;
        }
    }
}
TOP

Related Classes of nginx.clojure.wave.InstrumentMethod

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.