blob: 5d9c17273730c1cbdabf7b1d27cd483beaae177f [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.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.names.ClassNames;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
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.LdcInsnNode;
import org.objectweb.asm.tree.LookupSwitchInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TypeInsnNode;
import static org.eclipse.objectteams.otredyn.bytecode.asm.AsmBoundClass.ASM_API;
import static org.eclipse.objectteams.otredyn.transformer.names.ConstantMembers.callOrig;
/**
* Every class, that wants to manipulate the bytecode of a class
* with the ASM Tree API, have to inherit from this class and do
* the transformations in the method transform().
* Additionally the class provides util methods to
* manipulate the bytecode
* @author Oliver Frank
*/
public abstract class AbstractTransformableClassNode extends ClassNode {
public AbstractTransformableClassNode() {
super(ASM_API);
}
/**
* Returns instructions, that are needed to pack all arguments of a method
* in an {@link Object} Array
* @param args The Types of the arguments
* @param isStatic is this method static or not
* @return
*/
protected InsnList getBoxingInstructions(Type[] args, boolean isStatic) {
int firstArgIndex = 1;
if (isStatic) {
firstArgIndex = 0;
}
InsnList instructions = new InsnList();
instructions.add(createLoadIntConstant(args.length));
instructions.add(new TypeInsnNode(Opcodes.ANEWARRAY,
ClassNames.OBJECT_SLASH));
for (int i=0, slot=0; i < args.length; slot += args[i++].getSize()) {
instructions.add(new InsnNode(Opcodes.DUP));
instructions.add(createLoadIntConstant(i));
instructions.add(new IntInsnNode(args[i].getOpcode(Opcodes.ILOAD),
slot + firstArgIndex));
if (args[i].getSort() != Type.OBJECT
&& args[i].getSort() != Type.ARRAY) {
instructions.add(AsmTypeHelper
.getBoxingInstructionForType(args[i]));
}
instructions.add(new InsnNode(Opcodes.AASTORE));
}
return instructions;
}
/**
* Returns the instructions, that are needed to convert
* a return value of the type {@link Object} to the real type
* @param returnType the real type
* @return
*/
protected InsnList getUnboxingInstructionsForReturnValue(Type returnType) {
InsnList instructions = new InsnList();
switch (returnType.getSort()) {
case Type.VOID:
instructions.add(new InsnNode(Opcodes.POP));
instructions.add(new InsnNode(Opcodes.RETURN));
break;
case Type.ARRAY: // fallthrough
case Type.OBJECT:
instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, returnType
.getInternalName()));
instructions.add(new InsnNode(Opcodes.ARETURN));
break;
default:
String objectType = AsmTypeHelper.getObjectType(returnType);
instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, objectType));
instructions.add(AsmTypeHelper.getUnboxingInstructionForType(
returnType, objectType));
instructions
.add(new InsnNode(returnType.getOpcode(Opcodes.IRETURN)));
}
return instructions;
}
/**
* Adds a new Label to an existing switch statement
* @param instructions the instructions, in which the switch statement is defined
* @param newInstructions the instructions of the new label
* @param labelIndex the index of the label
*/
protected void addNewLabelToSwitch(InsnList instructions,
InsnList newInstructions, int labelIndex) {
ListIterator<AbstractInsnNode> iter = instructions.iterator();
LookupSwitchInsnNode lSwitch = null;
while (iter.hasNext()) {
AbstractInsnNode node = (AbstractInsnNode) iter.next();
if (node.getType() == AbstractInsnNode.LOOKUPSWITCH_INSN) {
lSwitch = (LookupSwitchInsnNode) node;
LabelNode label = new LabelNode();
boolean labelAdded = false;
for (int i = 0; i < lSwitch.keys.size(); i++) {
Integer key = (Integer) lSwitch.keys.get(i);
if (key >= labelIndex) {
lSwitch.keys.add(i, labelIndex);
lSwitch.labels.add(i, label);
labelAdded = true;
break;
}
}
if (!labelAdded) {
lSwitch.labels.add(label);
lSwitch.keys.add(labelIndex);
}
boolean foundDefLabel = false;
AbstractInsnNode prevNode = node;
while (iter.hasNext()) {
node = (AbstractInsnNode) iter.next();
if (node.getType() == AbstractInsnNode.LABEL) {
if (!foundDefLabel) {
foundDefLabel = true;
} else {
break;
}
}
prevNode = node;
}
instructions.insert(prevNode, label);
instructions.insert(label, newInstructions);
}
}
if (lSwitch == null) {
throw new RuntimeException("No switch statement found.");
}
}
/**
* Returns a {@link MethodNode} for a given {@link Method} instance
* @param method
* @return the {@link MethodNode} or null if there is no such method
*/
protected MethodNode getMethod(Method method) {
List<MethodNode> methodList = methods;
for (MethodNode methodNode : methodList) {
if (methodNode.name.compareTo(method.getName()) == 0
&& methodNode.desc.compareTo(method.getSignature()) == 0) {
return methodNode;
}
}
return null;
}
/**
* This method could be used to generate debug outputs in the generated code in the form: <br>
* <code>
* Sytsem.out.println(message);
* </code>
* @param message
* @return
*/
protected InsnList getInstructionsForDebugOutput(String message) {
InsnList instructions = new InsnList();
instructions.add(new FieldInsnNode(Opcodes.GETSTATIC,
"java/lang/System", "out", "Ljava/io/PrintStream;"));
instructions.add(new LdcInsnNode(message));
instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
"java/io/PrintStream", "println", "(Ljava/lang/String;)V", false));
return instructions;
}
protected InsnList getInstructionsForDebugObjectOutput(AbstractInsnNode getObjectInsn) {
InsnList instructions = new InsnList();
instructions.add(new FieldInsnNode(Opcodes.GETSTATIC,
"java/lang/System", "out", "Ljava/io/PrintStream;"));
instructions.add(getObjectInsn);
instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
"java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false));
return instructions;
}
/**
* Adds instructions to put all arguments of a method on the stack.
* @param instructions
* @param args
* @param isStatic
*/
protected void addInstructionsForLoadArguments(InsnList instructions, Type[] args, boolean isStatic) {
int firstArgIndex = 1;
if (isStatic) {
firstArgIndex = 0;
}
// put "this" on the stack for an non-static method
if (!isStatic) {
instructions.add(new IntInsnNode(Opcodes.ALOAD, 0));
}
for (int i=0, slot=firstArgIndex; i < args.length; slot+=args[i++].getSize()) {
instructions.add(new IntInsnNode(args[i].getOpcode(Opcodes.ILOAD),
slot));
}
}
/**
* Replace all return statements in the given instructions with new
* statements that convert the real return value to {@link Object}
* and return this new {@link Object}
*
* @param instructions
* @param returnType
*/
protected void replaceReturn(InsnList instructions, Type returnType) {
if (returnType.getSort() != Type.OBJECT &&
returnType.getSort() != Type.ARRAY &&
returnType.getSort() != Type.VOID) {
ListIterator<AbstractInsnNode> orgMethodIter = instructions.iterator();
while (orgMethodIter.hasNext()) {
AbstractInsnNode orgMethodNode = orgMethodIter.next();
if (orgMethodNode.getOpcode() == returnType.getOpcode(Opcodes.IRETURN)) {
instructions.insertBefore(orgMethodNode, AsmTypeHelper.getBoxingInstructionForType(returnType));
instructions.set(orgMethodNode, new InsnNode(Opcodes.ARETURN));
}
}
} else if (returnType.getSort() == Type.VOID) {
ListIterator<AbstractInsnNode> orgMethodIter = instructions.iterator();
while (orgMethodIter.hasNext()) {
AbstractInsnNode orgMethodNode = orgMethodIter.next();
if (orgMethodNode.getOpcode() == Opcodes.RETURN) {
instructions.insertBefore(orgMethodNode, new InsnNode(Opcodes.ACONST_NULL));
instructions.insertBefore(orgMethodNode, new InsnNode(Opcodes.ARETURN));
instructions.remove(orgMethodNode);
}
}
}
}
/**
* Create an instruction for loading an integer constant,
* using the most compact possible format.
*/
protected AbstractInsnNode createLoadIntConstant(int constant) {
if (constant <= 5)
return new InsnNode(Opcodes.ICONST_0+constant);
else if (constant < Byte.MAX_VALUE)
return new IntInsnNode(Opcodes.BIPUSH, constant);
else if (constant < Short.MAX_VALUE)
return new IntInsnNode(Opcodes.SIPUSH, constant);
else
return new LdcInsnNode(constant);
}
/** Call back interface for {@link #replaceSuperCallsWithCallToCallOrig()}. */
protected interface IBoundMethodIdInsnProvider {
AbstractInsnNode getLoadBoundMethodIdInsn(MethodInsnNode methodInsn);
}
protected void replaceSuperCallsWithCallToCallOrig(InsnList instructions, List<MethodInsnNode> superCalls,
boolean returnsJLObject, AbstractBoundClass superclass, IBoundMethodIdInsnProvider insnProvider) {
for (MethodInsnNode oldNode : superCalls) {
superclass.addWeavingOfSubclassTask(oldNode.name, oldNode.desc, oldNode.getOpcode() == Opcodes.INVOKESTATIC);
Type[] args = Type.getArgumentTypes(oldNode.desc);
Type returnType = Type.getReturnType(oldNode.desc);
// 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, insnProvider.getLoadBoundMethodIdInsn(oldNode));
// prepare array as second arg to _OT$callOrig():
instructions.insertBefore(firstInsert, createLoadIntConstant(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], createLoadIntConstant(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));
}
// before an areturn j.l.Object we don't need any type adjustments
// (incl. the case where we change another return to areturn j.l.O):
boolean nextIsGeneralizedReturn = false;
AbstractInsnNode next = oldNode.getNext();
if (returnsJLObject)
nextIsGeneralizedReturn = next != null && next.getOpcode() >= Opcodes.IRETURN && next.getOpcode() <= Opcodes.ARETURN;
if (!nextIsGeneralizedReturn) {
if (returnType == Type.VOID_TYPE) {
instructions.insert(oldNode, new InsnNode(Opcodes.POP));
} else {
instructions.insert(oldNode, AsmTypeHelper.getUnboxingInstructionForType(returnType));
String boxTypeName = AsmTypeHelper.getObjectType(returnType);
if (boxTypeName != null)
instructions.insert(oldNode, new TypeInsnNode(Opcodes.CHECKCAST, boxTypeName));
}
}
MethodInsnNode newMethodNode = new MethodInsnNode(Opcodes.INVOKESPECIAL, ((MethodInsnNode)oldNode).owner, callOrig.getName(), callOrig.getSignature(), false);
instructions.set(oldNode, newMethodNode);
if (nextIsGeneralizedReturn && next != null && next.getOpcode() != Opcodes.ARETURN)
instructions.set(next, new InsnNode(Opcodes.ARETURN)); // prevent further manipulation by replaceReturn()
}
}
/**
* In this method, concrete Implementations of this class
* can manipulate the bytecode
* @return whether transformation actually happened
*/
protected abstract boolean transform();
}