blob: c94e91a4a8a579b5a972aef16dadbe12ac888270 [file] [log] [blame]
/*
* Copyright 2003,2004 The Apache Software Foundation
*
* 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.mockito.cglib.core;
import java.io.*;
import java.util.*;
import org.mockito.asm.*;
/**
* @author Juozas Baliuka, Chris Nokleberg
*/
public class CodeEmitter extends LocalVariablesSorter {
private static final Signature BOOLEAN_VALUE =
TypeUtils.parseSignature("boolean booleanValue()");
private static final Signature CHAR_VALUE =
TypeUtils.parseSignature("char charValue()");
private static final Signature LONG_VALUE =
TypeUtils.parseSignature("long longValue()");
private static final Signature DOUBLE_VALUE =
TypeUtils.parseSignature("double doubleValue()");
private static final Signature FLOAT_VALUE =
TypeUtils.parseSignature("float floatValue()");
private static final Signature INT_VALUE =
TypeUtils.parseSignature("int intValue()");
private static final Signature CSTRUCT_NULL =
TypeUtils.parseConstructor("");
private static final Signature CSTRUCT_STRING =
TypeUtils.parseConstructor("String");
public static final int ADD = Constants.IADD;
public static final int MUL = Constants.IMUL;
public static final int XOR = Constants.IXOR;
public static final int USHR = Constants.IUSHR;
public static final int SUB = Constants.ISUB;
public static final int DIV = Constants.IDIV;
public static final int NEG = Constants.INEG;
public static final int REM = Constants.IREM;
public static final int AND = Constants.IAND;
public static final int OR = Constants.IOR;
public static final int GT = Constants.IFGT;
public static final int LT = Constants.IFLT;
public static final int GE = Constants.IFGE;
public static final int LE = Constants.IFLE;
public static final int NE = Constants.IFNE;
public static final int EQ = Constants.IFEQ;
private ClassEmitter ce;
private State state;
private static class State
extends MethodInfo
{
ClassInfo classInfo;
int access;
Signature sig;
Type[] argumentTypes;
int localOffset;
Type[] exceptionTypes;
State(ClassInfo classInfo, int access, Signature sig, Type[] exceptionTypes) {
this.classInfo = classInfo;
this.access = access;
this.sig = sig;
this.exceptionTypes = exceptionTypes;
localOffset = TypeUtils.isStatic(access) ? 0 : 1;
argumentTypes = sig.getArgumentTypes();
}
public ClassInfo getClassInfo() {
return classInfo;
}
public int getModifiers() {
return access;
}
public Signature getSignature() {
return sig;
}
public Type[] getExceptionTypes() {
return exceptionTypes;
}
public Attribute getAttribute() {
// TODO
return null;
}
}
CodeEmitter(ClassEmitter ce, MethodVisitor mv, int access, Signature sig, Type[] exceptionTypes) {
super(access, sig.getDescriptor(), mv);
this.ce = ce;
state = new State(ce.getClassInfo(), access, sig, exceptionTypes);
}
public CodeEmitter(CodeEmitter wrap) {
super(wrap);
this.ce = wrap.ce;
this.state = wrap.state;
}
public boolean isStaticHook() {
return false;
}
public Signature getSignature() {
return state.sig;
}
public Type getReturnType() {
return state.sig.getReturnType();
}
public MethodInfo getMethodInfo() {
return state;
}
public ClassEmitter getClassEmitter() {
return ce;
}
public void end_method() {
visitMaxs(0, 0);
}
public Block begin_block() {
return new Block(this);
}
public void catch_exception(Block block, Type exception) {
if (block.getEnd() == null) {
throw new IllegalStateException("end of block is unset");
}
mv.visitTryCatchBlock(block.getStart(),
block.getEnd(),
mark(),
exception.getInternalName());
}
public void goTo(Label label) { mv.visitJumpInsn(Constants.GOTO, label); }
public void ifnull(Label label) { mv.visitJumpInsn(Constants.IFNULL, label); }
public void ifnonnull(Label label) { mv.visitJumpInsn(Constants.IFNONNULL, label); }
public void if_jump(int mode, Label label) {
mv.visitJumpInsn(mode, label);
}
public void if_icmp(int mode, Label label) {
if_cmp(Type.INT_TYPE, mode, label);
}
public void if_cmp(Type type, int mode, Label label) {
int intOp = -1;
int jumpmode = mode;
switch (mode) {
case GE: jumpmode = LT; break;
case LE: jumpmode = GT; break;
}
switch (type.getSort()) {
case Type.LONG:
mv.visitInsn(Constants.LCMP);
break;
case Type.DOUBLE:
mv.visitInsn(Constants.DCMPG);
break;
case Type.FLOAT:
mv.visitInsn(Constants.FCMPG);
break;
case Type.ARRAY:
case Type.OBJECT:
switch (mode) {
case EQ:
mv.visitJumpInsn(Constants.IF_ACMPEQ, label);
return;
case NE:
mv.visitJumpInsn(Constants.IF_ACMPNE, label);
return;
}
throw new IllegalArgumentException("Bad comparison for type " + type);
default:
switch (mode) {
case EQ: intOp = Constants.IF_ICMPEQ; break;
case NE: intOp = Constants.IF_ICMPNE; break;
case GE: swap(); /* fall through */
case LT: intOp = Constants.IF_ICMPLT; break;
case LE: swap(); /* fall through */
case GT: intOp = Constants.IF_ICMPGT; break;
}
mv.visitJumpInsn(intOp, label);
return;
}
if_jump(jumpmode, label);
}
public void pop() { mv.visitInsn(Constants.POP); }
public void pop2() { mv.visitInsn(Constants.POP2); }
public void dup() { mv.visitInsn(Constants.DUP); }
public void dup2() { mv.visitInsn(Constants.DUP2); }
public void dup_x1() { mv.visitInsn(Constants.DUP_X1); }
public void dup_x2() { mv.visitInsn(Constants.DUP_X2); }
public void dup2_x1() { mv.visitInsn(Constants.DUP2_X1); }
public void dup2_x2() { mv.visitInsn(Constants.DUP2_X2); }
public void swap() { mv.visitInsn(Constants.SWAP); }
public void aconst_null() { mv.visitInsn(Constants.ACONST_NULL); }
public void swap(Type prev, Type type) {
if (type.getSize() == 1) {
if (prev.getSize() == 1) {
swap(); // same as dup_x1(), pop();
} else {
dup_x2();
pop();
}
} else {
if (prev.getSize() == 1) {
dup2_x1();
pop2();
} else {
dup2_x2();
pop2();
}
}
}
public void monitorenter() { mv.visitInsn(Constants.MONITORENTER); }
public void monitorexit() { mv.visitInsn(Constants.MONITOREXIT); }
public void math(int op, Type type) { mv.visitInsn(type.getOpcode(op)); }
public void array_load(Type type) { mv.visitInsn(type.getOpcode(Constants.IALOAD)); }
public void array_store(Type type) { mv.visitInsn(type.getOpcode(Constants.IASTORE)); }
/**
* Casts from one primitive numeric type to another
*/
public void cast_numeric(Type from, Type to) {
if (from != to) {
if (from == Type.DOUBLE_TYPE) {
if (to == Type.FLOAT_TYPE) {
mv.visitInsn(Constants.D2F);
} else if (to == Type.LONG_TYPE) {
mv.visitInsn(Constants.D2L);
} else {
mv.visitInsn(Constants.D2I);
cast_numeric(Type.INT_TYPE, to);
}
} else if (from == Type.FLOAT_TYPE) {
if (to == Type.DOUBLE_TYPE) {
mv.visitInsn(Constants.F2D);
} else if (to == Type.LONG_TYPE) {
mv.visitInsn(Constants.F2L);
} else {
mv.visitInsn(Constants.F2I);
cast_numeric(Type.INT_TYPE, to);
}
} else if (from == Type.LONG_TYPE) {
if (to == Type.DOUBLE_TYPE) {
mv.visitInsn(Constants.L2D);
} else if (to == Type.FLOAT_TYPE) {
mv.visitInsn(Constants.L2F);
} else {
mv.visitInsn(Constants.L2I);
cast_numeric(Type.INT_TYPE, to);
}
} else {
if (to == Type.BYTE_TYPE) {
mv.visitInsn(Constants.I2B);
} else if (to == Type.CHAR_TYPE) {
mv.visitInsn(Constants.I2C);
} else if (to == Type.DOUBLE_TYPE) {
mv.visitInsn(Constants.I2D);
} else if (to == Type.FLOAT_TYPE) {
mv.visitInsn(Constants.I2F);
} else if (to == Type.LONG_TYPE) {
mv.visitInsn(Constants.I2L);
} else if (to == Type.SHORT_TYPE) {
mv.visitInsn(Constants.I2S);
}
}
}
}
public void push(int i) {
if (i < -1) {
mv.visitLdcInsn(new Integer(i));
} else if (i <= 5) {
mv.visitInsn(TypeUtils.ICONST(i));
} else if (i <= Byte.MAX_VALUE) {
mv.visitIntInsn(Constants.BIPUSH, i);
} else if (i <= Short.MAX_VALUE) {
mv.visitIntInsn(Constants.SIPUSH, i);
} else {
mv.visitLdcInsn(new Integer(i));
}
}
public void push(long value) {
if (value == 0L || value == 1L) {
mv.visitInsn(TypeUtils.LCONST(value));
} else {
mv.visitLdcInsn(new Long(value));
}
}
public void push(float value) {
if (value == 0f || value == 1f || value == 2f) {
mv.visitInsn(TypeUtils.FCONST(value));
} else {
mv.visitLdcInsn(new Float(value));
}
}
public void push(double value) {
if (value == 0d || value == 1d) {
mv.visitInsn(TypeUtils.DCONST(value));
} else {
mv.visitLdcInsn(new Double(value));
}
}
public void push(String value) {
mv.visitLdcInsn(value);
}
public void newarray() {
newarray(Constants.TYPE_OBJECT);
}
public void newarray(Type type) {
if (TypeUtils.isPrimitive(type)) {
mv.visitIntInsn(Constants.NEWARRAY, TypeUtils.NEWARRAY(type));
} else {
emit_type(Constants.ANEWARRAY, type);
}
}
public void arraylength() {
mv.visitInsn(Constants.ARRAYLENGTH);
}
public void load_this() {
if (TypeUtils.isStatic(state.access)) {
throw new IllegalStateException("no 'this' pointer within static method");
}
mv.visitVarInsn(Constants.ALOAD, 0);
}
/**
* Pushes all of the arguments of the current method onto the stack.
*/
public void load_args() {
load_args(0, state.argumentTypes.length);
}
/**
* Pushes the specified argument of the current method onto the stack.
* @param index the zero-based index into the argument list
*/
public void load_arg(int index) {
load_local(state.argumentTypes[index],
state.localOffset + skipArgs(index));
}
// zero-based (see load_this)
public void load_args(int fromArg, int count) {
int pos = state.localOffset + skipArgs(fromArg);
for (int i = 0; i < count; i++) {
Type t = state.argumentTypes[fromArg + i];
load_local(t, pos);
pos += t.getSize();
}
}
private int skipArgs(int numArgs) {
int amount = 0;
for (int i = 0; i < numArgs; i++) {
amount += state.argumentTypes[i].getSize();
}
return amount;
}
private void load_local(Type t, int pos) {
// TODO: make t == null ok?
mv.visitVarInsn(t.getOpcode(Constants.ILOAD), pos);
}
private void store_local(Type t, int pos) {
// TODO: make t == null ok?
mv.visitVarInsn(t.getOpcode(Constants.ISTORE), pos);
}
public void iinc(Local local, int amount) {
mv.visitIincInsn(local.getIndex(), amount);
}
public void store_local(Local local) {
store_local(local.getType(), local.getIndex());
}
public void load_local(Local local) {
load_local(local.getType(), local.getIndex());
}
public void return_value() {
mv.visitInsn(state.sig.getReturnType().getOpcode(Constants.IRETURN));
}
public void getfield(String name) {
ClassEmitter.FieldInfo info = ce.getFieldInfo(name);
int opcode = TypeUtils.isStatic(info.access) ? Constants.GETSTATIC : Constants.GETFIELD;
emit_field(opcode, ce.getClassType(), name, info.type);
}
public void putfield(String name) {
ClassEmitter.FieldInfo info = ce.getFieldInfo(name);
int opcode = TypeUtils.isStatic(info.access) ? Constants.PUTSTATIC : Constants.PUTFIELD;
emit_field(opcode, ce.getClassType(), name, info.type);
}
public void super_getfield(String name, Type type) {
emit_field(Constants.GETFIELD, ce.getSuperType(), name, type);
}
public void super_putfield(String name, Type type) {
emit_field(Constants.PUTFIELD, ce.getSuperType(), name, type);
}
public void super_getstatic(String name, Type type) {
emit_field(Constants.GETSTATIC, ce.getSuperType(), name, type);
}
public void super_putstatic(String name, Type type) {
emit_field(Constants.PUTSTATIC, ce.getSuperType(), name, type);
}
public void getfield(Type owner, String name, Type type) {
emit_field(Constants.GETFIELD, owner, name, type);
}
public void putfield(Type owner, String name, Type type) {
emit_field(Constants.PUTFIELD, owner, name, type);
}
public void getstatic(Type owner, String name, Type type) {
emit_field(Constants.GETSTATIC, owner, name, type);
}
public void putstatic(Type owner, String name, Type type) {
emit_field(Constants.PUTSTATIC, owner, name, type);
}
// package-protected for EmitUtils, try to fix
void emit_field(int opcode, Type ctype, String name, Type ftype) {
mv.visitFieldInsn(opcode,
ctype.getInternalName(),
name,
ftype.getDescriptor());
}
public void super_invoke() {
super_invoke(state.sig);
}
public void super_invoke(Signature sig) {
emit_invoke(Constants.INVOKESPECIAL, ce.getSuperType(), sig);
}
public void invoke_constructor(Type type) {
invoke_constructor(type, CSTRUCT_NULL);
}
public void super_invoke_constructor() {
invoke_constructor(ce.getSuperType());
}
public void invoke_constructor_this() {
invoke_constructor(ce.getClassType());
}
private void emit_invoke(int opcode, Type type, Signature sig) {
if (sig.getName().equals(Constants.CONSTRUCTOR_NAME) &&
((opcode == Constants.INVOKEVIRTUAL) ||
(opcode == Constants.INVOKESTATIC))) {
// TODO: error
}
mv.visitMethodInsn(opcode,
type.getInternalName(),
sig.getName(),
sig.getDescriptor());
}
public void invoke_interface(Type owner, Signature sig) {
emit_invoke(Constants.INVOKEINTERFACE, owner, sig);
}
public void invoke_virtual(Type owner, Signature sig) {
emit_invoke(Constants.INVOKEVIRTUAL, owner, sig);
}
public void invoke_static(Type owner, Signature sig) {
emit_invoke(Constants.INVOKESTATIC, owner, sig);
}
public void invoke_virtual_this(Signature sig) {
invoke_virtual(ce.getClassType(), sig);
}
public void invoke_static_this(Signature sig) {
invoke_static(ce.getClassType(), sig);
}
public void invoke_constructor(Type type, Signature sig) {
emit_invoke(Constants.INVOKESPECIAL, type, sig);
}
public void invoke_constructor_this(Signature sig) {
invoke_constructor(ce.getClassType(), sig);
}
public void super_invoke_constructor(Signature sig) {
invoke_constructor(ce.getSuperType(), sig);
}
public void new_instance_this() {
new_instance(ce.getClassType());
}
public void new_instance(Type type) {
emit_type(Constants.NEW, type);
}
private void emit_type(int opcode, Type type) {
String desc;
if (TypeUtils.isArray(type)) {
desc = type.getDescriptor();
} else {
desc = type.getInternalName();
}
mv.visitTypeInsn(opcode, desc);
}
public void aaload(int index) {
push(index);
aaload();
}
public void aaload() { mv.visitInsn(Constants.AALOAD); }
public void aastore() { mv.visitInsn(Constants.AASTORE); }
public void athrow() { mv.visitInsn(Constants.ATHROW); }
public Label make_label() {
return new Label();
}
public Local make_local() {
return make_local(Constants.TYPE_OBJECT);
}
public Local make_local(Type type) {
return new Local(newLocal(type.getSize()), type);
}
public void checkcast_this() {
checkcast(ce.getClassType());
}
public void checkcast(Type type) {
if (!type.equals(Constants.TYPE_OBJECT)) {
emit_type(Constants.CHECKCAST, type);
}
}
public void instance_of(Type type) {
emit_type(Constants.INSTANCEOF, type);
}
public void instance_of_this() {
instance_of(ce.getClassType());
}
public void process_switch(int[] keys, ProcessSwitchCallback callback) {
float density;
if (keys.length == 0) {
density = 0;
} else {
density = (float)keys.length / (keys[keys.length - 1] - keys[0] + 1);
}
process_switch(keys, callback, density >= 0.5f);
}
public void process_switch(int[] keys, ProcessSwitchCallback callback, boolean useTable) {
if (!isSorted(keys))
throw new IllegalArgumentException("keys to switch must be sorted ascending");
Label def = make_label();
Label end = make_label();
try {
if (keys.length > 0) {
int len = keys.length;
int min = keys[0];
int max = keys[len - 1];
int range = max - min + 1;
if (useTable) {
Label[] labels = new Label[range];
Arrays.fill(labels, def);
for (int i = 0; i < len; i++) {
labels[keys[i] - min] = make_label();
}
mv.visitTableSwitchInsn(min, max, def, labels);
for (int i = 0; i < range; i++) {
Label label = labels[i];
if (label != def) {
mark(label);
callback.processCase(i + min, end);
}
}
} else {
Label[] labels = new Label[len];
for (int i = 0; i < len; i++) {
labels[i] = make_label();
}
mv.visitLookupSwitchInsn(def, keys, labels);
for (int i = 0; i < len; i++) {
mark(labels[i]);
callback.processCase(keys[i], end);
}
}
}
mark(def);
callback.processDefault();
mark(end);
} catch (RuntimeException e) {
throw e;
} catch (Error e) {
throw e;
} catch (Exception e) {
throw new CodeGenerationException(e);
}
}
private static boolean isSorted(int[] keys) {
for (int i = 1; i < keys.length; i++) {
if (keys[i] < keys[i - 1])
return false;
}
return true;
}
public void mark(Label label) {
mv.visitLabel(label);
}
Label mark() {
Label label = make_label();
mv.visitLabel(label);
return label;
}
public void push(boolean value) {
push(value ? 1 : 0);
}
/**
* Toggles the integer on the top of the stack from 1 to 0 or vice versa
*/
public void not() {
push(1);
math(XOR, Type.INT_TYPE);
}
public void throw_exception(Type type, String msg) {
new_instance(type);
dup();
push(msg);
invoke_constructor(type, CSTRUCT_STRING);
athrow();
}
/**
* If the argument is a primitive class, replaces the primitive value
* on the top of the stack with the wrapped (Object) equivalent. For
* example, char -> Character.
* If the class is Void, a null is pushed onto the stack instead.
* @param type the class indicating the current type of the top stack value
*/
public void box(Type type) {
if (TypeUtils.isPrimitive(type)) {
if (type == Type.VOID_TYPE) {
aconst_null();
} else {
Type boxed = TypeUtils.getBoxedType(type);
new_instance(boxed);
if (type.getSize() == 2) {
// Pp -> Ppo -> oPpo -> ooPpo -> ooPp -> o
dup_x2();
dup_x2();
pop();
} else {
// p -> po -> opo -> oop -> o
dup_x1();
swap();
}
invoke_constructor(boxed, new Signature(Constants.CONSTRUCTOR_NAME, Type.VOID_TYPE, new Type[]{ type }));
}
}
}
/**
* If the argument is a primitive class, replaces the object
* on the top of the stack with the unwrapped (primitive)
* equivalent. For example, Character -> char.
* @param type the class indicating the desired type of the top stack value
* @return true if the value was unboxed
*/
public void unbox(Type type) {
Type t = Constants.TYPE_NUMBER;
Signature sig = null;
switch (type.getSort()) {
case Type.VOID:
return;
case Type.CHAR:
t = Constants.TYPE_CHARACTER;
sig = CHAR_VALUE;
break;
case Type.BOOLEAN:
t = Constants.TYPE_BOOLEAN;
sig = BOOLEAN_VALUE;
break;
case Type.DOUBLE:
sig = DOUBLE_VALUE;
break;
case Type.FLOAT:
sig = FLOAT_VALUE;
break;
case Type.LONG:
sig = LONG_VALUE;
break;
case Type.INT:
case Type.SHORT:
case Type.BYTE:
sig = INT_VALUE;
}
if (sig == null) {
checkcast(type);
} else {
checkcast(t);
invoke_virtual(t, sig);
}
}
/**
* Allocates and fills an Object[] array with the arguments to the
* current method. Primitive values are inserted as their boxed
* (Object) equivalents.
*/
public void create_arg_array() {
/* generates:
Object[] args = new Object[]{ arg1, new Integer(arg2) };
*/
push(state.argumentTypes.length);
newarray();
for (int i = 0; i < state.argumentTypes.length; i++) {
dup();
push(i);
load_arg(i);
box(state.argumentTypes[i]);
aastore();
}
}
/**
* Pushes a zero onto the stack if the argument is a primitive class, or a null otherwise.
*/
public void zero_or_null(Type type) {
if (TypeUtils.isPrimitive(type)) {
switch (type.getSort()) {
case Type.DOUBLE:
push(0d);
break;
case Type.LONG:
push(0L);
break;
case Type.FLOAT:
push(0f);
break;
case Type.VOID:
aconst_null();
default:
push(0);
}
} else {
aconst_null();
}
}
/**
* Unboxes the object on the top of the stack. If the object is null, the
* unboxed primitive value becomes zero.
*/
public void unbox_or_zero(Type type) {
if (TypeUtils.isPrimitive(type)) {
if (type != Type.VOID_TYPE) {
Label nonNull = make_label();
Label end = make_label();
dup();
ifnonnull(nonNull);
pop();
zero_or_null(type);
goTo(end);
mark(nonNull);
unbox(type);
mark(end);
}
} else {
checkcast(type);
}
}
public void visitMaxs(int maxStack, int maxLocals) {
if (!TypeUtils.isAbstract(state.access)) {
mv.visitMaxs(0, 0);
}
}
public void invoke(MethodInfo method, Type virtualType) {
ClassInfo classInfo = method.getClassInfo();
Type type = classInfo.getType();
Signature sig = method.getSignature();
if (sig.getName().equals(Constants.CONSTRUCTOR_NAME)) {
invoke_constructor(type, sig);
} else if (TypeUtils.isInterface(classInfo.getModifiers())) {
invoke_interface(type, sig);
} else if (TypeUtils.isStatic(method.getModifiers())) {
invoke_static(type, sig);
} else {
invoke_virtual(virtualType, sig);
}
}
public void invoke(MethodInfo method) {
invoke(method, method.getClassInfo().getType());
}
}