[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.