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