1 /* 2 * Copyright (c) 2021 Mockito contributors 3 * This program is made available under the terms of the MIT License. 4 */ 5 package org.mockito.internal.util; 6 7 import org.mockito.internal.exceptions.Reporter; 8 import org.mockito.internal.stubbing.answers.InvocationInfo; 9 import org.mockito.invocation.InvocationOnMock; 10 11 import java.lang.annotation.Annotation; 12 import java.lang.reflect.InvocationTargetException; 13 import java.lang.reflect.Method; 14 15 public class KotlinInlineClassUtil { 16 private static Class<Annotation> jvmInlineAnnotation; 17 18 static { 19 try { 20 jvmInlineAnnotation = (Class<Annotation>) Class.forName("kotlin.jvm.JvmInline"); 21 } catch (ClassNotFoundException e) { 22 // Do nothing: kotlin is pre 1.5.0 23 } 24 } 25 26 // When mocking function, returning inline class, its return type is 27 // underlying type. 28 // So, `thenReturn` calls fails, because of non-compatible types. isInlineClassWithAssignableUnderlyingType( Class<?> inlineClass, Class<?> underlyingType)29 public static boolean isInlineClassWithAssignableUnderlyingType( 30 Class<?> inlineClass, Class<?> underlyingType) { 31 try { 32 // Since 1.5.0 inline classes have @JvmInline annotation 33 if (jvmInlineAnnotation == null 34 || !inlineClass.isAnnotationPresent(jvmInlineAnnotation)) return false; 35 36 // All inline classes have 'box-impl' method, which accepts 37 // underlying type and returns inline class. 38 // Make sure that the current inline class is also the class that is compatible with the 39 // underlying type. 40 // If we don't make this check, then we would potentially pass a mock of inline type A 41 // into a method 42 // that accepts inline type B. 43 Object ignored = inlineClass.getDeclaredMethod("box-impl", underlyingType); 44 return true; 45 } catch (NoSuchMethodException e) { 46 return false; 47 } 48 } 49 unboxInlineClassIfPossible(Object boxedValue)50 private static Object unboxInlineClassIfPossible(Object boxedValue) { 51 Class<?> inlineClass = boxedValue.getClass(); 52 try { 53 Method unboxImpl = inlineClass.getDeclaredMethod("unbox-impl"); 54 return unboxImpl.invoke(boxedValue); 55 } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 56 throw Reporter.inlineClassWithoutUnboxImpl(inlineClass, e); 57 } 58 } 59 unboxUnderlyingValueIfNeeded(InvocationOnMock invocation, Object value)60 public static Object unboxUnderlyingValueIfNeeded(InvocationOnMock invocation, Object value) { 61 // Short path - Kotlin 1.5+ is not present. 62 if (value == null || jvmInlineAnnotation == null) { 63 return value; 64 } 65 66 Class<?> valueType = value.getClass(); 67 InvocationInfo invocationInfo = new InvocationInfo(invocation); 68 Class<?> returnType = invocationInfo.getMethod().getReturnType(); 69 if (valueType.isAssignableFrom(returnType)) return value; 70 71 if (isInlineClassWithAssignableUnderlyingType(valueType, returnType)) { 72 return unboxInlineClassIfPossible(value); 73 } else { 74 return value; 75 } 76 } 77 } 78