Skip to content

Commit

Permalink
Merge pull request #1227 from hcoles/feature/check_mutator_scans
Browse files Browse the repository at this point in the history
Ensure mutators do not modify bytecode during scan stage
  • Loading branch information
hcoles committed Jun 2, 2023
2 parents cbd3fb1 + d727fce commit 6b5c769
Show file tree
Hide file tree
Showing 4 changed files with 239 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ default MethodVisitor create(MutationContext context,
}


default AnnotationVisitor createForAnnotation(NoMethodContext context, AnnotationInfo annotationInfo, AnnotationVisitor next) {
default AnnotationVisitor createForAnnotation(BasicContext context, AnnotationInfo annotationInfo, AnnotationVisitor next) {
return null;
}

default boolean skipAnnotation(NoMethodContext nonMethodContext, AnnotationInfo annotationInfo) {
default boolean skipAnnotation(BasicContext nonMethodContext, AnnotationInfo annotationInfo) {
return false;
}

default FieldVisitor createForField(NoMethodContext context, FieldInfo fieldInfo, FieldVisitor fieldVisitor) {
default FieldVisitor createForField(BasicContext context, FieldInfo fieldInfo, FieldVisitor fieldVisitor) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,31 +43,33 @@ private final class InlineConstantVisitor extends MethodVisitor {
this.context = context;
}

private void mutate(final Double constant) {
private boolean mutate(final Double constant) {
// avoid addition to floating points as may yield same value

final Double replacement = (constant == 1D) ? 2D : 1D;

if (shouldMutate(constant, replacement)) {
translateToByteCode(replacement);
} else {
translateToByteCode(constant);
return true;
}

return false;
}

private void mutate(final Float constant) {
private boolean mutate(final Float constant) {
// avoid addition to floating points as may yield same value

final Float replacement = (constant == 1F) ? 2F : 1F;

if (shouldMutate(constant, replacement)) {
translateToByteCode(replacement);
} else {
translateToByteCode(constant);
return true;
}

return false;
}

private void mutate(final Integer constant) {
private boolean mutate(final Integer constant) {
final Integer replacement;

switch (constant.intValue()) {
Expand All @@ -87,33 +89,35 @@ private void mutate(final Integer constant) {

if (shouldMutate(constant, replacement)) {
translateToByteCode(replacement);
} else {
translateToByteCode(constant);
return true;
}

return false;
}

private void mutate(final Long constant) {
private boolean mutate(final Long constant) {

final Long replacement = constant + 1L;

if (shouldMutate(constant, replacement)) {
translateToByteCode(replacement);
} else {
translateToByteCode(constant);
return true;
}

return false;

}

private void mutate(final Number constant) {
private boolean mutate(final Number constant) {

if (constant instanceof Integer) {
mutate((Integer) constant);
return mutate((Integer) constant);
} else if (constant instanceof Long) {
mutate((Long) constant);
return mutate((Long) constant);
} else if (constant instanceof Float) {
mutate((Float) constant);
return mutate((Float) constant);
} else if (constant instanceof Double) {
mutate((Double) constant);
return mutate((Double) constant);
} else {
throw new PitError("Unsupported subtype of Number found:"
+ constant.getClass());
Expand Down Expand Up @@ -251,7 +255,9 @@ public void visitInsn(final int opcode) {
return;
}

mutate(inlineConstant);
if (!mutate(inlineConstant) ) {
super.visitInsn(opcode);
}
}

/*
Expand All @@ -262,10 +268,12 @@ public void visitInsn(final int opcode) {
@Override
public void visitIntInsn(final int opcode, final int operand) {
if ((opcode == Opcodes.BIPUSH) || (opcode == Opcodes.SIPUSH)) {
mutate(operand);
} else {
super.visitIntInsn(opcode, operand);
if (mutate(operand) ) {
return;
}
}

super.visitIntInsn(opcode, operand);
}

/*
Expand All @@ -277,10 +285,12 @@ public void visitIntInsn(final int opcode, final int operand) {
public void visitLdcInsn(final Object constant) {
// do not mutate strings or .class here
if (constant instanceof Number) {
mutate((Number) constant);
} else {
super.visitLdcInsn(constant);
if (mutate((Number) constant)) {
return;
}
}

super.visitLdcInsn(constant);
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package org.pitest.verifier.mutants;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.util.Textifier;
import org.objectweb.asm.util.TraceClassVisitor;
import org.pitest.classinfo.ClassByteArraySource;
import org.pitest.classinfo.ClassName;
import org.pitest.classpath.ClassloaderByteArraySource;
Expand All @@ -8,6 +12,8 @@
import org.pitest.mutationtest.engine.gregor.MethodInfo;
import org.pitest.mutationtest.engine.gregor.MethodMutatorFactory;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
Expand All @@ -21,6 +27,7 @@
import java.util.stream.Collectors;

import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;

public class MutatorVerifierStart {

Expand Down Expand Up @@ -69,6 +76,7 @@ public MutatorVerifierStart notCheckingUnMutatedValues() {
}

public MutatorVerifier forClass(Class<?> clazz) {
assertScanDoesNotAlterClass(clazz);
GregorMutater engine = makeEngine();
return new MutatorVerifier(engine, clazz, mutantFilter, checkUnmutatedValues);
}
Expand All @@ -79,31 +87,37 @@ public MutatorVerifier forClass(String clazz) {
}

public <B> CallableMutantVerifier<B> forCallableClass(Class<? extends Callable<B>> clazz) {
assertScanDoesNotAlterClass(clazz);
GregorMutater engine = makeEngine();
return new CallableMutantVerifier<B>(engine, clazz, mutantFilter, checkUnmutatedValues);
}

public <A,B> MutantVerifier<A,B> forFunctionClass(Class<? extends Function<A,B>> clazz) {
assertScanDoesNotAlterClass(clazz);
GregorMutater engine = makeEngine();
return new MutantVerifier<A,B>(engine, clazz, mutantFilter, checkUnmutatedValues);
}

public <B> IntMutantVerifier<B> forIntFunctionClass(Class<? extends IntFunction<B>> clazz) {
assertScanDoesNotAlterClass(clazz);
GregorMutater engine = makeEngine();
return new IntMutantVerifier<>(engine, clazz, mutantFilter, checkUnmutatedValues);
}

public <B> LongMutantVerifier<B> forLongFunctionClass(Class<? extends LongFunction<B>> clazz) {
assertScanDoesNotAlterClass(clazz);
GregorMutater engine = makeEngine();
return new LongMutantVerifier<>(engine, clazz, mutantFilter, checkUnmutatedValues);
}

public <B> DoubleMutantVerifier<B> forDoubleFunctionClass(Class<? extends DoubleFunction<B>> clazz) {
assertScanDoesNotAlterClass(clazz);
GregorMutater engine = makeEngine();
return new DoubleMutantVerifier<>(engine, clazz, mutantFilter, checkUnmutatedValues);
}

public <A,B,C> BiFunctionMutantVerifier<A,B,C> forBiFunctionClass(Class<? extends BiFunction<A,B,C>> clazz) {
assertScanDoesNotAlterClass(clazz);
GregorMutater engine = makeEngine();
return new BiFunctionMutantVerifier<>(engine, clazz, mutantFilter, checkUnmutatedValues);
}
Expand All @@ -118,4 +132,35 @@ private GregorMutater makeEngine() {
return new GregorMutater(source, filter, mutators);
}

public void assertScanDoesNotAlterClass(Class<?> clazz) {
ClassByteArraySource source = ClassloaderByteArraySource.fromContext();
byte[] bytes = source.getBytes(clazz.getName()).get();

ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
final ClassReader reader = new ClassReader(bytes);
final ScanningClassVisitor nv = new ScanningClassVisitor(writer, mmfs);

reader.accept(nv, ClassReader.EXPAND_FRAMES);

assertThat(asString(writer.toByteArray()))
.isEqualTo(asString(plainTransform(bytes)))
.describedAs("Mutator modified class when mutation was not active");

}

private byte[] plainTransform(byte[] bytes) {
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
final ClassReader reader = new ClassReader(bytes);
reader.accept(writer, ClassReader.EXPAND_FRAMES);
return writer.toByteArray();
}

private String asString(byte[] bytes) {
ClassReader reader = new ClassReader(bytes);
StringWriter writer = new StringWriter();
reader.accept(new TraceClassVisitor(null, new Textifier(), new PrintWriter(
writer)), ClassReader.EXPAND_FRAMES);
return writer.toString();
}

}
Loading

0 comments on commit 6b5c769

Please sign in to comment.