Skip to content

Commit

Permalink
filter equivalent divide by minus one mutants
Browse files Browse the repository at this point in the history
Filter out equivalent mutants of the form

(a + b) * -1 -> (a + b) / -1

Does not filter the opposite as, although it is also equivalent, there
is no good reason to express conversion to a negative as a division
operation over the more common multiplication.
  • Loading branch information
hcoles committed May 19, 2023
1 parent 407ee8b commit 6a0775b
Show file tree
Hide file tree
Showing 7 changed files with 261 additions and 36 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.pitest.mutationtest.build.intercept;

import org.pitest.mutationtest.engine.Mutater;
import org.pitest.mutationtest.engine.MutationDetails;
import org.pitest.mutationtest.engine.gregor.MethodMutatorFactory;

import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public abstract class MutatorSpecificInterceptor extends RegionInterceptor {
private final List<MethodMutatorFactory> mutators;

protected MutatorSpecificInterceptor(List<MethodMutatorFactory> mutators) {
this.mutators = mutators;
}

@Override
public Collection<MutationDetails> intercept(
Collection<MutationDetails> mutations, Mutater unused) {
return mutations.stream()
.filter(forRelevantMutator().negate().or(buildPredicate().negate()))
.collect(Collectors.toList());
}

private Predicate<MutationDetails> forRelevantMutator() {
return md -> mutators.stream().anyMatch(m -> m.isMutatorFor(md.getId()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public Collection<MutationDetails> intercept(
}


private Predicate<MutationDetails> buildPredicate() {
protected Predicate<MutationDetails> buildPredicate() {
return a -> {
final int instruction = a.getInstructionIndex();
final Optional<MethodTree> method = this.currentClass.method(a.getId().getLocation());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package org.pitest.mutationtest.build.intercept.equivalent;

import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.pitest.bytecode.analysis.MethodTree;
import org.pitest.mutationtest.build.intercept.MutatorSpecificInterceptor;
import org.pitest.mutationtest.build.intercept.Region;
import org.pitest.mutationtest.engine.gregor.MethodMutatorFactory;
import org.pitest.sequence.Context;
import org.pitest.sequence.Match;
import org.pitest.sequence.QueryParams;
import org.pitest.sequence.QueryStart;
import org.pitest.sequence.SequenceMatcher;
import org.pitest.sequence.Slot;
import org.pitest.sequence.SlotWrite;

import java.util.List;
import java.util.stream.Collectors;

import static java.util.Arrays.asList;
import static org.pitest.bytecode.analysis.InstructionMatchers.anyInstruction;
import static org.pitest.bytecode.analysis.InstructionMatchers.notAnInstruction;
import static org.pitest.bytecode.analysis.OpcodeMatchers.DMUL;
import static org.pitest.bytecode.analysis.OpcodeMatchers.FMUL;
import static org.pitest.bytecode.analysis.OpcodeMatchers.ICONST_M1;
import static org.pitest.bytecode.analysis.OpcodeMatchers.IMUL;
import static org.pitest.bytecode.analysis.OpcodeMatchers.LMUL;
import static org.pitest.sequence.Result.result;

/**
* Filters equivalent mutations of the form
*
* (a + b) * -1 -> (a + b) / -1
*
*/
class DivisionByMinusOneFilter extends MutatorSpecificInterceptor {

static final Slot<AbstractInsnNode> AVOID = Slot.create(AbstractInsnNode.class);

static final SequenceMatcher<AbstractInsnNode> DIVISION_BY_1 = QueryStart
.any(AbstractInsnNode.class)
.then(ICONST_M1.or(loads(-1L)).or(loads(-1f)).or(loads(-1d)))
.then(IMUL.or(LMUL.or(FMUL).or(DMUL)).and(store(AVOID.write())))
.zeroOrMore(QueryStart.match(anyInstruction()))
.compile(QueryParams.params(AbstractInsnNode.class)
.withIgnores(notAnInstruction())
);

DivisionByMinusOneFilter(MethodMutatorFactory... mutators) {
super(asList(mutators));
}


private static Match<AbstractInsnNode> loads(Object l) {
return (c,n) ->
result(n instanceof LdcInsnNode && ((LdcInsnNode) n).cst.equals(l), c);
}
private static Match<AbstractInsnNode> store(SlotWrite<AbstractInsnNode> slot) {
return (c, n) -> result(true, c.store(slot, n));
}

@Override
protected List<Region> computeRegions(MethodTree method) {
Context context = Context.start();
return DIVISION_BY_1.contextMatches(method.instructions(), context).stream()
.map(c -> new Region(c.retrieve(AVOID.read()).get(), c.retrieve(AVOID.read()).get()))
.collect(Collectors.toList());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.pitest.mutationtest.build.intercept.equivalent;

import org.pitest.mutationtest.build.InterceptorParameters;
import org.pitest.mutationtest.build.MutationInterceptor;
import org.pitest.mutationtest.build.MutationInterceptorFactory;
import org.pitest.mutationtest.engine.gregor.mutators.MathMutator;
import org.pitest.plugin.Feature;

public class DivisionByMinusOneFilterFactory implements MutationInterceptorFactory {

@Override
public String description() {
return "Division by one equivalent mutant filter";
}

@Override
public Feature provides() {
return Feature.named("FSEQUIVDIV")
.withOnByDefault(true)
.withDescription("Filters equivalent mutations of the form x * -1 -> x / -1");
}

@Override
public MutationInterceptor createInterceptor(InterceptorParameters params) {
return new DivisionByMinusOneFilter(MathMutator.MATH);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.pitest.bytecode.analysis.MethodTree;
import org.pitest.mutationtest.build.intercept.MutatorSpecificInterceptor;
import org.pitest.mutationtest.build.intercept.Region;
import org.pitest.mutationtest.build.intercept.RegionInterceptor;
import org.pitest.mutationtest.engine.Mutater;
import org.pitest.mutationtest.engine.MutationDetails;
import org.pitest.mutationtest.engine.gregor.MethodMutatorFactory;
import org.pitest.sequence.Context;
import org.pitest.sequence.Match;
Expand All @@ -17,13 +15,10 @@
import org.pitest.sequence.Slot;
import org.pitest.sequence.SlotWrite;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static java.util.Arrays.asList;
import static org.pitest.bytecode.analysis.InstructionMatchers.aVariableAccess;
import static org.pitest.bytecode.analysis.InstructionMatchers.anyInstruction;
import static org.pitest.bytecode.analysis.InstructionMatchers.isA;
Expand All @@ -33,24 +28,20 @@
import static org.pitest.bytecode.analysis.OpcodeMatchers.ASTORE;
import static org.pitest.sequence.Result.result;

class EmptyReturnsFilter extends RegionInterceptor {
class EmptyReturnsFilter extends MutatorSpecificInterceptor {

private static final Slot<AbstractInsnNode> AVOID = Slot.create(AbstractInsnNode.class);
private static final Slot<Integer> LOCAL_VAR = Slot.create(Integer.class);

private final SequenceQuery<AbstractInsnNode> matches;
private final Set<String> mutatorIds;

private final SequenceMatcher<AbstractInsnNode> zeroValues;
private final Match<AbstractInsnNode> returnMatch;

EmptyReturnsFilter(SequenceQuery<AbstractInsnNode> matches, Match<AbstractInsnNode> returnMatch, MethodMutatorFactory... mutators) {
super(asList(mutators));

this.matches = matches;
this.returnMatch = returnMatch;
this.mutatorIds = Arrays.stream(mutators)
.map(m -> m.getGloballyUniqueId())
.collect(Collectors.toSet());

this.zeroValues = directValues().or(inDirectValues())
.compile(QueryParams.params(AbstractInsnNode.class)
.withIgnores(notAnInstruction().or(isA(LabelNode.class)))
Expand Down Expand Up @@ -94,27 +85,6 @@ protected List<Region> computeRegions(MethodTree method) {
.collect(Collectors.toList());
}


@Override
public Collection<MutationDetails> intercept(
Collection<MutationDetails> mutations, Mutater unused) {

List<MutationDetails> targets = mutations.stream()
.filter(m -> mutatorIds.contains(m.getMutator()))
.collect(Collectors.toList());

// performance hack. Avoid class analysis if no relevent matches
if (targets.isEmpty()) {
return mutations;
}

List<MutationDetails> toReturn = new ArrayList<>(mutations);
toReturn.removeAll(targets);
toReturn.addAll(super.intercept(targets, unused));

return toReturn;
}

private static Match<AbstractInsnNode> aStoreTo(Slot<Integer> variable) {
return ASTORE.and(aVariableAccess(variable.write()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ org.pitest.mutationtest.filter.LimitNumberOfMutationsPerClassFilterFactory
org.pitest.mutationtest.build.intercept.equivalent.EqualsPerformanceShortcutFilterFactory
org.pitest.mutationtest.build.intercept.equivalent.EquivalentReturnMutationFilter
org.pitest.mutationtest.build.intercept.exclude.FirstLineInterceptorFactory
org.pitest.mutationtest.build.intercept.equivalent.DivisionByMinusOneFilterFactory

org.pitest.plugin.export.MutantExportFactory
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package org.pitest.mutationtest.build.intercept.equivalent;

import org.junit.Test;
import org.pitest.bytecode.analysis.OpcodeMatchers;
import org.pitest.mutationtest.engine.gregor.mutators.MathMutator;
import org.pitest.mutationtest.engine.gregor.mutators.NullMutateEverything;
import org.pitest.verifier.interceptors.FactoryVerifier;
import org.pitest.verifier.interceptors.InterceptorVerifier;
import org.pitest.verifier.interceptors.VerifierStart;

public class DivisionByMinusOneFilterFactoryTest {

DivisionByMinusOneFilterFactory underTest = new DivisionByMinusOneFilterFactory();

InterceptorVerifier v = VerifierStart.forInterceptorFactory(underTest)
.usingMutator(MathMutator.MATH);

@Test
public void isOnChain() {
FactoryVerifier.confirmFactory(underTest)
.isOnChain();
}

@Test
public void doesNotFilterNonEquivalentMutants() {
v.forClass(NonEquivalentMultiplication.class)
.forAnyCode()
.mutantsAreGenerated()
.noMutantsAreFiltered()
.verify();
}

@Test
public void doesNotFilterMutantsFromOtherMutators() {
VerifierStart.forInterceptorFactory(underTest)
.usingMutator(new NullMutateEverything())
.forClass(EquivalentMultiplicationByMinus1.class)
.forAnyCode()
.mutantsAreGenerated()
.noMutantsAreFiltered()
.verify();
}

@Test
public void filtersMutationsToIMulNegative1() {
v.forClass(EquivalentBoxedMultiplicationByMinus1.class)
.forCodeMatching(OpcodeMatchers.IMUL.asPredicate())
.mutantsAreGenerated()
.allMutantsAreFiltered()
.verify();
}

@Test
public void filtersMutationsToBoxedIMulNegative1() {
v.forClass(EquivalentBoxedMultiplicationByMinus1.class)
.forCodeMatching(OpcodeMatchers.IMUL.asPredicate())
.mutantsAreGenerated()
.allMutantsAreFiltered()
.verify();
}

@Test
public void filtersMutationsToLMulNegative1() {
v.forClass(EquivalentLongMultiplicationByMinus1.class)
.forCodeMatching(OpcodeMatchers.LMUL.asPredicate())
.mutantsAreGenerated()
.allMutantsAreFiltered()
.verify();
}

@Test
public void filtersMutationsToFMulNegative1() {
v.forClass(EquivalentFloatMultiplicationByMinus1.class)
.forCodeMatching(OpcodeMatchers.FMUL.asPredicate())
.mutantsAreGenerated()
.allMutantsAreFiltered()
.verify();
}

@Test
public void filtersMutationsToDMulNegative1() {
v.forClass(EquivalentDoubleMultiplicationByMinus1.class)
.forCodeMatching(OpcodeMatchers.DMUL.asPredicate())
.mutantsAreGenerated()
.allMutantsAreFiltered()
.verify();
}

}


class NonEquivalentMultiplication {
public int mutateMe(int a, int b) {
return (a + b) * -2;
}
}

class EquivalentMultiplicationByMinus1 {
public int mutateMe(int a, int b) {
return (a + b) * -1;
}
}

class EquivalentBoxedMultiplicationByMinus1 {
public Integer mutateMe(Integer a, Integer b) {
return (a + b) * -1;
}
}

class EquivalentLongMultiplicationByMinus1 {
public long mutateMe(long a, long b) {
return (a + b) * -1;
}
}

class EquivalentFloatMultiplicationByMinus1 {
public float mutateMe(float a, float b) {
return (a + b) * -1;
}
}

class EquivalentDoubleMultiplicationByMinus1 {
public double mutateMe(double a, double b) {
return (a + b) * -1;
}
}

0 comments on commit 6a0775b

Please sign in to comment.