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 d77e0ac65..d7d5351f6 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 @@ -10,6 +10,7 @@ import static org.objectweb.asm.Opcodes.ICONST_M1; import static org.objectweb.asm.Opcodes.ILOAD; import static org.objectweb.asm.Opcodes.ISTORE; +import static org.pitest.sequence.Result.result; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; @@ -41,17 +42,17 @@ public static Match notAnInstruction() { } public static Match opCode(final int opcode) { - return (c, a) -> a.getOpcode() == opcode; + return (c, a) -> result(a.getOpcode() == opcode, c); } public static Match isA( final Class cls) { - return (c, a) -> a.getClass().isAssignableFrom(cls); + return (c, a) -> result(a.getClass().isAssignableFrom(cls), c); } public static Match incrementsVariable(final SlotRead counterVariable) { - return (context, a) -> (a instanceof IincInsnNode) - && context.retrieve(counterVariable).filter(isEqual(((IincInsnNode)a).var)).isPresent(); + return (context, a) -> result((a instanceof IincInsnNode) + && context.retrieve(counterVariable).filter(isEqual(((IincInsnNode)a).var)).isPresent(), context); } public static Match anIStore( @@ -59,9 +60,19 @@ public static Match anIStore( return opCode(Opcodes.ISTORE).and(aVariableAccess(counterVariable)); } + public static Match anILoad( + final SlotWrite counterVariable) { + return opCode(Opcodes.ILOAD).and(aVariableAccess(counterVariable)); + } + public static Match aVariableAccess( final SlotWrite counterVariable) { - return (c, t) -> (t instanceof VarInsnNode) && c.store(counterVariable, ((VarInsnNode) t).var); + return (c, t) -> { + if (t instanceof VarInsnNode) { + return result(true, c.store(counterVariable, ((VarInsnNode) t).var)); + } + return result(false, c); + }; } public static Match anIStoreTo( @@ -76,8 +87,8 @@ public static Match anILoadOf( public static Match variableMatches( final SlotRead counterVariable) { - return (c, t) -> (t instanceof VarInsnNode) - && c.retrieve(counterVariable).filter(isEqual(((VarInsnNode) t).var)).isPresent(); + return (c, t) -> result((t instanceof VarInsnNode) + && c.retrieve(counterVariable).filter(isEqual(((VarInsnNode) t).var)).isPresent(), c); } @@ -100,9 +111,9 @@ public static Match aJump() { } public static Match aConditionalJump() { - return (c, t) -> (t instanceof JumpInsnNode) + return (c, t) -> result((t instanceof JumpInsnNode) && (t.getOpcode() != Opcodes.GOTO) - && (t.getOpcode() != Opcodes.JSR); + && (t.getOpcode() != Opcodes.JSR), c); } public static Match aConditionalJumpTo(Slot label) { @@ -113,19 +124,18 @@ public static Match aConditionalJumpTo(Slot label) public static Match writeNodeToSlot(final SlotWrite slot, final Class clazz) { return (c, t) -> { if (clazz.isAssignableFrom(t.getClass()) ) { - c.store(slot, clazz.cast(t)); - return true; + return result(true, c.store(slot, clazz.cast(t))); } - return false; + return result(false, c); }; } public static Match methodCallThatReturns(final ClassName type) { return (c, t) -> { if ( t instanceof MethodInsnNode ) { - return ((MethodInsnNode) t).desc.endsWith(type.asInternalName() + ";"); + return result(((MethodInsnNode) t).desc.endsWith(type.asInternalName() + ";"), c); } - return false; + return result(false, c); }; } @@ -137,9 +147,18 @@ public static Match methodCallNamed(String name) { return (c, t) -> { if ( t instanceof MethodInsnNode ) { final MethodInsnNode call = (MethodInsnNode) t; - return call.name.equals(name); + return result(call.name.equals(name), c); + } + return result(false, c); + }; + } + + public static Match methodDescEquals(final String desc) { + return (c, t) -> { + if ( t instanceof MethodInsnNode ) { + return result(((MethodInsnNode) t).desc.equals(desc), c); } - return false; + return result(false, c); }; } @@ -147,24 +166,24 @@ public static Match methodCallTo(final ClassName owner, final return (c, t) -> { if ( t instanceof MethodInsnNode ) { final MethodInsnNode call = (MethodInsnNode) t; - return call.name.equals(name) && call.owner.equals(owner.asInternalName()); + return result( call.name.equals(name) && call.owner.equals(owner.asInternalName()), c); } - return false; + return result(false, c); }; } public static Match isInstruction(final SlotRead target) { - return (c, t) -> c.retrieve(target).get() == t; + return (c, t) -> result(c.retrieve(target).get() == t, c); } 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 result( t.getOpcode() == Opcodes.GETSTATIC && fieldNode.name.equals(field) && fieldNode.owner.equals(owner), c); } - return false; + return result(false, c); }; } @@ -174,9 +193,9 @@ public static Match getStatic(String owner, String field) { public static Match recordTarget(final SlotRead target, final SlotWrite found) { return (c, t) -> { if (c.retrieve(target).get() == t) { - c.store(found, true); + return result(true, c.store(found, true)); } - return true; + return result(true, c); }; } @@ -185,10 +204,9 @@ private static Match storeJumpTarget( final SlotWrite label) { return (c, t) -> { if (t instanceof JumpInsnNode ) { - c.store(label, ((JumpInsnNode) t).label); - return true; + return result(true, c.store(label, ((JumpInsnNode) t).label)); } - return false; + return result(false, c); }; } @@ -196,11 +214,11 @@ public static Match jumpsTo( final SlotRead loopStart) { return (context, a) -> { if (!(a instanceof JumpInsnNode)) { - return false; + return result(false, context); } final JumpInsnNode jump = (JumpInsnNode) a; - return context.retrieve(loopStart).filter(isEqual(jump.label)).isPresent(); + return result(context.retrieve(loopStart).filter(isEqual(jump.label)).isPresent(), context); }; } @@ -218,19 +236,19 @@ public static Match labelNode( final SlotRead loopEnd) { return (c, t) -> { if (!(t instanceof LabelNode)) { - return false; + return result(false, c); } final LabelNode l = (LabelNode) t; - return c.retrieve(loopEnd).filter(isEqual(l)).isPresent(); + return result(c.retrieve(loopEnd).filter(isEqual(l)).isPresent(), c); }; } public static Match debug(final String msg) { return (context, a) -> { - context.debug(msg); - return true; + context.debug(msg, a); + return result(true, context); }; } } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/InterceptorType.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/InterceptorType.java index 4611863ef..b74267301 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/InterceptorType.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/InterceptorType.java @@ -8,11 +8,27 @@ * * OTHER - * MODIFY - Modify mutants in a way that is functionally significant (e.g mark as poisoning JVM) + * PRE_SCAN_FILTER - Remove mutants from processing, in prescan and main scan * FILTER - Remove mutants from processing * MODIFY_COSMETIC - Modify mutants in way that will not affect processing (e.g update descriptions) * REPORT - Output mutant in their final state * */ public enum InterceptorType { - OTHER, MODIFY, FILTER, MODIFY_COSMETIC, REPORT + OTHER(true), + MODIFY(true), + PRE_SCAN_FILTER(true), + FILTER(false), + MODIFY_COSMETIC(false), + REPORT(false); + + private final boolean includeInPrescan; + + InterceptorType(boolean includeInPrescan) { + this.includeInPrescan = includeInPrescan; + } + + public boolean includeInPrescan() { + return includeInPrescan; + } } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/equivalent/EqualsPerformanceShortcutFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/equivalent/EqualsPerformanceShortcutFilter.java index aac7438d8..0d5d51d75 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/equivalent/EqualsPerformanceShortcutFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/equivalent/EqualsPerformanceShortcutFilter.java @@ -17,7 +17,6 @@ import org.pitest.bytecode.analysis.InstructionMatchers; import org.pitest.bytecode.analysis.MethodMatchers; import org.pitest.bytecode.analysis.MethodTree; -import org.pitest.functional.FCollection; import org.pitest.mutationtest.build.InterceptorType; import org.pitest.mutationtest.build.MutationInterceptor; import org.pitest.mutationtest.engine.Location; @@ -31,7 +30,7 @@ public class EqualsPerformanceShortcutFilter implements MutationInterceptor { private static final boolean DEBUG = false; - // Looks fairly specifically for a conditional mutated to a unconditional + // Looks fairly specifically for a conditional mutated to an unconditional // rather than any always false condition static final SequenceMatcher ALWAYS_FALSE = QueryStart .any(AbstractInsnNode.class) @@ -60,9 +59,14 @@ public void begin(ClassTree clazz) { @Override public Collection intercept( Collection mutations, Mutater m) { - final List doNotTouch = FCollection.filter(mutations, inEqualsMethod().negate()); + final List doNotTouch = mutations.stream() + .filter(inEqualsMethod().negate()) + .collect(Collectors.toList()); + if (doNotTouch.size() != mutations.size()) { - final List inEquals = FCollection.filter(mutations, inEqualsMethod()); + final List inEquals = mutations.stream() + .filter(inEqualsMethod()) + .collect(Collectors.toList()); final List filtered = filter(inEquals, m); doNotTouch.addAll(filtered); } @@ -83,10 +87,10 @@ private List filter( } private Predicate isShortcutEquals(final MethodTree tree, final Mutater m) { - return a -> shortCutEquals(tree,a, m); + return a -> shortCutEquals(tree, a, m); } - private Boolean shortCutEquals(MethodTree tree, MutationDetails a, Mutater m) { + private boolean shortCutEquals(MethodTree tree, MutationDetails a, Mutater m) { if (!mutatesAConditionalJump(tree, a.getInstructionIndex())) { return false; } @@ -102,7 +106,7 @@ private Boolean shortCutEquals(MethodTree tree, MutationDetails a, Mutater m) { private boolean mutatesAConditionalJump(MethodTree tree, int index) { final AbstractInsnNode mutatedInsns = tree.instruction(index); - return InstructionMatchers.aConditionalJump().test(null, mutatedInsns); + return InstructionMatchers.aConditionalJump().test(null, mutatedInsns).result(); } private Predicate inEqualsMethod() { 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 de4230d94..fb525298c 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 @@ -12,6 +12,7 @@ import static org.pitest.bytecode.analysis.InstructionMatchers.notAnInstruction; import static org.pitest.bytecode.analysis.InstructionMatchers.opCode; import static org.pitest.bytecode.analysis.InstructionMatchers.variableMatches; +import static org.pitest.sequence.Result.result; import java.util.Arrays; import java.util.Collection; @@ -151,8 +152,8 @@ 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)); + Context context = Context.start(); + context = context.store(MUTATED_INSTRUCTION.write(), method.instruction(instruction)); return EQUIVALENT_TRUE.matches(method.instructions(), context); } }; @@ -295,7 +296,7 @@ private static Match aStoreTo(Slot variable) { } private static Match isZeroConstant() { - return (context,node) -> ZERO_CONSTANTS.contains(node.getOpcode()); + return (context,node) -> result(ZERO_CONSTANTS.contains(node.getOpcode()), context); } private Predicate isEquivalent(Mutater m) { @@ -318,8 +319,8 @@ public boolean test(MutationDetails a) { private Boolean returnsZeroValue(SequenceMatcher sequence, MethodTree method, int mutatedInstruction) { - final Context context = Context.start(method.instructions(), false); - context.store(MUTATED_INSTRUCTION.write(), method.instruction(mutatedInstruction)); + Context context = Context.start(); + context = context.store(MUTATED_INSTRUCTION.write(), method.instruction(mutatedInstruction)); return sequence.matches(method.instructions(), context); } @@ -354,9 +355,9 @@ private static Match takesNoArgs() { return (c,node) -> { if (node instanceof MethodInsnNode ) { final MethodInsnNode call = (MethodInsnNode) node; - return Type.getArgumentTypes(call.desc).length == 0; + return result(Type.getArgumentTypes(call.desc).length == 0, c); } - return false; + return result(false, c); }; } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ForEachLoopFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ForEachLoopFilter.java index 3443b0c02..55ad31017 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ForEachLoopFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ForEachLoopFilter.java @@ -14,21 +14,24 @@ import static org.pitest.bytecode.analysis.InstructionMatchers.methodCallTo; import static org.pitest.bytecode.analysis.InstructionMatchers.notAnInstruction; import static org.pitest.bytecode.analysis.InstructionMatchers.opCode; -import static org.pitest.bytecode.analysis.InstructionMatchers.recordTarget; +import static org.pitest.sequence.Result.result; +import java.util.ArrayList; import java.util.Collection; +import java.util.IdentityHashMap; import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.function.Predicate; +import java.util.stream.Collectors; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.LabelNode; import org.pitest.bytecode.analysis.ClassTree; -import org.pitest.bytecode.analysis.MethodMatchers; import org.pitest.bytecode.analysis.MethodTree; import org.pitest.classinfo.ClassName; -import org.pitest.functional.FCollection; -import org.pitest.functional.prelude.Prelude; import org.pitest.mutationtest.build.InterceptorType; import org.pitest.mutationtest.build.MutationInterceptor; import org.pitest.mutationtest.engine.Mutater; @@ -45,23 +48,20 @@ public class ForEachLoopFilter implements MutationInterceptor { private static final boolean DEBUG = false; - private static final Slot MUTATED_INSTRUCTION = Slot.create(AbstractInsnNode.class); - private static final Slot FOUND = Slot.create(Boolean.class); + private static final Slot> LOOP_INSTRUCTIONS = Slot.createList(AbstractInsnNode.class); - - private static final SequenceMatcher ITERATOR_LOOP = QueryStart - .match(Match.never()) - .or(conditionalAtStart()) + private static final SequenceMatcher ITERATOR_LOOP = + conditionalAtStart() .or(conditionalAtEnd()) .or(arrayConditionalAtEnd()) .or(arrayConditionalAtStart()) - .then(containMutation(FOUND)) .compile(QueryParams.params(AbstractInsnNode.class) .withIgnores(notAnInstruction()) .withDebug(DEBUG) ); private ClassTree currentClass; + private Map> cache; private static SequenceQuery conditionalAtEnd() { @@ -79,7 +79,7 @@ private static SequenceQuery conditionalAtEnd() { .zeroOrMore(QueryStart.match(anyInstruction())) .then(labelNode(loopEnd.read())) .then(opCode(Opcodes.ALOAD)) - .then(methodCallTo(ClassName.fromString("java/util/Iterator"), "hasNext").and(mutationPoint())) + .then(hasNextMethodCall().and(mutationPoint())) .then(aConditionalJumpTo(loopStart).and(mutationPoint())) .zeroOrMore(QueryStart.match(anyInstruction())); } @@ -95,7 +95,7 @@ private static SequenceQuery conditionalAtStart() { .then(opCode(Opcodes.ASTORE)) .then(aLabelNode(loopStart.write())) .then(opCode(Opcodes.ALOAD)) - .then(methodCallTo(ClassName.fromString("java/util/Iterator"), "hasNext").and(mutationPoint())) + .then(hasNextMethodCall().and(mutationPoint())) .then(aConditionalJump().and(jumpsTo(loopEnd.write())).and(mutationPoint())) .then(opCode(Opcodes.ALOAD)) .then(methodCallTo(ClassName.fromString("java/util/Iterator"), "next").and(mutationPoint())) @@ -105,7 +105,6 @@ private static SequenceQuery conditionalAtStart() { .zeroOrMore(QueryStart.match(anyInstruction())); } - private static SequenceQuery arrayConditionalAtEnd() { final Slot loopStart = Slot.create(LabelNode.class); final Slot loopEnd = Slot.create(LabelNode.class); @@ -149,21 +148,25 @@ private static SequenceQuery arrayConditionalAtStart() { .zeroOrMore(QueryStart.match(anyInstruction())); } + private static Match hasNextMethodCall() { + return methodCallTo(ClassName.fromString("java/util/Iterator"), "hasNext"); + } private static Match aMethodCallReturningAnIterator() { return methodCallThatReturns(ClassName.fromClass(Iterator.class)); } private static Match mutationPoint() { - return recordTarget(MUTATED_INSTRUCTION.read(), FOUND.write()); + return (c,t) -> { + c.retrieve(LOOP_INSTRUCTIONS.read()).get().add(t); + return result(true, c); + }; } - private static Match containMutation(final Slot found) { - return (c, t) -> c.retrieve(found.read()).isPresent(); + return (c, t) -> result(c.retrieve(found.read()).isPresent(), c); } - @Override public InterceptorType type() { return InterceptorType.FILTER; @@ -172,31 +175,49 @@ public InterceptorType type() { @Override public void begin(ClassTree clazz) { this.currentClass = clazz; + this.cache = new IdentityHashMap<>(); } @Override public Collection intercept( Collection mutations, Mutater m) { - return FCollection.filter(mutations, Prelude.not(mutatesIteratorLoopPlumbing())); + return mutations.stream().filter(mutatesIteratorLoopPlumbing().negate()) + .collect(Collectors.toList()); } private Predicate mutatesIteratorLoopPlumbing() { return a -> { final int instruction = a.getInstructionIndex(); - final MethodTree method = currentClass.methods().stream() - .filter(MethodMatchers.forLocation(a.getId().getLocation())) - .findFirst() - .get(); + final MethodTree method = currentClass.method(a.getId().getLocation()).get(); + + //performance hack + if (!mightContainForLoop(method.instructions())) { + return false; + } + final AbstractInsnNode mutatedInstruction = method.instruction(instruction); - final Context context = Context.start(method.instructions(), DEBUG); - context.store(MUTATED_INSTRUCTION.write(), mutatedInstruction); - return ITERATOR_LOOP.matches(method.instructions(), context); + Set toAvoid = cache.computeIfAbsent(method, this::findLoopInstructions); + + return toAvoid.contains(mutatedInstruction); }; } + private Set findLoopInstructions(MethodTree method) { + Context context = Context.start(DEBUG).store(LOOP_INSTRUCTIONS.write(), new ArrayList<>()); + return ITERATOR_LOOP.contextMatches(method.instructions(), context).stream() + .flatMap(c -> c.retrieve(LOOP_INSTRUCTIONS.read()).get().stream()) + .collect(Collectors.toSet()); + } + + private boolean mightContainForLoop(List instructions) { + return instructions.stream() + .anyMatch(i -> hasNextMethodCall().or(opCode(Opcodes.ARRAYLENGTH)).test(null, i).result()); + } + @Override public void end() { this.currentClass = null; + this.cache = null; } } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ImplicitNullCheckFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ImplicitNullCheckFilter.java index 9c57d33d8..5a5cb3252 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ImplicitNullCheckFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ImplicitNullCheckFilter.java @@ -13,6 +13,7 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.MethodInsnNode; import org.pitest.bytecode.analysis.ClassTree; import org.pitest.bytecode.analysis.MethodTree; import org.pitest.classinfo.ClassName; @@ -72,8 +73,13 @@ private Predicate isAnImplicitNullCheck() { final AbstractInsnNode mutatedInstruction = method.instruction(instruction); - final Context context = Context.start(method.instructions(), DEBUG); - context.store(MUTATED_INSTRUCTION.write(), mutatedInstruction); + // performance hack + if (!(mutatedInstruction instanceof MethodInsnNode)) { + return false; + } + + Context context = Context.start(DEBUG); + context = context.store(MUTATED_INSTRUCTION.write(), mutatedInstruction); return GET_CLASS_NULL_CHECK.matches(method.instructions(), context); }; } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/InlinedFinallyBlockFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/InlinedFinallyBlockFilter.java index 64a6e3680..2c5439b7e 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/InlinedFinallyBlockFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/InlinedFinallyBlockFilter.java @@ -45,6 +45,7 @@ import static org.pitest.functional.FCollection.map; import static org.pitest.functional.FCollection.mapTo; import static org.pitest.functional.prelude.Prelude.not; +import static org.pitest.sequence.Result.result; /** * Detects mutations on same line, but within different code blocks. This @@ -92,9 +93,9 @@ private static Match handlerLabel(Slot handlers) { if (t instanceof LabelNode) { LabelNode label = (LabelNode) t; List labels = c.retrieve(handlers.read()).get(); - return labels.contains(label); + return result(labels.contains(label), c); } - return false; + return result(false, c); }; } @@ -180,9 +181,9 @@ private boolean isInFinallyBlock(MutationDetails m) { AbstractInsnNode mutatedInstruction = method.instruction(m.getInstructionIndex()); - Context context = Context.start(method.instructions(), DEBUG); - context.store(MUTATED_INSTRUCTION.write(), mutatedInstruction); - context.store(HANDLERS.write(), handlers); + Context context = Context.start(DEBUG); + context = context.store(MUTATED_INSTRUCTION.write(), mutatedInstruction); + context = context.store(HANDLERS.write(), handlers); return IS_IN_HANDLER.matches(method.instructions(), context); } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/MethodReferenceNullCheckFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/MethodReferenceNullCheckFilter.java index 4c9a0432c..b7441232e 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/MethodReferenceNullCheckFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/MethodReferenceNullCheckFilter.java @@ -12,6 +12,7 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; import org.pitest.bytecode.analysis.ClassTree; import org.pitest.bytecode.analysis.MethodMatchers; import org.pitest.bytecode.analysis.MethodTree; @@ -77,9 +78,12 @@ private Predicate isAnImplicitNullCheck() { .get(); final AbstractInsnNode mutatedInstruction = method.instruction(instruction); - - final Context context = Context.start(method.instructions(), DEBUG); - context.store(MUTATED_INSTRUCTION.write(), mutatedInstruction); + // performance hack + if (!(mutatedInstruction instanceof MethodInsnNode)) { + return false; + } + Context context = Context.start(DEBUG); + context = context.store(MUTATED_INSTRUCTION.write(), mutatedInstruction); return NULL_CHECK.matches(method.instructions(), context); }; } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/TryWithResourcesFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/TryWithResourcesFilter.java index b5003b7ca..cc78f4f0a 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/TryWithResourcesFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/TryWithResourcesFilter.java @@ -13,126 +13,203 @@ 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.SequenceQuery; import org.pitest.sequence.Slot; +import org.pitest.sequence.SlotRead; import java.util.Collection; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; import java.util.function.Predicate; +import java.util.stream.Collectors; import static org.objectweb.asm.Opcodes.ALOAD; import static org.objectweb.asm.Opcodes.ASTORE; -import static org.objectweb.asm.Opcodes.ATHROW; import static org.objectweb.asm.Opcodes.GOTO; +import static org.objectweb.asm.Opcodes.ATHROW; import static org.objectweb.asm.Opcodes.IFNONNULL; import static org.objectweb.asm.Opcodes.IFNULL; import static org.objectweb.asm.Opcodes.IF_ACMPEQ; +import static org.objectweb.asm.Opcodes.INVOKEINTERFACE; +import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; import static org.pitest.bytecode.analysis.InstructionMatchers.anyInstruction; +import static org.pitest.bytecode.analysis.InstructionMatchers.debug; import static org.pitest.bytecode.analysis.InstructionMatchers.isA; import static org.pitest.bytecode.analysis.InstructionMatchers.methodCallNamed; +import static org.pitest.bytecode.analysis.InstructionMatchers.methodDescEquals; import static org.pitest.bytecode.analysis.InstructionMatchers.notAnInstruction; import static org.pitest.bytecode.analysis.InstructionMatchers.opCode; -import static org.pitest.bytecode.analysis.InstructionMatchers.recordTarget; +import static org.pitest.bytecode.analysis.InstructionMatchers.writeNodeToSlot; import static org.pitest.sequence.QueryStart.any; import static org.pitest.sequence.QueryStart.match; +import static org.pitest.sequence.Result.result; public class TryWithResourcesFilter implements MutationInterceptor { private static final boolean DEBUG = false; - private static final Slot MUTATED_INSTRUCTION = Slot.create(AbstractInsnNode.class); - private static final Slot FOUND = Slot.create(Boolean.class); + private static final Slot> HANDLERS = Slot.createList(LabelNode.class); + + private static final Slot START = Slot.create(AbstractInsnNode.class); + private static final Slot END = Slot.create(AbstractInsnNode.class); + + private ClassTree currentClass; + private Map> cache; + + private static final SequenceMatcher TRY_WITH_RESOURCES = + javac11() + .or(javac()) + .or(ecj()) + .compile(QueryParams.params(AbstractInsnNode.class) + .withIgnores(notAnInstruction().or(aLabel().and(isLabel(HANDLERS.read()).negate()))) + .withDebug(DEBUG) + ); private static SequenceQuery javac11() { return any(AbstractInsnNode.class) .zeroOrMore(match(anyInstruction())) - .then(aLabel()) - .then(anALoad()) - .then(closeCall()) - .then(aLabel()) - .then(aGoto()) - .then(aLabel()) - .then(anAStore()) - .then(anALoad()) - .then(anALoad()) - .then(addSuppressedCall()) + .then(closeSequence(true)) + .zeroOrMore(match(anyInstruction())) + .then(isLabel(HANDLERS.read()).and(debug("handler"))) + .then(opCode(ASTORE)) + .then(opCode(ALOAD)) + .then(closeSequence(false)) + .then(opCode(GOTO)) + .then(isLabel(HANDLERS.read()).and(debug("handler"))) + .then(opCode(ASTORE)) + .then(opCode(ALOAD)) + .then(opCode(ALOAD)) + .then(addSuppressedMethodCall().and(debug("add suppressed"))) + .then(opCode(ALOAD)) + .then(opCode(ATHROW).and(recordPoint(END, true))) .zeroOrMore(match(anyInstruction())); } - private static SequenceQuery javac8() { + private static SequenceQuery javac() { return any(AbstractInsnNode.class) .zeroOrMore(match(anyInstruction())) - .then(ifNull()) - .then(anALoad()) - .then(ifNull()) - .then(aLabel()) - .then(anALoad()) - .then(closeCall()) - .then(aLabel()) - .then(aGoto()) - .then(aLabel()) - .then(anAStore()) - .then(aLabel()) - .then(anALoad()) - .then(anALoad()) - .then(addSuppressedCall()) - .then(aLabel()) - .then(aGoto()) - .then(aLabel()) - .then(anALoad()) - .then(closeCall()) + .then(javacCloseSequence(true)) + .zeroOrMore(match(anyInstruction())) + .then(isLabel(HANDLERS.read()).and(debug("handler"))) + .then(opCode(ASTORE)) + .then(opCode(ALOAD)) + .then(opCode(ASTORE)) + .then(opCode(ALOAD)) + .then(opCode(ATHROW)) + .then(opCode(ASTORE)) + .then(javacCloseSequence(false)) + .then(opCode(ALOAD)) + .then(opCode(ATHROW).and(recordPoint(END, true))) .zeroOrMore(match(anyInstruction())); } private static SequenceQuery ecj() { return any(AbstractInsnNode.class) .zeroOrMore(match(anyInstruction())) - .then(closeCall()) - .then(aLabel()) - .then(anALoad()) - .then(opCode(ATHROW).and(mutationPoint())) - .then(aLabel()) - .then(anAStore()) - .then(anALoad()) - .then(ifNonNull()) - .then(anALoad()) - .then(anAStore()) - .then(aGoto()) - .then(aLabel()) + .then(ecjCloseSequence(true)) + .zeroOrMore(match(anyInstruction())) + .then(ecjCloseAndThrow()) + .zeroOrMore(ecjCloseSuppress()) + .then(ecjSuppress()) + .then(opCode(ALOAD)) + .then(opCode(ATHROW).and(recordPoint(END, true))) .zeroOrMore(match(anyInstruction())); } - private static SequenceQuery ecjAddSuppressedCheck() { - return any(AbstractInsnNode.class) - .zeroOrMore(match(anyInstruction())) - .then(ifNonNull()) - .then(anALoad()) - .then(anAStore()) - .then(aGoto()) - .then(aLabel()) - .then(anALoad()) - .then(anALoad()) - .then(opCode(IF_ACMPEQ).and(mutationPoint())) - .then(anALoad()) - .then(anALoad()) - .then(addSuppressedCall()) - .then(aLabel()) - .zeroOrMore(match(anyInstruction())); + private static SequenceQuery ecjCloseSuppress() { + return ecjCloseSequence(false) + .then(opCode(GOTO)) // FIXME check jump target? + .then(ecjSuppress()) + .then(ecjCloseAndThrow()); } + private static SequenceQuery ecjSuppress() { + return match(opCode(ASTORE)) + .then(opCode(ALOAD)) + .then(opCode(IFNONNULL)) + .then(opCode(ALOAD)) + .then(opCode(ASTORE)) + .then(opCode(GOTO)) + .then(opCode(ALOAD)) + .then(opCode(ALOAD)) + .then(opCode(IF_ACMPEQ)) + .then(opCode(ALOAD)) + .then(opCode(ALOAD)) + .then(addSuppressedMethodCall()); + } - private static final SequenceMatcher TRY_WITH_RESOURCES = match(Match.never()) - .or(javac11()) - .or(javac8()) - .or(ecj()) - .or(ecjAddSuppressedCheck()) - .then(containMutation(FOUND)) - .compile(QueryParams.params(AbstractInsnNode.class) - .withIgnores(notAnInstruction()) - .withDebug(DEBUG) - ); + private static SequenceQuery ecjCloseSequence(boolean record) { + return match(opCode(ALOAD).and(recordPoint(START,record))) + .then(opCode(IFNULL)) // FIXME check jump target? + .then(opCode(ALOAD)) + .then(closeMethodCall()); + } + + private static SequenceQuery ecjCloseAndThrow() { + return match(opCode(ALOAD)) + .then(opCode(IFNULL)) // FIXME check jump target? + .then(opCode(ALOAD)) + .then(closeMethodCall()) + // omit label check ? + .then(opCode(ALOAD)) + .then(opCode(ATHROW)); + } + + private static SequenceQuery javacCloseSequence(boolean record) { + // javac may (or may not) generate a null check before the close + return methodSequence(record) + .or(fullSequence(record)) + .or(omittedNullCheckSequence(record)) + .or(optimalSequence(record)); + } + + private static SequenceQuery methodSequence(boolean record) { + return QueryStart.match(opCode(ALOAD).and(recordPoint(START, record))) + .then(opCode(IFNULL)) + .then(opCode(ALOAD)) + .then(opCode(ALOAD)) + .then(closeResourceMethodCall()); + } + + private static SequenceQuery fullSequence(boolean record) { + return QueryStart.match(opCode(ALOAD).and(recordPoint(START, record))) + .then(opCode(IFNULL)) + .then(omittedNullCheckSequence(false)); + } + + private static SequenceQuery omittedNullCheckSequence(boolean record) { + return QueryStart.match(opCode(ALOAD).and(recordPoint(START, record))) + .then(opCode(IFNULL)) + .then(opCode(ALOAD)) + .then(closeMethodCall()) + .then(opCode(GOTO).and(debug("goto"))) + .then(isLabel(HANDLERS.read()).and(debug("handler"))) + .then(opCode(ASTORE).and(debug("store"))) + .then(opCode(ALOAD)) + .then(opCode(ALOAD)) + .then(addSuppressedMethodCall()) + .then(opCode(GOTO)) + .then(opCode(ALOAD)) + .then(closeMethodCall().and(debug("end of sequence"))); + } + + private static SequenceQuery optimalSequence(boolean record) { + return QueryStart.match(opCode(ALOAD).and(recordPoint(START, record))) + .then(opCode(ALOAD)) + .then(closeResourceMethodCall()); + } + + private static SequenceQuery closeSequence(boolean record) { + // javac may (or may not) generate a null check before the close + return match(closeMethodCall().and(recordPoint(START, record))) + .or(match(opCode(IFNULL).and(recordPoint(START, record))) + .then(opCode(ALOAD)) + .then(closeMethodCall())); + } - private ClassTree currentClass; @Override public InterceptorType type() { @@ -142,6 +219,7 @@ public InterceptorType type() { @Override public void begin(ClassTree clazz) { this.currentClass = clazz; + this.cache = new IdentityHashMap<>(); } @Override @@ -161,56 +239,76 @@ private Predicate mutatesTryWithResourcesScaffolding() { return false; } - AbstractInsnNode mutatedInstruction = method.instruction(instruction); + List regions = cache.computeIfAbsent(method, this::computeRegions); + + return regions.stream() + .anyMatch(r -> instruction >= method.instructions().indexOf(r.start) && instruction <= method.instructions().indexOf(r.end)); - Context context = Context.start(method.instructions(), DEBUG); - context.store(MUTATED_INSTRUCTION.write(), mutatedInstruction); - return TRY_WITH_RESOURCES.matches(method.instructions(), context); }; } + private List computeRegions(MethodTree method) { + List handlers = method.rawNode().tryCatchBlocks.stream() + .filter(t -> "java/lang/Throwable".equals(t.type)) + .filter(t -> t.handler != null) + .map(t -> t.handler) + .collect(Collectors.toList()); + + + Context context = Context.start(DEBUG); + context = context.store(HANDLERS.write(), handlers); + List regions = TRY_WITH_RESOURCES.contextMatches(method.instructions(), context).stream() + .map(c -> new Region(c.retrieve(START.read()).get(), c.retrieve(END.read()).get())) + .collect(Collectors.toList()); + return regions; + } + + static class Region { + final AbstractInsnNode start; + final AbstractInsnNode end; + Region(AbstractInsnNode start, AbstractInsnNode end) { + this.start = start; + this.end = end; + } + + } + @Override public void end() { this.currentClass = null; + this.cache = null; } private static Match aLabel() { return isA(LabelNode.class); } - private static Match anALoad() { - return opCode(ALOAD).and(mutationPoint()); - } - private static Match aGoto() { - return opCode(GOTO).and(mutationPoint()); + private static Match isLabel(SlotRead> read) { + return aLabel().and((c,t) -> result(c.retrieve(read).get().contains(t), c)); } - private static Match addSuppressedCall() { - return methodCallNamed("addSuppressed").and(mutationPoint()); + private static Match closeMethodCall() { + return methodCallNamed("close") + .and(opCode(INVOKEINTERFACE).or(opCode(INVOKEVIRTUAL))) + .and(methodDescEquals("()V")); } - private static Match anAStore() { - return opCode(ASTORE).and(mutationPoint()); + private static Match closeResourceMethodCall() { + return methodCallNamed("$closeResource") + .and(methodDescEquals("(Ljava/lang/Throwable;Ljava/lang/AutoCloseable;)V")); } - private static Match closeCall() { - return methodCallNamed("close").and(mutationPoint()); + private static Match addSuppressedMethodCall() { + return methodCallNamed("addSuppressed").and(methodDescEquals("(Ljava/lang/Throwable;)V")); } - private static Match ifNonNull() { - return opCode(IFNONNULL).and(mutationPoint()); + private static Match recordPoint(Slot slot, boolean record) { + if (!record) { + return (c,t) -> result(true,c); + } + return writeNodeToSlot(slot.write(), AbstractInsnNode.class); } - private static Match ifNull() { - return opCode(IFNULL).and(mutationPoint()); - } - - private static Match mutationPoint() { - return recordTarget(MUTATED_INSTRUCTION.read(), FOUND.write()); - } +} - private static Match containMutation(final Slot found) { - return (c, t) -> c.retrieve(found.read()).isPresent(); - } -} diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/logging/LoggingCallsFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/logging/LoggingCallsFilter.java index dba30164b..06480195e 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/logging/LoggingCallsFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/logging/LoggingCallsFilter.java @@ -5,6 +5,7 @@ import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.Collectors; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; @@ -12,7 +13,6 @@ import org.pitest.bytecode.analysis.ClassTree; import org.pitest.bytecode.analysis.MethodTree; import org.pitest.functional.FCollection; -import org.pitest.functional.prelude.Prelude; import org.pitest.mutationtest.build.InterceptorType; import org.pitest.mutationtest.build.MutationInterceptor; import org.pitest.mutationtest.engine.Mutater; @@ -47,7 +47,9 @@ private void findLoggingLines(MethodTree each, Set lines) { @Override public Collection intercept( Collection mutations, Mutater m) { - return FCollection.filter(mutations, Prelude.not(isOnLoggingLine())); + return mutations.stream() + .filter(isOnLoggingLine().negate()) + .collect(Collectors.toList()); } private Predicate isOnLoggingLine() { @@ -80,7 +82,7 @@ class LoggingLineScanner extends MethodVisitor { @Override public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc, boolean itf) { - if (FCollection.contains(this.loggingClasses, owner::startsWith)) { + if (this.loggingClasses.stream().anyMatch(owner::startsWith)) { this.lines.add(this.currentLineNumber); } } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterFilter.java index a50260bd2..f808a276d 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterFilter.java @@ -3,6 +3,7 @@ import static org.pitest.bytecode.analysis.InstructionMatchers.aConditionalJump; import static org.pitest.bytecode.analysis.InstructionMatchers.aConditionalJumpTo; import static org.pitest.bytecode.analysis.InstructionMatchers.aLabelNode; +import static org.pitest.bytecode.analysis.InstructionMatchers.anILoad; import static org.pitest.bytecode.analysis.InstructionMatchers.anILoadOf; import static org.pitest.bytecode.analysis.InstructionMatchers.anIStore; import static org.pitest.bytecode.analysis.InstructionMatchers.anIntegerConstant; @@ -11,23 +12,27 @@ import static org.pitest.bytecode.analysis.InstructionMatchers.gotoLabel; import static org.pitest.bytecode.analysis.InstructionMatchers.incrementsVariable; import static org.pitest.bytecode.analysis.InstructionMatchers.isA; -import static org.pitest.bytecode.analysis.InstructionMatchers.isInstruction; import static org.pitest.bytecode.analysis.InstructionMatchers.jumpsTo; import static org.pitest.bytecode.analysis.InstructionMatchers.labelNode; import static org.pitest.bytecode.analysis.InstructionMatchers.notAnInstruction; import static org.pitest.bytecode.analysis.InstructionMatchers.opCode; +import static org.pitest.sequence.Result.result; import java.util.Collection; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.function.Predicate; +import java.util.stream.Collectors; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.IincInsnNode; import org.objectweb.asm.tree.LabelNode; import org.objectweb.asm.tree.MethodInsnNode; import org.pitest.bytecode.analysis.ClassTree; import org.pitest.bytecode.analysis.MethodTree; -import org.pitest.functional.FCollection; -import org.pitest.functional.prelude.Prelude; import org.pitest.mutationtest.build.InterceptorType; import org.pitest.mutationtest.build.MutationInterceptor; import org.pitest.mutationtest.engine.Mutater; @@ -39,6 +44,7 @@ import org.pitest.sequence.SequenceMatcher; import org.pitest.sequence.SequenceQuery; import org.pitest.sequence.Slot; +import org.pitest.sequence.SlotWrite; /** * Removes mutants that affect for loop counters as these have @@ -55,9 +61,7 @@ public class AvoidForLoopCounterFilter implements MutationInterceptor { private static final Slot MUTATED_INSTRUCTION = Slot.create(AbstractInsnNode.class); - static final SequenceMatcher MUTATED_FOR_COUNTER = QueryStart - .match(Match.never()) - .or(conditionalAtEnd()) + static final SequenceMatcher MUTATED_FOR_COUNTER = conditionalAtEnd() .or(conditionalAtStart()) .compile(QueryParams.params(AbstractInsnNode.class) .withIgnores(IGNORE) @@ -66,6 +70,7 @@ public class AvoidForLoopCounterFilter implements MutationInterceptor { private ClassTree currentClass; + private Map> cache; private static SequenceQuery conditionalAtEnd() { @@ -75,8 +80,8 @@ private static SequenceQuery conditionalAtEnd() { return QueryStart .any(AbstractInsnNode.class) .then(anIStore(counterVariable.write()).and(debug("end_counter"))) - .then(isA(LabelNode.class)) - .then(gotoLabel(loopEnd.write())) + .then(isA(LabelNode.class).and(debug("label 1"))) + .then(gotoLabel(loopEnd.write()).and(debug("goto"))) .then(aLabelNode(loopStart.write()).and(debug("loop start"))) .zeroOrMore(anything()) .then(targetInstruction(counterVariable).and(debug("target"))) @@ -94,19 +99,17 @@ private static SequenceQuery conditionalAtStart() { final Slot loopStart = Slot.create(LabelNode.class); final Slot loopEnd = Slot.create(LabelNode.class); return QueryStart - .any(AbstractInsnNode.class) - .then(anIStore(counterVariable.write()).and(debug("store"))) - .then(aLabelNode(loopStart.write()).and(debug("label"))) - .then(anILoadOf(counterVariable.read()).and(debug("load"))) - .zeroOrMore(QueryStart.match(opCode(Opcodes.ALOAD))) // optionally put object on stack - .then(loadsAnIntegerToCompareTo().and(debug("push"))) - .then(jumpsTo(loopEnd.write()).and(aConditionalJump())) - .then(isA(LabelNode.class)) - .zeroOrMore(anything()) - .then(targetInstruction(counterVariable).and(debug("target"))) - .then(jumpsTo(loopStart.read()).and(debug("jump"))) - .then(labelNode(loopEnd.read())) - .zeroOrMore(anything()); + .any(AbstractInsnNode.class) + .then(aLabelNode(loopStart.write()).and(debug("conditional at start label"))) + .then(anILoad(counterVariable.write()).and(debug("iload"))) + .zeroOrMore(anything()) + .then(jumpsTo(loopEnd.write()).and(aConditionalJump()).and(debug("jump"))) + .then(isA(LabelNode.class)) + .zeroOrMore(anything()) + .then(targetInstruction(counterVariable).and(debug("target"))) + .then(jumpsTo(loopStart.read()).and(debug("jump"))) + .then(labelNode(loopEnd.read())) + .zeroOrMore(anything()); } private static Match loadsAnIntegerToCompareTo() { @@ -131,7 +134,12 @@ private static Match integerMethodCall() { } private static Match targetInstruction(Slot counterVariable) { - return incrementsVariable(counterVariable.read()).and(isInstruction(MUTATED_INSTRUCTION.read())); + return incrementsVariable(counterVariable.read()) + .and(recordInstruction(MUTATED_INSTRUCTION.write())); + } + + private static Match recordInstruction(SlotWrite slot) { + return (c,t) -> result(true, c.store(slot, t)); } @Override @@ -142,12 +150,15 @@ public InterceptorType type() { @Override public void begin(ClassTree clazz) { this.currentClass = clazz; + this.cache = new IdentityHashMap<>(); } @Override public Collection intercept( Collection mutations, Mutater m) { - return FCollection.filter(mutations, Prelude.not(mutatesAForLoopCounter())); + return mutations.stream() + .filter(mutatesAForLoopCounter().negate()) + .collect(Collectors.toList()); } private Predicate mutatesAForLoopCounter() { @@ -156,15 +167,30 @@ private Predicate mutatesAForLoopCounter() { final MethodTree method = AvoidForLoopCounterFilter.this.currentClass.method(a.getId().getLocation()).get(); final AbstractInsnNode mutatedInstruction = method.instruction(instruction); - final Context context = Context.start(method.instructions(), DEBUG); - context.store(MUTATED_INSTRUCTION.write(), mutatedInstruction); - return MUTATED_FOR_COUNTER.matches(method.instructions(), context); + // performance hack + if (!(mutatedInstruction instanceof IincInsnNode)) { + return false; + } + + Set loopIncrements = cache.computeIfAbsent(method, this::findLoopCounters); + + return loopIncrements.contains(mutatedInstruction); }; } + private Set findLoopCounters(MethodTree method) { + Context context = Context.start(DEBUG); + return MUTATED_FOR_COUNTER.contextMatches(method.instructions(), context).stream() + .map(c -> c.retrieve(MUTATED_INSTRUCTION.read())) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toSet()); + } + @Override public void end() { this.currentClass = null; + this.cache = null; } } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/InfiniteForLoopFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/InfiniteForLoopFilter.java index 0d0172bb9..ff39e6d90 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/InfiniteForLoopFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/InfiniteForLoopFilter.java @@ -22,7 +22,6 @@ import org.objectweb.asm.tree.LabelNode; import org.pitest.bytecode.analysis.MethodTree; import org.pitest.mutationtest.engine.MutationDetails; -import org.pitest.sequence.Match; import org.pitest.sequence.QueryParams; import org.pitest.sequence.QueryStart; import org.pitest.sequence.SequenceMatcher; @@ -37,9 +36,7 @@ public class InfiniteForLoopFilter extends InfiniteLoopFilter { private static final boolean DEBUG = false; - static final SequenceMatcher INFINITE_LOOP = QueryStart - .match(Match.never()) - .or(countingLoopWithoutWriteConditionalAtStart()) + static final SequenceMatcher INFINITE_LOOP = countingLoopWithoutWriteConditionalAtStart() .or(countingLoopWithoutWriteConditionAtEnd()) .compile(QueryParams.params(AbstractInsnNode.class) .withIgnores(notAnInstruction()) diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/InfiniteIteratorLoopFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/InfiniteIteratorLoopFilter.java index 34b7d4035..7f43a6e09 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/InfiniteIteratorLoopFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/InfiniteIteratorLoopFilter.java @@ -18,7 +18,6 @@ import org.pitest.bytecode.analysis.MethodTree; import org.pitest.classinfo.ClassName; import org.pitest.mutationtest.engine.MutationDetails; -import org.pitest.sequence.Match; import org.pitest.sequence.QueryParams; import org.pitest.sequence.QueryStart; import org.pitest.sequence.SequenceMatcher; @@ -32,9 +31,7 @@ public class InfiniteIteratorLoopFilter extends InfiniteLoopFilter { private static final boolean DEBUG = false; - static final SequenceMatcher INFINITE_LOOP = QueryStart - .match(Match.never()) - .or(inifniteIteratorLoop()) + static final SequenceMatcher INFINITE_LOOP = inifniteIteratorLoop() .or(infiniteIteratorLoopJavac()) .compile(QueryParams.params(AbstractInsnNode.class) .withIgnores(notAnInstruction()) @@ -57,7 +54,7 @@ private static SequenceQuery doesNotBreakIteratorLoop() { } private boolean isIteratorNext(AbstractInsnNode instruction) { - return InstructionMatchers.methodCallTo(ClassName.fromClass(Iterator.class), "next").test(null, instruction); + return InstructionMatchers.methodCallTo(ClassName.fromClass(Iterator.class), "next").test(null, instruction).result(); } private static SequenceQuery inifniteIteratorLoop() { diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/tooling/MutationCoverage.java b/pitest-entry/src/main/java/org/pitest/mutationtest/tooling/MutationCoverage.java index 38ae304b6..4312a78fa 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/tooling/MutationCoverage.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/tooling/MutationCoverage.java @@ -36,7 +36,6 @@ import org.pitest.mutationtest.MutationAnalyser; import org.pitest.mutationtest.MutationConfig; import org.pitest.mutationtest.MutationResultListener; -import org.pitest.mutationtest.build.InterceptorType; import org.pitest.mutationtest.build.MutationAnalysisUnit; import org.pitest.mutationtest.build.MutationGrouper; import org.pitest.mutationtest.build.MutationInterceptor; @@ -163,7 +162,7 @@ private CombinedStatistics runAnalysis(Runtime runtime, long t0, EngineArguments engine, args, allInterceptors()); this.timings.registerEnd(Timings.Stage.BUILD_MUTATION_TESTS); - LOG.info("Created " + tus.size() + " mutation test units"); + LOG.info("Created " + tus.size() + " mutation test units" ); recordClassPath(history, coverageData); @@ -202,13 +201,13 @@ private List findMutations(MutationEngine engine, EngineAr // an initial run here we are able to skip coverage generation when no mutants // are found, e.g if pitest is being run against diffs. this.timings.registerStart(Timings.Stage.MUTATION_PRE_SCAN); - List mutants = buildMutationTests(new NoCoverage(), new NullHistoryStore(), engine, args, noReports()); + List mutants = buildMutationTests(new NoCoverage(), new NullHistoryStore(), engine, args, noReportsOrFilters()); this.timings.registerEnd(Timings.Stage.MUTATION_PRE_SCAN); return mutants; } - private Predicate noReports() { - return i -> !i.type().equals(InterceptorType.REPORT); + private Predicate noReportsOrFilters() { + return i -> i.type().includeInPrescan(); } diff --git a/pitest-entry/src/main/java/org/pitest/sequence/Context.java b/pitest-entry/src/main/java/org/pitest/sequence/Context.java new file mode 100644 index 000000000..f4a2efd4b --- /dev/null +++ b/pitest-entry/src/main/java/org/pitest/sequence/Context.java @@ -0,0 +1,32 @@ +package org.pitest.sequence; + +import java.util.Optional; + +public interface Context { + + static Context start() { + return start(false); + } + + static Context start(boolean debug) { + if (debug) { + return EmptyContext.WITH_DEBUG; + } + return EmptyContext.WITHOUT_DEBUG; + } + + Context store(SlotWrite slot, S value); + + @SuppressWarnings("unchecked") + Optional retrieve(SlotRead slot); + + default boolean debug() { + return false; + } + + default void debug(String msg, T t) { + if (debug()) { + System.out.println(msg + " for " + t); + } + } +} diff --git a/pitest-entry/src/main/java/org/pitest/sequence/Context1.java b/pitest-entry/src/main/java/org/pitest/sequence/Context1.java new file mode 100644 index 000000000..bc523131c --- /dev/null +++ b/pitest-entry/src/main/java/org/pitest/sequence/Context1.java @@ -0,0 +1,60 @@ +package org.pitest.sequence; + +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +/** + * Specialisation of context for single values + */ +final class Context1 implements Context { + private final boolean debug; + private final Slot slot; + private final Object value; + + Context1(Slot slot, Object value, boolean debug) { + this.slot = slot; + this.value = value; + this.debug = debug; + } + + @Override + public boolean debug() { + return debug; + } + + @Override + public Context store(SlotWrite slot, S value) { + Map mutatedSlots = new IdentityHashMap<>(); + mutatedSlots.put(this.slot, this.value); + mutatedSlots.put(slot.slot(), value); + return new MultiContext(mutatedSlots, debug); + } + + @SuppressWarnings("unchecked") + @Override + public Optional retrieve(SlotRead read) { + if (read.slot().equals(slot)) { + return (Optional) Optional.ofNullable(value); + } + return Optional.empty(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Context1)) { + return false; + } + Context1 context1 = (Context1) o; + return slot.equals(context1.slot) && Objects.equals(value, context1.value); + } + + @Override + public int hashCode() { + return Objects.hash(slot, value); + } +} diff --git a/pitest-entry/src/main/java/org/pitest/sequence/EmptyContext.java b/pitest-entry/src/main/java/org/pitest/sequence/EmptyContext.java new file mode 100644 index 000000000..9b04564e9 --- /dev/null +++ b/pitest-entry/src/main/java/org/pitest/sequence/EmptyContext.java @@ -0,0 +1,35 @@ +package org.pitest.sequence; + +import java.util.Optional; + +/** + * Specialisation of context with no data + */ +enum EmptyContext implements Context { + + WITHOUT_DEBUG(false), + WITH_DEBUG(true); + + private final boolean debug; + + EmptyContext(boolean debug) { + this.debug = debug; + } + + @Override + public boolean debug() { + return debug; + } + + @Override + public Context store(SlotWrite slot, S value) { + return new Context1(slot.slot(), value, debug); + } + + @SuppressWarnings("unchecked") + @Override + public Optional retrieve(SlotRead slot) { + return Optional.empty(); + } + +} diff --git a/pitest-entry/src/main/java/org/pitest/sequence/Match.java b/pitest-entry/src/main/java/org/pitest/sequence/Match.java new file mode 100644 index 000000000..514d6f57b --- /dev/null +++ b/pitest-entry/src/main/java/org/pitest/sequence/Match.java @@ -0,0 +1,59 @@ +package org.pitest.sequence; + +import static org.pitest.sequence.Result.result; + +/** + * Predicate with additional context. + * + * Implemented as abstract class as we're still on Java + * 6 and don't have default methods + * + * @param Type to match + */ +@FunctionalInterface +public interface Match { + + Result test(Context c, T t); + + static Match always() { + return (c, t) -> result(true, c); + } + + static Match never() { + return (c, t) -> result(false, c); + } + + static Match isEqual(final Object targetRef) { + return (c, t) -> result(targetRef.equals(t), c); + } + + default Match and(final Match other) { + return (c, t) -> { + Result r = this.test(c,t); + if (!r.result()) { + return r; + } + return other.test(r.context(), t); + }; + } + + default Match negate() { + return (c, t) -> { + Result r = this.test(c,t); + if (!r.result()) { + return result(true, r.context()); + } + return result(false, c); + }; + } + + default Match or(final Match other) { + return (c, t) -> { + Result r = this.test(c,t); + if (r.result()) { + return r; + } + return other.test(c,t); + }; + } +} diff --git a/pitest-entry/src/main/java/org/pitest/sequence/MultiContext.java b/pitest-entry/src/main/java/org/pitest/sequence/MultiContext.java new file mode 100644 index 000000000..ea2cd4a99 --- /dev/null +++ b/pitest-entry/src/main/java/org/pitest/sequence/MultiContext.java @@ -0,0 +1,55 @@ +package org.pitest.sequence; + +import java.util.IdentityHashMap; + +import java.util.Map; +import java.util.Optional; + +/** + * Specialisation of context for unlimited values + */ +final class MultiContext implements Context { + + private final boolean debug; + private final Map slots; + + MultiContext(Map slots, boolean debug) { + this.slots = slots; + this.debug = debug; + } + + @Override + public boolean debug() { + return debug; + } + + @Override + public Context store(SlotWrite slot, S value) { + Map mutatedSlots = new IdentityHashMap<>(slots); + mutatedSlots.put(slot.slot(), value); + return new MultiContext(mutatedSlots, debug); + } + + @SuppressWarnings("unchecked") + @Override + public Optional retrieve(SlotRead slot) { + return Optional.ofNullable((S)slots.get(slot.slot())); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof MultiContext)) { + return false; + } + MultiContext context = (MultiContext) o; + return slots.equals(context.slots); + } + + @Override + public int hashCode() { + return slots.hashCode(); + } +} diff --git a/pitest/src/main/java/org/pitest/sequence/QueryParams.java b/pitest-entry/src/main/java/org/pitest/sequence/QueryParams.java similarity index 100% rename from pitest/src/main/java/org/pitest/sequence/QueryParams.java rename to pitest-entry/src/main/java/org/pitest/sequence/QueryParams.java diff --git a/pitest/src/main/java/org/pitest/sequence/QueryStart.java b/pitest-entry/src/main/java/org/pitest/sequence/QueryStart.java similarity index 100% rename from pitest/src/main/java/org/pitest/sequence/QueryStart.java rename to pitest-entry/src/main/java/org/pitest/sequence/QueryStart.java diff --git a/pitest-entry/src/main/java/org/pitest/sequence/Result.java b/pitest-entry/src/main/java/org/pitest/sequence/Result.java new file mode 100644 index 000000000..2af85d0d6 --- /dev/null +++ b/pitest-entry/src/main/java/org/pitest/sequence/Result.java @@ -0,0 +1,24 @@ +package org.pitest.sequence; + +public class Result { + private final boolean result; + private final Context context; + + Result(boolean result, Context context) { + this.result = result; + this.context = context; + } + + public static Result result(boolean result, Context context) { + return new Result(result, context); + } + + public boolean result() { + return result; + } + + public Context context() { + return context; + } + +} diff --git a/pitest-entry/src/main/java/org/pitest/sequence/SequenceMatcher.java b/pitest-entry/src/main/java/org/pitest/sequence/SequenceMatcher.java new file mode 100644 index 000000000..4c976d442 --- /dev/null +++ b/pitest-entry/src/main/java/org/pitest/sequence/SequenceMatcher.java @@ -0,0 +1,12 @@ +package org.pitest.sequence; + +import java.util.List; + +public interface SequenceMatcher { + + boolean matches(List sequence); + + boolean matches(List sequence, Context initialContext); + + List contextMatches(List sequence, Context initialContext); +} diff --git a/pitest/src/main/java/org/pitest/sequence/SequenceQuery.java b/pitest-entry/src/main/java/org/pitest/sequence/SequenceQuery.java similarity index 66% rename from pitest/src/main/java/org/pitest/sequence/SequenceQuery.java rename to pitest-entry/src/main/java/org/pitest/sequence/SequenceQuery.java index 9372a7d6c..06271e90f 100644 --- a/pitest/src/main/java/org/pitest/sequence/SequenceQuery.java +++ b/pitest-entry/src/main/java/org/pitest/sequence/SequenceQuery.java @@ -3,6 +3,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; public class SequenceQuery { @@ -151,60 +152,80 @@ class NFASequenceMatcher implements SequenceMatcher { @Override public boolean matches(List sequence) { - return matches(sequence, Context.start(sequence, this.debug)); + return matches(sequence, Context.start(this.debug)); } @Override - public boolean matches(List sequence, Context context) { - Set> currentState = new HashSet<>(); - addstate(currentState, this.start); + public boolean matches(List sequence, Context initialContext) { + Set> currentState = run(sequence, initialContext); + return currentState.stream() + .map(c -> c.state) + .anyMatch(s -> s != null && s == EndMatch.MATCH); + } - for (final T t : sequence) { - context.moveForward(); + @Override + public List contextMatches(List sequence, Context initialContext) { + Set> currentState = run(sequence, initialContext); + return currentState.stream() + .filter(s -> s.state != null && s.state == EndMatch.MATCH) + .map(c -> c.context) + .collect(Collectors.toList()); + } - if (this.ignore.test(context, t)) { + private Set> run(List sequence, Context initialContext) { + Set> currentState = new HashSet<>(); + addState(currentState, new StateContext<>(this.start, initialContext)); + + for (final T t : sequence) { + // only initial context used in ignore checks + if (this.ignore.test(initialContext, t).result()) { continue; } - final Set> nextStates = step(context, currentState, t); + final Set> nextStates = step(currentState, t); currentState = nextStates; + } - return isMatch(currentState); + return currentState; } - private static void addstate(Set> set, State state) { - if (state == null) { - return; - } - if (state instanceof Split) { - final Split split = (Split) state; - addstate(set, split.out1); - addstate(set, split.out2); + private static void addState(Set> set, StateContext state) { + + if (state.state instanceof Split) { + final Split split = (Split) state.state; + if (split.out1 != null) { + addState(set, new StateContext(split.out1, state.context)); + } + if (split.out2 != null) { + addState(set, new StateContext(split.out2, state.context)); + } } else { set.add(state); } } - private static Set> step(Context context, Set> currentState, T c) { + private static Set> step(Set> currentState, T c) { + + // adhoc testing suggests setting the initial HashSet size saves 15% of analysis + // execution time + final Set> nextStates = new HashSet<>(currentState.size()); - final Set> nextStates = new HashSet<>(); - for (final State each : currentState) { - if (each instanceof Consume) { - final Consume consume = (Consume) each; - if (consume.c.test(context, c)) { - addstate(nextStates, consume.out); + for (final StateContext each : currentState) { + if (each.state instanceof Consume) { + final Consume consume = (Consume) each.state; + + final Result result = consume.c.test(each.context, c); + if (result.result()) { + // note, context updated here + addState(nextStates, new StateContext<>(consume.out, result.context())); } } } return nextStates; } - private static boolean isMatch(Set> currentState) { - return currentState.contains(EndMatch.MATCH); - } - } interface State { @@ -212,17 +233,14 @@ interface State { } class Consume implements State { - final Match< T> c; - final State out; + final Match c; + final State out; Consume(Match c, State out) { this.c = c; this.out = out; } - boolean matches(Context context, T t) { - return this.c.test(context, t); - } } class Split implements State { diff --git a/pitest-entry/src/main/java/org/pitest/sequence/Slot.java b/pitest-entry/src/main/java/org/pitest/sequence/Slot.java new file mode 100644 index 000000000..06da33173 --- /dev/null +++ b/pitest-entry/src/main/java/org/pitest/sequence/Slot.java @@ -0,0 +1,21 @@ +package org.pitest.sequence; + +import java.util.List; + +public final class Slot { + public static Slot create(Class clazz) { + return new Slot<>(); + } + + public static Slot> createList(Class clazz) { + return new Slot<>(); + } + + public SlotWrite write() { + return new SlotWrite<>(this); + } + + public SlotRead read() { + return new SlotRead<>(this); + } +} diff --git a/pitest/src/main/java/org/pitest/sequence/SlotRead.java b/pitest-entry/src/main/java/org/pitest/sequence/SlotRead.java similarity index 100% rename from pitest/src/main/java/org/pitest/sequence/SlotRead.java rename to pitest-entry/src/main/java/org/pitest/sequence/SlotRead.java diff --git a/pitest/src/main/java/org/pitest/sequence/SlotWrite.java b/pitest-entry/src/main/java/org/pitest/sequence/SlotWrite.java similarity index 100% rename from pitest/src/main/java/org/pitest/sequence/SlotWrite.java rename to pitest-entry/src/main/java/org/pitest/sequence/SlotWrite.java diff --git a/pitest-entry/src/main/java/org/pitest/sequence/StateContext.java b/pitest-entry/src/main/java/org/pitest/sequence/StateContext.java new file mode 100644 index 000000000..d5c06c12f --- /dev/null +++ b/pitest-entry/src/main/java/org/pitest/sequence/StateContext.java @@ -0,0 +1,33 @@ +package org.pitest.sequence; + +import java.util.Objects; + +/** + * Pair class to hold state and context. + */ +final class StateContext { + + StateContext(State state, Context context) { + this.state = state; + this.context = context; + } + final State state; + final Context context; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof StateContext)) { + return false; + } + StateContext that = (StateContext) o; + return state.equals(that.state) && context.equals(that.context); + } + + @Override + public int hashCode() { + return Objects.hash(state, context); + } +} \ No newline at end of file diff --git a/pitest-entry/src/test/java/com/example/trywithresources/LargeTryWithResources.java b/pitest-entry/src/test/java/com/example/trywithresources/LargeTryWithResources.java new file mode 100644 index 000000000..4b3c9149f --- /dev/null +++ b/pitest-entry/src/test/java/com/example/trywithresources/LargeTryWithResources.java @@ -0,0 +1,23 @@ +package com.example.trywithresources; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class LargeTryWithResources { + public static void main(String[] args) { + try (ByteArrayOutputStream baos1 = new ByteArrayOutputStream(); + ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); + ByteArrayOutputStream baos3 = new ByteArrayOutputStream(); + ByteArrayOutputStream baos4 = new ByteArrayOutputStream() + ) { + baos1.flush(); + baos2.flush(); + baos3.flush(); + baos4.flush(); + System.out.println("bo!"); + } catch (IOException e) { + e.printStackTrace(); + } + + } +} diff --git a/pitest-entry/src/test/java/org/pitest/bytecode/analysis/InstructionMatchersTest.java b/pitest-entry/src/test/java/org/pitest/bytecode/analysis/InstructionMatchersTest.java index c615117ef..de12c91b6 100644 --- a/pitest-entry/src/test/java/org/pitest/bytecode/analysis/InstructionMatchersTest.java +++ b/pitest-entry/src/test/java/org/pitest/bytecode/analysis/InstructionMatchersTest.java @@ -30,38 +30,39 @@ import org.pitest.classinfo.ClassName; import java.util.Optional; import org.pitest.sequence.Context; +import org.pitest.sequence.Result; import org.pitest.sequence.Slot; public class InstructionMatchersTest { - private final Context context = Context.start(Collections.emptyList()); + private Context context = Context.start(); @Test public void anyInstructionShouldMatchAnything() { final AbstractInsnNode node = new InsnNode(-1); - assertTrue(anyInstruction().test(this.context, node)); + assertTrue(anyInstruction().test(this.context, node).result()); } @Test public void opCodeShouldMatchOnOpcode() { final AbstractInsnNode node = new InsnNode(-1); - assertTrue(opCode(-1).test(this.context, node)); - assertFalse(opCode(0).test(this.context, node)); + assertTrue(opCode(-1).test(this.context, node).result()); + assertFalse(opCode(0).test(this.context, node).result()); } @Test public void isAShouldMatchOnType() { final AbstractInsnNode node = new InsnNode(-1); - assertTrue(isA(InsnNode.class).test(this.context, node)); - assertFalse(isA(LabelNode.class).test(this.context, node)); + assertTrue(isA(InsnNode.class).test(this.context, node).result()); + assertFalse(isA(LabelNode.class).test(this.context, node).result()); } @Test public void shouldMatchIncrementsToStoredLocalVariable() { final Slot slot = Slot.create(Integer.class); - this.context.store(slot.write(), 42); + context = context.store(slot.write(), 42); final IincInsnNode node = new IincInsnNode(42, 1); - assertTrue(incrementsVariable(slot.read()).test(this.context,node)); + assertTrue(incrementsVariable(slot.read()).test(context,node).result()); } @Test @@ -69,96 +70,99 @@ public void shouldNotMatchIncrementsToDifferentLocalVariable() { final Slot slot = Slot.create(Integer.class); this.context.store(slot.write(), 42); final IincInsnNode node = new IincInsnNode(42 + 1, 1); - assertFalse(incrementsVariable(slot.read()).test(this.context,node)); + assertFalse(incrementsVariable(slot.read()).test(this.context,node).result()); } @Test public void shouldCaptureIStoreVariable() { final Slot slot = Slot.create(Integer.class); final VarInsnNode node = new VarInsnNode(Opcodes.ISTORE, 3); - assertTrue(anIStore(slot.write()).test(this.context,node)); - assertThat(this.context.retrieve(slot.read())).isEqualTo(Optional.ofNullable(3)); + Result result = anIStore(slot.write()).test(this.context,node); + assertTrue(result.result()); + assertThat(result.context().retrieve(slot.read())).isEqualTo(Optional.ofNullable(3)); } @Test public void shouldMatchAgainstCapturedIStoreVariable() { final Slot slot = Slot.create(Integer.class); - this.context.store(slot.write(), 3); + context = context.store(slot.write(), 3); final VarInsnNode matchingNode = new VarInsnNode(Opcodes.ISTORE, 3); - assertTrue(anIStoreTo(slot.read()).test(this.context,matchingNode)); + assertTrue(anIStoreTo(slot.read()).test(context,matchingNode).result()); final VarInsnNode nonMatchingNode = new VarInsnNode(Opcodes.ISTORE, 4); - assertFalse(anIStoreTo(slot.read()).test(this.context,nonMatchingNode)); + assertFalse(anIStoreTo(slot.read()).test(context,nonMatchingNode).result()); } @Test public void shouldMatchAgainstCapturedILoadVariable() { final Slot slot = Slot.create(Integer.class); - this.context.store(slot.write(), 3); + this.context = this.context.store(slot.write(), 3); final VarInsnNode matchingNode = new VarInsnNode(Opcodes.ILOAD, 3); - assertTrue(anILoadOf(slot.read()).test(this.context,matchingNode)); + assertTrue(anILoadOf(slot.read()).test(this.context,matchingNode).result()); final VarInsnNode nonMatchingNode = new VarInsnNode(Opcodes.ILOAD, 4); - assertFalse(anILoadOf(slot.read()).test(this.context,nonMatchingNode)); + assertFalse(anILoadOf(slot.read()).test(this.context,nonMatchingNode).result()); } @Test public void shouldMatchAllIntegerConstants() { - assertFalse(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ACONST_NULL)))); - assertTrue(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ICONST_M1)))); - assertTrue(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ICONST_0)))); - assertTrue(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ICONST_1)))); - assertTrue(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ICONST_2)))); - assertTrue(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ICONST_3)))); - assertTrue(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ICONST_4)))); - assertTrue(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ICONST_5)))); + assertFalse(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ACONST_NULL))).result()); + assertTrue(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ICONST_M1))).result()); + assertTrue(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ICONST_0))).result()); + assertTrue(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ICONST_1))).result()); + assertTrue(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ICONST_2))).result()); + assertTrue(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ICONST_3))).result()); + assertTrue(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ICONST_4))).result()); + assertTrue(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ICONST_5))).result()); } @Test public void shouldCaptureLabels() { final Slot slot = Slot.create(LabelNode.class); final LabelNode label = new LabelNode(); - assertFalse(aLabelNode(slot.write()).test(this.context,new InsnNode(Opcodes.NULL))); - assertTrue(aLabelNode(slot.write()).test(this.context,label)); - assertThat(this.context.retrieve(slot.read())).isEqualTo(Optional.ofNullable(label)); + assertFalse(aLabelNode(slot.write()).test(this.context,new InsnNode(Opcodes.NULL)).result()); + + Result result = aLabelNode(slot.write()).test(this.context,label); + assertTrue(result.result()); + assertThat(result.context().retrieve(slot.read())).isEqualTo(Optional.ofNullable(label)); } @Test public void shouldMatchJumps() { - assertTrue(aJump().test(this.context,new JumpInsnNode(Opcodes.GOTO, null))); - assertFalse(aJump().test(this.context, new InsnNode(Opcodes.ACONST_NULL))); + assertTrue(aJump().test(this.context,new JumpInsnNode(Opcodes.GOTO, null)).result()); + assertFalse(aJump().test(this.context, new InsnNode(Opcodes.ACONST_NULL)).result()); } @Test public void shouldMatchConditionalJumps() { - assertFalse(aConditionalJump().test(this.context,new JumpInsnNode(Opcodes.GOTO, null))); - assertFalse(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.JSR, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFEQ, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFNE, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFLT, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFGE, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFGT, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFLE, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ICMPEQ, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ICMPNE, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ICMPLT, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ICMPGE, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ICMPGT, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ICMPLE, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ACMPEQ, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ACMPNE, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFNULL, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFNONNULL, null))); + assertFalse(aConditionalJump().test(this.context,new JumpInsnNode(Opcodes.GOTO, null)).result()); + assertFalse(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.JSR, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFEQ, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFNE, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFLT, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFGE, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFGT, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFLE, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ICMPEQ, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ICMPNE, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ICMPLT, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ICMPGE, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ICMPGT, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ICMPLE, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ACMPEQ, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ACMPNE, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFNULL, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFNONNULL, null)).result()); } @Test public void shouldMatchMethodCallByOwnerAndName() { final ClassName clazz = ClassName.fromString("clazz"); assertTrue(methodCallTo(clazz, "name") - .test(this.context, new MethodInsnNode(Opcodes.INVOKEINTERFACE, "clazz", "name", "desc", true))); + .test(this.context, new MethodInsnNode(Opcodes.INVOKEINTERFACE, "clazz", "name", "desc", true)).result()); assertFalse(methodCallTo(clazz, "name") - .test(this.context, new MethodInsnNode(Opcodes.INVOKEINTERFACE, "clazz", "notName", "desc", true))); + .test(this.context, new MethodInsnNode(Opcodes.INVOKEINTERFACE, "clazz", "notName", "desc", true)).result()); assertFalse(methodCallTo(clazz, "name") - .test(this.context, new MethodInsnNode(Opcodes.INVOKEINTERFACE, "notClazz", "name", "desc", true))); + .test(this.context, new MethodInsnNode(Opcodes.INVOKEINTERFACE, "notClazz", "name", "desc", true)).result()); } } 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 e7094ecb4..89d31e446 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 @@ -100,6 +100,17 @@ public void assertCombinedMutantExists(Predicate match, Class m.getId().getIndexes().size() > 1)); } + public void assertLeavesNMutants(int n, Class clazz) { + final Sample s = makeSampleForCurrentCompiler(clazz); + GregorMutater mutator = mutateFromClassLoader(); + final List mutations = mutator.findMutations(s.className); + final Collection actual = filter(s.clazz, mutations, mutator); + + assertThat(actual) + .describedAs("Wrong number of mutants with " + s.compiler + " for class \n" + s.clazz + " (started with " + mutations.size() + ")") + .hasSize(n); + } + public void assertLeavesNMutants(int n, String sample) { final GregorMutater mutator = mutateFromResourceDir(); atLeastOneSampleExists(sample); @@ -111,7 +122,7 @@ public void assertLeavesNMutants(int n, String sample) { final Collection actual = filter(s.clazz, mutations, mutator); softly.assertThat(actual) - .describedAs("Wrong number of mutants with " + s.compiler + " for class \n" + s.clazz) + .describedAs("Wrong number of mutants with " + s.compiler + " for class \n" + s.clazz + " (started with " + mutations.size() + ")") .hasSize(n); } diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/TryWithResourcesFilterTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/TryWithResourcesFilterTest.java index 3e61b9b1a..153788cfd 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/TryWithResourcesFilterTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/TryWithResourcesFilterTest.java @@ -2,7 +2,7 @@ import static org.junit.Assert.assertEquals; -import com.example.trywithresources.SimpleCloseCall; +import com.example.trywithresources.LargeTryWithResources; import org.junit.Test; import org.pitest.mutationtest.build.InterceptorType; import org.pitest.mutationtest.engine.gregor.config.Mutator; @@ -50,4 +50,14 @@ public void doesNotFilterProgrammerAddedCloseCalls() { this.verifier.assertLeavesNMutants(3, "SimpleCloseCall"); } + @Test + public void filtersMultiResourceTries() { + this.verifier.assertLeavesNMutants(6, "LargeTryWithResources"); + } + + @Test + public void filtersMultiResourceTriesCurrentCompiler() { + this.verifier.assertLeavesNMutants(6, LargeTryWithResources.class); + } + } diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterTest.java index 4e9e2c7fa..33d120aae 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterTest.java @@ -4,6 +4,7 @@ import java.util.List; +import org.junit.Ignore; import org.junit.Test; import org.pitest.mutationtest.build.InterceptorType; import org.pitest.mutationtest.build.intercept.javafeatures.FilterTester; @@ -69,6 +70,31 @@ public void shouldFilterLoopsWithSmallConstant() { this.verifier.assertFiltersNMutationFromClass(1, SmallConstantLoop.class); } + @Test + public void shouldFilterReverseLoops() { + this.verifier.assertFiltersNMutationFromClass(1, ReverseLoop.class); + } + + @Test + public void shouldFilterLoopsWithoutInitialiser() { + this.verifier.assertFiltersNMutationFromClass(1, ReverseNoInitialiseLoop.class); + } + + static class ReverseNoInitialiseLoop { + void foo(int i) { + for (; i > 0; i--) { + System.out.println("" + i); + } + } + } + + static class ReverseLoop { + void foo() { + for (int i = 9; i > 0; i--) { + System.out.println("" + i); + } + } + } static class LessThanLoop { void foo() { diff --git a/pitest-entry/src/test/java/org/pitest/sequence/Context1Test.java b/pitest-entry/src/test/java/org/pitest/sequence/Context1Test.java new file mode 100644 index 000000000..bdfc80d42 --- /dev/null +++ b/pitest-entry/src/test/java/org/pitest/sequence/Context1Test.java @@ -0,0 +1,15 @@ +package org.pitest.sequence; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.Test; + +public class Context1Test { + + @Test + public void obeysHashCodeEqualsContract() { + EqualsVerifier.forClass(Context1.class) + .withNonnullFields("slot") + .withIgnoredFields("debug") + .verify(); + } +} \ No newline at end of file diff --git a/pitest-entry/src/test/java/org/pitest/sequence/ContextTest.java b/pitest-entry/src/test/java/org/pitest/sequence/ContextTest.java new file mode 100644 index 000000000..b4c24b739 --- /dev/null +++ b/pitest-entry/src/test/java/org/pitest/sequence/ContextTest.java @@ -0,0 +1,76 @@ +package org.pitest.sequence; + +import org.junit.Test; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ContextTest { + + @Test + public void retrieveIsStartsEmpty() { + SlotRead slot = Slot.create(Integer.class).read(); + assertThat(Context.start().retrieve(slot)).isEmpty(); + } + + @Test + public void canStoreThenRetrieve() { + SlotRead slot = Slot.create(Integer.class).read(); + Context underTest = Context.start().store(slot.slot().write(), 42); + + Optional actual = underTest.retrieve(slot); + assertThat(actual).contains(42); + } + + @Test + public void canStoreAndRetrieveTwoValues() { + Slot slot1 = Slot.create(Integer.class); + Slot slot2 = Slot.create(Integer.class); + Context underTest = Context.start() + .store(slot1.write(), 42) + .store(slot2.write(), 101); + + assertThat(underTest.retrieve(slot1.read())).contains(42); + assertThat(underTest.retrieve(slot2.read())).contains(101); + } + + @Test + public void canStoreAndRetrieveThreeValues() { + Slot slot1 = Slot.create(Integer.class); + Slot slot2 = Slot.create(Integer.class); + Slot slot3 = Slot.create(Integer.class); + Context underTest = Context.start() + .store(slot1.write(), 42) + .store(slot2.write(), 101) + .store(slot3.write(), 8); + + assertThat(underTest.retrieve(slot1.read())).contains(42); + assertThat(underTest.retrieve(slot2.read())).contains(101); + assertThat(underTest.retrieve(slot3.read())).contains(8); + } + + @Test + public void canStoreAndRetrieveMultipleValues() { + Slot slot1 = Slot.create(Integer.class); + Slot slot2 = Slot.create(Integer.class); + Slot slot3 = Slot.create(Integer.class); + Slot slot4 = Slot.create(Integer.class); + Slot slot5 = Slot.create(Integer.class); + Context underTest = Context.start() + .store(slot1.write(), 1) + .store(slot2.write(), 2) + .store(slot3.write(), 3) + .store(slot4.write(), 4) + .store(slot5.write(), 5); + + + assertThat(underTest.retrieve(slot1.read())).contains(1); + assertThat(underTest.retrieve(slot2.read())).contains(2); + assertThat(underTest.retrieve(slot3.read())).contains(3); + assertThat(underTest.retrieve(slot4.read())).contains(4); + assertThat(underTest.retrieve(slot5.read())).contains(5); + } + + +} \ No newline at end of file diff --git a/pitest-entry/src/test/java/org/pitest/sequence/MatchTest.java b/pitest-entry/src/test/java/org/pitest/sequence/MatchTest.java new file mode 100644 index 000000000..7a8a53e1c --- /dev/null +++ b/pitest-entry/src/test/java/org/pitest/sequence/MatchTest.java @@ -0,0 +1,60 @@ +package org.pitest.sequence; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class MatchTest { + + private final Context unused = null; + + @Test + public void alwaysShouldAlwaysMatch() { + final Match testee = Match.always(); + assertTrue(testee.test(this.unused, 1).result()); + assertTrue(testee.test(this.unused, Integer.MAX_VALUE).result()); + } + + @Test + public void neverShouldNeverMatch() { + final Match testee = Match.never(); + assertFalse(testee.test(this.unused, 1).result()); + assertFalse(testee.test(this.unused, Integer.MAX_VALUE).result()); + } + + @Test + public void negateShouldInvertLogic() { + final Match testee = Match.never(); + assertTrue(testee.negate().test(this.unused, 1).result()); + assertFalse(testee.negate().negate().test(this.unused, Integer.MAX_VALUE).result()); + } + + @Test + public void isEqualShouldCheckEquality() { + final Match testee = Match.isEqual(1); + assertTrue(testee.test(this.unused, 1).result()); + assertFalse(testee.test(this.unused, 2).result()); + } + + @Test + public void andShouldLogicallyAnd() { + final Match isTrue = Match.always(); + final Match isFalse = Match.never(); + assertTrue(isTrue.and(isTrue).test(this.unused, 1).result()); + assertFalse(isTrue.and(isFalse).test(this.unused, 1).result()); + assertFalse(isFalse.and(isFalse).test(this.unused, 1).result()); + assertFalse(isFalse.and(isTrue).test(this.unused, 1).result()); + } + + @Test + public void orShouldLogicallyOr() { + final Match isTrue = Match.always(); + final Match isFalse = Match.never(); + assertTrue(isTrue.or(isTrue).test(this.unused, 1).result()); + assertTrue(isTrue.or(isFalse).test(this.unused, 1).result()); + assertFalse(isFalse.or(isFalse).test(this.unused, 1).result()); + assertTrue(isFalse.or(isTrue).test(this.unused, 1).result()); + } + +} diff --git a/pitest-entry/src/test/java/org/pitest/sequence/MultiContextTest.java b/pitest-entry/src/test/java/org/pitest/sequence/MultiContextTest.java new file mode 100644 index 000000000..a474bef31 --- /dev/null +++ b/pitest-entry/src/test/java/org/pitest/sequence/MultiContextTest.java @@ -0,0 +1,16 @@ +package org.pitest.sequence; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.Test; + +public class MultiContextTest { + + @Test + public void obeysHashCodeEqualsContract() { + EqualsVerifier.forClass(MultiContext.class) + .withNonnullFields("slots") + .withIgnoredFields("debug") + .verify(); + } + +} \ No newline at end of file diff --git a/pitest-entry/src/test/java/org/pitest/sequence/SequenceQueryTest.java b/pitest-entry/src/test/java/org/pitest/sequence/SequenceQueryTest.java new file mode 100644 index 000000000..cc596a062 --- /dev/null +++ b/pitest-entry/src/test/java/org/pitest/sequence/SequenceQueryTest.java @@ -0,0 +1,193 @@ +package org.pitest.sequence; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.pitest.sequence.QueryStart.match; +import static org.pitest.sequence.Result.result; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.Test; + +public class SequenceQueryTest { + + private static List asList(Integer... is) { + return Arrays.asList(is); + } + + @Test + public void shouldMatchSingleLiterals() { + final SequenceMatcher testee = match(eq(1)) + .compile(); + + assertTrue(testee.matches(asList(1))); + assertFalse(testee.matches(asList(2))); + } + + @Test + public void shouldMatchSimpleSequences() { + final SequenceMatcher testee = match(eq(1)) + .then(eq(2)) + .then(eq(3)) + .compile(); + + assertTrue(testee.matches(asList(1, 2, 3))); + assertFalse(testee.matches(asList(1, 2))); + assertFalse(testee.matches(asList(1, 2, 3, 4))); + } + + @Test + public void shouldMatchSimpleOrs() { + final SequenceQuery right = match(eq(2)); + + final SequenceMatcher testee = match(eq(1)) + .or(right) + .compile(); + + assertTrue(testee.matches(asList(1))); + assertTrue(testee.matches(asList(2))); + assertFalse(testee.matches(asList(3))); + } + + @Test + public void shouldMatchSimpleZeroOrMores() { + final SequenceQuery right = match(eq(2)); + + final SequenceMatcher testee = match(eq(1)) + .zeroOrMore(right) + .compile(); + + assertTrue(testee.matches(asList(1))); + assertTrue(testee.matches(asList(1, 2))); + assertTrue(testee.matches(asList(1, 2, 2, 2))); + assertFalse(testee.matches(asList(1, 2, 3))); + assertFalse(testee.matches(asList(1, 3))); + } + + @Test + public void shouldMatchSimpleOneOrMores() { + final SequenceQuery right = match(eq(2)); + + final SequenceMatcher testee = match(eq(1)) + .oneOrMore(right) + .compile(); + + assertFalse(testee.matches(asList(1))); + assertTrue(testee.matches(asList(1, 2))); + assertTrue(testee.matches(asList(1, 2, 2, 2))); + assertFalse(testee.matches(asList(1, 2, 3))); + assertFalse(testee.matches(asList(1, 3))); + } + + @Test + public void shouldMatchAnyOf() { + final SequenceQuery left = match(eq(2)); + + final SequenceQuery right = match(eq(3)); + + final SequenceMatcher testee = match(eq(1)) + .thenAnyOf(left, right) + .then(eq(99)) + .compile(); + + assertTrue(testee.matches(asList(1, 2, 99))); + assertTrue(testee.matches(asList(1, 3, 99))); + assertFalse(testee.matches(asList(1, 2))); + assertFalse(testee.matches(asList(1, 2, 3, 99))); + } + + @Test + public void shouldSkipItemsMatchingIgnoreList() { + final SequenceMatcher testee = match(eq(1)) + .then(eq(2)) + .compile(QueryParams.params(Integer.class).withIgnores(eq(99))); + + assertTrue(testee.matches(asList(1, 99, 2))); + } + + @Test + public void contextBranchesWithAnd() { + Slot slot1 = Slot.create(Integer.class); + Slot slot2 = Slot.create(Integer.class); + + List sequence = asList(1, 2, 2, 4); + + Context context = Context.start() + .store(slot1.write(), 2); + + final SequenceMatcher testee = match(eq(1)) + .then(matchesSlot(slot1.read()).and(write(slot2.write()))) + .then(matchesSlot(slot2.read())) + .then(eq(4)) + .compile(QueryParams.params(Integer.class)); + + assertTrue(testee.matches(sequence, context)); + } + + @Test + public void returnsSingleMatchingContexts() { + Slot slot1 = Slot.create(Integer.class); + + List sequence = asList(1, 2, 3); + + Context context = Context.start(); + + final SequenceMatcher testee = match(eq(1)) + .then(eq(2).and(write(slot1.write()))) + .then(eq(3)) + .compile(QueryParams.params(Integer.class)); + + List actual = testee.contextMatches(sequence, context); + assertThat(actual).hasSize(1); + assertThat(actual.get(0).retrieve(slot1.read())).contains(2); + } + + @Test + public void returnsMultipleMatchingContexts() { + Slot slot1 = Slot.create(Integer.class); + + List sequence = asList(1, 2, 3); + + Context context = Context.start(); + + SequenceQuery a = match(anyThing().and(write(slot1.write()))) + .zeroOrMore(match(anyThing())); + SequenceQuery b = match(eq(1)) + .then(anyThing().and(write(slot1.write()))) + .zeroOrMore(match(anyThing())); + + final SequenceMatcher testee = + a.or(b) + .compile(QueryParams.params(Integer.class)); + + List actual = testee.contextMatches(sequence, context) + .stream().map(c -> c.retrieve(slot1.read()).get()) + .collect(Collectors.toList()); + + assertThat(actual).hasSize(2); + assertThat(actual).containsExactlyInAnyOrder(1,2); + } + + private Match anyThing() { + return (c,i) -> result(true,c); + } + + private Match write(SlotWrite slot) { + return (c, i) -> result(true, c.store(slot, i)); + } + + private Match matchesSlot(SlotRead read) { + return (c, i) -> { + boolean b = c.retrieve(read).get().equals(i); + return result(b,c); + }; + } + + private Match eq(final int i) { + return Match.isEqual(i); + } + +} diff --git a/pitest-entry/src/test/java/org/pitest/sequence/StateContextTest.java b/pitest-entry/src/test/java/org/pitest/sequence/StateContextTest.java new file mode 100644 index 000000000..552eb9243 --- /dev/null +++ b/pitest-entry/src/test/java/org/pitest/sequence/StateContextTest.java @@ -0,0 +1,13 @@ +package org.pitest.sequence; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.Test; + +public class StateContextTest { + @Test + public void obeysHashCodeEqualsContract() { + EqualsVerifier.forClass(StateContext.class) + .withNonnullFields("state", "context") + .verify(); + } +} \ No newline at end of file diff --git a/pitest/src/test/java/twr/example1/TryExample.java b/pitest-entry/src/test/java/twr/example1/TryExample.java similarity index 100% rename from pitest/src/test/java/twr/example1/TryExample.java rename to pitest-entry/src/test/java/twr/example1/TryExample.java diff --git a/pitest/src/test/java/twr/example2/TryCatchExample.java b/pitest-entry/src/test/java/twr/example2/TryCatchExample.java similarity index 100% rename from pitest/src/test/java/twr/example2/TryCatchExample.java rename to pitest-entry/src/test/java/twr/example2/TryCatchExample.java diff --git a/pitest/src/test/java/twr/example3/TryCatchFinallyExample.java b/pitest-entry/src/test/java/twr/example3/TryCatchFinallyExample.java similarity index 100% rename from pitest/src/test/java/twr/example3/TryCatchFinallyExample.java rename to pitest-entry/src/test/java/twr/example3/TryCatchFinallyExample.java diff --git a/pitest/src/test/java/twr/example4/TryFinallyExample.java b/pitest-entry/src/test/java/twr/example4/TryFinallyExample.java similarity index 100% rename from pitest/src/test/java/twr/example4/TryFinallyExample.java rename to pitest-entry/src/test/java/twr/example4/TryFinallyExample.java diff --git a/pitest/src/test/java/twr/example5/TryWithTwoCloseableExample.java b/pitest-entry/src/test/java/twr/example5/TryWithTwoCloseableExample.java similarity index 100% rename from pitest/src/test/java/twr/example5/TryWithTwoCloseableExample.java rename to pitest-entry/src/test/java/twr/example5/TryWithTwoCloseableExample.java diff --git a/pitest/src/test/java/twr/example6/TryWithNestedTryExample.java b/pitest-entry/src/test/java/twr/example6/TryWithNestedTryExample.java similarity index 100% rename from pitest/src/test/java/twr/example6/TryWithNestedTryExample.java rename to pitest-entry/src/test/java/twr/example6/TryWithNestedTryExample.java diff --git a/pitest/src/test/java/twr/example7/TryWithInterfaceExample.java b/pitest-entry/src/test/java/twr/example7/TryWithInterfaceExample.java similarity index 100% rename from pitest/src/test/java/twr/example7/TryWithInterfaceExample.java rename to pitest-entry/src/test/java/twr/example7/TryWithInterfaceExample.java diff --git a/pitest-entry/src/test/resources/sampleClasses/trywithresources/LargeTryWithResources_ecj.class.bin b/pitest-entry/src/test/resources/sampleClasses/trywithresources/LargeTryWithResources_ecj.class.bin new file mode 100644 index 000000000..4be028e33 Binary files /dev/null and b/pitest-entry/src/test/resources/sampleClasses/trywithresources/LargeTryWithResources_ecj.class.bin differ diff --git a/pitest-entry/src/test/resources/sampleClasses/trywithresources/LargeTryWithResources_javac.class.bin b/pitest-entry/src/test/resources/sampleClasses/trywithresources/LargeTryWithResources_javac.class.bin new file mode 100644 index 000000000..a6a1e13b0 Binary files /dev/null and b/pitest-entry/src/test/resources/sampleClasses/trywithresources/LargeTryWithResources_javac.class.bin differ diff --git a/pitest-entry/src/test/resources/sampleClasses/trywithresources/LargeTryWithResources_javac11.class.bin b/pitest-entry/src/test/resources/sampleClasses/trywithresources/LargeTryWithResources_javac11.class.bin new file mode 100644 index 000000000..783a828d5 Binary files /dev/null and b/pitest-entry/src/test/resources/sampleClasses/trywithresources/LargeTryWithResources_javac11.class.bin differ diff --git a/pitest/src/main/java/org/pitest/sequence/Context.java b/pitest/src/main/java/org/pitest/sequence/Context.java deleted file mode 100644 index 32f268915..000000000 --- a/pitest/src/main/java/org/pitest/sequence/Context.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.pitest.sequence; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import java.util.Optional; - -public class Context { - - private final boolean debug; - private final Map, Object> slots; - private final List sequence; - private int position; - - Context(Map, Object> slots, List sequence, int position, boolean debug) { - this.slots = slots; - this.sequence = sequence; - this.position = position; - this.debug = debug; - } - - public static Context start(List sequence) { - return start(sequence, false); - } - - public static Context start(List sequence, boolean debug) { - return new Context<>(new HashMap<>(), sequence, -1, debug); - } - - public boolean store(SlotWrite slot, S value) { - this.slots.put(slot.slot(), value); - return true; - } - - @SuppressWarnings("unchecked") - public Optional retrieve(SlotRead slot) { - return (Optional) Optional.ofNullable(this.slots.get(slot.slot())); - } - - - void moveForward() { - this.position = this.position + 1; - } - - public int position() { - return this.position; - } - - public void debug(String msg) { - if (this.debug) { - System.out.println(msg + " at " + this.position + " for " + this.sequence.get(this.position)); - } - } - -} diff --git a/pitest/src/main/java/org/pitest/sequence/Match.java b/pitest/src/main/java/org/pitest/sequence/Match.java deleted file mode 100644 index 97aaf9734..000000000 --- a/pitest/src/main/java/org/pitest/sequence/Match.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.pitest.sequence; - -/** - * Predicate with additional context. - * - * Implemented as abstract class as we're still on Java - * 6 and don't have default methods - * - * @param Type to match - */ -@FunctionalInterface -public interface Match { - - boolean test(Context c, T t); - - static Match always() { - return (c, t) -> true; - } - - static Match never() { - return (c, t) -> false; - } - - static Match isEqual(final Object targetRef) { - return (c, t) -> targetRef.equals(t); - } - - default Match and(final Match other) { - return (c, t) -> this.test(c,t) && other.test(c,t); - } - - default Match negate() { - return (c, t) -> !this.test(c,t); - } - - default Match or(final Match other) { - return (c, t) -> this.test(c,t) || other.test(c,t); - } -} diff --git a/pitest/src/main/java/org/pitest/sequence/SequenceMatcher.java b/pitest/src/main/java/org/pitest/sequence/SequenceMatcher.java deleted file mode 100644 index 2f152139b..000000000 --- a/pitest/src/main/java/org/pitest/sequence/SequenceMatcher.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.pitest.sequence; - -import java.util.List; - -public interface SequenceMatcher { - - boolean matches(List sequence); - - boolean matches(List sequence, Context initialContext); -} diff --git a/pitest/src/main/java/org/pitest/sequence/Slot.java b/pitest/src/main/java/org/pitest/sequence/Slot.java deleted file mode 100644 index ad8535677..000000000 --- a/pitest/src/main/java/org/pitest/sequence/Slot.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.pitest.sequence; - -public final class Slot { - public static Slot create(Class clazz) { - return new Slot<>(); - } - - public SlotWrite write() { - return new SlotWrite<>(this); - } - - public SlotRead read() { - return new SlotRead<>(this); - } -} diff --git a/pitest/src/test/java/org/pitest/sequence/MatchTest.java b/pitest/src/test/java/org/pitest/sequence/MatchTest.java deleted file mode 100644 index d92e2175c..000000000 --- a/pitest/src/test/java/org/pitest/sequence/MatchTest.java +++ /dev/null @@ -1,60 +0,0 @@ -package org.pitest.sequence; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import org.junit.Test; - -public class MatchTest { - - private final Context unused = null; - - @Test - public void alwaysShouldAlwaysMatch() { - final Match testee = Match.always(); - assertTrue(testee.test(this.unused, 1)); - assertTrue(testee.test(this.unused, Integer.MAX_VALUE)); - } - - @Test - public void neverShouldNeverMatch() { - final Match testee = Match.never(); - assertFalse(testee.test(this.unused, 1)); - assertFalse(testee.test(this.unused, Integer.MAX_VALUE)); - } - - @Test - public void negateShouldInvertLogic() { - final Match testee = Match.never(); - assertTrue(testee.negate().test(this.unused, 1)); - assertFalse(testee.negate().negate().test(this.unused, Integer.MAX_VALUE)); - } - - @Test - public void isEqualShouldCheckEquality() { - final Match testee = Match.isEqual(1); - assertTrue(testee.test(this.unused, 1)); - assertFalse(testee.test(this.unused, 2)); - } - - @Test - public void andShouldLogicallyAnd() { - final Match isTrue = Match.always(); - final Match isFalse = Match.never(); - assertTrue(isTrue.and(isTrue).test(this.unused, 1)); - assertFalse(isTrue.and(isFalse).test(this.unused, 1)); - assertFalse(isFalse.and(isFalse).test(this.unused, 1)); - assertFalse(isFalse.and(isTrue).test(this.unused, 1)); - } - - @Test - public void orShouldLogicallyOr() { - final Match isTrue = Match.always(); - final Match isFalse = Match.never(); - assertTrue(isTrue.or(isTrue).test(this.unused, 1)); - assertTrue(isTrue.or(isFalse).test(this.unused, 1)); - assertFalse(isFalse.or(isFalse).test(this.unused, 1)); - assertTrue(isFalse.or(isTrue).test(this.unused, 1)); - } - -} diff --git a/pitest/src/test/java/org/pitest/sequence/SequenceQueryTest.java b/pitest/src/test/java/org/pitest/sequence/SequenceQueryTest.java deleted file mode 100644 index efa5c6cf0..000000000 --- a/pitest/src/test/java/org/pitest/sequence/SequenceQueryTest.java +++ /dev/null @@ -1,117 +0,0 @@ -package org.pitest.sequence; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import java.util.Arrays; -import java.util.List; - -import org.junit.Test; - -public class SequenceQueryTest { - - @Test - public void shouldMatchSingleLiterals() { - final SequenceMatcher testee = QueryStart - .match(eq(1)) - .compile(); - - assertTrue(testee.matches(asList(1))); - assertFalse(testee.matches( asList(2))); - } - - @Test - public void shouldMatchSimpleSequences() { - final SequenceMatcher testee = QueryStart - .match(eq(1)) - .then(eq(2)) - .then(eq(3)) - .compile(); - - assertTrue(testee.matches(asList(1, 2, 3))); - assertFalse(testee.matches(asList(1, 2))); - assertFalse(testee.matches(asList(1, 2, 3, 4))); - } - - @Test - public void shouldMatchSimpleOrs() { - final SequenceQuery right = QueryStart.match(eq(2)); - - final SequenceMatcher testee = QueryStart - .match(eq(1)) - .or(right) - .compile(); - - assertTrue(testee.matches(asList(1))); - assertTrue(testee.matches(asList(2))); - assertFalse(testee.matches(asList(3))); - } - - @Test - public void shouldMatchSimpleZeroOrMores() { - final SequenceQuery right = QueryStart.match(eq(2)); - - final SequenceMatcher testee = QueryStart - .match(eq(1)) - .zeroOrMore(right) - .compile(); - - assertTrue(testee.matches(asList(1))); - assertTrue(testee.matches(asList(1, 2))); - assertTrue(testee.matches(asList(1, 2, 2, 2))); - assertFalse(testee.matches(asList(1, 2, 3))); - assertFalse(testee.matches(asList(1, 3))); - } - - @Test - public void shouldMatchSimpleOneOrMores() { - final SequenceQuery right = QueryStart.match(eq(2)); - - final SequenceMatcher testee = QueryStart - .match(eq(1)) - .oneOrMore(right) - .compile(); - - assertFalse(testee.matches(asList(1))); - assertTrue(testee.matches(asList(1, 2))); - assertTrue(testee.matches(asList(1, 2, 2, 2))); - assertFalse(testee.matches(asList(1, 2, 3))); - assertFalse(testee.matches(asList(1, 3))); - } - - @Test - public void shouldMatchAnyOf() { - final SequenceQuery left = QueryStart.match(eq(2)); - - final SequenceQuery right = QueryStart.match(eq(3)); - - final SequenceMatcher testee = QueryStart.match(eq(1)) - .thenAnyOf(left, right) - .then(eq(99)) - .compile(); - - assertTrue(testee.matches(asList(1, 2, 99))); - assertTrue(testee.matches(asList(1, 3, 99))); - assertFalse(testee.matches(asList(1, 2))); - assertFalse(testee.matches(asList(1, 2, 3, 99))); - } - - @Test - public void shouldSkipItemsMatchingIgnoreList() { - final SequenceMatcher testee = QueryStart - .match(eq(1)) - .then(eq(2)) - .compile(QueryParams.params(Integer.class).withIgnores(eq(99))); - - assertTrue(testee.matches(asList(1, 99, 2))); - } - - private Match eq(final int i) { - return Match.isEqual(i); - } - - private static List asList(Integer... is) { - return Arrays.asList(is); - } - -}