1/* 2 * Copyright (C) 2017 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 17package com.android.dx.mockito.inline; 18 19import android.os.AsyncTask; 20import android.os.Build; 21import android.util.ArraySet; 22 23import com.android.dx.stock.ProxyBuilder; 24import com.android.dx.stock.ProxyBuilder.MethodSetEntry; 25 26import org.mockito.Mockito; 27import org.mockito.exceptions.base.MockitoException; 28import org.mockito.internal.creation.instance.Instantiator; 29import org.mockito.internal.util.reflection.LenientCopyTool; 30import org.mockito.invocation.MockHandler; 31import org.mockito.mock.MockCreationSettings; 32import org.mockito.plugins.InstantiatorProvider; 33import org.mockito.plugins.MockMaker; 34 35import java.io.IOException; 36import java.io.InputStream; 37import java.lang.ref.Reference; 38import java.lang.ref.ReferenceQueue; 39import java.lang.ref.WeakReference; 40import java.lang.reflect.InvocationTargetException; 41import java.lang.reflect.Method; 42import java.lang.reflect.Modifier; 43import java.lang.reflect.Proxy; 44import java.util.AbstractMap; 45import java.util.Collection; 46import java.util.HashMap; 47import java.util.HashSet; 48import java.util.Map; 49import java.util.Set; 50 51/** 52 * Generates mock instances on Android's runtime that can mock final methods. 53 * 54 * <p>This is done by transforming the byte code of the classes to add method entry hooks. 55 */ 56public final class InlineDexmakerMockMaker implements MockMaker { 57 private static final String DISPATCHER_CLASS_NAME = 58 "com.android.dx.mockito.inline.MockMethodDispatcher"; 59 private static final String DISPATCHER_JAR = "dispatcher.jar"; 60 61 /** {@link com.android.dx.mockito.inline.JvmtiAgent} set up during one time init */ 62 private static final JvmtiAgent AGENT; 63 64 /** Error during one time init or {@code null} if init was successful*/ 65 private static final Throwable INITIALIZATION_ERROR; 66 67 /** 68 * Class injected into the bootstrap classloader. All entry hooks added to methods will call 69 * this class. 70 */ 71 private static final Class DISPATCHER_CLASS; 72 73 /* 74 * One time setup to allow the system to mocking via this mock maker. 75 */ 76 static { 77 JvmtiAgent agent; 78 Throwable initializationError = null; 79 Class dispatcherClass = null; 80 try { 81 try { 82 agent = new JvmtiAgent(); 83 84 try (InputStream is = InlineDexmakerMockMaker.class.getClassLoader() 85 .getResource(DISPATCHER_JAR).openStream()) { 86 agent.appendToBootstrapClassLoaderSearch(is); 87 } 88 89 try { 90 dispatcherClass = Class.forName(DISPATCHER_CLASS_NAME, true, 91 Object.class.getClassLoader()); 92 93 if (dispatcherClass == null) { 94 throw new IllegalStateException(DISPATCHER_CLASS_NAME 95 + " could not be loaded"); 96 } 97 } catch (ClassNotFoundException cnfe) { 98 throw new IllegalStateException( 99 "Mockito failed to inject the MockMethodDispatcher class into the " 100 + "bootstrap class loader\n\nIt seems like your current VM does not " 101 + "support the jvmti API correctly.", cnfe); 102 } 103 } catch (IOException ioe) { 104 throw new IllegalStateException( 105 "Mockito could not self-attach a jvmti agent to the current VM. This " 106 + "feature is required for inline mocking.\nThis error occured due to an " 107 + "I/O error during the creation of this agent: " + ioe + "\n\n" 108 + "Potentially, the current VM does not support the jvmti API correctly", 109 ioe); 110 } 111 112 // Blacklisted APIs were introduced in Android P: 113 // 114 // https://android-developers.googleblog.com/2018/02/ 115 // improving-stability-by-reducing-usage.html 116 // 117 // This feature prevents access to blacklisted fields and calling of blacklisted APIs 118 // if the calling class is not trusted. 119 Method allowHiddenApiReflectionFrom; 120 try { 121 Class vmDebug = Class.forName("dalvik.system.VMDebug"); 122 allowHiddenApiReflectionFrom = vmDebug.getDeclaredMethod( 123 "allowHiddenApiReflectionFrom", Class.class); 124 } catch (ClassNotFoundException | NoSuchMethodException e) { 125 throw new IllegalStateException("Cannot find " 126 + "VMDebug#allowHiddenApiReflectionFrom."); 127 } 128 129 // The LenientCopyTool copies the fields to a spy when creating the copy from an 130 // existing object. Some of the fields might be blacklisted. Marking the LenientCopyTool 131 // as trusted allows the tool to copy all fields, including the blacklisted ones. 132 try { 133 allowHiddenApiReflectionFrom.invoke(null, LenientCopyTool.class); 134 } catch (InvocationTargetException e) { 135 throw e.getCause(); 136 } 137 138 // The MockMethodAdvice is used by methods of spies to call the real methods. As the 139 // real methods might be blacklisted, this class needs to be marked as trusted. 140 try { 141 allowHiddenApiReflectionFrom.invoke(null, MockMethodAdvice.class); 142 } catch (InvocationTargetException e) { 143 throw e.getCause(); 144 } 145 } catch (Throwable throwable) { 146 agent = null; 147 initializationError = throwable; 148 } 149 150 AGENT = agent; 151 INITIALIZATION_ERROR = initializationError; 152 DISPATCHER_CLASS = dispatcherClass; 153 } 154 155 /** 156 * All currently active mocks. We modify the class's byte code. Some objects of the class are 157 * modified, some are not. This list helps the {@link MockMethodAdvice} help figure out if a 158 * object's method calls should be intercepted. 159 */ 160 private final Map<Object, InvocationHandlerAdapter> mocks; 161 162 /** 163 * Class doing the actual byte code transformation. 164 */ 165 private final ClassTransformer classTransformer; 166 167 /** 168 * Create a new mock maker. 169 */ 170 public InlineDexmakerMockMaker() { 171 if (INITIALIZATION_ERROR != null) { 172 throw new RuntimeException( 173 "Could not initialize inline mock maker.\n" 174 + "\n" 175 + "Release: Android " + Build.VERSION.RELEASE + " " + Build.VERSION.INCREMENTAL 176 + "Device: " + Build.BRAND + " " + Build.MODEL, INITIALIZATION_ERROR); 177 } 178 179 mocks = new MockMap(); 180 classTransformer = new ClassTransformer(AGENT, DISPATCHER_CLASS, mocks); 181 } 182 183 /** 184 * Get methods to proxy. 185 * 186 * <p>Only abstract methods will need to get proxied as all other methods will get an entry 187 * hook. 188 * 189 * @param settings description of the current mocking process. 190 * 191 * @return methods to proxy. 192 */ 193 private <T> Method[] getMethodsToProxy(MockCreationSettings<T> settings) { 194 Set<MethodSetEntry> abstractMethods = new HashSet<>(); 195 Set<MethodSetEntry> nonAbstractMethods = new HashSet<>(); 196 197 Class<?> superClass = settings.getTypeToMock(); 198 while (superClass != null) { 199 for (Method method : superClass.getDeclaredMethods()) { 200 if (Modifier.isAbstract(method.getModifiers()) 201 && !nonAbstractMethods.contains(new MethodSetEntry(method))) { 202 abstractMethods.add(new MethodSetEntry(method)); 203 } else { 204 nonAbstractMethods.add(new MethodSetEntry(method)); 205 } 206 } 207 208 superClass = superClass.getSuperclass(); 209 } 210 211 for (Class<?> i : settings.getTypeToMock().getInterfaces()) { 212 for (Method method : i.getMethods()) { 213 if (!nonAbstractMethods.contains(new MethodSetEntry(method))) { 214 abstractMethods.add(new MethodSetEntry(method)); 215 } 216 } 217 } 218 219 for (Class<?> i : settings.getExtraInterfaces()) { 220 for (Method method : i.getMethods()) { 221 if (!nonAbstractMethods.contains(new MethodSetEntry(method))) { 222 abstractMethods.add(new MethodSetEntry(method)); 223 } 224 } 225 } 226 227 Method[] methodsToProxy = new Method[abstractMethods.size()]; 228 int i = 0; 229 for (MethodSetEntry entry : abstractMethods) { 230 methodsToProxy[i++] = entry.originalMethod; 231 } 232 233 return methodsToProxy; 234 } 235 236 @Override 237 public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) { 238 Class<T> typeToMock = settings.getTypeToMock(); 239 Set<Class<?>> interfacesSet = settings.getExtraInterfaces(); 240 Class<?>[] extraInterfaces = interfacesSet.toArray(new Class[interfacesSet.size()]); 241 InvocationHandlerAdapter handlerAdapter = new InvocationHandlerAdapter(handler); 242 243 T mock; 244 if (typeToMock.isInterface()) { 245 // support interfaces via java.lang.reflect.Proxy 246 Class[] classesToMock = new Class[extraInterfaces.length + 1]; 247 classesToMock[0] = typeToMock; 248 System.arraycopy(extraInterfaces, 0, classesToMock, 1, extraInterfaces.length); 249 250 // newProxyInstance returns the type of typeToMock 251 mock = (T) Proxy.newProxyInstance(typeToMock.getClassLoader(), classesToMock, 252 handlerAdapter); 253 } else { 254 boolean subclassingRequired = !interfacesSet.isEmpty() 255 || Modifier.isAbstract(typeToMock.getModifiers()); 256 257 // Add entry hooks to non-abstract methods. 258 classTransformer.mockClass(MockFeatures.withMockFeatures(typeToMock, interfacesSet)); 259 260 Class<? extends T> proxyClass; 261 262 Instantiator instantiator = Mockito.framework().getPlugins() 263 .getDefaultPlugin(InstantiatorProvider.class).getInstantiator(settings); 264 265 if (subclassingRequired) { 266 try { 267 // support abstract methods via dexmaker's ProxyBuilder 268 proxyClass = ProxyBuilder.forClass(typeToMock).implementing(extraInterfaces) 269 .onlyMethods(getMethodsToProxy(settings)).withSharedClassLoader() 270 .buildProxyClass(); 271 } catch (RuntimeException e) { 272 throw e; 273 } catch (Exception e) { 274 throw new MockitoException("Failed to mock " + typeToMock, e); 275 } 276 277 try { 278 mock = instantiator.newInstance(proxyClass); 279 } catch (org.mockito.internal.creation.instance.InstantiationException e) { 280 throw new MockitoException("Unable to create mock instance of type '" 281 + proxyClass.getSuperclass().getSimpleName() + "'", e); 282 } 283 284 ProxyBuilder.setInvocationHandler(mock, handlerAdapter); 285 } else { 286 try { 287 mock = instantiator.newInstance(typeToMock); 288 } catch (org.mockito.internal.creation.instance.InstantiationException e) { 289 throw new MockitoException("Unable to create mock instance of type '" 290 + typeToMock.getSimpleName() + "'", e); 291 } 292 } 293 } 294 295 mocks.put(mock, handlerAdapter); 296 return mock; 297 } 298 299 @Override 300 public void resetMock(Object mock, MockHandler newHandler, MockCreationSettings settings) { 301 InvocationHandlerAdapter adapter = getInvocationHandlerAdapter(mock); 302 if (adapter != null) { 303 adapter.setHandler(newHandler); 304 } 305 } 306 307 @Override 308 public TypeMockability isTypeMockable(final Class<?> type) { 309 return new TypeMockability() { 310 @Override 311 public boolean mockable() { 312 return !type.isPrimitive() && type != String.class; 313 } 314 315 @Override 316 public String nonMockableReason() { 317 if (type.isPrimitive()) { 318 return "primitive type"; 319 } 320 321 if (type == String.class) { 322 return "string"; 323 } 324 325 return "not handled type"; 326 } 327 }; 328 } 329 330 @Override 331 public MockHandler getHandler(Object mock) { 332 InvocationHandlerAdapter adapter = getInvocationHandlerAdapter(mock); 333 return adapter != null ? adapter.getHandler() : null; 334 } 335 336 /** 337 * Get the {@link InvocationHandlerAdapter} registered for a mock. 338 * 339 * @param instance instance that might be mocked 340 * 341 * @return adapter for this mock, or {@code null} if instance is not mocked 342 */ 343 private InvocationHandlerAdapter getInvocationHandlerAdapter(Object instance) { 344 if (instance == null) { 345 return null; 346 } 347 348 return mocks.get(instance); 349 } 350 351 /** 352 * A map mock -> adapter that holds weak references to the mocks and cleans them up when a 353 * stale reference is found. 354 */ 355 private static class MockMap extends ReferenceQueue<Object> 356 implements Map<Object, InvocationHandlerAdapter> { 357 private static final int MIN_CLEAN_INTERVAL_MILLIS = 16000; 358 private static final int MAX_GET_WITHOUT_CLEAN = 16384; 359 360 private final Object lock = new Object(); 361 private static StrongKey cachedKey; 362 363 private HashMap<WeakKey, InvocationHandlerAdapter> adapters = new HashMap<>(); 364 365 /** 366 * The time we issues the last cleanup 367 */ 368 long mLastCleanup = 0; 369 370 /** 371 * If {@link #cleanStaleReferences} is currently cleaning stale references out of 372 * {@link #adapters} 373 */ 374 private boolean isCleaning = false; 375 376 /** 377 * The number of time {@link #get} was called without cleaning up stale references. 378 * {@link #get} is a method that is called often. 379 * 380 * We need to do periodic cleanups as we might never look at mocks at higher indexes and 381 * hence never realize that their references are stale. 382 */ 383 private int getCount = 0; 384 385 /** 386 * Try to get a recycled cached key. 387 * 388 * @param obj the reference the key wraps 389 * 390 * @return The recycled cached key or a new one 391 */ 392 private StrongKey createStrongKey(Object obj) { 393 synchronized (lock) { 394 if (cachedKey == null) { 395 cachedKey = new StrongKey(); 396 } 397 398 cachedKey.obj = obj; 399 StrongKey newKey = cachedKey; 400 cachedKey = null; 401 402 return newKey; 403 } 404 } 405 406 /** 407 * Recycle a key. The key should not be used afterwards 408 * 409 * @param key The key to recycle 410 */ 411 private void recycleStrongKey(StrongKey key) { 412 synchronized (lock) { 413 cachedKey = key; 414 } 415 } 416 417 @Override 418 public int size() { 419 return adapters.size(); 420 } 421 422 @Override 423 public boolean isEmpty() { 424 return adapters.isEmpty(); 425 } 426 427 @Override 428 public boolean containsKey(Object mock) { 429 synchronized (lock) { 430 StrongKey key = createStrongKey(mock); 431 boolean containsKey = adapters.containsKey(key); 432 recycleStrongKey(key); 433 434 return containsKey; 435 } 436 } 437 438 @Override 439 public boolean containsValue(Object adapter) { 440 synchronized (lock) { 441 return adapters.containsValue(adapter); 442 } 443 } 444 445 @Override 446 public InvocationHandlerAdapter get(Object mock) { 447 synchronized (lock) { 448 if (getCount > MAX_GET_WITHOUT_CLEAN) { 449 cleanStaleReferences(); 450 getCount = 0; 451 } else { 452 getCount++; 453 } 454 455 StrongKey key = createStrongKey(mock); 456 InvocationHandlerAdapter adapter = adapters.get(key); 457 recycleStrongKey(key); 458 459 return adapter; 460 } 461 } 462 463 /** 464 * Remove entries that reference a stale mock from {@link #adapters}. 465 */ 466 private void cleanStaleReferences() { 467 synchronized (lock) { 468 if (!isCleaning) { 469 if (System.currentTimeMillis() - MIN_CLEAN_INTERVAL_MILLIS < mLastCleanup) { 470 return; 471 } 472 473 isCleaning = true; 474 475 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { 476 @Override 477 public void run() { 478 synchronized (lock) { 479 while (true) { 480 Reference<?> ref = MockMap.this.poll(); 481 if (ref == null) { 482 break; 483 } 484 485 adapters.remove(ref); 486 } 487 488 mLastCleanup = System.currentTimeMillis(); 489 isCleaning = false; 490 } 491 } 492 }); 493 } 494 } 495 } 496 497 @Override 498 public InvocationHandlerAdapter put(Object mock, InvocationHandlerAdapter adapter) { 499 synchronized (lock) { 500 InvocationHandlerAdapter oldValue = remove(mock); 501 adapters.put(new WeakKey(mock), adapter); 502 503 return oldValue; 504 } 505 } 506 507 @Override 508 public InvocationHandlerAdapter remove(Object mock) { 509 synchronized (lock) { 510 StrongKey key = createStrongKey(mock); 511 InvocationHandlerAdapter adapter = adapters.remove(key); 512 recycleStrongKey(key); 513 514 return adapter; 515 } 516 } 517 518 @Override 519 public void putAll(Map<?, ? extends InvocationHandlerAdapter> map) { 520 synchronized (lock) { 521 for (Entry<?, ? extends InvocationHandlerAdapter> entry : map.entrySet()) { 522 put(entry.getKey(), entry.getValue()); 523 } 524 } 525 } 526 527 @Override 528 public void clear() { 529 synchronized (lock) { 530 adapters.clear(); 531 } 532 } 533 534 @Override 535 public Set<Object> keySet() { 536 synchronized (lock) { 537 Set<Object> mocks = new ArraySet<>(adapters.size()); 538 539 boolean hasStaleReferences = false; 540 for (WeakKey key : adapters.keySet()) { 541 Object mock = key.get(); 542 543 if (mock == null) { 544 hasStaleReferences = true; 545 } else { 546 mocks.add(mock); 547 } 548 } 549 550 if (hasStaleReferences) { 551 cleanStaleReferences(); 552 } 553 554 return mocks; 555 } 556 } 557 558 @Override 559 public Collection<InvocationHandlerAdapter> values() { 560 synchronized (lock) { 561 return adapters.values(); 562 } 563 } 564 565 @Override 566 public Set<Entry<Object, InvocationHandlerAdapter>> entrySet() { 567 synchronized (lock) { 568 Set<Entry<Object, InvocationHandlerAdapter>> entries = new ArraySet<>( 569 adapters.size()); 570 571 boolean hasStaleReferences = false; 572 for (Entry<WeakKey, InvocationHandlerAdapter> entry : adapters.entrySet()) { 573 Object mock = entry.getKey().get(); 574 575 if (mock == null) { 576 hasStaleReferences = true; 577 } else { 578 entries.add(new AbstractMap.SimpleEntry<>(mock, entry.getValue())); 579 } 580 } 581 582 if (hasStaleReferences) { 583 cleanStaleReferences(); 584 } 585 586 return entries; 587 } 588 } 589 590 /** 591 * A weakly referencing wrapper to a mock. 592 * 593 * Only equals other weak or strong keys where the mock is the same. 594 */ 595 private class WeakKey extends WeakReference<Object> { 596 private final int hashCode; 597 598 private WeakKey(/*@NonNull*/ Object obj) { 599 super(obj, MockMap.this); 600 601 // Cache the hashcode as the referenced object might disappear 602 hashCode = System.identityHashCode(obj); 603 } 604 605 @Override 606 public boolean equals(Object other) { 607 if (other == this) { 608 return true; 609 } 610 611 if (other == null) { 612 return false; 613 } 614 615 // Checking hashcode is cheap 616 if (other.hashCode() != hashCode) { 617 return false; 618 } 619 620 Object obj = get(); 621 622 if (obj == null) { 623 cleanStaleReferences(); 624 return false; 625 } 626 627 if (other instanceof WeakKey) { 628 Object otherObj = ((WeakKey) other).get(); 629 630 if (otherObj == null) { 631 cleanStaleReferences(); 632 return false; 633 } 634 635 return obj == otherObj; 636 } else if (other instanceof StrongKey) { 637 Object otherObj = ((StrongKey) other).obj; 638 return obj == otherObj; 639 } else { 640 return false; 641 } 642 } 643 644 @Override 645 public int hashCode() { 646 return hashCode; 647 } 648 } 649 650 /** 651 * A strongly referencing wrapper to a mock. 652 * 653 * Only equals other weak or strong keys where the mock is the same. 654 */ 655 private class StrongKey { 656 /*@NonNull*/ private Object obj; 657 658 @Override 659 public boolean equals(Object other) { 660 if (other instanceof WeakKey) { 661 Object otherObj = ((WeakKey) other).get(); 662 663 if (otherObj == null) { 664 cleanStaleReferences(); 665 return false; 666 } 667 668 return obj == otherObj; 669 } else if (other instanceof StrongKey) { 670 return this.obj == ((StrongKey)other).obj; 671 } else { 672 return false; 673 } 674 } 675 676 @Override 677 public int hashCode() { 678 return System.identityHashCode(obj); 679 } 680 } 681 } 682} 683