// This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include #include #include #include "nvim/misc1.h" #include "nvim/getchar.h" #include "nvim/garray.h" #include "nvim/func_attr.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/api/vim.h" #include "nvim/vim.h" #include "nvim/ex_getln.h" #include "nvim/ex_cmds2.h" #include "nvim/message.h" #include "nvim/memline.h" #include "nvim/buffer_defs.h" #include "nvim/macros.h" #include "nvim/screen.h" #include "nvim/cursor.h" #include "nvim/undo.h" #include "nvim/ascii.h" #include "nvim/lua/executor.h" #include "nvim/lua/converter.h" typedef struct { Error err; String lua_err_str; } LuaError; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "lua/vim_module.generated.h" # include "lua/executor.c.generated.h" #endif /// Name of the run code for use in messages #define NLUA_EVAL_NAME "" /// Call C function which does not expect any arguments /// /// @param function Called function /// @param numret Number of returned arguments #define NLUA_CALL_C_FUNCTION_0(lstate, function, numret) \ do { \ lua_pushcfunction(lstate, &function); \ lua_call(lstate, 0, numret); \ } while (0) /// Call C function which expects one argument /// /// @param function Called function /// @param numret Number of returned arguments /// @param a… Supplied argument (should be a void* pointer) #define NLUA_CALL_C_FUNCTION_1(lstate, function, numret, a1) \ do { \ lua_pushcfunction(lstate, &function); \ lua_pushlightuserdata(lstate, a1); \ lua_call(lstate, 1, numret); \ } while (0) /// Call C function which expects two arguments /// /// @param function Called function /// @param numret Number of returned arguments /// @param a… Supplied argument (should be a void* pointer) #define NLUA_CALL_C_FUNCTION_2(lstate, function, numret, a1, a2) \ do { \ lua_pushcfunction(lstate, &function); \ lua_pushlightuserdata(lstate, a1); \ lua_pushlightuserdata(lstate, a2); \ lua_call(lstate, 2, numret); \ } while (0) /// Call C function which expects three arguments /// /// @param function Called function /// @param numret Number of returned arguments /// @param a… Supplied argument (should be a void* pointer) #define NLUA_CALL_C_FUNCTION_3(lstate, function, numret, a1, a2, a3) \ do { \ lua_pushcfunction(lstate, &function); \ lua_pushlightuserdata(lstate, a1); \ lua_pushlightuserdata(lstate, a2); \ lua_pushlightuserdata(lstate, a3); \ lua_call(lstate, 3, numret); \ } while (0) /// Call C function which expects five arguments /// /// @param function Called function /// @param numret Number of returned arguments /// @param a… Supplied argument (should be a void* pointer) #define NLUA_CALL_C_FUNCTION_4(lstate, function, numret, a1, a2, a3, a4) \ do { \ lua_pushcfunction(lstate, &function); \ lua_pushlightuserdata(lstate, a1); \ lua_pushlightuserdata(lstate, a2); \ lua_pushlightuserdata(lstate, a3); \ lua_pushlightuserdata(lstate, a4); \ lua_call(lstate, 4, numret); \ } while (0) /// Convert lua error into a Vim error message /// /// @param lstate Lua interpreter state. /// @param[in] msg Message base, must contain one `%s`. static void nlua_error(lua_State *const lstate, const char *const msg) FUNC_ATTR_NONNULL_ALL { size_t len; const char *const str = lua_tolstring(lstate, -1, &len); emsgf(msg, (int)len, str); lua_pop(lstate, 1); } /// Compare two strings, ignoring case /// /// Expects two values on the stack: compared strings. Returns one of the /// following numbers: 0, -1 or 1. /// /// Does no error handling: never call it with non-string or with some arguments /// omitted. static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { size_t s1_len; size_t s2_len; const char *s1 = luaL_checklstring(lstate, 1, &s1_len); const char *s2 = luaL_checklstring(lstate, 2, &s2_len); char *nul1; char *nul2; int ret = 0; assert(s1[s1_len] == NUL); assert(s2[s2_len] == NUL); do { nul1 = memchr(s1, NUL, s1_len); nul2 = memchr(s2, NUL, s2_len); ret = STRICMP(s1, s2); if (ret == 0) { // Compare "a\0" greater then "a". if ((nul1 == NULL) != (nul2 == NULL)) { ret = ((nul1 != NULL) - (nul2 != NULL)); break; } if (nul1 != NULL) { assert(nul2 != NULL); // Can't shift both strings by the same amount of bytes: lowercase // letter may have different byte-length than uppercase. s1_len -= (size_t)(nul1 - s1) + 1; s2_len -= (size_t)(nul2 - s2) + 1; s1 = nul1 + 1; s2 = nul2 + 1; } else { break; } } else { break; } } while (true); lua_pop(lstate, 2); lua_pushnumber(lstate, (lua_Number)((ret > 0) - (ret < 0))); return 1; } /// Evaluate lua string /// /// Expects two values on the stack: string to evaluate, pointer to the /// location where result is saved. Always returns nothing (from the lua point /// of view). static int nlua_exec_lua_string(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { const String *const str = (const String *)lua_touserdata(lstate, 1); typval_T *const ret_tv = (typval_T *)lua_touserdata(lstate, 2); lua_pop(lstate, 2); if (luaL_loadbuffer(lstate, str->data, str->size, NLUA_EVAL_NAME)) { nlua_error(lstate, _("E5104: Error while creating lua chunk: %.*s")); return 0; } if (lua_pcall(lstate, 0, 1, 0)) { nlua_error(lstate, _("E5105: Error while calling lua chunk: %.*s")); return 0; } if (!nlua_pop_typval(lstate, ret_tv)) { return 0; } return 0; } /// Evaluate lua string for each line in range /// /// Expects two values on the stack: string to evaluate and pointer to integer /// array with line range. Always returns nothing (from the lua point of view). static int nlua_exec_luado_string(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { const String *const str = (const String *)lua_touserdata(lstate, 1); const linenr_T *const range = (const linenr_T *)lua_touserdata(lstate, 2); lua_pop(lstate, 2); #define DOSTART "return function(line, linenr) " #define DOEND " end" const size_t lcmd_len = (str->size + (sizeof(DOSTART) - 1) + (sizeof(DOEND) - 1)); char *lcmd; if (lcmd_len < IOSIZE) { lcmd = (char *)IObuff; } else { lcmd = xmalloc(lcmd_len + 1); } memcpy(lcmd, DOSTART, sizeof(DOSTART) - 1); memcpy(lcmd + sizeof(DOSTART) - 1, str->data, str->size); memcpy(lcmd + sizeof(DOSTART) - 1 + str->size, DOEND, sizeof(DOEND) - 1); #undef DOSTART #undef DOEND if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) { nlua_error(lstate, _("E5109: Error while creating lua chunk: %.*s")); if (lcmd_len >= IOSIZE) { xfree(lcmd); } return 0; } if (lcmd_len >= IOSIZE) { xfree(lcmd); } if (lua_pcall(lstate, 0, 1, 0)) { nlua_error(lstate, _("E5110: Error while creating lua function: %.*s")); return 0; } for (linenr_T l = range[0]; l <= range[1]; l++) { if (l > curbuf->b_ml.ml_line_count) { break; } lua_pushvalue(lstate, -1); lua_pushstring(lstate, (const char *)ml_get_buf(curbuf, l, false)); lua_pushnumber(lstate, (lua_Number)l); if (lua_pcall(lstate, 2, 1, 0)) { nlua_error(lstate, _("E5111: Error while calling lua function: %.*s")); break; } if (lua_isstring(lstate, -1)) { size_t new_line_len; const char *const new_line = lua_tolstring(lstate, -1, &new_line_len); char *const new_line_transformed = xmemdupz(new_line, new_line_len); for (size_t i = 0; i < new_line_len; i++) { if (new_line_transformed[i] == NUL) { new_line_transformed[i] = '\n'; } } ml_replace(l, (char_u *)new_line_transformed, false); changed_bytes(l, 0); } lua_pop(lstate, 1); } lua_pop(lstate, 1); check_cursor(); update_screen(NOT_VALID); return 0; } /// Evaluate lua file /// /// Expects one value on the stack: file to evaluate. Always returns nothing /// (from the lua point of view). static int nlua_exec_lua_file(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { const char *const filename = (const char *)lua_touserdata(lstate, 1); lua_pop(lstate, 1); if (luaL_loadfile(lstate, filename)) { nlua_error(lstate, _("E5112: Error while creating lua chunk: %.*s")); return 0; } if (lua_pcall(lstate, 0, 0, 0)) { nlua_error(lstate, _("E5113: Error while calling lua chunk: %.*s")); return 0; } return 0; } /// Initialize lua interpreter state /// /// Called by lua interpreter itself to initialize state. static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { // print lua_pushcfunction(lstate, &nlua_print); lua_setglobal(lstate, "print"); // debug.debug lua_getglobal(lstate, "debug"); lua_pushcfunction(lstate, &nlua_debug); lua_setfield(lstate, -2, "debug"); lua_pop(lstate, 1); // vim if (luaL_dostring(lstate, (char *)&vim_module[0])) { nlua_error(lstate, _("E5106: Error while creating vim module: %.*s")); return 1; } // vim.api nlua_add_api_functions(lstate); // vim.types, vim.type_idx, vim.val_idx nlua_init_types(lstate); // stricmp lua_pushcfunction(lstate, &nlua_stricmp); lua_setfield(lstate, -2, "stricmp"); lua_setglobal(lstate, "vim"); return 0; } /// Initialize lua interpreter /// /// Crashes Nvim if initialization fails. Should be called once per lua /// interpreter instance. /// /// @return New lua interpreter instance. static lua_State *nlua_init(void) FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT { lua_State *lstate = luaL_newstate(); if (lstate == NULL) { EMSG(_("E970: Failed to initialize lua interpreter")); preserve_exit(); } luaL_openlibs(lstate); NLUA_CALL_C_FUNCTION_0(lstate, nlua_state_init, 0); return lstate; } /// Enter lua interpreter /// /// Calls nlua_init() if needed. Is responsible for pre-lua call initalization /// like updating `package.[c]path` with directories derived from &runtimepath. /// /// @return Interpreter instance to use. Will either be initialized now or /// taken from previous initialization. static lua_State *nlua_enter(void) FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT { static lua_State *global_lstate = NULL; if (global_lstate == NULL) { global_lstate = nlua_init(); } lua_State *const lstate = global_lstate; // Last used p_rtp value. Must not be dereferenced because value pointed to // may already be freed. Used to check whether &runtimepath option value // changed. static const void *last_p_rtp = NULL; if (last_p_rtp != (const void *)p_rtp) { // stack: (empty) lua_getglobal(lstate, "vim"); // stack: vim lua_getfield(lstate, -1, "_update_package_paths"); // stack: vim, vim._update_package_paths if (lua_pcall(lstate, 0, 0, 0)) { // stack: vim, error nlua_error(lstate, _("E5117: Error while updating package paths: %.*s")); // stack: vim } // stack: vim lua_pop(lstate, 1); // stack: (empty) last_p_rtp = (const void *)p_rtp; } return lstate; } /// Execute lua string /// /// @param[in] str String to execute. /// @param[out] ret_tv Location where result will be saved. /// /// @return Result of the execution. void executor_exec_lua(const String str, typval_T *const ret_tv) FUNC_ATTR_NONNULL_ALL { NLUA_CALL_C_FUNCTION_2(nlua_enter(), nlua_exec_lua_string, 0, (void *)&str, ret_tv); } /// Evaluate lua string /// /// Used for luaeval(). Expects three values on the stack: /// /// 1. String to evaluate. /// 2. _A value. /// 3. Pointer to location where result is saved. /// /// @param[in,out] lstate Lua interpreter state. static int nlua_eval_lua_string(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { const String *const str = (const String *)lua_touserdata(lstate, 1); typval_T *const arg = (typval_T *)lua_touserdata(lstate, 2); typval_T *const ret_tv = (typval_T *)lua_touserdata(lstate, 3); lua_pop(lstate, 3); garray_T str_ga; ga_init(&str_ga, 1, 80); #define EVALHEADER "local _A=select(1,...) return (" const size_t lcmd_len = sizeof(EVALHEADER) - 1 + str->size + 1; char *lcmd; if (lcmd_len < IOSIZE) { lcmd = (char *)IObuff; } else { lcmd = xmalloc(lcmd_len); } memcpy(lcmd, EVALHEADER, sizeof(EVALHEADER) - 1); memcpy(lcmd + sizeof(EVALHEADER) - 1, str->data, str->size); lcmd[lcmd_len - 1] = ')'; #undef EVALHEADER if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) { nlua_error(lstate, _("E5107: Error while creating lua chunk for luaeval(): %.*s")); if (lcmd != (char *)IObuff) { xfree(lcmd); } return 0; } if (lcmd != (char *)IObuff) { xfree(lcmd); } if (arg == NULL || arg->v_type == VAR_UNKNOWN) { lua_pushnil(lstate); } else { nlua_push_typval(lstate, arg); } if (lua_pcall(lstate, 1, 1, 0)) { nlua_error(lstate, _("E5108: Error while calling lua chunk for luaeval(): %.*s")); return 0; } if (!nlua_pop_typval(lstate, ret_tv)) { return 0; } return 0; } /// Evaluate lua string /// /// Expects four values on the stack: string to evaluate, pointer to args array, /// and locations where result and error are saved, respectively. Always /// returns nothing (from the lua point of view). static int nlua_exec_lua_string_api(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { const String *str = (const String *)lua_touserdata(lstate, 1); const Array *args = (const Array *)lua_touserdata(lstate, 2); Object *retval = (Object *)lua_touserdata(lstate, 3); Error *err = (Error *)lua_touserdata(lstate, 4); lua_pop(lstate, 4); if (luaL_loadbuffer(lstate, str->data, str->size, "")) { size_t len; const char *str = lua_tolstring(lstate, -1, &len); api_set_error(err, kErrorTypeValidation, "Error loading lua: %.*s", (int)len, str); return 0; } for (size_t i = 0; i < args->size; i++) { nlua_push_Object(lstate, args->items[i]); } if (lua_pcall(lstate, (int)args->size, 1, 0)) { size_t len; const char *str = lua_tolstring(lstate, -1, &len); api_set_error(err, kErrorTypeException, "Error executing lua: %.*s", (int)len, str); return 0; } *retval = nlua_pop_Object(lstate, err); return 0; } /// Print as a Vim message /// /// @param lstate Lua interpreter state. static int nlua_print(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { #define PRINT_ERROR(msg) \ do { \ errmsg = msg; \ errmsg_len = sizeof(msg) - 1; \ goto nlua_print_error; \ } while (0) const int nargs = lua_gettop(lstate); lua_getglobal(lstate, "tostring"); const char *errmsg = NULL; size_t errmsg_len = 0; garray_T msg_ga; ga_init(&msg_ga, 1, 80); int curargidx = 1; for (; curargidx <= nargs; curargidx++) { lua_pushvalue(lstate, -1); // tostring lua_pushvalue(lstate, curargidx); // arg if (lua_pcall(lstate, 1, 1, 0)) { errmsg = lua_tolstring(lstate, -1, &errmsg_len); goto nlua_print_error; } size_t len; const char *const s = lua_tolstring(lstate, -1, &len); if (s == NULL) { PRINT_ERROR( ""); } ga_concat_len(&msg_ga, s, len); if (curargidx < nargs) { ga_append(&msg_ga, ' '); } lua_pop(lstate, 1); } #undef PRINT_ERROR lua_pop(lstate, nargs + 1); ga_append(&msg_ga, NUL); { const size_t len = (size_t)msg_ga.ga_len - 1; char *const str = (char *)msg_ga.ga_data; for (size_t i = 0; i < len;) { const size_t start = i; while (i < len) { switch (str[i]) { case NUL: { str[i] = NL; i++; continue; } case NL: { str[i] = NUL; i++; break; } default: { i++; continue; } } break; } msg((char_u *)str + start); } if (len && str[len - 1] == NUL) { // Last was newline msg((char_u *)""); } } ga_clear(&msg_ga); return 0; nlua_print_error: emsgf(_("E5114: Error while converting print argument #%i: %.*s"), curargidx, errmsg_len, errmsg); ga_clear(&msg_ga); lua_pop(lstate, lua_gettop(lstate)); return 0; } /// debug.debug implementation: interaction with user while debugging /// /// @param lstate Lua interpreter state. int nlua_debug(lua_State *lstate) FUNC_ATTR_NONNULL_ALL { const typval_T input_args[] = { { .v_lock = VAR_FIXED, .v_type = VAR_STRING, .vval.v_string = (char_u *)"lua_debug> ", }, { .v_type = VAR_UNKNOWN, }, }; for (;;) { lua_settop(lstate, 0); typval_T input; get_user_input(input_args, &input, false); msg_putchar('\n'); // Avoid outputting on input line. if (input.v_type != VAR_STRING || input.vval.v_string == NULL || *input.vval.v_string == NUL || STRCMP(input.vval.v_string, "cont") == 0) { tv_clear(&input); return 0; } if (luaL_loadbuffer(lstate, (const char *)input.vval.v_string, STRLEN(input.vval.v_string), "=(debug command)")) { nlua_error(lstate, _("E5115: Error while loading debug string: %.*s")); } tv_clear(&input); if (lua_pcall(lstate, 0, 0, 0)) { nlua_error(lstate, _("E5116: Error while calling debug string: %.*s")); } } return 0; } /// Evaluate lua string /// /// Used for luaeval(). /// /// @param[in] str String to execute. /// @param[in] arg Second argument to `luaeval()`. /// @param[out] ret_tv Location where result will be saved. /// /// @return Result of the execution. void executor_eval_lua(const String str, typval_T *const arg, typval_T *const ret_tv) FUNC_ATTR_NONNULL_ALL { NLUA_CALL_C_FUNCTION_3(nlua_enter(), nlua_eval_lua_string, 0, (void *)&str, arg, ret_tv); } /// Execute lua string /// /// Used for nvim_execute_lua(). /// /// @param[in] str String to execute. /// @param[in] args array of ... args /// @param[out] err Location where error will be saved. /// /// @return Return value of the execution. Object executor_exec_lua_api(const String str, const Array args, Error *err) { Object retval = NIL; NLUA_CALL_C_FUNCTION_4(nlua_enter(), nlua_exec_lua_string_api, 0, (void *)&str, (void *)&args, &retval, err); return retval; } /// Run lua string /// /// Used for :lua. /// /// @param eap VimL command being run. void ex_lua(exarg_T *const eap) FUNC_ATTR_NONNULL_ALL { size_t len; char *const code = script_get(eap, &len); if (eap->skip) { xfree(code); return; } typval_T tv = { .v_type = VAR_UNKNOWN }; executor_exec_lua((String) { .data = code, .size = len }, &tv); tv_clear(&tv); xfree(code); } /// Run lua string for each line in range /// /// Used for :luado. /// /// @param eap VimL command being run. void ex_luado(exarg_T *const eap) FUNC_ATTR_NONNULL_ALL { if (u_save(eap->line1 - 1, eap->line2 + 1) == FAIL) { EMSG(_("cannot save undo information")); return; } const String cmd = { .size = STRLEN(eap->arg), .data = (char *)eap->arg, }; const linenr_T range[] = { eap->line1, eap->line2 }; NLUA_CALL_C_FUNCTION_2(nlua_enter(), nlua_exec_luado_string, 0, (void *)&cmd, (void *)range); } /// Run lua file /// /// Used for :luafile. /// /// @param eap VimL command being run. void ex_luafile(exarg_T *const eap) FUNC_ATTR_NONNULL_ALL { NLUA_CALL_C_FUNCTION_1(nlua_enter(), nlua_exec_lua_file, 0, (void *)eap->arg); }