• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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