Skip to content

Commit

Permalink
improve try-with-resources-filtering #766
Browse files Browse the repository at this point in the history
Javac emits fewer null checks for try with resources in 11 than in 8.
This trips up the junk mutation filtering.

This change broadens the filtering to catch java 11 generated code and
changes the implementation to use the bytecode matching system.
  • Loading branch information
Henry Coles committed Feb 16, 2021
1 parent 141244e commit 6abe32b
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 251 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ public Collection<MutationDetails> intercept(
private Predicate<MutationDetails> mutatesIteratorLoopPlumbing() {
return a -> {
final int instruction = a.getInstructionIndex();
final MethodTree method = ForEachLoopFilter.this.currentClass.methods().stream()
final MethodTree method = currentClass.methods().stream()
.filter(MethodMatchers.forLocation(a.getId().getLocation()))
.findFirst()
.get();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package org.pitest.mutationtest.build.intercept.javafeatures;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;

import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.pitest.bytecode.analysis.ClassTree;
import org.pitest.bytecode.analysis.MethodTree;
import org.pitest.functional.FCollection;
Expand All @@ -13,10 +10,129 @@
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.Match;
import org.pitest.sequence.QueryParams;
import org.pitest.sequence.SequenceMatcher;
import org.pitest.sequence.SequenceQuery;
import org.pitest.sequence.Slot;

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

import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ASTORE;
import static org.objectweb.asm.Opcodes.ATHROW;
import static org.objectweb.asm.Opcodes.GOTO;
import static org.objectweb.asm.Opcodes.IFNONNULL;
import static org.objectweb.asm.Opcodes.IFNULL;
import static org.objectweb.asm.Opcodes.IF_ACMPEQ;
import static org.pitest.bytecode.analysis.InstructionMatchers.anyInstruction;
import static org.pitest.bytecode.analysis.InstructionMatchers.isA;
import static org.pitest.bytecode.analysis.InstructionMatchers.methodCallNamed;
import static org.pitest.bytecode.analysis.InstructionMatchers.notAnInstruction;
import static org.pitest.bytecode.analysis.InstructionMatchers.opCode;
import static org.pitest.bytecode.analysis.InstructionMatchers.recordTarget;
import static org.pitest.sequence.QueryStart.any;
import static org.pitest.sequence.QueryStart.match;

public class TryWithResourcesFilter implements MutationInterceptor {

private Set<Integer> lines;
private static final boolean DEBUG = false;

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

private static SequenceQuery<AbstractInsnNode> javac11() {
return any(AbstractInsnNode.class)
.zeroOrMore(match(anyInstruction()))
.then(aLabel())
.then(anALoad())
.then(closeCall())
.then(aLabel())
.then(aGoto())
.then(aLabel())
.then(anAStore())
.then(anALoad())
.then(anALoad())
.then(addSuppressedCall())
.zeroOrMore(match(anyInstruction()));
}

private static SequenceQuery<AbstractInsnNode> javac8() {
return any(AbstractInsnNode.class)
.zeroOrMore(match(anyInstruction()))
.then(ifNull())
.then(anALoad())
.then(ifNull())
.then(aLabel())
.then(anALoad())
.then(closeCall())
.then(aLabel())
.then(aGoto())
.then(aLabel())
.then(anAStore())
.then(aLabel())
.then(anALoad())
.then(anALoad())
.then(addSuppressedCall())
.then(aLabel())
.then(aGoto())
.then(aLabel())
.then(anALoad())
.then(closeCall())
.zeroOrMore(match(anyInstruction()));
}

private static SequenceQuery<AbstractInsnNode> ecj() {
return any(AbstractInsnNode.class)
.zeroOrMore(match(anyInstruction()))
.then(closeCall())
.then(aLabel())
.then(anALoad())
.then(opCode(ATHROW).and(mutationPoint()))
.then(aLabel())
.then(anAStore())
.then(anALoad())
.then(ifNonNull())
.then(anALoad())
.then(anAStore())
.then(aGoto())
.then(aLabel())
.zeroOrMore(match(anyInstruction()));
}

private static SequenceQuery<AbstractInsnNode> ecjAddSuppressedCheck() {
return any(AbstractInsnNode.class)
.zeroOrMore(match(anyInstruction()))
.then(ifNonNull())
.then(anALoad())
.then(anAStore())
.then(aGoto())
.then(aLabel())
.then(anALoad())
.then(anALoad())
.then(opCode(IF_ACMPEQ).and(mutationPoint()))
.then(anALoad())
.then(anALoad())
.then(addSuppressedCall())
.then(aLabel())
.zeroOrMore(match(anyInstruction()));
}


private static final SequenceMatcher<AbstractInsnNode> TRY_WITH_RESOURCES = match(Match.<AbstractInsnNode>never())
.or(javac11())
.or(javac8())
.or(ecj())
.or(ecjAddSuppressedCheck())
.then(containMutation(FOUND))
.compile(QueryParams.params(AbstractInsnNode.class)
.withIgnores(notAnInstruction())
.withDebug(DEBUG)
);

private ClassTree currentClass;

@Override
public InterceptorType type() {
Expand All @@ -25,29 +141,76 @@ public InterceptorType type() {

@Override
public void begin(ClassTree clazz) {
this.lines = new HashSet<>();
for (final MethodTree each : clazz.methods()) {
checkMehod(each,this.lines);
}
}

private void checkMehod(MethodTree each, Set<Integer> lines) {
each.rawNode().accept(new TryWithResourcesMethodVisitor(lines));
this.currentClass = clazz;
}

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

private Predicate<MutationDetails> isOnMarkedLine() {
return a -> TryWithResourcesFilter.this.lines.contains(a.getClassLine().getLineNumber());
private Predicate<MutationDetails> mutatesTryWithResourcesScaffolding() {
return a -> {
int instruction = a.getInstructionIndex();
MethodTree method = currentClass.method(a.getId().getLocation())
.orElseThrow(() -> new IllegalStateException("Could not find method for mutant " + a));

// performance hack
if (method.rawNode().tryCatchBlocks.size() <= 1) {
return false;
}

AbstractInsnNode mutatedInstruction = method.instruction(instruction);

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

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

private static Match<AbstractInsnNode> aLabel() {
return isA(LabelNode.class);
}
private static Match<AbstractInsnNode> anALoad() {
return opCode(ALOAD).and(mutationPoint());
}

private static Match<AbstractInsnNode> aGoto() {
return opCode(GOTO).and(mutationPoint());
}

private static Match<AbstractInsnNode> addSuppressedCall() {
return methodCallNamed("addSuppressed").and(mutationPoint());
}

private static Match<AbstractInsnNode> anAStore() {
return opCode(ASTORE).and(mutationPoint());
}

private static Match<AbstractInsnNode> closeCall() {
return methodCallNamed("close").and(mutationPoint());
}

private static Match<AbstractInsnNode> ifNonNull() {
return opCode(IFNONNULL).and(mutationPoint());
}

private static Match<AbstractInsnNode> ifNull() {
return opCode(IFNULL).and(mutationPoint());
}

private static Match<AbstractInsnNode> mutationPoint() {
return recordTarget(MUTATED_INSTRUCTION.read(), FOUND.write());
}

private static Match<AbstractInsnNode> containMutation(final Slot<Boolean> found) {
return (c, t) -> c.retrieve(found.read()).isPresent();
}

}
Loading

0 comments on commit 6abe32b

Please sign in to comment.