<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