Skip to content

Commit

Permalink
fixes #22619; don't lift cursor fields in the hook calls (#22638)
Browse files Browse the repository at this point in the history
fixes #22619

It causes double free for closure iterators because cursor fields are
destroyed in the lifted destructors of `Env`.

Besides, according to the Nim manual

> In fact, cursor more generally prevents object
construction/destruction pairs and so can also be useful in other
contexts.

At least, destruction of cursor fields might cause troubles.


todo
- [x] tests
- [x] revert a certain old PR

---------

Co-authored-by: zerbina <100542850+zerbina@users.noreply.github.com>
  • Loading branch information
ringabout and zerbina authored Sep 5, 2023
1 parent 6000cc8 commit eb91cf9
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 2 deletions.
3 changes: 1 addition & 2 deletions compiler/liftdestructors.nim
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,7 @@ proc fillBodyObj(c: var TLiftCtx; n, body, x, y: PNode; enforceDefaultOp: bool)
if c.filterDiscriminator != nil: return
let f = n.sym
let b = if c.kind == attachedTrace: y else: y.dotField(f)
if (sfCursor in f.flags and f.typ.skipTypes(abstractInst).kind in {tyRef, tyProc} and
c.g.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcHooks}) or
if (sfCursor in f.flags and c.g.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcHooks}) or
enforceDefaultOp:
defaultOp(c, f.typ, body, x.dotField(f), b)
else:
Expand Down
79 changes: 79 additions & 0 deletions tests/iter/t22619.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# bug #22619

when false: # todo fixme
block:
type
Resource = object
value: int

Object = object
r {.cursor.}: Resource
s {.cursor.}: seq[Resource]

var numDestroy = 0

proc `=copy`(x: var Resource, y: Resource) {.error.} # disallow full copies
proc `=destroy`(x: Resource) =
inc numDestroy

proc test() =
# perform the test in procedure so that globals aren't used (their different
# semantics with regards to destruction would interfere)
var
r = Resource(value: 1) # initialize a resource
s = @[Resource(value: 2)]

# make sure no copy is required in the initializer expression:
var o = Object(r: r, s: s)

# copying the object doesn't perform a full copy of the cursor fields:
var o2 = o
discard addr(o2) # prevent `o2` from being turned into a cursor

# check that the fields were shallow-copied:
doAssert o2.r.value == 1
doAssert o2.s[0].value == 2

# make sure no copy is required with normal field assignments:
o.r = r
o.s = s


# when `o` and `o2` are destroyed, their destructor must not be called on
# their fields

test()

# one call for the `r` local and one for the object in `s`
doAssert numDestroy == 2

block:
type Value = distinct int

var numDestroy = 0

proc `=destroy`(x: Value) =
inc numDestroy

iterator iter(s: seq[Value]): int {.closure.} =
# because it is used across yields, `s2` is lifted into the iterator's
# environment. Since non-ref cursors in object didn't have their hooks
# disabled inside the environments lifted hooks, this led to double
# frees
var s2 {.cursor.} = s
var i = 0
let L = s2.len
while i < L:
yield s2[i].int
inc i

proc test() =
var s = @[Value(1), Value(2)]
let cl = iter
# make sure resuming the iterator works:
doAssert cl(s) == 1
doAssert cl(s) == 2
doAssert cl(s) == 0

test()
doAssert numDestroy == 2

0 comments on commit eb91cf9

Please sign in to comment.