org.evosuite.testcase.MethodStatement.java Source code

Java tutorial

Introduction

Here is the source code for org.evosuite.testcase.MethodStatement.java

Source

/**
 * Copyright (C) 2011,2012 Gordon Fraser, Andrea Arcuri and EvoSuite
 * contributors
 * 
 * This file is part of EvoSuite.
 * 
 * EvoSuite is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Public License as published by the Free Software Foundation,
 * either version 3 of the License, or (at your option) any later version.
 * 
 * EvoSuite 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 Public License for more details.
 * 
 * You should have received a copy of the GNU Public License along with
 * EvoSuite. If not, see <http://www.gnu.org/licenses/>.
 * 
 * @author Gordon Fraser
 */
package org.evosuite.testcase;

import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.evosuite.Properties;
import org.evosuite.utils.GenericClass;
import org.evosuite.utils.GenericMethod;
import org.evosuite.utils.Randomness;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;

public class MethodStatement extends AbstractStatement {

    private static final long serialVersionUID = 6134126797102983073L;

    protected GenericMethod method;

    protected VariableReference callee;

    protected List<VariableReference> parameters;

    /**
     * <p>
     * Constructor for MethodStatement.
     * </p>
     * 
     * @param tc
     *            a {@link org.evosuite.testcase.TestCase} object.
     * @param method
     *            a {@link java.lang.reflect.Method} object.
     * @param callee
     *            a {@link org.evosuite.testcase.VariableReference} object.
     * @param type
     *            a {@link java.lang.reflect.Type} object.
     * @param parameters
     *            a {@link java.util.List} object.
     */
    public MethodStatement(TestCase tc, GenericMethod method, VariableReference callee,
            List<VariableReference> parameters) throws IllegalArgumentException {
        super(tc, method.getReturnType());

        init(method, callee, parameters);
    }

    public MethodStatement(TestCase tc, GenericMethod method, VariableReference callee,
            List<VariableReference> parameters, VariableReference retVal) throws IllegalArgumentException {
        this(tc, method, callee, parameters);
        this.retval = retVal;
    }

    /**
     * This constructor allows you to use an already existing VariableReference
     * as retvar. This should only be done, iff an old statement is replaced
     * with this statement. And already existing objects should in the future
     * reference this object.
     * 
     * @param tc
     *            a {@link org.evosuite.testcase.TestCase} object.
     * @param method
     *            a {@link java.lang.reflect.Method} object.
     * @param callee
     *            a {@link org.evosuite.testcase.VariableReference} object.
     * @param retvar
     *            a {@link org.evosuite.testcase.VariableReference} object.
     * @param parameters
     *            a {@link java.util.List} object.
     */
    public MethodStatement(TestCase tc, GenericMethod method, VariableReference callee, VariableReference retvar,
            List<VariableReference> parameters) {
        super(tc, retvar);

        if (retvar.getStPosition() >= tc.size()) {
            //as an old statement should be replaced by this statement
            throw new IllegalArgumentException("Cannot replace in position " + retvar.getStPosition()
                    + " when the test case has only " + tc.size() + " elements");
        }

        init(method, callee, parameters);
    }

    private void init(GenericMethod method, VariableReference callee, List<VariableReference> parameters)
            throws IllegalArgumentException {
        if (callee == null && !method.isStatic()) {
            throw new IllegalArgumentException("A null callee cannot call a non-static method");
        }
        if (parameters == null) {
            throw new IllegalArgumentException("Parameter list cannot be null");
        }
        for (VariableReference var : parameters) {
            if (var == null) {
                //recall that 'null' would be mapped to a NullReference
                throw new IllegalArgumentException(
                        "Parameter list cannot have null parameters (this is different from a NullReference)");
            }
        }
        if (method.getParameterTypes().length != parameters.size()) {
            throw new IllegalArgumentException("Parameters list mismatch from the types declared in the method: "
                    + method.getParameterTypes().length + " != " + parameters.size());
        }

        this.method = method;
        if (isStatic())
            this.callee = null;
        else
            this.callee = callee;
        this.parameters = parameters;
    }

    /**
     * <p>
     * Getter for the field <code>method</code>.
     * </p>
     * 
     * @return a {@link java.lang.reflect.Method} object.
     */
    public GenericMethod getMethod() {
        return method;
    }

    /**
     * <p>
     * Setter for the field <code>method</code>.
     * </p>
     * 
     * @param method
     *            a {@link java.lang.reflect.Method} object.
     */
    public void setMethod(GenericMethod method) {
        this.method = method;
    }

    /**
     * <p>
     * Getter for the field <code>callee</code>.
     * </p>
     * 
     * @return a {@link org.evosuite.testcase.VariableReference} object.
     */
    public VariableReference getCallee() {
        return callee;
    }

    /**
     * <p>
     * Setter for the field <code>callee</code>.
     * </p>
     * 
     * @param callee
     *            a {@link org.evosuite.testcase.VariableReference} object.
     */
    public void setCallee(VariableReference callee) {
        if (!isStatic())
            this.callee = callee;
    }

    /**
     * <p>
     * isStatic
     * </p>
     * 
     * @return a boolean.
     */
    public boolean isStatic() {
        return method.isStatic();
    }

    private boolean isInstanceMethod() {
        return !method.isStatic();
    }

    /** {@inheritDoc} */
    @Override
    public Throwable execute(final Scope scope, PrintStream out) throws InvocationTargetException,
            IllegalArgumentException, IllegalAccessException, InstantiationException {
        logger.trace("Executing method {}", method.getName());
        final Object[] inputs = new Object[parameters.size()];
        Throwable exceptionThrown = null;

        try {
            return super.exceptionHandler(new Executer() {

                @Override
                public void execute() throws InvocationTargetException, IllegalArgumentException,
                        IllegalAccessException, InstantiationException, CodeUnderTestException {
                    Object callee_object;
                    try {
                        java.lang.reflect.Type[] parameterTypes = method.getParameterTypes();
                        for (int i = 0; i < parameters.size(); i++) {
                            VariableReference parameterVar = parameters.get(i);
                            try {
                                // Try exact parameter types if known
                                if (!parameterVar.isAssignableTo(parameterTypes[i])) {
                                    throw new CodeUnderTestException(new UncompilableCodeException(
                                            "Not assignable: " + parameterVar + " to " + parameterTypes[i]));
                                }
                            } catch (CodeUnderTestException e) {
                                throw e;
                            } catch (Throwable t) {
                                // GenericTypeReflector.getExactParameterTypes is buggy and may return null
                                if (!parameterVar.isAssignableTo(parameterTypes[i])) {
                                    logger.debug("Not assignable: {} to parameter of type {}", parameterVar,
                                            parameterTypes[i]);
                                    throw new CodeUnderTestException(new UncompilableCodeException());
                                }
                            }
                            inputs[i] = parameterVar.getObject(scope);
                            if (inputs[i] == null && method.getMethod().getParameterTypes()[i].isPrimitive()) {
                                throw new CodeUnderTestException(new NullPointerException());
                            }
                        }

                        callee_object = method.isStatic() ? null : callee.getObject(scope);
                        if (!method.isStatic() && callee_object == null) {
                            throw new CodeUnderTestException(new NullPointerException());
                        }
                    } catch (CodeUnderTestException e) {
                        throw e;
                        // throw CodeUnderTestException.throwException(e.getCause());
                    } catch (Throwable e) {
                        e.printStackTrace();
                        throw new EvosuiteError(e);
                    }

                    Object ret = method.getMethod().invoke(callee_object, inputs);
                    // Try exact return type
                    /*
                     * TODO: Sometimes we do want to cast an Object to String etc...
                     */
                    if (method.getReturnType() instanceof Class<?>) {
                        Class<?> returnClass = (Class<?>) method.getReturnType();

                        if (!returnClass.isPrimitive() && ret != null
                                && !returnClass.isAssignableFrom(ret.getClass())) {
                            throw new ClassCastException("Cannot assign " + method.getReturnType()
                                    + " to variable of type " + retval.getType());
                        }
                    }

                    try {
                        retval.setObject(scope, ret);
                    } catch (CodeUnderTestException e) {
                        throw e;
                        // throw CodeUnderTestException.throwException(e);
                    } catch (Throwable e) {
                        throw new EvosuiteError(e);
                    }
                }

                @Override
                public Set<Class<? extends Throwable>> throwableExceptions() {
                    Set<Class<? extends Throwable>> t = new HashSet<Class<? extends Throwable>>();
                    t.add(InvocationTargetException.class);
                    return t;
                }
            });

        } catch (InvocationTargetException e) {
            exceptionThrown = e.getCause();
            logger.debug("Exception thrown in method {}: {}", method.getName(), exceptionThrown);
        }
        return exceptionThrown;
    }

    /** {@inheritDoc} */
    @Override
    public boolean isDeclaredException(Throwable t) {
        for (Class<?> declaredException : method.getMethod().getExceptionTypes()) {
            if (declaredException.isAssignableFrom(t.getClass()))
                return true;
        }
        return false;
    }

    /** {@inheritDoc} */
    @Override
    public StatementInterface copy(TestCase newTestCase, int offset) {
        ArrayList<VariableReference> new_params = new ArrayList<VariableReference>();
        for (VariableReference r : parameters) {
            new_params.add(r.copy(newTestCase, offset));
        }

        MethodStatement m;
        if (isStatic()) {
            // FIXXME: If callee is an array index, this will return an invalid
            // copy of the cloned variable!
            m = new MethodStatement(newTestCase, method.copy(), null, new_params);
        } else {
            VariableReference newCallee = callee.copy(newTestCase, offset);
            m = new MethodStatement(newTestCase, method.copy(), newCallee, new_params);

        }
        if (retval instanceof ArrayReference && !(m.getReturnValue() instanceof ArrayReference)) {
            // logger.info("Copying array retval: " + retval.getGenericClass());
            //   assert (retval.getGenericClass() != null);
            //   assert (retval.getGenericClass().isArray()) : method.toString();
            ArrayReference newRetVal = new ArrayReference(newTestCase, retval.getGenericClass(),
                    ((ArrayReference) retval).getArrayLength());
            m.setRetval(newRetVal);

        }

        // m.assertions = copyAssertions(newTestCase, offset);

        return m;
    }

    /** {@inheritDoc} */
    @Override
    public Set<VariableReference> getVariableReferences() {
        Set<VariableReference> references = new LinkedHashSet<VariableReference>();
        references.add(retval);
        if (isInstanceMethod()) {
            references.add(callee);
            if (callee.getAdditionalVariableReference() != null)
                references.add(callee.getAdditionalVariableReference());
        }
        references.addAll(parameters);
        for (VariableReference param : parameters) {
            if (param.getAdditionalVariableReference() != null)
                references.add(param.getAdditionalVariableReference());
        }
        references.addAll(getAssertionReferences());
        return references;
    }

    /* (non-Javadoc)
     * @see org.evosuite.testcase.StatementInterface#replace(org.evosuite.testcase.VariableReference, org.evosuite.testcase.VariableReference)
     */
    /** {@inheritDoc} */
    @Override
    public void replace(VariableReference var1, VariableReference var2) {
        if (retval.equals(var1))
            retval = var2;

        if (isInstanceMethod()) {
            if (callee.equals(var1))
                callee = var2;
            else
                callee.replaceAdditionalVariableReference(var1, var2);
        }
        for (int i = 0; i < parameters.size(); i++) {

            if (parameters.get(i).equals(var1))
                parameters.set(i, var2);
            else
                parameters.get(i).replaceAdditionalVariableReference(var1, var2);
        }
    }

    /**
     * <p>
     * getParameterReferences
     * </p>
     * 
     * @return a {@link java.util.List} object.
     */
    public List<VariableReference> getParameterReferences() {
        return parameters;
    }

    /* (non-Javadoc)
     * @see org.evosuite.testcase.StatementInterface#getNumParameters()
     */
    @Override
    public int getNumParameters() {
        return parameters.size() + (isStatic() ? 0 : 1);
    }

    /**
     * <p>
     * replaceParameterReference
     * </p>
     * 
     * @param var
     *            a {@link org.evosuite.testcase.VariableReference} object.
     * @param numParameter
     *            a int.
     */
    public void replaceParameterReference(VariableReference var, int numParameter) {
        assert (numParameter >= 0);
        assert (numParameter < parameters.size());
        parameters.set(numParameter, var);
    }

    /** {@inheritDoc} */
    @Override
    public String toString() {
        return method.getName() + Type.getMethodDescriptor(method.getMethod());
    }

    /** {@inheritDoc} */
    @Override
    public boolean equals(Object s) {
        if (this == s)
            return true;
        if (s == null)
            return false;
        if (getClass() != s.getClass())
            return false;

        MethodStatement ms = (MethodStatement) s;
        if (ms.parameters.size() != parameters.size())
            return false;

        if (!this.method.equals(ms.method))
            return false;

        for (int i = 0; i < parameters.size(); i++) {
            if (!parameters.get(i).equals(ms.parameters.get(i)))
                return false;
        }

        if (!retval.equals(ms.retval))
            return false;

        if ((callee == null && ms.callee != null) || (callee != null && ms.callee == null)) {
            return false;
        } else {
            if (callee == null)
                return true;
            else
                return (callee.equals(ms.callee));
        }
    }

    /** {@inheritDoc} */
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((callee == null) ? 0 : callee.hashCode());
        result = prime * result + ((method == null) ? 0 : method.hashCode());
        result = prime * result + ((parameters == null) ? 0 : parameters.hashCode());
        return result;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.evosuite.testcase.Statement#getBytecode(org.objectweb.
     * asm.commons.GeneratorAdapter)
     */
    /** {@inheritDoc} */
    @Override
    public void getBytecode(GeneratorAdapter mg, Map<Integer, Integer> locals, Throwable exception) {
        Label start = mg.newLabel();
        Label end = mg.newLabel();

        // if(exception != null)
        mg.mark(start);

        if (!isStatic()) {
            callee.loadBytecode(mg, locals);
            if (!method.getMethod().getDeclaringClass().equals(callee.getVariableClass())) {
                logger.debug("Types don't match - casting!");
                mg.cast(Type.getType(callee.getVariableClass()),
                        Type.getType(method.getMethod().getDeclaringClass()));
            }
        }
        int num = 0;
        for (VariableReference parameter : parameters) {
            parameter.loadBytecode(mg, locals);
            if (method.getMethod().getParameterTypes()[num].isPrimitive()) {
                if (parameter.getGenericClass().isWrapperType()) {
                    mg.unbox(Type.getType(parameter.getGenericClass().getUnboxedType()));
                } else if (!parameter.getGenericClass().isPrimitive()) {
                    Class<?> parameterClass = new GenericClass(method.getParameterTypes()[num]).getBoxedType();
                    Type parameterType = Type.getType(parameterClass);
                    mg.checkCast(parameterType);
                    mg.unbox(Type.getType(method.getMethod().getParameterTypes()[num]));
                }

                if (!method.getParameterTypes()[num].equals(parameter.getVariableClass())) {
                    logger.debug("Types don't match - casting!");
                    mg.cast(Type.getType(parameter.getVariableClass()),
                            Type.getType(method.getMethod().getParameterTypes()[num]));
                }
            } else if (parameter.getVariableClass().isPrimitive()) {
                mg.box(Type.getType(parameter.getVariableClass()));
            }
            num++;
        }
        logger.debug("Invoking method");
        // if(exception != null) {
        //
        // mg.visitTryCatchBlock(start, end, handler,
        // exception.getClass().getName().replace('.', '/'));
        // }
        if (isStatic())
            mg.invokeStatic(Type.getType(method.getMethod().getDeclaringClass()),
                    org.objectweb.asm.commons.Method.getMethod(method.getMethod()));
        else {
            if (!callee.getVariableClass().isInterface()) {
                mg.invokeVirtual(Type.getType(callee.getVariableClass()),
                        org.objectweb.asm.commons.Method.getMethod(method.getMethod()));
            } else {
                mg.invokeInterface(Type.getType(callee.getVariableClass()),
                        org.objectweb.asm.commons.Method.getMethod(method.getMethod()));
            }
        }

        if (!retval.isVoid()) {
            if (!retval.getVariableClass().equals(method.getReturnType())) {
                if (!retval.getVariableClass().isPrimitive()) {
                    mg.checkCast(Type.getType(retval.getVariableClass()));
                } else {
                    mg.cast(Type.getType(method.getMethod().getReturnType()),
                            Type.getType(retval.getVariableClass()));
                }
            }
            retval.storeBytecode(mg, locals);
        }

        // if(exception != null) {
        mg.mark(end);
        Label l = mg.newLabel();
        mg.goTo(l);
        // mg.catchException(start, end,
        // Type.getType(getExceptionClass(exception)));
        mg.catchException(start, end, Type.getType(Throwable.class));
        mg.pop(); // Pop exception from stack
        if (!retval.isVoid()) {
            Class<?> clazz = retval.getVariableClass();
            if (clazz.equals(boolean.class))
                mg.push(false);
            else if (clazz.equals(char.class))
                mg.push(0);
            else if (clazz.equals(int.class))
                mg.push(0);
            else if (clazz.equals(short.class))
                mg.push(0);
            else if (clazz.equals(long.class))
                mg.push(0L);
            else if (clazz.equals(float.class))
                mg.push(0.0F);
            else if (clazz.equals(double.class))
                mg.push(0.0);
            else if (clazz.equals(byte.class))
                mg.push(0);
            else if (clazz.equals(String.class))
                mg.push("");
            else
                mg.visitInsn(Opcodes.ACONST_NULL);

            retval.storeBytecode(mg, locals);
        }
        mg.mark(l);
        // }
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.evosuite.testcase.Statement#getDeclaredExceptions()
     */
    /** {@inheritDoc} */
    @Override
    public Set<Class<?>> getDeclaredExceptions() {
        Set<Class<?>> ex = super.getDeclaredExceptions();
        for (Class<?> t : method.getMethod().getExceptionTypes())
            ex.add(t);
        return ex;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.evosuite.testcase.Statement#getUniqueVariableReferences()
     */
    /** {@inheritDoc} */
    @Override
    public List<VariableReference> getUniqueVariableReferences() {
        List<VariableReference> references = new ArrayList<VariableReference>();
        references.add(retval);
        if (isInstanceMethod()) {
            references.add(callee);
            if (callee instanceof ArrayIndex)
                references.add(((ArrayIndex) callee).getArray());
        }
        references.addAll(parameters);
        for (VariableReference param : parameters) {
            if (param instanceof ArrayIndex)
                references.add(((ArrayIndex) param).getArray());
        }
        return references;
    }

    @Override
    public boolean isAccessible() {
        if (!method.isAccessible())
            return false;

        return super.isAccessible();
    }

    /* (non-Javadoc)
     * @see org.evosuite.testcase.StatementInterface#isValid()
     */
    /** {@inheritDoc} */
    @Override
    public boolean isValid() {
        assert (super.isValid());
        for (VariableReference v : parameters) {
            v.getStPosition();
        }
        if (!isStatic()) {
            callee.getStPosition();
        }
        return true;
    }

    /** {@inheritDoc} */
    @Override
    public boolean same(StatementInterface s) {
        if (this == s)
            return true;
        if (s == null)
            return false;
        if (getClass() != s.getClass())
            return false;

        MethodStatement ms = (MethodStatement) s;
        if (ms.parameters.size() != parameters.size())
            return false;

        for (int i = 0; i < parameters.size(); i++) {
            if (!parameters.get(i).same(ms.parameters.get(i)))
                return false;
        }

        if (!this.method.equals(ms.method))
            return false;

        if (!retval.same(ms.retval))
            return false;

        if ((callee == null && ms.callee != null) || (callee != null && ms.callee == null)) {
            return false;
        } else {
            if (callee == null)
                return true;
            else
                return (callee.same(ms.callee));
        }
    }

    /**
     * Go through parameters of method call and apply local search
     * 
     * @param test
     * @param statement
     * @param objective
     */
    @Override
    public boolean mutate(TestCase test, TestFactory factory) {

        if (Randomness.nextDouble() >= Properties.P_CHANGE_PARAMETER)
            return false;

        List<VariableReference> parameters = getParameterReferences();
        if (parameters.isEmpty())
            return false;

        int max = parameters.size();
        if (!isStatic()) {
            max++;
        }
        int numParameter = Randomness.nextInt(max);
        if (numParameter == parameters.size()) {
            // replace callee
            VariableReference callee = getCallee();
            List<VariableReference> objects = test.getObjects(callee.getType(), getPosition());
            objects.remove(callee);
            if (objects.isEmpty())
                return false;

            VariableReference replacement = Randomness.choice(objects);
            setCallee(replacement);
            return true;

        } else {
            VariableReference parameter = parameters.get(numParameter);
            List<VariableReference> objects = test.getObjects(parameter.getType(), getPosition());
            objects.remove(parameter);
            objects.remove(getReturnValue());
            NullStatement nullStatement = new NullStatement(test, parameter.getType());
            StatementInterface primitiveCopy = null;

            if (!parameter.isPrimitive())
                objects.add(nullStatement.getReturnValue());
            else {
                StatementInterface originalStatement = test.getStatement(parameter.getStPosition());
                if (originalStatement instanceof PrimitiveStatement<?>) {
                    PrimitiveStatement<?> copy = (PrimitiveStatement<?>) originalStatement.clone(test);
                    copy.delta();
                    objects.add(copy.getReturnValue());
                    primitiveCopy = copy;
                }
            }

            if (objects.isEmpty())
                return false;

            VariableReference replacement = Randomness.choice(objects);
            if (replacement == nullStatement.getReturnValue()) {
                test.addStatement(nullStatement, getPosition());
            } else if (primitiveCopy != null && replacement == primitiveCopy.getReturnValue()) {
                test.addStatement(primitiveCopy, getPosition());
            }
            replaceParameterReference(replacement, numParameter);
            return true;
        }

    }

    /** {@inheritDoc} */
    @Override
    public GenericMethod getAccessibleObject() {
        return method;
    }

    /** {@inheritDoc} */
    @Override
    public boolean isAssignmentStatement() {
        return false;
    }

    /* (non-Javadoc)
     * @see org.evosuite.testcase.StatementInterface#changeClassLoader(java.lang.ClassLoader)
     */
    /** {@inheritDoc} */
    @Override
    public void changeClassLoader(ClassLoader loader) {
        method.changeClassLoader(loader);
        super.changeClassLoader(loader);
    }
}