/* * Copyright 2011 Henry Coles and Stefan Penndorf * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.pitest.mutationtest.engine.gregor.mutators.experimental; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.pitest.mutationtest.engine.MutationIdentifier; import org.pitest.mutationtest.engine.gregor.MethodInfo; import org.pitest.mutationtest.engine.gregor.MethodMutatorFactory; import org.pitest.mutationtest.engine.gregor.MutationContext; /** * The <code>ReturnValuesMutator</code> mutates the return values of method * calls. Depending on the return type of the method another mutation is used. * * <p> * Replacements for primitive types are simple. Replacements of object * references are handled by ObjectReferenceReplacer. Those replacements can get * more complex. * </p> * * * @author Stefan Penndorf <stefan.penndorf@gmail.com> */ public class ReturnValuesMutator implements MethodMutatorFactory { private static final class ObjectMutationMethod { private final String mutatorMethodName; private final String mutatorInternalName; private final String mutationMethodDescriptor; ObjectMutationMethod() { final Type mutatorType = Type.getType(ReturnValuesMutator.class); this.mutatorInternalName = mutatorType.getInternalName(); this.mutatorMethodName = "mutateObjectInstance"; final Type objectType = Type.getType(Object.class); final Type classType = Type.getType(Class.class); this.mutationMethodDescriptor = Type.getMethodDescriptor(objectType, new Type[] { objectType, classType }); } public String getClassName() { return this.mutatorInternalName; } public String getMethodDescriptor() { return this.mutationMethodDescriptor; } public String getMethodName() { return this.mutatorMethodName; } } private static final class ObjectReferenceReplacer { /** * See {@link ReturnValuesMutator#mutateObjectInstance(Object, Class)} for * details. */ private Object replaceObjectInstance(final Object object, final Class<?> declaredReturnType) { if (Boolean.class == declaredReturnType) { if (Boolean.TRUE.equals(object)) { return Boolean.FALSE; } else { return Boolean.TRUE; } } if (Integer.class == declaredReturnType) { final Integer intValue = (Integer) object; if (intValue == null) { return Integer.valueOf(1); } else if (intValue == 1) { return Integer.valueOf(0); } else { return intValue + 1; } } if (Long.class == declaredReturnType) { final Long longValue = (Long) object; if (longValue == null) { return Long.valueOf(1L); } else { return longValue + 1L; } } if (Object.class == declaredReturnType) { if (object != null) { return null; } else { return new Object(); } } if (object == null) { throw new RuntimeException( "Mutated return of null object to throwing a runtime exception"); } return null; } } private final class ReturnValuesMethodVisitor extends MethodVisitor { private static final String DESCRIPTION_MESSAGE_PATTERN = "replaced return of %s value with %s"; private final MutationContext context; private final MethodInfo methodInfo; private ReturnValuesMethodVisitor(final MutationContext context, final MethodInfo methodInfo, final MethodVisitor delegateVisitor) { super(Opcodes.ASM5, delegateVisitor); this.context = context; this.methodInfo = methodInfo; } private void mutateObjectReferenceReturn() { if (shouldMutate("object reference", "[see docs for details]")) { final Type returnType = this.methodInfo.getReturnType(); super.visitLdcInsn(returnType); super.visitMethodInsn(Opcodes.INVOKESTATIC, OBJECT_MUTATION_METHOD.getClassName(), OBJECT_MUTATION_METHOD.getMethodName(), OBJECT_MUTATION_METHOD.getMethodDescriptor(), false); super.visitTypeInsn(Opcodes.CHECKCAST, returnType.getInternalName()); } super.visitInsn(Opcodes.ARETURN); } /** * Mutates a primitive double return (<code>Opcode.DRETURN</code>). The * strategy used was translated from jumble BCEL code. The following is * complicated by the problem of <tt>NaN</tt>s. By default the new value is * <code>-(x + 1)</code>, but this doesn't work for <tt>NaN</tt>s. But for a * <tt>NaN</tt> <code>x != x</code> is true, and we use this to detect them. * * @see #mutatePrimitiveFloatReturn() */ private void mutatePrimitiveDoubleReturn() { if (shouldMutate("primitive double", "(x != NaN)? -(x + 1) : -1 ")) { final Label label = new Label(); super.visitInsn(Opcodes.DUP2); super.visitInsn(Opcodes.DUP2); super.visitInsn(Opcodes.DCMPG); super.visitJumpInsn(Opcodes.IFEQ, label); super.visitInsn(Opcodes.POP2); super.visitInsn(Opcodes.DCONST_0); // the following code is executed in NaN case, too super.visitLabel(label); super.visitInsn(Opcodes.DCONST_1); super.visitInsn(Opcodes.DADD); super.visitInsn(Opcodes.DNEG); super.visitInsn(Opcodes.DRETURN); } } /** * Mutates a primitive float return (<code>Opcode.FRETURN</code>). The * strategy used was translated from jumble BCEL code. The following is * complicated by the problem of <tt>NaN</tt>s. By default the new value is * <code>-(x + 1)</code>, but this doesn't work for <tt>NaN</tt>s. But for a * <tt>NaN</tt> <code>x != x</code> is true, and we use this to detect them. * * @see #mutatePrimitiveDoubleReturn() */ private void mutatePrimitiveFloatReturn() { if (shouldMutate("primitive float", "(x != NaN)? -(x + 1) : -1 ")) { final Label label = new Label(); super.visitInsn(Opcodes.DUP); super.visitInsn(Opcodes.DUP); super.visitInsn(Opcodes.FCMPG); super.visitJumpInsn(Opcodes.IFEQ, label); super.visitInsn(Opcodes.POP); super.visitInsn(Opcodes.FCONST_0); // the following code is executed in NaN case, too super.visitLabel(label); super.visitInsn(Opcodes.FCONST_1); super.visitInsn(Opcodes.FADD); super.visitInsn(Opcodes.FNEG); super.visitInsn(Opcodes.FRETURN); } } private void mutatePrimitiveIntegerReturn() { if (shouldMutate("primitive boolean/byte/short/integer", "(x == 1) ? 0 : x + 1")) { final Label label = new Label(); super.visitInsn(Opcodes.DUP); super.visitInsn(Opcodes.ICONST_1); super.visitJumpInsn(Opcodes.IF_ICMPEQ, label); super.visitInsn(Opcodes.ICONST_1); super.visitInsn(Opcodes.IADD); super.visitInsn(Opcodes.IRETURN); super.visitLabel(label); super.visitInsn(Opcodes.ICONST_0); super.visitInsn(Opcodes.IRETURN); } } private void mutatePrimitiveLongReturn() { if (shouldMutate("primitive long", "x + 1")) { super.visitInsn(Opcodes.LCONST_1); super.visitInsn(Opcodes.LADD); super.visitInsn(Opcodes.LRETURN); } } private boolean shouldMutate(final String type, final String replacement) { final String description = String.format(DESCRIPTION_MESSAGE_PATTERN, type, replacement); final MutationIdentifier mutationId = this.context.registerMutation( ReturnValuesMutator.this, description); return this.context.shouldMutate(mutationId); } @Override public void visitInsn(final int opcode) { switch (opcode) { case Opcodes.IRETURN: mutatePrimitiveIntegerReturn(); break; case Opcodes.LRETURN: mutatePrimitiveLongReturn(); break; case Opcodes.FRETURN: mutatePrimitiveFloatReturn(); break; case Opcodes.DRETURN: mutatePrimitiveDoubleReturn(); break; case Opcodes.ARETURN: mutateObjectReferenceReturn(); break; default: super.visitInsn(opcode); break; } } } /** * Do not change thread safe singleton instantiation. */ private static final ObjectMutationMethod OBJECT_MUTATION_METHOD = new ObjectMutationMethod(); /** * Do not change thread safe singleton instantiation. */ private static final ObjectReferenceReplacer SINGLETON_REPLACER = new ObjectReferenceReplacer(); /** * Mutates a given object instance / reference. The reference * <code>object</code> may be <code>null</code>. The class given as second * parameter <code>clazz</code> will be the class of the <code>object</code> * or one of it's super classes. The returned object must be of type * <code>clazz</code> or one of it's child classes. * * @param object * the object reference to mutate, maybe <code>null</code>. * @param clazz * the type the returned object must have. Usually that's also the * type of <code>object</code> but might be a super type of * <code>object</code>. * @return the mutated object reference (can also be <code>null</code>). */ public static Object mutateObjectInstance(final Object object, final Class<?> clazz) { return SINGLETON_REPLACER.replaceObjectInstance(object, clazz); } @Override public MethodVisitor create(final MutationContext context, final MethodInfo methodInfo, final MethodVisitor methodVisitor) { return new ReturnValuesMethodVisitor(context, methodInfo, methodVisitor); } @Override public String getGloballyUniqueId() { return this.getClass().getName(); } @Override public String getName() { return "EXPERIMENTAL_RETURN_VALUES_MUTATOR"; } }