Skip to content

Commit

Permalink
Filter junk mutations in compiler generated calls to Objects.requireN…
Browse files Browse the repository at this point in the history
…onNull for #404
  • Loading branch information
hcoles committed Aug 29, 2019
1 parent ce14688 commit fd3de31
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package org.pitest.mutationtest.build.intercept.javafeatures;

import static org.pitest.bytecode.analysis.InstructionMatchers.anyInstruction;
import static org.pitest.bytecode.analysis.InstructionMatchers.isInstruction;
import static org.pitest.bytecode.analysis.InstructionMatchers.methodCallTo;
import static org.pitest.bytecode.analysis.InstructionMatchers.notAnInstruction;
import static org.pitest.bytecode.analysis.InstructionMatchers.opCode;

import java.util.Collection;
import java.util.Objects;
import java.util.function.Predicate;

import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.pitest.bytecode.analysis.ClassTree;
import org.pitest.bytecode.analysis.MethodMatchers;
import org.pitest.bytecode.analysis.MethodTree;
import org.pitest.classinfo.ClassName;
import org.pitest.functional.FCollection;
import org.pitest.functional.prelude.Prelude;
import org.pitest.mutationtest.build.InterceptorType;
import org.pitest.mutationtest.build.MutationInterceptor;
import org.pitest.mutationtest.engine.Mutater;
import org.pitest.mutationtest.engine.MutationDetails;
import org.pitest.sequence.Context;
import org.pitest.sequence.QueryParams;
import org.pitest.sequence.QueryStart;
import org.pitest.sequence.SequenceMatcher;
import org.pitest.sequence.Slot;

/**
* Filters out the calls to Objects.requireNotNull the compiler inserts when using method references.
*
*/
public class MethodReferenceNullCheckFilter implements MutationInterceptor {

private static final boolean DEBUG = false;

private static final Slot<AbstractInsnNode> MUTATED_INSTRUCTION = Slot.create(AbstractInsnNode.class);

static final SequenceMatcher<AbstractInsnNode> NULL_CHECK = QueryStart
.any(AbstractInsnNode.class)
.then(methodCallTo(ClassName.fromClass(Objects.class), "requireNonNull").and(isInstruction(MUTATED_INSTRUCTION.read())))
.then(opCode(Opcodes.POP))
.then(opCode(Opcodes.INVOKEDYNAMIC))
.zeroOrMore(QueryStart.match(anyInstruction()))
.compile(QueryParams.params(AbstractInsnNode.class)
.withIgnores(notAnInstruction())
.withDebug(DEBUG)
);


private ClassTree currentClass;

@Override
public InterceptorType type() {
return InterceptorType.FILTER;
}

@Override
public void begin(ClassTree clazz) {
this.currentClass = clazz;
}

@Override
public Collection<MutationDetails> intercept(
Collection<MutationDetails> mutations, Mutater m) {
return FCollection.filter(mutations, Prelude.not(isAnImplicitNullCheck()));
}

private Predicate<MutationDetails> isAnImplicitNullCheck() {
return a -> {
final int instruction = a.getInstructionIndex();
final MethodTree method = MethodReferenceNullCheckFilter.this.currentClass.methods().stream()
.filter(MethodMatchers.forLocation(a.getId().getLocation()))
.findFirst()
.get();

final AbstractInsnNode mutatedInstruction = method.instruction(instruction);

final Context<AbstractInsnNode> context = Context.start(method.instructions(), DEBUG);
context.store(MUTATED_INSTRUCTION.write(), mutatedInstruction);
return NULL_CHECK.matches(method.instructions(), context);
};
}

@Override
public void end() {
this.currentClass = null;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.pitest.mutationtest.build.intercept.javafeatures;

import org.pitest.mutationtest.build.InterceptorParameters;
import org.pitest.mutationtest.build.MutationInterceptor;
import org.pitest.mutationtest.build.MutationInterceptorFactory;
import org.pitest.plugin.Feature;

public class MethodReferenceNullCheckFilterFactory implements MutationInterceptorFactory {

@Override
public String description() {
return "Method reference null check filter";
}

@Override
public MutationInterceptor createInterceptor(InterceptorParameters params) {
return new MethodReferenceNullCheckFilter();
}

@Override
public Feature provides() {
return Feature.named("FMRNULL")
.withOnByDefault(true)
.withDescription("Filters mutations in compiler generated code that inserts Objects.requireNonNull for method references");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ org.pitest.mutationtest.build.intercept.annotations.ExcludedAnnotationIntercepto
org.pitest.mutationtest.build.intercept.javafeatures.InlinedFinallyBlockFilterFactory
org.pitest.mutationtest.build.intercept.javafeatures.TryWithResourcesFilterFactory
org.pitest.mutationtest.build.intercept.javafeatures.ImplicitNullCheckFilterFactory
org.pitest.mutationtest.build.intercept.javafeatures.MethodReferenceNullCheckFilterFactory
org.pitest.mutationtest.build.intercept.javafeatures.ForEachLoopFilterFactory
org.pitest.mutationtest.build.intercept.logging.LoggingCallsFilterFactory
org.pitest.mutationtest.build.intercept.timeout.InfiniteForLoopFilterFactory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,21 @@ public void shouldFilterImplicitNullChecksInLambdas() {

assertThat(foundWhenDisabled.size()).isGreaterThan(foundByDefault.size());
}

@Test
public void shouldFilterObjectsRequireNonNullCallsForMethodReferences() {
final ClassName clazz = ClassName.fromString("requirenotnull/MethodReferenceNullChecks_javac");

this.data.setMutators(Collections.singletonList("ALL"));

final Collection<MutationDetails> foundByDefault = findMutants(clazz);

this.data.setFeatures(Collections.singletonList("-FMRNULL"));

final Collection<MutationDetails> foundWhenDisabled = findMutants(clazz);

assertThat(foundWhenDisabled.size()).isGreaterThan(foundByDefault.size());
}

@Test
public void shouldFilterMutationsToForLoopIncrements() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.pitest.mutationtest.build.intercept.javafeatures;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.Objects;

import org.junit.Test;
import org.pitest.mutationtest.build.InterceptorType;
import org.pitest.mutationtest.engine.gregor.config.Mutator;

public class MethodReferenceNullCheckFilterTest {

private static final String PATH = "requirenotnull/{0}_{1}";

MethodReferenceNullCheckFilter testee = new MethodReferenceNullCheckFilter();

FilterTester verifier = new FilterTester(PATH, this.testee, Mutator.all());

@Test
public void shouldDeclareTypeAsFilter() {
assertThat(this.testee.type()).isEqualTo(InterceptorType.FILTER);
}

@Test
public void filtersRequireNotNullChecksForMethodReferences() {
this.verifier.assertFiltersNMutationFromSample(2, "MethodReferenceNullChecks");
}

@Test
public void shouldNotFilterDeadCallsToGetClassInNonLambdaMethods() {
this.verifier.assertFiltersNMutationFromClass(0, HasNormalRequireNonNullCheck.class);
}

}


class HasNormalRequireNonNullCheck {
String aField;
public void amethod() {
Objects.requireNonNull(aField);
System.out.println(aField);
}
}

Binary file not shown.

0 comments on commit fd3de31

Please sign in to comment.