/*
* JBoss, Home of Professional Open Source
* Copyright 2008-10 Red Hat and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
* @authors Andrew Dinn
*/
package org.jboss.byteman.rule.expression;
import org.jboss.byteman.rule.compiler.CompileContext;
import org.jboss.byteman.rule.type.Type;
import org.jboss.byteman.rule.exception.TypeException;
import org.jboss.byteman.rule.exception.ExecuteException;
import org.jboss.byteman.rule.exception.EarlyReturnException;
import org.jboss.byteman.rule.exception.CompileException;
import org.jboss.byteman.rule.Rule;
import org.jboss.byteman.rule.helper.HelperAdapter;
import org.jboss.byteman.rule.grammar.ParseNode;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import java.io.StringWriter;
/**
* A return expression which is used in a rule action to cause a return from the rule trigger
* method, supplying a return value where appropriate.
*/
public class ReturnExpression extends Expression
{
private Expression returnValue;
public ReturnExpression(Rule rule, ParseNode token, Expression returnValue)
{
// the trigger method may return any old type but the return expression can only occur
// at the top level in a rule action seuqence so it is actually a VOID expression
super(rule, Type.VOID, token);
this.returnValue = returnValue;
}
/**
* verify that variables mentioned in this expression are actually available in the supplied
* bindings list and infer/validate the type of this expression or its subexpressions
* where possible
*
* @throws TypeException if any variable is missing or has the wrong type
*/
public void bind() throws TypeException
{
if (returnValue != null) {
// ensure the return value expression has all its bindings
returnValue.bind();
}
}
/**
* ensure that all type references in the expression and its component expressions
* can be resolved, that the type of the expression is well-defined and that it is
* compatible with the type expected in the context in which it occurs.
*
* @param expected the type expected for the expression in the contxt in which it occurs. this
* may be void but should not be undefined at the point where type checking is performed.
* @return the expression type
* @throws org.jboss.byteman.rule.exception.TypeException if type checking fails
*
*/
public Type typeCheck(Type expected) throws TypeException {
// we need to check the returnValue expression against the type of the trigger method
type = rule.getReturnType();
if (returnValue == null && !type.isVoid()) {
throw new TypeException("ReturnExpression.typeCheck : return expression must supply argument when triggered from method with return type " + type.getName() + getPos());
} else if (returnValue != null) {
if (type.isVoid()) {
throw new TypeException("ReturnExpression.typeCheck : return expression must not supply argument when triggered from void method" + getPos());
}
returnValue.typeCheck(type);
}
return type;
}
/**
* evaluate the expression by interpreting the expression tree
*
* @param helper an execution context associated with the rule whcih contains a map of
* current bindings for rule variables and another map of their declared types both of which
* are indexed by varoable name. This includes entries for the helper (name "-1"), the
* recipient if the trigger method is not static (name "0") and the trigger method arguments
* (names "1", ...)
* @return the result of evaluation as an Object
* @throws org.jboss.byteman.rule.exception.ExecuteException if an error occurs during execution
*
*/
public Object interpret(HelperAdapter helper) throws ExecuteException
{
// time to take an early bath -- the code compield into the trigger method should
// catch this and return as appropriate
if (returnValue != null) {
Object value = returnValue.interpret(helper);
Type subtype = returnValue.type;
if (type.isNumeric()) {
// make sure we produce the expected type of numeric
if (type == Type.C && subtype != Type.C) {
// ok, transform Number to a Character
int number = ((Number)value).intValue();
value = Character.valueOf((char)number);
} else if (subtype == Type.C) {
// ok, transform Character to a boxed Numeric if necessary
char c = ((Character)value).charValue();
if (type == Type.B) {
value = Byte.valueOf((byte)c);
} else if (type == Type.S) {
value = Short.valueOf((short)c);
} else if (type == Type.I) {
value = Integer.valueOf((int)c);
} else if (type == Type.J) {
value = Long.valueOf((int)c);
} else if (type == Type.F) {
value = Float.valueOf((int)c);
} else if (type == Type.D) {
value = Double.valueOf((int)c);
}
} else {
if (type == Type.B && subtype != Type.B) {
Number number = (Number)value;
value = Byte.valueOf(number.byteValue());
} else if (type == Type.S && subtype != Type.S) {
Number number = (Number)value;
value = Short.valueOf(number.shortValue());
} else if (type == Type.I && subtype != Type.I) {
Number number = (Number)value;
value = Integer.valueOf(number.intValue());
} else if (type == Type.J && subtype != Type.J) {
Number number = (Number)value;
value = Long.valueOf(number.longValue());
} else if (type == Type.F && subtype != Type.F) {
Number number = (Number)value;
value = Float.valueOf(number.floatValue());
} else if (type == Type.D && subtype != Type.D) {
Number number = (Number)value;
value = Double.valueOf(number.doubleValue());
}
}
}
throw new EarlyReturnException("return from " + helper.getName(), value);
} else {
throw new EarlyReturnException("return from " + helper.getName());
}
}
public void compile(MethodVisitor mv, CompileContext compileContext) throws CompileException
{
// make sure we are at the right source line
compileContext.notifySourceLine(line);
Type valueType = (returnValue == null ? Type.VOID : returnValue.getType());
int currentStack = compileContext.getStackCount();
int expected = 1;
// ok, we need to create the EarlyReturnException instance and then
// initialise it using the appropriate return value or null if no
// return value is needed. strictly we should maybe delay the
// new until after computing the return expression so we avoid a new
// if the expression throws an error. but that means we end up doing
// stack manipulations so lets do it the easy way.
// create am EarlyReturnException -- adds 1 to stack
String exceptionClassName = Type.internalName(EarlyReturnException.class);
mv.visitTypeInsn(Opcodes.NEW, exceptionClassName);
compileContext.addStackCount(1);
// copy the exception so we can initialise it -- adds 1 to stack
mv.visitInsn(Opcodes.DUP);
compileContext.addStackCount(1);
// stack a string constant to initialise the exception with -- adds 1 to stack
mv.visitLdcInsn("return from " + rule.getName());
compileContext.addStackCount(1);
// stack any required return value or null -- adds 1 to stack but may use 2 slots
if (returnValue != null) {
returnValue.compile(mv, compileContext);
// we may need to convert from the value type to the return type
if (valueType != type) {
compileTypeConversion(valueType, type, mv, compileContext);
}
if (type.isPrimitive()) {
// we need an object not a primitive
compileBox(Type.boxType(type), mv, compileContext);
}
} else {
// just push null
mv.visitInsn(Opcodes.ACONST_NULL);
compileContext.addStackCount(1);
}
// construct the exception -- pops 3
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, exceptionClassName, "<init>", "(Ljava/lang/String;Ljava/lang/Object;)V");
compileContext.addStackCount(-3);
// check current stack and increment max stack if necessary
if (compileContext.getStackCount() != currentStack + expected) {
throw new CompileException("ReturnExpression.compile : invalid stack height " + compileContext.getStackCount() + " expecting " + (currentStack + expected));
}
// now insert the throw instruction and decrement the stack height accordingly
mv.visitInsn(Opcodes.ATHROW);
compileContext.addStackCount(-1);
}
public void writeTo(StringWriter stringWriter) {
if (returnValue != null) {
stringWriter.write("RETURN ");
returnValue.writeTo(stringWriter);
} else {
stringWriter.write("RETURN");
}
}
}