[vim/vim] stringifying a list or dict can free the item being iterated (PR #21001)

Samuel Schlesinger (Vim Github Repository) <[email protected]> Mon, 10 Aug 2026 17:52:49 -0700
Newsgroups gmane.editors.vim.devel
Message-ID <vim/vim/pull/[email protected]>
## Problem

`match()`, `matchstr()`, `matchend()`, `matchlist()` and `matchstrpos()`
over a list, `join()` of a list, and `string()` or `:echo` of a list or
dict stringify each item while iterating over the container.  When an item
is an object this runs its user-defined `string()` method, which can remove
the item the loop is standing on, or grow a dict so that its hash table is
reallocated.  Either leaves the loop reading freed memory.

`find_some_match()` in `evalfunc.c`, `list_join_inner()` in `list.c` and
`dict2string()` in `dict.c` each iterate a container while calling
`echo_string()`.  A minimal reproducer for each crashes an unpatched Vim:

```vim
vim9script
class C
  def string(): string
    if len(g:l) > 0
      remove(g:l, 0)
    endif
    return 'x'
  enddef
endclass
g:l = [C.new(), C.new(), C.new()]
echo match(g:l, 'y')   " also: join(g:l), string(g:l)
```

## Solution

Lock the container while iterating, so a change from the `string()` method
fails with `E741` instead of corrupting the iterator, the same way
`filter()`, `map()`, `sort()` and `reduce()` already do.  For a dict also
`hash_lock()` it, so a grow cannot reallocate the hash table mid-iteration,
mirroring the dict path of `filter()`/`map()`.  `list_reduce()` was the
existing pattern these three were missing.

Adds tests for `match()` and `matchstr()` over a list, `join()` and
`string()` of a list, and `string()` of a dict, each with an object whose
`string()` method mutates the container; every one crashes an unpatched
Vim.

AI assistance is acknowledged with Co-Authored-By trailers on the commit,
per AGENTS.md.

You can view, comment on, or merge this pull request online at:

  https://github.com/vim/vim/pull/21001

-- Commit Summary --

  * stringifying a list or dict can free the item being iterated

-- File Changes --

    M src/dict.c (12)
    M src/evalfunc.c (13)
    M src/list.c (11)
    M src/testdir/test_functions.vim (83)

-- Patch Links --

https://github.com/vim/vim/pull/21001.patch
https://github.com/vim/vim/pull/21001.diff

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

Message ID: <vim/vim/pull/[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/pull/21001%40github.com.