Skip to content

Commit

Permalink
Ensure mutators do not modify bytecode during scan stage
Browse files Browse the repository at this point in the history
Add in assertion for all tests using verifier to ensure bytecode remains
unaltered.

Fix applied to InlineConstantMutator which produced functionally
equivalent but different bytecode.
  • Loading branch information
hcoles committed Jun 2, 2023
1 parent cbd3fb1 commit d727fce
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 d727fce

Please sign in to comment.