mockit.internal.expectations.mocking.MockedClassModifier Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jmockit Show documentation
Show all versions of jmockit Show documentation
JMockit is a Java toolkit for automated developer testing.
It contains mocking/faking APIs and a code coverage tool, supporting both JUnit and TestNG.
The mocking APIs allow all kinds of Java code, without testability restrictions, to be tested
in isolation from selected dependencies.
/*
* Copyright (c) 2006 JMockit developers
* This file is subject to the terms of the MIT license (see LICENSE.txt).
*/
package mockit.internal.expectations.mocking;
import java.util.*;
import javax.annotation.*;
import static java.lang.reflect.Modifier.*;
import mockit.asm.classes.*;
import mockit.asm.controlFlow.*;
import mockit.asm.methods.*;
import mockit.asm.types.*;
import mockit.internal.*;
import mockit.internal.expectations.*;
import mockit.internal.util.*;
import static mockit.asm.jvmConstants.Access.SYNTHETIC;
import static mockit.asm.jvmConstants.Access.ENUM;
import static mockit.asm.jvmConstants.Opcodes.*;
import static mockit.internal.MockingFilters.*;
import static mockit.internal.util.ObjectMethods.isMethodFromObject;
import static mockit.internal.util.Utilities.*;
final class MockedClassModifier extends BaseClassModifier
{
private static final boolean NATIVE_UNSUPPORTED = !HOTSPOT_VM;
private static final int METHOD_ACCESS_MASK = PRIVATE + SYNTHETIC + ABSTRACT;
private static final int PUBLIC_OR_PROTECTED = PUBLIC + PROTECTED;
@Nullable private final MockedType mockedType;
private final boolean classFromNonBootstrapClassLoader;
private String className;
@Nullable private String baseClassNameForCapturedInstanceMethods;
private boolean ignoreConstructors;
private ExecutionMode executionMode;
private boolean isProxy;
@Nullable private String defaultFilters;
@Nullable List enumSubclasses;
MockedClassModifier(@Nullable ClassLoader classLoader, @Nonnull ClassReader classReader, @Nullable MockedType typeMetadata) {
super(classReader);
mockedType = typeMetadata;
classFromNonBootstrapClassLoader = classLoader != null;
setUseClassLoadingBridge(classLoader);
executionMode = ExecutionMode.Regular;
useInstanceBasedMockingIfApplicable();
}
private void useInstanceBasedMockingIfApplicable() {
if (mockedType != null && mockedType.injectable) {
ignoreConstructors = !mockedType.withInstancesToCapture();
executionMode = ExecutionMode.PerInstance;
}
}
void setClassNameForCapturedInstanceMethods(@Nonnull String internalClassName) {
baseClassNameForCapturedInstanceMethods = internalClassName;
}
void useDynamicMocking(boolean methodsOnly) {
ignoreConstructors = methodsOnly;
executionMode = ExecutionMode.Partial;
}
@Override
public void visit(int version, int access, @Nonnull String name, @Nonnull ClassInfo additionalInfo) {
validateMockingOfJREClass(name);
super.visit(version, access, name, additionalInfo);
isProxy = "java/lang/reflect/Proxy".equals(additionalInfo.superName);
if (isProxy) {
className = additionalInfo.interfaces[0];
defaultFilters = null;
}
else {
className = name;
defaultFilters = filtersForClass(name);
if (defaultFilters != null && defaultFilters.isEmpty()) {
throw VisitInterruptedException.INSTANCE;
}
}
}
private void validateMockingOfJREClass(@Nonnull String internalName) {
if (internalName.startsWith("java/")) {
validateAsMockable(internalName);
if (executionMode == ExecutionMode.Regular && mockedType != null && isFullMockingDisallowed(internalName)) {
String modifyingClassName = internalName.replace('/', '.');
if (modifyingClassName.equals(mockedType.getClassType().getName())) {
throw new IllegalArgumentException(
"Class " + internalName.replace('/', '.') + " cannot be @Mocked fully; instead, use @Injectable or partial mocking");
}
}
}
}
@Override
public void visitInnerClass(@Nonnull String name, @Nullable String outerName, @Nullable String innerName, int access) {
cw.visitInnerClass(name, outerName, innerName, access);
if (access == ENUM + STATIC) {
if (enumSubclasses == null) {
enumSubclasses = new ArrayList<>();
}
enumSubclasses.add(name);
}
}
@Nullable @Override
public MethodVisitor visitMethod(
int access, @Nonnull String name, @Nonnull String desc, @Nullable String signature, @Nullable String[] exceptions
) {
if ((access & METHOD_ACCESS_MASK) != 0) {
return unmodifiedBytecode(access, name, desc, signature, exceptions);
}
boolean visitingConstructor = "".equals(name);
ExecutionMode actualExecutionMode = determineAppropriateExecutionMode(visitingConstructor);
String internalClassName = className;
if (visitingConstructor) {
if (isConstructorNotAllowedByMockingFilters(name)) {
return unmodifiedBytecode(access, name, desc, signature, exceptions);
}
startModifiedMethodVersion(access, name, desc, signature, exceptions);
generateCallToRegisterConstructorExecutionIfNeeded(actualExecutionMode);
generateCallToSuperConstructor();
}
else {
if (isMethodNotToBeMocked(access, name, desc)) {
return unmodifiedBytecode(access, name, desc, signature, exceptions);
}
if ("".equals(name)) {
return stubOutClassInitializationIfApplicable(access);
}
if (stubOutFinalizeMethod(access, name, desc)) {
return null;
}
if (isMethodNotAllowedByMockingFilters(access, name)) {
return unmodifiedBytecode(access, name, desc, signature, exceptions);
}
startModifiedMethodVersion(access, name, desc, signature, exceptions);
if (baseClassNameForCapturedInstanceMethods != null) {
internalClassName = baseClassNameForCapturedInstanceMethods;
}
}
if (useClassLoadingBridge) {
return generateCallToHandlerThroughMockingBridge(signature, internalClassName, visitingConstructor, actualExecutionMode);
}
generateDirectCallToHandler(internalClassName, access, name, desc, signature, actualExecutionMode);
generateDecisionBetweenReturningOrContinuingToRealImplementation();
// Constructors of non-JRE classes can't be modified (unless running with "-noverify") in a way that
// "super(...)/this(...)" get called twice, so we disregard such calls when copying the original bytecode.
return copyOriginalImplementationCode(visitingConstructor);
}
@Nonnull
private MethodVisitor unmodifiedBytecode(
int access, @Nonnull String name, @Nonnull String desc, @Nullable String signature, @Nullable String[] exceptions
) {
return cw.visitMethod(access, name, desc, signature, exceptions);
}
@Nonnull
private ExecutionMode determineAppropriateExecutionMode(boolean visitingConstructor) {
if (executionMode == ExecutionMode.PerInstance) {
if (visitingConstructor) {
return ignoreConstructors ? ExecutionMode.Regular : ExecutionMode.Partial;
}
if (isStatic(methodAccess)) {
return ExecutionMode.Partial;
}
}
return executionMode;
}
private boolean isConstructorNotAllowedByMockingFilters(@Nonnull String name) {
return isProxy || ignoreConstructors || isUnmockableInvocation(defaultFilters, name);
}
private boolean isMethodNotToBeMocked(int access, @Nonnull String name, @Nonnull String desc) {
return
isNative(access) && (NATIVE_UNSUPPORTED || (access & PUBLIC_OR_PROTECTED) == 0) ||
(isProxy || executionMode == ExecutionMode.Partial) && (
isMethodFromObject(name, desc) || "annotationType".equals(name) && "()Ljava/lang/Class;".equals(desc)
);
}
@Nullable
private MethodVisitor stubOutClassInitializationIfApplicable(int access) {
startModifiedMethodVersion(access, "", "()V", null, null);
if (mockedType != null && mockedType.isClassInitializationToBeStubbedOut()) {
generateEmptyImplementation();
return null;
}
return mw;
}
private boolean stubOutFinalizeMethod(int access, @Nonnull String name, @Nonnull String desc) {
if ("finalize".equals(name) && "()V".equals(desc)) {
startModifiedMethodVersion(access, name, desc, null, null);
generateEmptyImplementation();
return true;
}
return false;
}
private boolean isMethodNotAllowedByMockingFilters(int access, @Nonnull String name) {
return
baseClassNameForCapturedInstanceMethods != null && (access & STATIC) != 0 ||
executionMode.isMethodToBeIgnored(access) || isUnmockableInvocation(defaultFilters, name);
}
private void generateCallToRegisterConstructorExecutionIfNeeded(@Nonnull ExecutionMode constructorExecutionMode) {
// mw.visitMethodInsn(INVOKESTATIC, "", "", "", false);
}
@Nonnull
private MethodVisitor generateCallToHandlerThroughMockingBridge(
@Nullable String genericSignature, @Nonnull String internalClassName, boolean visitingConstructor,
@Nonnull ExecutionMode actualExecutionMode
) {
generateCodeToObtainInstanceOfClassLoadingBridge(MockedBridge.MB);
// First and second "invoke" arguments:
boolean isStatic = generateCodeToPassThisOrNullIfStaticMethod();
mw.visitInsn(ACONST_NULL);
// Create array for call arguments (third "invoke" argument):
JavaType[] argTypes = JavaType.getArgumentTypes(methodDesc);
generateCodeToCreateArrayOfObject(6 + argTypes.length);
int i = 0;
generateCodeToFillArrayElement(i++, methodAccess);
generateCodeToFillArrayElement(i++, internalClassName);
generateCodeToFillArrayElement(i++, methodName);
generateCodeToFillArrayElement(i++, methodDesc);
generateCodeToFillArrayElement(i++, genericSignature);
generateCodeToFillArrayElement(i++, actualExecutionMode.ordinal());
generateCodeToFillArrayWithParameterValues(argTypes, i, isStatic ? 0 : 1);
generateCallToInvocationHandler();
generateDecisionBetweenReturningOrContinuingToRealImplementation();
// Copies the entire original implementation even for a constructor, in which case the complete bytecode inside
// the constructor fails the strict verification activated by "-Xfuture". However, this is necessary to allow the
// full execution of a bootstrap class constructor when the call was not meant to be mocked.
return copyOriginalImplementationCode(visitingConstructor && classFromNonBootstrapClassLoader);
}
private void generateDecisionBetweenReturningOrContinuingToRealImplementation() {
Label startOfRealImplementation = new Label();
mw.visitInsn(DUP);
mw.visitLdcInsn(VOID_TYPE);
mw.visitJumpInsn(IF_ACMPEQ, startOfRealImplementation);
generateReturnWithObjectAtTopOfTheStack(methodDesc);
mw.visitLabel(startOfRealImplementation);
mw.visitInsn(POP);
}
}