Java tutorial
/* * 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.Rule; import org.jboss.byteman.rule.compiler.CompileContext; import org.jboss.byteman.rule.exception.CompileException; import org.jboss.byteman.rule.exception.ExecuteException; import org.jboss.byteman.rule.exception.TypeException; import org.jboss.byteman.rule.grammar.ParseNode; import org.jboss.byteman.rule.helper.HelperAdapter; import org.jboss.byteman.rule.type.Type; import org.jboss.byteman.rule.type.TypeGroup; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import java.io.StringWriter; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * Expression which implements a new operation. */ public class NewExpression extends Expression { private String typeName; private List<Expression> arguments; private List<Expression> arrayDims; private List<Type> argumentTypes; private List<Type> paramTypes; private Constructor constructor; // if the new value is an array it will have this many dimensions private int arrayDimCount; // if the new value is an array this many of its dimensions are specified and are to be instantiated private int arrayDimDefinedCount; public NewExpression(Rule rule, ParseNode token, List<Expression> arguments, List<Expression> arraySizes) { super(rule, Type.UNDEFINED, token); this.typeName = token.getText(); this.arguments = arguments; this.arrayDims = arraySizes; this.arrayDimCount = arraySizes.size(); // we check this at bind time and throw a TypeError if it is invalid this.arrayDimDefinedCount = 0; this.argumentTypes = null; this.constructor = null; } /** * verify that variables mentioned in this expression are actually available in the supplied * bindings list * * @return true if all variables in this expression are bound and no type mismatches have * been detected during inference/validation. */ public void bind() throws TypeException { // check that the recipient and argument expressions have valid bindings Iterator<Expression> iterator = arguments.iterator(); while (iterator.hasNext()) { iterator.next().bind(); } // repeat for the array size expressions iterator = arrayDims.iterator(); while (iterator.hasNext()) { Expression expr = iterator.next(); if (expr != null) { expr.bind(); arrayDimDefinedCount++; } } } /** * 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 shoudl not be undefined at the point where type checking is performed. * @return * @throws org.jboss.byteman.rule.exception.TypeException * */ public Type typeCheck(Type expected) throws TypeException { // check the new instance type is defined and then look for a relevant constructor TypeGroup typeGroup = getTypeGroup(); type = Type.dereference(typeGroup.create(typeName)); if (type == null || type.isUndefined()) { throw new TypeException("NewExpression.typeCheck : unknown type " + typeName + getPos()); } if (type.isObject() && arrayDimCount == 0) { // we need to look for a suitable constructor Class clazz = type.getTargetClass(); // if we can find a unique method then we can use it to type the parameters // otherwise we do it the hard way int arity = arguments.size(); Constructor[] constructors = clazz.getConstructors(); List<Constructor> candidates = new ArrayList<Constructor>(); boolean duplicates = false; for (Constructor constructor : constructors) { if (constructor.getParameterTypes().length == arity) { candidates.add(constructor); } } argumentTypes = new ArrayList<Type>(); // check each argument in turn -- if all candidates have the same argument type then // use that as the type to check against for (int i = 0; i < arguments.size(); i++) { if (candidates.isEmpty()) { throw new TypeException("NewExpression.typeCheck : invalid constructor for target class " + typeName + getPos()); } // TODO get and prune operations do not allow for coercion but type check does! // e.g. the parameter type may be int and the arg type float // or the parameter type may be String and the arg type class Foo // reimplement this using type inter-assignability to do the pruning Class candidateClass = getCandidateArgClass(candidates, i); Type candidateType; if (candidateClass != null) { candidateType = typeGroup.ensureType(candidateClass); } else { candidateType = Type.UNDEFINED; } Type argType = arguments.get(i).typeCheck(candidateType); argumentTypes.add(argType); if (candidateType == Type.UNDEFINED) { // we had several constructors to choose from candidates = pruneCandidates(candidates, i, argType.getTargetClass()); } } if (candidates.isEmpty()) { throw new TypeException( "NewExpression.typeCheck : invalid constructor for target class " + typeName + getPos()); } if (candidates.size() > 1) { throw new TypeException( "NewExpression.typeCheck : ambiguous constructor signature for target class " + typeName + getPos()); } constructor = candidates.get(0); // make sure we know the formal parameter types and have included them in the typegroup paramTypes = new ArrayList<Type>(); Class<?>[] paramClasses = constructor.getParameterTypes(); for (int i = 0; i < arguments.size(); i++) { paramTypes.add(typeGroup.ensureType(paramClasses[i])); } } else if (arrayDimCount == 0) { // if we have a primitive type then have to have some array dimensions throw new TypeException("NewExpression.typeCheck : invalid type for new operation " + getPos()); } // if this is a new array operation we must have at least one defined dimension and we cannot have // more dimensions than we can fit into a byte if (arrayDimCount > 0 && arrayDimDefinedCount == 0) { throw new TypeException("NewExpression.typeCheck : array dimension missing " + getPos()); } if (arrayDimCount > Byte.MAX_VALUE) { throw new TypeException("NewExpression.typeCheck : too many array dimensions " + getPos()); } // if we have any array dimension sizings then ensure they all type check as integer expressions for (int i = 0; i < arrayDimCount; i++) { if (i < arrayDimDefinedCount) { Expression expr = arrayDims.get(i); expr.typeCheck(Type.I); } // replace the current type with the corresponding array type type = typeGroup.createArray(type); } // if the expected type is defined then ensure we can assign this type to it if (Type.dereference(expected).isDefined() && !expected.isAssignableFrom(type)) { throw new TypeException( "NewExpression.typeCheck : invalid expected result type " + expected.getName() + getPos()); } return type; } public Class getCandidateArgClass(List<Constructor> candidates, int argIdx) { Class argClazz = null; for (Constructor c : candidates) { Class nextClazz = c.getParameterTypes()[argIdx]; if (argClazz == null) { argClazz = nextClazz; } else if (argClazz != nextClazz) { return null; } } return argClazz; } public List<Constructor> pruneCandidates(List<Constructor> candidates, int argIdx, Class argClazz) { for (int i = 0; i < candidates.size();) { Constructor c = candidates.get(i); Class nextClazz = c.getParameterTypes()[argIdx]; if (nextClazz != argClazz) { candidates.remove(i); } else { i++; } } return candidates; } /** * 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 * */ public Object interpret(HelperAdapter helper) throws ExecuteException { if (arrayDimCount == 0) { int l = arguments.size(); int i; Object[] callArgs = new Object[l]; for (i = 0; i < l; i++) { callArgs[i] = arguments.get(i).interpret(helper); } try { Object result = constructor.newInstance(callArgs); return result; } catch (InstantiationException e) { throw new ExecuteException( "NewExpression.interpret : unable to instantiate class " + typeName + getPos(), e); } catch (IllegalAccessException e) { throw new ExecuteException( "NewExpression.interpret : unable to access class " + typeName + getPos(), e); } catch (InvocationTargetException e) { throw new ExecuteException( "NewExpression.interpret : unable to invoke constructor for class " + typeName + getPos(), e); } } else { int[] dims = new int[arrayDimDefinedCount]; Type componentType = type; for (int i = 0; i < arrayDimDefinedCount; i++) { Expression dim = arrayDims.get(i); int dimValue = (Integer) dim.interpret(helper); dims[i] = dimValue; componentType = componentType.getBaseType(); } try { Object result = Array.newInstance(componentType.getTargetClass(), dims); return result; } catch (IllegalArgumentException e) { throw new ExecuteException( "NewExpression.interpret : unable to instantiate array " + typeName + getPos(), e); } catch (NegativeArraySizeException e) { // should never happen throw new ExecuteException( "NewExpression.interpret : unable to instantiate array " + typeName + getPos(), e); } } } public void compile(MethodVisitor mv, CompileContext compileContext) throws CompileException { // make sure we are at the right source line compileContext.notifySourceLine(line); int currentStack = compileContext.getStackCount(); int expected = 1; int extraParams = 0; if (arrayDimCount == 0) { // ok, we need to create the new instance and then initialise it. // create the new instance -- adds 1 to stack String instantiatedClassName = type.getInternalName(); mv.visitTypeInsn(Opcodes.NEW, instantiatedClassName); compileContext.addStackCount(1); // copy the exception so we can init it mv.visitInsn(Opcodes.DUP); compileContext.addStackCount(1); int argCount = arguments.size(); // stack each of the arguments to the constructor for (int i = 0; i < argCount; i++) { Type argType = argumentTypes.get(i); Type paramType = paramTypes.get(i); int paramCount = (paramType.getNBytes() > 4 ? 2 : 1); // track extra storage used after type conversion extraParams += (paramCount); arguments.get(i).compile(mv, compileContext); compileTypeConversion(argType, paramType, mv, compileContext); } // construct the exception mv.visitMethodInsn(Opcodes.INVOKESPECIAL, instantiatedClassName, "<init>", getDescriptor()); // modify the stack height to account for the removed exception and params compileContext.addStackCount(-(extraParams + 1)); } else { // TODO !!! implement compilation for array types !!! if (arrayDimCount == 1) { // we can use a NEWARRAY or ANEWARRAY Type baseType = type.getBaseType(); // compile first array dimension adds 1 to stack arrayDims.get(0).compile(mv, compileContext); // compile new array op -- pops 1 and adds 1 to stack if (baseType.isObject()) { mv.visitTypeInsn(Opcodes.ANEWARRAY, baseType.getInternalName()); // } else if (baseType.isArray()) { // cannot happen!!! } else { int operand = 0; if (baseType.equals(Type.Z)) { operand = Opcodes.T_BOOLEAN; } else if (baseType.equals(Type.B)) { operand = Opcodes.T_BYTE; } else if (baseType.equals(Type.S)) { operand = Opcodes.T_SHORT; } else if (baseType.equals(Type.C)) { operand = Opcodes.T_CHAR; } else if (baseType.equals(Type.I)) { operand = Opcodes.T_INT; } else if (baseType.equals(Type.J)) { operand = Opcodes.T_LONG; } else if (baseType.equals(Type.F)) { operand = Opcodes.T_FLOAT; } else if (baseType.equals(Type.D)) { operand = Opcodes.T_DOUBLE; } mv.visitIntInsn(Opcodes.NEWARRAY, operand); } } else { // we need to use MULTIANEWARRAY for (int i = 0; i < arrayDimDefinedCount; i++) { // compile next array dimension adds 1 to stack arrayDims.get(i).compile(mv, compileContext); } // execute the MULTIANEWARRAY operation -- pops arrayDims operands and pushes 1 mv.visitMultiANewArrayInsn(type.getInternalName(), arrayDimDefinedCount); compileContext.addStackCount(1 - arrayDimDefinedCount); } } if (compileContext.getStackCount() != currentStack + expected) { throw new CompileException("NewExpression.compile : invalid stack height " + compileContext.getStackCount() + " expecting " + (currentStack + expected)); } } private String getDescriptor() { StringBuffer buffer = new StringBuffer(); buffer.append("("); int nParams = paramTypes.size(); for (int i = 0; i < nParams; i++) { buffer.append(paramTypes.get(i).getInternalName(true, true)); } buffer.append(")V"); return buffer.toString(); } public void writeTo(StringWriter stringWriter) { stringWriter.write("throw "); if (type == null || Type.UNDEFINED == type) { stringWriter.write(typeName); } else { stringWriter.write(type.getName()); } String separator = ""; stringWriter.write("("); for (Expression argument : arguments) { stringWriter.write(separator); argument.writeTo(stringWriter); separator = ","; } stringWriter.write(")"); } }