blob: 1c512204d0f98687eabd92462492267ad5de636b [file] [log] [blame]
/**********************************************************************
* This file is part of "Object Teams Dynamic Runtime Environment"
*
* Copyright 2009, 2014 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.Method;
import org.eclipse.objectteams.otredyn.transformer.IWeavingContext;
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.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TypeInsnNode;
/**
* This class moves the code of a method to callOrig.
* @author Oliver Frank
*/
public class MoveCodeToCallOrigAdapter extends AbstractTransformableClassNode {
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;
public MoveCodeToCallOrigAdapter(AsmWritableBoundClass clazz, Method method, int boundMethodId, 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());
}
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);
int boundMethodIdSlot = firstArgIndex;
if (args.length > 0) {
// move boundMethodId to a higher slot, to make lower slots available for original locals
newInstructions.add(new IntInsnNode(Opcodes.ILOAD, boundMethodIdSlot));
boundMethodIdSlot = callOrig.maxLocals+1;
newInstructions.add(new IntInsnNode(Opcodes.ISTORE, boundMethodIdSlot));
newInstructions.add(new IntInsnNode(Opcodes.ALOAD, firstArgIndex + argOffset + 1));
int slot = firstArgIndex + argOffset;
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.getObjectType(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 IntInsnNode(args[i].getOpcode(Opcodes.ISTORE), slot));
slot += arg.getSize();
}
}
if (superIsWeavable)
adjustSuperCalls(orgMethod.instructions, orgMethod.name, args, returnType, boundMethodIdSlot);
// replace return of the original method with areturn and box the result value if needed
replaceReturn(orgMethod.instructions, returnType);
newInstructions.add(orgMethod.instructions);
addNewLabelToSwitch(callOrig.instructions, newInstructions, boundMethodId);
// 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;
}
/** 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, Type[] args, Type returnType, 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))
toReplace.add((MethodInsnNode) orgMethodNode);
}
if (toReplace.isEmpty())
return;
// replace:
for (MethodInsnNode oldNode : toReplace) {
// we need to insert into the loading sequence before the invocation, find the insertion points:
AbstractInsnNode[] insertionPoints = StackBalanceAnalyzer.findInsertionPointsBefore(oldNode, args);
AbstractInsnNode firstInsert = insertionPoints.length > 0 ? insertionPoints[0] : oldNode;
// push first arg to _OT$callOrig():
instructions.insertBefore(firstInsert, new IntInsnNode(Opcodes.ILOAD, boundMethodIdSlot));
// prepare array as second arg to _OT$callOrig():
instructions.insertBefore(firstInsert, new IntInsnNode(Opcodes.BIPUSH, args.length));
instructions.insertBefore(firstInsert, new TypeInsnNode(Opcodes.ANEWARRAY, "java/lang/Object"));
for (int i = 0; i < insertionPoints.length; i++) {
// NB: each iteration has an even stack balance, where the top is the Object[].
instructions.insertBefore(insertionPoints[i], new InsnNode(Opcodes.DUP));
instructions.insertBefore(insertionPoints[i], new IntInsnNode(Opcodes.BIPUSH, i));
// leave the original loading sequence in tact and continue at the next point:
AbstractInsnNode insertAt = (i +1 < insertionPoints.length) ? insertionPoints[i+1] : oldNode;
instructions.insertBefore(insertAt, AsmTypeHelper.getBoxingInstructionForType(args[i]));
instructions.insertBefore(insertAt, new InsnNode(Opcodes.AASTORE));
}
if (returnType == Type.VOID_TYPE)
instructions.insert(oldNode, new InsnNode(Opcodes.POP));
else
instructions.insert(oldNode, AsmTypeHelper.getUnboxingInstructionForType(returnType));
instructions.set(oldNode, new MethodInsnNode(Opcodes.INVOKESPECIAL, ((MethodInsnNode)oldNode).owner, callOrig.getName(), callOrig.getSignature()));
}
}
}