org.evosuite.instrumentation.mutation.ReplaceVariable Maven / Gradle / Ivy
/**
* Copyright (C) 2010-2018 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 Lesser General Public License as published
* by the Free Software Foundation, either version 3.0 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
* Lesser Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with EvoSuite. If not, see .
*/
/**
*
*/
package org.evosuite.instrumentation.mutation;
import java.io.FileDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang3.ClassUtils;
import org.evosuite.PackageInfo;
import org.evosuite.Properties;
import org.evosuite.coverage.mutation.Mutation;
import org.evosuite.coverage.mutation.MutationPool;
import org.evosuite.graphs.cfg.BytecodeInstruction;
import org.evosuite.setup.TestClusterUtils;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.IincInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.VarInsnNode;
import org.objectweb.asm.tree.analysis.BasicValue;
import org.objectweb.asm.tree.analysis.Frame;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* ReplaceVariable class.
*
*
* @author Gordon Fraser
*/
public class ReplaceVariable implements MutationOperator {
private static Logger logger = LoggerFactory.getLogger(ReplaceVariable.class);
public static final String NAME = "ReplaceVariable";
/* (non-Javadoc)
* @see org.evosuite.cfg.instrumentation.mutation.MutationOperator#apply(org.objectweb.asm.tree.MethodNode, java.lang.String, java.lang.String, org.evosuite.cfg.BytecodeInstruction)
*/
/** {@inheritDoc} */
@Override
public List apply(MethodNode mn, String className, String methodName,
BytecodeInstruction instruction, Frame frame) {
List mutations = new LinkedList();
if (mn.localVariables.isEmpty()) {
logger.debug("Have no information about local variables - recompile with full debug information");
return mutations;
}
logger.debug("Starting variable replacement in " + methodName);
try {
String origName = getName(mn, instruction.getASMNode());
int numReplacements = 0;
for (Entry mutation : getReplacements(
mn,
className,
instruction.getASMNode(),
frame).entrySet()) {
if (numReplacements++ > Properties.MAX_REPLACE_MUTANTS) {
logger.info("Reached maximum number of variable replacements");
break;
}
// insert mutation into pool
Mutation mutationObject = MutationPool.addMutation(className,
methodName,
NAME + " "
+ origName
+ " -> "
+ mutation.getKey(),
instruction,
mutation.getValue(),
getInfectionDistance(getType(mn,
instruction.getASMNode()),
instruction.getASMNode(),
mutation.getValue()));
mutations.add(mutationObject);
}
} catch (VariableNotFoundException e) {
logger.info("Variable not found: " + instruction);
}
logger.debug("Finished variable replacement in " + methodName);
return mutations;
}
private Type getType(MethodNode mn, AbstractInsnNode node)
throws VariableNotFoundException {
if (node instanceof VarInsnNode) {
LocalVariableNode var = getLocal(mn, node, ((VarInsnNode) node).var);
return Type.getType(var.desc);
} else if (node instanceof FieldInsnNode) {
return Type.getType(((FieldInsnNode) node).desc);
} else if (node instanceof IincInsnNode) {
IincInsnNode incNode = (IincInsnNode) node;
LocalVariableNode var = getLocal(mn, node, incNode.var);
return Type.getType(var.desc);
} else {
throw new RuntimeException("Unknown variable node: " + node);
}
}
private String getName(MethodNode mn, AbstractInsnNode node)
throws VariableNotFoundException {
if (node instanceof VarInsnNode) {
LocalVariableNode var = getLocal(mn, node, ((VarInsnNode) node).var);
return var.name;
} else if (node instanceof FieldInsnNode) {
return ((FieldInsnNode) node).name;
} else if (node instanceof IincInsnNode) {
IincInsnNode incNode = (IincInsnNode) node;
LocalVariableNode var = getLocal(mn, node, incNode.var);
return var.name;
} else {
throw new RuntimeException("Unknown variable node: " + node);
}
}
/**
*
* copy
*
*
* @param orig
* a {@link org.objectweb.asm.tree.InsnList} object.
* @return a {@link org.objectweb.asm.tree.InsnList} object.
*/
public static InsnList copy(InsnList orig) {
Iterator> it = orig.iterator();
InsnList copy = new InsnList();
while (it.hasNext()) {
AbstractInsnNode node = (AbstractInsnNode) it.next();
if (node instanceof VarInsnNode) {
VarInsnNode vn = (VarInsnNode) node;
copy.add(new VarInsnNode(vn.getOpcode(), vn.var));
} else if (node instanceof FieldInsnNode) {
FieldInsnNode fn = (FieldInsnNode) node;
copy.add(new FieldInsnNode(fn.getOpcode(), fn.owner, fn.name, fn.desc));
} else if (node instanceof InsnNode) {
if (node.getOpcode() != Opcodes.POP)
copy.add(new InsnNode(node.getOpcode()));
} else if (node instanceof LdcInsnNode) {
copy.add(new LdcInsnNode(((LdcInsnNode) node).cst));
} else {
throw new RuntimeException("Unexpected node type: " + node.getClass());
}
}
return copy;
}
/**
*
* addPrimitiveDistanceCheck
*
*
* @param distance
* a {@link org.objectweb.asm.tree.InsnList} object.
* @param type
* a {@link org.objectweb.asm.Type} object.
* @param mutant
* a {@link org.objectweb.asm.tree.InsnList} object.
*/
public static void addPrimitiveDistanceCheck(InsnList distance, Type type,
InsnList mutant) {
distance.add(cast(type, Type.DOUBLE_TYPE));
distance.add(copy(mutant));
distance.add(cast(type, Type.DOUBLE_TYPE));
distance.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
PackageInfo.getNameWithSlash(ReplaceVariable.class),
"getDistance", "(DD)D", false));
}
/**
*
* addReferenceDistanceCheck
*
*
* @param distance
* a {@link org.objectweb.asm.tree.InsnList} object.
* @param type
* a {@link org.objectweb.asm.Type} object.
* @param mutant
* a {@link org.objectweb.asm.tree.InsnList} object.
*/
public static void addReferenceDistanceCheck(InsnList distance, Type type,
InsnList mutant) {
distance.add(copy(mutant));
distance.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
PackageInfo.getNameWithSlash(ReplaceVariable.class),
"getDistance", "(Ljava/lang/Object;Ljava/lang/Object;)D", false));
}
/**
*
* getInfectionDistance
*
*
* @param type
* a {@link org.objectweb.asm.Type} object.
* @param original
* a {@link org.objectweb.asm.tree.AbstractInsnNode} object.
* @param mutant
* a {@link org.objectweb.asm.tree.InsnList} object.
* @return a {@link org.objectweb.asm.tree.InsnList} object.
*/
public InsnList getInfectionDistance(Type type, AbstractInsnNode original,
InsnList mutant) {
// TODO: Treat reference types different!
InsnList distance = new InsnList();
if (original instanceof VarInsnNode) {
VarInsnNode node = (VarInsnNode) original;
distance.add(new VarInsnNode(node.getOpcode(), node.var));
if (type.getDescriptor().startsWith("L")
|| type.getDescriptor().startsWith("["))
addReferenceDistanceCheck(distance, type, mutant);
else
addPrimitiveDistanceCheck(distance, type, mutant);
} else if (original instanceof FieldInsnNode) {
if (original.getOpcode() == Opcodes.GETFIELD)
distance.add(new InsnNode(Opcodes.DUP)); //make sure to re-load this for GETFIELD
FieldInsnNode node = (FieldInsnNode) original;
distance.add(new FieldInsnNode(node.getOpcode(), node.owner, node.name,
node.desc));
if (type.getDescriptor().startsWith("L")
|| type.getDescriptor().startsWith("["))
addReferenceDistanceCheck(distance, type, mutant);
else
addPrimitiveDistanceCheck(distance, type, mutant);
} else if (original instanceof IincInsnNode) {
distance.add(Mutation.getDefaultInfectionDistance());
}
return distance;
}
/**
*
* getDistance
*
*
* @param val1
* a double.
* @param val2
* a double.
* @return a double.
*/
public static double getDistance(double val1, double val2) {
return val1 == val2 ? 1.0 : 0.0;
}
/**
*
* getDistance
*
*
* @param obj1
* a {@link java.lang.Object} object.
* @param obj2
* a {@link java.lang.Object} object.
* @return a double.
*/
public static double getDistance(Object obj1, Object obj2) {
if (obj1 == obj2)
return 1.0;
else
return 0.0;
}
/**
* Retrieve the set of variables that have the same type and are in scope
*
* @param node
* @return
*/
private Map getReplacements(MethodNode mn, String className,
AbstractInsnNode node, Frame frame) {
Map variables = new HashMap();
if (node instanceof VarInsnNode) {
VarInsnNode var = (VarInsnNode) node;
try {
LocalVariableNode origVar = getLocal(mn, node, var.var);
//LocalVariableNode origVar = (LocalVariableNode) mn.localVariables.get(var.var);
logger.debug("Looking for replacements for " + origVar.name + " of type "
+ origVar.desc + " at index " + origVar.index);
// FIXXME: ASM gets scopes wrong, so we only use primitive vars?
//if (!origVar.desc.startsWith("L"))
variables.putAll(getLocalReplacements(mn, origVar.desc, node, frame));
variables.putAll(getFieldReplacements(mn, className, origVar.desc, node));
} catch (VariableNotFoundException e) {
logger.info("Could not find variable, not replacing it: " + var.var);
Iterator> it = mn.localVariables.iterator();
while (it.hasNext()) {
LocalVariableNode n = (LocalVariableNode) it.next();
logger.info(n.index + ": " + n.name);
}
logger.info(e.toString());
e.printStackTrace();
}
} else if (node instanceof FieldInsnNode) {
FieldInsnNode field = (FieldInsnNode) node;
if (field.owner.replace('/', '.').equals(className)) {
logger.info("Looking for replacements for static field " + field.name
+ " of type " + field.desc);
variables.putAll(getLocalReplacements(mn, field.desc, node, frame));
variables.putAll(getFieldReplacements(mn, className, field.desc, node));
}
} else if (node instanceof IincInsnNode) {
IincInsnNode incNode = (IincInsnNode) node;
try {
LocalVariableNode origVar = getLocal(mn, node, incNode.var);
variables.putAll(getLocalReplacementsInc(mn, origVar.desc, incNode, frame));
} catch (VariableNotFoundException e) {
logger.info("Could not find variable, not replacing it: " + incNode.var);
}
} else {
//throw new RuntimeException("Unknown type: " + node);
}
return variables;
}
private LocalVariableNode getLocal(MethodNode mn, AbstractInsnNode node, int index)
throws VariableNotFoundException {
int currentId = mn.instructions.indexOf(node);
for (Object v : mn.localVariables) {
LocalVariableNode localVar = (LocalVariableNode) v;
int startId = mn.instructions.indexOf(localVar.start);
int endId = mn.instructions.indexOf(localVar.end);
logger.info("Checking " + localVar.index + " in scope " + startId + " - "
+ endId);
if (currentId >= startId && currentId <= endId && localVar.index == index)
return localVar;
}
throw new VariableNotFoundException("Could not find local variable " + index
+ " at position " + currentId + ", have variables: "
+ mn.localVariables.size());
}
private Map getLocalReplacements(MethodNode mn, String desc,
AbstractInsnNode node, Frame frame) {
Map replacements = new HashMap();
//if (desc.equals("I"))
// return replacements;
int otherNum = -1;
if (node instanceof VarInsnNode) {
VarInsnNode vNode = (VarInsnNode) node;
otherNum = vNode.var;
}
if (otherNum == -1)
return replacements;
int currentId = mn.instructions.indexOf(node);
logger.info("Looking for replacements at position " + currentId + " of variable "
+ otherNum + " of type " + desc);
// return replacements;
for (Object v : mn.localVariables) {
LocalVariableNode localVar = (LocalVariableNode) v;
int startId = mn.instructions.indexOf(localVar.start);
int endId = mn.instructions.indexOf(localVar.end);
logger.info("Checking local variable " + localVar.name + " of type "
+ localVar.desc + " at index " + localVar.index);
if (!localVar.desc.equals(desc))
logger.info("- Types do not match");
if (localVar.index == otherNum)
logger.info("- Replacement = original");
if (currentId < startId)
logger.info("- Out of scope (start)");
if (currentId > endId)
logger.info("- Out of scope (end)");
BasicValue newValue = (BasicValue) frame.getLocal(localVar.index);
if (newValue == BasicValue.UNINITIALIZED_VALUE)
logger.info("- Not initialized");
if (localVar.desc.equals(desc) && localVar.index != otherNum
&& currentId >= startId && currentId <= endId
&& newValue != BasicValue.UNINITIALIZED_VALUE) {
logger.info("Adding local variable " + localVar.name + " of type "
+ localVar.desc + " at index " + localVar.index + ", " + startId
+ "-" + endId + ", " + currentId);
InsnList list = new InsnList();
if (node.getOpcode() == Opcodes.GETFIELD) {
list.add(new InsnNode(Opcodes.POP)); // Remove field owner from stack
}
list.add(new VarInsnNode(getLoadOpcode(localVar), localVar.index));
replacements.put(localVar.name, list);
}
}
return replacements;
}
private Map getLocalReplacementsInc(MethodNode mn, String desc,
IincInsnNode node, Frame frame) {
Map replacements = new HashMap();
int otherNum = -1;
otherNum = node.var;
int currentId = mn.instructions.indexOf(node);
for (Object v : mn.localVariables) {
LocalVariableNode localVar = (LocalVariableNode) v;
int startId = mn.instructions.indexOf(localVar.start);
int endId = mn.instructions.indexOf(localVar.end);
logger.info("Checking local variable " + localVar.name + " of type "
+ localVar.desc + " at index " + localVar.index);
if (!localVar.desc.equals(desc))
logger.info("- Types do not match: " + localVar.name);
if (localVar.index == otherNum)
logger.info("- Replacement = original " + localVar.name);
if (currentId < startId)
logger.info("- Out of scope (start) " + localVar.name);
if (currentId > endId)
logger.info("- Out of scope (end) " + localVar.name);
BasicValue newValue = (BasicValue) frame.getLocal(localVar.index);
if (newValue == BasicValue.UNINITIALIZED_VALUE)
logger.info("- Not initialized");
if (localVar.desc.equals(desc) && localVar.index != otherNum
&& currentId >= startId && currentId <= endId
&& newValue != BasicValue.UNINITIALIZED_VALUE) {
logger.info("Adding local variable " + localVar.name + " of type "
+ localVar.desc + " at index " + localVar.index);
InsnList list = new InsnList();
list.add(new IincInsnNode(localVar.index, node.incr));
replacements.put(localVar.name, list);
}
}
return replacements;
}
private int getLoadOpcode(LocalVariableNode var) {
Type type = Type.getType(var.desc);
return type.getOpcode(Opcodes.ILOAD);
}
private Map getFieldReplacements(MethodNode mn, String className,
String desc, AbstractInsnNode node) {
Map alternatives = new HashMap();
boolean isStatic = (mn.access & Opcodes.ACC_STATIC) == Opcodes.ACC_STATIC;
String otherName = "";
if (node instanceof FieldInsnNode) {
FieldInsnNode fNode = (FieldInsnNode) node;
otherName = fNode.name;
}
try {
logger.info("Checking class " + className);
Class> clazz = Class.forName(className, false,
ReplaceVariable.class.getClassLoader());
for (Field field : TestClusterUtils.getFields(clazz)) {
// We have to use a special version of canUse to avoid
// that we access the CUT before it is fully initialised
if (!canUse(field))
continue;
Type type = Type.getType(field.getType());
logger.info("Checking replacement field variable " + field.getName());
if (field.getName().equals(otherName))
continue;
if (isStatic && !(Modifier.isStatic(field.getModifiers())))
continue;
if (type.getDescriptor().equals(desc)) {
logger.info("Adding replacement field variable " + field.getName());
InsnList list = new InsnList();
if (node.getOpcode() == Opcodes.GETFIELD) {
list.add(new InsnNode(Opcodes.POP)); // Remove field owner from stack
}
// new fieldinsnnode
if (Modifier.isStatic(field.getModifiers()))
list.add(new FieldInsnNode(Opcodes.GETSTATIC,
className.replace('.', '/'), field.getName(),
type.getDescriptor()));
else {
list.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
list.add(new FieldInsnNode(Opcodes.GETFIELD,
className.replace('.', '/'), field.getName(),
type.getDescriptor()));
}
alternatives.put(field.getName(), list);
} else {
logger.info("Descriptor does not match: " + field.getName() + " - "
+ type.getDescriptor());
}
}
} catch (Throwable t) {
logger.info("Class not found: " + className);
// TODO Auto-generated catch block
//e.printStackTrace();
}
return alternatives;
}
/**
* Generates the instructions to cast a numerical value from one type to
* another.
*
* @param from
* the type of the top stack value
* @param to
* the type into which this value must be cast.
* @return a {@link org.objectweb.asm.tree.InsnList} object.
*/
public static InsnList cast(final Type from, final Type to) {
InsnList list = new InsnList();
if (from != to) {
if (from == Type.DOUBLE_TYPE) {
if (to == Type.FLOAT_TYPE) {
list.add(new InsnNode(Opcodes.D2F));
} else if (to == Type.LONG_TYPE) {
list.add(new InsnNode(Opcodes.D2L));
} else {
list.add(new InsnNode(Opcodes.D2I));
list.add(cast(Type.INT_TYPE, to));
}
} else if (from == Type.FLOAT_TYPE) {
if (to == Type.DOUBLE_TYPE) {
list.add(new InsnNode(Opcodes.F2D));
} else if (to == Type.LONG_TYPE) {
list.add(new InsnNode(Opcodes.F2L));
} else {
list.add(new InsnNode(Opcodes.F2I));
list.add(cast(Type.INT_TYPE, to));
}
} else if (from == Type.LONG_TYPE) {
if (to == Type.DOUBLE_TYPE) {
list.add(new InsnNode(Opcodes.L2D));
} else if (to == Type.FLOAT_TYPE) {
list.add(new InsnNode(Opcodes.L2F));
} else {
list.add(new InsnNode(Opcodes.L2I));
list.add(cast(Type.INT_TYPE, to));
}
} else {
if (to == Type.BYTE_TYPE) {
list.add(new InsnNode(Opcodes.I2B));
} else if (to == Type.CHAR_TYPE) {
list.add(new InsnNode(Opcodes.I2C));
} else if (to == Type.DOUBLE_TYPE) {
list.add(new InsnNode(Opcodes.I2D));
} else if (to == Type.FLOAT_TYPE) {
list.add(new InsnNode(Opcodes.I2F));
} else if (to == Type.LONG_TYPE) {
list.add(new InsnNode(Opcodes.I2L));
} else if (to == Type.SHORT_TYPE) {
list.add(new InsnNode(Opcodes.I2S));
}
}
}
return list;
}
/**
* This replicates TestUsageChecker.canUse but we need to avoid that
* we try to access Properties.getTargetClassAndDontInitialise
*
* @param f
* @return
*/
public static boolean canUse(Field f) {
if (f.getDeclaringClass().equals(java.lang.Object.class))
return false;// handled here to avoid printing reasons
if (f.getDeclaringClass().equals(java.lang.Thread.class))
return false;// handled here to avoid printing reasons
if (f.isSynthetic()) {
logger.debug("Skipping synthetic field " + f.getName());
return false;
}
if (f.getName().startsWith("ajc$")) {
logger.debug("Skipping AspectJ field " + f.getName());
return false;
}
// in, out, err
if(f.getDeclaringClass().equals(FileDescriptor.class)) {
return false;
}
if (Modifier.isPublic(f.getModifiers())) {
// It may still be the case that the field is defined in a non-visible superclass of the class
// we already know we can use. In that case, the compiler would be fine with accessing the
// field, but reflection would start complaining about IllegalAccess!
// Therefore, we set the field accessible to be on the safe side
TestClusterUtils.makeAccessible(f);
return true;
}
// If default access rights, then check if this class is in the same package as the target class
if (!Modifier.isPrivate(f.getModifiers())) {
String packageName = ClassUtils.getPackageName(f.getDeclaringClass());
if (packageName.equals(Properties.CLASS_PREFIX)) {
TestClusterUtils.makeAccessible(f);
return true;
}
}
return false;
}
/* (non-Javadoc)
* @see org.evosuite.cfg.instrumentation.mutation.MutationOperator#isApplicable(org.evosuite.cfg.BytecodeInstruction)
*/
/** {@inheritDoc} */
@Override
public boolean isApplicable(BytecodeInstruction instruction) {
return instruction.isLocalVariableUse()
|| instruction.getASMNode().getOpcode() == Opcodes.GETSTATIC
|| instruction.getASMNode().getOpcode() == Opcodes.GETFIELD;
}
}