blob: 2593b47ac4440910424293b96f120ac07ccfbfad [file] [log] [blame]
/**********************************************************************
* This file is part of "Object Teams Dynamic Runtime Environment"
*
* Copyright 2009, 2016 Oliver Frank and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Please visit http://www.eclipse.org/objectteams for updates and contact.
*
* Contributors:
* Oliver Frank - Initial API and implementation
* Stephan Herrmann - Initial API and implementation
**********************************************************************/
package org.eclipse.objectteams.otredyn.bytecode.asm;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import org.eclipse.objectteams.otredyn.bytecode.AbstractBoundClass;
import org.eclipse.objectteams.otredyn.bytecode.Method;
import org.eclipse.objectteams.otredyn.transformer.IWeavingContext;
import org.eclipse.objectteams.otredyn.transformer.names.ClassNames;
import org.eclipse.objectteams.otredyn.transformer.names.ConstantMembers;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TryCatchBlockNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;
/**
* This class moves the code of a method to callOrig.
* @author Oliver Frank
*/
public class MoveCodeToCallOrigAdapter extends AbstractTransformableClassNode {
private static final String BOUND_METHOD_ID = "_OT$boundMethodId";
private Method method;
private int boundMethodId;
private int firstArgIndex; // slot index of the first argument (0 (static) or 1 (non-static))
private int argOffset; // used to skip synth args if the callOrig method itself is a statid role method
private Method callOrig;
private boolean superIsWeavable = true;
private boolean baseSuperRequired;
private AbstractBoundClass superclass;
public MoveCodeToCallOrigAdapter(AsmWritableBoundClass clazz, Method method, int boundMethodId, boolean baseSuperRequired, IWeavingContext weavingContext) {
this.method = method;
this.boundMethodId = boundMethodId;
if (method.isStatic()) {
firstArgIndex = 0;
argOffset = clazz.isRole() ? 2 : 0;
callOrig = clazz.getCallOrigStatic();
} else {
firstArgIndex = 1;
callOrig = ConstantMembers.callOrig;
}
if (weavingContext != null)
superIsWeavable = weavingContext.isWeavable(clazz.getSuperClassName(), false);
if (superIsWeavable)
superclass = clazz.getSuperclass();
this.baseSuperRequired = baseSuperRequired;
}
public boolean transform() {
MethodNode orgMethod = getMethod(method);
if ((orgMethod.access & Opcodes.ACC_ABSTRACT) != 0) return false;
MethodNode callOrig = getMethod(this.callOrig);
Type returnType = Type.getReturnType(orgMethod.desc);
InsnList newInstructions = new InsnList();
//Unboxing arguments
Type[] args = Type.getArgumentTypes(orgMethod.desc);
LabelNode start = new LabelNode(), end = new LabelNode(); // range for new local variables
newInstructions.add(start);
int line = peekFirstLineNumber(orgMethod.instructions);
if (line != -1)
addLineNumber(newInstructions, line);
int boundMethodIdSlot = firstArgIndex;
if (args.length > 0) {
// move boundMethodId to a higher slot, to make lower slots available for original locals
newInstructions.add(new VarInsnNode(Opcodes.ILOAD, boundMethodIdSlot));
boundMethodIdSlot = orgMethod.maxLocals+1;
addLocal(callOrig, BOUND_METHOD_ID, "I", boundMethodIdSlot, start, end, false);
newInstructions.add(new VarInsnNode(Opcodes.ISTORE, boundMethodIdSlot));
newInstructions.add(new VarInsnNode(Opcodes.ALOAD, firstArgIndex + argOffset + 1));
int slot = firstArgIndex + argOffset;
List<LocalVariableNode> origLocals = orgMethod.localVariables;
for (int i = argOffset; i < args.length; i++) {
if (i < args.length - 1) {
newInstructions.add(new InsnNode(Opcodes.DUP));
}
newInstructions.add(createLoadIntConstant(i));
newInstructions.add(new InsnNode(Opcodes.AALOAD));
Type arg = args[i];
if (arg.getSort() != Type.ARRAY && arg.getSort() != Type.OBJECT) {
String objectType = AsmTypeHelper.getBoxingType(arg);
newInstructions.add(new TypeInsnNode(Opcodes.CHECKCAST, objectType));
newInstructions.add(AsmTypeHelper.getUnboxingInstructionForType(arg, objectType));
} else {
newInstructions.add(new TypeInsnNode(Opcodes.CHECKCAST, arg.getInternalName()));
}
newInstructions.add(new VarInsnNode(args[i].getOpcode(Opcodes.ISTORE), slot));
int origLocalIdx = i+firstArgIndex;
if (origLocals != null && origLocalIdx < origLocals.size())
addLocal(callOrig, origLocals.get(origLocalIdx).name, arg.getDescriptor(), slot, start, end, false);
slot += arg.getSize();
}
} else {
addLocal(callOrig, BOUND_METHOD_ID, "I", boundMethodIdSlot, start, end, false);
}
InsnList orgInstructions = orgMethod.instructions;
if (superIsWeavable)
adjustSuperCalls(orgInstructions, orgMethod.name, orgMethod.desc, args, returnType, boundMethodIdSlot);
// replace return of the original method with areturn and box the result value if needed
replaceReturn(orgInstructions, returnType);
newInstructions.add(orgInstructions); // this wipes orgInstructions
addReturn(orgMethod.instructions,Type.getReturnType(orgMethod.desc)); // restores minimal code
if (orgMethod.tryCatchBlocks != null) {
addTryCatchBlocks(orgMethod, callOrig);
orgMethod.tryCatchBlocks.clear();
}
if (orgMethod.localVariables != null) {
orgMethod.localVariables.clear();
}
newInstructions.add(end);
addNewLabelToSwitch(callOrig.instructions, newInstructions, boundMethodId);
if (this.baseSuperRequired && !superName.equals(ClassNames.OBJECT_SLASH) && !method.isStatic()) {
newInstructions = superOrigCall(method, args);
addNewLabelToSwitch(callOrig.instructions, newInstructions, boundMethodId+1);
}
// a minimum stacksize of 3 is needed to box the arguments
callOrig.maxStack = Math.max(Math.max(callOrig.maxStack, orgMethod.maxStack), 3);
// we have to increment the max. stack size, because we have to put NULL on the stack
if (returnType.getSort() == Type.VOID) {
callOrig.maxStack += 1;
}
callOrig.maxLocals = Math.max(callOrig.maxLocals, orgMethod.maxLocals);
return true;
}
@SuppressWarnings("unchecked")
private void addTryCatchBlocks(MethodNode orgMethod, MethodNode callOrig) {
if (callOrig.tryCatchBlocks == null)
callOrig.tryCatchBlocks = new ArrayList<TryCatchBlockNode>();
callOrig.tryCatchBlocks.addAll(orgMethod.tryCatchBlocks);
}
private InsnList superOrigCall(Method method, Type[] args) {
InsnList newInstructions = new InsnList();
newInstructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
for (int i = argOffset; i < args.length; i++) {
newInstructions.add(new VarInsnNode(Opcodes.ALOAD, firstArgIndex + argOffset + 1));
newInstructions.add(createLoadIntConstant(i));
newInstructions.add(new InsnNode(Opcodes.AALOAD));
Type arg = args[i];
if (arg.getSort() != Type.ARRAY && arg.getSort() != Type.OBJECT) {
String objectType = AsmTypeHelper.getBoxingType(arg);
newInstructions.add(new TypeInsnNode(Opcodes.CHECKCAST, objectType));
newInstructions.add(AsmTypeHelper.getUnboxingInstructionForType(arg, objectType));
} else {
newInstructions.add(new TypeInsnNode(Opcodes.CHECKCAST, arg.getInternalName()));
}
}
newInstructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, superName, method.getName(), method.getSignature(), false));
Type returnType = Type.getReturnType(method.getSignature());
switch (returnType.getSort()) {
case Type.VOID:
newInstructions.add(new InsnNode(Opcodes.ACONST_NULL));
break;
case Type.OBJECT:
case Type.ARRAY:
break;
default:
newInstructions.add(AsmTypeHelper.getBoxingInstructionForType(returnType));
break;
}
newInstructions.add(new InsnNode(Opcodes.ARETURN));
return newInstructions;
}
/** To avoid infinite recursion, calls super.m(a1, a2) must be translated to super.callOrig(boundMethodId, new Object[] {a1, a2}). */
private void adjustSuperCalls(InsnList instructions, String selector, String descriptor,
Type[] args, Type returnType, final int boundMethodIdSlot) {
// search:
List<MethodInsnNode> toReplace = new ArrayList<MethodInsnNode>();
ListIterator<AbstractInsnNode> orgMethodIter = instructions.iterator();
while (orgMethodIter.hasNext()) {
AbstractInsnNode orgMethodNode = orgMethodIter.next();
if (orgMethodNode.getOpcode() == Opcodes.INVOKESPECIAL
&& ((MethodInsnNode)orgMethodNode).name.equals(selector)
&& ((MethodInsnNode)orgMethodNode).desc.equals(descriptor))
toReplace.add((MethodInsnNode) orgMethodNode);
}
if (toReplace.isEmpty())
return;
// replace:
replaceSuperCallsWithCallToCallOrig(instructions, toReplace, true, superclass, new IBoundMethodIdInsnProvider() {
@Override public AbstractInsnNode getLoadBoundMethodIdInsn(MethodInsnNode methodInsn) {
return new VarInsnNode(Opcodes.ILOAD, boundMethodIdSlot);
}
});
}
}