[vim/vim] vim9: SIGSEGV storing to a loop variable from a closure — generate_STOREOUTER() encodes loop depth with a sentinel the executor decodes as an array index (Issue #20877)

"Niklas E. O. Harju" (Vim Github Repository) <[email protected]> Wed, 29 Jul 2026 14:56:32 -0700
Newsgroups gmane.editors.vim.devel
Message-ID <vim/vim/issues/[email protected]>
neoharju created an issue (vim/vim#20877)

### Steps to reproduce

```vim9script
vim9script
def BenchTimer(iterations: number): void
    for i in range(iterations)
        var done = false
        timer_start(1, (_id: number) => {
            done = true
         })
         var w = 0
         while !done && w < 1000
             sleep 5m
             w += 5
         endwhile
    endfor
enddef
BenchTimer(1)
```

`vim -u NONE -N -es -S repro.vim` → Segmentation fault (exit 139). Crashes with a single iteration. No channels or jobs needed — plain timer_start() is enough. 

### Expected behaviour

Backtrace puts the crash at vim9execute.c, in exec_instructions() handling ISN_LOADOUTER/ISN_STOREOUTER: 
`tv = ((typval_T *)outer->out_loop[-depth - 1].stack->ga_data)`

gdb at the crash site: 

```c++
depth                            = -9
outer->out_loop_size = 1
outer->out_loop[8]     = {stack = 0x0, var_idx = 0, var_count = 0}
````

So `-depth - 1` is 8, indexing an array whose only valid entry is 0, and .stack is NULL → null dereference. 

-9 is OUTER_LOOP_DEPTH (vim9.h), used by generate_STOREOUTER(): 
/* vim9instr.c, generate_STOREOUTER() */
`isn->isn_arg.outer.outer_depth = OUTER_LOOP_DEPTH; /* -9 */`


But its sibling generate_LOADOUTER(), for the same situation, encodes the depth the way the executor actually decodes it: 
/* vim9instr.c, generate_LOADOUTER() */
`isn->isn_arg.outer.outer_depth = -loop_depth - 1; /* -1 for depth 0 */`


The executor has one decoder for both instructions, so STOREOUTER must use the same encoding. Using a fixed sentinel also can’t express loop depth at all, so nested loops could never have worked through this path. So it could be fixed by e.g. make generate_STOREOUTER() take loop_depth and encode -loop_depth - 1, exactly like generate_LOADOUTER(); pass `lhs->lhs_lvar->lv_loop_depth` at the call site (already available and already used for the LOADOUTER call). 

- add a bounds check in exec_instructions() before indexing out_loop[], so a bad index reports an internal error instead of crashing
Vim;
- update the ISN_STOREOUTER disassembly to detect loop variables by negative depth (like ISN_LOADOUTER does) rather than the sentinel,
printing STOREOUTER $0 in loop level 1

OUTER_LOOP_DEPTH becomes unused after this and could be removed from vim9.h

### Version of Vim

Reproduced on Vim 9.2 (patches 1-804, commit 6f02e5cd7, built from source with --with-features=huge) and on distro-packaged Vim 9.1.0016-1ubuntu7.18. Linux x86_64. 

### Environment

fix example: [0001-vim9-fix-STOREOUTER-loop-depth-segfault.patch](https://github.com/user-attachments/files/30523825/0001-vim9-fix-STOREOUTER-loop-depth-segfault.patch)

### Logs and stack traces

```shell

```

-- 
Reply to this email directly or view it on GitHub:
https://github.com/vim/vim/issues/20877
You are receiving this because you are subscribed to this thread.

Message ID: <vim/vim/issues/[email protected]>

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups "vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To view this discussion visit https://groups.google.com/d/msgid/vim_dev/vim/vim/issues/20877%40github.com.