@@ -11,44 +11,42 @@ import java.util.concurrent.locks.*
1111import kotlin.concurrent.*
1212
1313private val throwableFields = Throwable ::class .java.fieldsCountOrDefault(- 1 )
14- private val cacheLock = ReentrantReadWriteLock ()
1514private 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+ }
1823
1924@Suppress(" UNCHECKED_CAST" )
2025internal fun <E : Throwable > tryCopyException (exception : E ): E ? {
2126 // Fast path for CopyableThrowable
2227 if (exception is CopyableThrowable <* >) {
2328 return runCatching { exception.createCopy() as E ? }.getOrNull()
2429 }
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
3637 /*
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 }
4242 for (constructor in constructors) {
43- ctor = createConstructor (constructor )
44- if (ctor != null ) break
43+ val result = createSafeConstructor (constructor )
44+ if (result != null ) return result
4545 }
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
4947}
5048
51- private fun createConstructor (constructor : Constructor <* >): Ctor ? {
49+ private fun createSafeConstructor (constructor : Constructor <* >): Ctor ? {
5250 val p = constructor .parameterTypes
5351 return when (p.size) {
5452 2 -> when {
@@ -71,11 +69,41 @@ private fun createConstructor(constructor: Constructor<*>): Ctor? {
7169private inline fun safeCtor (crossinline block : (Throwable ) -> Throwable ): Ctor =
7270 { e -> runCatching { block(e) }.getOrNull() }
7371
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)
7574
7675private tailrec fun Class <* >.fieldsCount (accumulator : Int = 0): Int {
7776 val fieldsCount = declaredFields.count { ! Modifier .isStatic(it.modifiers) }
7877 val totalFields = accumulator + fieldsCount
7978 val superClass = superclass ? : return totalFields
8079 return superClass.fieldsCount(totalFields)
8180}
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