Java tutorial
/* ------------------------------------------------------------------------ * Copyright 2009 Tim Vernum * ------------------------------------------------------------------------ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ------------------------------------------------------------------------ */ package org.adjective.stout.tools; import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Attribute; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.util.AbstractVisitor; /** * @author <a href="http://blog.adjective.org/">Tim Vernum</a> */ class StackVisualiserMethodVisitor extends AbstractVisitor implements MethodVisitor { private static final Type OBJECT_TYPE = Type.getType(Object.class); class StackValue { public final String description; public final Type type; public StackValue(String valueDescription, Type valueType) { description = valueDescription; type = valueType; } public StackValue(String typeDescriptor) { this.type = Type.getType(typeDescriptor); this.description = type.getClassName(); } public String toString() { return (this.type == null ? ' ' : this.type) + " : " + this.description; } } private final PrintStream _output; private final List<StackValue> _stack; private final Type _this; private final Type _superType; private final boolean _isStatic; private final Type[] _arguments; private final Map<Label, StringBuilder> _labels; public StackVisualiserMethodVisitor(PrintStream output, Type thisType, Type superType, boolean isStatic, Type[] arguments) { _output = output; _this = thisType; _superType = superType; _isStatic = isStatic; _arguments = arguments; _stack = new ArrayList<StackValue>(); _labels = new HashMap<Label, StringBuilder>(); } public AnnotationVisitor visitAnnotation(String desc, boolean visible) { return null; } public AnnotationVisitor visitAnnotationDefault() { return null; } public void visitAttribute(Attribute attr) { // no-op } public void visitFrame(int type, int local, Object[] local2, int stack, Object[] stack2) { // no-op } public void visitCode() { _output.println("{ /*******/"); } public void visitEnd() { _output.println("} /*******/"); } private void print(int opcode, String string) { _output.println(OPCODES[opcode] + " " + string); for (StackValue value : _stack) { _output.println("\t\t== " + value); } _output.println(); } private void push(String description, Type type) { StackValue value = new StackValue(description, type); push(value); if (type.getSize() == 2) { _stack.add(new StackValue("(.." + description + "..)", null)); } } private void push(StackValue value) { _stack.add(value); } private void push(String description, String typeDescriptor) { push(description, Type.getType(typeDescriptor)); } private void push(String description, Class<?> type) { push(description, Type.getType(type)); } private StackValue peek() { return peek(1); } private StackValue peek(int n) { int index = _stack.size() - n; if (index < 0) { return new StackValue("???", OBJECT_TYPE); } return _stack.get(index); } private StackValue pop() { return popValue(true); } private StackValue popValue(boolean checkDoubleWidth) { if (_stack.isEmpty()) { error("Attempt to pop value from empty stack"); return new StackValue("*DNE*", OBJECT_TYPE); } StackValue value = _stack.remove(_stack.size() - 1); _output.println("\t\t<< " + value); if (checkDoubleWidth && value.type == null) { error("Attempt to pop second half of double width value " + peek()); } return value; } private void error(String message) { _output.println("*** ERROR *** " + message); } private StackValue pop(Type... types) { StackValue value = pop(types[0].getSize()); checkType(value, types); return value; } /** * @return the last vaue pop'ed */ private StackValue pop(int count) { if (count <= 0) { throw new IllegalArgumentException("Attempt to pop " + count + " values"); } if (count == 1) { return popValue(true); } else if (count == 2) { popValue(false); return popValue(true); } else { pop(2); return pop(count - 2); } } public void visitFieldInsn(int opcode, String owner, String name, String desc) { String description = owner + "." + name; switch (opcode) { case Opcodes.GETFIELD: pop(); push(description, desc); break; case Opcodes.PUTFIELD: pop(2); break; case Opcodes.GETSTATIC: push(description, desc); break; case Opcodes.PUTSTATIC: pop(1); break; } print(opcode, description); } public void visitIincInsn(int var, int increment) { print(Opcodes.IINC, Integer.toString(var) + " " + increment); } public void visitInsn(int opcode) { switch (opcode) { case Opcodes.NOP: break; case Opcodes.ACONST_NULL: push("null", Object.class); break; case Opcodes.ICONST_M1: case Opcodes.ICONST_0: case Opcodes.ICONST_1: case Opcodes.ICONST_2: case Opcodes.ICONST_3: case Opcodes.ICONST_4: case Opcodes.ICONST_5: push(Integer.toString(opcode - Opcodes.ICONST_0), Type.INT_TYPE); break; case Opcodes.LCONST_0: case Opcodes.LCONST_1: push(Integer.toString(opcode - Opcodes.LCONST_0), Type.LONG_TYPE); break; case Opcodes.FCONST_0: case Opcodes.FCONST_1: case Opcodes.FCONST_2: push(Integer.toString(opcode - Opcodes.FCONST_0), Type.FLOAT_TYPE); break; case Opcodes.DCONST_0: case Opcodes.DCONST_1: push(Integer.toString(opcode - Opcodes.DCONST_0), Type.DOUBLE_TYPE); break; case Opcodes.IALOAD: case Opcodes.LALOAD: case Opcodes.FALOAD: case Opcodes.DALOAD: case Opcodes.AALOAD: case Opcodes.BALOAD: case Opcodes.CALOAD: case Opcodes.SALOAD: { Type opType = getType(Opcodes.IALOAD, opcode); StackValue idx = pop(Type.INT_TYPE); StackValue arr = popArray(opType); push(arr.description + "[" + idx.description + "]", opType); } break; case Opcodes.IASTORE: case Opcodes.LASTORE: case Opcodes.FASTORE: case Opcodes.DASTORE: case Opcodes.AASTORE: case Opcodes.BASTORE: case Opcodes.CASTORE: case Opcodes.SASTORE: { Type opType = getType(Opcodes.IASTORE, opcode); pop(opType); pop(Type.INT_TYPE); popArray(opType); } break; case Opcodes.POP: pop(); break; case Opcodes.POP2: pop(2); break; case Opcodes.DUP: push(peek()); break; case Opcodes.DUP2: push(peek(2)); push(peek(1)); break; case Opcodes.DUP_X1: { StackValue a = pop(); StackValue b = pop(); push(a); push(b); push(a); } break; case Opcodes.DUP_X2: { StackValue a = pop(); StackValue b = pop(); StackValue c = pop(); push(a); push(c); push(b); push(a); } break; case Opcodes.DUP2_X1: { StackValue a = popValue(false); StackValue b = pop(); StackValue c = pop(); push(b); push(a); push(c); push(b); push(a); } case Opcodes.DUP2_X2: { StackValue a = popValue(false); StackValue b = pop(); StackValue c = popValue(false); StackValue d = pop(); push(b); push(a); push(d); push(c); push(b); push(a); } break; case Opcodes.SWAP: { StackValue a = pop(); StackValue b = pop(); push(a); push(b); } break; case Opcodes.IADD: case Opcodes.LADD: case Opcodes.FADD: case Opcodes.DADD: math(Opcodes.IADD, opcode, "+"); break; case Opcodes.ISUB: case Opcodes.LSUB: case Opcodes.FSUB: case Opcodes.DSUB: math(Opcodes.ISUB, opcode, "-"); break; case Opcodes.IMUL: case Opcodes.LMUL: case Opcodes.FMUL: case Opcodes.DMUL: math(Opcodes.IMUL, opcode, "*"); break; case Opcodes.IDIV: case Opcodes.LDIV: case Opcodes.FDIV: case Opcodes.DDIV: math(Opcodes.IDIV, opcode, "/"); break; case Opcodes.IREM: case Opcodes.LREM: case Opcodes.FREM: case Opcodes.DREM: math(Opcodes.IREM, opcode, "%"); break; case Opcodes.IAND: case Opcodes.LAND: math(Opcodes.IAND, opcode, "&"); break; case Opcodes.IOR: case Opcodes.LOR: math(Opcodes.IOR, opcode, "|"); break; case Opcodes.IXOR: case Opcodes.LXOR: math(Opcodes.IXOR, opcode, "^"); break; case Opcodes.INEG: case Opcodes.LNEG: case Opcodes.FNEG: case Opcodes.DNEG: { Type type = getType(Opcodes.INEG, opcode); StackValue a = pop(type); push("-" + a.description, type); } break; case Opcodes.ISHL: case Opcodes.LSHL: { Type type = getType(Opcodes.ISHL, opcode); StackValue n = pop(Type.INT_TYPE); StackValue a = pop(type); push(a.description + "<<" + n.description, type); } break; case Opcodes.ISHR: case Opcodes.LSHR: { Type type = getType(Opcodes.ISHR, opcode); StackValue n = pop(Type.INT_TYPE); StackValue a = pop(type); push(a.description + ">>" + n.description, type); } break; case Opcodes.IUSHR: case Opcodes.LUSHR: { Type type = getType(Opcodes.IUSHR, opcode); StackValue n = pop(Type.INT_TYPE); StackValue a = pop(type); push(a.description + ">>>" + n.description, type); } case Opcodes.LCMP: { StackValue a = pop(Type.LONG_TYPE); StackValue b = pop(Type.LONG_TYPE); push(a.description + " cmp " + b.description + " {-1|0|1}", Type.LONG_TYPE); } break; case Opcodes.I2L: case Opcodes.I2F: case Opcodes.I2D: case Opcodes.L2I: case Opcodes.L2F: case Opcodes.L2D: case Opcodes.F2I: case Opcodes.F2L: case Opcodes.F2D: case Opcodes.D2I: case Opcodes.D2L: case Opcodes.D2F: case Opcodes.I2B: case Opcodes.I2C: case Opcodes.I2S: cast(opcode); break; case Opcodes.ARETURN: case Opcodes.ATHROW: popObject(); break; case Opcodes.RETURN: break; default: throw new IllegalArgumentException("Unsupported opcode " + opcode + " - " + OPCODES[opcode]); } print(opcode, ""); /* * FCMPL, FCMPG, DCMPL, DCMPG, IRETURN, LRETURN, * FRETURN, DRETURN, ARETURN, RETURN, ARRAYLENGTH, ATHROW, * MONITORENTER, or MONITOREXIT */ } private StackValue popObject() { StackValue pop = pop(); if (!pop.type.getDescriptor().startsWith("L")) { error("Expected object on stack, but was " + pop); } return pop; } private void cast(int opcode) { String mnemonic = OPCODES[opcode]; Type from = getType(mnemonic.charAt(0)); Type to = getType(mnemonic.charAt(2)); String pop = pop(from).description; push(pop, to); } private Type getType(char ch) { switch (ch) { case 'I': return Type.INT_TYPE; case 'L': return Type.LONG_TYPE; case 'F': return Type.FLOAT_TYPE; case 'D': return Type.DOUBLE_TYPE; case 'B': return Type.BYTE_TYPE; case 'C': return Type.CHAR_TYPE; case 'S': return Type.SHORT_TYPE; } throw new IllegalArgumentException("Bad type mnemonic " + ch); } private StackValue popArray(Type elementType) { StackValue arr = pop(); if (arr.type.getDimensions() == 0) { error("Attempt to load an element into the non-array " + arr); } else { checkType(arr.type.getElementType(), arr, elementType); } return arr; } private void checkType(StackValue value, Type... types) { checkType(value.type, value, types); } private void checkType(Type stackType, StackValue stackItem, Type... allowed) { for (Type required : allowed) { if (required.equals(stackType)) { return; } if ("Z".equals(required.getDescriptor())) { if ("I".equals(stackType.getDescriptor())) { return; } } if (required.getSort() == Type.OBJECT) { if (Object.class.getName().equals(required.getClassName())) { return; } if (Object.class.getName().equals(stackType.getClassName())) { return; } Type type = stackType; if (type.equals(_this)) { type = _superType; } try { Class<?> stackClass = Class.forName(type.getClassName()); if (checkClass(stackClass, required)) { return; } } catch (ClassNotFoundException e) { // :( error("Cannot load class : " + e); continue; } } } error("Incorrect type '" + stackType + "' (at item " + stackItem + ") Expected one of: " + Arrays.toString(allowed)); } @SuppressWarnings("unchecked") private boolean checkClass(Class<?> cls, Type required) { if (cls == null) { return false; } if (cls.getName().equals(required.getClassName())) { return true; } if (checkClass(cls.getSuperclass(), required)) { return true; } for (Class iface : cls.getInterfaces()) { if (checkClass(iface, required)) { return true; } } return false; } private Type getType(int base, int opcode) { int t = opcode - base; switch (t) { case 0: return Type.INT_TYPE; case 1: return Type.LONG_TYPE; case 2: return Type.FLOAT_TYPE; case 3: return Type.DOUBLE_TYPE; case 4: return OBJECT_TYPE; case 5: return Type.BYTE_TYPE; case 6: return Type.CHAR_TYPE; case 7: return Type.SHORT_TYPE; } throw new IllegalArgumentException( "Opcode " + OPCODES[opcode] + " is not an extension of " + OPCODES[base]); } private void math(int base, int opcode, String operator) { Type type = getType(base, opcode); StackValue a = pop(type); StackValue b = pop(type); push(a.description + operator + b.description, type); } public void visitIntInsn(int opcode, int operand) { throw new IllegalArgumentException("Unsupported opcode " + OPCODES[opcode]); } public void visitJumpInsn(int opcode, Label label) { switch (opcode) { case Opcodes.IFEQ: pop(Type.INT_TYPE, Type.BOOLEAN_TYPE); break; case Opcodes.GOTO: break; default: throw new IllegalArgumentException("Unsupported opcode " + OPCODES[opcode]); } print(opcode, label.toString()); label(label, OPCODES[opcode]); } public void visitLabel(Label label) { CharSequence message = _labels.get(label); if (message == null) { message = ""; } else { message = " : " + message; } _output.println("-=[ " + label + message + " ]=-"); // No-op } public void visitLdcInsn(Object cst) { Type type; if (cst instanceof String) { type = Type.getType(String.class); } else if (cst instanceof Long) { type = Type.LONG_TYPE; } else { error("Unknown LDC type " + cst.getClass()); type = Type.INT_TYPE; } push(cst.toString(), type); print(Opcodes.LDC, cst.toString()); } public void visitLineNumber(int line, Label start) { // No-op } public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { // No-op } public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { throw new IllegalArgumentException("Unsupported instruction - SWITCH "); } public void visitMaxs(int maxStack, int maxLocals) { // No-op } public void visitMethodInsn(int opcode, String owner, String name, String desc) { Type[] argumentTypes = Type.getArgumentTypes(desc); Type returnType = Type.getReturnType(desc); StringBuilder args = new StringBuilder(); for (int i = argumentTypes.length; i > 0; i--) { args.insert(0, pop(argumentTypes[i - 1]).description + ","); } switch (opcode) { case Opcodes.INVOKESTATIC: break; case Opcodes.INVOKEINTERFACE: case Opcodes.INVOKEVIRTUAL: case Opcodes.INVOKESPECIAL: pop(Type.getObjectType(owner)); break; default: throw new IllegalArgumentException("Unsupported opcode " + OPCODES[opcode]); } String description = simpleName(owner) + "." + name + "(" + args + ")"; if (!returnType.equals(Type.VOID_TYPE)) { push(description, returnType); } print(opcode, description + " {" + desc + "}"); } private String simpleName(String owner) { int idx = owner.lastIndexOf('/'); return owner.substring(idx + 1); } public void visitMultiANewArrayInsn(String desc, int dims) { throw new IllegalArgumentException("Unsupported - Multi New Array " + desc); } public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { return null; } public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) { throw new IllegalArgumentException("Unsupported instruction - SWITCH"); } public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { print(0, "TRY(" + start + "," + end + ") CATCH(" + type + ") : " + handler); label(start, "Try:START"); label(end, "Try:END"); label(handler, "Catch:" + type); } private void label(Label label, String message) { if (_labels.containsKey(label)) { _labels.get(label).append(",").append(message); } else { _labels.put(label, new StringBuilder(message)); } } public void visitTypeInsn(int opcode, String type) { String description = type.replace('/', '.'); Type objectType = Type.getObjectType(type); switch (opcode) { case Opcodes.NEW: push(simpleName(type), objectType); break; case Opcodes.ANEWARRAY: StackValue size = pop(Type.INT_TYPE); push(simpleName(type) + "[" + size.description + "]", arrayOf(objectType)); break; case Opcodes.CHECKCAST: StackValue pop = pop(); push("(" + type + ") " + pop.description, objectType); break; default: throw new IllegalArgumentException("Unsupported opcode " + OPCODES[opcode]); } print(opcode, description); } private Type arrayOf(Type element) { return Type.getType("[" + element.getDescriptor()); } public void visitVarInsn(int opcode, int var) { String description = (var == 0 ? "this" : "v" + var); switch (opcode) { case Opcodes.ISTORE: case Opcodes.LSTORE: case Opcodes.FSTORE: case Opcodes.DSTORE: case Opcodes.ASTORE: { Type type = getType(Opcodes.ISTORE, opcode); pop(type); } break; case Opcodes.ILOAD: case Opcodes.LLOAD: case Opcodes.FLOAD: case Opcodes.DLOAD: case Opcodes.ALOAD: { Type type = getType(Opcodes.ILOAD, opcode); if (_isStatic) { var++; } if (var == 0) { type = _this; } else if (var <= _arguments.length) { type = _arguments[var - 1]; } push(description, type); } break; default: throw new IllegalArgumentException("Unsupported opcode " + OPCODES[opcode]); } print(opcode, description); } }