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

mockit.internal.expectations.mocking.ExpectationsModifier 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.mocking;

import java.util.*;

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

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

import mockit.external.asm4.*;
import mockit.internal.startup.*;
import mockit.internal.util.*;

final class ExpectationsModifier extends MockedTypeModifier
{
   private static final int METHOD_ACCESS_MASK = ACC_SYNTHETIC + ACC_ABSTRACT;

   private static final Map DEFAULT_FILTERS = new HashMap();
   static {
      DEFAULT_FILTERS.put("java/lang/Object", " getClass hashCode");
      DEFAULT_FILTERS.put("java/lang/String", "");
      DEFAULT_FILTERS.put("java/lang/AbstractStringBuilder", "");
      DEFAULT_FILTERS.put("java/lang/StringBuilder", "");
      DEFAULT_FILTERS.put("java/lang/StringBuffer", "");
      DEFAULT_FILTERS.put("java/lang/System", "arraycopy getProperties getSecurityManager identityHashCode");
      DEFAULT_FILTERS.put("java/lang/Throwable", " fillInStackTrace");
      DEFAULT_FILTERS.put("java/lang/Exception", "");
      DEFAULT_FILTERS.put("java/lang/Thread", "currentThread isInterrupted interrupted");
      DEFAULT_FILTERS.put("java/util/Hashtable", "get hash");
      DEFAULT_FILTERS.put("java/util/ArrayList", "");
      DEFAULT_FILTERS.put("java/util/HashMap", "");
      DEFAULT_FILTERS.put("java/util/jar/JarEntry", "");
      DEFAULT_FILTERS.put("java/util/logging/LogManager",
                          "getLogger getLogManager getUserContext readPrimordialConfiguration");
   }

   private final MockingConfiguration mockingCfg;
   private String className;
   private String baseClassNameForCapturedInstanceMethods;
   private boolean stubOutClassInitialization;
   private boolean ignoreConstructors;
   private int executionMode;
   private boolean isProxy;
   private String defaultFilters;

   ExpectationsModifier(ClassLoader classLoader, ClassReader classReader, MockedType typeMetadata)
   {
      super(classReader, null);

      if (typeMetadata == null) {
         mockingCfg = null;
      }
      else {
         mockingCfg = typeMetadata.mockingCfg;
         stubOutClassInitialization = typeMetadata.isClassInitializationToBeStubbedOut();
      }

      setUseMockingBridge(classLoader);
   }

   public void setClassNameForCapturedInstanceMethods(String internalClassName)
   {
      baseClassNameForCapturedInstanceMethods = internalClassName;
   }

   public void useDynamicMocking(boolean methodsOnly)
   {
      ignoreConstructors = methodsOnly;
      executionMode = 1;
   }

   public void useDynamicMockingForInstanceMethods(MockedType typeMetadata)
   {
      ignoreConstructors = typeMetadata == null || typeMetadata.getMaxInstancesToCapture() <= 0;
      executionMode = 2;
   }

   void useDynamicMockingForSuperClass()
   {
      if (executionMode == 0) {
         executionMode = 3;
      }
   }

   @Override
   public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
   {
      if ("java/lang/Class".equals(name) || "java/lang/ClassLoader".equals(name) ) {
         throw new IllegalArgumentException("Class " + name.replace('/', '.') + " is not mockable");
      }

      super.visit(version, access, name, signature, superName, interfaces);
      isProxy = "java/lang/reflect/Proxy".equals(superName);

      if (isProxy) {
         className = interfaces[0];
         defaultFilters = null;
      }
      else {
         className = name;
         defaultFilters = DEFAULT_FILTERS.get(name);

         if (defaultFilters != null && defaultFilters.length() == 0) {
            throw VisitInterruptedException.INSTANCE;
         }
      }
   }

   @Override
   public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions)
   {
      boolean syntheticOrAbstractMethod = (access & METHOD_ACCESS_MASK) != 0;

      if (syntheticOrAbstractMethod || isProxy && isConstructorOrSystemMethodNotToBeMocked(name, desc)) {
         return unmodifiedBytecode(access, name, desc, signature, exceptions);
      }

      boolean noFiltersToMatch = mockingCfg == null;
      boolean matchesFilters = noFiltersToMatch || mockingCfg.matchesFilters(name, desc);

      if ("".equals(name)) {
         return stubOutClassInitializationIfApplicable(access, noFiltersToMatch, matchesFilters);
      }
      else if (stubOutFinalizeMethod(access, name, desc)) {
         return null;
      }

      if (
         !matchesFilters ||
         isMethodFromCapturedClassNotToBeMocked(access) ||
         noFiltersToMatch && isMethodOrConstructorNotToBeMocked(access, name)
      ) {
         return unmodifiedBytecode(access, name, desc, signature, exceptions);
      }

      // Otherwise, replace original implementation with redirect to JMockit.
      validateModificationOfNativeMethod(access, name);
      startModifiedMethodVersion(access, name, desc, signature, exceptions);

      boolean visitingConstructor = "".equals(name);

      if (visitingConstructor && superClassName != null) {
         generateCallToSuperConstructor();
      }

      String internalClassName = className;

      if (!visitingConstructor && baseClassNameForCapturedInstanceMethods != null) {
         internalClassName = baseClassNameForCapturedInstanceMethods;
      }

      int actualExecutionMode = determineAppropriateExecutionMode(access, visitingConstructor);

      if (useMockingBridge) {
         return
            generateCallToHandlerThroughMockingBridge(
               access, signature, exceptions, internalClassName, actualExecutionMode);
      }

      generateDirectCallToHandler(internalClassName, access, name, desc, signature, exceptions, actualExecutionMode);

      if (actualExecutionMode > 0) {
         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 visitingConstructor ? new DynamicConstructorModifier() : copyOriginalImplementationCode(access);
      }

      generateReturnWithObjectAtTopOfTheStack(methodDesc);
      mw.visitMaxs(1, 0);
      return methodAnnotationsVisitor;
   }

   private MethodVisitor unmodifiedBytecode(int access, String name, String desc, String signature, String[] exceptions)
   {
      return super.visitMethod(access, name, desc, signature, exceptions);
   }

   private boolean isConstructorOrSystemMethodNotToBeMocked(String name, String desc)
   {
      return
         "".equals(name) || isMethodFromObject(name, desc) ||
         "annotationType".equals(name) && "()Ljava/lang/Class;".equals(desc);
   }

   private MethodVisitor stubOutClassInitializationIfApplicable(int access, boolean noFilters, boolean matchesFilters)
   {
      mw = super.visitMethod(access, "", "()V", null, null);

      if (!noFilters && matchesFilters || stubOutClassInitialization) {
         generateEmptyImplementation();
         return null;
      }

      return mw;
   }

   private boolean stubOutFinalizeMethod(int access, String name, String desc)
   {
      if ("finalize".equals(name) && "()V".equals(desc)) {
         mw = super.visitMethod(access, name, desc, null, null);
         generateEmptyImplementation();
         return true;
      }
      
      return false;
   }
   
   private boolean isMethodFromCapturedClassNotToBeMocked(int access)
   {
      return baseClassNameForCapturedInstanceMethods != null && (isStatic(access) || isPrivate(access));
   }

   private boolean isMethodOrConstructorNotToBeMocked(int access, String name)
   {
      return
         isConstructorToBeIgnored(name) ||
         isStaticMethodToBeIgnored(access) ||
         isNativeMethodForDynamicMocking(access) ||
         useMockingBridge && isPrivate(access) && isNative(access) ||
         defaultFilters != null && defaultFilters.contains(name);
   }

   private boolean isConstructorToBeIgnored(String name) { return ignoreConstructors && "".equals(name); }
   private boolean isStaticMethodToBeIgnored(int access) { return executionMode == 2 && isStatic(access); }
   private boolean isNativeMethodForDynamicMocking(int access) { return executionMode > 0 && isNative(access); }

   private void validateModificationOfNativeMethod(int access, String name)
   {
      if (isNative(access) && !Startup.isJava6OrLater()) {
         throw new IllegalArgumentException(
            "Mocking of native methods not supported under JDK 1.5; please filter out method \"" +
            name + "\", or run under JDK 1.6+");
      }
   }

   private int determineAppropriateExecutionMode(int access, boolean visitingConstructor)
   {
      if (executionMode == 2) {
         if (visitingConstructor) {
            return ignoreConstructors ? 0 : 1;
         }
         else if (isStatic(access)) {
            return 0;
         }
      }

      return executionMode;
   }

   private MethodVisitor generateCallToHandlerThroughMockingBridge(
      int access, String genericSignature, String[] exceptions, String internalClassName, int executionMode)
   {
      generateCodeToObtainInstanceOfMockingBridge(MockedBridge.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++, access);
      generateCodeToFillArrayElement(i++, internalClassName);
      generateCodeToFillArrayElement(i++, methodName);
      generateCodeToFillArrayElement(i++, methodDesc);
      generateCodeToFillArrayElement(i++, genericSignature);
      generateCodeToFillArrayElement(i++, getListOfExceptionsAsSingleString(exceptions));
      generateCodeToFillArrayElement(i++, executionMode);

      generateCodeToPassMethodArgumentsAsVarargs(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 JRE constructor when the call was not meant to be mocked.
      return copyOriginalImplementationCode(access);
   }

   private MethodVisitor copyOriginalImplementationCode(int access)
   {
      if (isNative(access)) {
         generateEmptyImplementation(methodDesc);
         return methodAnnotationsVisitor;
      }

      return new DynamicModifier();
   }

   private class DynamicModifier extends MethodVisitor
   {
      DynamicModifier() { super(mw); }

      @Override
      public final void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int idx)
      {
         registerParameterName(name, idx);

         // For some reason, the start position for "this" gets displaced by bytecode inserted at the beginning,
         // in a method modified by the EMMA tool. If not treated, this causes a ClassFormatError.
         if (end.position > 0 && start.position > end.position) {
            start.position = end.position;
         }

         // Ignores any local variable with required information missing, to avoid a VerifyError/ClassFormatError.
         if (start.position > 0 && end.position > 0) {
            super.visitLocalVariable(name, desc, signature, start, end, idx);
         }
      }
   }

   private final class DynamicConstructorModifier extends DynamicModifier
   {
      @Override
      public void visitMethodInsn(int opcode, String owner, String name, String desc)
      {
         disregardIfInvokingAnotherConstructor(opcode, owner, name, desc);
      }
   }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy