Files
Nim/tests/iter/t26041.nim
2026-09-09 09:58:13 +02:00

93 lines
2.1 KiB
Nim

discard """
matrix: "--mm:refc; --mm:arc; --mm:orc"
targets: "c cpp"
output: "ok"
"""
# bug #26041: a closure iterator local was released at the `yield` that
# suspended the iterator instead of at the end of its scope.
func firstAddr[T](x: openArray[T]): ptr T =
x[0].addr
proc churn() =
## Recycle enough memory that a freed payload would be reused.
var junk: seq[seq[int]]
for i in 0 ..< 100_000: junk.add @[-7]
block: # the local outlives the `yield`, so its payload must, too
var p: ptr int
iterator leak(): int {.closure.} =
var data = @[123]
p = firstAddr(data)
yield 1
yield 2 # `data`'s scope only ends here
let it = leak
discard it() # run to the first `yield` and suspend
GC_fullCollect()
churn()
doAssert p[] == 123, "suspended iterator's seq payload was recycled"
block: # same, but the local's scope is a nested one
var p: ptr int
iterator leak(): int {.closure.} =
while true:
var data = @[456]
p = firstAddr(data)
yield 1
break # `data` is never read again, but its scope reaches to here
let it = leak
discard it()
GC_fullCollect()
churn()
doAssert p[] == 456
block: # a `try` body is a scope as well
var p: ptr int
iterator leak(): int {.closure.} =
try:
var data = @[789]
p = firstAddr(data)
yield 1
finally:
discard
let it = leak
discard it()
GC_fullCollect()
churn()
doAssert p[] == 789
when defined(gcDestructors):
block: # the lifted local is destroyed exactly once, with the environment
var destroyed = 0
type Res = object
id: int
proc `=destroy`(x: Res) =
if x.id != 0: inc destroyed
proc run() =
iterator iter(): int {.closure.} =
var r = Res(id: 1) # never read again; the scope is what keeps it alive
yield 1
yield 2
var it = iter
doAssert it() == 1
doAssert destroyed == 0
doAssert it() == 2
doAssert destroyed == 0
discard it() # run the iterator to completion
run() # only now is the environment gone
doAssert destroyed == 1, $destroyed
echo "ok"