Skip to content

Commit

Permalink
Merge pull request #1030 from hcoles/feature/enumswitch
Browse files Browse the repository at this point in the history
filter enum switches
  • Loading branch information
hcoles authored Jun 14, 2022
2 parents 58c9744 + 865aace commit 93a1a6c
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,17 @@ public class AssertFilter extends RegionInterceptor {
);

private static Match<AbstractInsnNode> getStatic(String name) {
return (c,n) -> {
return (c, n) -> {
if (n instanceof FieldInsnNode) {
return result(((FieldInsnNode) n).name.equals(name), c);
return result(((FieldInsnNode) n).name.equals(name), c);
}
return result(false,c);
return result(false, c);
};
}


private static Match<AbstractInsnNode> store(SlotWrite<AbstractInsnNode> slot) {
return (c,n) -> result(true, c.store(slot, n));
return (c, n) -> result(true, c.store(slot, n));
}

protected List<Region> computeRegions(MethodTree method) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package org.pitest.mutationtest.build.intercept.javafeatures;

import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.pitest.bytecode.analysis.MethodTree;
import org.pitest.mutationtest.build.intercept.Region;
import org.pitest.mutationtest.build.intercept.RegionInterceptor;
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 org.pitest.bytecode.analysis.InstructionMatchers.anyInstruction;
import static org.pitest.bytecode.analysis.InstructionMatchers.methodCallNamed;
import static org.pitest.bytecode.analysis.InstructionMatchers.notAnInstruction;
import static org.pitest.bytecode.analysis.OpcodeMatchers.ALOAD;
import static org.pitest.bytecode.analysis.OpcodeMatchers.IALOAD;
import static org.pitest.bytecode.analysis.OpcodeMatchers.LOOKUPSWITCH;
import static org.pitest.bytecode.analysis.OpcodeMatchers.TABLESWITCH;
import static org.pitest.sequence.Result.result;

/**
* For switches on Enums java creates a synthetic class
* with an int array field. The following code then accesses
* it
*
* GETSTATIC pkg/Person$1.$SwitchMap$pkg$MyEnum : [I
* ALOAD 1
* INVOKEVIRTUAL pkg/MyEnum.ordinal ()I
* IALOAD
* LOOKUPSWITCH
*
* As the generated class is synthetic, no mutants will be
* seeded in it. The code that accesses it must however be
* filtered.
*/
public class EnumSwitchFilter extends RegionInterceptor {
static final Slot<AbstractInsnNode> START = Slot.create(AbstractInsnNode.class);
static final Slot<AbstractInsnNode> END = Slot.create(AbstractInsnNode.class);

static final SequenceMatcher<AbstractInsnNode> ENUM_SWITCH = QueryStart
.any(AbstractInsnNode.class)
.then(getStatic("$SwitchMap$").and(store(START.write())))
.then(ALOAD)
.then(methodCallNamed("ordinal"))
.then(IALOAD.and(store(END.write())))
.then(LOOKUPSWITCH.or(TABLESWITCH))
.zeroOrMore(QueryStart.match(anyInstruction()))
.compile(QueryParams.params(AbstractInsnNode.class)
.withIgnores(notAnInstruction())
);

private static Match<AbstractInsnNode> getStatic(String name) {
return (c, n) -> {
if (n instanceof FieldInsnNode) {
return result(((FieldInsnNode) n).name.contains(name), c);
}
return result(false, c);
};
}


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

private static Match<AbstractInsnNode> store(SlotWrite<AbstractInsnNode> slot) {
return (c, n) -> result(true, c.store(slot, n));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.pitest.mutationtest.build.intercept.javafeatures;

import org.pitest.mutationtest.build.InterceptorParameters;
import org.pitest.mutationtest.build.MutationInterceptor;
import org.pitest.mutationtest.build.MutationInterceptorFactory;
import org.pitest.plugin.Feature;

public class EnumSwitchFilterFactory implements MutationInterceptorFactory {

@Override
public String description() {
return "Enum switch filter";
}

@Override
public MutationInterceptor createInterceptor(InterceptorParameters params) {
return new EnumSwitchFilter();
}

@Override
public Feature provides() {
return Feature.named("FESWITCH")
.withOnByDefault(true)
.withDescription("Filters mutations in switch statements on enums");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ org.pitest.mutationtest.build.intercept.javafeatures.EnumConstructorFilterFactor
org.pitest.mutationtest.build.intercept.javafeatures.RecordFilterFactory
org.pitest.mutationtest.build.intercept.javafeatures.StringSwitchFilterFactory
org.pitest.mutationtest.build.intercept.javafeatures.AssertionsFilterFactory
org.pitest.mutationtest.build.intercept.javafeatures.EnumSwitchFilterFactory
org.pitest.mutationtest.build.intercept.logging.LoggingCallsFilterFactory
org.pitest.mutationtest.build.intercept.timeout.InfiniteForLoopFilterFactory
org.pitest.mutationtest.build.intercept.timeout.InfiniteIteratorLoopFilterFactory
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package org.pitest.mutationtest.build.intercept.javafeatures;

import org.junit.Test;
import org.pitest.classinfo.ClassName;
import org.pitest.mutationtest.engine.gregor.mutators.NullMutateEverything;
import org.pitest.verifier.interceptors.InterceptorVerifier;
import org.pitest.verifier.interceptors.VerifierStart;

import static org.pitest.bytecode.analysis.InstructionMatchers.methodCallTo;
import static org.pitest.bytecode.analysis.OpcodeMatchers.GETSTATIC;
import static org.pitest.bytecode.analysis.OpcodeMatchers.LOOKUPSWITCH;
import static org.pitest.bytecode.analysis.OpcodeMatchers.TABLESWITCH;

public class EnumSwitchTest {

EnumSwitchFilterFactory underTest = new EnumSwitchFilterFactory();
InterceptorVerifier v = VerifierStart.forInterceptorFactory(underTest)
.usingMutator(new NullMutateEverything());

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

@Test
public void filtersOrdinalCall() {
v.forClass(HasEnumTableSwitch.class)
.forCodeMatching(methodCallTo(ClassName.fromClass(Letters.class), "ordinal").asPredicate())
.mutantsAreGenerated()
.allMutantsAreFiltered()
.verify();
}

@Test
public void filtersFieldAccess() {
v.forClass(HasEnumTableSwitch.class)
.forCodeMatching(GETSTATIC.asPredicate())
.mutantsAreGenerated()
.allMutantsAreFiltered()
.verify();
}

@Test
public void leavesTableSwitchStatement() {
v.forClass(HasEnumTableSwitch.class)
.forCodeMatching(TABLESWITCH.asPredicate())
.mutantsAreGenerated()
.noMutantsAreFiltered()
.verify();
}

@Test
public void filtersOrdinalCallWhenLookupSwitch() {
v.forClass(HasEnumLookupSwitch.class)
.forCodeMatching(methodCallTo(ClassName.fromClass(Sides.class), "ordinal").asPredicate())
.mutantsAreGenerated()
.allMutantsAreFiltered()
.verify();
}

@Test
public void leavesLookupSwitchStatement() {
v.forClass(HasEnumLookupSwitch.class)
.forCodeMatching(LOOKUPSWITCH.asPredicate())
.mutantsAreGenerated()
.noMutantsAreFiltered()
.verify();
}
}

class NormalSwitch {
int foo(int i) {
switch (i) {
case 1:
return 1;
case 2:
case 3:
return 2;
default:
return 0;
}
}
}

enum Letters {
A,B,C,D,E,F;
}

class HasEnumTableSwitch {
int foo(Letters i) {
switch (i) {
case A:
return 1;
case B:
case E:
return 2;
default:
return 0;
}
}
}

class HasEnumLookupSwitch {
int foo(Sides i) {
switch (i) {
case UP:
return 1;
case DOWN:
return 0;
}
return 2;
}
}

enum Sides {
UP, DOWN;
}

0 comments on commit 93a1a6c

Please # to comment.