blob: a03163db365733ca45dedfa68e69d504916712dc [file] [log] [blame]
/*
* Copyright 2016-2020 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)
}