Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
mockit.internal.expectations.transformation.InvocationBlockModifier Maven / Gradle / Ivy
/*
* Copyright (c) 2006-2013 Rogério Liesenfeld
* This file is subject to the terms of the MIT license (see LICENSE.txt).
*/
package mockit.internal.expectations.transformation;
import mockit.external.asm4.*;
import static mockit.external.asm4.Opcodes.*;
import static mockit.internal.util.TypeConversion.*;
final class InvocationBlockModifier extends MethodVisitor
{
private static final String CLASS_DESC = Type.getInternalName(ActiveInvocations.class);
// Input data:
private final String owner;
private final boolean callEndInvocations;
// Takes care of "withCapture()" matchers, if any:
private final ArgumentCapturing argumentCapturing;
// Helper fields that allow argument matchers to be moved to the correct positions of their
// corresponding parameters:
private final int[] matcherStacks;
private int matcherCount;
private Type[] parameterTypes;
Capture createCapture(int opcode, int var, String typeToCapture)
{
return new Capture(opcode, var, typeToCapture);
}
final class Capture
{
final int opcode;
private final int var;
private final String typeToCapture;
private int parameterIndex;
private boolean parameterIndexFixed;
Capture(int opcode, int var, String typeToCapture)
{
this.opcode = opcode;
this.var = var;
this.typeToCapture = typeToCapture;
parameterIndex = matcherCount - 1;
}
/**
* Generates bytecode that will be responsible for performing the following steps:
* 1. Get the argument value (an Object) for the last matched invocation.
* 2. Cast to a reference type or unbox to a primitive type, as needed.
* 3. Store the converted value in its local variable.
*/
void generateCodeToStoreCapturedValue()
{
mv.visitIntInsn(SIPUSH, parameterIndex);
generateCallToActiveInvocationsMethod("matchedArgument", "(I)Ljava/lang/Object;");
Type argType = getArgumentType();
generateCastOrUnboxing(mv, argType, opcode);
mv.visitVarInsn(opcode, var);
}
private Type getArgumentType()
{
if (typeToCapture == null) {
return parameterTypes[parameterIndex];
}
else if (typeToCapture.charAt(0) == '[') {
return Type.getType(typeToCapture);
}
else {
return Type.getType('L' + typeToCapture + ';');
}
}
boolean fixParameterIndex(int originalIndex, int newIndex)
{
if (!parameterIndexFixed && parameterIndex == originalIndex) {
parameterIndex = newIndex;
parameterIndexFixed = true;
return true;
}
return false;
}
void generateCallToSetArgumentTypeIfNeeded()
{
if (typeToCapture != null && !isTypeToCaptureSameAsParameterType()) {
mv.visitIntInsn(SIPUSH, parameterIndex);
mv.visitLdcInsn(typeToCapture);
generateCallToActiveInvocationsMethod("setExpectedArgumentType", "(ILjava/lang/String;)V");
}
}
private boolean isTypeToCaptureSameAsParameterType()
{
Type parameterType = parameterTypes[parameterIndex];
int sort = parameterType.getSort();
if (sort == Type.OBJECT || sort == Type.ARRAY) {
return typeToCapture.equals(parameterType.getInternalName());
}
else {
return isPrimitiveWrapper(typeToCapture);
}
}
}
InvocationBlockModifier(MethodVisitor mw, String owner, boolean callEndInvocations)
{
super(mw);
this.owner = owner;
this.callEndInvocations = callEndInvocations;
matcherStacks = new int[40];
argumentCapturing = new ArgumentCapturing();
}
private void generateCallToActiveInvocationsMethod(String name, String desc)
{
mv.visitMethodInsn(INVOKESTATIC, CLASS_DESC, name, desc);
}
@Override
public void visitFieldInsn(int opcode, String owner, String name, String desc)
{
if ((opcode == GETSTATIC || opcode == PUTSTATIC) && isFieldDefinedByInvocationBlock(owner)) {
if (opcode == PUTSTATIC) {
if (generateCodeThatReplacesAssignmentToSpecialField(name)) return;
}
else if (name.startsWith("any")) {
generateCodeToAddArgumentMatcherForAnyField(owner, name, desc);
return;
}
}
mv.visitFieldInsn(opcode, owner, name, desc);
}
private boolean isFieldDefinedByInvocationBlock(String owner)
{
return
this.owner.equals(owner) ||
("mockit/Expectations mockit/NonStrictExpectations " +
"mockit/Verifications mockit/VerificationsInOrder " +
"mockit/FullVerifications mockit/FullVerificationsInOrder").contains(owner);
}
private boolean generateCodeThatReplacesAssignmentToSpecialField(String fieldName)
{
if ("result".equals(fieldName)) {
generateCallToActiveInvocationsMethod("addResult", "(Ljava/lang/Object;)V");
return true;
}
else if ("forEachInvocation".equals(fieldName)) {
generateCallToActiveInvocationsMethod("setHandler", "(Ljava/lang/Object;)V");
return true;
}
else if ("times".equals(fieldName) || "minTimes".equals(fieldName) || "maxTimes".equals(fieldName)) {
generateCallToActiveInvocationsMethod(fieldName, "(I)V");
return true;
}
else if ("$".equals(fieldName)) {
generateCallToActiveInvocationsMethod("setErrorMessage", "(Ljava/lang/CharSequence;)V");
return true;
}
return false;
}
private void generateCodeToAddArgumentMatcherForAnyField(String owner, String name, String desc)
{
mv.visitFieldInsn(GETSTATIC, owner, name, desc);
generateCallToActiveInvocationsMethod("addArgMatcher", "()V");
matcherStacks[matcherCount++] = mv.stackSize2;
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc)
{
if (opcode == INVOKESTATIC && (isBoxing(owner, name, desc) || isAccessMethod(owner, name))) {
// It's an invocation to a primitive boxing method or to a synthetic method for private access, just ignore it.
mv.visitMethodInsn(INVOKESTATIC, owner, name, desc);
}
else if (opcode == INVOKEVIRTUAL && owner.equals(this.owner) && name.startsWith("with")) {
mv.visitMethodInsn(INVOKEVIRTUAL, owner, name, desc);
matcherStacks[matcherCount++] = mv.stackSize2;
argumentCapturing.registerCapturingMatcherIfApplicable(name, desc);
}
else if (isUnboxing(opcode, owner, desc)) {
if (argumentCapturing.justAfterWithCaptureInvocation) {
generateCodeToReplaceNullWithZeroOnTopOfStack(desc.charAt(2));
argumentCapturing.justAfterWithCaptureInvocation = false;
}
else {
mv.visitMethodInsn(opcode, owner, name, desc);
}
}
else if (matcherCount == 0) {
mv.visitMethodInsn(opcode, owner, name, desc);
}
else {
parameterTypes = Type.getArgumentTypes(desc);
int stackSize = mv.stackSize2;
int stackAfter = stackSize - sumOfParameterSizes();
boolean mockedInvocationUsingTheMatchers = stackAfter < matcherStacks[0];
if (mockedInvocationUsingTheMatchers) {
generateCallsToMoveArgMatchers(stackAfter);
argumentCapturing.generateCallsToSetArgumentTypesToCaptureIfAny();
matcherCount = 0;
}
mv.visitMethodInsn(opcode, owner, name, desc);
if (mockedInvocationUsingTheMatchers) {
argumentCapturing.generateCallsToCaptureMatchedArgumentsIfPending();
}
argumentCapturing.justAfterWithCaptureInvocation = false;
}
}
private boolean isAccessMethod(String owner, String name)
{
return !owner.equals(this.owner) && name.startsWith("access$");
}
private void generateCodeToReplaceNullWithZeroOnTopOfStack(char primitiveTypeCode)
{
mv.visitInsn(POP);
int zeroOpcode;
switch (primitiveTypeCode) {
case 'J': zeroOpcode = LCONST_0; break;
case 'F': zeroOpcode = FCONST_0; break;
case 'D': zeroOpcode = DCONST_0; break;
default: zeroOpcode = ICONST_0;
}
mv.visitInsn(zeroOpcode);
}
private int sumOfParameterSizes()
{
int sum = 0;
for (Type argType : parameterTypes) {
sum += argType.getSize();
}
return sum;
}
private void generateCallsToMoveArgMatchers(int initialStack)
{
int stack = initialStack;
int nextMatcher = 0;
int matcherStack = matcherStacks[0];
for (int i = 0; i < parameterTypes.length && nextMatcher < matcherCount; i++) {
stack += parameterTypes[i].getSize();
if (stack == matcherStack || stack == matcherStack + 1) {
if (nextMatcher < i) {
generateCallToMoveArgMatcher(nextMatcher, i);
argumentCapturing.updateCaptureIfAny(nextMatcher, i);
}
matcherStack = matcherStacks[++nextMatcher];
}
}
}
private void generateCallToMoveArgMatcher(int originalMatcherIndex, int toIndex)
{
mv.visitIntInsn(SIPUSH, originalMatcherIndex);
mv.visitIntInsn(SIPUSH, toIndex);
generateCallToActiveInvocationsMethod("moveArgMatcher", "(II)V");
}
@Override
public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index)
{
// In classes instrumented with EMMA some local variable information can be lost, so we discard it entirely to
// avoid a ClassFormatError.
if (end.position > 0) {
super.visitLocalVariable(name, desc, signature, start, end, index);
}
}
@Override
public void visitTypeInsn(int opcode, String type)
{
argumentCapturing.registerTypeToCaptureIfApplicable(opcode, type);
mv.visitTypeInsn(opcode, type);
}
@Override
public void visitVarInsn(int opcode, int var)
{
argumentCapturing.registerAssignmentToCaptureVariableIfApplicable(this, opcode, var);
mv.visitVarInsn(opcode, var);
}
@Override
public void visitInsn(int opcode)
{
if (opcode == RETURN && callEndInvocations) {
generateCallToActiveInvocationsMethod("endInvocations", "()V");
}
mv.visitInsn(opcode);
}
}