From 7d8ce22a00e82f203fcda3824973d7e3b20b047b Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Mon, 22 Jan 2024 17:34:16 +0000 Subject: [PATCH 1/4] mutate delayed execution code stored in statics We don't want to mutate code executed only during static initialization. Unfortunately the current filtering also picks up code that is executed after static initialization as lambdas. This change implelements an imperfect comprimise where code stored as Suppliers, Functions etc will be considered for mutation. This will fail to mutate delayed execution code stored in other types, and will incorrectly mutate code that is executed only during initialization if it is within a method that returns a Supplier etc. Although imperfect, it is an improvement. --- .../StaticInitializerInterceptor.java | 119 ++++++++++++++++-- .../staticinitializers/EnumFieldSupplier.java | 20 +++ .../StaticFunctionField.java | 14 +++ .../StaticSupplierField.java | 14 +++ .../StaticInitializerInterceptorTest.java | 35 +++++- 5 files changed, 191 insertions(+), 11 deletions(-) create mode 100644 pitest-entry/src/test/java/com/example/staticinitializers/EnumFieldSupplier.java create mode 100644 pitest-entry/src/test/java/com/example/staticinitializers/StaticFunctionField.java create mode 100644 pitest-entry/src/test/java/com/example/staticinitializers/StaticSupplierField.java diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptor.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptor.java index ed8a23e97..4b225e9b1 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptor.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptor.java @@ -1,6 +1,9 @@ package org.pitest.mutationtest.build.intercept.staticinitializers; import org.objectweb.asm.Handle; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.InvokeDynamicInsnNode; import org.objectweb.asm.tree.MethodInsnNode; import org.pitest.bytecode.analysis.ClassTree; @@ -11,6 +14,14 @@ import org.pitest.mutationtest.engine.Location; import org.pitest.mutationtest.engine.Mutater; import org.pitest.mutationtest.engine.MutationDetails; +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.SlotWrite; import java.util.Arrays; import java.util.Collection; @@ -18,6 +29,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Function; @@ -25,6 +37,13 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static org.pitest.bytecode.analysis.InstructionMatchers.anyInstruction; +import static org.pitest.bytecode.analysis.InstructionMatchers.isA; +import static org.pitest.bytecode.analysis.InstructionMatchers.methodCallNamed; +import static org.pitest.bytecode.analysis.InstructionMatchers.notAnInstruction; +import static org.pitest.bytecode.analysis.OpcodeMatchers.PUTSTATIC; +import static org.pitest.sequence.Result.result; + /** * Identifies and marks mutations in code that is active during class * Initialisation. @@ -39,6 +58,29 @@ */ class StaticInitializerInterceptor implements MutationInterceptor { + static final Slot START = Slot.create(AbstractInsnNode.class); + + static final SequenceMatcher DELAYED_EXECUTION = QueryStart + .any(AbstractInsnNode.class) + .then(returnsDeferredExecutionCode().or(isA(InvokeDynamicInsnNode.class)).and(store(START.write()))) + .then(enumConstructorCallAndStore().or(QueryStart.match(PUTSTATIC))) + .zeroOrMore(QueryStart.match(anyInstruction())) + .compile(QueryParams.params(AbstractInsnNode.class) + .withIgnores(notAnInstruction()) + ); + + private static Match returnsDeferredExecutionCode() { + return (c,n) -> result(n.getOpcode() == Opcodes.INVOKESTATIC && returnDelayedExecutionType(((MethodInsnNode) n).desc), c); + } + + private static boolean returnDelayedExecutionType(String desc) { + int endOfParams = desc.indexOf(')'); + return endOfParams <= 0 || desc.substring(endOfParams + 1).startsWith("Ljava/util/function/"); + } + + private static SequenceQuery enumConstructorCallAndStore() { + return QueryStart.match(methodCallNamed("")).then(PUTSTATIC); + } private Predicate isStaticInitCode; @Override @@ -73,28 +115,54 @@ private void analyseClass(ClassTree tree) { .map(MethodTree::asLocation) .collect(Collectors.toSet()); + Set storedToSupplier = findsCallsStoredToSuppliers(tree); + // Get map of each private method to the private methods it calls - // Any call to a no private method breaks the chain + // Any call to a non private method breaks the chain Map> callTree = tree.methods().stream() .filter(m -> m.isPrivate() || m.rawNode().name.equals("")) .flatMap(m -> allCallsFor(tree, m).stream().map(c -> new Call(m.asLocation(), c))) .filter(c -> privateMethods.contains(c.to())) + .filter(c -> !storedToSupplier.contains(c)) .collect(Collectors.groupingBy(Call::from)); - Set visited = new HashSet<>(); - visit(callTree, visited, clinit.get().asLocation()); + Set calledOnlyFromStaticInitializer = new HashSet<>(); - this.isStaticInitCode = m -> visited.contains(m.getId().getLocation()); + visit(callTree, calledOnlyFromStaticInitializer, clinit.get().asLocation()); + + this.isStaticInitCode = m -> calledOnlyFromStaticInitializer.contains(m.getId().getLocation()); } } + private Set findsCallsStoredToSuppliers(ClassTree tree) { + Set all = new HashSet<>(directClinitCallsToDelayedExecutionCode(tree)); + all.addAll(storedViaEnumConstructor()); + return all; + } + + private Set storedViaEnumConstructor() { +return Collections.emptySet(); + } + + private Set directClinitCallsToDelayedExecutionCode(ClassTree tree) { + return tree.methods().stream() + .filter(m -> m.isPrivate() || m.rawNode().name.equals("")) + .flatMap(m -> delayedExecutionCall(m).stream().map(c -> new Call(m.asLocation(), c))) + .collect(Collectors.toSet()); + } + + private List delayedExecutionCall(MethodTree method) { + Context context = Context.start(); + return DELAYED_EXECUTION.contextMatches(method.instructions(), context).stream() + .map(c -> c.retrieve(START.read()).get()) + .flatMap(this::nodeToLocation) + .collect(Collectors.toList()); + } + private List allCallsFor(ClassTree tree, MethodTree m) { - // temporarily disable dynamic calls as they are more likely to be involved - // in storing delayed execution code within static fields. - return callsFor(tree,m).collect(Collectors.toList()); - // return Stream.concat(callsFor(tree,m), invokeDynamicCallsFor(tree,m)) - // .collect(Collectors.toList()); + return Stream.concat(callsFor(tree,m), invokeDynamicCallsFor(tree,m)) + .collect(Collectors.toList()); } private Stream callsFor(ClassTree tree, MethodTree m) { @@ -123,6 +191,18 @@ private void visit(Map> callTree, Set visited, Lo } } + private Stream nodeToLocation(AbstractInsnNode n) { + if (n instanceof MethodInsnNode) { + return Stream.of(asLocation((MethodInsnNode) n)); + } + + if (n instanceof InvokeDynamicInsnNode) { + return asLocation((InvokeDynamicInsnNode) n); + } + + return Stream.empty(); + } + private Location asLocation(MethodInsnNode call) { return Location.location(ClassName.fromString(call.owner), call.name, call.desc); } @@ -166,6 +246,10 @@ public InterceptorType type() { return InterceptorType.FILTER; } + private static Match store(SlotWrite slot) { + return (c, n) -> result(true, c.store(slot, n)); + } + } class Call { @@ -184,4 +268,21 @@ Location from() { Location to() { return to; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Call call = (Call) o; + return Objects.equals(from, call.from) && Objects.equals(to, call.to); + } + + @Override + public int hashCode() { + return Objects.hash(from, to); + } } \ No newline at end of file diff --git a/pitest-entry/src/test/java/com/example/staticinitializers/EnumFieldSupplier.java b/pitest-entry/src/test/java/com/example/staticinitializers/EnumFieldSupplier.java new file mode 100644 index 000000000..1e1111643 --- /dev/null +++ b/pitest-entry/src/test/java/com/example/staticinitializers/EnumFieldSupplier.java @@ -0,0 +1,20 @@ +package com.example.staticinitializers; + +import java.util.function.Supplier; + +public enum EnumFieldSupplier { + A(canMutate()); + + private final Supplier supplier; + + EnumFieldSupplier(Supplier supplier) { + this.supplier = supplier; + } + + private static Supplier canMutate() { + // don't mutate + System.out.println("ideally would mutate me"); + + return () -> "Do not mutate"; // mutate + } +} diff --git a/pitest-entry/src/test/java/com/example/staticinitializers/StaticFunctionField.java b/pitest-entry/src/test/java/com/example/staticinitializers/StaticFunctionField.java new file mode 100644 index 000000000..aef6d53e0 --- /dev/null +++ b/pitest-entry/src/test/java/com/example/staticinitializers/StaticFunctionField.java @@ -0,0 +1,14 @@ +package com.example.staticinitializers; + +import java.util.function.Function; + +public class StaticFunctionField { + private static final Function FOO = canMutate(); + + private static Function canMutate() { + // don't mutate + System.out.println("ideally would mutate me"); + + return s -> s + "foo"; + } +} diff --git a/pitest-entry/src/test/java/com/example/staticinitializers/StaticSupplierField.java b/pitest-entry/src/test/java/com/example/staticinitializers/StaticSupplierField.java new file mode 100644 index 000000000..c7a82c0df --- /dev/null +++ b/pitest-entry/src/test/java/com/example/staticinitializers/StaticSupplierField.java @@ -0,0 +1,14 @@ +package com.example.staticinitializers; + +import java.util.function.Supplier; + +public class StaticSupplierField { + final static Supplier SUPPLER = canMutate(); + + private static Supplier canMutate() { + // don't mutate + System.out.println("ideally would mutate me"); + + return () -> "Do not mutate"; // mutate + } +} diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptorTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptorTest.java index 644f1a972..e4abb6b66 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptorTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptorTest.java @@ -1,11 +1,14 @@ package org.pitest.mutationtest.build.intercept.staticinitializers; import com.example.staticinitializers.BrokenChain; +import com.example.staticinitializers.EnumFieldSupplier; import com.example.staticinitializers.EnumWithLambdaInConstructor; import com.example.staticinitializers.MethodsCallsEachOtherInLoop; import com.example.staticinitializers.NestedEnumWithLambdaInStaticInitializer; import com.example.staticinitializers.SecondLevelPrivateMethods; import com.example.staticinitializers.SingletonWithWorkInInitializer; +import com.example.staticinitializers.StaticFunctionField; +import com.example.staticinitializers.StaticSupplierField; import com.example.staticinitializers.ThirdLevelPrivateMethods; import org.junit.Ignore; import org.junit.Test; @@ -146,7 +149,6 @@ public void analysisDoesNotGetStuckInInfiniteLoop() { } @Test - @Ignore("temporally disabled while filtering reworked") public void filtersMutantsInEnumPrivateMethodsCalledViaMethodRef() { v.forClass(EnumWithLambdaInConstructor.class) .forMutantsMatching(inMethodStartingWith("doStuff")) @@ -156,7 +158,6 @@ public void filtersMutantsInEnumPrivateMethodsCalledViaMethodRef() { } @Test - @Ignore("temporally disabled while filtering reworked") public void filtersMutantsInLambdaCalledFromStaticInitializerInNestedEnum() { v.forClass(NestedEnumWithLambdaInStaticInitializer.TOYS.class) .forMutantsMatching(inMethodStartingWith("lambda")) @@ -165,6 +166,36 @@ public void filtersMutantsInLambdaCalledFromStaticInitializerInNestedEnum() { .verify(); } + @Test + public void doesNotSuppressDownStreamMutationsForCodeStoredInSuppliers() { + v.forClass(StaticSupplierField.class) + .forMethod("canMutate") + .forAnyCode() + .mutantsAreGenerated() + .noMutantsAreFiltered() + .verify(); + } + + @Test + public void doesNotSuppressDownStreamMutationsForCodeStoredInFunctions() { + v.forClass(StaticFunctionField.class) + .forMethod("canMutate") + .forAnyCode() + .mutantsAreGenerated() + .noMutantsAreFiltered() + .verify(); + } + + @Test + public void doesNotSuppressDownStreamMutationsForEnumFieldSuppliers() { + v.forClass(EnumFieldSupplier.class) + .forMethod("canMutate") + .forAnyCode() + .mutantsAreGenerated() + .noMutantsAreFiltered() + .verify(); + } + private Predicate inMethod(String name, String desc) { return m -> m.getMethod().equals(name) && m.getId().getLocation().getMethodDesc().equals(desc); } From a53e47ef95a276fe3b129c41a762830f96052aac Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Thu, 1 Aug 2024 17:07:38 +0100 Subject: [PATCH 2/4] check field type when looking for stored delayed execution methods --- .../StaticInitializerInterceptor.java | 39 ++++++++++++------ .../delayedexecution/EnumFieldMethodRef.java | 18 +++++++++ .../EnumFieldSupplier.java | 4 +- .../delayedexecution/EnumMixedFields.java | 23 +++++++++++ .../StaticFunctionField.java | 2 +- .../StaticSupplierField.java | 2 +- .../StaticInitializerInterceptorTest.java | 40 +++++++++++++++++-- 7 files changed, 107 insertions(+), 21 deletions(-) create mode 100644 pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/EnumFieldMethodRef.java rename pitest-entry/src/test/java/com/example/staticinitializers/{ => delayedexecution}/EnumFieldSupplier.java (80%) create mode 100644 pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/EnumMixedFields.java rename pitest-entry/src/test/java/com/example/staticinitializers/{ => delayedexecution}/StaticFunctionField.java (84%) rename pitest-entry/src/test/java/com/example/staticinitializers/{ => delayedexecution}/StaticSupplierField.java (84%) diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptor.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptor.java index 4b225e9b1..6c5f9052c 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptor.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptor.java @@ -38,7 +38,6 @@ import java.util.stream.Stream; import static org.pitest.bytecode.analysis.InstructionMatchers.anyInstruction; -import static org.pitest.bytecode.analysis.InstructionMatchers.isA; import static org.pitest.bytecode.analysis.InstructionMatchers.methodCallNamed; import static org.pitest.bytecode.analysis.InstructionMatchers.notAnInstruction; import static org.pitest.bytecode.analysis.OpcodeMatchers.PUTSTATIC; @@ -62,13 +61,32 @@ class StaticInitializerInterceptor implements MutationInterceptor { static final SequenceMatcher DELAYED_EXECUTION = QueryStart .any(AbstractInsnNode.class) - .then(returnsDeferredExecutionCode().or(isA(InvokeDynamicInsnNode.class)).and(store(START.write()))) - .then(enumConstructorCallAndStore().or(QueryStart.match(PUTSTATIC))) + // look for calls returning delayed execution types. Unfortunately this is not guarantee that we + // store the result to an appropriate type + .then(returnsDeferredExecutionCode().or(dynamicallyReturnsDeferredExecutionCode()).and(store(START.write()))) + // allow for other method calls etc + .zeroOrMore(QueryStart.match(anyInstruction())) + .then(enumConstructorCallAndStore().or(QueryStart.match(delayedExecutionField()))) .zeroOrMore(QueryStart.match(anyInstruction())) .compile(QueryParams.params(AbstractInsnNode.class) .withIgnores(notAnInstruction()) ); + private static Match delayedExecutionField() { + return PUTSTATIC.and(isAUtilFunctionField()); + } + + private static Match isAUtilFunctionField() { + return (c,n) -> { + FieldInsnNode fieldNode = ((FieldInsnNode) n); + return result( fieldNode.desc.startsWith("Ljava/util/function/"), c); + }; + } + + private static Match dynamicallyReturnsDeferredExecutionCode() { + return (c,n) -> result(n.getOpcode() == Opcodes.INVOKEDYNAMIC && returnDelayedExecutionType(((InvokeDynamicInsnNode) n).desc), c); + } + private static Match returnsDeferredExecutionCode() { return (c,n) -> result(n.getOpcode() == Opcodes.INVOKESTATIC && returnDelayedExecutionType(((MethodInsnNode) n).desc), c); } @@ -111,11 +129,11 @@ private void analyseClass(ClassTree tree) { // We can't see if a method *call* is private from the call site // so collect a set of private methods within the class first Set privateMethods = tree.methods().stream() - .filter(m -> m.isPrivate()) + .filter(MethodTree::isPrivate) .map(MethodTree::asLocation) .collect(Collectors.toSet()); - Set storedToSupplier = findsCallsStoredToSuppliers(tree); + Set storedToSupplier = findCallsStoredToDelayedExecutionCode(tree); // Get map of each private method to the private methods it calls // Any call to a non private method breaks the chain @@ -135,17 +153,12 @@ private void analyseClass(ClassTree tree) { } } - private Set findsCallsStoredToSuppliers(ClassTree tree) { - Set all = new HashSet<>(directClinitCallsToDelayedExecutionCode(tree)); - all.addAll(storedViaEnumConstructor()); - return all; + private Set findCallsStoredToDelayedExecutionCode(ClassTree tree) { + return new HashSet<>(privateAndClinitCallsToDelayedExecutionCode(tree)); } - private Set storedViaEnumConstructor() { -return Collections.emptySet(); - } - private Set directClinitCallsToDelayedExecutionCode(ClassTree tree) { + private Set privateAndClinitCallsToDelayedExecutionCode(ClassTree tree) { return tree.methods().stream() .filter(m -> m.isPrivate() || m.rawNode().name.equals("")) .flatMap(m -> delayedExecutionCall(m).stream().map(c -> new Call(m.asLocation(), c))) diff --git a/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/EnumFieldMethodRef.java b/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/EnumFieldMethodRef.java new file mode 100644 index 000000000..ef83c0928 --- /dev/null +++ b/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/EnumFieldMethodRef.java @@ -0,0 +1,18 @@ +package com.example.staticinitializers.delayedexecution; + +import java.util.function.Supplier; + +public enum EnumFieldMethodRef { + A(EnumFieldMethodRef::canMutate), B(EnumFieldMethodRef::canMutate); + + private final Supplier supplier; + + + EnumFieldMethodRef(Supplier supplier) { + this.supplier = supplier; + } + + private static String canMutate() { + return "Do not mutate"; + } +} diff --git a/pitest-entry/src/test/java/com/example/staticinitializers/EnumFieldSupplier.java b/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/EnumFieldSupplier.java similarity index 80% rename from pitest-entry/src/test/java/com/example/staticinitializers/EnumFieldSupplier.java rename to pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/EnumFieldSupplier.java index 1e1111643..2dffdb64a 100644 --- a/pitest-entry/src/test/java/com/example/staticinitializers/EnumFieldSupplier.java +++ b/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/EnumFieldSupplier.java @@ -1,9 +1,9 @@ -package com.example.staticinitializers; +package com.example.staticinitializers.delayedexecution; import java.util.function.Supplier; public enum EnumFieldSupplier { - A(canMutate()); + A(canMutate()), B(canMutate()); private final Supplier supplier; diff --git a/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/EnumMixedFields.java b/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/EnumMixedFields.java new file mode 100644 index 000000000..ae8e18324 --- /dev/null +++ b/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/EnumMixedFields.java @@ -0,0 +1,23 @@ +package com.example.staticinitializers.delayedexecution; + +import java.util.function.Supplier; + +public enum EnumMixedFields { + A(EnumMixedFields::canMutate, doNotMutate()), B(EnumMixedFields::canMutate, doNotMutate()); + + private final Supplier supplier; + private final String s; + + EnumMixedFields(Supplier supplier, String s) { + this.supplier = supplier; + this.s = s; + } + + private static String canMutate() { + return "mutate me"; // mutate + } + + private static String doNotMutate() { + return "Do not mutate"; + } +} \ No newline at end of file diff --git a/pitest-entry/src/test/java/com/example/staticinitializers/StaticFunctionField.java b/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/StaticFunctionField.java similarity index 84% rename from pitest-entry/src/test/java/com/example/staticinitializers/StaticFunctionField.java rename to pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/StaticFunctionField.java index aef6d53e0..fb4a3dae3 100644 --- a/pitest-entry/src/test/java/com/example/staticinitializers/StaticFunctionField.java +++ b/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/StaticFunctionField.java @@ -1,4 +1,4 @@ -package com.example.staticinitializers; +package com.example.staticinitializers.delayedexecution; import java.util.function.Function; diff --git a/pitest-entry/src/test/java/com/example/staticinitializers/StaticSupplierField.java b/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/StaticSupplierField.java similarity index 84% rename from pitest-entry/src/test/java/com/example/staticinitializers/StaticSupplierField.java rename to pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/StaticSupplierField.java index c7a82c0df..55fd5cb3a 100644 --- a/pitest-entry/src/test/java/com/example/staticinitializers/StaticSupplierField.java +++ b/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/StaticSupplierField.java @@ -1,4 +1,4 @@ -package com.example.staticinitializers; +package com.example.staticinitializers.delayedexecution; import java.util.function.Supplier; diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptorTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptorTest.java index e4abb6b66..624535e90 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptorTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptorTest.java @@ -1,16 +1,17 @@ package org.pitest.mutationtest.build.intercept.staticinitializers; import com.example.staticinitializers.BrokenChain; -import com.example.staticinitializers.EnumFieldSupplier; +import com.example.staticinitializers.delayedexecution.EnumFieldMethodRef; +import com.example.staticinitializers.delayedexecution.EnumFieldSupplier; import com.example.staticinitializers.EnumWithLambdaInConstructor; import com.example.staticinitializers.MethodsCallsEachOtherInLoop; import com.example.staticinitializers.NestedEnumWithLambdaInStaticInitializer; import com.example.staticinitializers.SecondLevelPrivateMethods; import com.example.staticinitializers.SingletonWithWorkInInitializer; -import com.example.staticinitializers.StaticFunctionField; -import com.example.staticinitializers.StaticSupplierField; +import com.example.staticinitializers.delayedexecution.EnumMixedFields; +import com.example.staticinitializers.delayedexecution.StaticFunctionField; +import com.example.staticinitializers.delayedexecution.StaticSupplierField; import com.example.staticinitializers.ThirdLevelPrivateMethods; -import org.junit.Ignore; import org.junit.Test; import org.pitest.mutationtest.engine.MutationDetails; import org.pitest.mutationtest.engine.gregor.mutators.NullMutateEverything; @@ -196,6 +197,37 @@ public void doesNotSuppressDownStreamMutationsForEnumFieldSuppliers() { .verify(); } + + @Test + public void doesNotSuppressDownStreamMutationsForMethodRefsStoredToEnumFields() { + v.forClass(EnumFieldMethodRef.class) + .forMethod("canMutate") + .forAnyCode() + .mutantsAreGenerated() + .noMutantsAreFiltered() + .verify(); + } + + @Test + public void doesNotSuppressDownStreamMutationsForMethodRefsStoredToEnumFieldsWhenOtherFieldsInitialized() { + v.forClass(EnumMixedFields.class) + .forMethod("canMutate") + .forAnyCode() + .mutantsAreGenerated() + .noMutantsAreFiltered() + .verify(); + } + + @Test + public void suppressesMutationsForStringsStoredToEnumFields() { + v.forClass(EnumMixedFields.class) + .forMethod("doNotMutate") + .forAnyCode() + .mutantsAreGenerated() + .allMutantsAreFiltered() + .verify(); + } + private Predicate inMethod(String name, String desc) { return m -> m.getMethod().equals(name) && m.getId().getLocation().getMethodDesc().equals(desc); } From 0587e7934e9eb8c3fc0d6f36c7fecc5e095f3406 Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Wed, 28 Aug 2024 09:41:23 +0100 Subject: [PATCH 3/4] additional test case --- .../EnumMethodReferenceNotStored.java | 20 +++++++++++++++++++ .../StaticInitializerInterceptorTest.java | 11 ++++++++++ 2 files changed, 31 insertions(+) create mode 100644 pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/EnumMethodReferenceNotStored.java diff --git a/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/EnumMethodReferenceNotStored.java b/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/EnumMethodReferenceNotStored.java new file mode 100644 index 000000000..ec998203e --- /dev/null +++ b/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/EnumMethodReferenceNotStored.java @@ -0,0 +1,20 @@ +package com.example.staticinitializers.delayedexecution; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public enum EnumMethodReferenceNotStored { + A(Arrays.asList(1,2,3)); + + private final List l; + EnumMethodReferenceNotStored(List list) { + l = list.stream() + .filter(this::doNotMutate) + .collect(Collectors.toList()); + } + + private boolean doNotMutate(Integer i) { + return i > 2; + } +} diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptorTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptorTest.java index 624535e90..8822bd49d 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptorTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptorTest.java @@ -8,6 +8,7 @@ import com.example.staticinitializers.NestedEnumWithLambdaInStaticInitializer; import com.example.staticinitializers.SecondLevelPrivateMethods; import com.example.staticinitializers.SingletonWithWorkInInitializer; +import com.example.staticinitializers.delayedexecution.EnumMethodReferenceNotStored; import com.example.staticinitializers.delayedexecution.EnumMixedFields; import com.example.staticinitializers.delayedexecution.StaticFunctionField; import com.example.staticinitializers.delayedexecution.StaticSupplierField; @@ -228,6 +229,16 @@ public void suppressesMutationsForStringsStoredToEnumFields() { .verify(); } + @Test + public void filtersMutationsForMethodReferencesUsedInEnumConstructor() { + v.forClass(EnumMethodReferenceNotStored.class) + .forMethod("doNotMutate") + .forAnyCode() + .mutantsAreGenerated() + .allMutantsAreFiltered() + .verify(); + } + private Predicate inMethod(String name, String desc) { return m -> m.getMethod().equals(name) && m.getId().getLocation().getMethodDesc().equals(desc); } From 74bcdfd29d894ee9fd0e022f81caccce1a30b523 Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Wed, 28 Aug 2024 11:17:53 +0100 Subject: [PATCH 4/4] backwards compatibility --- .../org/pitest/mutationtest/MutationStatusTestPair.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pitest/src/main/java/org/pitest/mutationtest/MutationStatusTestPair.java b/pitest/src/main/java/org/pitest/mutationtest/MutationStatusTestPair.java index 8889508e8..52d01b6ae 100644 --- a/pitest/src/main/java/org/pitest/mutationtest/MutationStatusTestPair.java +++ b/pitest/src/main/java/org/pitest/mutationtest/MutationStatusTestPair.java @@ -31,6 +31,12 @@ public final class MutationStatusTestPair implements Serializable { private final List coveringTests; + @Deprecated + // for backwards compatibility. Remove at next major release + public static MutationStatusTestPair notAnalysed(int testsRun, DetectionStatus status) { + return notAnalysed(testsRun, status, Collections.emptyList()); + } + public static MutationStatusTestPair notAnalysed(int testsRun, DetectionStatus status, List coveringTests) { return new MutationStatusTestPair(testsRun, status, Collections.emptyList(), Collections.emptyList(), coveringTests); }