Skip to content

Commit

Permalink
Merge pull request #1213 from hcoles/feature/division_by_one
Browse files Browse the repository at this point in the history
filter equivalent divide by minus one mutants
  • Loading branch information
hcoles authored May 19, 2023
2 parents 407ee8b + 6a0775b commit 3330ad9
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 3330ad9

Please sign in to comment.