Skip to content

Commit

Permalink
scan for functional annotation
Browse files Browse the repository at this point in the history
  • Loading branch information
hcoles committed Aug 29, 2024
1 parent 45d9f34 commit c0a2548
Show file tree
Hide file tree
Showing 18 changed files with 479 additions and 42 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -24,10 +25,11 @@ public CompoundMutationInterceptor createInterceptor(
ReportOptions data,
CoverageDatabase coverage,
ClassByteArraySource source,
TestPrioritiser testPrioritiser
TestPrioritiser testPrioritiser,
CodeSource code
) {
final List<MutationInterceptor> interceptors = this.features.getActiveFeatures().stream()
.map(toInterceptor(this.features, data, coverage, source, testPrioritiser))
List<MutationInterceptor> interceptors = this.features.getActiveFeatures().stream()
.map(toInterceptor(this.features, data, coverage, source, testPrioritiser, code))
.collect(Collectors.toList());
return new CompoundMutationInterceptor(interceptors);
}
Expand All @@ -38,10 +40,11 @@ private static Function<MutationInterceptorFactory, MutationInterceptor> 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));

}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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() {
Expand All @@ -50,6 +57,10 @@ public TestPrioritiser testPrioritiser() {
return this.testPrioritiser;
}

public CodeSource code() {
return code;
}

public Optional<String> getString(FeatureParameter limit) {
if (this.conf == null) {
return Optional.empty();
Expand Down
Original file line number Diff line number Diff line change
@@ -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<CodeSource, Set<String>> {
@Override
public Set<String> apply(CodeSource codeSource) {
return codeSource.codeTrees()
.filter(this::isFunctionalInterface)
.map(c -> c.rawNode().name)
.collect(Collectors.toSet());
}

private boolean isFunctionalInterface(ClassTree classTree) {
List<AnnotationNode> annotations = classTree.rawNode().visibleAnnotations;
if (annotations == null) {
return false;
}

return annotations.stream()
.anyMatch(a -> a.desc.equals("Ljava/lang/FunctionalInterface;"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -59,10 +60,12 @@
*/
class StaticInitializerInterceptor implements MutationInterceptor {

static final Slot<AbstractInsnNode> START = Slot.create(AbstractInsnNode.class);
static final Slot<Set<String>> DELAYED_EXECUTION_FIELDS = Slot.createSet(String.class);
private final Set<String> delayedExecutionTypes;

static final SequenceMatcher<AbstractInsnNode> DELAYED_EXECUTION = QueryStart
private static final Slot<AbstractInsnNode> START = Slot.create(AbstractInsnNode.class);
private static final Slot<Set<String>> DELAYED_EXECUTION_FIELDS = Slot.createSet(String.class);

private final SequenceMatcher<AbstractInsnNode> 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
Expand All @@ -75,6 +78,10 @@ class StaticInitializerInterceptor implements MutationInterceptor {
.withIgnores(notAnInstruction())
);

StaticInitializerInterceptor(Set<String> delayedExecutionTypes) {
this.delayedExecutionTypes = delayedExecutionTypes;
}

private static Match<AbstractInsnNode> delayedExecutionField(SlotRead<Set<String>> delayedFields) {
return PUTSTATIC.and(isADelayedExecutionField(delayedFields));
}
Expand All @@ -86,22 +93,27 @@ private static Match<AbstractInsnNode> isADelayedExecutionField(SlotRead<Set<Str
};
}

private static Match<AbstractInsnNode> dynamicallyReturnsDeferredExecutionCode() {
private Match<AbstractInsnNode> dynamicallyReturnsDeferredExecutionCode() {
return (c,n) -> result(n.getOpcode() == Opcodes.INVOKEDYNAMIC && returnsDelayedExecutionType(((InvokeDynamicInsnNode) n).desc), c);
}

private static Match<AbstractInsnNode> returnsDeferredExecutionCode() {
private Match<AbstractInsnNode> 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<AbstractInsnNode> enumConstructorCallAndStore() {
Expand Down Expand Up @@ -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<Call> findCallsStoredToDelayedExecutionCode(ClassTree tree, Set<String> delayedExecutionFields) {
Expand All @@ -189,7 +201,7 @@ private Set<Call> privateAndClinitCallsToDelayedExecutionCode(ClassTree tree, Se

private List<Location> delayedExecutionCall(MethodTree method, Set<String> 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());
Expand Down
Original file line number Diff line number Diff line change
@@ -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<CodeSource, Set<String>> delayedExecutionTypes;

public StaticInitializerInterceptorFactory() {
this(new FunctionalInterfaceScanner());
}

public StaticInitializerInterceptorFactory(Function<CodeSource, Set<String>> delayedExecutionTypes) {
this.delayedExecutionTypes = delayedExecutionTypes.andThen(this::jdkFunctionalClasses);
}

@Override
public String description() {
return "Static initializer code detector plugin";
}

@Override
public MutationInterceptor createInterceptor(InterceptorParameters params) {
Set<String> 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<String> jdkFunctionalClasses(Set<String> existing) {
Set<String> 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!");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ private List<MutationAnalysisUnit> 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);
Expand Down
94 changes: 94 additions & 0 deletions pitest-entry/src/main/resources/jdkfunctionalinterfaces.txt
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.example.staticinitializers.delayedexecution;

@FunctionalInterface
public interface CustomFunction <T, R> {
R apply(T t);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.example.staticinitializers.delayedexecution;

// NOT annotated as a functional interface
public interface CustomFunctionNotAnnotated <T, R> {
R apply(T t);
}
Loading

0 comments on commit c0a2548

Please sign in to comment.