mirror of
https://github.com/neovim/neovim
synced 2025-07-15 16:51:49 +00:00
fix(treesitter): ensure TSLuaTree is always immutable
Problem:
The previous fix in #34314 relies on copying the tree in `tree_root` to
ensure the `TSNode`'s tree cannot be mutated. But that causes the
problem where two calls to `tree_root` return nodes from different
copies of a tree, which do not compare as equal. This has broken at
least one plugin.
Solution:
Make all `TSTree`s on the Lua side always immutable, avoiding the need
to copy the tree in `tree_root`, and make the only mutation point,
`tree_edit`, copy the tree instead.
(cherry picked from commit 168bf0024e
)
This commit is contained in:
committed by
github-actions[bot]
parent
37fb09c162
commit
a80bdf0d9b
@ -26,6 +26,7 @@ function TSTree:root() end
|
||||
---@param end_col_old integer
|
||||
---@param end_row_new integer
|
||||
---@param end_col_new integer
|
||||
---@return TSTree
|
||||
---@nodoc
|
||||
function TSTree:edit(start_byte, end_byte_old, end_byte_new, start_row, start_col, end_row_old, end_col_old, end_row_new, end_col_new) end
|
||||
|
||||
|
@ -1082,8 +1082,8 @@ function LanguageTree:_edit(
|
||||
end_row_new,
|
||||
end_col_new
|
||||
)
|
||||
for _, tree in pairs(self._trees) do
|
||||
tree:edit(
|
||||
for i, tree in pairs(self._trees) do
|
||||
self._trees[i] = tree:edit(
|
||||
start_byte,
|
||||
end_byte_old,
|
||||
end_byte_new,
|
||||
|
@ -49,7 +49,8 @@ typedef struct {
|
||||
} TSLuaLoggerOpts;
|
||||
|
||||
typedef struct {
|
||||
TSTree *tree;
|
||||
// We derive TSNode's, TSQueryCursor's, etc., from the TSTree, so it must not be mutated.
|
||||
const TSTree *tree;
|
||||
} TSLuaTree;
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
@ -490,7 +491,7 @@ static void push_ranges(lua_State *L, const TSRange *ranges, const size_t length
|
||||
static int parser_parse(lua_State *L)
|
||||
{
|
||||
TSParser *p = parser_check(L, 1);
|
||||
TSTree *old_tree = NULL;
|
||||
const TSTree *old_tree = NULL;
|
||||
if (!lua_isnil(L, 2)) {
|
||||
TSLuaTree *ud = luaL_checkudata(L, 2, TS_META_TREE);
|
||||
old_tree = ud ? ud->tree : NULL;
|
||||
@ -765,8 +766,8 @@ static struct luaL_Reg tree_meta[] = {
|
||||
/// Push tree interface on to the lua stack.
|
||||
///
|
||||
/// The tree is not copied. Ownership of the tree is transferred from C to
|
||||
/// Lua. If needed use ts_tree_copy() in the caller
|
||||
static void push_tree(lua_State *L, TSTree *tree)
|
||||
/// Lua. If needed use ts_tree_copy() in the caller.
|
||||
static void push_tree(lua_State *L, const TSTree *tree)
|
||||
{
|
||||
if (tree == NULL) {
|
||||
lua_pushnil(L);
|
||||
@ -807,9 +808,12 @@ static int tree_edit(lua_State *L)
|
||||
TSInputEdit edit = { start_byte, old_end_byte, new_end_byte,
|
||||
start_point, old_end_point, new_end_point };
|
||||
|
||||
ts_tree_edit(ud->tree, &edit);
|
||||
TSTree *new_tree = ts_tree_copy(ud->tree);
|
||||
ts_tree_edit(new_tree, &edit);
|
||||
|
||||
return 0;
|
||||
push_tree(L, new_tree); // [tree]
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int tree_get_ranges(lua_State *L)
|
||||
@ -830,7 +834,12 @@ static int tree_get_ranges(lua_State *L)
|
||||
static int tree_gc(lua_State *L)
|
||||
{
|
||||
TSLuaTree *ud = luaL_checkudata(L, 1, TS_META_TREE);
|
||||
ts_tree_delete(ud->tree);
|
||||
|
||||
// SAFETY: we can cast the const away because the tree is only garbage collected after all of its
|
||||
// TSNode's, TSQuerCurors, etc., are unreachable (each contains a reference to the TSLuaTree)
|
||||
TSTree *tree = (TSTree *)ud->tree;
|
||||
|
||||
ts_tree_delete(tree);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -844,11 +853,7 @@ static int tree_root(lua_State *L)
|
||||
{
|
||||
TSLuaTree *ud = luaL_checkudata(L, 1, TS_META_TREE);
|
||||
|
||||
// The tree may be mutate by tree_edit, but node needs that its tree is not
|
||||
// mutated while the node is alive. So we make a copy of tree here.
|
||||
TSTree *tree_copy = ts_tree_copy(ud->tree);
|
||||
|
||||
TSNode root = ts_tree_root_node(tree_copy);
|
||||
TSNode root = ts_tree_root_node(ud->tree);
|
||||
|
||||
TSNode *node_ud = lua_newuserdata(L, sizeof(TSNode)); // [node]
|
||||
*node_ud = root;
|
||||
@ -860,7 +865,7 @@ static int tree_root(lua_State *L)
|
||||
// Note: environments (fenvs) associated with userdata have no meaning in Lua
|
||||
// and are only used to associate a table.
|
||||
lua_createtable(L, 1, 0); // [node, reftable]
|
||||
push_tree(L, tree_copy); // [node, reftable, tree]
|
||||
lua_pushvalue(L, 1); // [node, reftable, tree]
|
||||
lua_rawseti(L, -2, 1); // [node, reftable]
|
||||
lua_setfenv(L, -2); // [node]
|
||||
|
||||
@ -1312,13 +1317,13 @@ static int node_root(lua_State *L)
|
||||
|
||||
static int node_tree(lua_State *L)
|
||||
{
|
||||
TSNode node = node_check(L, 1);
|
||||
node_check(L, 1);
|
||||
|
||||
// The node's tree must not be mutated, but `tree_edit` may violate that. So
|
||||
// we make a copy of the tree before pushing it to the LUA stack.
|
||||
TSTree *tree = ts_tree_copy(node.tree);
|
||||
// Get the tree from the node fenv. We cannot use `push_tree(node.tree)` here because that would
|
||||
// cause a double free.
|
||||
lua_getfenv(L, 1); // [node, reftable]
|
||||
lua_rawgeti(L, 2, 1); // [node, reftable, tree]
|
||||
|
||||
push_tree(L, tree);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user