aboutsummaryrefslogtreecommitdiff
path: root/kotlinx-coroutines-core/jvm/src/internal/ExceptionsConstuctor.kt
blob: 60328ebdc0745590d00774a760effd6a37afe053 (plain)
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
/*
 * 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 val cacheLock = ReentrantReadWriteLock()
private typealias Ctor = (Throwable) -> Throwable?
// Replace it with ClassValue when Java 6 support is over
private val exceptionCtors: WeakHashMap<Class<out Throwable>, Ctor> = WeakHashMap()

@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()
    }
    // Use cached ctor if found
    cacheLock.read { exceptionCtors[exception.javaClass] }?.let { cachedCtor ->
        return cachedCtor(exception) as E?
    }
    /*
     * Skip reflective copy if an exception has additional fields (that are usually populated in user-defined constructors)
     */
    if (throwableFields != exception.javaClass.fieldsCountOrDefault(0)) {
        cacheLock.write { exceptionCtors[exception.javaClass] = { null } }
        return null
    }
    /*
     * Try to reflectively find constructor(), constructor(message, cause), constructor(cause) or constructor(message).
     * Exceptions are shared among coroutines, so we should copy exception before recovering current stacktrace.
     */
    var ctor: Ctor? = null
    val constructors = exception.javaClass.constructors.sortedByDescending { it.parameterTypes.size }
    for (constructor in constructors) {
        ctor = createConstructor(constructor)
        if (ctor != null) break
    }
    // Store the resulting ctor to cache
    cacheLock.write { exceptionCtors[exception.javaClass] = ctor ?: { null } }
    return ctor?.invoke(exception) as E?
}

private fun createConstructor(constructor: Constructor<*>): Ctor? {
    val p = constructor.parameterTypes
    return when (p.size) {
        2 -> when {
            p[0] == String::class.java && p[1] == Throwable::class.java ->
                safeCtor { e -> constructor.newInstance(e.message, e) as Throwable }
            else -> null
        }
        1 -> when (p[0]) {
            Throwable::class.java ->
                safeCtor { e -> constructor.newInstance(e) as Throwable }
            String::class.java ->
                safeCtor { e -> (constructor.newInstance(e.message) as Throwable).also { it.initCause(e) } }
            else -> null
        }
        0 -> safeCtor { e -> (constructor.newInstance() as Throwable).also { it.initCause(e) } }
        else -> null
    }
}

private inline fun safeCtor(crossinline block: (Throwable) -> Throwable): Ctor =
    { e -> runCatching { block(e) }.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)
}