Java tutorial
/*** * 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; }