@@ -6,7 +6,6 @@ package kotlinx.coroutines.debug.internal
6
6
7
7
import kotlinx.coroutines.*
8
8
import kotlinx.coroutines.debug.*
9
- import kotlinx.coroutines.internal.artificialFrame
10
9
import net.bytebuddy.*
11
10
import net.bytebuddy.agent.*
12
11
import net.bytebuddy.dynamic.loading.*
@@ -16,6 +15,7 @@ import java.util.*
16
15
import kotlin.collections.ArrayList
17
16
import kotlin.coroutines.*
18
17
import kotlin.coroutines.jvm.internal.*
18
+ import kotlinx.coroutines.internal.artificialFrame as createArtificialFrame // IDEA bug workaround
19
19
20
20
/* *
21
21
* Mirror of [DebugProbes] with actual implementation.
@@ -134,7 +134,7 @@ internal object DebugProbesImpl {
134
134
135
135
append(" \n\n Coroutine $key , state: $state " )
136
136
if (observedStackTrace.isEmpty()) {
137
- append(" \n\t at ${artificialFrame (ARTIFICIAL_FRAME_MESSAGE )} " )
137
+ append(" \n\t at ${createArtificialFrame (ARTIFICIAL_FRAME_MESSAGE )} " )
138
138
printStackTrace(value.creationStackTrace)
139
139
} else {
140
140
printStackTrace(enhancedStackTrace)
@@ -168,7 +168,6 @@ internal object DebugProbesImpl {
168
168
* 2) Find the next frame after BaseContinuationImpl.resumeWith (continuation machinery).
169
169
* Invariant: this method is called under the lock, so such method **should** be present
170
170
* in continuation stacktrace.
171
- *
172
171
* 3) Find target method in continuation stacktrace (metadata-based)
173
172
* 4) Prepend dumped stacktrace (trimmed by target frame) to continuation stacktrace
174
173
*
@@ -181,33 +180,62 @@ internal object DebugProbesImpl {
181
180
it.fileName == " ContinuationImpl.kt"
182
181
}
183
182
184
- // We haven't found "BaseContinuationImpl.resumeWith" resume call in stacktrace
185
- // This is some inconsistency in machinery, do not fail, fallback
186
- val continuationFrame = actualTrace.getOrNull(indexOfResumeWith - 1 )
187
- ? : return coroutineTrace
188
-
189
- val continuationStartFrame = coroutineTrace.indexOfFirst {
190
- it.fileName == continuationFrame.fileName &&
191
- it.className == continuationFrame.className &&
192
- it.methodName == continuationFrame.methodName
193
- } + 1
183
+ val (continuationStartFrame, frameSkipped) = findContinuationStartIndex(
184
+ indexOfResumeWith,
185
+ actualTrace,
186
+ coroutineTrace)
194
187
195
- if (continuationStartFrame == 0 ) return coroutineTrace
188
+ if (continuationStartFrame == - 1 ) return coroutineTrace
196
189
197
- val expectedSize = indexOfResumeWith + coroutineTrace.size - continuationStartFrame
190
+ val delta = if (frameSkipped) 1 else 0
191
+ val expectedSize = indexOfResumeWith + coroutineTrace.size - continuationStartFrame - 1 - delta
198
192
val result = ArrayList <StackTraceElement >(expectedSize)
199
-
200
- for (index in 0 until indexOfResumeWith) {
193
+ for (index in 0 until indexOfResumeWith - delta) {
201
194
result + = actualTrace[index]
202
195
}
203
196
204
- for (index in continuationStartFrame until coroutineTrace.size) {
197
+ for (index in continuationStartFrame + 1 until coroutineTrace.size) {
205
198
result + = coroutineTrace[index]
206
199
}
207
200
208
201
return result
209
202
}
210
203
204
+ /* *
205
+ * Tries to find the lowest meaningful frame above `resumeWith` in the real stacktrace and
206
+ * its match in a coroutines stacktrace (steps 2-3 in heuristic).
207
+ *
208
+ * This method does more than just matching `realTrace.indexOf(resumeWith) - 1`:
209
+ * If method above `resumeWith` has no line number (thus it is `stateMachine.invokeSuspend`),
210
+ * it's skipped and attempt to match next one is made because state machine could have been missing in the original coroutine stacktrace.
211
+ *
212
+ * Returns index of such frame (or -1) and flag indicating whether frame with state machine was skipped
213
+ */
214
+ private fun findContinuationStartIndex (
215
+ indexOfResumeWith : Int ,
216
+ actualTrace : Array <StackTraceElement >,
217
+ coroutineTrace : List <StackTraceElement >
218
+ ): Pair <Int , Boolean > {
219
+ val result = findIndexOfFrame(indexOfResumeWith - 1 , actualTrace, coroutineTrace)
220
+ if (result == - 1 ) return findIndexOfFrame(indexOfResumeWith - 2 , actualTrace, coroutineTrace) to true
221
+ return result to false
222
+ }
223
+
224
+ private fun findIndexOfFrame (
225
+ frameIndex : Int ,
226
+ actualTrace : Array <StackTraceElement >,
227
+ coroutineTrace : List <StackTraceElement >
228
+ ): Int {
229
+ val continuationFrame = actualTrace.getOrNull(frameIndex)
230
+ ? : return - 1
231
+
232
+ return coroutineTrace.indexOfFirst {
233
+ it.fileName == continuationFrame.fileName &&
234
+ it.className == continuationFrame.className &&
235
+ it.methodName == continuationFrame.methodName
236
+ }
237
+ }
238
+
211
239
private fun StringBuilder.printStackTrace (frames : List <StackTraceElement >) {
212
240
frames.forEach { frame ->
213
241
append(" \n\t at $frame " )
@@ -292,7 +320,7 @@ internal object DebugProbesImpl {
292
320
293
321
if (! DebugProbes .sanitizeStackTraces) {
294
322
return List (size - probeIndex) {
295
- if (it == 0 ) artificialFrame (ARTIFICIAL_FRAME_MESSAGE ) else stackTrace[it + probeIndex]
323
+ if (it == 0 ) createArtificialFrame (ARTIFICIAL_FRAME_MESSAGE ) else stackTrace[it + probeIndex]
296
324
}
297
325
}
298
326
@@ -302,7 +330,7 @@ internal object DebugProbesImpl {
302
330
* output will be [e, i1, i3, e, i4, e, i5, i7]
303
331
*/
304
332
val result = ArrayList <StackTraceElement >(size - probeIndex + 1 )
305
- result + = artificialFrame (ARTIFICIAL_FRAME_MESSAGE )
333
+ result + = createArtificialFrame (ARTIFICIAL_FRAME_MESSAGE )
306
334
var includeInternalFrame = true
307
335
for (i in (probeIndex + 1 ) until size - 1 ) {
308
336
val element = stackTrace[i]
0 commit comments