Skip to content

Commit

Permalink
filter mutations to defensive unmodifiable collection returns
Browse files Browse the repository at this point in the history
  • Loading branch information
hcoles committed Feb 12, 2024
1 parent 0316619 commit 3794506
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.pitest.mutationtest.build.intercept.defensive;

import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.pitest.bytecode.analysis.MethodTree;
import org.pitest.classinfo.ClassName;
import org.pitest.mutationtest.build.intercept.Region;
import org.pitest.mutationtest.build.intercept.RegionInterceptor;
import org.pitest.sequence.Context;
import org.pitest.sequence.Match;
import org.pitest.sequence.QueryParams;
import org.pitest.sequence.QueryStart;
import org.pitest.sequence.SequenceMatcher;
import org.pitest.sequence.Slot;
import org.pitest.sequence.SlotWrite;

import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import static org.pitest.bytecode.analysis.InstructionMatchers.anyInstruction;
import static org.pitest.bytecode.analysis.InstructionMatchers.isA;
import static org.pitest.bytecode.analysis.InstructionMatchers.methodCallTo;
import static org.pitest.bytecode.analysis.InstructionMatchers.notAnInstruction;
import static org.pitest.bytecode.analysis.OpcodeMatchers.ARETURN;
import static org.pitest.bytecode.analysis.OpcodeMatchers.INVOKESTATIC;
import static org.pitest.sequence.Result.result;

public class ReturnUnmodifiableCollection extends RegionInterceptor {

private static final ClassName COLLECTIONS = ClassName.fromClass(Collections.class);

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

static final SequenceMatcher<AbstractInsnNode> DEFENSIVE_RETURN = QueryStart
.any(AbstractInsnNode.class)
.then(INVOKESTATIC.and(methodCallTo(COLLECTIONS, "unmodifiableSet")).and(store(MUTATED_INSTRUCTION.write())))
.then(ARETURN)
.zeroOrMore(QueryStart.match(anyInstruction()))
.compile(QueryParams.params(AbstractInsnNode.class)
.withIgnores(notAnInstruction().or(isA(LabelNode.class)))
);


@Override
protected List<Region> computeRegions(MethodTree method) {
Context context = Context.start();
return DEFENSIVE_RETURN.contextMatches(method.instructions(), context).stream()
.map(c -> c.retrieve(MUTATED_INSTRUCTION.read()).get())
.map(n -> new Region(n, n))
.collect(Collectors.toList());
}

private static Match<AbstractInsnNode> store(SlotWrite<AbstractInsnNode> slot) {
return (c,n) -> result(true, c.store(slot, n));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.pitest.mutationtest.build.intercept.defensive;

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 ReturnUnmodifiableCollectionFactory implements MutationInterceptorFactory {
@Override
public MutationInterceptor createInterceptor(InterceptorParameters params) {
return new ReturnUnmodifiableCollection();
}

@Override
public Feature provides() {
return Feature.named("DEFENSIVERETURN")
.withOnByDefault(true)
.withDescription(description());
}

@Override
public String description() {
return "Filter mutations to defensive return wrappers such as unmodifiableCollection";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,7 @@ org.pitest.mutationtest.build.intercept.equivalent.EquivalentReturnMutationFilte
org.pitest.mutationtest.build.intercept.exclude.FirstLineInterceptorFactory
org.pitest.mutationtest.build.intercept.equivalent.DivisionByMinusOneFilterFactory
org.pitest.mutationtest.build.intercept.lombok.LombokFilter
org.pitest.mutationtest.build.intercept.defensive.ReturnUnmodifiableCollectionFactory


org.pitest.plugin.export.MutantExportFactory
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package org.pitest.mutationtest.build.intercept.defensive;

import org.junit.Test;
import org.pitest.mutationtest.build.InterceptorType;
import org.pitest.mutationtest.build.MutationInterceptorFactory;
import org.pitest.mutationtest.engine.gregor.mutators.NullMutateEverything;
import org.pitest.verifier.interceptors.FactoryVerifier;
import org.pitest.verifier.interceptors.InterceptorVerifier;
import org.pitest.verifier.interceptors.VerifierStart;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import static org.pitest.bytecode.analysis.OpcodeMatchers.INVOKESTATIC;


public class ReturnUnmodifiableCollectionFactoryTest {
private final MutationInterceptorFactory underTest = new ReturnUnmodifiableCollectionFactory();
InterceptorVerifier v = VerifierStart.forInterceptorFactory(underTest)
.usingMutator(new NullMutateEverything());

@Test
public void isOnChain() {
FactoryVerifier.confirmFactory(underTest)
.isOnChain();
}

@Test
public void isOnByDefault() {
FactoryVerifier.confirmFactory(underTest)
.isOnByDefault();
}

@Test
public void featureIsCalledLombok() {
FactoryVerifier.confirmFactory(underTest)
.featureName().isEqualTo("defensivereturn");
}

@Test
public void createsFilters() {
FactoryVerifier.confirmFactory(underTest)
.createsInterceptorsOfType(InterceptorType.FILTER);
}


@Test
public void filtersMutationsToReturnUnmodifiableSet() {
v.forClass(HasUnmodifiableSetReturn.class)
.forCodeMatching(INVOKESTATIC.asPredicate())
.allMutantsAreFiltered()
.verify();
}

@Test
public void doesNotFilterOtherCode() {
v.forClass(HasUnmodifiableSetReturn.class)
.forCodeMatching(INVOKESTATIC.asPredicate().negate())
.noMutantsAreFiltered()
.verify();
}

@Test
public void doesNotFilterOtherCallsToUnModifiableSet() {
v.forClass(HasUnmodifiableSetNonReturn.class)
.forAnyCode()
.noMutantsAreFiltered()
.verify();
}
}

class HasUnmodifiableSetReturn {
private final Set<String> s = new HashSet<>();

public Set<String> mutateMe(int i) {
if (i != 1) {
return Collections.unmodifiableSet(s);
}

return s;
}
}

class HasUnmodifiableSetNonReturn {
private final Set<String> s = new HashSet<>();
private Set<String> copy;


public Set<String> dontMutateME(int i) {
if (i != 1) {
copy = Collections.unmodifiableSet(s);
}

return s;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import org.pitest.mutationtest.engine.gregor.MethodMutatorFactory;
import org.pitest.mutationtest.engine.gregor.config.Mutator;
import org.pitest.mutationtest.engine.gregor.mutators.returns.BooleanFalseReturnValsMutator;
import org.pitest.mutationtest.engine.gregor.mutators.returns.PrimitiveReturnsMutator;

public class EqualsPerformanceShortcutFilterTest {

Expand Down
3 changes: 2 additions & 1 deletion pitest/src/main/java/org/pitest/util/StreamUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
Expand Down Expand Up @@ -35,7 +36,7 @@ private static void copy(final InputStream input, final OutputStream output)
final WritableByteChannel dest = Channels.newChannel(output);
final ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
while (src.read(buffer) != -1) {
buffer.flip();
((Buffer)buffer).flip();
dest.write(buffer);
buffer.compact();
}
Expand Down

0 comments on commit 3794506

Please sign in to comment.