/* * Copyright 2014 Stefan Mandel, Urs Metz * * 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; import static java.util.Arrays.asList; import static org.objectweb.asm.Opcodes.DUP2_X1; import static org.objectweb.asm.Opcodes.DUP2_X2; import static org.objectweb.asm.Opcodes.DUP_X2; import static org.objectweb.asm.Opcodes.INVOKESTATIC; import static org.objectweb.asm.Opcodes.POP; import static org.objectweb.asm.Opcodes.POP2; import static org.objectweb.asm.Opcodes.SWAP; import java.util.Arrays; 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.MethodMutatorFactory; import org.pitest.mutationtest.engine.gregor.MutationContext; class ArgumentPropagationVisitor extends MethodVisitor { private final MethodMutatorFactory factory; private final MutationContext context; ArgumentPropagationVisitor(final MutationContext context, final MethodVisitor writer, final MethodMutatorFactory factory) { super(Opcodes.ASM5, writer); this.factory = factory; this.context = context; } @Override public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc, final boolean itf) { if (hasArgumentMatchingTheReturnType(desc)) { final MutationIdentifier newId = this.context.registerMutation( this.factory, "replaced call to " + owner + "::" + name + " with argument"); if (this.context.shouldMutate(newId)) { final Type returnType = Type.getReturnType(desc); replaceMethodCallWithArgumentHavingSameTypeAsReturnValue( Type.getArgumentTypes(desc), returnType, opcode); } else { this.mv.visitMethodInsn(opcode, owner, name, desc, itf); } } else { this.mv.visitMethodInsn(opcode, owner, name, desc, itf); } } private boolean hasArgumentMatchingTheReturnType(final String desc) { return findLastIndexOfArgumentWithSameTypeAsReturnValue( Type.getArgumentTypes(desc), Type.getReturnType(desc)) > -1; } private void replaceMethodCallWithArgumentHavingSameTypeAsReturnValue( final Type[] argTypes, final Type returnType, final int opcode) { final int indexOfPropagatedArgument = findLastIndexOfArgumentWithSameTypeAsReturnValue( argTypes, returnType); popArgumentsBeforePropagatedArgument(argTypes, indexOfPropagatedArgument); popArgumentsFollowingThePropagated(argTypes, returnType, indexOfPropagatedArgument); removeThisFromStackIfNotStatic(returnType, opcode); } private int findLastIndexOfArgumentWithSameTypeAsReturnValue( final Type[] argTypes, final Type returnType) { return asList(argTypes).lastIndexOf(returnType); } private void popArgumentsBeforePropagatedArgument(final Type[] argTypes, final int indexOfPropagatedArgument) { final Type[] argumentTypesBeforeNewReturnValue = Arrays.copyOfRange( argTypes, indexOfPropagatedArgument + 1, argTypes.length); popArguments(argumentTypesBeforeNewReturnValue); } private void popArguments(final Type[] argumentTypes) { for (int i = argumentTypes.length - 1; i >= 0; i--) { popArgument(argumentTypes[i]); } } private void popArgumentsFollowingThePropagated(final Type[] argTypes, final Type returnType, final int indexOfPropagatedArgument) { final Type[] argsFollowing = Arrays.copyOfRange(argTypes, 0, indexOfPropagatedArgument); for (int j = argsFollowing.length - 1; j >= 0; j--) { swap(this.mv, returnType, argsFollowing[j]); popArgument(argsFollowing[j]); } } private void removeThisFromStackIfNotStatic(final Type returnType, final int opcode) { if (isNotStatic(opcode)) { swap(this.mv, returnType, Type.getType(Object.class)); this.mv.visitInsn(POP); } } private void popArgument(final Type argumentType) { if (argumentType.getSize() != 1) { this.mv.visitInsn(POP2); } else { this.mv.visitInsn(POP); } } private static boolean isNotStatic(final int opcode) { return INVOKESTATIC != opcode; } // based on: http://stackoverflow.com/a/11359551 private static void swap(final MethodVisitor mv, final Type stackTop, final Type belowTop) { if (stackTop.getSize() == 1) { if (belowTop.getSize() == 1) { // Top = 1, below = 1 mv.visitInsn(SWAP); } else { // Top = 1, below = 2 mv.visitInsn(DUP_X2); mv.visitInsn(POP); } } else { if (belowTop.getSize() == 1) { // Top = 2, below = 1 mv.visitInsn(DUP2_X1); } else { // Top = 2, below = 2 mv.visitInsn(DUP2_X2); } mv.visitInsn(POP2); } } }