From 45d9f34ef657ec9d71dcc0d1622cd5444b8a2279 Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Thu, 29 Aug 2024 08:28:21 +0100 Subject: [PATCH 1/6] identify fields with a prescan --- .../StaticInitializerInterceptor.java | 54 +++++++++++++------ .../main/java/org/pitest/sequence/Slot.java | 5 ++ .../delayedexecution/EnumListOfSuppliers.java | 21 ++++++++ .../StaticListOfFunctions.java | 19 +++++++ .../StaticInitializerInterceptorTest.java | 24 +++++++++ .../org/pitest/bytecode/SignatureParser.java | 31 +++++++++++ .../pitest/bytecode/SignatureParserTest.java | 25 +++++++++ 7 files changed, 163 insertions(+), 16 deletions(-) create mode 100644 pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/EnumListOfSuppliers.java create mode 100644 pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/StaticListOfFunctions.java create mode 100644 pitest/src/main/java/org/pitest/bytecode/SignatureParser.java create mode 100644 pitest/src/test/java/org/pitest/bytecode/SignatureParserTest.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 6c5f9052c..540d20c96 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 @@ -4,8 +4,10 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.FieldNode; import org.objectweb.asm.tree.InvokeDynamicInsnNode; import org.objectweb.asm.tree.MethodInsnNode; +import org.pitest.bytecode.SignatureParser; import org.pitest.bytecode.analysis.ClassTree; import org.pitest.bytecode.analysis.MethodTree; import org.pitest.classinfo.ClassName; @@ -21,6 +23,7 @@ import org.pitest.sequence.SequenceMatcher; import org.pitest.sequence.SequenceQuery; import org.pitest.sequence.Slot; +import org.pitest.sequence.SlotRead; import org.pitest.sequence.SlotWrite; import java.util.Arrays; @@ -53,11 +56,11 @@ * 1. In a static initializer (i.e ) * 2. In a private method or constructor called from or another private method in the call tree * - * */ class StaticInitializerInterceptor implements MutationInterceptor { static final Slot START = Slot.create(AbstractInsnNode.class); + static final Slot> DELAYED_EXECUTION_FIELDS = Slot.createSet(String.class); static final SequenceMatcher DELAYED_EXECUTION = QueryStart .any(AbstractInsnNode.class) @@ -66,36 +69,41 @@ class StaticInitializerInterceptor implements MutationInterceptor { .then(returnsDeferredExecutionCode().or(dynamicallyReturnsDeferredExecutionCode()).and(store(START.write()))) // allow for other method calls etc .zeroOrMore(QueryStart.match(anyInstruction())) - .then(enumConstructorCallAndStore().or(QueryStart.match(delayedExecutionField()))) + .then(enumConstructorCallAndStore().or(QueryStart.match(delayedExecutionField(DELAYED_EXECUTION_FIELDS.read())))) .zeroOrMore(QueryStart.match(anyInstruction())) .compile(QueryParams.params(AbstractInsnNode.class) .withIgnores(notAnInstruction()) ); - private static Match delayedExecutionField() { - return PUTSTATIC.and(isAUtilFunctionField()); + private static Match delayedExecutionField(SlotRead> delayedFields) { + return PUTSTATIC.and(isADelayedExecutionField(delayedFields)); } - private static Match isAUtilFunctionField() { + private static Match isADelayedExecutionField(SlotRead> delayedFields) { return (c,n) -> { FieldInsnNode fieldNode = ((FieldInsnNode) n); - return result( fieldNode.desc.startsWith("Ljava/util/function/"), c); + return result( c.retrieve(delayedFields).get().contains(fieldNode.name), c); }; } private static Match dynamicallyReturnsDeferredExecutionCode() { - return (c,n) -> result(n.getOpcode() == Opcodes.INVOKEDYNAMIC && returnDelayedExecutionType(((InvokeDynamicInsnNode) n).desc), c); + return (c,n) -> result(n.getOpcode() == Opcodes.INVOKEDYNAMIC && returnsDelayedExecutionType(((InvokeDynamicInsnNode) n).desc), c); } private static Match returnsDeferredExecutionCode() { - return (c,n) -> result(n.getOpcode() == Opcodes.INVOKESTATIC && returnDelayedExecutionType(((MethodInsnNode) n).desc), c); + return (c,n) -> result(n.getOpcode() == Opcodes.INVOKESTATIC && returnsDelayedExecutionType(((MethodInsnNode) n).desc), c); } - private static boolean returnDelayedExecutionType(String desc) { + private static boolean returnsDelayedExecutionType(String desc) { int endOfParams = desc.indexOf(')'); return endOfParams <= 0 || desc.substring(endOfParams + 1).startsWith("Ljava/util/function/"); } + + private static boolean isADelayedExecutionType(String type) { + return type.startsWith("java/util/function/"); + } + private static SequenceQuery enumConstructorCallAndStore() { return QueryStart.match(methodCallNamed("")).then(PUTSTATIC); } @@ -126,6 +134,15 @@ private void analyseClass(ClassTree tree) { final Optional clinit = tree.methods().stream().filter(nameEquals("")).findFirst(); if (clinit.isPresent()) { + + // Find delayed execution fields (Function, Supplier, List etc). Full generic signature is available in the + // declaration, but not on call. + Set delayedExecutionFields = tree.rawNode().fields.stream() + .filter(this::isDelayedExecutionField) + .map(n -> n.name) + .collect(Collectors.toSet()); + + // 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() @@ -133,7 +150,7 @@ private void analyseClass(ClassTree tree) { .map(MethodTree::asLocation) .collect(Collectors.toSet()); - Set storedToSupplier = findCallsStoredToDelayedExecutionCode(tree); + Set storedToSupplier = findCallsStoredToDelayedExecutionCode(tree, delayedExecutionFields); // Get map of each private method to the private methods it calls // Any call to a non private method breaks the chain @@ -153,20 +170,25 @@ private void analyseClass(ClassTree tree) { } } - private Set findCallsStoredToDelayedExecutionCode(ClassTree tree) { - return new HashSet<>(privateAndClinitCallsToDelayedExecutionCode(tree)); + private boolean isDelayedExecutionField(FieldNode fieldNode) { + return SignatureParser.extractTypes(fieldNode.signature).stream() + .anyMatch(StaticInitializerInterceptor::isADelayedExecutionType); + } + + private Set findCallsStoredToDelayedExecutionCode(ClassTree tree, Set delayedExecutionFields) { + return new HashSet<>(privateAndClinitCallsToDelayedExecutionCode(tree, delayedExecutionFields)); } - private Set privateAndClinitCallsToDelayedExecutionCode(ClassTree tree) { + private Set privateAndClinitCallsToDelayedExecutionCode(ClassTree tree, Set delayedExecutionFields) { return tree.methods().stream() .filter(m -> m.isPrivate() || m.rawNode().name.equals("")) - .flatMap(m -> delayedExecutionCall(m).stream().map(c -> new Call(m.asLocation(), c))) + .flatMap(m -> delayedExecutionCall(m, delayedExecutionFields).stream().map(c -> new Call(m.asLocation(), c))) .collect(Collectors.toSet()); } - private List delayedExecutionCall(MethodTree method) { - Context context = Context.start(); + private List delayedExecutionCall(MethodTree method, Set delayedExecutionFields) { + Context context = Context.start().store(DELAYED_EXECUTION_FIELDS.write(), delayedExecutionFields); return DELAYED_EXECUTION.contextMatches(method.instructions(), context).stream() .map(c -> c.retrieve(START.read()).get()) .flatMap(this::nodeToLocation) diff --git a/pitest-entry/src/main/java/org/pitest/sequence/Slot.java b/pitest-entry/src/main/java/org/pitest/sequence/Slot.java index 06da33173..798eb7028 100644 --- a/pitest-entry/src/main/java/org/pitest/sequence/Slot.java +++ b/pitest-entry/src/main/java/org/pitest/sequence/Slot.java @@ -1,6 +1,7 @@ package org.pitest.sequence; import java.util.List; +import java.util.Set; public final class Slot { public static Slot create(Class clazz) { @@ -11,6 +12,10 @@ public static Slot> createList(Class clazz) { return new Slot<>(); } + public static Slot> createSet(Class clazz) { + return new Slot<>(); + } + public SlotWrite write() { return new SlotWrite<>(this); } diff --git a/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/EnumListOfSuppliers.java b/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/EnumListOfSuppliers.java new file mode 100644 index 000000000..4a12684b8 --- /dev/null +++ b/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/EnumListOfSuppliers.java @@ -0,0 +1,21 @@ +package com.example.staticinitializers.delayedexecution; + +import java.util.List; +import java.util.function.Supplier; + +import static java.util.Arrays.asList; + +public enum EnumListOfSuppliers { + A(EnumListOfSuppliers::canMutate), B(EnumListOfSuppliers::canMutate); + + private final List> supplier; + + EnumListOfSuppliers(Supplier supplier) { + this.supplier = asList(supplier); + } + + private static String canMutate() { + return "mutate me"; // mutate + } + +} diff --git a/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/StaticListOfFunctions.java b/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/StaticListOfFunctions.java new file mode 100644 index 000000000..cea2da4c4 --- /dev/null +++ b/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/StaticListOfFunctions.java @@ -0,0 +1,19 @@ +package com.example.staticinitializers.delayedexecution; + +import java.util.function.Function; +import java.util.List; + +import static java.util.Arrays.asList; + +public class StaticListOfFunctions { + public static final List> FUNCTIONS = + asList(StaticListOfFunctions::canMutate, StaticListOfFunctions::canAlsoMutate); + + private static Integer canMutate(Integer a) { + return a + 1; + } + + private static Integer canAlsoMutate(Integer a) { + return a + 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 8822bd49d..d29dff285 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,9 +8,11 @@ import com.example.staticinitializers.NestedEnumWithLambdaInStaticInitializer; import com.example.staticinitializers.SecondLevelPrivateMethods; import com.example.staticinitializers.SingletonWithWorkInInitializer; +import com.example.staticinitializers.delayedexecution.EnumListOfSuppliers; import com.example.staticinitializers.delayedexecution.EnumMethodReferenceNotStored; import com.example.staticinitializers.delayedexecution.EnumMixedFields; import com.example.staticinitializers.delayedexecution.StaticFunctionField; +import com.example.staticinitializers.delayedexecution.StaticListOfFunctions; import com.example.staticinitializers.delayedexecution.StaticSupplierField; import com.example.staticinitializers.ThirdLevelPrivateMethods; import org.junit.Test; @@ -239,6 +241,28 @@ public void filtersMutationsForMethodReferencesUsedInEnumConstructor() { .verify(); } + @Test + public void mutatesMethodsStoredInListOfSuppliers() { + v.forClass(EnumListOfSuppliers.class) + .forMethod("canMutate") + .forAnyCode() + .mutantsAreGenerated() + .noMutantsAreFiltered() + .verify(); + } + + @Test + public void mutatesMethodsStoredInStaticListOfFunctions() { + v.forClass(StaticListOfFunctions.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); } diff --git a/pitest/src/main/java/org/pitest/bytecode/SignatureParser.java b/pitest/src/main/java/org/pitest/bytecode/SignatureParser.java new file mode 100644 index 000000000..5b9112b86 --- /dev/null +++ b/pitest/src/main/java/org/pitest/bytecode/SignatureParser.java @@ -0,0 +1,31 @@ +package org.pitest.bytecode; + +import org.objectweb.asm.signature.SignatureReader; +import org.objectweb.asm.signature.SignatureVisitor; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public class SignatureParser { + + /** + * Extracts all types referenced in a field generic signature + */ + public static Set extractTypes(String signature) { + if (signature == null) { + return Collections.emptySet(); + } + + Set types = new HashSet<>(); + SignatureReader r = new SignatureReader(signature); + SignatureVisitor visitor = new SignatureVisitor(ASMVersion.asmVersion()) { + @Override + public void visitClassType(String name) { + types.add(name); + } + }; + r.acceptType(visitor); + return types; + } +} diff --git a/pitest/src/test/java/org/pitest/bytecode/SignatureParserTest.java b/pitest/src/test/java/org/pitest/bytecode/SignatureParserTest.java new file mode 100644 index 000000000..93fc1334d --- /dev/null +++ b/pitest/src/test/java/org/pitest/bytecode/SignatureParserTest.java @@ -0,0 +1,25 @@ +package org.pitest.bytecode; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SignatureParserTest { + + @Test + public void noTypesForNullSignature() { + assertThat(SignatureParser.extractTypes(null)).isEmpty(); + } + + @Test + public void parsesSupplierOfStrings() { + String signature = "Ljava/util/function/Supplier;"; + assertThat(SignatureParser.extractTypes(signature)).containsExactlyInAnyOrder("java/util/function/Supplier", "java/lang/String"); + } + + @Test + public void parsesListsOfFunctions() { + String signature = "Ljava/util/List;>;"; + assertThat(SignatureParser.extractTypes(signature)).containsExactlyInAnyOrder("java/util/List", "java/util/function/Function", "java/lang/Integer"); + } +} \ No newline at end of file From c0a254895770e87ee6480820bd10f203c753a335 Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Thu, 29 Aug 2024 13:49:44 +0100 Subject: [PATCH 2/6] scan for functional annotation --- .../build/CompoundInterceptorFactory.java | 13 ++- .../build/InterceptorParameters.java | 15 ++- .../FunctionalInterfaceScanner.java | 30 ++++++ .../StaticInitializerInterceptor.java | 36 ++++--- .../StaticInitializerInterceptorFactory.java | 65 +++++++++---- .../tooling/MutationCoverage.java | 2 +- .../resources/jdkfunctionalinterfaces.txt | 94 +++++++++++++++++++ .../delayedexecution/CustomFunction.java | 6 ++ .../CustomFunctionNotAnnotated.java | 6 ++ .../StaticListOfFunctionalInterface.java | 19 ++++ .../StaticListOfUnannotatedInterfaces.java | 18 ++++ .../pitest/mutationtest/FixedCodeSource.java | 89 ++++++++++++++++++ .../build/InterceptorParametersTest.java | 4 +- .../build/MutationDiscoveryTest.java | 3 +- .../FunctionalInterfaceScannerTest.java | 84 +++++++++++++++++ .../StaticInitializerInterceptorTest.java | 27 +++++- .../interceptors/FactoryVerifier.java | 2 +- .../verifier/interceptors/VerifierStart.java | 8 +- 18 files changed, 479 insertions(+), 42 deletions(-) create mode 100644 pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/staticinitializers/FunctionalInterfaceScanner.java create mode 100644 pitest-entry/src/main/resources/jdkfunctionalinterfaces.txt create mode 100644 pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/CustomFunction.java create mode 100644 pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/CustomFunctionNotAnnotated.java create mode 100644 pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/StaticListOfFunctionalInterface.java create mode 100644 pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/StaticListOfUnannotatedInterfaces.java create mode 100644 pitest-entry/src/test/java/org/pitest/mutationtest/FixedCodeSource.java create mode 100644 pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/staticinitializers/FunctionalInterfaceScannerTest.java diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/CompoundInterceptorFactory.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/CompoundInterceptorFactory.java index cccc4d34a..ecab99aab 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/CompoundInterceptorFactory.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/CompoundInterceptorFactory.java @@ -1,6 +1,7 @@ package org.pitest.mutationtest.build; import org.pitest.classinfo.ClassByteArraySource; +import org.pitest.classpath.CodeSource; import org.pitest.coverage.CoverageDatabase; import org.pitest.mutationtest.config.ReportOptions; import org.pitest.plugin.FeatureSelector; @@ -24,10 +25,11 @@ public CompoundMutationInterceptor createInterceptor( ReportOptions data, CoverageDatabase coverage, ClassByteArraySource source, - TestPrioritiser testPrioritiser + TestPrioritiser testPrioritiser, + CodeSource code ) { - final List interceptors = this.features.getActiveFeatures().stream() - .map(toInterceptor(this.features, data, coverage, source, testPrioritiser)) + List interceptors = this.features.getActiveFeatures().stream() + .map(toInterceptor(this.features, data, coverage, source, testPrioritiser, code)) .collect(Collectors.toList()); return new CompoundMutationInterceptor(interceptors); } @@ -38,10 +40,11 @@ private static Function toInter ReportOptions data, CoverageDatabase coverage, ClassByteArraySource source, - TestPrioritiser testPrioritiser + TestPrioritiser testPrioritiser, + CodeSource code ) { - return a -> a.createInterceptor(new InterceptorParameters(features.getSettingForFeature(a.provides().name()), data, coverage, source, testPrioritiser)); + return a -> a.createInterceptor(new InterceptorParameters(features.getSettingForFeature(a.provides().name()), data, coverage, source, testPrioritiser, code)); } } \ No newline at end of file diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/InterceptorParameters.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/InterceptorParameters.java index 09a94fe0b..cc36e6903 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/InterceptorParameters.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/InterceptorParameters.java @@ -1,6 +1,7 @@ package org.pitest.mutationtest.build; import org.pitest.classinfo.ClassByteArraySource; +import org.pitest.classpath.CodeSource; import org.pitest.coverage.CoverageDatabase; import org.pitest.mutationtest.config.ReportOptions; import org.pitest.plugin.FeatureParameter; @@ -16,17 +17,23 @@ public final class InterceptorParameters { private final ReportOptions data; private final ClassByteArraySource source; private final CoverageDatabase coverage; + private final CodeSource code; private final TestPrioritiser testPrioritiser; - public InterceptorParameters(FeatureSetting conf, ReportOptions data, CoverageDatabase coverage, - ClassByteArraySource source, TestPrioritiser testPrioritiser) { + public InterceptorParameters(FeatureSetting conf, + ReportOptions data, + CoverageDatabase coverage, + ClassByteArraySource source, + TestPrioritiser testPrioritiser, + CodeSource code) { this.conf = conf; this.data = data; this.coverage = coverage; this.source = source; this.testPrioritiser = testPrioritiser; + this.code = code; } public ReportOptions data() { @@ -50,6 +57,10 @@ public TestPrioritiser testPrioritiser() { return this.testPrioritiser; } + public CodeSource code() { + return code; + } + public Optional getString(FeatureParameter limit) { if (this.conf == null) { return Optional.empty(); diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/staticinitializers/FunctionalInterfaceScanner.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/staticinitializers/FunctionalInterfaceScanner.java new file mode 100644 index 000000000..d777d5593 --- /dev/null +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/staticinitializers/FunctionalInterfaceScanner.java @@ -0,0 +1,30 @@ +package org.pitest.mutationtest.build.intercept.staticinitializers; + +import org.objectweb.asm.tree.AnnotationNode; +import org.pitest.bytecode.analysis.ClassTree; +import org.pitest.classpath.CodeSource; + +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class FunctionalInterfaceScanner implements Function> { + @Override + public Set apply(CodeSource codeSource) { + return codeSource.codeTrees() + .filter(this::isFunctionalInterface) + .map(c -> c.rawNode().name) + .collect(Collectors.toSet()); + } + + private boolean isFunctionalInterface(ClassTree classTree) { + List annotations = classTree.rawNode().visibleAnnotations; + if (annotations == null) { + return false; + } + + return annotations.stream() + .anyMatch(a -> a.desc.equals("Ljava/lang/FunctionalInterface;")); + } +} 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 540d20c96..28e81bcca 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 @@ -2,6 +2,7 @@ import org.objectweb.asm.Handle; import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.FieldNode; @@ -59,10 +60,12 @@ */ class StaticInitializerInterceptor implements MutationInterceptor { - static final Slot START = Slot.create(AbstractInsnNode.class); - static final Slot> DELAYED_EXECUTION_FIELDS = Slot.createSet(String.class); + private final Set delayedExecutionTypes; - static final SequenceMatcher DELAYED_EXECUTION = QueryStart + private static final Slot START = Slot.create(AbstractInsnNode.class); + private static final Slot> DELAYED_EXECUTION_FIELDS = Slot.createSet(String.class); + + private final SequenceMatcher delayedExecution = QueryStart .any(AbstractInsnNode.class) // look for calls returning delayed execution types. Unfortunately this is not guarantee that we // store the result to an appropriate type @@ -75,6 +78,10 @@ class StaticInitializerInterceptor implements MutationInterceptor { .withIgnores(notAnInstruction()) ); + StaticInitializerInterceptor(Set delayedExecutionTypes) { + this.delayedExecutionTypes = delayedExecutionTypes; + } + private static Match delayedExecutionField(SlotRead> delayedFields) { return PUTSTATIC.and(isADelayedExecutionField(delayedFields)); } @@ -86,22 +93,27 @@ private static Match isADelayedExecutionField(SlotRead dynamicallyReturnsDeferredExecutionCode() { + private Match dynamicallyReturnsDeferredExecutionCode() { return (c,n) -> result(n.getOpcode() == Opcodes.INVOKEDYNAMIC && returnsDelayedExecutionType(((InvokeDynamicInsnNode) n).desc), c); } - private static Match returnsDeferredExecutionCode() { + private Match returnsDeferredExecutionCode() { return (c,n) -> result(n.getOpcode() == Opcodes.INVOKESTATIC && returnsDelayedExecutionType(((MethodInsnNode) n).desc), c); } - private static boolean returnsDelayedExecutionType(String desc) { - int endOfParams = desc.indexOf(')'); - return endOfParams <= 0 || desc.substring(endOfParams + 1).startsWith("Ljava/util/function/"); + private boolean returnsDelayedExecutionType(String desc) { + Type returnType = Type.getReturnType(desc); + // fixme Arrays? + if (returnType.getSort() != Type.OBJECT) { + return false; + } + + return isADelayedExecutionType(returnType.getInternalName()); } - private static boolean isADelayedExecutionType(String type) { - return type.startsWith("java/util/function/"); + private boolean isADelayedExecutionType(String type) { + return delayedExecutionTypes.contains(type); } private static SequenceQuery enumConstructorCallAndStore() { @@ -172,7 +184,7 @@ private void analyseClass(ClassTree tree) { private boolean isDelayedExecutionField(FieldNode fieldNode) { return SignatureParser.extractTypes(fieldNode.signature).stream() - .anyMatch(StaticInitializerInterceptor::isADelayedExecutionType); + .anyMatch(this::isADelayedExecutionType); } private Set findCallsStoredToDelayedExecutionCode(ClassTree tree, Set delayedExecutionFields) { @@ -189,7 +201,7 @@ private Set privateAndClinitCallsToDelayedExecutionCode(ClassTree tree, Se private List delayedExecutionCall(MethodTree method, Set delayedExecutionFields) { Context context = Context.start().store(DELAYED_EXECUTION_FIELDS.write(), delayedExecutionFields); - return DELAYED_EXECUTION.contextMatches(method.instructions(), context).stream() + return delayedExecution.contextMatches(method.instructions(), context).stream() .map(c -> c.retrieve(START.read()).get()) .flatMap(this::nodeToLocation) .collect(Collectors.toList()); diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptorFactory.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptorFactory.java index afcaac29a..4062cb258 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptorFactory.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptorFactory.java @@ -1,26 +1,59 @@ package org.pitest.mutationtest.build.intercept.staticinitializers; +import org.pitest.classpath.CodeSource; import org.pitest.mutationtest.build.InterceptorParameters; import org.pitest.mutationtest.build.MutationInterceptor; import org.pitest.mutationtest.build.MutationInterceptorFactory; import org.pitest.plugin.Feature; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Function; + public class StaticInitializerInterceptorFactory implements MutationInterceptorFactory { - @Override - public String description() { - return "Static initializer code detector plugin"; - } - - @Override - public MutationInterceptor createInterceptor(InterceptorParameters params) { - return new StaticInitializerInterceptor(); - } - - @Override - public Feature provides() { - return Feature.named("FSTATI") - .withOnByDefault(true) - .withDescription("Filters mutations in static initializers and code called only from them"); - } + private final Function> delayedExecutionTypes; + + public StaticInitializerInterceptorFactory() { + this(new FunctionalInterfaceScanner()); + } + + public StaticInitializerInterceptorFactory(Function> delayedExecutionTypes) { + this.delayedExecutionTypes = delayedExecutionTypes.andThen(this::jdkFunctionalClasses); + } + + @Override + public String description() { + return "Static initializer code detector plugin"; + } + + @Override + public MutationInterceptor createInterceptor(InterceptorParameters params) { + Set types = delayedExecutionTypes.apply(params.code()); + return new StaticInitializerInterceptor(types); + } + + @Override + public Feature provides() { + return Feature.named("FSTATI") + .withOnByDefault(true) + .withDescription("Filters mutations in static initializers and code called only from them"); + } + + private Set jdkFunctionalClasses(Set existing) { + Set classes = new HashSet<>(existing); + try (BufferedReader r = new BufferedReader(new InputStreamReader(this.getClass().getResourceAsStream("/jdkfunctionalinterfaces.txt")))) { + String line = r.readLine(); + while (line != null) { + classes.add(line); + line = r.readLine(); + } + return classes; + } catch (IOException e) { + throw new RuntimeException("Could not read embedded jdk class list!"); + } + } } 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 2529233d0..648dfac90 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 @@ -337,7 +337,7 @@ private List buildMutationTests(CoverageDatabase coverageD coverageData); final MutationInterceptor interceptor = this.settings.getInterceptor() - .createInterceptor(this.data, coverageData, bas, testPrioritiser) + .createInterceptor(this.data, coverageData, bas, testPrioritiser, code) .filter(interceptorFilter); interceptor.initialise(this.code); diff --git a/pitest-entry/src/main/resources/jdkfunctionalinterfaces.txt b/pitest-entry/src/main/resources/jdkfunctionalinterfaces.txt new file mode 100644 index 000000000..8c09fd3d6 --- /dev/null +++ b/pitest-entry/src/main/resources/jdkfunctionalinterfaces.txt @@ -0,0 +1,94 @@ +java/io/FileFilter +java/io/FilenameFilter +java/io/ObjectInputFilter +java/lang/Runnable +java/lang/Thread$UncaughtExceptionHandler +java/nio/file/DirectoryStream$Filter +java/nio/file/PathMatcher +java/security/PrivilegedAction +java/security/PrivilegedExceptionAction +java/time/temporal/TemporalAdjuster +java/time/temporal/TemporalQuery +java/util/Comparator +java/util/concurrent/Callable +java/util/concurrent/Flow$Publisher +java/util/function/BiConsumer +java/util/function/BiFunction +java/util/function/BiPredicate +java/util/function/BinaryOperator +java/util/function/BooleanSupplier +java/util/function/Consumer +java/util/function/DoubleBinaryOperator +java/util/function/DoubleConsumer +java/util/function/DoubleFunction +java/util/function/DoublePredicate +java/util/function/DoubleSupplier +java/util/function/DoubleToIntFunction +java/util/function/DoubleToLongFunction +java/util/function/DoubleUnaryOperator +java/util/function/Function +java/util/function/IntBinaryOperator +java/util/function/IntConsumer +java/util/function/IntFunction +java/util/function/IntPredicate +java/util/function/IntSupplier +java/util/function/IntToDoubleFunction +java/util/function/IntToLongFunction +java/util/function/IntUnaryOperator +java/util/function/LongBinaryOperator +java/util/function/LongConsumer +java/util/function/LongFunction +java/util/function/LongPredicate +java/util/function/LongSupplier +java/util/function/LongToDoubleFunction +java/util/function/LongToIntFunction +java/util/function/LongUnaryOperator +java/util/function/ObjDoubleConsumer +java/util/function/ObjIntConsumer +java/util/function/ObjLongConsumer +java/util/function/Predicate +java/util/function/Supplier +java/util/function/ToDoubleBiFunction +java/util/function/ToDoubleFunction +java/util/function/ToIntBiFunction +java/util/function/ToIntFunction +java/util/function/ToLongBiFunction +java/util/function/ToLongFunction +java/util/function/UnaryOperator +java/util/regex/Pattern$CharPredicate +java/util/stream/DoubleStream$DoubleMapMultiConsumer +java/util/stream/IntStream$IntMapMultiConsumer +java/util/stream/LongStream$LongMapMultiConsumer +jdk/internal/access/JavaObjectInputStreamAccess +jdk/internal/access/JavaObjectInputStreamReadString +sun/net/www/protocol/http/AuthenticatorKeys$AuthenticatorKeyAccess +sun/nio/ch/MembershipRegistry$ThrowingConsumer +sun/security/pkcs12/PKCS12KeyStore$RetryWithZero +java/awt/KeyEventDispatcher +java/awt/KeyEventPostProcessor +javax/management/remote/JMXConnectorFactory$ConnectorFactory +com/sun/naming/internal/ObjectFactoriesFilter$FactoryInfo +java/util/logging/Filter +java/net/http/HttpResponse$BodyHandler +jdk/internal/net/http/common/Cancelable +jdk/internal/net/http/common/MinimalFuture$ExceptionalSupplier +jdk/internal/net/http/common/SequentialScheduler$RestartableTask +jdk/internal/net/http/frame/FramesDecoder$FrameProcessor +jdk/internal/net/http/hpack/DecodingCallback +jdk/internal/net/http/hpack/HPACK$BufferUpdateConsumer +jdk/internal/net/http/websocket/TransportFactory +java/util/prefs/PreferenceChangeListener +jdk/dynalink/beans/MissingMemberHandlerFactory +jdk/dynalink/linker/GuardedInvocationTransformer +jdk/dynalink/linker/MethodHandleTransformer +jdk/dynalink/linker/MethodTypeConversionStrategy +jdk/incubator/foreign/SegmentAllocator +jdk/incubator/foreign/SymbolLookup +jdk/internal/foreign/abi/BindingInterpreter$LoadFunc +jdk/internal/foreign/abi/BindingInterpreter$StoreFunc +jdk/internal/org/jline/reader/Widget +jdk/internal/org/jline/utils/Colors$Distance +jdk/jpackage/internal/AppImageBundler$ParamsValidator +jdk/jpackage/internal/IOUtils$XmlConsumer +jdk/jpackage/internal/LibProvidersLookup$PackageLookup +jdk/jpackage/internal/OverridableResource$SourceHandler \ No newline at end of file diff --git a/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/CustomFunction.java b/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/CustomFunction.java new file mode 100644 index 000000000..1bd897418 --- /dev/null +++ b/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/CustomFunction.java @@ -0,0 +1,6 @@ +package com.example.staticinitializers.delayedexecution; + +@FunctionalInterface +public interface CustomFunction { + R apply(T t); +} \ No newline at end of file diff --git a/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/CustomFunctionNotAnnotated.java b/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/CustomFunctionNotAnnotated.java new file mode 100644 index 000000000..4aad1e7ed --- /dev/null +++ b/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/CustomFunctionNotAnnotated.java @@ -0,0 +1,6 @@ +package com.example.staticinitializers.delayedexecution; + +// NOT annotated as a functional interface +public interface CustomFunctionNotAnnotated { + R apply(T t); +} \ No newline at end of file diff --git a/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/StaticListOfFunctionalInterface.java b/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/StaticListOfFunctionalInterface.java new file mode 100644 index 000000000..dac8bb08f --- /dev/null +++ b/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/StaticListOfFunctionalInterface.java @@ -0,0 +1,19 @@ +package com.example.staticinitializers.delayedexecution; + +import java.util.List; + +import static java.util.Arrays.asList; + +public class StaticListOfFunctionalInterface { + public static final List> FUNCTIONS = + asList(StaticListOfFunctionalInterface::canMutate, StaticListOfFunctionalInterface::canAlsoMutate); + + private static Integer canMutate(Integer a) { + return a + 1; + } + + private static Integer canAlsoMutate(Integer a) { + return a + 2; + } +} + diff --git a/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/StaticListOfUnannotatedInterfaces.java b/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/StaticListOfUnannotatedInterfaces.java new file mode 100644 index 000000000..ccd220eb8 --- /dev/null +++ b/pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/StaticListOfUnannotatedInterfaces.java @@ -0,0 +1,18 @@ +package com.example.staticinitializers.delayedexecution; + +import java.util.List; + +import static java.util.Arrays.asList; + +public class StaticListOfUnannotatedInterfaces { + public static final List> FUNCTIONS = + asList(StaticListOfUnannotatedInterfaces::canMutate, StaticListOfUnannotatedInterfaces::canAlsoMutate); + + private static Integer canMutate(Integer a) { + return a + 1; + } + + private static Integer canAlsoMutate(Integer a) { + return a + 2; + } + } \ No newline at end of file diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/FixedCodeSource.java b/pitest-entry/src/test/java/org/pitest/mutationtest/FixedCodeSource.java new file mode 100644 index 000000000..75dd7ed59 --- /dev/null +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/FixedCodeSource.java @@ -0,0 +1,89 @@ +package org.pitest.mutationtest; + +import org.pitest.bytecode.analysis.ClassTree; +import org.pitest.classinfo.ClassHash; +import org.pitest.classinfo.ClassName; +import org.pitest.classpath.ClassPath; +import org.pitest.classpath.ClassloaderByteArraySource; +import org.pitest.classpath.CodeSource; +import org.pitest.util.IsolationUtils; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class FixedCodeSource implements CodeSource { + + private final List classes; + + public FixedCodeSource(Class ... classes) { + this(Arrays.stream(classes) + .map(FixedCodeSource::toTree).collect(Collectors.toList())); + } + + public FixedCodeSource(List classes) { + this.classes = classes; + } + + private static ClassTree toTree(Class aClass) { + ClassloaderByteArraySource cba = new ClassloaderByteArraySource(IsolationUtils.getContextClassLoader()); + return ClassTree.fromBytes(cba.getBytes(aClass.getName()).get()); + } + + @Override + public Stream codeTrees() { + return classes.stream(); + } + + @Override + public Set getCodeUnderTestNames() { + return classes.stream() + .map(ClassTree::name) + .collect(Collectors.toSet()); + } + + @Override + public Set getTestClassNames() { + return Collections.emptySet(); + } + + @Override + public Stream testTrees() { + return Stream.empty(); + } + + @Override + public ClassPath getClassPath() { + throw new UnsupportedOperationException(); + } + + @Override + public Optional findTestee(String className) { + throw new UnsupportedOperationException(); + } + + @Override + public Optional fetchClassBytes(ClassName clazz) { + throw new UnsupportedOperationException(); + } + + @Override + public Optional fetchClassHash(ClassName clazz) { + throw new UnsupportedOperationException(); + } + + @Override + public Collection fetchClassHashes(Collection classes) { + throw new UnsupportedOperationException(); + } + + @Override + public Optional getBytes(String clazz) { + throw new UnsupportedOperationException(); + } +} diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/build/InterceptorParametersTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/build/InterceptorParametersTest.java index 96a8179e6..9f6128cce 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/build/InterceptorParametersTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/build/InterceptorParametersTest.java @@ -30,7 +30,7 @@ public void shouldReturnNoneWhenValueAbsent() { @Test public void shouldReturnNoneWhenFeatureSettingsAbsent() { - this.testee = new InterceptorParameters(null, null, null, null, null); + this.testee = new InterceptorParameters(null, null, null, null, null, null); assertThat(this.testee.getString(FeatureParameter.named("foo"))).isEqualTo(Optional.empty()); } @@ -50,7 +50,7 @@ private InterceptorParameters makeFor(String key, String ... vals) { final Map> values = new HashMap<>(); values.put(key, Arrays.asList(vals)); final FeatureSetting fs = new FeatureSetting(null, null, values); - return new InterceptorParameters(fs, null, null,null, null); + return new InterceptorParameters(fs, null, null,null, null, null); } } diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/build/MutationDiscoveryTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/build/MutationDiscoveryTest.java index 1fda5c933..8f1009bc8 100755 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/build/MutationDiscoveryTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/build/MutationDiscoveryTest.java @@ -6,6 +6,7 @@ import org.pitest.classinfo.ClassName; import org.pitest.classpath.ClassloaderByteArraySource; import org.pitest.mutationtest.EngineArguments; +import org.pitest.mutationtest.FixedCodeSource; import org.pitest.mutationtest.MutationConfig; import org.pitest.mutationtest.build.intercept.javafeatures.AnEnum; import org.pitest.mutationtest.build.intercept.javafeatures.ForEachFilterTest.HasForEachLoop; @@ -277,7 +278,7 @@ MutationSource createSource(ClassByteArraySource source) { final SettingsFactory settings = new SettingsFactory(this.data, PluginServices.makeForContextLoader()); final MutationInterceptor interceptor = settings.getInterceptor() - .createInterceptor(this.data, null, source, null); + .createInterceptor(this.data, null, source, null, new FixedCodeSource()); final MutationEngine engine = new GregorEngineFactory().createEngine( EngineArguments.arguments().withExcludedMethods(this.data.getExcludedMethods()) diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/staticinitializers/FunctionalInterfaceScannerTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/staticinitializers/FunctionalInterfaceScannerTest.java new file mode 100644 index 000000000..85b889a97 --- /dev/null +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/staticinitializers/FunctionalInterfaceScannerTest.java @@ -0,0 +1,84 @@ +package org.pitest.mutationtest.build.intercept.staticinitializers; + +import org.junit.Test; +import org.pitest.bytecode.analysis.ClassTree; +import org.pitest.classinfo.ClassByteArraySource; +import org.pitest.classinfo.ClassHash; +import org.pitest.classinfo.ClassName; +import org.pitest.classpath.ClassPath; +import org.pitest.classpath.ClassloaderByteArraySource; +import org.pitest.classpath.CodeSource; +import org.pitest.mutationtest.FixedCodeSource; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; + + +public class FunctionalInterfaceScannerTest { + + private final ClassByteArraySource source = ClassloaderByteArraySource.fromContext(); + + FunctionalInterfaceScanner underTest = new FunctionalInterfaceScanner(); + + @Test + public void recognisesFunctionalInterfaces() { + ClassTree functional = ClassTree.fromBytes(bytesFor(Function.class)); + ClassTree not = ClassTree.fromBytes(bytesFor(String.class)); + + assertThat(underTest.apply(codeSourceFor(functional, not))).containsExactly("java/util/function/Function"); + } + + /* not a test + @Test + public void wholeClassPath() throws Exception { + ClassPath cp = new ClassPath(); + List classes = cp.classNames().stream() + .map(n -> source.getBytes(n)) + .map(b -> ClassTree.fromBytes(b.get())) + .flatMap(t -> underTest.apply(codeSourceFor(t)).stream()) + .collect(Collectors.toList()); + } + */ + + /* + // not a test and only works with java 9+ + public void crudelyScanJdk() throws Exception { + FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/")); + Path modules = fs.getPath(""); + + List jdkClasses = Files.walk(modules, FileVisitOption.FOLLOW_LINKS) + .filter(f -> f.getFileName().toString().endsWith(".class")) + .map(this::toBytes) + .map(ClassTree::fromBytes) + .flatMap(t -> underTest.apply(codeSourceFor(t)).stream()) + .distinct() + .collect(Collectors.toList()); + + + System.out.println(jdkClasses.stream() + .collect(Collectors.joining("\n"))); + }*/ + + private byte[] toBytes(Path p) { + try { + return Files.readAllBytes(p); + } catch (IOException x) { + throw new RuntimeException(x); + } + } + + private CodeSource codeSourceFor(ClassTree... classes) { + return new FixedCodeSource(asList(classes)); + } + + byte[] bytesFor(Class clazz) { + return this.source.getBytes(clazz.getName()).get(); + } +} \ No newline at end of file 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 d29dff285..83dab502b 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,6 +1,8 @@ package org.pitest.mutationtest.build.intercept.staticinitializers; import com.example.staticinitializers.BrokenChain; +import com.example.staticinitializers.delayedexecution.CustomFunction; +import com.example.staticinitializers.delayedexecution.CustomFunctionNotAnnotated; import com.example.staticinitializers.delayedexecution.EnumFieldMethodRef; import com.example.staticinitializers.delayedexecution.EnumFieldSupplier; import com.example.staticinitializers.EnumWithLambdaInConstructor; @@ -12,10 +14,13 @@ import com.example.staticinitializers.delayedexecution.EnumMethodReferenceNotStored; import com.example.staticinitializers.delayedexecution.EnumMixedFields; import com.example.staticinitializers.delayedexecution.StaticFunctionField; +import com.example.staticinitializers.delayedexecution.StaticListOfFunctionalInterface; import com.example.staticinitializers.delayedexecution.StaticListOfFunctions; +import com.example.staticinitializers.delayedexecution.StaticListOfUnannotatedInterfaces; import com.example.staticinitializers.delayedexecution.StaticSupplierField; import com.example.staticinitializers.ThirdLevelPrivateMethods; import org.junit.Test; +import org.pitest.mutationtest.FixedCodeSource; import org.pitest.mutationtest.engine.MutationDetails; import org.pitest.mutationtest.engine.gregor.mutators.NullMutateEverything; import org.pitest.verifier.interceptors.InterceptorVerifier; @@ -25,7 +30,8 @@ public class StaticInitializerInterceptorTest { - InterceptorVerifier v = VerifierStart.forInterceptorFactory(new StaticInitializerInterceptorFactory()) + InterceptorVerifier v = VerifierStart.forInterceptorFactory(new StaticInitializerInterceptorFactory() + , new FixedCodeSource(CustomFunction.class, CustomFunctionNotAnnotated.class)) .usingMutator(new NullMutateEverything()); @@ -261,6 +267,25 @@ public void mutatesMethodsStoredInStaticListOfFunctions() { .verify(); } + @Test + public void recognisesCustomFunctionalInterfaces() { + v.forClass(StaticListOfFunctionalInterface.class) + .forMethod("canMutate") + .forAnyCode() + .mutantsAreGenerated() + .noMutantsAreFiltered() + .verify(); + } + + @Test + public void stillMutatesIfClassUsedAsFunctionIsntAnnotated() { + v.forClass(StaticListOfUnannotatedInterfaces.class) + .forMethod("canMutate") + .forAnyCode() + .mutantsAreGenerated() + .allMutantsAreFiltered() + .verify(); + } private Predicate inMethod(String name, String desc) { diff --git a/pitest-entry/src/test/java/org/pitest/verifier/interceptors/FactoryVerifier.java b/pitest-entry/src/test/java/org/pitest/verifier/interceptors/FactoryVerifier.java index ba7134a36..1e9762855 100644 --- a/pitest-entry/src/test/java/org/pitest/verifier/interceptors/FactoryVerifier.java +++ b/pitest-entry/src/test/java/org/pitest/verifier/interceptors/FactoryVerifier.java @@ -64,7 +64,7 @@ private static void factoryIsOnChain(Class factory) { } public static InterceptorParameters emptyParams(ReportOptions data) { - return new InterceptorParameters(null, data, null, null, null); + return new InterceptorParameters(null, data, null, null, null, null); } public static ReportOptions emptyOptions() { diff --git a/pitest-entry/src/test/java/org/pitest/verifier/interceptors/VerifierStart.java b/pitest-entry/src/test/java/org/pitest/verifier/interceptors/VerifierStart.java index a7e18b7d5..14032e35c 100644 --- a/pitest-entry/src/test/java/org/pitest/verifier/interceptors/VerifierStart.java +++ b/pitest-entry/src/test/java/org/pitest/verifier/interceptors/VerifierStart.java @@ -1,5 +1,7 @@ package org.pitest.verifier.interceptors; +import org.pitest.classpath.CodeSource; +import org.pitest.mutationtest.build.InterceptorParameters; import org.pitest.mutationtest.build.MutationInterceptor; import org.pitest.mutationtest.build.MutationInterceptorFactory; @@ -10,7 +12,11 @@ public static InterceptorVerifier forInterceptor(MutationInterceptor interceptor } public static InterceptorVerifier forInterceptorFactory(MutationInterceptorFactory f) { - return new InterceptorVerifier(f.createInterceptor(null)); + return new InterceptorVerifier(f.createInterceptor( new InterceptorParameters(null, null, null, null, null, null))); + } + + public static InterceptorVerifier forInterceptorFactory(MutationInterceptorFactory f, CodeSource code) { + return new InterceptorVerifier(f.createInterceptor( new InterceptorParameters(null, null, null, null, null, code))); } } From a7d9d9060a5267c93a4e58bd3de7d9196959e773 Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Thu, 29 Aug 2024 21:16:32 +0100 Subject: [PATCH 3/6] expand list of recognised function types --- .../StaticInitializerInterceptorFactory.java | 8 +- .../main/resources/functional_interfaces.txt | 133 ++++++++++++++++++ .../resources/jdkfunctionalinterfaces.txt | 94 ------------- .../FunctionalInterfaceScannerTest.java | 15 +- 4 files changed, 145 insertions(+), 105 deletions(-) create mode 100644 pitest-entry/src/main/resources/functional_interfaces.txt delete mode 100644 pitest-entry/src/main/resources/jdkfunctionalinterfaces.txt diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptorFactory.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptorFactory.java index 4062cb258..828444943 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptorFactory.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptorFactory.java @@ -22,7 +22,7 @@ public StaticInitializerInterceptorFactory() { } public StaticInitializerInterceptorFactory(Function> delayedExecutionTypes) { - this.delayedExecutionTypes = delayedExecutionTypes.andThen(this::jdkFunctionalClasses); + this.delayedExecutionTypes = delayedExecutionTypes.andThen(this::functionalInterfaces); } @Override @@ -43,9 +43,9 @@ public Feature provides() { .withDescription("Filters mutations in static initializers and code called only from them"); } - private Set jdkFunctionalClasses(Set existing) { + private Set functionalInterfaces(Set existing) { Set classes = new HashSet<>(existing); - try (BufferedReader r = new BufferedReader(new InputStreamReader(this.getClass().getResourceAsStream("/jdkfunctionalinterfaces.txt")))) { + try (BufferedReader r = new BufferedReader(new InputStreamReader(this.getClass().getResourceAsStream("/functional_interfaces.txt")))) { String line = r.readLine(); while (line != null) { classes.add(line); @@ -53,7 +53,7 @@ private Set jdkFunctionalClasses(Set existing) { } return classes; } catch (IOException e) { - throw new RuntimeException("Could not read embedded jdk class list!"); + throw new RuntimeException("Could not read embedded list of functional interfaces!"); } } } diff --git a/pitest-entry/src/main/resources/functional_interfaces.txt b/pitest-entry/src/main/resources/functional_interfaces.txt new file mode 100644 index 000000000..4240ce000 --- /dev/null +++ b/pitest-entry/src/main/resources/functional_interfaces.txt @@ -0,0 +1,133 @@ +java/io/FileFilter +java/io/FilenameFilter +java/io/ObjectInputFilter +java/lang/Runnable +java/util/Comparator +java/util/concurrent/Callable +java/util/function/BiConsumer +java/util/function/BiFunction +java/util/function/BiPredicate +java/util/function/BinaryOperator +java/util/function/BooleanSupplier +java/util/function/Consumer +java/util/function/DoubleBinaryOperator +java/util/function/DoubleConsumer +java/util/function/DoubleFunction +java/util/function/DoublePredicate +java/util/function/DoubleSupplier +java/util/function/DoubleToIntFunction +java/util/function/DoubleToLongFunction +java/util/function/DoubleUnaryOperator +java/util/function/Function +java/util/function/IntBinaryOperator +java/util/function/IntConsumer +java/util/function/IntFunction +java/util/function/IntPredicate +java/util/function/IntSupplier +java/util/function/IntToDoubleFunction +java/util/function/IntToLongFunction +java/util/function/IntUnaryOperator +java/util/function/LongBinaryOperator +java/util/function/LongConsumer +java/util/function/LongFunction +java/util/function/LongPredicate +java/util/function/LongSupplier +java/util/function/LongToDoubleFunction +java/util/function/LongToIntFunction +java/util/function/LongUnaryOperator +java/util/function/ObjDoubleConsumer +java/util/function/ObjIntConsumer +java/util/function/ObjLongConsumer +java/util/function/Predicate +java/util/function/Supplier +java/util/function/ToDoubleBiFunction +java/util/function/ToDoubleFunction +java/util/function/ToIntBiFunction +java/util/function/ToIntFunction +java/util/function/ToLongBiFunction +java/util/function/ToLongFunction +java/util/function/UnaryOperator +java/util/stream/DoubleStream$DoubleMapMultiConsumer +java/util/stream/IntStream$IntMapMultiConsumer +java/util/stream/LongStream$LongMapMultiConsumer +org/pitest/sequence/Match +org/apache/commons/text/lookup/BiStringLookup +org/apache/commons/text/lookup/StringLookup +org/apache/commons/lang3/Functions$FailableBiConsumer +org/apache/commons/lang3/Functions$FailableBiFunction +org/apache/commons/lang3/Functions$FailableBiPredicate +org/apache/commons/lang3/Functions$FailableCallable +org/apache/commons/lang3/Functions$FailableConsumer +org/apache/commons/lang3/Functions$FailableFunction +org/apache/commons/lang3/Functions$FailablePredicate +org/apache/commons/lang3/Functions$FailableRunnable +org/apache/commons/lang3/Functions$FailableSupplier +org/apache/commons/lang3/ThreadUtils$ThreadGroupPredicate +org/apache/commons/lang3/ThreadUtils$ThreadPredicate +org/apache/commons/lang3/function/FailableBiConsumer +org/apache/commons/lang3/function/FailableBiFunction +org/apache/commons/lang3/function/FailableBiPredicate +org/apache/commons/lang3/function/FailableBooleanSupplier +org/apache/commons/lang3/function/FailableCallable +org/apache/commons/lang3/function/FailableConsumer +org/apache/commons/lang3/function/FailableDoubleBinaryOperator +org/apache/commons/lang3/function/FailableDoubleConsumer +org/apache/commons/lang3/function/FailableDoubleFunction +org/apache/commons/lang3/function/FailableDoublePredicate +org/apache/commons/lang3/function/FailableDoubleSupplier +org/apache/commons/lang3/function/FailableDoubleToIntFunction +org/apache/commons/lang3/function/FailableDoubleToLongFunction +org/apache/commons/lang3/function/FailableFunction +org/apache/commons/lang3/function/FailableIntBinaryOperator +org/apache/commons/lang3/function/FailableIntConsumer +org/apache/commons/lang3/function/FailableIntFunction +org/apache/commons/lang3/function/FailableIntPredicate +org/apache/commons/lang3/function/FailableIntSupplier +org/apache/commons/lang3/function/FailableIntToDoubleFunction +org/apache/commons/lang3/function/FailableIntToLongFunction +org/apache/commons/lang3/function/FailableLongBinaryOperator +org/apache/commons/lang3/function/FailableLongConsumer +org/apache/commons/lang3/function/FailableLongFunction +org/apache/commons/lang3/function/FailableLongPredicate +org/apache/commons/lang3/function/FailableLongSupplier +org/apache/commons/lang3/function/FailableLongToDoubleFunction +org/apache/commons/lang3/function/FailableLongToIntFunction +org/apache/commons/lang3/function/FailableObjDoubleConsumer +org/apache/commons/lang3/function/FailableObjIntConsumer +org/apache/commons/lang3/function/FailableObjLongConsumer +org/apache/commons/lang3/function/FailablePredicate +org/apache/commons/lang3/function/FailableRunnable +org/apache/commons/lang3/function/FailableShortSupplier +org/apache/commons/lang3/function/FailableSupplier +org/apache/commons/lang3/function/FailableToDoubleBiFunction +org/apache/commons/lang3/function/FailableToDoubleFunction +org/apache/commons/lang3/function/FailableToIntBiFunction +org/apache/commons/lang3/function/FailableToIntFunction +org/apache/commons/lang3/function/FailableToLongBiFunction +org/apache/commons/lang3/function/FailableToLongFunction +org/apache/commons/lang3/function/ToBooleanBiFunction +org/apache/commons/lang3/function/TriFunction +com/google/errorprone/matchers/Matcher +com/google/common/base/Function +com/google/common/base/Predicate +com/google/common/base/Supplier +com/google/common/cache/RemovalListener +com/google/common/cache/Weigher +com/google/common/collect/Maps$EntryTransformer +com/google/common/util/concurrent/AsyncCallable +com/google/common/util/concurrent/AsyncFunction +com/google/common/util/concurrent/ClosingFuture$AsyncClosingCallable +com/google/common/util/concurrent/ClosingFuture$AsyncClosingFunction +com/google/common/util/concurrent/ClosingFuture$ClosingCallable +com/google/common/util/concurrent/ClosingFuture$ClosingFunction +com/google/common/util/concurrent/ClosingFuture$Combiner$AsyncCombiningCallable +com/google/common/util/concurrent/ClosingFuture$Combiner$CombiningCallable +com/google/common/util/concurrent/ClosingFuture$Combiner2$AsyncClosingFunction2 +com/google/common/util/concurrent/ClosingFuture$Combiner2$ClosingFunction2 +com/google/common/util/concurrent/ClosingFuture$Combiner3$AsyncClosingFunction3 +com/google/common/util/concurrent/ClosingFuture$Combiner3$ClosingFunction3 +com/google/common/util/concurrent/ClosingFuture$Combiner4$AsyncClosingFunction4 +com/google/common/util/concurrent/ClosingFuture$Combiner4$ClosingFunction4 +com/google/common/util/concurrent/ClosingFuture$Combiner5$AsyncClosingFunction5 +com/google/common/util/concurrent/ClosingFuture$Combiner5$ClosingFunction5 +com/google/common/util/concurrent/ClosingFuture$ValueAndCloserConsumer \ No newline at end of file diff --git a/pitest-entry/src/main/resources/jdkfunctionalinterfaces.txt b/pitest-entry/src/main/resources/jdkfunctionalinterfaces.txt deleted file mode 100644 index 8c09fd3d6..000000000 --- a/pitest-entry/src/main/resources/jdkfunctionalinterfaces.txt +++ /dev/null @@ -1,94 +0,0 @@ -java/io/FileFilter -java/io/FilenameFilter -java/io/ObjectInputFilter -java/lang/Runnable -java/lang/Thread$UncaughtExceptionHandler -java/nio/file/DirectoryStream$Filter -java/nio/file/PathMatcher -java/security/PrivilegedAction -java/security/PrivilegedExceptionAction -java/time/temporal/TemporalAdjuster -java/time/temporal/TemporalQuery -java/util/Comparator -java/util/concurrent/Callable -java/util/concurrent/Flow$Publisher -java/util/function/BiConsumer -java/util/function/BiFunction -java/util/function/BiPredicate -java/util/function/BinaryOperator -java/util/function/BooleanSupplier -java/util/function/Consumer -java/util/function/DoubleBinaryOperator -java/util/function/DoubleConsumer -java/util/function/DoubleFunction -java/util/function/DoublePredicate -java/util/function/DoubleSupplier -java/util/function/DoubleToIntFunction -java/util/function/DoubleToLongFunction -java/util/function/DoubleUnaryOperator -java/util/function/Function -java/util/function/IntBinaryOperator -java/util/function/IntConsumer -java/util/function/IntFunction -java/util/function/IntPredicate -java/util/function/IntSupplier -java/util/function/IntToDoubleFunction -java/util/function/IntToLongFunction -java/util/function/IntUnaryOperator -java/util/function/LongBinaryOperator -java/util/function/LongConsumer -java/util/function/LongFunction -java/util/function/LongPredicate -java/util/function/LongSupplier -java/util/function/LongToDoubleFunction -java/util/function/LongToIntFunction -java/util/function/LongUnaryOperator -java/util/function/ObjDoubleConsumer -java/util/function/ObjIntConsumer -java/util/function/ObjLongConsumer -java/util/function/Predicate -java/util/function/Supplier -java/util/function/ToDoubleBiFunction -java/util/function/ToDoubleFunction -java/util/function/ToIntBiFunction -java/util/function/ToIntFunction -java/util/function/ToLongBiFunction -java/util/function/ToLongFunction -java/util/function/UnaryOperator -java/util/regex/Pattern$CharPredicate -java/util/stream/DoubleStream$DoubleMapMultiConsumer -java/util/stream/IntStream$IntMapMultiConsumer -java/util/stream/LongStream$LongMapMultiConsumer -jdk/internal/access/JavaObjectInputStreamAccess -jdk/internal/access/JavaObjectInputStreamReadString -sun/net/www/protocol/http/AuthenticatorKeys$AuthenticatorKeyAccess -sun/nio/ch/MembershipRegistry$ThrowingConsumer -sun/security/pkcs12/PKCS12KeyStore$RetryWithZero -java/awt/KeyEventDispatcher -java/awt/KeyEventPostProcessor -javax/management/remote/JMXConnectorFactory$ConnectorFactory -com/sun/naming/internal/ObjectFactoriesFilter$FactoryInfo -java/util/logging/Filter -java/net/http/HttpResponse$BodyHandler -jdk/internal/net/http/common/Cancelable -jdk/internal/net/http/common/MinimalFuture$ExceptionalSupplier -jdk/internal/net/http/common/SequentialScheduler$RestartableTask -jdk/internal/net/http/frame/FramesDecoder$FrameProcessor -jdk/internal/net/http/hpack/DecodingCallback -jdk/internal/net/http/hpack/HPACK$BufferUpdateConsumer -jdk/internal/net/http/websocket/TransportFactory -java/util/prefs/PreferenceChangeListener -jdk/dynalink/beans/MissingMemberHandlerFactory -jdk/dynalink/linker/GuardedInvocationTransformer -jdk/dynalink/linker/MethodHandleTransformer -jdk/dynalink/linker/MethodTypeConversionStrategy -jdk/incubator/foreign/SegmentAllocator -jdk/incubator/foreign/SymbolLookup -jdk/internal/foreign/abi/BindingInterpreter$LoadFunc -jdk/internal/foreign/abi/BindingInterpreter$StoreFunc -jdk/internal/org/jline/reader/Widget -jdk/internal/org/jline/utils/Colors$Distance -jdk/jpackage/internal/AppImageBundler$ParamsValidator -jdk/jpackage/internal/IOUtils$XmlConsumer -jdk/jpackage/internal/LibProvidersLookup$PackageLookup -jdk/jpackage/internal/OverridableResource$SourceHandler \ No newline at end of file diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/staticinitializers/FunctionalInterfaceScannerTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/staticinitializers/FunctionalInterfaceScannerTest.java index 85b889a97..aef14f206 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/staticinitializers/FunctionalInterfaceScannerTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/staticinitializers/FunctionalInterfaceScannerTest.java @@ -3,9 +3,6 @@ import org.junit.Test; import org.pitest.bytecode.analysis.ClassTree; import org.pitest.classinfo.ClassByteArraySource; -import org.pitest.classinfo.ClassHash; -import org.pitest.classinfo.ClassName; -import org.pitest.classpath.ClassPath; import org.pitest.classpath.ClassloaderByteArraySource; import org.pitest.classpath.CodeSource; import org.pitest.mutationtest.FixedCodeSource; @@ -13,9 +10,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.List; import java.util.function.Function; -import java.util.stream.Collectors; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; @@ -35,17 +30,23 @@ public void recognisesFunctionalInterfaces() { assertThat(underTest.apply(codeSourceFor(functional, not))).containsExactly("java/util/function/Function"); } - /* not a test + + /* @Test public void wholeClassPath() throws Exception { ClassPath cp = new ClassPath(); List classes = cp.classNames().stream() .map(n -> source.getBytes(n)) .map(b -> ClassTree.fromBytes(b.get())) + .filter(ClassTree::isInterface) .flatMap(t -> underTest.apply(codeSourceFor(t)).stream()) + .distinct() .collect(Collectors.toList()); + + System.out.println(classes.stream() + .collect(Collectors.joining("\n"))); } - */ +*/ /* // not a test and only works with java 9+ From 4c86cb4214cb9a0a57dc2d6dc5c5b831c1e64901 Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Fri, 30 Aug 2024 10:50:51 +0100 Subject: [PATCH 4/6] add kotlin function types --- .../main/resources/functional_interfaces.txt | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/pitest-entry/src/main/resources/functional_interfaces.txt b/pitest-entry/src/main/resources/functional_interfaces.txt index 4240ce000..25eee8bf9 100644 --- a/pitest-entry/src/main/resources/functional_interfaces.txt +++ b/pitest-entry/src/main/resources/functional_interfaces.txt @@ -130,4 +130,29 @@ com/google/common/util/concurrent/ClosingFuture$Combiner4$AsyncClosingFunction4 com/google/common/util/concurrent/ClosingFuture$Combiner4$ClosingFunction4 com/google/common/util/concurrent/ClosingFuture$Combiner5$AsyncClosingFunction5 com/google/common/util/concurrent/ClosingFuture$Combiner5$ClosingFunction5 -com/google/common/util/concurrent/ClosingFuture$ValueAndCloserConsumer \ No newline at end of file +com/google/common/util/concurrent/ClosingFuture$ValueAndCloserConsumer +kotlin/Function +kotlin/jvm/functions/Function0 +kotlin/jvm/functions/Function1 +kotlin/jvm/functions/Function10 +kotlin/jvm/functions/Function11 +kotlin/jvm/functions/Function12 +kotlin/jvm/functions/Function13 +kotlin/jvm/functions/Function14 +kotlin/jvm/functions/Function15 +kotlin/jvm/functions/Function16 +kotlin/jvm/functions/Function17 +kotlin/jvm/functions/Function18 +kotlin/jvm/functions/Function19 +kotlin/jvm/functions/Function2 +kotlin/jvm/functions/Function20 +kotlin/jvm/functions/Function21 +kotlin/jvm/functions/Function22 +kotlin/jvm/functions/Function3 +kotlin/jvm/functions/Function4 +kotlin/jvm/functions/Function5 +kotlin/jvm/functions/Function6 +kotlin/jvm/functions/Function7 +kotlin/jvm/functions/Function8 +kotlin/jvm/functions/Function9 +kotlin/jvm/functions/FunctionN From 376d38672b91b389358ec80c83c8527999158fdf Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Fri, 30 Aug 2024 12:31:35 +0100 Subject: [PATCH 5/6] fix comment and remove unneccesary logging of common scenario --- .../org/pitest/mutationtest/tooling/MutationCoverage.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) 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 648dfac90..c776404a5 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 @@ -65,7 +65,6 @@ import java.util.Optional; import java.util.Set; import java.util.function.Predicate; -import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -377,9 +376,8 @@ private CoverageGenerator coverage() { return this.strategies.coverage(); } - // For reasons not yet understood classes from rt.jar are not resolved for some - // projects during static analysis phase. For now fall back to the classloader when - // a class not provided by project classpath + // Since java 9 rt.jar is no longer on the classpath so jdk classes will not resolve from + // the filesystem and must be pulled out via the classloader private ClassByteArraySource fallbackToClassLoader(final ClassByteArraySource bas) { final ClassByteArraySource clSource = ClassloaderByteArraySource.fromContext(); return clazz -> { @@ -387,7 +385,6 @@ private ClassByteArraySource fallbackToClassLoader(final ClassByteArraySource ba if (maybeBytes.isPresent()) { return maybeBytes; } - LOG.log(Level.FINE, "Could not find " + clazz + " on classpath for analysis. Falling back to classloader"); return clSource.getBytes(clazz); }; } From b3747367dc7803027dbec4f29ce528f0c83fdade Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Fri, 30 Aug 2024 15:31:13 +0100 Subject: [PATCH 6/6] disable functional interface scanning --- .../StaticInitializerInterceptorFactory.java | 19 +++---------------- .../StaticInitializerInterceptorTest.java | 2 ++ 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptorFactory.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptorFactory.java index 828444943..2057f010c 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptorFactory.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptorFactory.java @@ -1,6 +1,5 @@ package org.pitest.mutationtest.build.intercept.staticinitializers; -import org.pitest.classpath.CodeSource; import org.pitest.mutationtest.build.InterceptorParameters; import org.pitest.mutationtest.build.MutationInterceptor; import org.pitest.mutationtest.build.MutationInterceptorFactory; @@ -11,20 +10,9 @@ import java.io.InputStreamReader; import java.util.HashSet; import java.util.Set; -import java.util.function.Function; public class StaticInitializerInterceptorFactory implements MutationInterceptorFactory { - private final Function> delayedExecutionTypes; - - public StaticInitializerInterceptorFactory() { - this(new FunctionalInterfaceScanner()); - } - - public StaticInitializerInterceptorFactory(Function> delayedExecutionTypes) { - this.delayedExecutionTypes = delayedExecutionTypes.andThen(this::functionalInterfaces); - } - @Override public String description() { return "Static initializer code detector plugin"; @@ -32,8 +20,7 @@ public String description() { @Override public MutationInterceptor createInterceptor(InterceptorParameters params) { - Set types = delayedExecutionTypes.apply(params.code()); - return new StaticInitializerInterceptor(types); + return new StaticInitializerInterceptor(functionalInterfaces()); } @Override @@ -43,8 +30,8 @@ public Feature provides() { .withDescription("Filters mutations in static initializers and code called only from them"); } - private Set functionalInterfaces(Set existing) { - Set classes = new HashSet<>(existing); + private Set functionalInterfaces() { + Set classes = new HashSet<>(); try (BufferedReader r = new BufferedReader(new InputStreamReader(this.getClass().getResourceAsStream("/functional_interfaces.txt")))) { String line = r.readLine(); while (line != null) { 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 83dab502b..3bea6dcd7 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 @@ -19,6 +19,7 @@ import com.example.staticinitializers.delayedexecution.StaticListOfUnannotatedInterfaces; import com.example.staticinitializers.delayedexecution.StaticSupplierField; import com.example.staticinitializers.ThirdLevelPrivateMethods; +import org.junit.Ignore; import org.junit.Test; import org.pitest.mutationtest.FixedCodeSource; import org.pitest.mutationtest.engine.MutationDetails; @@ -268,6 +269,7 @@ public void mutatesMethodsStoredInStaticListOfFunctions() { } @Test + @Ignore("functionality disabled, may get re-introduced") public void recognisesCustomFunctionalInterfaces() { v.forClass(StaticListOfFunctionalInterface.class) .forMethod("canMutate")