diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index a346bf5963..2c24264919 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -774,20 +774,9 @@ static void push_tree(lua_State *L, TSTree *tree) } TSLuaTree *ud = lua_newuserdata(L, sizeof(TSLuaTree)); // [udata] - ud->tree = tree; - lua_getfield(L, LUA_REGISTRYINDEX, TS_META_TREE); // [udata, meta] lua_setmetatable(L, -2); // [udata] - - // To prevent the tree from being garbage collected, create a reference to it - // in the fenv which will be passed to userdata nodes of the tree. - // Note: environments (fenvs) associated with userdata have no meaning in Lua - // and are only used to associate a table. - lua_createtable(L, 1, 0); // [udata, reftable] - lua_pushvalue(L, -2); // [udata, reftable, udata] - lua_rawseti(L, -2, 1); // [udata, reftable] - lua_setfenv(L, -2); // [udata] } static int tree_copy(lua_State *L) @@ -854,8 +843,27 @@ static int tree_tostring(lua_State *L) static int tree_root(lua_State *L) { TSLuaTree *ud = luaL_checkudata(L, 1, TS_META_TREE); - TSNode root = ts_tree_root_node(ud->tree); - push_node(L, root, 1); + + // 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 *node_ud = lua_newuserdata(L, sizeof(TSNode)); // [node] + *node_ud = root; + lua_getfield(L, LUA_REGISTRYINDEX, TS_META_NODE); // [node, meta] + lua_setmetatable(L, -2); // [node] + + // To prevent the tree from being garbage collected, create a reference to it + // in the fenv which will be passed to userdata nodes of the tree. + // 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_rawseti(L, -2, 1); // [node, reftable] + lua_setfenv(L, -2); // [node] + return 1; } @@ -902,9 +910,9 @@ static struct luaL_Reg node_meta[] = { /// Push node interface on to the Lua stack /// -/// Top of stack must either be the tree this node belongs to or another node -/// of the same tree! This value is not popped. Can only be called inside a -/// cfunction with the tslua environment. +/// Stack at `uindex` must have a value with a fenv with a reference to node's +/// tree. This value is not popped. Can only be called inside a cfunction with +/// the tslua environment. static void push_node(lua_State *L, TSNode node, int uindex) { assert(uindex > 0 || uindex < -LUA_MINSTACK); @@ -912,12 +920,13 @@ static void push_node(lua_State *L, TSNode node, int uindex) lua_pushnil(L); // [nil] return; } + TSNode *ud = lua_newuserdata(L, sizeof(TSNode)); // [udata] *ud = node; lua_getfield(L, LUA_REGISTRYINDEX, TS_META_NODE); // [udata, meta] lua_setmetatable(L, -2); // [udata] - // Copy the fenv which contains the nodes tree. + // Copy the fenv to keep alive a reference to the node's tree. lua_getfenv(L, uindex); // [udata, reftable] lua_setfenv(L, -2); // [udata] } @@ -1303,9 +1312,13 @@ static int node_root(lua_State *L) static int node_tree(lua_State *L) { - node_check(L, 1); - lua_getfenv(L, 1); // [udata, reftable] - lua_rawgeti(L, -1, 1); // [udata, reftable, tree_udata] + TSNode node = 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); + + push_tree(L, tree); return 1; }