From: Hirohito Higashi Date: Thu, 30 Jul 2026 18:43:11 +0000 (+0000) Subject: patch 9.2.0877: Vim9: crash when a closure assigns to a variable declared in a loop X-Git-Tag: v9.2.0877^0 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=4fce82d7535b936aa356d13f9f9d5418cd7a7952;p=thirdparty%2Fvim.git patch 9.2.0877: Vim9: crash when a closure assigns to a variable declared in a loop 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) Signed-off-by: Hirohito Higashi Signed-off-by: Christian Brabandt --- diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim index 2f54f6da62..91873845d8 100644 --- a/src/testdir/test_vim9_script.vim +++ b/src/testdir/test_vim9_script.vim @@ -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 + 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() diff --git a/src/version.c b/src/version.c index e921a5c367..3fba5845f9 100644 --- a/src/version.c +++ b/src/version.c @@ -758,6 +758,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 877, /**/ 876, /**/ diff --git a/src/vim9.h b/src/vim9.h index c10d435a24..58cda7b852 100644 --- a/src/vim9.h +++ b/src/vim9.h @@ -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 diff --git a/src/vim9execute.c b/src/vim9execute.c index 68ca777d06..1c30338eb8 100644 --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -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); diff --git a/src/vim9instr.c b/src/vim9instr.c index 24834af673..5e9ab38b7b 100644 --- a/src/vim9instr.c +++ b/src/vim9instr.c @@ -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;