org.summer.aop.ltw.Frame.java Source code

Java tutorial

Introduction

Here is the source code for org.summer.aop.ltw.Frame.java

Source

/***
 * Summer: An enhanced, non-invasive, and easy-to-use IoC container and
 * LTW-AOP framework enabling brand-new mocking capabilities in combination
 * with a lightweight rule engine and general meta expression language purely
 * written in Java.
 * It provides component composition at run-time as well as brand-new mocking
 * capabilities that are also applicable to binary third-party libraries, to name
 * only two of all its features.
 * There are barely limitations due to the lack of
 * Java in adding and removing fields and methods to and from a class at run-time.
 *
 * Copyright (C) 2011-2013  Sandro Sebastian Koll
 *
 * This file is part of Summer.
 *
 * Summer is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Summer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Summer. If not, see <http://www.gnu.org/licenses/>.
 */
package org.summer.aop.ltw;

import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * @author Sandro Sebastian Koll
 */
public final class Frame implements Opcodes {
    Frame(Type[] argTypes) {
        for (Type type : argTypes) {
            addLocalVar(toInternalName(type));
        }
    }

    Frame(String thisInternalName, Type[] argTypes) {
        addLocalVar(thisInternalName);
        for (Type type : argTypes) {
            addLocalVar(toInternalName(type));
        }
    }

    Object[] getLocals() {
        Object[] ret = new Object[getLocalSize()];
        for (int i = 0; i < ret.length; ++i) {
            ret[i] = toFrameType(getLocalVar(i));
        }
        return ret;
    }

    int getLocalSize() {
        return locals.size();
    }

    int getCurrentLocalSlots() {
        int ret = 0;
        for (int slots : localSlots) {
            ret += slots;
        }
        return ret;
    }

    int getMaxLocalSlots() {
        return maxLocalSlots;
    }

    Object[] getStack() {
        Object[] ret = new Object[getStackSize()];
        for (int i = 0; i < ret.length; ++i) {
            ret[i] = toFrameType(getOperand(i));
        }
        return ret;
    }

    int getStackSize() {
        return stack.size();
    }

    int getCurrentStackSlots() {
        int ret = 0;
        for (int slots : stackSlots) {
            ret += slots;
        }
        return ret;
    }

    int getMaxStackSlots() {
        return maxStackSlots;
    }

    void resetStack() {
        clearStack();
    }

    void resetStack(String exType) {
        if (exType == null)
            throw new IllegalArgumentException("Exception type may not be null");
        resetStack();
        push(exType);
    }

    void insn(int opcode) {
        switch (opcode) {
        case NOP:
            break;
        case ACONST_NULL:
            aconstNull();
            break;
        case ICONST_0:
        case ICONST_1:
        case ICONST_2:
        case ICONST_3:
        case ICONST_4:
        case ICONST_5:
            iconstX();
            break;
        case POP:
        case POP2:
            pop();
            break;
        case DUP:
        case DUP2:
            dup();
            break;
        case SWAP:
            swap();
            break;
        case DUP_X1:
            dupX1();
            break;
        case IALOAD:
            iaload();
            break;
        case LALOAD:
            laload();
            break;
        case FALOAD:
            faload();
            break;
        case DALOAD:
            daload();
            break;
        case AALOAD:
            aaload();
            break;
        case BALOAD:
            baload();
            break;
        case CALOAD:
            caload();
            break;
        case SALOAD:
            saload();
            break;
        case IASTORE:
        case LASTORE:
        case FASTORE:
        case DASTORE:
        case AASTORE:
        case BASTORE:
        case CASTORE:
        case SASTORE:
            xastore();
            break;
        case IRETURN:
        case LRETURN:
        case FRETURN:
        case DRETURN:
        case ARETURN:
        case RETURN:
            xreturn();
            break;
        case ATHROW:
            athrow();
            break;
        case LCONST_0:
        case LCONST_1:
        case FCONST_0:
        case FCONST_1:
        case FCONST_2:
        case DCONST_0:
        case DCONST_1:
        case DUP_X2:
        case DUP2_X1:
        case DUP2_X2:
        case IADD:
        case LADD:
        case FADD:
        case DADD:
        case ISUB:
        case LSUB:
        case FSUB:
        case DSUB:
        case IMUL:
        case LMUL:
        case FMUL:
        case DMUL:
        case IDIV:
        case LDIV:
        case FDIV:
        case DDIV:
        case IREM:
        case LREM:
        case FREM:
        case DREM:
        case INEG:
        case LNEG:
        case FNEG:
        case DNEG:
        case ISHL:
        case LSHL:
        case ISHR:
        case LSHR:
        case IUSHR:
        case LUSHR:
        case IAND:
        case LAND:
        case IOR:
        case LOR:
        case IXOR:
        case LXOR:
        case LCMP:
        case FCMPL:
        case FCMPG:
        case DCMPL:
        case DCMPG:
        case ICONST_M1:
        case ARRAYLENGTH:
        case MONITORENTER:
        case MONITOREXIT:
            throw new RuntimeException("Instruction " + opcode + " is not supported yet");
        default:
            throw new RuntimeException("Invalid instruction " + opcode);
        }
    }

    void varInsn(int opcode, int slot) {
        switch (opcode) {
        case ILOAD:
            iload(getLocalIndex(slot));
            break;
        case LLOAD:
            lload(getLocalIndex(slot));
            break;
        case FLOAD:
            fload(getLocalIndex(slot));
            break;
        case DLOAD:
            dload(getLocalIndex(slot));
            break;
        case ALOAD:
            aload(getLocalIndex(slot));
            break;
        case ISTORE:
            istore(getLocalIndex(slot));
            break;
        case LSTORE:
            lstore(getLocalIndex(slot));
            break;
        case FSTORE:
            fstore(getLocalIndex(slot));
            break;
        case DSTORE:
            dstore(getLocalIndex(slot));
            break;
        case ASTORE:
            astore(getLocalIndex(slot));
            break;
        default:
            if (opcode < 54)
                aload(getLocalIndex(slot));
            else
                astore(getLocalIndex(slot));
        }
    }

    void typeInsn(int opcode, String type) {
        switch (opcode) {
        case NEW:
            newInsn(type);
            break;
        case ANEWARRAY:
            anewarray(type);
            break;
        case CHECKCAST:
            checkcast(type);
            break;
        case INSTANCEOF:
            throw new RuntimeException("Type instruction INSTANCEOF is not supported yet");
        default:
            throw new RuntimeException("Invalid type instruction " + opcode);
        }
    }

    void ldcInsn(Object cst) {
        String type = toInternalName(Type.getType(cst.getClass()));
        if (type.equals("java/lang/Byte") || type.equals("java/lang/Short") || type.equals("java/lang/Integer")
                || type.equals("java/lang/Boolean") || type.equals("java/lang/Character") || isInteger(type))
            type = "I";
        else if (type.equals("java/lang/Long"))
            type = "J";
        else if (type.equals("java/lang/Float"))
            type = "F";
        else if (type.equals("java/lang/Double"))
            type = "D";
        push(type);
    }

    void intInsn(int opcode, int operand) {
        switch (opcode) {
        case BIPUSH:
            bipush(operand);
            break;
        case SIPUSH:
            sipush(operand);
            break;
        case NEWARRAY:
            newarray(operand);
            break;
        default:
            throw new RuntimeException("Invalid int instruction " + opcode);
        }
    }

    void fieldInsn(int opcode, String owner, String name, String desc) {
        String fieldType = toInternalName(Type.getType(desc));
        switch (opcode) {
        case GETSTATIC:
            getstatic(fieldType);
            break;
        case GETFIELD:
            getfield(fieldType);
            break;
        case PUTSTATIC:
        case PUTFIELD:
            throw new RuntimeException("Field instructions PUTSTATIC and PUTFIELD are not supported yet");
        default:
            throw new RuntimeException("Invalid field instruction " + opcode);
        }
    }

    void methodInsn(int opcode, String owner, String name, String desc) {
        String returnTypeInternalName = toInternalName(Type.getReturnType(desc));
        for (int i = 0; i < Type.getArgumentTypes(desc).length; ++i) {
            pop();
        }
        switch (opcode) {
        case INVOKESTATIC:
            invokestatic(returnTypeInternalName);
            break;
        case INVOKEINTERFACE:
            invokeinterface(returnTypeInternalName);
            break;
        case INVOKESPECIAL:
            invokespecial(returnTypeInternalName);
            break;
        case INVOKEVIRTUAL:
            invokevirtual(returnTypeInternalName);
            break;
        default:
            throw new RuntimeException("Invalid method instruction " + opcode);
        }
    }

    String toInternalName(Type type) {
        switch (type.getSort()) {
        case Type.BYTE:
            return "B";
        case Type.SHORT:
            return "S";
        case Type.INT:
            return "I";
        case Type.BOOLEAN:
            return "Z";
        case Type.CHAR:
            return "C";
        case Type.LONG:
            return "J";
        case Type.FLOAT:
            return "F";
        case Type.DOUBLE:
            return "D";
        case Type.VOID:
            return "V";
        default:
            return type.getInternalName();
        }
    }

    private int getLocalIndex(int slot) {
        int index = 0;
        while (slot > 0) {
            --slot;
            if (isLongOrDouble(locals.get(index)))
                --slot;
            ++index;
        }
        return index;
    }

    private String getLocalVar(int index) {
        return locals.get(index);
    }

    private void refreshMaxLocalSlots() {
        maxLocalSlots = Math.max(maxLocalSlots, getCurrentLocalSlots());
    }

    private void addLocalVar(String type) {
        locals.add(type);
        localSlots.add(getSlots(type));
        refreshMaxLocalSlots();
    }

    private void setLocalVar(int index, String type) {
        if (index < locals.size()) {
            removeLocalVar(index);
            locals.add(index, type);
            localSlots.add(index, getSlots(type));
            refreshMaxLocalSlots();
        } else
            addLocalVar(type);
    }

    private void removeLocalVar(int index) {
        locals.remove(index);
        localSlots.remove(index);
    }

    private String getLastOperand() {
        if (getStackSize() == 0)
            throw new RuntimeException("getLastOperand() failed, because the operand stack is empty");
        return stack.peek();
    }

    private String getOperand(int index) {
        return stack.get(index);
    }

    int getSlots(Type type) {
        return getSlots(toInternalName(type));
    }

    private int getSlots(String type) {
        return isLongOrDouble(type) ? 2 : 1;
    }

    private void clearStack() {
        stack.clear();
        stackSlots.clear();
    }

    public static boolean isByte(String type) {
        return type.equals("B");
    }

    public static boolean isShort(String type) {
        return type.equals("S");
    }

    public static boolean isInt(String type) {
        return type.equals("I");
    }

    public static boolean isBoolean(String type) {
        return type.equals("Z");
    }

    public static boolean isChar(String type) {
        return type.equals("C");
    }

    public static boolean isInteger(String type) {
        return isByte(type) || isShort(type) || isInt(type) || isBoolean(type) || isChar(type);
    }

    public static boolean isLong(String type) {
        return type.equals("J");
    }

    public static boolean isFloat(String type) {
        return type.equals("F");
    }

    public static boolean isDouble(String type) {
        return type.equals("D");
    }

    public static boolean isLongOrDouble(String type) {
        return isLong(type) || isDouble(type);
    }

    public static boolean isReference(String type) {
        return !isPrimitive(type);
    }

    public static boolean isPrimitive(String type) {
        return isInteger(type) || isFloat(type) || isLongOrDouble(type);
    }

    public static boolean isVoid(String type) {
        return type.equals("V");
    }

    private void refreshMaxStackSlots() {
        maxStackSlots = Math.max(maxStackSlots, getCurrentStackSlots());
    }

    private void push(String type) {
        stack.push(type);
        stackSlots.push(getSlots(type));
        refreshMaxStackSlots();
    }

    private String pop() {
        if (getStackSize() == 0)
            throw new RuntimeException("Cannot pop off the last operand, because the operand stack is empty");
        stackSlots.pop();
        return stack.pop();
    }

    private void dup() {
        if (getStackSize() == 0)
            throw new RuntimeException("Cannot duplicate last operand, because the operand stack is empty");
        push(getLastOperand());
    }

    private void swap() {
        if (getStackSize() < 2)
            throw new RuntimeException(
                    "Cannot swap last operands, because the operand stack size is " + getStackSize());
        String lastOperand = pop();
        String lastButOneOperand = pop();
        push(lastOperand);
        push(lastButOneOperand);
    }

    private void dupX1() {
        if (getStackSize() < 2)
            throw new RuntimeException(
                    "Cannot DUP_X1 last operand, because the operand stack size is " + getStackSize());
        String lastOperand = pop();
        String lastButOneOperand = pop();
        push(lastOperand);
        push(lastButOneOperand);
        push(lastOperand);
    }

    private void iload(int index) {
        String localVar = getLocalVar(index);
        if (!isInteger(localVar))
            throw new RuntimeException(
                    "Local variable " + localVar + " at " + index + " is not a byte, short, int, boolean or char");
        push(localVar);
    }

    private void lload(int index) {
        String localVar = getLocalVar(index);
        if (!isDouble(localVar))
            throw new RuntimeException("Local variable " + localVar + " at " + index + " is not a long");
        push(localVar);
    }

    private void fload(int index) {
        String localVar = getLocalVar(index);
        if (!isFloat(localVar))
            throw new RuntimeException("Local variable " + localVar + " at " + index + " is not a float");
        push(localVar);
    }

    private void dload(int index) {
        String localVar = getLocalVar(index);
        if (!isDouble(localVar))
            throw new RuntimeException("Local variable " + localVar + " at " + index + " is not a double");
        push(localVar);
    }

    private void aload(int index) {
        String localVar = getLocalVar(index);
        if (!isReference(localVar))
            localVar = toWrapperType(localVar);
        push(localVar);
    }

    private void istore(int index) {
        if (getStackSize() == 0)
            throw new RuntimeException("ISTORE failed, because the operand stack is empty");
        String lastOperand = pop();
        if (!isInteger(lastOperand))
            throw new RuntimeException(
                    "The last operand " + lastOperand + " is not a byte, short, int, boolean or char");
        setLocalVar(index, lastOperand);
    }

    private void lstore(int index) {
        if (getStackSize() == 0)
            throw new RuntimeException("LSTORE failed, because the operand stack is empty");
        String lastOperand = pop();
        if (!isLong(lastOperand))
            throw new RuntimeException("The last operand " + lastOperand + " is not a long");
        setLocalVar(index, lastOperand);
    }

    private void fstore(int index) {
        if (getStackSize() == 0)
            throw new RuntimeException("FSTORE failed, because the operand stack is empty");
        String lastOperand = pop();
        if (!isFloat(lastOperand))
            throw new RuntimeException("The last operand " + lastOperand + " is not a float");
        setLocalVar(index, lastOperand);
    }

    private void dstore(int index) {
        if (getStackSize() == 0)
            throw new RuntimeException("DSTORE failed, because the operand stack is empty");
        String lastOperand = pop();
        if (!isDouble(lastOperand))
            throw new RuntimeException("The last operand " + lastOperand + " is not a double");
        setLocalVar(index, lastOperand);
    }

    private void astore(int index) {
        if (getStackSize() == 0)
            throw new RuntimeException("ASTORE failed, because the operand stack is empty");
        String lastOperand = pop();
        if (!isReference(lastOperand))
            lastOperand = toWrapperType(lastOperand);
        setLocalVar(index, lastOperand);
    }

    private void bipush(int byteValue) {
        if (byteValue < Byte.MIN_VALUE || byteValue > Byte.MAX_VALUE)
            throw new RuntimeException("Byte " + byteValue + " is out of bounds. Use SIPUSH instead");
        push("B");
    }

    private void sipush(int shortValue) {
        if (shortValue < Short.MIN_VALUE || shortValue > Short.MAX_VALUE)
            throw new RuntimeException("Short " + shortValue + " is out of bounds");
        push("S");
    }

    private void newarray(int operand) {
        switch (operand) {
        case T_BYTE:
            push("[B");
            break;
        case T_SHORT:
            push("[S");
            break;
        case T_INT:
            push("[I");
            break;
        case T_BOOLEAN:
            push("[Z");
            break;
        case T_CHAR:
            push("[C");
            break;
        case T_LONG:
            push("[J");
            break;
        case T_FLOAT:
            push("[F");
            break;
        case T_DOUBLE:
            push("[D");
            break;
        default:
            throw new RuntimeException("Invalid int instruction operand " + operand + " for NEWARRAY");
        }
    }

    private void baload() {
        pop();
        pop();
        push("B");
    }

    private void saload() {
        pop();
        pop();
        push("S");
    }

    private void iaload() {
        pop();
        pop();
        push("I");
    }

    private void caload() {
        pop();
        pop();
        push("C");
    }

    private void laload() {
        pop();
        pop();
        push("J");
    }

    private void faload() {
        pop();
        pop();
        push("F");
    }

    private void daload() {
        pop();
        pop();
        push("D");
    }

    private void aaload() {
        pop();
        String arrayType = pop();
        if (!isReference(arrayType) || !arrayType.startsWith("["))
            throw new RuntimeException("The last but one operand is not an array");
        push(arrayType.substring(1));
    }

    private void newInsn(String type) {
        push(type);
    }

    private void anewarray(String type) {
        pop();
        push("[" + type);
    }

    private void checkcast(String type) {
        pop();
        push(type);
    }

    private void getstatic(String type) {
        push(type);
    }

    private void getfield(String type) {
        pop();
        push(type);
    }

    private void invokestatic(String type) {
        if (!isVoid(type))
            push(type);
    }

    private void invokeinterface(String type) {
        pop();
        if (!isVoid(type))
            push(type);
    }

    private void invokespecial(String type) {
        pop();
        if (!isVoid(type))
            push(type);
    }

    private void invokevirtual(String type) {
        pop();
        if (!isVoid(type))
            push(type);
    }

    private void aconstNull() {
        push("null");
    }

    private void iconstX() {
        push("B");
    }

    private void xastore() {
        pop();
        pop();
        pop();
    }

    private void xreturn() {
        clearStack();
    }

    private void athrow() {
        clearStack();
    }

    String toWrapperType(String type) {
        if (isByte(type))
            return "java/lang/Byte";
        if (isShort(type))
            return "java/lang/Short";
        if (isInt(type))
            return "java/lang/Integer";
        if (isBoolean(type))
            return "java/lang/Boolean";
        if (isChar(type))
            return "java/lang/Character";
        if (isLong(type))
            return "java/lang/Long";
        if (isFloat(type))
            return "java/lang/Float";
        if (isDouble(type))
            return "java/lang/Double";
        return type;
    }

    private Object toFrameType(String type) {
        if (type == null || type.isEmpty() || type.equals("null"))
            return NULL;
        if (type.equals(".") || type.equals("uninitialized"))
            return UNINITIALIZED_THIS;
        if (type.equals("NaN"))
            return TOP;
        if (isInteger(type))
            return INTEGER;
        if (isLong(type))
            return LONG;
        if (isFloat(type))
            return FLOAT;
        if (isDouble(type))
            return DOUBLE;
        return type;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("Frame(locals={");
        Object[] a = getLocals();
        for (int i = 0; i < a.length; ++i) {
            if (i > 0)
                sb.append(" ");
            sb.append(a[i]);
        }
        sb.append("} size=");
        sb.append(a.length);
        sb.append(" slots=[");
        sb.append(getCurrentLocalSlots());
        sb.append("/");
        sb.append(getMaxLocalSlots());
        a = getStack();
        sb.append("]  :::  stack={");
        for (int i = 0; i < a.length; ++i) {
            if (i > 0)
                sb.append(" ");
            sb.append(a[i]);
        }
        sb.append("} size=");
        sb.append(a.length);
        sb.append(" slots=[");
        sb.append(getCurrentStackSlots());
        sb.append("/");
        sb.append(getMaxStackSlots());
        sb.append("])");
        return sb.toString();
    }

    private List<Integer> localSlots = new ArrayList<>();
    private List<String> locals = new ArrayList<>();
    private int maxLocalSlots = 0;
    private Stack<Integer> stackSlots = new Stack<>();
    private Stack<String> stack = new Stack<>();
    private int maxStackSlots = 0;
}