org.eclipse.objectteams.otredyn.bytecode.asm.AbstractTransformableClassNode.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.objectteams.otredyn.bytecode.asm.AbstractTransformableClassNode.java

Source

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

/**
 * 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
     */
    @SuppressWarnings("unchecked")
    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
     */
    @SuppressWarnings("unchecked")
    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;
    }

    /**
     * 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.insertBefore(orgMethodNode, new InsnNode(Opcodes.ARETURN));
                    instructions.remove(orgMethodNode);
                }
            }
        } 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
            return new LdcInsnNode(constant);
    }

    /**
     * In this method, concrete Implementations of this class
     * can manipulate the bytecode
     * @return whether transformation actually happened
     */
    protected abstract boolean transform();
}