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