• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
DexmakerMockMaker()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
createMock(MockCreationSettings<T> settings, MockHandler handler)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
resetMock(Object mock, MockHandler newHandler, MockCreationSettings settings)119     public void resetMock(Object mock, MockHandler newHandler, MockCreationSettings settings) {
120         InvocationHandlerAdapter adapter = getInvocationHandlerAdapter(mock);
121         adapter.setHandler(newHandler);
122     }
123 
124     @Override
isTypeMockable(final Class<?> type)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