From ed97a44e89ec6ba4768df828492e1134c61fca3b Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Mon, 8 Nov 2021 11:22:17 +0000 Subject: [PATCH] Filter mutants returning Boolean.TRUE & Bollean.FALSE for #957 --- README.md | 1 + .../analysis/InstructionMatchers.java | 11 +++ .../EquivalentReturnMutationFilter.java | 68 ++++++++++++------- .../EquivalentReturnMutationFilterTest.java | 28 +++++++- .../intercept/javafeatures/FilterTester.java | 7 +- 5 files changed, 89 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index aae9ac965..49f47b4ab 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Read all about it at http://pitest.org * #952 Mutate map return to `emptyMap` instead of null * #954 Allow mutators to be excluded +* #957 Filter equivalent mutations to Boolean.TRUE and Boolean.FALSE ### 1.7.2 diff --git a/pitest-entry/src/main/java/org/pitest/bytecode/analysis/InstructionMatchers.java b/pitest-entry/src/main/java/org/pitest/bytecode/analysis/InstructionMatchers.java index 192c94256..d77e0ac65 100644 --- a/pitest-entry/src/main/java/org/pitest/bytecode/analysis/InstructionMatchers.java +++ b/pitest-entry/src/main/java/org/pitest/bytecode/analysis/InstructionMatchers.java @@ -13,6 +13,7 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.FrameNode; import org.objectweb.asm.tree.IincInsnNode; import org.objectweb.asm.tree.JumpInsnNode; @@ -157,6 +158,16 @@ public static Match isInstruction(final SlotRead c.retrieve(target).get() == t; } + public static Match getStatic(String owner, String field) { + return (c, t) -> { + if (t instanceof FieldInsnNode) { + FieldInsnNode fieldNode = (FieldInsnNode) t; + return t.getOpcode() == Opcodes.GETSTATIC && fieldNode.name.equals(field) && fieldNode.owner.equals(owner); + } + return false; + }; + } + /** * Records if a instruction matches the target, but always returns true */ diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/equivalent/EquivalentReturnMutationFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/equivalent/EquivalentReturnMutationFilter.java index c16318742..9fa2b7c56 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/equivalent/EquivalentReturnMutationFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/equivalent/EquivalentReturnMutationFilter.java @@ -1,6 +1,7 @@ package org.pitest.mutationtest.build.intercept.equivalent; import static org.pitest.bytecode.analysis.InstructionMatchers.anyInstruction; +import static org.pitest.bytecode.analysis.InstructionMatchers.getStatic; import static org.pitest.bytecode.analysis.InstructionMatchers.isInstruction; import static org.pitest.bytecode.analysis.InstructionMatchers.methodCallNamed; import static org.pitest.bytecode.analysis.InstructionMatchers.notAnInstruction; @@ -40,6 +41,7 @@ import org.pitest.sequence.QueryParams; import org.pitest.sequence.QueryStart; import org.pitest.sequence.SequenceMatcher; +import org.pitest.sequence.SequenceQuery; import org.pitest.sequence.Slot; /** @@ -80,16 +82,22 @@ public InterceptorType type() { class HardCodedTrueEquivalentFilter implements MutationInterceptor { private static final Slot MUTATED_INSTRUCTION = Slot.create(AbstractInsnNode.class); - - static final SequenceMatcher BOXED_TRUE = QueryStart - .any(AbstractInsnNode.class) - .zeroOrMore(QueryStart.match(anyInstruction())) - .then(opCode(Opcodes.ICONST_1)) - .then(methodCallNamed("valueOf")) - .then(isInstruction(MUTATED_INSTRUCTION.read())) - .zeroOrMore(QueryStart.match(anyInstruction())) - .compile(QueryParams.params(AbstractInsnNode.class) - .withIgnores(notAnInstruction()) + + static final SequenceQuery BOXED_TRUE = QueryStart + .match(opCode(Opcodes.ICONST_1)) + .then(methodCallNamed("valueOf")); + + static final SequenceQuery CONSTANT_TRUE = QueryStart + .match(getStatic("java/lang/Boolean","TRUE")); + + static final SequenceMatcher EQUIVALENT_TRUE = QueryStart + .any(AbstractInsnNode.class) + .zeroOrMore(QueryStart.match(anyInstruction())) + .then(BOXED_TRUE.or(CONSTANT_TRUE)) + .then(isInstruction(MUTATED_INSTRUCTION.read())) + .zeroOrMore(QueryStart.match(anyInstruction())) + .compile(QueryParams.params(AbstractInsnNode.class) + .withIgnores(notAnInstruction()) ); private static final Set MUTATOR_IDS = new HashSet<>(); @@ -137,7 +145,7 @@ private boolean primitiveTrue(int instruction, MethodTree method) { private boolean boxedTrue(int instruction, MethodTree method) { final Context context = Context.start(method.instructions(), false); context.store(MUTATED_INSTRUCTION.write(), method.instruction(instruction)); - return BOXED_TRUE.matches(method.instructions(), context); + return EQUIVALENT_TRUE.matches(method.instructions(), context); } }; } @@ -146,6 +154,7 @@ private boolean boxedTrue(int instruction, MethodTree method) { public void end() { this.currentClass = null; } + } @@ -201,21 +210,32 @@ public void end() { } +/** + * Handles methods already returning a 0 value, and also + * those returning Boolean.FALSE + */ class EmptyReturnsFilter implements MutationInterceptor { private static final Slot MUTATED_INSTRUCTION = Slot.create(AbstractInsnNode.class); - - private static final SequenceMatcher CONSTANT_ZERO = QueryStart - .any(AbstractInsnNode.class) - .zeroOrMore(QueryStart.match(anyInstruction())) - .then(isZeroConstant()) - .then(methodCallNamed("valueOf")) - .then(isInstruction(MUTATED_INSTRUCTION.read())) - .zeroOrMore(QueryStart.match(anyInstruction())) - .compile(QueryParams.params(AbstractInsnNode.class) - .withIgnores(notAnInstruction()) - ); - + + static final SequenceQuery CONSTANT_ZERO = QueryStart + .match(isZeroConstant()) + .then(methodCallNamed("valueOf")); + + static final SequenceQuery CONSTANT_FALSE = QueryStart + .match(getStatic("java/lang/Boolean","FALSE")); + + static final SequenceMatcher ZERO_VALUES = QueryStart + .any(AbstractInsnNode.class) + .zeroOrMore(QueryStart.match(anyInstruction())) + .then(CONSTANT_ZERO.or(CONSTANT_FALSE)) + .then(isInstruction(MUTATED_INSTRUCTION.read())) + .zeroOrMore(QueryStart.match(anyInstruction())) + .compile(QueryParams.params(AbstractInsnNode.class) + .withIgnores(notAnInstruction()) + ); + + private static final Set MUTATOR_IDS = new HashSet<>(); private static final Set ZERO_CONSTANTS = new HashSet<>(); static { @@ -278,7 +298,7 @@ private Boolean returnsZeroValue(MethodTree method, int mutatedInstruction) { final Context context = Context.start(method.instructions(), false); context.store(MUTATED_INSTRUCTION.write(), method.instruction(mutatedInstruction)); - return CONSTANT_ZERO.matches(method.instructions(), context); + return ZERO_VALUES.matches(method.instructions(), context); } private boolean returns(MethodTree method, int mutatedInstruction, String owner, String name) { diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/equivalent/EquivalentReturnMutationFilterTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/equivalent/EquivalentReturnMutationFilterTest.java index d90252b2f..876224df7 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/equivalent/EquivalentReturnMutationFilterTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/equivalent/EquivalentReturnMutationFilterTest.java @@ -90,7 +90,8 @@ public void filtersEquivalentPrimitiveDoubleMutants() { @Test public void filtersEquivalentBoxedBooleanMutants() { - this.verifier.assertFiltersNMutationFromClass(1, AlreadyReturnsBoxedFalse.class); + this.verifier.assertFiltersMutationsFromMutator(FALSE_RETURNS.getGloballyUniqueId() + , AlreadyReturnsBoxedFalse.class); } @Test @@ -99,6 +100,18 @@ public void filtersEquivalentBoxedBooleanTrueMutants() { , AlreadyReturnsBoxedTrue.class); } + @Test + public void filtersEquivalentConstantTrueMutants() { + this.verifier.assertFiltersMutationsFromMutator(TRUE_RETURNS.getGloballyUniqueId() + , ReturnsConstantTrue.class); + } + + @Test + public void filtersEquivalentConstantFalseMutants() { + this.verifier.assertFiltersMutationsFromMutator(FALSE_RETURNS.getGloballyUniqueId() + , ReturnsConstantFalse.class); + } + @Test public void filtersEquivalentBoxedIntegerMutants() { this.verifier.assertFiltersNMutationFromClass(1, AlreadyReturnsBoxedZeroInteger.class); @@ -250,6 +263,19 @@ public Integer a() { } } +class ReturnsConstantTrue { + public Boolean a() { + return Boolean.TRUE; + } +} + +class ReturnsConstantFalse { + public Boolean a() { + return Boolean.FALSE; + } +} + + class CallsAnIntegerReturningStaticWith0 { static Integer foo(int a) { diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/FilterTester.java b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/FilterTester.java index b6c6878c4..e7094ecb4 100755 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/FilterTester.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/FilterTester.java @@ -231,7 +231,7 @@ public void assertFiltersMutationsFromMutator(String id, Class clazz) { final List filteredOut = FCollection.filter(mutations, notIn(actual)); - softly.assertThat(filteredOut).describedAs("No mutants filtered").isNotEmpty(); + softly.assertThat(filteredOut).describedAs("No mutants filtered " + s).isNotEmpty(); softly.assertThat(filteredOut).have(mutatedBy(id)); softly.assertAll(); @@ -354,4 +354,9 @@ class Sample { ClassName className; String compiler; ClassTree clazz; + + @Override + public String toString() { + return "Compiled by " + compiler + "\n" + clazz.toString(); + } }