mirror of
https://github.com/neovim/neovim.git
synced 2026-06-19 10:02:36 +00:00
fix(treesitter): crash in ts_parser_delete after gc #39497
Problem:
parser_gc() calls ts_parser_delete() but leaves the userdata pointer
pointing to freed memory. If the GC finalizer runs at an unexpected time
(e.g. inside nvim_buf_get_lines #39411), a stale pointer could cause a crash.
Solution:
- NULL out `*ud` after ts_parser_delete() in parser_gc()
- Update parser_check() to handle NULL with a clear error message,
guarding all parser methods against UAF
Co-authored-by: Lewis Russell <lewis6991@gmail.com>
Signed-off-by: Szymon Wilczek <swilczek.lx@gmail.com>
(cherry picked from commit 0c3e6e1b0e)
This commit is contained in:
committed by
github-actions[bot]
parent
382882976e
commit
dcf9e8a98e
@@ -400,10 +400,10 @@ static int tslua_push_parser(lua_State *L)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static TSParser *parser_check(lua_State *L, uint16_t index)
|
static TSParser *parser_check(lua_State *L, int index)
|
||||||
{
|
{
|
||||||
TSParser **ud = luaL_checkudata(L, index, TS_META_PARSER);
|
TSParser **ud = luaL_checkudata(L, index, TS_META_PARSER);
|
||||||
luaL_argcheck(L, *ud, index, "TSParser expected");
|
luaL_argcheck(L, *ud != NULL, index, "Parser has been deleted");
|
||||||
return *ud;
|
return *ud;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,9 +420,12 @@ static void logger_gc(TSLogger logger)
|
|||||||
|
|
||||||
static int parser_gc(lua_State *L)
|
static int parser_gc(lua_State *L)
|
||||||
{
|
{
|
||||||
TSParser *p = parser_check(L, 1);
|
TSParser **ud = luaL_checkudata(L, 1, TS_META_PARSER);
|
||||||
logger_gc(ts_parser_logger(p));
|
if (*ud) {
|
||||||
ts_parser_delete(p);
|
logger_gc(ts_parser_logger(*ud));
|
||||||
|
ts_parser_delete(*ud);
|
||||||
|
*ud = NULL;
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ local eq = t.eq
|
|||||||
local insert = n.insert
|
local insert = n.insert
|
||||||
local exec_lua = n.exec_lua
|
local exec_lua = n.exec_lua
|
||||||
local feed = n.feed
|
local feed = n.feed
|
||||||
|
local matches = t.matches
|
||||||
|
local pcall_err = t.pcall_err
|
||||||
local run_query = ts_t.run_query
|
local run_query = ts_t.run_query
|
||||||
local assert_alive = n.assert_alive
|
local assert_alive = n.assert_alive
|
||||||
|
|
||||||
@@ -107,6 +109,24 @@ describe('treesitter parser API', function()
|
|||||||
)
|
)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('ignores repeated parser finalization', function()
|
||||||
|
matches(
|
||||||
|
'Parser has been deleted',
|
||||||
|
pcall_err(exec_lua, function()
|
||||||
|
vim.treesitter.language.add('c')
|
||||||
|
|
||||||
|
local parser = vim._create_ts_parser('c')
|
||||||
|
-- The parser metatable exposes __gc through __index, so this simulates a finalizer
|
||||||
|
-- running before all Lua references to the userdata are gone.
|
||||||
|
parser:__gc()
|
||||||
|
parser:__gc()
|
||||||
|
|
||||||
|
parser:parse(nil, 'int x;', false)
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
assert_alive()
|
||||||
|
end)
|
||||||
|
|
||||||
it('respects eol settings when parsing buffer', function()
|
it('respects eol settings when parsing buffer', function()
|
||||||
insert([[
|
insert([[
|
||||||
int main() {
|
int main() {
|
||||||
|
|||||||
Reference in New Issue
Block a user