mirror of
https://github.com/neovim/neovim.git
synced 2024-12-31 17:13:26 -07:00
fix(treesitter): fix TSNode:tree() double free (#24796)
Problem: `push_tree`, every time its called for the same TSTree with `do_copy=false` argument, creates a new userdata for it. Each userdata, when garbage collected, frees the same TSTree C object. Solution: Add flag to userdata, which indicates, should C object, which userdata points to, be freed, when userdata is garbage collected.
This commit is contained in:
parent
e3389c1533
commit
a3c963adfc
@ -43,6 +43,11 @@ typedef struct {
|
|||||||
int max_match_id;
|
int max_match_id;
|
||||||
} TSLua_cursor;
|
} TSLua_cursor;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
TSTree *tree;
|
||||||
|
bool gc;
|
||||||
|
} TSLuaTree;
|
||||||
|
|
||||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
# include "lua/treesitter.c.generated.h"
|
# include "lua/treesitter.c.generated.h"
|
||||||
#endif
|
#endif
|
||||||
@ -402,8 +407,8 @@ static int parser_parse(lua_State *L)
|
|||||||
|
|
||||||
TSTree *old_tree = NULL;
|
TSTree *old_tree = NULL;
|
||||||
if (!lua_isnil(L, 2)) {
|
if (!lua_isnil(L, 2)) {
|
||||||
TSTree **tmp = tree_check(L, 2);
|
TSLuaTree *ud = tree_check(L, 2);
|
||||||
old_tree = tmp ? *tmp : NULL;
|
old_tree = ud ? ud->tree : NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
TSTree *new_tree = NULL;
|
TSTree *new_tree = NULL;
|
||||||
@ -456,7 +461,7 @@ static int parser_parse(lua_State *L)
|
|||||||
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, false); // [tree]
|
push_tree(L, new_tree, true); // [tree]
|
||||||
|
|
||||||
push_ranges(L, changed, n_ranges, include_bytes); // [tree, ranges]
|
push_ranges(L, changed, n_ranges, include_bytes); // [tree, ranges]
|
||||||
|
|
||||||
@ -476,12 +481,13 @@ static int parser_reset(lua_State *L)
|
|||||||
|
|
||||||
static int tree_copy(lua_State *L)
|
static int tree_copy(lua_State *L)
|
||||||
{
|
{
|
||||||
TSTree **tree = tree_check(L, 1);
|
TSLuaTree *ud = tree_check(L, 1);
|
||||||
if (!(*tree)) {
|
if (!ud) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
push_tree(L, *tree, true); // [tree]
|
TSTree *copy = ts_tree_copy(ud->tree);
|
||||||
|
push_tree(L, copy, true); // [tree]
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -493,8 +499,8 @@ static int tree_edit(lua_State *L)
|
|||||||
return lua_error(L);
|
return lua_error(L);
|
||||||
}
|
}
|
||||||
|
|
||||||
TSTree **tree = tree_check(L, 1);
|
TSLuaTree *ud = tree_check(L, 1);
|
||||||
if (!(*tree)) {
|
if (!ud) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -508,22 +514,22 @@ static int tree_edit(lua_State *L)
|
|||||||
TSInputEdit edit = { start_byte, old_end_byte, new_end_byte,
|
TSInputEdit edit = { start_byte, old_end_byte, new_end_byte,
|
||||||
start_point, old_end_point, new_end_point };
|
start_point, old_end_point, new_end_point };
|
||||||
|
|
||||||
ts_tree_edit(*tree, &edit);
|
ts_tree_edit(ud->tree, &edit);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int tree_get_ranges(lua_State *L)
|
static int tree_get_ranges(lua_State *L)
|
||||||
{
|
{
|
||||||
TSTree **tree = tree_check(L, 1);
|
TSLuaTree *ud = tree_check(L, 1);
|
||||||
if (!(*tree)) {
|
if (!ud) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool include_bytes = (lua_gettop(L) >= 2) && lua_toboolean(L, 2);
|
bool include_bytes = (lua_gettop(L) >= 2) && lua_toboolean(L, 2);
|
||||||
|
|
||||||
uint32_t len;
|
uint32_t len;
|
||||||
TSRange *ranges = ts_tree_included_ranges(*tree, &len);
|
TSRange *ranges = ts_tree_included_ranges(ud->tree, &len);
|
||||||
|
|
||||||
push_ranges(L, ranges, len, include_bytes);
|
push_ranges(L, ranges, len, include_bytes);
|
||||||
|
|
||||||
@ -676,20 +682,17 @@ static int parser_get_timeout(lua_State *L)
|
|||||||
|
|
||||||
/// push tree interface on lua stack.
|
/// push tree interface on lua stack.
|
||||||
///
|
///
|
||||||
/// This makes a copy of the tree, so ownership of the argument is unaffected.
|
/// The tree is garbage collected if gc is true
|
||||||
void push_tree(lua_State *L, TSTree *tree, bool do_copy)
|
void push_tree(lua_State *L, TSTree *tree, bool gc)
|
||||||
{
|
{
|
||||||
if (tree == NULL) {
|
if (tree == NULL) {
|
||||||
lua_pushnil(L);
|
lua_pushnil(L);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
TSTree **ud = lua_newuserdata(L, sizeof(TSTree *)); // [udata]
|
TSLuaTree *ud = lua_newuserdata(L, sizeof(TSLuaTree)); // [udata]
|
||||||
|
|
||||||
if (do_copy) {
|
ud->tree = tree;
|
||||||
*ud = ts_tree_copy(tree);
|
ud->gc = gc;
|
||||||
} else {
|
|
||||||
*ud = tree;
|
|
||||||
}
|
|
||||||
|
|
||||||
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]
|
||||||
@ -703,20 +706,18 @@ void push_tree(lua_State *L, TSTree *tree, bool do_copy)
|
|||||||
lua_setfenv(L, -2); // [udata]
|
lua_setfenv(L, -2); // [udata]
|
||||||
}
|
}
|
||||||
|
|
||||||
static TSTree **tree_check(lua_State *L, int index)
|
static TSLuaTree *tree_check(lua_State *L, int index)
|
||||||
{
|
{
|
||||||
TSTree **ud = luaL_checkudata(L, index, TS_META_TREE);
|
TSLuaTree *ud = luaL_checkudata(L, index, TS_META_TREE);
|
||||||
return ud;
|
return ud;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int tree_gc(lua_State *L)
|
static int tree_gc(lua_State *L)
|
||||||
{
|
{
|
||||||
TSTree **tree = tree_check(L, 1);
|
TSLuaTree *ud = tree_check(L, 1);
|
||||||
if (!tree) {
|
if (ud && ud->gc) {
|
||||||
return 0;
|
ts_tree_delete(ud->tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
ts_tree_delete(*tree);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -728,11 +729,11 @@ static int tree_tostring(lua_State *L)
|
|||||||
|
|
||||||
static int tree_root(lua_State *L)
|
static int tree_root(lua_State *L)
|
||||||
{
|
{
|
||||||
TSTree **tree = tree_check(L, 1);
|
TSLuaTree *ud = tree_check(L, 1);
|
||||||
if (!tree) {
|
if (!ud) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
TSNode root = ts_tree_root_node(*tree);
|
TSNode root = ts_tree_root_node(ud->tree);
|
||||||
push_node(L, root, 1);
|
push_node(L, root, 1);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ local clear = helpers.clear
|
|||||||
local eq = helpers.eq
|
local eq = helpers.eq
|
||||||
local exec_lua = helpers.exec_lua
|
local exec_lua = helpers.exec_lua
|
||||||
local insert = helpers.insert
|
local insert = helpers.insert
|
||||||
|
local assert_alive = helpers.assert_alive
|
||||||
|
|
||||||
before_each(clear)
|
before_each(clear)
|
||||||
|
|
||||||
@ -14,6 +15,17 @@ end
|
|||||||
describe('treesitter node API', function()
|
describe('treesitter node API', function()
|
||||||
clear()
|
clear()
|
||||||
|
|
||||||
|
it('double free tree', function()
|
||||||
|
insert('F')
|
||||||
|
exec_lua([[
|
||||||
|
vim.treesitter.start(0, 'lua')
|
||||||
|
vim.treesitter.get_node():tree()
|
||||||
|
vim.treesitter.get_node():tree()
|
||||||
|
collectgarbage()
|
||||||
|
]])
|
||||||
|
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) {
|
||||||
|
Loading…
Reference in New Issue
Block a user