| // DISABLE_NATIVE: optimizationMode=DEBUG | |
| // DISABLE_NATIVE: optimizationMode=NO | |
| // DISABLE_NATIVE: cacheMode=STATIC_ONLY_DIST | |
| // DISABLE_NATIVE: cacheMode=STATIC_EVERYWHERE | |
| // DISABLE_NATIVE: cacheMode=STATIC_PER_FILE_EVERYWHERE | |
| // IGNORE_KLIB_RUNTIME_ERRORS_WITH_CUSTOM_FIRST_STAGE: Native:* | |
| // IGNORE_KLIB_RUNTIME_ERRORS_WITH_CUSTOM_SECOND_STAGE: Native:* | |
| @file:OptIn(kotlin.native.runtime.NativeRuntimeApi::class) | |
| import kotlin.native.internal.isStack | |
| import kotlin.native.runtime.GC | |
| open class Node { | |
| var next: Node? = null | |
| } | |
| class Middle : Node() { | |
| var next2: Node? = null | |
| var next3: Node? = null | |
| var next4: Node? = null | |
| var next5: Node? = null | |
| } | |
| var count = 0 | |
| fun left(depth: Int) { | |
| if (depth <= 0) return | |
| // Schedule the GC often enough to find the problem sooner: | |
| if (count++ % 500 == 0) GC.schedule() | |
| val head = Node() | |
| check(head.isStack()) | |
| // Use many stack objects to increase the probability of hitting one of them with zeroes: | |
| val tail1 = Node() | |
| val tail2 = Node() | |
| val tail3 = Node() | |
| val tail4 = Node() | |
| val tail5 = Node() | |
| repeat(1) { | |
| val middle = Middle() | |
| check(!middle.isStack()) | |
| head.next = middle | |
| middle.next = tail1 | |
| middle.next2 = tail2 | |
| middle.next3 = tail3 | |
| middle.next4 = tail4 | |
| middle.next5 = tail5 | |
| } | |
| // The bug is that `middle` is marked as a normal heap object, not as a local object. | |
| // So, we have a heap object referencing a stack object. | |
| // In CMS, when it marks concurrently, by the time it scans `middle`, | |
| // the referred stack objects might be overwritten. | |
| // That's exactly what this test tries to achieve. | |
| // | |
| // `left` and `right` are similar, but the former allocates such stack objects, | |
| // while the latter zeroes the stack. | |
| // So, if the stack trace changes between scanning stack roots (in STW) | |
| // and marking `middle` (concurrently), then the deepest frame changes | |
| // from `left` to `right`, so left's objects are overwritten by right's zeroing. | |
| left(depth - 1) | |
| right(depth - 1) | |
| } | |
| fun right(depth: Int) { | |
| if (depth <= 0) return | |
| val zeroes = IntArray(16) | |
| check(zeroes.isStack()) // Zeroes the stack. | |
| left(depth - 1) | |
| right(depth - 1) | |
| } | |
| fun box(): String { | |
| left(20) | |
| // Wait till the scheduled GC finishes: | |
| GC.collect() | |
| return "OK" | |
| } |