All Downloads are FREE. Search and download functionalities are using the official Maven repository.

mockit.internal.mockups.MockupsModifier 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.mockups;

import static java.lang.reflect.Modifier.*;

import org.jetbrains.annotations.*;

import static mockit.external.asm4.Opcodes.*;

import mockit.*;
import mockit.external.asm4.*;
import mockit.external.asm4.Type;
import mockit.internal.*;
import mockit.internal.startup.*;
import mockit.internal.state.*;
import mockit.internal.mockups.MockMethods.MockMethod;

/**
 * Responsible for generating all necessary bytecode in the redefined (real) class.
 * Such code will redirect calls made on "real" methods to equivalent calls on the corresponding "mock" methods.
 * The original code won't be executed by the running JVM until the class redefinition is undone.
 * 

* Methods in the real class which have no corresponding mock method are unaffected. *

* Any fields (static or not) in the real class remain untouched. */ final class MockupsModifier extends BaseClassModifier { private static final String CLASS_WITH_STATE = "mockit/internal/state/TestRun"; private final int mockInstanceIndex; private final boolean forStartupMock; @NotNull private final MockMethods mockMethods; private final boolean useMockingBridgeForUpdatingMockState; @NotNull private final Class mockedClass; // Helper fields: private MockMethod mockMethod; private int varIndex; // Output data: private boolean classWasModified; MockupsModifier( @NotNull ClassReader cr, @NotNull Class realClass, @NotNull MockUp mock, @NotNull MockMethods mockMethods, boolean forStartupMock) { this(cr, realClass, mock, mockMethods, forStartupMock, true); } /** * Initializes the modifier for a given real/mock class pair. *

* The mock instance provided will receive calls for any instance methods defined in the mock class. * Therefore, it needs to be later recovered by the modified bytecode inside the real method. * To enable this, the mock instance is added to the end of a global list made available through the * {@link mockit.internal.state.TestRun#getMock(int)} method. * * @param cr the class file reader for the real class * @param mock an instance of the mock class, never null * @param mockMethods contains the set of mock methods collected from the mock class; each mock method is identified * by a pair composed of "name" and "desc", where "name" is the method name, and "desc" is the JVM internal * description of the parameters; once the real class modification is complete this set will be empty, unless no * corresponding real method was found for any of its method identifiers */ MockupsModifier( @NotNull ClassReader cr, @NotNull Class realClass, @NotNull MockUp mock, @NotNull MockMethods mockMethods, boolean forStartupMock, boolean computeFrames) { super(cr, computeFrames); mockedClass = realClass; mockInstanceIndex = TestRun.getMockClasses().getMocks(forStartupMock).addMock(mock); this.mockMethods = mockMethods; this.forStartupMock = forStartupMock; ClassLoader classLoaderOfRealClass = realClass.getClassLoader(); useMockingBridgeForUpdatingMockState = classLoaderOfRealClass == null; inferUseOfMockingBridge(classLoaderOfRealClass, mock); } private void inferUseOfMockingBridge(@Nullable ClassLoader classLoaderOfRealClass, @NotNull Object mock) { setUseMockingBridge(classLoaderOfRealClass); if (!useMockingBridge && !isPublic(mock.getClass().getModifiers())) { useMockingBridge = true; } } /** * If the specified method has a mock definition, then generates bytecode to redirect calls made to it to the mock * method. If it has no mock, does nothing. * * @param access not relevant * @param name together with desc, used to identity the method in given set of mock methods * @param signature not relevant * @param exceptions not relevant * * @return null if the method was redefined, otherwise a MethodWriter that writes out the visited method code without * changes */ @Override public MethodVisitor visitMethod( int access, @NotNull String name, @NotNull String desc, @Nullable String signature, @Nullable String[] exceptions) { if ((access & ACC_SYNTHETIC) != 0) { return super.visitMethod(access, name, desc, signature, exceptions); } if ("".equals(name) && isMockedSuperclass() || !hasMock(name, desc, signature)) { return super.visitMethod(access, name, desc, signature, exceptions); } validateMethodModifiers(access, name); startModifiedMethodVersion(access, name, desc, signature, exceptions); classWasModified = true; if (mockMethod.isForConstructor()) { generateCallToSuperConstructor(); } if (isToPreserveRealImplementation(access)) { return getAlternativeMethodWriter(access); } generateCallToUpdateMockStateIfAny(access); generateCallToMockMethod(access); generateMethodReturn(); mw.visitMaxs(1, 0); // dummy values, real ones are calculated by ASM return methodAnnotationsVisitor; } private boolean hasMock(@NotNull String name, @NotNull String desc, @Nullable String signature) { String mockName = getCorrespondingMockName(name); mockMethod = mockMethods.containsMethod(mockName, desc, signature); return mockMethod != null; } @NotNull private String getCorrespondingMockName(@NotNull String name) { if ("".equals(name)) { return "$init"; } else if ("".equals(name)) { return "$clinit"; } return name; } private void validateMethodModifiers(int access, @NotNull String name) { if (isAbstract(access)) { throw new IllegalArgumentException("Attempted to mock abstract method \"" + name + '\"'); } else if (isNative(access)) { mockMethod.markAsNativeRealMethod(); if (!Startup.isJava6OrLater()) { throw new IllegalArgumentException( "Mocking of native methods not supported under JDK 1.5: \"" + name + '\"'); } } } @NotNull private MethodVisitor getAlternativeMethodWriter(int mockedAccess) { generateDynamicCallToMock(mockedAccess); final boolean forConstructor = methodName.charAt(0) == '<'; return new MethodVisitor(mw) { @Override public void visitLocalVariable( @NotNull String name, @NotNull String desc, @Nullable String signature, @NotNull Label start, @NotNull Label end, int index) { // Discards debug info with missing information, to avoid a ClassFormatError (happens with EMMA). if (end.position > 0) { mw.visitLocalVariable(name, desc, signature, start, end, index); } } @Override public void visitMethodInsn(int opcode, @NotNull String owner, @NotNull String name, @NotNull String desc) { if (forConstructor) { disregardIfInvokingAnotherConstructor(opcode, owner, name, desc); } else { mw.visitMethodInsn(opcode, owner, name, desc); } } }; } private boolean isToPreserveRealImplementation(int mockedAccess) { return !isNative(mockedAccess) && (isMockedSuperclass() || mockMethod.isDynamic()); } private boolean isMockedSuperclass() { return mockedClass != mockMethods.getRealClass(); } private void generateDynamicCallToMock(int mockedAccess) { if (!isStatic(mockedAccess) && !mockMethod.isForConstructor() && isMockedSuperclass()) { startOfRealImplementation = new Label(); mw.visitVarInsn(ALOAD, 0); mw.visitTypeInsn(INSTANCEOF, Type.getInternalName(mockMethods.getRealClass())); mw.visitJumpInsn(IFEQ, startOfRealImplementation); } generateCallToUpdateMockStateIfAny(mockedAccess); if (mockMethod.isReentrant()) { generateCallToReentrantMockMethod(mockedAccess); } else if (mockMethod.isDynamic()) { generateCallToMockMethod(mockedAccess); generateDecisionBetweenReturningOrContinuingToRealImplementation(); } else if (startOfRealImplementation != null) { generateCallToMockMethod(mockedAccess); generateMethodReturn(); mw.visitLabel(startOfRealImplementation); } startOfRealImplementation = null; } private void generateCallToUpdateMockStateIfAny(int mockedAccess) { int mockStateIndex = mockMethod.getIndexForMockState(); if (mockStateIndex >= 0) { if (useMockingBridgeForUpdatingMockState) { generateCallToControlMethodThroughMockingBridge(true, mockedAccess); mw.visitTypeInsn(CHECKCAST, "java/lang/Boolean"); mw.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z"); } else { mw.visitLdcInsn(mockMethods.getMockClassInternalName()); mw.visitIntInsn(SIPUSH, mockStateIndex); mw.visitMethodInsn(INVOKESTATIC, CLASS_WITH_STATE, "updateMockState", "(Ljava/lang/String;I)Z"); } } } private void generateCallToReentrantMockMethod(int mockedAccess) { if (startOfRealImplementation == null) { startOfRealImplementation = new Label(); } mw.visitJumpInsn(IFEQ, startOfRealImplementation); Label l0 = new Label(); Label l1 = new Label(); Label l2 = new Label(); mw.visitTryCatchBlock(l0, l1, l2, null); Label l3 = new Label(); mw.visitTryCatchBlock(l2, l3, l2, null); mw.visitLabel(l0); generateCallToMockMethod(mockedAccess); mw.visitLabel(l1); generateCallToExitReentrantMock(); generateMethodReturn(); mw.visitLabel(l2); mw.visitVarInsn(ASTORE, varIndex); mw.visitLabel(l3); generateCallToExitReentrantMock(); mw.visitVarInsn(ALOAD, varIndex); mw.visitInsn(ATHROW); mw.visitLabel(startOfRealImplementation); } private void generateCallToControlMethodThroughMockingBridge(boolean enteringMethod, int mockAccess) { generateCodeToObtainInstanceOfMockingBridge(MockupBridge.MB); // First and second "invoke" arguments: generateCodeToPassThisOrNullIfStaticMethod(mockAccess); mw.visitInsn(ACONST_NULL); // Create array for call arguments (third "invoke" argument): generateCodeToCreateArrayOfObject(3); int i = 0; generateCodeToFillArrayElement(i++, enteringMethod); generateCodeToFillArrayElement(i++, mockMethods.getMockClassInternalName()); generateCodeToFillArrayElement(i, mockMethod.getIndexForMockState()); generateCallToInvocationHandler(); } private void generateCallToMockMethod(int access) { if (mockMethod.isStatic) { generateStaticMethodCall(access); } else { generateInstanceMethodCall(access); } } private void generateStaticMethodCall(int access) { if (shouldUseMockingBridge()) { generateCallToMockMethodThroughMockingBridge(false, access); } else { generateMethodOrConstructorArguments(access); mw.visitMethodInsn(INVOKESTATIC, mockMethods.getMockClassInternalName(), mockMethod.name, mockMethod.desc); } } private boolean shouldUseMockingBridge() { return useMockingBridge || mockMethod.hasInvocationParameter; } private void generateCallToMockMethodThroughMockingBridge(boolean callingInstanceMethod, int access) { generateCodeToObtainInstanceOfMockingBridge(MockMethodBridge.MB); // First and second "invoke" arguments: boolean isStatic = generateCodeToPassThisOrNullIfStaticMethod(access); mw.visitInsn(ACONST_NULL); // Create array for call arguments (third "invoke" argument): Type[] argTypes = Type.getArgumentTypes(methodDesc); generateCodeToCreateArrayOfObject(7 + argTypes.length); int i = 0; generateCodeToFillArrayElement(i++, callingInstanceMethod); generateCodeToFillArrayElement(i++, mockMethods.getMockClassInternalName()); generateCodeToFillArrayElement(i++, mockMethod.name); generateCodeToFillArrayElement(i++, mockMethod.desc); generateCodeToFillArrayElement(i++, mockMethod.getIndexForMockState()); generateCodeToFillArrayElement(i++, mockInstanceIndex); generateCodeToFillArrayElement(i++, forStartupMock); generateCodeToPassMethodArgumentsAsVarargs(argTypes, i, isStatic ? 0 : 1); generateCallToInvocationHandler(); } private void generateInstanceMethodCall(int access) { if (shouldUseMockingBridge()) { generateCallToMockMethodThroughMockingBridge(true, access); return; } generateGetMockCallWithMockInstanceIndex(); generateMockInstanceMethodInvocationWithRealMethodArgs(access); } private void generateGetMockCallWithMockInstanceIndex() { mw.visitIntInsn(SIPUSH, mockInstanceIndex); String getterName = forStartupMock ? "getStartupMock" : "getMock"; mw.visitMethodInsn(INVOKESTATIC, "mockit/internal/state/TestRun", getterName, "(I)Ljava/lang/Object;"); mw.visitTypeInsn(CHECKCAST, mockMethods.getMockClassInternalName()); } private void generateMockInstanceMethodInvocationWithRealMethodArgs(int access) { generateMethodOrConstructorArguments(access); mw.visitMethodInsn(INVOKEVIRTUAL, mockMethods.getMockClassInternalName(), mockMethod.name, mockMethod.desc); } private void generateMethodOrConstructorArguments(int access) { boolean hasInvokedInstance = (access & ACC_STATIC) == 0; varIndex = hasInvokedInstance ? 1 : 0; Type[] argTypes = Type.getArgumentTypes(mockMethod.desc); boolean forGenericMethod = mockMethod.isForGenericMethod(); for (Type argType : argTypes) { int opcode = argType.getOpcode(ILOAD); mw.visitVarInsn(opcode, varIndex); if (forGenericMethod && argType.getSort() >= Type.ARRAY) { mw.visitTypeInsn(CHECKCAST, argType.getInternalName()); } varIndex += argType.getSize(); } } private void generateMethodReturn() { if (shouldUseMockingBridge()) { generateReturnWithObjectAtTopOfTheStack(methodDesc); } else { Type returnType = Type.getReturnType(methodDesc); mw.visitInsn(returnType.getOpcode(IRETURN)); } } private void generateCallToExitReentrantMock() { if (useMockingBridgeForUpdatingMockState) { generateCallToControlMethodThroughMockingBridge(false, ACC_STATIC); mw.visitInsn(POP); } else { mw.visitLdcInsn(mockMethods.getMockClassInternalName()); mw.visitIntInsn(SIPUSH, mockMethod.getIndexForMockState()); mw.visitMethodInsn(INVOKESTATIC, CLASS_WITH_STATE, "exitReentrantMock", "(Ljava/lang/String;I)V"); } } boolean wasModified() { return classWasModified; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy