/* * 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.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.util.List; import java.util.Iterator; import java.io.StringWriter; import java.lang.reflect.Array; /** * an expression which identifies an array reference. */ public class ArrayExpression extends AssignableExpression { public ArrayExpression(Rule rule, Type type, ParseNode token, Expression arrayRef, List<Expression> idxList) { super(rule, type, token); this.arrayRef = arrayRef; this.idxList = idxList; } /** * 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 { // we have to make sure that any names occuring in the array reference are bound // and that the index expressions contain valid bindings arrayRef.bind(); Iterator<Expression> iterator = idxList.iterator(); while (iterator.hasNext()) { iterator.next().bind(); } } public Type typeCheck(Type expected) throws TypeException { typeCheckAny(); if (Type.dereference(expected).isDefined() && !expected.isAssignableFrom(type)) { throw new TypeException("ArrayExpression.typeCheck : invalid expected result type " + expected.getName() + getPos()); } return type; } public Type typeCheckAssign(Type expected) throws TypeException { typeCheckAny(); if (Type.dereference(expected).isDefined() && !type.isAssignableFrom(expected)) { throw new TypeException("ArrayExpression.typeCheckAssign : invalid value type " + expected.getName() + " for array assignment " + getPos()); } return type; } private void typeCheckAny() throws TypeException { Type arrayType = arrayRef.typeCheck(Type.UNDEFINED); Type nextType = arrayType; for (Expression expr : idxList) { if (!nextType.isArray()) { throw new TypeException("ArrayExpression.typeCheck : invalid type for array dereference " + nextType.getName() + getPos()); } nextType = nextType.getBaseType(); expr.typeCheck(Type.N); } type = nextType; } public Object interpret(HelperAdapter helper) throws ExecuteException { // evaluate the array expression then evaluate each index expression in turn and // dereference to access the array element try { Object value = arrayRef.interpret(helper); Type nextType = arrayRef.getType(); for (Expression expr : idxList) { int idx = ((Number) expr.interpret(helper)).intValue(); if (value == null) { throw new ExecuteException("ArrayExpression.interpret : attempted array indirection through null value " + arrayRef.token.getText() + getPos()); } value = Array.get(value, idx); nextType = nextType.getBaseType(); } return value; } catch (ExecuteException e) { throw e; } catch (IllegalArgumentException e) { throw new ExecuteException("ArrayExpression.interpret : failed to evaluate expression " + arrayRef.token.getText() + getPos(), e); } catch (ArrayIndexOutOfBoundsException e) { throw new ExecuteException("ArrayExpression.interpret : invalid index for array " + arrayRef.token.getText() + getPos(), e); } catch (ClassCastException e) { throw new ExecuteException("ArrayExpression.interpret : invalid index dereferencing array " + arrayRef.token.getText() + getPos(), e); } catch (Exception e) { throw new ExecuteException("ArrayExpression.interpret : unexpected exception dereferencing array " + arrayRef.token.getText() + getPos(), e); } } public void compile(MethodVisitor mv, CompileContext compileContext) throws CompileException { // make sure we are at the right source line compileContext.notifySourceLine(line); Type valueType = arrayRef.getType().getBaseType(); int currentStack = compileContext.getStackCount(); int expected = 0; // compile load of array reference -- adds 1 to stack height arrayRef.compile(mv, compileContext); // for each index expression compile the expression and the do an array load Iterator<Expression> iterator = idxList.iterator(); while (iterator.hasNext()) { Expression idxExpr = iterator.next(); // compile expression index -- adds 1 to height idxExpr.compile(mv, compileContext); // make sure the index is an integer compileTypeConversion(idxExpr.getType(), Type.I, mv, compileContext); if (valueType.isObject() || valueType.isArray()) { // compile load object - pops 2 and adds 1 mv.visitInsn(Opcodes.AALOAD); expected = 1; } else if (valueType == Type.Z || valueType == Type.B) { // compile load byte - pops 2 and adds 1 mv.visitInsn(Opcodes.BALOAD); expected = 1; } else if (valueType == Type.S) { // compile load short - pops 2 and adds 1 mv.visitInsn(Opcodes.SALOAD); expected = 1; } else if (valueType == Type.C) { // compile load char - pops 2 and adds 1 mv.visitInsn(Opcodes.CALOAD); expected = 1; } else if (valueType == Type.I) { // compile load int - pops 2 and adds 1 mv.visitInsn(Opcodes.IALOAD); expected = 1; } else if (valueType == Type.J) { // compile load long - pops 2 and adds 2 mv.visitInsn(Opcodes.LALOAD); expected = 2; } else if (valueType == Type.F) { // compile load float - pops 2 and adds 1 mv.visitInsn(Opcodes.FALOAD); expected = 1; } else if (valueType == Type.D) { // compile load double - pops 2 and adds 2 mv.visitInsn(Opcodes.DALOAD); expected = 2; } compileContext.addStackCount(expected - 2); if (iterator.hasNext()) { assert valueType.isArray(); valueType =valueType.getBaseType(); } } // check stack height if (compileContext.getStackCount() != currentStack + expected) { throw new CompileException("ArrayExpression.compile : invalid stack height " + compileContext.getStackCount() + " expecting " + (currentStack + expected)); } // we needed room for an aray and an index or for a one or two word result // but the recursive evaluations will have made sure the max stack is big enough // so there is no need to update the maximum stack height } public void writeTo(StringWriter stringWriter) { arrayRef.writeTo(stringWriter); for (Expression expr : idxList) { stringWriter.write("["); expr.writeTo(stringWriter); stringWriter.write("]"); } } Expression arrayRef; List<Expression> idxList; @Override public Object interpretAssign(HelperAdapter helper, Object value) throws ExecuteException { // evaluate the array expression then evaluate each index expression in turn up to the last one // and dereference to access the array at that index. finally evaluate the final index expression // and use it as the position at which to install the supplied value in the dereferenced array try { Object array = arrayRef.interpret(helper); Type nextType = arrayRef.getType(); int count = idxList.size() - 1; for (Expression expr : idxList) { int idx = ((Number) expr.interpret(helper)).intValue(); if (array == null) { throw new ExecuteException("ArrayExpression.interpret : attempted array indirection through null value " + arrayRef.token.getText() + getPos()); } if (count-- > 0) { array = Array.get(array, idx); nextType = nextType.getBaseType(); } else { Array.set(array, idx, value); } } return value; } catch (ExecuteException e) { throw e; } catch (IllegalArgumentException e) { throw new ExecuteException("ArrayExpression.interpret : failed to evaluate expression " + arrayRef.token.getText() + getPos(), e); } catch (ArrayIndexOutOfBoundsException e) { throw new ExecuteException("ArrayExpression.interpret : invalid index for array " + arrayRef.token.getText() + getPos(), e); } catch (ClassCastException e) { throw new ExecuteException("ArrayExpression.interpret : invalid index dereferencing array " + arrayRef.token.getText() + getPos(), e); } catch (Exception e) { throw new ExecuteException("ArrayExpression.interpret : unexpected exception dereferencing array " + arrayRef.token.getText() + getPos(), e); } } @Override public void compileAssign(MethodVisitor mv, CompileContext compileContext) throws CompileException { // make sure we are at the right source line compileContext.notifySourceLine(line); Type valueType = arrayRef.getType().getBaseType(); int currentStack = compileContext.getStackCount(); boolean isTwoWords = (valueType.getNBytes() > 4); int toPop = 0; int size = (isTwoWords ? 2 : 1); // value to be assigned is TOS and will already be coerced to the correct value type // copy it so we can install the copy and leave the original as a a return value on the stack if (isTwoWords) { // [... val1 val2 ==> ... val1 val2 val1 val2] mv.visitInsn(Opcodes.DUP2); } else { // [... val ==> ... val val] mv.visitInsn(Opcodes.DUP); } compileContext.addStackCount(size); // compile load of array reference -- adds 1 to stack height arrayRef.compile(mv, compileContext); // for each index expression compile the expression and the do an array load Iterator<Expression> iterator = idxList.iterator(); while (iterator.hasNext()) { Expression idxExpr = iterator.next(); if (iterator.hasNext()) { // dereference the array to get an embedded array // compile expression index -- adds 1 to height idxExpr.compile(mv, compileContext); // make sure the index is an integer compileTypeConversion(idxExpr.getType(), Type.I, mv, compileContext); // fetch embedded array pop 2 and add 1 mv.visitInsn(Opcodes.AALOAD); compileContext.addStackCount(-1); valueType = valueType.getBaseType(); } else { if (isTwoWords) { // stack is [..., val1, val2, val1, val2, aref ] and we want [..., val1, val2, aref, val1, val2 ] mv.visitInsn(Opcodes.DUP_X2); // ==> [..., val1, val2, aref. val1, val2, aref ] compileContext.addStackCount(1); mv.visitInsn(Opcodes.POP); // ==> [..., val1, val2, aref. val1, val2 ] compileContext.addStackCount(-1); } else { // stack is [..., val, val, aref ] and we want [..., val, aref, val ] mv.visitInsn(Opcodes.SWAP); } // compile expression index -- adds 1 to height idxExpr.compile(mv, compileContext); // make sure the index is an integer compileTypeConversion(idxExpr.getType(), Type.I, mv, compileContext); if (isTwoWords) { // stack is [..., val1, val2, aref, val1, val2, idx] and we want [..., val1, val2, aref, idx, val1, val2 ] mv.visitInsn(Opcodes.DUP_X2); // ==> [..., val1, val2, aref, idx, val1, val2, idx] compileContext.addStackCount(1); mv.visitInsn(Opcodes.POP); // ==> [..., val1, val2, aref, idx, val1, val2 ] compileContext.addStackCount(-1); } else { // stack is [..., val, aref, val, idx] and we want [..., val, aref, idx, val ] mv.visitInsn(Opcodes.SWAP); } // now we can do the array store if (valueType.isObject() || valueType.isArray()) { // compile load object - pops 3 mv.visitInsn(Opcodes.AASTORE); toPop =- 3; } else if (valueType == Type.Z || valueType == Type.B) { // compile load byte - pops 3 mv.visitInsn(Opcodes.BASTORE); toPop = -3; } else if (valueType == Type.S) { // compile load short - pops 3 mv.visitInsn(Opcodes.SASTORE); toPop = -3; } else if (valueType == Type.C) { // compile load char - pops 3 mv.visitInsn(Opcodes.CASTORE); toPop = -3; } else if (valueType == Type.I) { // compile load int - pops 3 mv.visitInsn(Opcodes.IASTORE); toPop = -3; } else if (valueType == Type.J) { // compile load long - pops 4 mv.visitInsn(Opcodes.LASTORE); toPop = -4; } else if (valueType == Type.F) { // compile load float - pops 3 mv.visitInsn(Opcodes.FASTORE); toPop = -3; } else if (valueType == Type.D) { // compile load double - pops 4 mv.visitInsn(Opcodes.DASTORE); toPop = -4; } compileContext.addStackCount(toPop); if (iterator.hasNext()) { assert valueType.isArray(); valueType =valueType.getBaseType(); } } } // check stack height if (compileContext.getStackCount() != currentStack) { throw new CompileException("ArrayExpression.compile : invalid stack height " + compileContext.getStackCount() + " expecting " + currentStack); } // we needed room for an aray and an index or for a one or two word result // but the recursive evaluations will have made sure the max stack is big enough // so there is no need to update the maximum stack height } @Override public void bindAssign() throws TypeException { bind(); } }