]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0902: Vim9: iterating over a tuple leaks memory v9.2.0902
authorSamuel Schlesinger <sgschlesinger@gmail.com>
Mon, 3 Aug 2026 19:54:50 +0000 (19:54 +0000)
committerChristian Brabandt <cb@256bit.org>
Mon, 3 Aug 2026 19:54:50 +0000 (19:54 +0000)
Problem:  Looping over a tuple with ":for" copies each item with
          copy_tv() but never clears the copy, leaking the value on
          every iteration (26MB over 100k iterations of a two-string
          tuple).  Container items keep an extra reference forever,
          also defeating garbage collection.
Solution: Clear the copied typval on both return paths, like the
          string branch of next_for_item() already does
          (Samuel Schlesinger).

closes: #20914

Supported by AI.

Signed-off-by: Samuel Schlesinger <sgschlesinger@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
src/eval.c
src/testdir/test_tuple.vim
src/version.c

index 7d212b5b30de75af9b0f44168d53c48a99c65cff..d5373c099164ec6ad541b5b7f03c9a6408c4f552 100644 (file)
@@ -3005,9 +3005,12 @@ next_for_item(void *fi_void, char_u *arg)
        ++fi->fi_tuple_idx;
        ++fi->fi_bi;
        if (skip_assign)
-           return TRUE;
-       return ex_let_vars(arg, &tv, TRUE, fi->fi_semicolon,
+           result = TRUE;
+       else
+           result = ex_let_vars(arg, &tv, TRUE, fi->fi_semicolon,
                                            fi->fi_varcount, flag, NULL) == OK;
+       clear_tv(&tv);
+       return result;
     }
 
     item = fi->fi_lw.lw_item;
index 68bdca08c2ec879bb5d87f32db2b6cbf527eb3e7..ace890c0406476c2cb77d15c3979b5335afa2e34 100644 (file)
@@ -778,6 +778,20 @@ func Test_tuple_for()
       LET sum += v2
     endfor
     call assert_equal(0, sum)
+
+    #" iterating over string items; the copied item must not be leaked
+    VAR res = ''
+    for v3 in ('a', 'bb', 'ccc')
+      LET res ..= v3
+    endfor
+    call assert_equal('abbccc', res)
+
+    #" iterating over container items must not leak a reference
+    VAR flat = []
+    for v4 in (['a'], ['b', 'c'])
+      LET flat += v4
+    endfor
+    call assert_equal(['a', 'b', 'c'], flat)
   END
   call v9.CheckSourceLegacyAndVim9Success(lines)
 
@@ -792,6 +806,18 @@ func Test_tuple_for()
   END
   call v9.CheckSourceSuccess(lines)
 
+  " ignoring the for loop assignment using '_'; string items must not be
+  " leaked
+  let lines =<< trim END
+    vim9script
+    var count = 0
+    for _ in ('a', 'bb', 'ccc')
+      count += 1
+    endfor
+    assert_equal(3, count)
+  END
+  call v9.CheckSourceSuccess(lines)
+
   let lines =<< trim END
     var sum = 0
     for v in null_tuple
index dcea5c2ab2b5341d79c5d9ad1d31daf3004c10a0..851013e9e555eb2343bc814d38b985b0dfa4e582 100644 (file)
@@ -763,6 +763,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    902,
 /**/
     901,
 /**/