package metamutator; import com.google.common.collect.Sets; import spoon.processing.AbstractProcessor; import spoon.reflect.code.BinaryOperatorKind; import spoon.reflect.code.CtBinaryOperator; import spoon.reflect.code.CtCodeSnippetExpression; import spoon.reflect.code.CtExpression; import spoon.reflect.declaration.CtAnonymousExecutable; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtConstructor; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtField; import spoon.reflect.reference.CtTypeReference; import java.util.EnumSet; import java.util.Set; import java.util.stream.Collectors; /** * inserts a mutation hotspot for each binary operator */ public class LogicalExpressionMetaMutator extends AbstractProcessor<CtBinaryOperator<Boolean>> { public static final String PREFIX = "_binaryLogicalOperatorHotSpot"; private static int index = 0; private static final int procId = 1; private static final EnumSet<BinaryOperatorKind> LOGICAL_OPERATORS = EnumSet .of(BinaryOperatorKind.AND, BinaryOperatorKind.OR); private static final EnumSet<BinaryOperatorKind> COMPARISON_OPERATORS = EnumSet .of(BinaryOperatorKind.EQ, BinaryOperatorKind.GE, BinaryOperatorKind.GT, BinaryOperatorKind.LE, BinaryOperatorKind.LT, BinaryOperatorKind.NE); private static final EnumSet<BinaryOperatorKind> REDUCED_COMPARISON_OPERATORS = EnumSet .of(BinaryOperatorKind.EQ, BinaryOperatorKind.NE); private Set<CtElement> hostSpots = Sets.newHashSet(); @Override public boolean isToBeProcessed(CtBinaryOperator<Boolean> element) { // if (element.getParent(CtAnonymousExecutable.class)!=null) { // System.out.println(element.getParent(CtAnonymousExecutable.class)); // } try { Selector.getTopLevelClass(element); } catch (NullPointerException e) { return false; } // not in constructors because we use static fields if (element.getParent(CtConstructor.class) != null) { return false; } // not in fields declaration because we use static fields if (element.getParent(CtField.class) != null) { return false; } return (LOGICAL_OPERATORS.contains(element.getKind()) || COMPARISON_OPERATORS .contains(element.getKind())) && (element.getParent(CtAnonymousExecutable.class) == null) // not // in // static // block ; } public void process(CtBinaryOperator<Boolean> binaryOperator) { BinaryOperatorKind kind = binaryOperator.getKind(); if (LOGICAL_OPERATORS.contains(kind)) { mutateOperator(binaryOperator, LOGICAL_OPERATORS); } else if (COMPARISON_OPERATORS.contains(kind)) { if (isNumber(binaryOperator.getLeftHandOperand()) && isNumber(binaryOperator.getRightHandOperand())) { mutateOperator(binaryOperator, COMPARISON_OPERATORS); } else { EnumSet<BinaryOperatorKind> clone = REDUCED_COMPARISON_OPERATORS.clone(); clone.add(kind); mutateOperator(binaryOperator, clone); } } } private boolean isNumber(CtExpression<?> operand) { if (operand.getType().toString().equals(CtTypeReference.NULL_TYPE_NAME)) return false; if (operand.toString().contains(".class")) return false; return operand.getType().getSimpleName().equals("int") || operand.getType().getSimpleName().equals("long") || operand.getType().getSimpleName().equals("byte") || operand.getType().getSimpleName().equals("char") || operand.getType().getSimpleName().equals("float") || operand.getType().getSimpleName().equals("double") || operand.getType().isSubtypeOf(getFactory().Type().createReference(Number.class)); } /** * Converts "a op b" bean op one of "<", "<=", "==", ">=", "!=" to: * ( (op(1, 0, "<") && (a < b)) * || (op(1, 1, "<=") && (a <= b)) * || (op(1, 2, "==") && (a == b)) * || (op(1, 3, ">=") && (a >= b)) * || (op(1, 4, ">") && (a > b)) * ) * */ private void mutateOperator(final CtBinaryOperator<Boolean> expression, EnumSet<BinaryOperatorKind> operators) { if (!operators.contains(expression.getKind())) { throw new IllegalArgumentException("not consistent "); } if (alreadyInHotsSpot(expression) || expression.toString().contains(".is(\"")) { System.out .println(String .format("Expression '%s' ignored because it is included in previous hot spot", expression)); return; } int thisIndex = ++index; BinaryOperatorKind originalKind = expression.getKind(); String newExpression = operators .stream() .map(kind -> { expression.setKind(kind); return String.format("("+ PREFIX + "%s.is(%s) && (%s))", thisIndex, kind.getDeclaringClass().getName()+"."+kind.name(), expression); }).collect(Collectors.joining(" || ")); CtCodeSnippetExpression<Boolean> codeSnippet = getFactory().Core() .createCodeSnippetExpression(); codeSnippet.setValue('(' + newExpression + ')'); expression.replace(codeSnippet); expression.replace(expression); Selector.generateSelector(expression, originalKind, thisIndex, operators, PREFIX); hostSpots.add(expression); } /** * Check if this sub expression was already inside an uppermost expression * that was processed has a hot spot. This version does not allowed * conflicting hot spots * * @param element * the current expression to test * @return true if this expression is descendant of an already processed * expression */ private boolean alreadyInHotsSpot(CtElement element) { CtElement parent = element.getParent(); while (!isTopLevel(parent) && parent != null) { if (hostSpots.contains(parent)) return true; parent = parent.getParent(); } return false; } private boolean isTopLevel(CtElement parent) { return parent instanceof CtClass && ((CtClass) parent).isTopLevel(); } }