/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.dragome.callbackevictor.serverside.bytecode.transformation.asm;
import java.util.List;
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.AbstractInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.analysis.Analyzer;
import org.objectweb.asm.tree.analysis.BasicValue;
import org.objectweb.asm.tree.analysis.Frame;
import com.dragome.callbackevictor.enhancers.StackRecorder;
public final class ContinuationMethodAdapter extends MethodVisitor implements Opcodes
{
private static final String STACK_RECORDER= Type.getInternalName(StackRecorder.class);
private static final String POP_METHOD= "pop";
private static final String PUSH_METHOD= "push";
private final ContinuationMethodAnalyzer canalyzer;
private final Analyzer analyzer;
private Label startLabel= new Label();
private final List<Label> labels;
private final List<MethodInsnNode> nodes;
private final int stackRecorderVar;
private final boolean isStatic;
private final String methodDesc;
private int currentIndex= 0;
private Frame currentFrame= null;
public ContinuationMethodAdapter(ContinuationMethodAnalyzer a)
{
super(Opcodes.ASM5, a.mv);
this.canalyzer= a;
this.analyzer= a.analyzer;
this.labels= a.labels;
this.nodes= a.nodes;
this.stackRecorderVar= a.stackRecorderVar;
this.isStatic= (a.access & ACC_STATIC) > 0;
this.methodDesc= a.desc;
}
public void visitCode()
{
mv.visitCode();
int fsize= labels.size();
Label[] restoreLabels= new Label[fsize];
for (int i= 0; i < restoreLabels.length; i++)
{
restoreLabels[i]= new Label();
}
// verify if restoring
Label l0= new Label();
// PC: StackRecorder stackRecorder = StackRecorder.get();
mv.visitMethodInsn(INVOKESTATIC, STACK_RECORDER, "get", "()L" + STACK_RECORDER + ";", false);
mv.visitInsn(DUP);
mv.visitVarInsn(ASTORE, stackRecorderVar);
mv.visitLabel(startLabel);
// PC: if (stackRecorder != null && !stackRecorder.isRestoring) {
mv.visitJumpInsn(IFNULL, l0);
mv.visitVarInsn(ALOAD, stackRecorderVar);
mv.visitFieldInsn(GETFIELD, STACK_RECORDER, "isRestoring", "Z");
mv.visitJumpInsn(IFEQ, l0);
mv.visitVarInsn(ALOAD, stackRecorderVar);
// PC: stackRecorder.popInt();
mv.visitMethodInsn(INVOKEVIRTUAL, STACK_RECORDER, POP_METHOD + "Int", "()I", false);
mv.visitTableSwitchInsn(0, fsize - 1, l0, restoreLabels);
// switch cases
for (int i= 0; i < fsize; i++)
{
Label frameLabel= labels.get(i);
mv.visitLabel(restoreLabels[i]);
MethodInsnNode mnode= nodes.get(i);
Frame frame= analyzer.getFrames()[canalyzer.getIndex(mnode)];
// for each local variable store the value in locals popping it from the stack!
// locals
int lsize= frame.getLocals();
for (int j= lsize - 1; j >= 0; j--)
{
BasicValue value= (BasicValue) frame.getLocal(j);
if (isNull(value))
{
mv.visitInsn(ACONST_NULL);
mv.visitVarInsn(ASTORE, j);
}
else if (value == BasicValue.UNINITIALIZED_VALUE)
{
// TODO ??
}
else if (value == BasicValue.RETURNADDRESS_VALUE)
{
// TODO ??
}
else
{
mv.visitVarInsn(ALOAD, stackRecorderVar);
Type type= value.getType();
if (value.isReference())
{
mv.visitMethodInsn(INVOKEVIRTUAL, STACK_RECORDER, POP_METHOD + "Object", "()Ljava/lang/Object;", false);
Type t= value.getType();
String desc= t.getDescriptor();
if (desc.charAt(0) == '[')
{
mv.visitTypeInsn(CHECKCAST, desc);
}
else
{
mv.visitTypeInsn(CHECKCAST, t.getInternalName());
}
mv.visitVarInsn(ASTORE, j);
}
else
{
mv.visitMethodInsn(INVOKEVIRTUAL, STACK_RECORDER, getPopMethod(type), "()" + type.getDescriptor(), false);
mv.visitVarInsn(type.getOpcode(ISTORE), j);
}
}
}
if (frame instanceof MonitoringFrame)
{
int[] monitoredLocals= ((MonitoringFrame) frame).getMonitored();
// System.out.println(System.identityHashCode(frame)+" AMonitored locals "+monitoredLocals.length);
for (int monitoredLocal : monitoredLocals)
{
// System.out.println(System.identityHashCode(frame)+" AMonitored local "+monitoredLocals[j]);
mv.visitVarInsn(ALOAD, monitoredLocal);
mv.visitInsn(MONITORENTER);
}
}
// stack
int argSize= Type.getArgumentTypes(mnode.desc).length;
int ownerSize= mnode.getOpcode() == INVOKESTATIC ? 0 : 1; // TODO
int initSize= mnode.name.equals("<init>") ? 2 : 0;
int ssize= frame.getStackSize();
for (int j= 0; j < ssize - argSize - ownerSize - initSize; j++)
{
BasicValue value= (BasicValue) frame.getStack(j);
if (isNull(value))
{
mv.visitInsn(ACONST_NULL);
}
else if (value == BasicValue.UNINITIALIZED_VALUE)
{
// TODO ??
}
else if (value == BasicValue.RETURNADDRESS_VALUE)
{
// TODO ??
}
else if (value.isReference())
{
mv.visitVarInsn(ALOAD, stackRecorderVar);
mv.visitMethodInsn(INVOKEVIRTUAL, STACK_RECORDER, POP_METHOD + "Object", "()Ljava/lang/Object;", false);
mv.visitTypeInsn(CHECKCAST, value.getType().getInternalName());
}
else
{
Type type= value.getType();
mv.visitVarInsn(ALOAD, stackRecorderVar);
mv.visitMethodInsn(INVOKEVIRTUAL, STACK_RECORDER, getPopMethod(type), "()" + type.getDescriptor(), false);
}
}
boolean hasMethodRef= false;
if (mnode.getOpcode() != INVOKESTATIC)
{
// Load the object whose method we are calling
BasicValue value= ((BasicValue) frame.getStack(ssize - argSize - 1));
if (isNull(value))
{
// If user code causes NPE, then we keep this behavior: load null to get NPE at runtime
mv.visitInsn(ACONST_NULL);
}
else
{
hasMethodRef= true;
mv.visitVarInsn(ALOAD, stackRecorderVar);
mv.visitMethodInsn(INVOKEVIRTUAL, STACK_RECORDER, POP_METHOD + "Reference", "()Ljava/lang/Object;", false);
mv.visitTypeInsn(CHECKCAST, value.getType().getInternalName());
}
}
// Create null types for the parameters of the method invocation
// RS:
if (hasMethodRef && canalyzer._continueReflection && mnode.name.contains("invoke") && mnode.owner.contains("java/lang/reflect/Method"))
{
ContinuationMethodAnalyzer.MyVariables vars= canalyzer._reflectMapping.get(mnode);
mv.visitVarInsn(ALOAD, vars.objectVar());
mv.visitVarInsn(ALOAD, vars.argsVar());
//mv.visitVarInsn(ALOAD, 2);
//mv.visitVarInsn(ALOAD, 4);
}
//RS:
else
{
for (Type paramType : Type.getArgumentTypes(mnode.desc))
{
pushDefault(paramType);
}
}
// continue to the next method
mv.visitJumpInsn(GOTO, frameLabel);
}
// PC: }
// end of start block
mv.visitLabel(l0);
}
public void visitLabel(Label label)
{
if (currentIndex < labels.size() && label == labels.get(currentIndex))
{
int i= canalyzer.getIndex(nodes.get(currentIndex));
currentFrame= analyzer.getFrames()[i];
}
mv.visitLabel(label);
}
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf)
{
mv.visitMethodInsn(opcode, owner, name, desc, itf);
if (currentFrame != null)
{
Label fl= new Label();
mv.visitVarInsn(ALOAD, stackRecorderVar);
mv.visitJumpInsn(IFNULL, fl);
mv.visitVarInsn(ALOAD, stackRecorderVar);
mv.visitFieldInsn(GETFIELD, STACK_RECORDER, "isCapturing", "Z");
mv.visitJumpInsn(IFEQ, fl);
// save stack
Type returnType= Type.getReturnType(desc);
boolean hasReturn= returnType != Type.VOID_TYPE;
if (hasReturn)
{
mv.visitInsn(returnType.getSize() == 1 ? POP : POP2);
}
Type[] params= Type.getArgumentTypes(desc);
int argSize= params.length;
int ownerSize= opcode == INVOKESTATIC ? 0 : 1; // TODO
int ssize= currentFrame.getStackSize() - argSize - ownerSize;
for (int i= ssize - 1; i >= 0; i--)
{
BasicValue value= (BasicValue) currentFrame.getStack(i);
if (isNull(value))
{
mv.visitInsn(POP);
}
else if (value == BasicValue.UNINITIALIZED_VALUE)
{
// TODO ??
}
else if (value.isReference())
{
mv.visitVarInsn(ALOAD, stackRecorderVar);
mv.visitInsn(SWAP);
mv.visitMethodInsn(INVOKEVIRTUAL, STACK_RECORDER, PUSH_METHOD + "Object", "(Ljava/lang/Object;)V", false);
}
else
{
Type type= value.getType();
if (type.getSize() > 1)
{
mv.visitInsn(ACONST_NULL); // dummy stack entry
mv.visitVarInsn(ALOAD, stackRecorderVar);
mv.visitInsn(DUP2_X2); // swap2 for long/double
mv.visitInsn(POP2);
mv.visitMethodInsn(INVOKEVIRTUAL, STACK_RECORDER, getPushMethod(type), "(" + type.getDescriptor() + ")V", false);
mv.visitInsn(POP); // remove dummy stack entry
}
else
{
mv.visitVarInsn(ALOAD, stackRecorderVar);
mv.visitInsn(SWAP);
mv.visitMethodInsn(INVOKEVIRTUAL, STACK_RECORDER, getPushMethod(type), "(" + type.getDescriptor() + ")V", false);
}
}
}
if (!isStatic)
{
//RS:
if (canalyzer._continueReflection && name.equals("invoke") && owner.startsWith("java/lang/reflect/Method"))
{
mv.visitVarInsn(ALOAD, stackRecorderVar);
ContinuationMethodAnalyzer.MyVariables vars= canalyzer._reflectMapping.get(nodes.get(currentIndex));
// System.out.println("Class:" + canalyzer.className + "method:" + stackRecorderVar + ":" + name + ":" + owner + ":" + desc + ":" + vars.methodVar());
mv.visitVarInsn(ALOAD, vars.methodVar());
//mv.visitVarInsn(ALOAD, 3);
mv.visitMethodInsn(INVOKEVIRTUAL, STACK_RECORDER, "replace" + "Reference", "(Ljava/lang/Object;)V", false);
}
//RS:
mv.visitVarInsn(ALOAD, stackRecorderVar);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKEVIRTUAL, STACK_RECORDER, PUSH_METHOD + "Reference", "(Ljava/lang/Object;)V", false);
}
// save locals
int fsize= currentFrame.getLocals();
for (int j= 0; j < fsize; j++)
{
BasicValue value= (BasicValue) currentFrame.getLocal(j);
if (isNull(value))
{
// no need to save null
}
else if (value == BasicValue.UNINITIALIZED_VALUE)
{
// no need to save uninitialized objects
}
else if (value.isReference())
{
mv.visitVarInsn(ALOAD, stackRecorderVar);
mv.visitVarInsn(ALOAD, j);
mv.visitMethodInsn(INVOKEVIRTUAL, STACK_RECORDER, PUSH_METHOD + "Object", "(Ljava/lang/Object;)V", false);
}
else
{
mv.visitVarInsn(ALOAD, stackRecorderVar);
Type type= value.getType();
mv.visitVarInsn(type.getOpcode(ILOAD), j);
mv.visitMethodInsn(INVOKEVIRTUAL, STACK_RECORDER, getPushMethod(type), "(" + type.getDescriptor() + ")V", false);
}
}
mv.visitVarInsn(ALOAD, stackRecorderVar);
if (currentIndex >= 128)
{
// if > 127 then it's a SIPUSH, not a BIPUSH...
mv.visitIntInsn(SIPUSH, currentIndex);
}
else
{
// TODO optimize to iconst_0...
mv.visitIntInsn(BIPUSH, currentIndex);
}
mv.visitMethodInsn(INVOKEVIRTUAL, STACK_RECORDER, "pushInt", "(I)V", false);
if (currentFrame instanceof MonitoringFrame)
{
int[] monitoredLocals= ((MonitoringFrame) currentFrame).getMonitored();
// System.out.println(System.identityHashCode(currentFrame)+" Monitored locals "+monitoredLocals.length);
for (int monitoredLocal : monitoredLocals)
{
// System.out.println(System.identityHashCode(currentFrame)+" Monitored local "+monitoredLocals[j]);
mv.visitVarInsn(ALOAD, monitoredLocal);
mv.visitInsn(MONITOREXIT);
}
}
Type methodReturnType= Type.getReturnType(methodDesc);
pushDefault(methodReturnType);
mv.visitInsn(methodReturnType.getOpcode(IRETURN));
mv.visitLabel(fl);
currentIndex++;
currentFrame= null;
}
}
public void visitMaxs(int maxStack, int maxLocals)
{
Label endLabel= new Label();
mv.visitLabel(endLabel);
mv.visitLocalVariable("__stackRecorder", "L" + STACK_RECORDER + ";", null, startLabel, endLabel, stackRecorderVar);
mv.visitMaxs(0, 0);
}
static boolean isNull(BasicValue value)
{
if (null == value)
{
return true;
}
if (!value.isReference())
{
return false;
}
final Type type= value.getType();
return "Lnull;".equals(type.getDescriptor());
}
void pushDefault(Type type)
{
switch (type.getSort())
{
case Type.VOID:
break;
case Type.DOUBLE:
mv.visitInsn(DCONST_0);
break;
case Type.LONG:
mv.visitInsn(LCONST_0);
break;
case Type.FLOAT:
mv.visitInsn(FCONST_0);
break;
case Type.OBJECT:
case Type.ARRAY:
mv.visitInsn(ACONST_NULL);
break;
default:
mv.visitInsn(ICONST_0);
break;
}
}
private static String[] SUFFIXES= { "Object", // 0 void
"Int", // 1 boolean
"Int", // 2 char
"Int", // 3 byte
"Int", // 4 short
"Int", // 5 int
"Float", // 6 float
"Long", // 7 long
"Double", // 8 double
"Object", // 9 array
"Object", // 10 object
};
String getPopMethod(Type type)
{
return POP_METHOD + SUFFIXES[type.getSort()];
}
String getPushMethod(Type type)
{
return PUSH_METHOD + SUFFIXES[type.getSort()];
}
}