Preserve a frame with source code location when sanitizing traces (#2452)
Fixes https://github.com/Kotlin/kotlinx.coroutines/issues/1437
diff --git a/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt b/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt
index 83bc02c..5ab8a63 100644
--- a/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt
+++ b/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt
@@ -477,33 +477,40 @@
/*
* Trim intervals of internal methods from the stacktrace (bounds are excluded from trimming)
- * E.g. for sequence [e, i1, i2, i3, e, i4, e, i5, i6, e7]
+ * E.g. for sequence [e, i1, i2, i3, e, i4, e, i5, i6, i7]
* output will be [e, i1, i3, e, i4, e, i5, i7]
+ *
+ * If an interval of internal methods ends in a synthetic method, the outermost non-synthetic method in that
+ * interval will also be included.
*/
val result = ArrayList<StackTraceElement>(size - probeIndex + 1)
result += createArtificialFrame(ARTIFICIAL_FRAME_MESSAGE)
- var includeInternalFrame = true
- for (i in (probeIndex + 1) until size - 1) {
- val element = stackTrace[i]
- if (!element.isInternalMethod) {
- includeInternalFrame = true
- result += element
- continue
- }
-
- if (includeInternalFrame) {
- result += element
- includeInternalFrame = false
- } else if (stackTrace[i + 1].isInternalMethod) {
- continue
+ var i = probeIndex + 1
+ while (i < size) {
+ if (stackTrace[i].isInternalMethod) {
+ result += stackTrace[i] // we include the boundary of the span in any case
+ // first index past the end of the span of internal methods that starts from `i`
+ var j = i + 1
+ while (j < size && stackTrace[j].isInternalMethod) {
+ ++j
+ }
+ // index of the last non-synthetic internal methods in this span, or `i` if there are no such methods
+ var k = j - 1
+ while (k > i && stackTrace[k].fileName == null) {
+ k -= 1
+ }
+ if (k > i && k < j - 1) {
+ /* there are synthetic internal methods at the end of this span, but there is a non-synthetic method
+ after `i`, so we include it. */
+ result += stackTrace[k]
+ }
+ result += stackTrace[j - 1] // we include the other boundary of this span in any case, too
+ i = j
} else {
- result += element
- includeInternalFrame = true
+ result += stackTrace[i]
+ ++i
}
-
}
-
- result += stackTrace[size - 1]
return result
}
diff --git a/kotlinx-coroutines-debug/test/SanitizedProbesTest.kt b/kotlinx-coroutines-debug/test/SanitizedProbesTest.kt
index c897e2e..67a283d 100644
--- a/kotlinx-coroutines-debug/test/SanitizedProbesTest.kt
+++ b/kotlinx-coroutines-debug/test/SanitizedProbesTest.kt
@@ -63,6 +63,7 @@
"\t(Coroutine creation stacktrace)\n" +
"\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)\n" +
"\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:23)\n" +
+ "\tat kotlinx.coroutines.BuildersKt__Builders_commonKt.async\$default(Builders.common.kt)\n" +
"\tat kotlinx.coroutines.BuildersKt.async\$default(Unknown Source)\n" +
"\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest.createActiveDeferred(SanitizedProbesTest.kt:62)\n" +
"\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest.access\$createActiveDeferred(SanitizedProbesTest.kt:16)\n" +
@@ -91,6 +92,7 @@
"\t(Coroutine creation stacktrace)\n" +
"\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)\n" +
"\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:25)\n" +
+ "\tat kotlinx.coroutines.BuildersKt__Builders_commonKt.launch\$default(Builders.common.kt)\n" +
"\tat kotlinx.coroutines.BuildersKt.launch\$default(Unknown Source)\n" +
"\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest.launchSelector(SanitizedProbesTest.kt:100)\n" +
"\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest.access\$launchSelector(SanitizedProbesTest.kt:16)\n" +