• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 usually populated in user-defined constructors)
37     if (throwableFields != clz.fieldsCountOrDefault(0)) return nullResult
38     /*
39     * Try to reflectively find constructor(), constructor(message, cause), constructor(cause) or constructor(message).
40     * Exceptions are shared among coroutines, so we should copy exception before recovering current stacktrace.
41     */
42     val constructors = clz.constructors.sortedByDescending { it.parameterTypes.size }
43     for (constructor in constructors) {
44         val result = createSafeConstructor(constructor)
45         if (result != null) return result
46     }
47     return nullResult
48 }
49 
createSafeConstructornull50 private fun createSafeConstructor(constructor: Constructor<*>): Ctor? {
51     val p = constructor.parameterTypes
52     return when (p.size) {
53         2 -> when {
54             p[0] == String::class.java && p[1] == Throwable::class.java ->
55                 safeCtor { e -> constructor.newInstance(e.message, e) as Throwable }
56             else -> null
57         }
58         1 -> when (p[0]) {
59             Throwable::class.java ->
60                 safeCtor { e -> constructor.newInstance(e) as Throwable }
61             String::class.java ->
62                 safeCtor { e -> (constructor.newInstance(e.message) as Throwable).also { it.initCause(e) } }
63             else -> null
64         }
65         0 -> safeCtor { e -> (constructor.newInstance() as Throwable).also { it.initCause(e) } }
66         else -> null
67     }
68 }
69 
safeCtornull70 private inline fun safeCtor(crossinline block: (Throwable) -> Throwable): Ctor =
71     { e -> runCatching { block(e) }.getOrNull() }
72 
fieldsCountOrDefaultnull73 private fun Class<*>.fieldsCountOrDefault(defaultValue: Int) =
74     kotlin.runCatching { fieldsCount() }.getOrDefault(defaultValue)
75 
fieldsCountnull76 private tailrec fun Class<*>.fieldsCount(accumulator: Int = 0): Int {
77     val fieldsCount = declaredFields.count { !Modifier.isStatic(it.modifiers) }
78     val totalFields = accumulator + fieldsCount
79     val superClass = superclass ?: return totalFields
80     return superClass.fieldsCount(totalFields)
81 }
82 
83 internal abstract class CtorCache {
getnull84     abstract fun get(key: Class<out Throwable>): Ctor
85 }
86 
87 private object WeakMapCtorCache : CtorCache() {
88     private val cacheLock = ReentrantReadWriteLock()
89     private val exceptionCtors: WeakHashMap<Class<out Throwable>, Ctor> = WeakHashMap()
90 
91     override fun get(key: Class<out Throwable>): Ctor {
92         cacheLock.read { exceptionCtors[key]?.let { return it } }
93         cacheLock.write {
94             exceptionCtors[key]?.let { return it }
95             return createConstructor(key).also { exceptionCtors[key] = it }
96         }
97     }
98 }
99 
100 @IgnoreJreRequirement
101 @SuppressLint("NewApi")
102 private object ClassValueCtorCache : CtorCache() {
103     private val cache = object : ClassValue<Ctor>() {
computeValuenull104         override fun computeValue(type: Class<*>?): Ctor {
105             @Suppress("UNCHECKED_CAST")
106             return createConstructor(type as Class<out Throwable>)
107         }
108     }
109 
getnull110     override fun get(key: Class<out Throwable>): Ctor = cache.get(key)
111 }
112