fix(treesitter): fix another TSNode:tree() double free

Unfortunately the gc=false objects can refer to a dangling tree if the
gc=true tree was freed first. This reuses the same tree object as the
node itself is keeping alive via the uservalue of the node userdata.
(wrapped in a table due to lua 5.1 restrictions)
This commit is contained in:
bfredl 2023-08-29 11:21:57 +02:00
parent 6e45567b49
commit 50a03c0e99
2 changed files with 25 additions and 11 deletions

View File

@ -53,7 +53,6 @@ typedef struct {
typedef struct { typedef struct {
TSTree *tree; TSTree *tree;
bool gc;
} TSLuaTree; } TSLuaTree;
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
@ -477,13 +476,12 @@ static int parser_parse(lua_State *L)
return luaL_error(L, "An error occurred when parsing."); return luaL_error(L, "An error occurred when parsing.");
} }
// The new tree will be pushed to the stack, without copy, ownership is now to // The new tree will be pushed to the stack, without copy, ownership is now to the lua GC.
// the lua GC. // Old tree is owned by lua GC since before
// Old tree is still owned by the lua GC.
uint32_t n_ranges = 0; uint32_t n_ranges = 0;
TSRange *changed = old_tree ? ts_tree_get_changed_ranges(old_tree, new_tree, &n_ranges) : NULL; TSRange *changed = old_tree ? ts_tree_get_changed_ranges(old_tree, new_tree, &n_ranges) : NULL;
push_tree(L, new_tree, true); // [tree] push_tree(L, new_tree); // [tree]
push_ranges(L, changed, n_ranges, include_bytes); // [tree, ranges] push_ranges(L, changed, n_ranges, include_bytes); // [tree, ranges]
@ -509,7 +507,7 @@ static int tree_copy(lua_State *L)
} }
TSTree *copy = ts_tree_copy(ud->tree); TSTree *copy = ts_tree_copy(ud->tree);
push_tree(L, copy, true); // [tree] push_tree(L, copy); // [tree]
return 1; return 1;
} }
@ -779,8 +777,9 @@ static int parser_get_logger(lua_State *L)
/// push tree interface on lua stack. /// push tree interface on lua stack.
/// ///
/// The tree is garbage collected if gc is true /// The tree is not copied. Ownership of the tree is transfered from c code to
void push_tree(lua_State *L, TSTree *tree, bool gc) /// lua. if needed use ts_tree_copy() in the caller
void push_tree(lua_State *L, TSTree *tree)
{ {
if (tree == NULL) { if (tree == NULL) {
lua_pushnil(L); lua_pushnil(L);
@ -789,7 +788,6 @@ void push_tree(lua_State *L, TSTree *tree, bool gc)
TSLuaTree *ud = lua_newuserdata(L, sizeof(TSLuaTree)); // [udata] TSLuaTree *ud = lua_newuserdata(L, sizeof(TSLuaTree)); // [udata]
ud->tree = tree; ud->tree = tree;
ud->gc = gc;
lua_getfield(L, LUA_REGISTRYINDEX, TS_META_TREE); // [udata, meta] lua_getfield(L, LUA_REGISTRYINDEX, TS_META_TREE); // [udata, meta]
lua_setmetatable(L, -2); // [udata] lua_setmetatable(L, -2); // [udata]
@ -812,7 +810,7 @@ static TSLuaTree *tree_check(lua_State *L, int index)
static int tree_gc(lua_State *L) static int tree_gc(lua_State *L)
{ {
TSLuaTree *ud = tree_check(L, 1); TSLuaTree *ud = tree_check(L, 1);
if (ud && ud->gc) { if (ud) {
ts_tree_delete(ud->tree); ts_tree_delete(ud->tree);
} }
return 0; return 0;
@ -1322,7 +1320,9 @@ static int node_tree(lua_State *L)
return 0; return 0;
} }
push_tree(L, (TSTree *)node.tree, false); lua_getfenv(L, 1); // [udata, reftable]
lua_rawgeti(L, -1, 1); // [udata, reftable, tree_udata]
return 1; return 1;
} }

View File

@ -26,6 +26,20 @@ describe('treesitter node API', function()
assert_alive() assert_alive()
end) end)
it('double free tree 2', function()
exec_lua([[
parser = vim.treesitter.get_parser(0, "c")
local x = parser:parse()[1]:root():tree()
vim.api.nvim_buf_set_text(0, 0,0, 0,0, {'y'})
parser:parse()
vim.api.nvim_buf_set_text(0, 0,0, 0,1, {'z'})
parser:parse()
collectgarbage()
x:root()
]])
assert_alive()
end)
it('can move between siblings', function() it('can move between siblings', function()
insert([[ insert([[
int main(int x, int y, int z) { int main(int x, int y, int z) {