1 /*
2 * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3 */
4
5 @file:Suppress("UNCHECKED_CAST", "NO_EXPLICIT_VISIBILITY_IN_API_MODE")
6
7 package kotlinx.coroutines.internal
8
9 import kotlinx.coroutines.*
10 import java.util.*
11 import kotlin.coroutines.*
12 import kotlin.coroutines.intrinsics.*
13
14 /*
15 * `Class.forName(name).canonicalName` instead of plain `name` is required to properly handle
16 * Android's minifier that renames these classes and breaks our recovery heuristic without such lookup.
17 */
18 private const val baseContinuationImplClass = "kotlin.coroutines.jvm.internal.BaseContinuationImpl"
19 private const val stackTraceRecoveryClass = "kotlinx.coroutines.internal.StackTraceRecoveryKt"
20
<lambda>null21 private val baseContinuationImplClassName = runCatching {
22 Class.forName(baseContinuationImplClass).canonicalName
23 }.getOrElse { baseContinuationImplClass }
24
<lambda>null25 private val stackTraceRecoveryClassName = runCatching {
26 Class.forName(stackTraceRecoveryClass).canonicalName
27 }.getOrElse { stackTraceRecoveryClass }
28
recoverStackTracenull29 internal actual fun <E : Throwable> recoverStackTrace(exception: E): E {
30 if (!RECOVER_STACK_TRACES) return exception
31 // No unwrapping on continuation-less path: exception is not reported multiple times via slow paths
32 val copy = tryCopyException(exception) ?: return exception
33 return copy.sanitizeStackTrace()
34 }
35
sanitizeStackTracenull36 private fun <E : Throwable> E.sanitizeStackTrace(): E {
37 val stackTrace = stackTrace
38 val size = stackTrace.size
39 val lastIntrinsic = stackTrace.frameIndex(stackTraceRecoveryClassName)
40 val startIndex = lastIntrinsic + 1
41 val endIndex = stackTrace.frameIndex(baseContinuationImplClassName)
42 val adjustment = if (endIndex == -1) 0 else size - endIndex
43 val trace = Array(size - lastIntrinsic - adjustment) {
44 if (it == 0) {
45 artificialFrame("Coroutine boundary")
46 } else {
47 stackTrace[startIndex + it - 1]
48 }
49 }
50
51 setStackTrace(trace)
52 return this
53 }
54
55 @Suppress("NOTHING_TO_INLINE") // Inline for better R8 optimization
recoverStackTracenull56 internal actual inline fun <E : Throwable> recoverStackTrace(exception: E, continuation: Continuation<*>): E {
57 if (!RECOVER_STACK_TRACES || continuation !is CoroutineStackFrame) return exception
58 return recoverFromStackFrame(exception, continuation)
59 }
60
recoverFromStackFramenull61 private fun <E : Throwable> recoverFromStackFrame(exception: E, continuation: CoroutineStackFrame): E {
62 /*
63 * Here we are checking whether exception has already recovered stacktrace.
64 * If so, we extract initial and merge recovered stacktrace and current one
65 */
66 val (cause, recoveredStacktrace) = exception.causeAndStacktrace()
67
68 // Try to create an exception of the same type and get stacktrace from continuation
69 val newException = tryCopyException(cause) ?: return exception
70 // Verify that the new exception has the same message as the original one (bail out if not, see #1631)
71 if (newException.message != cause.message) return exception
72 // Update stacktrace
73 val stacktrace = createStackTrace(continuation)
74 if (stacktrace.isEmpty()) return exception
75 // Merge if necessary
76 if (cause !== exception) {
77 mergeRecoveredTraces(recoveredStacktrace, stacktrace)
78 }
79 // Take recovered stacktrace, merge it with existing one if necessary and return
80 return createFinalException(cause, newException, stacktrace)
81 }
82
83 /*
84 * Here we partially copy original exception stackTrace to make current one much prettier.
85 * E.g. for
86 * ```
87 * fun foo() = async { error(...) }
88 * suspend fun bar() = foo().await()
89 * ```
90 * we would like to produce following exception:
91 * IllegalStateException
92 * at foo
93 * at kotlin.coroutines.resumeWith
94 * (Coroutine boundary)
95 * at bar
96 * ...real stackTrace...
97 * caused by "IllegalStateException" (original one)
98 */
createFinalExceptionnull99 private fun <E : Throwable> createFinalException(cause: E, result: E, resultStackTrace: ArrayDeque<StackTraceElement>): E {
100 resultStackTrace.addFirst(artificialFrame("Coroutine boundary"))
101 val causeTrace = cause.stackTrace
102 val size = causeTrace.frameIndex(baseContinuationImplClassName)
103 if (size == -1) {
104 result.stackTrace = resultStackTrace.toTypedArray()
105 return result
106 }
107
108 val mergedStackTrace = arrayOfNulls<StackTraceElement>(resultStackTrace.size + size)
109 for (i in 0 until size) {
110 mergedStackTrace[i] = causeTrace[i]
111 }
112
113 for ((index, element) in resultStackTrace.withIndex()) {
114 mergedStackTrace[size + index] = element
115 }
116
117 result.stackTrace = mergedStackTrace
118 return result
119 }
120
121 /**
122 * Find initial cause of the exception without restored stacktrace.
123 * Returns intermediate stacktrace as well in order to avoid excess cloning of array as an optimization.
124 */
causeAndStacktracenull125 private fun <E : Throwable> E.causeAndStacktrace(): Pair<E, Array<StackTraceElement>> {
126 val cause = cause
127 return if (cause != null && cause.javaClass == javaClass) {
128 val currentTrace = stackTrace
129 if (currentTrace.any { it.isArtificial() })
130 cause as E to currentTrace
131 else this to emptyArray()
132 } else {
133 this to emptyArray()
134 }
135 }
136
mergeRecoveredTracesnull137 private fun mergeRecoveredTraces(recoveredStacktrace: Array<StackTraceElement>, result: ArrayDeque<StackTraceElement>) {
138 // Merge two stacktraces and trim common prefix
139 val startIndex = recoveredStacktrace.indexOfFirst { it.isArtificial() } + 1
140 val lastFrameIndex = recoveredStacktrace.size - 1
141 for (i in lastFrameIndex downTo startIndex) {
142 val element = recoveredStacktrace[i]
143 if (element.elementWiseEquals(result.last)) {
144 result.removeLast()
145 }
146 result.addFirst(recoveredStacktrace[i])
147 }
148 }
149
150 @Suppress("NOTHING_TO_INLINE")
recoverAndThrownull151 internal actual suspend inline fun recoverAndThrow(exception: Throwable): Nothing {
152 if (!RECOVER_STACK_TRACES) throw exception
153 suspendCoroutineUninterceptedOrReturn<Nothing> {
154 if (it !is CoroutineStackFrame) throw exception
155 throw recoverFromStackFrame(exception, it)
156 }
157 }
158
159 @Suppress("NOTHING_TO_INLINE") // Inline for better R8 optimizations
unwrapnull160 internal actual inline fun <E : Throwable> unwrap(exception: E): E =
161 if (!RECOVER_STACK_TRACES) exception else unwrapImpl(exception)
162
163 internal fun <E : Throwable> unwrapImpl(exception: E): E {
164 val cause = exception.cause
165 // Fast-path to avoid array cloning
166 if (cause == null || cause.javaClass != exception.javaClass) {
167 return exception
168 }
169 // Slow path looks for artificial frames in a stack-trace
170 if (exception.stackTrace.any { it.isArtificial() }) {
171 @Suppress("UNCHECKED_CAST")
172 return cause as E
173 } else {
174 return exception
175 }
176 }
177
createStackTracenull178 private fun createStackTrace(continuation: CoroutineStackFrame): ArrayDeque<StackTraceElement> {
179 val stack = ArrayDeque<StackTraceElement>()
180 continuation.getStackTraceElement()?.let { stack.add(it) }
181
182 var last = continuation
183 while (true) {
184 last = (last as? CoroutineStackFrame)?.callerFrame ?: break
185 last.getStackTraceElement()?.let { stack.add(it) }
186 }
187 return stack
188 }
189
190 /**
191 * @suppress
192 */
193 @InternalCoroutinesApi
artificialFramenull194 public fun artificialFrame(message: String): StackTraceElement = java.lang.StackTraceElement("\b\b\b($message", "\b", "\b", -1)
195 internal fun StackTraceElement.isArtificial() = className.startsWith("\b\b\b")
196 private fun Array<StackTraceElement>.frameIndex(methodName: String) = indexOfFirst { methodName == it.className }
197
StackTraceElementnull198 private fun StackTraceElement.elementWiseEquals(e: StackTraceElement): Boolean {
199 /*
200 * In order to work on Java 9 where modules and classloaders of enclosing class
201 * are part of the comparison
202 */
203 return lineNumber == e.lineNumber && methodName == e.methodName
204 && fileName == e.fileName && className == e.className
205 }
206
207 @Suppress("ACTUAL_WITHOUT_EXPECT")
208 internal actual typealias CoroutineStackFrame = kotlin.coroutines.jvm.internal.CoroutineStackFrame
209
210 @Suppress("ACTUAL_WITHOUT_EXPECT")
211 internal actual typealias StackTraceElement = java.lang.StackTraceElement
212
initCausenull213 internal actual fun Throwable.initCause(cause: Throwable) {
214 // Resolved to member, verified by test
215 initCause(cause)
216 }
217