]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0877: Vim9: crash when a closure assigns to a variable declared in a loop v9.2.0877
authorHirohito Higashi <h.east.727@gmail.com>
Thu, 30 Jul 2026 18:43:11 +0000 (18:43 +0000)
committerChristian Brabandt <cb@256bit.org>
Thu, 30 Jul 2026 18:43:11 +0000 (18:43 +0000)
Problem:  Vim9: crash when a closure assigns to a variable that was
          declared in a loop (neoharju)
Solution: Encode the loop depth in the STOREOUTER instruction the same way
          as in LOADOUTER, so that the executor decodes it correctly
          (Hirohito Higashi)

fixes:  #20877
closes: #20881

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Hirohito Higashi <h.east.727@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
src/testdir/test_vim9_script.vim
src/version.c
src/vim9.h
src/vim9execute.c
src/vim9instr.c

index 2f54f6da62b484eb9029fe13dada149bd9217652..91873845d83b638469c5f325ab21e3654be2963f 100644 (file)
@@ -2899,6 +2899,36 @@ def Test_for_loop_with_closure()
       endfor
   END
   v9.CheckDefAndScriptSuccess(lines)
+
+  # assigning to a variable declared in the loop from a closure
+  lines =<< trim END
+      for i in range(3)
+        var inloop = 0
+        var F = () => {
+              inloop = i + 1
+            }
+        F()
+        assert_equal(i + 1, inloop)
+      endfor
+  END
+  v9.CheckDefAndScriptSuccess(lines)
+
+  # same in a nested loop
+  lines =<< trim END
+      var result: list<number>
+      for i in range(2)
+        for j in range(2)
+          var inloop = 0
+          var F = () => {
+                inloop = i * 10 + j
+              }
+          F()
+          result += [inloop]
+        endfor
+      endfor
+      assert_equal([0, 1, 10, 11], result)
+  END
+  v9.CheckDefAndScriptSuccess(lines)
 enddef
 
 def Test_define_global_closure_in_loops()
index e921a5c367f55145f3249e59f534048b708706c5..3fba5845f92da02d74c189b92b6f9a84c23795b7 100644 (file)
@@ -758,6 +758,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    877,
 /**/
     876,
 /**/
index c10d435a2427fae8afa95a74a1ea731208d73fa9..58cda7b852ce7d7c638858a4ea36897d076508e3 100644 (file)
@@ -444,11 +444,11 @@ typedef struct {
 // arguments to ISN_LOADOUTER and ISN_STOREOUTER
 typedef struct {
     int                outer_idx;      // index
-    int                outer_depth;    // nesting level, stack frames to go up
+    int                outer_depth;    // nesting level, stack frames to go up;
+                               // negative for a loop variable, the loop
+                               // depth is -outer_depth - 1
 } isn_outer_T;
 
-#define OUTER_LOOP_DEPTH -9    // used for outer_depth for loop variables
-
 // arguments to ISN_SUBSTITUTE
 typedef struct {
     char_u     *subs_cmd;      // :s command
index 68ca777d069c5fbe23105452aa1775e0f78781d6..1c30338eb845f2b0106624a8755a577ab6a07da8 100644 (file)
@@ -7330,9 +7330,10 @@ list_instructions(char *pfx, isn_T *instr, int instr_count, ufunc_T *ufunc)
                {
                    isn_outer_T *outer = &iptr->isn_arg.outer;
 
-                   if (outer->outer_depth == OUTER_LOOP_DEPTH)
-                       smsg("%s%4d STOREOUTER level 1 $%d in loop",
-                               pfx, current, outer->outer_idx);
+                   if (outer->outer_depth < 0)
+                       smsg("%s%4d STOREOUTER $%d in loop level %d",
+                               pfx, current, outer->outer_idx,
+                               -outer->outer_depth);
                    else
                        smsg("%s%4d STOREOUTER level %d $%d", pfx, current,
                                outer->outer_depth, outer->outer_idx);
index 24834af673d65278246f5b4d96ae4d933b568cfd..5e9ab38b7bc6131e0828b5c3fd3b5d0941934225 100644 (file)
@@ -1177,7 +1177,12 @@ generate_CLASSMEMBER(
  * Generate an ISN_STOREOUTER instruction.
  */
     static int
-generate_STOREOUTER(cctx_T *cctx, int idx, int level, int loop_idx)
+generate_STOREOUTER(
+       cctx_T      *cctx,
+       int         idx,
+       int         level,
+       int         loop_depth,
+       int         loop_idx)
 {
     isn_T      *isn;
 
@@ -1187,9 +1192,9 @@ generate_STOREOUTER(cctx_T *cctx, int idx, int level, int loop_idx)
     if (level == 1 && loop_idx >= 0 && idx >= loop_idx)
     {
        // Store a variable defined in a loop.  A copy will be made at the end
-       // of the loop.  TODO: how about deeper nesting?
+       // of the loop.
        isn->isn_arg.outer.outer_idx = idx - loop_idx;
-       isn->isn_arg.outer.outer_depth = OUTER_LOOP_DEPTH;
+       isn->isn_arg.outer.outer_depth = -loop_depth - 1;
     }
     else
     {
@@ -2644,7 +2649,8 @@ generate_store_lhs(cctx_T *cctx, lhs_T *lhs, int instr_count, int is_decl)
     }
     else if (lhs->lhs_lvar->lv_from_outer > 0)
        generate_STOREOUTER(cctx, lhs->lhs_lvar->lv_idx,
-               lhs->lhs_lvar->lv_from_outer, lhs->lhs_lvar->lv_loop_idx);
+               lhs->lhs_lvar->lv_from_outer, lhs->lhs_lvar->lv_loop_depth,
+               lhs->lhs_lvar->lv_loop_idx);
     else
        generate_STORE(cctx, ISN_STORE, lhs->lhs_lvar->lv_idx, NULL);
     return OK;