From a5e5ec8910ea35ebb86dcba7f58333d9d4caca47 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Fri, 16 Jan 2026 00:53:55 +0000 Subject: [PATCH] fix(api): parse_expression crash with unopened ] and node Problem: nvim_parse_expression null pointer dereference with unmatched ] followed by a node. Solution: if ast_stack was empty, set new_top_node_p to top of the stack after pushing the list literal node; similar to what's done for curlies. This bug was originally found by a Matrix user, but I couldn't remember how to trigger it... Ran into the other crash while finding a repro. :P --- src/nvim/viml/parser/expressions.c | 3 +- test/functional/api/vim_spec.lua | 2 ++ test/unit/viml/expressions/parser_tests.lua | 32 +++++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 1d5852526c..c82584f137 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -2406,7 +2406,7 @@ viml_pexpr_parse_valid_colon: // Always drop the topmost value: // // 1. When want_node != kENodeValue topmost item on stack is - // a *finished* left operand, which may as well be "{@a}" which + // a *finished* left operand, which may as well be "[@a]" which // needs not be finished again. // 2. Otherwise it is pointing to NULL what nobody wants. kv_drop(ast_stack, 1); @@ -2417,6 +2417,7 @@ viml_pexpr_parse_valid_colon: cur_node->children = *top_node_p; } *top_node_p = cur_node; + new_top_node_p = top_node_p; goto viml_pexpr_parse_bracket_closing_error; } if (want_node == kENodeValue) { diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index cc90eec84f..ae0fa4b07d 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -3242,6 +3242,8 @@ describe('API', function() api.nvim_input(':=') api.nvim_input('1bork/') -- #29648 assert_alive() + api.nvim_input('];') + assert_alive() api.nvim_parse_expression('a{b}', '', false) assert_alive() end) diff --git a/test/unit/viml/expressions/parser_tests.lua b/test/unit/viml/expressions/parser_tests.lua index aa2bf740de..25c92ab25d 100644 --- a/test/unit/viml/expressions/parser_tests.lua +++ b/test/unit/viml/expressions/parser_tests.lua @@ -4755,6 +4755,38 @@ return function(itp, _check_parsing, hl, fmtn) hl('InvalidList', ']'), }) + check_parsing(']a', { + -- 01 + ast = { + { + 'OpMissing:0:1:', + children = { + 'ListLiteral:0:0:', + 'PlainIdentifier(scope=0,ident=a):0:1:a', + }, + }, + }, + err = { + arg = ']a', + msg = 'E15: Unexpected closing figure brace: %.*s', + }, + }, { + hl('InvalidList', ']'), + hl('InvalidIdentifierName', 'a'), + }, { + [1] = { + ast = { + len = 1, + ast = { + 'ListLiteral:0:0:', + }, + }, + hl_fs = { + [2] = REMOVE_THIS, + }, + }, + }) + check_parsing('[] []', { -- 01234 ast = {