From de48fbbd5f8800bd7f1909a6fb41e53e871cf74c Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Thu, 20 Jun 2024 14:48:06 +0200 Subject: [PATCH] fix(messages)!: vim.ui_attach message callbacks are unsafe Problem: Lua callbacks for "msg_show" events with vim.ui_attach() are executed when it is not safe. Solution: Disallow non-fast API calls for "msg_show" event callbacks. Automatically detach callback after excessive errors. Make sure fast APIs do not modify Nvim state. --- runtime/doc/lua.txt | 4 + runtime/doc/news.txt | 3 +- runtime/lua/vim/_meta/builtin.lua | 4 + src/nvim/api/ui_events.in.h | 2 +- src/nvim/api/vim.c | 8 +- src/nvim/errors.h | 2 +- src/nvim/eval.c | 2 +- src/nvim/generators/gen_api_dispatch.lua | 2 +- src/nvim/generators/gen_api_ui_events.lua | 4 +- src/nvim/lua/executor.c | 39 ++++-- src/nvim/ui.c | 49 ++++--- src/nvim/ui.h | 4 +- test/functional/api/extmark_spec.lua | 2 +- test/functional/lua/loop_spec.lua | 4 +- test/functional/lua/ui_event_spec.lua | 155 ++++++++++++++++++---- test/functional/lua/vim_spec.lua | 4 +- test/functional/vimscript/eval_spec.lua | 68 ---------- 17 files changed, 215 insertions(+), 141 deletions(-) diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 6ae1a1a3b8..e6f7e62e02 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -1083,6 +1083,10 @@ vim.ui_attach({ns}, {options}, {callback}) *vim.ui_attach()* |ui-popupmenu| and the sections below for event format for respective events. + Callbacks for `msg_show` events are executed in |api-fast| context. + + Excessive errors inside the callback will result in forced detachment. + WARNING: This api is considered experimental. Usability will vary for different screen elements. In particular `ext_messages` behavior is subject to further changes and usability improvements. This is expected to diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 41b9d623a2..6056c80265 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -72,7 +72,8 @@ EDITOR EVENTS -• TODO +• |vim.ui_attach()| callbacks for |ui-messages| `msg_show` events are executed in + |api-fast| context. LSP diff --git a/runtime/lua/vim/_meta/builtin.lua b/runtime/lua/vim/_meta/builtin.lua index 234c75d38f..dd6ef69eb8 100644 --- a/runtime/lua/vim/_meta/builtin.lua +++ b/runtime/lua/vim/_meta/builtin.lua @@ -233,6 +233,10 @@ function vim.wait(time, callback, interval, fast_only) end --- {callback} receives event name plus additional parameters. See |ui-popupmenu| --- and the sections below for event format for respective events. --- +--- Callbacks for `msg_show` events are executed in |api-fast| context. +--- +--- Excessive errors inside the callback will result in forced detachment. +--- --- WARNING: This api is considered experimental. Usability will vary for --- different screen elements. In particular `ext_messages` behavior is subject --- to further changes and usability improvements. This is expected to be diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index 865e84ab91..0ed208fc1a 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -159,7 +159,7 @@ void wildmenu_hide(void) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void msg_show(String kind, Array content, Boolean replace_last) - FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; + FUNC_API_SINCE(6) FUNC_API_FAST FUNC_API_REMOTE_ONLY; void msg_clear(void) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; void msg_showcmd(Array content) diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 998f911392..e6ec88c5e8 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -594,10 +594,12 @@ ArrayOf(String) nvim_get_runtime_file(String name, Boolean all, Arena *arena, Er kvi_init(cookie.rv); int flags = DIP_DIRFILE | (all ? DIP_ALL : 0); + TryState tstate; + + try_enter(&tstate); + do_in_runtimepath((name.size ? name.data : ""), flags, find_runtime_cb, &cookie); + vim_ignored = try_leave(&tstate, err); - TRY_WRAP(err, { - do_in_runtimepath((name.size ? name.data : ""), flags, find_runtime_cb, &cookie); - }); return arena_take_arraybuilder(arena, &cookie.rv); } diff --git a/src/nvim/errors.h b/src/nvim/errors.h index 7897a71489..df94945a3d 100644 --- a/src/nvim/errors.h +++ b/src/nvim/errors.h @@ -151,7 +151,7 @@ EXTERN const char e_auabort[] INIT(= N_("E855: Autocommands caused command to ab EXTERN const char e_api_error[] INIT(= N_("E5555: API call: %s")); -EXTERN const char e_luv_api_disabled[] INIT(= N_("E5560: %s must not be called in a lua loop callback")); +EXTERN const char e_fast_api_disabled[] INIT(= N_("E5560: %s must not be called in a fast event context")); EXTERN const char e_floatonly[] INIT(= N_("E5601: Cannot close window, only floating window would remain")); EXTERN const char e_floatexchange[] INIT(= N_("E5602: Cannot exchange or rotate float")); diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 1fb7666167..9acbc05fdf 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -8616,7 +8616,7 @@ bool eval_has_provider(const char *feat, bool throw_if_fast) } if (throw_if_fast && !nlua_is_deferred_safe()) { - semsg(e_luv_api_disabled, "Vimscript function"); + semsg(e_fast_api_disabled, "Vimscript function"); return false; } diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index 9e0aa407a1..a78f746fee 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -750,7 +750,7 @@ local function process_function(fn) write_shifted_output( [[ if (!nlua_is_deferred_safe()) { - return luaL_error(lstate, e_luv_api_disabled, "%s"); + return luaL_error(lstate, e_fast_api_disabled, "%s"); } ]], fn.name diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua index 3e8ae19c9a..30a83330eb 100644 --- a/src/nvim/generators/gen_api_ui_events.lua +++ b/src/nvim/generators/gen_api_ui_events.lua @@ -136,8 +136,8 @@ for i = 1, #events do call_output:write(' }\n') call_output:write(' entered = true;\n') write_arglist(call_output, ev) - call_output:write(' ui_call_event("' .. ev.name .. '", ' .. args .. ');\n') - call_output:write(' entered = false;\n') + call_output:write((' ui_call_event("%s", %s, %s)'):format(ev.name, tostring(ev.fast), args)) + call_output:write(';\n entered = false;\n') elseif ev.compositor_impl then call_output:write(' ui_comp_' .. ev.name) write_signature(call_output, ev, '', true) diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index e4da274204..15f70fb725 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -187,7 +187,7 @@ static void nlua_luv_error_event(void **argv) msg_ext_set_kind("lua_error"); switch (type) { case kCallback: - semsg_multiline("Error executing luv callback:\n%s", error); + semsg_multiline("Error executing callback:\n%s", error); break; case kThread: semsg_multiline("Error in luv thread:\n%s", error); @@ -201,13 +201,13 @@ static void nlua_luv_error_event(void **argv) xfree(error); } -static int nlua_luv_cfpcall(lua_State *lstate, int nargs, int nresult, int flags) +/// Execute callback in "fast" context. Used for luv and some vim.ui_event +/// callbacks where using the API directly is not safe. +static int nlua_fast_cfpcall(lua_State *lstate, int nargs, int nresult, int flags) FUNC_ATTR_NONNULL_ALL { int retval; - // luv callbacks might be executed at any os_breakcheck/line_breakcheck - // call, so using the API directly here is not safe. in_fast_callback++; int top = lua_gettop(lstate); @@ -366,11 +366,13 @@ static int nlua_init_argv(lua_State *const L, char **argv, int argc, int lua_arg static void nlua_schedule_event(void **argv) { LuaRef cb = (LuaRef)(ptrdiff_t)argv[0]; + uint32_t ns_id = (uint32_t)(ptrdiff_t)argv[1]; lua_State *const lstate = global_lstate; nlua_pushref(lstate, cb); nlua_unref_global(lstate, cb); if (nlua_pcall(lstate, 0, 0)) { nlua_error(lstate, _("Error executing vim.schedule lua callback: %.*s")); + ui_remove_cb(ns_id, true); } } @@ -392,8 +394,9 @@ static int nlua_schedule(lua_State *const lstate) } LuaRef cb = nlua_ref_global(lstate, 1); - - multiqueue_put(main_loop.events, nlua_schedule_event, (void *)(ptrdiff_t)cb); + // Pass along UI event handler to disable on error. + multiqueue_put(main_loop.events, nlua_schedule_event, (void *)(ptrdiff_t)cb, + (void *)(ptrdiff_t)ui_event_ns_id); return 0; } @@ -425,7 +428,7 @@ static int nlua_wait(lua_State *lstate) FUNC_ATTR_NONNULL_ALL { if (in_fast_callback) { - return luaL_error(lstate, e_luv_api_disabled, "vim.wait"); + return luaL_error(lstate, e_fast_api_disabled, "vim.wait"); } intptr_t timeout = luaL_checkinteger(lstate, 1); @@ -598,7 +601,7 @@ static void nlua_common_vim_init(lua_State *lstate, bool is_thread, bool is_stan luv_set_cthread(lstate, nlua_luv_thread_cfcpcall); } else { luv_set_loop(lstate, &main_loop.uv); - luv_set_callback(lstate, nlua_luv_cfpcall); + luv_set_callback(lstate, nlua_fast_cfpcall); } luaopen_luv(lstate); lua_pushvalue(lstate, -1); @@ -724,7 +727,7 @@ static int nlua_ui_detach(lua_State *lstate) return luaL_error(lstate, "invalid ns_id"); } - ui_remove_cb(ns_id); + ui_remove_cb(ns_id, false); return 0; } @@ -1174,7 +1177,7 @@ int nlua_call(lua_State *lstate) size_t name_len; const char *name = luaL_checklstring(lstate, 1, &name_len); if (!nlua_is_deferred_safe() && !viml_func_is_fast(name)) { - return luaL_error(lstate, e_luv_api_disabled, "Vimscript function"); + return luaL_error(lstate, e_fast_api_disabled, "Vimscript function"); } int nargs = lua_gettop(lstate) - 1; @@ -1231,7 +1234,7 @@ free_vim_args: static int nlua_rpcrequest(lua_State *lstate) { if (!nlua_is_deferred_safe()) { - return luaL_error(lstate, e_luv_api_disabled, "rpcrequest"); + return luaL_error(lstate, e_fast_api_disabled, "rpcrequest"); } return nlua_rpc(lstate, true); } @@ -1593,6 +1596,12 @@ bool nlua_ref_is_function(LuaRef ref) /// @return Return value of function, as per mode Object nlua_call_ref(LuaRef ref, const char *name, Array args, LuaRetMode mode, Arena *arena, Error *err) +{ + return nlua_call_ref_ctx(false, ref, name, args, mode, arena, err); +} + +Object nlua_call_ref_ctx(bool fast, LuaRef ref, const char *name, Array args, LuaRetMode mode, + Arena *arena, Error *err) { lua_State *const lstate = global_lstate; nlua_pushref(lstate, ref); @@ -1605,7 +1614,13 @@ Object nlua_call_ref(LuaRef ref, const char *name, Array args, LuaRetMode mode, nlua_push_Object(lstate, &args.items[i], 0); } - if (nlua_pcall(lstate, nargs, 1)) { + if (fast) { + if (nlua_fast_cfpcall(lstate, nargs, 1, -1) < 0) { + // error is already scheduled, set anyways to convey failure. + api_set_error(err, kErrorTypeException, "fast context failure"); + } + return NIL; + } else if (nlua_pcall(lstate, nargs, 1)) { // if err is passed, the caller will deal with the error. if (err) { size_t len; diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 560f76d0bd..d50747e63f 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -44,6 +44,7 @@ typedef struct { LuaRef cb; + uint8_t errors; bool ext_widgets[kUIGlobalCount]; } UIEventCallback; @@ -212,21 +213,20 @@ void ui_refresh(void) cursor_row = cursor_col = 0; pending_cursor_update = true; + bool had_message = ui_ext[kUIMessages]; for (UIExtension i = 0; (int)i < kUIExtCount; i++) { + ui_ext[i] = ext_widgets[i] | ui_cb_ext[i]; if (i < kUIGlobalCount) { - ext_widgets[i] |= ui_cb_ext[i]; + ui_call_option_set(cstr_as_string(ui_ext_names[i]), BOOLEAN_OBJ(ui_ext[i])); } - // Set 'cmdheight' to zero for all tabpages when ext_messages becomes active. - if (i == kUIMessages && !ui_ext[i] && ext_widgets[i]) { - set_option_value(kOptCmdheight, NUMBER_OPTVAL(0), 0); - command_height(); - FOR_ALL_TABS(tp) { - tp->tp_ch_used = 0; - } - } - ui_ext[i] = ext_widgets[i]; - if (i < kUIGlobalCount) { - ui_call_option_set(cstr_as_string(ui_ext_names[i]), BOOLEAN_OBJ(ext_widgets[i])); + } + + // Reset 'cmdheight' for all tabpages when ext_messages toggles. + if (had_message != ui_ext[kUIMessages]) { + set_option_value(kOptCmdheight, NUMBER_OPTVAL(had_message), 0); + command_height(); + FOR_ALL_TABS(tp) { + tp->tp_ch_used = had_message; } } @@ -713,13 +713,15 @@ void ui_grid_resize(handle_T grid_handle, int width, int height, Error *err) } } -void ui_call_event(char *name, Array args) +void ui_call_event(char *name, bool fast, Array args) { - UIEventCallback *event_cb; bool handled = false; - map_foreach_value(&ui_event_cbs, event_cb, { + UIEventCallback *event_cb; + map_foreach(&ui_event_cbs, ui_event_ns_id, event_cb, { Error err = ERROR_INIT; - Object res = nlua_call_ref(event_cb->cb, name, args, kRetNilBool, NULL, &err); + uint32_t ns_id = ui_event_ns_id; + Object res = nlua_call_ref_ctx(fast, event_cb->cb, name, args, kRetNilBool, NULL, &err); + ui_event_ns_id = 0; // TODO(bfredl/luukvbaal): should this be documented or reconsidered? // Why does truthy return from Lua callback mean remote UI should not receive // the event. @@ -728,6 +730,7 @@ void ui_call_event(char *name, Array args) } if (ERROR_SET(&err)) { ELOG("Error executing UI event callback: %s", err.msg); + ui_remove_cb(ns_id, true); } api_clear_error(&err); }) @@ -780,12 +783,16 @@ void ui_add_cb(uint32_t ns_id, LuaRef cb, bool *ext_widgets) ui_refresh(); } -void ui_remove_cb(uint32_t ns_id) +void ui_remove_cb(uint32_t ns_id, bool checkerr) { - if (map_has(uint32_t, &ui_event_cbs, ns_id)) { - UIEventCallback *item = pmap_del(uint32_t)(&ui_event_cbs, ns_id, NULL); + UIEventCallback *item = pmap_get(uint32_t)(&ui_event_cbs, ns_id); + if (item && (!checkerr || ++item->errors > 10)) { + pmap_del(uint32_t)(&ui_event_cbs, ns_id, NULL); free_ui_event_callback(item); + ui_cb_update_ext(); + ui_refresh(); + if (checkerr) { + msg_schedule_semsg("Excessive errors in vim.ui_attach() callback from ns: %d.", ns_id); + } } - ui_cb_update_ext(); - ui_refresh(); } diff --git a/src/nvim/ui.h b/src/nvim/ui.h index 8718c7b506..4aeb8ffda9 100644 --- a/src/nvim/ui.h +++ b/src/nvim/ui.h @@ -18,4 +18,6 @@ EXTERN Array noargs INIT(= ARRAY_DICT_INIT); #endif // uncrustify:on -EXTERN MultiQueue *resize_events; +// vim.ui_attach() namespace of currently executed callback. +EXTERN uint32_t ui_event_ns_id INIT( = 0); +EXTERN MultiQueue *resize_events INIT( = NULL); diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua index f8d6fca74c..43be0c0e43 100644 --- a/test/functional/api/extmark_spec.lua +++ b/test/functional/api/extmark_spec.lua @@ -757,7 +757,7 @@ describe('API/extmarks', function() { Ïf (!nlua_is_deferred_safe(lstate)) { // strictly not allowed - Яetörn luaL_error(lstate, e_luv_api_disabled, "rpcrequest"); + Яetörn luaL_error(lstate, e_fast_api_disabled, "rpcrequest"); } return nlua_rpc(lstate, true); }]]) diff --git a/test/functional/lua/loop_spec.lua b/test/functional/lua/loop_spec.lua index 0c72843fad..52a8fec0bf 100644 --- a/test/functional/lua/loop_spec.lua +++ b/test/functional/lua/loop_spec.lua @@ -87,9 +87,9 @@ describe('vim.uv', function() screen:expect([[ | {2: }| - {3:Error executing luv callback:} | + {3:Error executing callback:} | {3:[string ""]:5: E5560: nvim_set_var must not }| - {3:be called in a lua loop callback} | + {3:be called in a fast event context} | {3:stack traceback:} | {3: [C]: in function 'nvim_set_var'} | {3: [string ""]:5: in function <[string }| diff --git a/test/functional/lua/ui_event_spec.lua b/test/functional/lua/ui_event_spec.lua index 714332bc2f..f78fced14e 100644 --- a/test/functional/lua/ui_event_spec.lua +++ b/test/functional/lua/ui_event_spec.lua @@ -32,15 +32,6 @@ describe('vim.ui_attach', function() ]] screen = Screen.new(40, 5) - screen:set_default_attr_ids({ - [1] = { bold = true, foreground = Screen.colors.Blue1 }, - [2] = { bold = true }, - [3] = { background = Screen.colors.Grey }, - [4] = { background = Screen.colors.LightMagenta }, - [5] = { reverse = true }, - [6] = { reverse = true, bold = true }, - [7] = { background = Screen.colors.Yellow1 }, - }) end) local function expect_events(expected) @@ -55,7 +46,7 @@ describe('vim.ui_attach', function() grid = [[ fo^ | {1:~ }|*3 - {2:-- INSERT --} | + {5:-- INSERT --} | ]], } @@ -64,7 +55,7 @@ describe('vim.ui_attach', function() grid = [[ food^ | {1:~ }|*3 - {2:-- INSERT --} | + {5:-- INSERT --} | ]], } expect_events { @@ -83,7 +74,7 @@ describe('vim.ui_attach', function() grid = [[ foobar^ | {1:~ }|*3 - {2:-- INSERT --} | + {5:-- INSERT --} | ]], } expect_events { @@ -105,10 +96,10 @@ describe('vim.ui_attach', function() screen:expect { grid = [[ food^ | - {3:food }{1: }| + {12:food }{1: }| {4:foobar }{1: }| {4:foo }{1: }| - {2:-- INSERT --} | + {5:-- INSERT --} | ]], } expect_events {} @@ -180,12 +171,17 @@ describe('vim.ui_attach', function() exec_lua([[ _G.cmdline = 0 vim.ui_attach(ns, { ext_messages = true }, function(ev) - vim.cmd.redraw() + if ev == 'msg_show' then + vim.schedule(function() vim.cmd.redraw() end) + else + vim.cmd.redraw() + end _G.cmdline = _G.cmdline + (ev == 'cmdline_show' and 1 or 0) end )]]) feed(':') - eq(1, exec_lua('return _G.cmdline')) + n.assert_alive() + eq(2, exec_lua('return _G.cmdline')) n.assert_alive() feed('versionv') n.assert_alive() @@ -211,9 +207,9 @@ describe('vim.ui_attach', function() screen:expect({ grid = [[ cmdline | - {5:cmdline [+] }| + {2:cmdline [+] }| fooba^r | - {6:[No Name] [+] }| + {3:[No Name] [+] }| | ]], }) @@ -222,9 +218,9 @@ describe('vim.ui_attach', function() screen:expect({ grid = [[ foo | - {5:cmdline [+] }| - {5:foo}ba^r | - {6:[No Name] [+] }| + {2:cmdline [+] }| + {2:foo}ba^r | + {3:[No Name] [+] }| | ]], }) @@ -233,13 +229,124 @@ describe('vim.ui_attach', function() screen:expect({ grid = [[ %s/bar/baz | - {5:cmdline [+] }| - foo{7:ba^z} | - {6:[No Name] [+] }| + {2:cmdline [+] }| + foo{10:ba^z} | + {3:[No Name] [+] }| | ]], }) end) + + it('aborts :function on error with ext_messages', function() + exec_lua([[ + vim.ui_attach(ns, { ext_messages = true }, function(event, _, content) + if event == "msg_show" then + -- "fast-api" does not prevent aborting :function + vim.api.nvim_get_runtime_file("foo", false) + -- non-"fast-api" is not allowed in msg_show callback and should be scheduled + local _, err = pcall(vim.api.nvim_buf_set_lines, 0, -2, -1, false, { content[1][2] }) + vim.schedule(function() + vim.api.nvim_buf_set_lines(0, -2, -1, false, { content[1][2], err }) + end) + end + end) + ]]) + feed(':func Foo()barendf:func Foo()') + screen:expect({ + grid = [[ + ^E122: Function Foo already exists, add !| + to replace it | + E5560: nvim_buf_set_lines must not be ca| + lled in a fast event context | + {1:~ }| + ]], + messages = { + { + content = { { 'E122: Function Foo already exists, add ! to replace it', 9, 7 } }, + kind = 'emsg', + }, + }, + }) + end) + + it('detaches after excessive errors', function() + screen:add_extra_attr_ids({ [100] = { bold = true, foreground = Screen.colors.SeaGreen } }) + exec_lua([[ + vim.ui_attach(vim.api.nvim_create_namespace(''), { ext_messages = true }, function() + vim.api.nvim_buf_set_lines(0, -2, -1, false, { err[1] }) + end) + ]]) + screen:expect({ + grid = [[ + ^ | + {1:~ }|*4 + ]], + }) + feed('ifoo') + screen:expect({ + grid = [[ + foo^ | + {1:~ }|*4 + ]], + showmode = { { '-- INSERT --', 5, 12 } }, + }) + feed(':1mes clear:mes') + screen:expect({ + grid = [[ + foo | + {3: }| + {9:Excessive errors in vim.ui_attach() call}| + {9:back from ns: 2.} | + {100:Press ENTER or type command to continue}^ | + ]], + }) + feed('') + -- Also when scheduled + exec_lua([[ + vim.ui_attach(vim.api.nvim_create_namespace(''), { ext_messages = true }, function() + vim.schedule(function() vim.api.nvim_buf_set_lines(0, -2, -1, false, { err[1] }) end) + end) + ]]) + screen:expect({ + any = 'fo^o', + messages = { + { + content = { + { + 'Error executing vim.schedule lua callback: [string ""]:2: attempt to index global \'err\' (a nil value)\nstack traceback:\n\t[string ""]:2: in function <[string ""]:2>', + 9, + 7, + }, + }, + kind = 'lua_error', + }, + { + content = { + { + 'Error executing vim.schedule lua callback: [string ""]:2: attempt to index global \'err\' (a nil value)\nstack traceback:\n\t[string ""]:2: in function <[string ""]:2>', + 9, + 7, + }, + }, + kind = 'lua_error', + }, + { + content = { { 'Press ENTER or type command to continue', 100, 19 } }, + kind = 'return_prompt', + }, + }, + }) + feed(':1mes clear:mes') + screen:expect({ + grid = [[ + foo | + {3: }| + {9:Excessive errors in vim.ui_attach() call}| + {9:back from ns: 3.} | + {100:Press ENTER or type command to continue}^ | + ]], + }) + end) end) describe('vim.ui_attach', function() diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 0578d88e66..3cfbfe167a 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -1414,7 +1414,7 @@ describe('lua stdlib', function() screen:expect { grid = [[ {9:[string ""]:6: E5560: rpcrequest must not be}| - {9: called in a lua loop callback} | + {9: called in a fast event context} | {9:stack traceback:} | {9: [C]: in function 'rpcrequest'} | {9: [string ""]:6: in function <[string }| @@ -3783,7 +3783,7 @@ stack traceback: end) ]]) screen:expect({ - any = pesc('E5560: vim.wait must not be called in a lua loop callback'), + any = pesc('E5560: vim.wait must not be called in a fast event context'), }) feed('') assert_alive() diff --git a/test/functional/vimscript/eval_spec.lua b/test/functional/vimscript/eval_spec.lua index de1a3d7c73..2a4835f7e1 100644 --- a/test/functional/vimscript/eval_spec.lua +++ b/test/functional/vimscript/eval_spec.lua @@ -25,8 +25,6 @@ local command = n.command local write_file = t.write_file local api = n.api local sleep = vim.uv.sleep -local matches = t.matches -local pcall_err = t.pcall_err local assert_alive = n.assert_alive local poke_eventloop = n.poke_eventloop local feed = n.feed @@ -227,72 +225,6 @@ describe('listing functions using :function', function() exec_capture(('function %s'):format(num)) ) end) - - it('does not crash if another function is deleted while listing', function() - local _ = Screen.new(80, 24) - matches( - 'Vim%(function%):E454: Function list was modified$', - pcall_err( - exec_lua, - [=[ - vim.cmd([[ - func Func1() - endfunc - func Func2() - endfunc - func Func3() - endfunc - ]]) - - local ns = vim.api.nvim_create_namespace('test') - - vim.ui_attach(ns, { ext_messages = true }, function(event, _, content) - if event == 'msg_show' and content[1][2] == 'function Func1()' then - vim.cmd('delfunc Func3') - end - end) - - vim.cmd('function') - - vim.ui_detach(ns) - ]=] - ) - ) - assert_alive() - end) - - it('does not crash if the same function is deleted while listing', function() - local _ = Screen.new(80, 24) - matches( - 'Vim%(function%):E454: Function list was modified$', - pcall_err( - exec_lua, - [=[ - vim.cmd([[ - func Func1() - endfunc - func Func2() - endfunc - func Func3() - endfunc - ]]) - - local ns = vim.api.nvim_create_namespace('test') - - vim.ui_attach(ns, { ext_messages = true }, function(event, _, content) - if event == 'msg_show' and content[1][2] == 'function Func1()' then - vim.cmd('delfunc Func2') - end - end) - - vim.cmd('function') - - vim.ui_detach(ns) - ]=] - ) - ) - assert_alive() - end) end) it('no double-free in garbage collection #16287', function()