@@ -11,44 +11,42 @@ import java.util.concurrent.locks.*
11
11
import kotlin.concurrent.*
12
12
13
13
private val throwableFields = Throwable ::class .java.fieldsCountOrDefault(- 1 )
14
- private val cacheLock = ReentrantReadWriteLock ()
15
14
private typealias Ctor = (Throwable ) -> Throwable ?
16
- // Replace it with ClassValue when Java 6 support is over
17
- private val exceptionCtors: WeakHashMap <Class <out Throwable >, Ctor > = WeakHashMap ()
15
+
16
+ private val ctorCache = try {
17
+ if (ANDROID_DETECTED ) WeakMapCtorCache
18
+ else ClassValueCtorCache
19
+ } catch (e: Throwable ) {
20
+ // Fallback on Java 6 or exotic setups
21
+ WeakMapCtorCache
22
+ }
18
23
19
24
@Suppress(" UNCHECKED_CAST" )
20
25
internal fun <E : Throwable > tryCopyException (exception : E ): E ? {
21
26
// Fast path for CopyableThrowable
22
27
if (exception is CopyableThrowable <* >) {
23
28
return runCatching { exception.createCopy() as E ? }.getOrNull()
24
29
}
25
- // Use cached ctor if found
26
- cacheLock.read { exceptionCtors[exception.javaClass] }?.let { cachedCtor ->
27
- return cachedCtor(exception) as E ?
28
- }
29
- /*
30
- * Skip reflective copy if an exception has additional fields (that are usually populated in user-defined constructors)
31
- */
32
- if (throwableFields != exception.javaClass.fieldsCountOrDefault(0 )) {
33
- cacheLock.write { exceptionCtors[exception.javaClass] = { null } }
34
- return null
35
- }
30
+ return ctorCache.get(exception.javaClass).invoke(exception) as E ?
31
+ }
32
+
33
+ private fun <E : Throwable > createConstructor (clz : Class <E >): Ctor {
34
+ val nullResult: Ctor = { null } // Pre-cache class
35
+ // Skip reflective copy if an exception has additional fields (that are usually populated in user-defined constructors)
36
+ if (throwableFields != clz.fieldsCountOrDefault(0 )) return nullResult
36
37
/*
37
- * Try to reflectively find constructor(), constructor(message, cause), constructor(cause) or constructor(message).
38
- * Exceptions are shared among coroutines, so we should copy exception before recovering current stacktrace.
39
- */
40
- var ctor: Ctor ? = null
41
- val constructors = exception.javaClass.constructors.sortedByDescending { it.parameterTypes.size }
38
+ * Try to reflectively find constructor(), constructor(message, cause), constructor(cause) or constructor(message).
39
+ * Exceptions are shared among coroutines, so we should copy exception before recovering current stacktrace.
40
+ */
41
+ val constructors = clz.constructors.sortedByDescending { it.parameterTypes.size }
42
42
for (constructor in constructors) {
43
- ctor = createConstructor (constructor )
44
- if (ctor != null ) break
43
+ val result = createSafeConstructor (constructor )
44
+ if (result != null ) return result
45
45
}
46
- // Store the resulting ctor to cache
47
- cacheLock.write { exceptionCtors[exception.javaClass] = ctor ? : { null } }
48
- return ctor?.invoke(exception) as E ?
46
+ return nullResult
49
47
}
50
48
51
- private fun createConstructor (constructor : Constructor <* >): Ctor ? {
49
+ private fun createSafeConstructor (constructor : Constructor <* >): Ctor ? {
52
50
val p = constructor .parameterTypes
53
51
return when (p.size) {
54
52
2 -> when {
@@ -71,11 +69,41 @@ private fun createConstructor(constructor: Constructor<*>): Ctor? {
71
69
private inline fun safeCtor (crossinline block : (Throwable ) -> Throwable ): Ctor =
72
70
{ e -> runCatching { block(e) }.getOrNull() }
73
71
74
- private fun Class <* >.fieldsCountOrDefault (defaultValue : Int ) = kotlin.runCatching { fieldsCount() }.getOrDefault(defaultValue)
72
+ private fun Class <* >.fieldsCountOrDefault (defaultValue : Int ) =
73
+ kotlin.runCatching { fieldsCount() }.getOrDefault(defaultValue)
75
74
76
75
private tailrec fun Class <* >.fieldsCount (accumulator : Int = 0): Int {
77
76
val fieldsCount = declaredFields.count { ! Modifier .isStatic(it.modifiers) }
78
77
val totalFields = accumulator + fieldsCount
79
78
val superClass = superclass ? : return totalFields
80
79
return superClass.fieldsCount(totalFields)
81
80
}
81
+
82
+ internal abstract class CtorCache {
83
+ abstract fun get (key : Class <out Throwable >): Ctor
84
+ }
85
+
86
+ private object WeakMapCtorCache : CtorCache() {
87
+ private val cacheLock = ReentrantReadWriteLock ()
88
+ private val exceptionCtors: WeakHashMap <Class <out Throwable >, Ctor > = WeakHashMap ()
89
+
90
+ override fun get (key : Class <out Throwable >): Ctor {
91
+ cacheLock.read { exceptionCtors[key]?.let { return it } }
92
+ cacheLock.write {
93
+ exceptionCtors[key]?.let { return it }
94
+ return createConstructor(key).also { exceptionCtors[key] = it }
95
+ }
96
+ }
97
+ }
98
+
99
+ @IgnoreJreRequirement
100
+ private object ClassValueCtorCache : CtorCache() {
101
+ private val cache = object : ClassValue <Ctor >() {
102
+ override fun computeValue (type : Class <* >? ): Ctor {
103
+ @Suppress(" UNCHECKED_CAST" )
104
+ return createConstructor(type as Class <out Throwable >)
105
+ }
106
+ }
107
+
108
+ override fun get (key : Class <out Throwable >): Ctor = cache.get(key)
109
+ }
0 commit comments