All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jboss.byteman.rule.expression.NewExpression Maven / Gradle / Ivy

Go to download

The Byteman agent jar contains the implementation of the Byteman java agent, including the bytecode transformer, rule parser, type checker and execution engine and the agent listener.

There is a newer version: 4.0.24
Show newest version
/*
* 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 arguments;
    private List arrayDims;
    private ArrayInitExpression arrayInits;
    private List argumentTypes;
    private List 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;
    // if hte new value has an array initializer list then this tracks how many entries it has
    private int arrayInitsCount;

    public NewExpression(Rule rule, ParseNode token, List arguments,
                         List arraySizes, ArrayInitExpression arrayInits) {
        super(rule, Type.UNDEFINED, token);
        this.typeName = token.getText();
        this.arguments = arguments;
        this.arrayDims = arraySizes;
        this.arrayInits = arrayInits;
        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
     *
     * @throws TypeException if any variable is missing or has the wrong type
     */
    public void bind() throws TypeException
    {
        // check that the recipient and argument expressions have valid bindings

        Iterator 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++;
            }
        }

        // bind the array init expression if we have one

        if (arrayInits != null) {
            arrayInits.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 shoudl 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 {
        // 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 candidates = new ArrayList();
            boolean duplicates = false;

            for (Constructor constructor : constructors) {
                if (constructor.getParameterTypes().length == arity) {
                    candidates.add(constructor);
                }
            }

            argumentTypes = new ArrayList();

            // 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();
            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 without an initializer then we must have at least one defined
        // dimension

        if (arrayDimCount > 0 && arrayDimDefinedCount == 0 && arrayInits == null) {
            throw new TypeException("NewExpression.typeCheck : array dimension missing " + getPos());
        }

        // we cannot have more dimensions than we can fit into a byte

        if (arrayDimCount > Byte.MAX_VALUE) {
            throw new TypeException("NewExpression.typeCheck : too many array dimensions " + getPos());
        }

        // if we have an array initializer then we must have some dimensions
        // but cannot have any specified dimensions

        if (arrayInits != null) {
            if (arrayDimCount == 0)
            {
                throw new TypeException("NewExpression.typeCheck : cannot specify initializer for non array" + getPos());
            }

            if (arrayDimDefinedCount != 0)
            {
                throw new TypeException("NewExpression.typeCheck : cannot specify both array dimensions and initializer " + 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 we have been passed an ArrayInitExpression then type check it against our result type

        if (arrayInits != null) {
            arrayInits.typeCheck(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 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 pruneCandidates(List 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 || nextClazz.isAssignableFrom(argClazz)) {
                i++;
            } else {
                candidates.remove(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 if an error occurs during execution
     *
     */
    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 if (arrayInits != null) {
            Object result = arrayInits.interpret(helper);
            return result;
        } 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, "", getDescriptor());

            // modify the stack height to account for the removed exception and params
            compileContext.addStackCount(-(extraParams + 1));
        } else if (arrayInits != null) {
            // use the initializer to create the array
            arrayInits.compile(mv, compileContext);
        } else {
            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("new ");
        if (type == null || Type.UNDEFINED == type) {
            stringWriter.write(typeName);
        } else {
            stringWriter.write(type.getName());
        }
        String separator = "";
        if (arrayDimCount == 0) {
            stringWriter.write("(");
            for (Expression argument : arguments) {
                stringWriter.write(separator);
                argument.writeTo(stringWriter);
                separator = ",";
            }
            stringWriter.write(")");
        } else {
            for (int i = 0; i < arrayDimCount ; i++) {
                stringWriter.write("[]");
            }
            if (arrayInits != null) {
                arrayInits.writeTo(stringWriter);
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy