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