diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index d5d35c5295..17dea65bdf 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -123,6 +123,34 @@ lua_State *get_global_lstate(void) return global_lstate; } +/// get error on top of stack as a string +/// +/// Might alter the top value on stack in place (but doesn't change stack height) +/// +/// "error" points to memory on the lua stack, use +/// or duplicate the string before using "lstate" again +/// +/// @param[out] len length of error (can be NULL) +static const char *nlua_get_error(lua_State *lstate, size_t *len) +{ + if (luaL_getmetafield(lstate, -1, "__tostring")) { + if (lua_isfunction(lstate, -1) && luaL_callmeta(lstate, -2, "__tostring")) { + // call __tostring, convert the result replace error with it + lua_replace(lstate, -3); + } + // pop __tostring. + lua_pop(lstate, 1); + } + + const char *str = lua_tolstring(lstate, -1, len); + if (str == NULL) { + str = "[?????]"; + *len = 7; + } + + return str; +} + /// Convert lua error into a Vim error message /// /// @param lstate Lua interpreter state. @@ -131,22 +159,7 @@ void nlua_error(lua_State *const lstate, const char *const msg) FUNC_ATTR_NONNULL_ALL { size_t len; - const char *str = NULL; - - if (luaL_getmetafield(lstate, -1, "__tostring")) { - if (lua_isfunction(lstate, -1) && luaL_callmeta(lstate, -2, "__tostring")) { - // call __tostring, convert the result and pop result. - str = lua_tolstring(lstate, -1, &len); - lua_pop(lstate, 1); - } - // pop __tostring. - lua_pop(lstate, 1); - } - - if (!str) { - // defer to lua default conversion, this will render tables as [NULL]. - str = lua_tolstring(lstate, -1, &len); - } + const char *str = nlua_get_error(lstate, &len); if (in_script) { fprintf(stderr, msg, (int)len, str); @@ -216,7 +229,9 @@ static int nlua_luv_cfpcall(lua_State *lstate, int nargs, int nresult, int flags // consider out of memory errors unrecoverable, just like xmalloc() preserve_exit(e_outofmem); } - const char *error = lua_tostring(lstate, -1); + + size_t len; + const char *error = nlua_get_error(lstate, &len); multiqueue_put(main_loop.events, nlua_luv_error_event, xstrdup(error), (void *)(intptr_t)kCallback); diff --git a/test/functional/lua/loop_spec.lua b/test/functional/lua/loop_spec.lua index 71eaf29009..270c4d4b64 100644 --- a/test/functional/lua/loop_spec.lua +++ b/test/functional/lua/loop_spec.lua @@ -136,4 +136,29 @@ describe('vim.uv', function() it("is equal to require('luv')", function() eq(true, exec_lua("return vim.uv == require('luv')")) end) + + it("doesn't crash on async callbacks throwing nil error", function() + local screen = Screen.new(50, 4) + screen:attach() + screen:set_default_attr_ids { + [1] = { bold = true, reverse = true }, + [2] = { background = Screen.colors.Red, foreground = Screen.colors.Grey100 }, + [3] = { foreground = Screen.colors.SeaGreen4, bold = true }, + } + + exec_lua [[ + local idle = vim.uv.new_idle() + idle:start(function() + idle:stop() + error() + end) + ]] + + screen:expect([[ + {1: }| + {2:Error executing luv callback:} | + {2:[?????]} | + {3:Press ENTER or type command to continue}^ | + ]]) + end) end)