<lambda>null1 package kotlinx.coroutines.internal
2 
3 import android.annotation.SuppressLint
4 import kotlinx.coroutines.*
5 import java.lang.reflect.*
6 import java.util.*
7 import java.util.concurrent.locks.*
8 import kotlin.concurrent.*
9 
10 private val throwableFields = Throwable::class.java.fieldsCountOrDefault(-1)
11 private typealias Ctor = (Throwable) -> Throwable?
12 
13 private val ctorCache = try {
14     if (ANDROID_DETECTED) WeakMapCtorCache
15     else ClassValueCtorCache
16 } catch (e: Throwable) {
17     // Fallback on Java 6 or exotic setups
18     WeakMapCtorCache
19 }
20 
21 @Suppress("UNCHECKED_CAST")
tryCopyExceptionnull22 internal fun <E : Throwable> tryCopyException(exception: E): E? {
23     // Fast path for CopyableThrowable
24     if (exception is CopyableThrowable<*>) {
25         return runCatching { exception.createCopy() as E? }.getOrNull()
26     }
27     return ctorCache.get(exception.javaClass).invoke(exception) as E?
28 }
29 
createConstructornull30 private fun <E : Throwable> createConstructor(clz: Class<E>): Ctor {
31     val nullResult: Ctor = { null } // Pre-cache class
32     // Skip reflective copy if an exception has additional fields (that are typically populated in user-defined constructors)
33     if (throwableFields != clz.fieldsCountOrDefault(0)) return nullResult
34     /*
35      * Try to reflectively find constructor(message, cause), constructor(message), constructor(cause), or constructor(),
36      * in that order of priority.
37      * Exceptions are shared among coroutines, so we should copy exception before recovering current stacktrace.
38      *
39      * By default, Java's reflection iterates over ctors in the source-code order and the sorting is stable, so we can
40      * not rely on the order of iteration. Instead, we assign a unique priority to each ctor type.
41      */
42     return clz.constructors.map { constructor ->
43         val p = constructor.parameterTypes
44         when (p.size) {
45             2 -> when {
46                 p[0] == String::class.java && p[1] == Throwable::class.java ->
47                     safeCtor { e -> constructor.newInstance(e.message, e) as Throwable } to 3
48                 else -> null to -1
49             }
50             1 -> when (p[0]) {
51                 String::class.java ->
52                     safeCtor { e -> (constructor.newInstance(e.message) as Throwable).also { it.initCause(e) } } to 2
53                 Throwable::class.java ->
54                     safeCtor { e -> constructor.newInstance(e) as Throwable } to 1
55                 else -> null to -1
56             }
57             0 -> safeCtor { e -> (constructor.newInstance() as Throwable).also { it.initCause(e) } } to 0
58             else -> null to -1
59         }
60     }.maxByOrNull(Pair<*, Int>::second)?.first ?: nullResult
61 }
62 
safeCtornull63 private fun safeCtor(block: (Throwable) -> Throwable): Ctor = { e ->
64     runCatching {
65         val result = block(e)
66         /*
67          * Verify that the new exception has the same message as the original one (bail out if not, see #1631)
68          * or if the new message complies the contract from `Throwable(cause).message` contract.
69          */
70         if (e.message != result.message && result.message != e.toString()) null
71         else result
72     }.getOrNull()
73 }
74 
fieldsCountOrDefaultnull75 private fun Class<*>.fieldsCountOrDefault(defaultValue: Int) =
76     kotlin.runCatching { fieldsCount() }.getOrDefault(defaultValue)
77 
fieldsCountnull78 private tailrec fun Class<*>.fieldsCount(accumulator: Int = 0): Int {
79     val fieldsCount = declaredFields.count { !Modifier.isStatic(it.modifiers) }
80     val totalFields = accumulator + fieldsCount
81     val superClass = superclass ?: return totalFields
82     return superClass.fieldsCount(totalFields)
83 }
84 
85 internal abstract class CtorCache {
getnull86     abstract fun get(key: Class<out Throwable>): Ctor
87 }
88 
89 private object WeakMapCtorCache : CtorCache() {
90     private val cacheLock = ReentrantReadWriteLock()
91     private val exceptionCtors: WeakHashMap<Class<out Throwable>, Ctor> = WeakHashMap()
92 
93     override fun get(key: Class<out Throwable>): Ctor {
94         cacheLock.read { exceptionCtors[key]?.let { return it } }
95         cacheLock.write {
96             exceptionCtors[key]?.let { return it }
97             return createConstructor(key).also { exceptionCtors[key] = it }
98         }
99     }
100 }
101 
102 @IgnoreJreRequirement
103 @SuppressLint("NewApi")
104 private object ClassValueCtorCache : CtorCache() {
105     private val cache = object : ClassValue<Ctor>() {
computeValuenull106         override fun computeValue(type: Class<*>?): Ctor {
107             @Suppress("UNCHECKED_CAST")
108             return createConstructor(type as Class<out Throwable>)
109         }
110     }
111 
getnull112     override fun get(key: Class<out Throwable>): Ctor = cache.get(key)
113 }
114