summary refs log tree commit diff stats
path: root/tests/gc/closureleak.nim
diff options
context:
space:
mode:
Diffstat (limited to 'tests/gc/closureleak.nim')
-rw-r--r--tests/gc/closureleak.nim52
1 files changed, 52 insertions, 0 deletions
diff --git a/tests/gc/closureleak.nim b/tests/gc/closureleak.nim
new file mode 100644
index 000000000..e67beb513
--- /dev/null
+++ b/tests/gc/closureleak.nim
@@ -0,0 +1,52 @@
+discard """
+  outputsub: "true"
+  disabled: "32bit"
+"""
+
+type
+  TFoo* = object
+    id: int
+    fn: proc() {.closure.}
+var foo_counter = 0
+var alive_foos = newseq[int](0)
+
+when defined(gcDestructors):
+  proc `=destroy`(some: TFoo) =
+    alive_foos.del alive_foos.find(some.id)
+    # TODO: fixme: investigate why `=destroy` requires `some.fn` to be `gcsafe`
+    # the debugging info below came from `symPrototype` in the liftdestructors
+    # proc (){.closure, gcsafe.}, {tfThread, tfHasAsgn, tfCheckedForDestructor, tfExplicitCallConv}
+    # var proc (){.closure, gcsafe.}, {tfHasGCedMem}
+    # it worked by accident with var T destructors because in the sempass2
+    #
+    # let argtype = skipTypes(a.typ, abstractInst) # !!! it does't skip `tyVar`
+    # if argtype.kind == tyProc and notGcSafe(argtype) and not tracked.inEnforcedGcSafe:
+    #   localError(tracked.config, n.info, $n & " is not GC safe")
+    {.cast(gcsafe).}:
+      `=destroy`(some.fn)
+
+else:
+  proc free*(some: ref TFoo) =
+    #echo "Tfoo #", some.id, " freed"
+    alive_foos.del alive_foos.find(some.id)
+
+proc newFoo*(): ref TFoo =
+  when defined(gcDestructors):
+    new result
+  else:
+    new result, free
+
+  result.id = foo_counter
+  alive_foos.add result.id
+  inc foo_counter
+
+for i in 0 ..< 10:
+  discard newFoo()
+
+for i in 0 ..< 10:
+  let f = newFoo()
+  f.fn = proc =
+    echo f.id
+
+GC_fullcollect()
+echo alive_foos.len <= 3