From c0a254895770e87ee6480820bd10f203c753a335 Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Thu, 29 Aug 2024 13:49:44 +0100 Subject: [PATCH] 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))); } }