Home | History | Annotate | Download | only in mockito
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.dx.mockito;
     18 
     19 import android.os.Build;
     20 import android.util.Log;
     21 
     22 import com.android.dx.stock.ProxyBuilder;
     23 import org.mockito.exceptions.base.MockitoException;
     24 import org.mockito.exceptions.stacktrace.StackTraceCleaner;
     25 import org.mockito.internal.util.reflection.LenientCopyTool;
     26 import org.mockito.invocation.MockHandler;
     27 import org.mockito.mock.MockCreationSettings;
     28 import org.mockito.plugins.MockMaker;
     29 import org.mockito.plugins.StackTraceCleanerProvider;
     30 
     31 import java.lang.reflect.InvocationHandler;
     32 import java.lang.reflect.InvocationTargetException;
     33 import java.lang.reflect.Method;
     34 import java.lang.reflect.Modifier;
     35 import java.lang.reflect.Proxy;
     36 import java.util.Set;
     37 
     38 /**
     39  * Generates mock instances on Android's runtime.
     40  */
     41 public final class DexmakerMockMaker implements MockMaker, StackTraceCleanerProvider {
     42     private static final String LOG_TAG = DexmakerMockMaker.class.getSimpleName();
     43 
     44     private final UnsafeAllocator unsafeAllocator = UnsafeAllocator.create();
     45 
     46     public DexmakerMockMaker() {
     47         if (Build.VERSION.SDK_INT >= 28) {
     48             // Blacklisted APIs were introduced in Android P:
     49             //
     50             // https://android-developers.googleblog.com/2018/02/
     51             // improving-stability-by-reducing-usage.html
     52             //
     53             // This feature prevents access to blacklisted fields and calling of blacklisted APIs
     54             // if the calling class is not trusted.
     55             Method allowHiddenApiReflectionFromMethod;
     56             try {
     57                 Class vmDebug = Class.forName("dalvik.system.VMDebug");
     58                 allowHiddenApiReflectionFromMethod = vmDebug.getDeclaredMethod(
     59                         "allowHiddenApiReflectionFrom", Class.class);
     60             } catch (ClassNotFoundException | NoSuchMethodException e) {
     61                 throw new IllegalStateException(
     62                         "Cannot find VMDebug#allowHiddenApiReflectionFrom. Method is needed to "
     63                                 + "allow spies to copy blacklisted fields.");
     64             }
     65 
     66             // The LenientCopyTool copies the fields to a spy when creating the copy from an
     67             // existing object. Some of the fields might be blacklisted. Marking the LenientCopyTool
     68             // as trusted allows the tool to copy all fields, including the blacklisted ones.
     69             try {
     70                 allowHiddenApiReflectionFromMethod.invoke(null, LenientCopyTool.class);
     71             } catch (InvocationTargetException | IllegalAccessException e) {
     72                 Log.w(LOG_TAG, "Cannot allow LenientCopyTool to copy spies of blacklisted fields. "
     73                         + "This might break spying on system classes.");
     74             }
     75         }
     76     }
     77 
     78     @Override
     79     public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {
     80         Class<T> typeToMock = settings.getTypeToMock();
     81         Set<Class<?>> interfacesSet = settings.getExtraInterfaces();
     82         Class<?>[] extraInterfaces = interfacesSet.toArray(new Class[interfacesSet.size()]);
     83         InvocationHandler invocationHandler = new InvocationHandlerAdapter(handler);
     84 
     85         if (typeToMock.isInterface()) {
     86             // support interfaces via java.lang.reflect.Proxy
     87             Class[] classesToMock = new Class[extraInterfaces.length + 1];
     88             classesToMock[0] = typeToMock;
     89             System.arraycopy(extraInterfaces, 0, classesToMock, 1, extraInterfaces.length);
     90             // newProxyInstance returns the type of typeToMock
     91             @SuppressWarnings("unchecked")
     92             T mock = (T) Proxy.newProxyInstance(typeToMock.getClassLoader(), classesToMock, invocationHandler);
     93             return mock;
     94 
     95         } else {
     96             // support concrete classes via dexmaker's ProxyBuilder
     97             try {
     98                 ProxyBuilder b = ProxyBuilder.forClass(typeToMock)
     99                         .implementing(extraInterfaces);
    100 
    101                 if (Boolean.parseBoolean(
    102                         System.getProperty("dexmaker.share_classloader", "false"))) {
    103                     b.withSharedClassLoader();
    104                 }
    105 
    106                 Class<? extends T> proxyClass = b.buildProxyClass();
    107                 T mock = unsafeAllocator.newInstance(proxyClass);
    108                 ProxyBuilder.setInvocationHandler(mock, invocationHandler);
    109                 return mock;
    110             } catch (RuntimeException e) {
    111                 throw e;
    112             } catch (Exception e) {
    113                 throw new MockitoException("Failed to mock " + typeToMock, e);
    114             }
    115         }
    116     }
    117 
    118     @Override
    119     public void resetMock(Object mock, MockHandler newHandler, MockCreationSettings settings) {
    120         InvocationHandlerAdapter adapter = getInvocationHandlerAdapter(mock);
    121         adapter.setHandler(newHandler);
    122     }
    123 
    124     @Override
    125     public TypeMockability isTypeMockable(final Class<?> type) {
    126         return new TypeMockability() {
    127             @Override
    128             public boolean mockable() {
    129                 return !type.isPrimitive() && !Modifier.isFinal(type.getModifiers());
    130             }
    131 
    132             @Override
    133             public String nonMockableReason() {
    134                 if (type.isPrimitive()) {
    135                     return "primitive type";
    136                 }
    137 
    138                 if (Modifier.isFinal(type.getModifiers())) {
    139                     return "final or anonymous class";
    140                 }
    141 
    142                 return "not handled type";
    143             }
    144         };
    145     }
    146 
    147     @Override
    148     public MockHandler getHandler(Object mock) {
    149         InvocationHandlerAdapter adapter = getInvocationHandlerAdapter(mock);
    150         return adapter != null ? adapter.getHandler() : null;
    151     }
    152 
    153     @Override
    154     public StackTraceCleaner getStackTraceCleaner(final StackTraceCleaner defaultCleaner) {
    155         return new StackTraceCleaner() {
    156             @Override
    157             public boolean isIn(StackTraceElement candidate) {
    158                 String className = candidate.getClassName();
    159 
    160                 return defaultCleaner.isIn(candidate)
    161                         && !className.endsWith("_Proxy") // dexmaker class proxies
    162                         && !className.startsWith("$Proxy") // dalvik interface proxies
    163                         && !className.startsWith("java.lang.reflect.Proxy")
    164                         && !(className.startsWith("com.android.dx.mockito.")
    165                              // Do not clean unit tests
    166                              && !className.startsWith("com.android.dx.mockito.tests"));
    167             }
    168         };
    169     }
    170 
    171     private InvocationHandlerAdapter getInvocationHandlerAdapter(Object mock) {
    172         if (mock == null) {
    173             return null;
    174         }
    175         if (Proxy.isProxyClass(mock.getClass())) {
    176             InvocationHandler invocationHandler = Proxy.getInvocationHandler(mock);
    177             return invocationHandler instanceof InvocationHandlerAdapter
    178                     ? (InvocationHandlerAdapter) invocationHandler
    179                     : null;
    180         }
    181 
    182         if (ProxyBuilder.isProxyClass(mock.getClass())) {
    183             InvocationHandler invocationHandler = ProxyBuilder.getInvocationHandler(mock);
    184             return invocationHandler instanceof InvocationHandlerAdapter
    185                     ? (InvocationHandlerAdapter) invocationHandler
    186                     : null;
    187         }
    188 
    189         return null;
    190     }
    191 }
    192