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_OR_CODENAME + " " 184 + Build.VERSION.INCREMENTAL 185 + "Device: " + Build.BRAND + " " + Build.MODEL, INITIALIZATION_ERROR); 186 } 187 188 mocks = new MockMap(); 189 classTransformer = new ClassTransformer(AGENT, DISPATCHER_CLASS, mocks); 190 } 191 192 /** 193 * Get methods to proxy. 194 * 195 * <p>Only abstract methods will need to get proxied as all other methods will get an entry 196 * hook. 197 * 198 * @param settings description of the current mocking process. 199 * 200 * @return methods to proxy. 201 */ getMethodsToProxy(MockCreationSettings<T> settings)202 private <T> Method[] getMethodsToProxy(MockCreationSettings<T> settings) { 203 Set<MethodSetEntry> abstractMethods = new HashSet<>(); 204 Set<MethodSetEntry> nonAbstractMethods = new HashSet<>(); 205 206 Class<?> superClass = settings.getTypeToMock(); 207 while (superClass != null) { 208 for (Method method : superClass.getDeclaredMethods()) { 209 if (Modifier.isAbstract(method.getModifiers()) 210 && !nonAbstractMethods.contains(new MethodSetEntry(method))) { 211 abstractMethods.add(new MethodSetEntry(method)); 212 } else { 213 nonAbstractMethods.add(new MethodSetEntry(method)); 214 } 215 } 216 217 superClass = superClass.getSuperclass(); 218 } 219 220 for (Class<?> i : settings.getTypeToMock().getInterfaces()) { 221 for (Method method : i.getMethods()) { 222 if (!nonAbstractMethods.contains(new MethodSetEntry(method))) { 223 abstractMethods.add(new MethodSetEntry(method)); 224 } 225 } 226 } 227 228 for (Class<?> i : settings.getExtraInterfaces()) { 229 for (Method method : i.getMethods()) { 230 if (!nonAbstractMethods.contains(new MethodSetEntry(method))) { 231 abstractMethods.add(new MethodSetEntry(method)); 232 } 233 } 234 } 235 236 Method[] methodsToProxy = new Method[abstractMethods.size()]; 237 int i = 0; 238 for (MethodSetEntry entry : abstractMethods) { 239 methodsToProxy[i++] = entry.originalMethod; 240 } 241 242 return methodsToProxy; 243 } 244 245 @Override createMock(MockCreationSettings<T> settings, MockHandler handler)246 public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) { 247 Class<T> typeToMock = settings.getTypeToMock(); 248 Set<Class<?>> interfacesSet = settings.getExtraInterfaces(); 249 Class<?>[] extraInterfaces = interfacesSet.toArray(new Class[interfacesSet.size()]); 250 InvocationHandlerAdapter handlerAdapter = new InvocationHandlerAdapter(handler); 251 252 T mock; 253 if (typeToMock.isInterface()) { 254 // support interfaces via java.lang.reflect.Proxy 255 Class[] classesToMock = new Class[extraInterfaces.length + 1]; 256 classesToMock[0] = typeToMock; 257 System.arraycopy(extraInterfaces, 0, classesToMock, 1, extraInterfaces.length); 258 259 // newProxyInstance returns the type of typeToMock 260 mock = (T) Proxy.newProxyInstance(typeToMock.getClassLoader(), classesToMock, 261 handlerAdapter); 262 } else { 263 boolean subclassingRequired = !interfacesSet.isEmpty() 264 || Modifier.isAbstract(typeToMock.getModifiers()); 265 266 // Add entry hooks to non-abstract methods. 267 classTransformer.mockClass(MockFeatures.withMockFeatures(typeToMock, interfacesSet)); 268 269 Class<? extends T> proxyClass; 270 271 Instantiator instantiator = Mockito.framework().getPlugins() 272 .getDefaultPlugin(InstantiatorProvider2.class).getInstantiator(settings); 273 274 if (subclassingRequired) { 275 try { 276 // support abstract methods via dexmaker's ProxyBuilder 277 ProxyBuilder builder = ProxyBuilder.forClass(typeToMock).implementing 278 (extraInterfaces) 279 .onlyMethods(getMethodsToProxy(settings)).withSharedClassLoader(); 280 281 if (Build.VERSION.SDK_INT >= 28) { 282 builder.markTrusted(); 283 } 284 285 proxyClass = builder.buildProxyClass(); 286 } catch (RuntimeException e) { 287 throw e; 288 } catch (Exception e) { 289 throw new MockitoException("Failed to mock " + typeToMock, e); 290 } 291 292 try { 293 mock = instantiator.newInstance(proxyClass); 294 } catch (org.mockito.creation.instance.InstantiationException e) { 295 throw new MockitoException("Unable to create mock instance of type '" 296 + proxyClass.getSuperclass().getSimpleName() + "'", e); 297 } 298 299 ProxyBuilder.setInvocationHandler(mock, handlerAdapter); 300 } else { 301 if (settings.getSpiedInstance() != null 302 && onSpyInProgressInstance.get() == settings.getSpiedInstance()) { 303 mock = (T) onSpyInProgressInstance.get(); 304 } else { 305 try { 306 mock = instantiator.newInstance(typeToMock); 307 } catch (org.mockito.creation.instance.InstantiationException e) { 308 throw new MockitoException("Unable to create mock instance of type '" 309 + typeToMock.getSimpleName() + "'", e); 310 } 311 } 312 } 313 } 314 315 mocks.put(mock, handlerAdapter); 316 return mock; 317 } 318 319 @Override resetMock(Object mock, MockHandler newHandler, MockCreationSettings settings)320 public void resetMock(Object mock, MockHandler newHandler, MockCreationSettings settings) { 321 InvocationHandlerAdapter adapter = getInvocationHandlerAdapter(mock); 322 if (adapter != null) { 323 adapter.setHandler(newHandler); 324 } 325 } 326 327 @Override isTypeMockable(final Class<?> type)328 public TypeMockability isTypeMockable(final Class<?> type) { 329 return new TypeMockability() { 330 @Override 331 public boolean mockable() { 332 return !type.isPrimitive() && type != String.class; 333 } 334 335 @Override 336 public String nonMockableReason() { 337 if (type.isPrimitive()) { 338 return "primitive type"; 339 } 340 341 if (type == String.class) { 342 return "string"; 343 } 344 345 return "not handled type"; 346 } 347 }; 348 } 349 350 @Override 351 public void clearMock(Object mock) { 352 mocks.remove(mock); 353 } 354 355 @Override 356 public void clearAllMocks() { 357 mocks.clear(); 358 } 359 360 @Override 361 public MockHandler getHandler(Object mock) { 362 InvocationHandlerAdapter adapter = getInvocationHandlerAdapter(mock); 363 return adapter != null ? adapter.getHandler() : null; 364 } 365 366 /** 367 * Get the {@link InvocationHandlerAdapter} registered for a mock. 368 * 369 * @param instance instance that might be mocked 370 * 371 * @return adapter for this mock, or {@code null} if instance is not mocked 372 */ 373 private InvocationHandlerAdapter getInvocationHandlerAdapter(Object instance) { 374 if (instance == null) { 375 return null; 376 } 377 378 return mocks.get(instance); 379 } 380 381 /** 382 * A map mock -> adapter that holds weak references to the mocks and cleans them up when a 383 * stale reference is found. 384 */ 385 private static class MockMap extends ReferenceQueue<Object> 386 implements Map<Object, InvocationHandlerAdapter> { 387 private static final int MIN_CLEAN_INTERVAL_MILLIS = 16000; 388 private static final int MAX_GET_WITHOUT_CLEAN = 16384; 389 390 private final Object lock = new Object(); 391 private StrongKey cachedKey; 392 393 private HashMap<WeakKey, InvocationHandlerAdapter> adapters = new HashMap<>(); 394 395 /** 396 * The time we issues the last cleanup 397 */ 398 long mLastCleanup = 0; 399 400 /** 401 * If {@link #cleanStaleReferences} is currently cleaning stale references out of 402 * {@link #adapters} 403 */ 404 private boolean isCleaning = false; 405 406 /** 407 * The number of time {@link #get} was called without cleaning up stale references. 408 * {@link #get} is a method that is called often. 409 * 410 * We need to do periodic cleanups as we might never look at mocks at higher indexes and 411 * hence never realize that their references are stale. 412 */ 413 private int getCount = 0; 414 415 /** 416 * Try to get a recycled cached key. 417 * 418 * @param obj the reference the key wraps 419 * 420 * @return The recycled cached key or a new one 421 */ 422 private StrongKey createStrongKey(Object obj) { 423 synchronized (lock) { 424 if (cachedKey == null) { 425 cachedKey = new StrongKey(); 426 } 427 428 cachedKey.obj = obj; 429 StrongKey newKey = cachedKey; 430 cachedKey = null; 431 432 return newKey; 433 } 434 } 435 436 /** 437 * Recycle a key. The key should not be used afterwards 438 * 439 * @param key The key to recycle 440 */ 441 private void recycleStrongKey(StrongKey key) { 442 synchronized (lock) { 443 cachedKey = key; 444 } 445 } 446 447 @Override 448 public int size() { 449 return adapters.size(); 450 } 451 452 @Override 453 public boolean isEmpty() { 454 return adapters.isEmpty(); 455 } 456 457 @SuppressWarnings("CollectionIncompatibleType") 458 @Override 459 public boolean containsKey(Object mock) { 460 synchronized (lock) { 461 StrongKey key = createStrongKey(mock); 462 boolean containsKey = adapters.containsKey(key); 463 recycleStrongKey(key); 464 465 return containsKey; 466 } 467 } 468 469 @Override 470 public boolean containsValue(Object adapter) { 471 synchronized (lock) { 472 return adapters.containsValue(adapter); 473 } 474 } 475 476 @SuppressWarnings("CollectionIncompatibleType") 477 @Override 478 public InvocationHandlerAdapter get(Object mock) { 479 synchronized (lock) { 480 if (getCount > MAX_GET_WITHOUT_CLEAN) { 481 cleanStaleReferences(); 482 getCount = 0; 483 } else { 484 getCount++; 485 } 486 487 StrongKey key = createStrongKey(mock); 488 InvocationHandlerAdapter adapter = adapters.get(key); 489 recycleStrongKey(key); 490 491 return adapter; 492 } 493 } 494 495 /** 496 * Remove entries that reference a stale mock from {@link #adapters}. 497 */ 498 private void cleanStaleReferences() { 499 synchronized (lock) { 500 if (!isCleaning) { 501 if (System.currentTimeMillis() - MIN_CLEAN_INTERVAL_MILLIS < mLastCleanup) { 502 return; 503 } 504 505 isCleaning = true; 506 507 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { 508 @Override 509 public void run() { 510 synchronized (lock) { 511 while (true) { 512 Reference<?> ref = MockMap.this.poll(); 513 if (ref == null) { 514 break; 515 } 516 517 adapters.remove(ref); 518 } 519 520 mLastCleanup = System.currentTimeMillis(); 521 isCleaning = false; 522 } 523 } 524 }); 525 } 526 } 527 } 528 529 @Override 530 public InvocationHandlerAdapter put(Object mock, InvocationHandlerAdapter adapter) { 531 synchronized (lock) { 532 InvocationHandlerAdapter oldValue = remove(mock); 533 adapters.put(new WeakKey(mock), adapter); 534 535 return oldValue; 536 } 537 } 538 539 @SuppressWarnings("CollectionIncompatibleType") 540 @Override 541 public InvocationHandlerAdapter remove(Object mock) { 542 synchronized (lock) { 543 StrongKey key = createStrongKey(mock); 544 InvocationHandlerAdapter adapter = adapters.remove(key); 545 recycleStrongKey(key); 546 547 return adapter; 548 } 549 } 550 551 @Override 552 public void putAll(Map<?, ? extends InvocationHandlerAdapter> map) { 553 synchronized (lock) { 554 for (Entry<?, ? extends InvocationHandlerAdapter> entry : map.entrySet()) { 555 put(entry.getKey(), entry.getValue()); 556 } 557 } 558 } 559 560 @Override 561 public void clear() { 562 synchronized (lock) { 563 adapters.clear(); 564 } 565 } 566 567 @Override 568 public Set<Object> keySet() { 569 synchronized (lock) { 570 Set<Object> mocks = new ArraySet<>(adapters.size()); 571 572 boolean hasStaleReferences = false; 573 for (WeakKey key : adapters.keySet()) { 574 Object mock = key.get(); 575 576 if (mock == null) { 577 hasStaleReferences = true; 578 } else { 579 mocks.add(mock); 580 } 581 } 582 583 if (hasStaleReferences) { 584 cleanStaleReferences(); 585 } 586 587 return mocks; 588 } 589 } 590 591 @Override 592 public Collection<InvocationHandlerAdapter> values() { 593 synchronized (lock) { 594 return adapters.values(); 595 } 596 } 597 598 @Override 599 public Set<Entry<Object, InvocationHandlerAdapter>> entrySet() { 600 synchronized (lock) { 601 Set<Entry<Object, InvocationHandlerAdapter>> entries = new ArraySet<>( 602 adapters.size()); 603 604 boolean hasStaleReferences = false; 605 for (Entry<WeakKey, InvocationHandlerAdapter> entry : adapters.entrySet()) { 606 Object mock = entry.getKey().get(); 607 608 if (mock == null) { 609 hasStaleReferences = true; 610 } else { 611 entries.add(new AbstractMap.SimpleEntry<>(mock, entry.getValue())); 612 } 613 } 614 615 if (hasStaleReferences) { 616 cleanStaleReferences(); 617 } 618 619 return entries; 620 } 621 } 622 623 /** 624 * A weakly referencing wrapper to a mock. 625 * 626 * Only equals other weak or strong keys where the mock is the same. 627 */ 628 private class WeakKey extends WeakReference<Object> { 629 private final int hashCode; 630 631 private WeakKey(/*@NonNull*/ Object obj) { 632 super(obj, MockMap.this); 633 634 // Cache the hashcode as the referenced object might disappear 635 hashCode = System.identityHashCode(obj); 636 } 637 638 @Override 639 public boolean equals(Object other) { 640 if (other == this) { 641 return true; 642 } 643 644 if (other == null) { 645 return false; 646 } 647 648 // Checking hashcode is cheap 649 if (other.hashCode() != hashCode) { 650 return false; 651 } 652 653 Object obj = get(); 654 655 if (obj == null) { 656 cleanStaleReferences(); 657 return false; 658 } 659 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 Object otherObj = ((StrongKey) other).obj; 671 return obj == otherObj; 672 } else { 673 return false; 674 } 675 } 676 677 @Override 678 public int hashCode() { 679 return hashCode; 680 } 681 } 682 683 /** 684 * A strongly referencing wrapper to a mock. 685 * 686 * Only equals other weak or strong keys where the mock is the same. 687 */ 688 private class StrongKey { 689 /*@NonNull*/ private Object obj; 690 691 @Override 692 public boolean equals(Object other) { 693 if (other instanceof WeakKey) { 694 Object otherObj = ((WeakKey) other).get(); 695 696 if (otherObj == null) { 697 cleanStaleReferences(); 698 return false; 699 } 700 701 return obj == otherObj; 702 } else if (other instanceof StrongKey) { 703 return this.obj == ((StrongKey)other).obj; 704 } else { 705 return false; 706 } 707 } 708 709 @Override 710 public int hashCode() { 711 return System.identityHashCode(obj); 712 } 713 } 714 } 715 } 716