/* * 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; } } }