mirror of
https://github.com/neovim/neovim.git
synced 2024-12-21 19:55:04 -07:00
Merge pull request #14226 from bfredl/luarefcount
lua: track reference ownership with ASAN when present
This commit is contained in:
commit
804ea22944
@ -222,11 +222,7 @@ Boolean nvim_buf_attach(uint64_t channel_id,
|
||||
return buf_updates_register(buf, channel_id, cb, send_buffer);
|
||||
|
||||
error:
|
||||
// TODO(bfredl): ASAN build should check that the ref table is empty?
|
||||
api_free_luaref(cb.on_lines);
|
||||
api_free_luaref(cb.on_bytes);
|
||||
api_free_luaref(cb.on_changedtick);
|
||||
api_free_luaref(cb.on_detach);
|
||||
buffer_update_callbacks_free(cb);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1708,33 +1708,6 @@ const char *describe_ns(NS ns_id)
|
||||
return "(UNKNOWN PLUGIN)";
|
||||
}
|
||||
|
||||
DecorProvider *get_provider(NS ns_id, bool force)
|
||||
{
|
||||
ssize_t i;
|
||||
for (i = 0; i < (ssize_t)kv_size(decor_providers); i++) {
|
||||
DecorProvider *item = &kv_A(decor_providers, i);
|
||||
if (item->ns_id == ns_id) {
|
||||
return item;
|
||||
} else if (item->ns_id > ns_id) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!force) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (ssize_t j = (ssize_t)kv_size(decor_providers)-1; j >= i; j++) {
|
||||
// allocates if needed:
|
||||
(void)kv_a(decor_providers, (size_t)j+1);
|
||||
kv_A(decor_providers, (size_t)j+1) = kv_A(decor_providers, j);
|
||||
}
|
||||
DecorProvider *item = &kv_a(decor_providers, (size_t)i);
|
||||
*item = DECORATION_PROVIDER_INIT(ns_id);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
static bool parse_float_anchor(String anchor, FloatAnchor *out)
|
||||
{
|
||||
if (anchor.size == 0) {
|
||||
|
@ -2708,6 +2708,7 @@ Dictionary nvim__stats(void)
|
||||
Dictionary rv = ARRAY_DICT_INIT;
|
||||
PUT(rv, "fsync", INTEGER_OBJ(g_stats.fsync));
|
||||
PUT(rv, "redraw", INTEGER_OBJ(g_stats.redraw));
|
||||
PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_refcount));
|
||||
return rv;
|
||||
}
|
||||
|
||||
@ -2880,19 +2881,6 @@ void nvim__screenshot(String path)
|
||||
ui_call_screenshot(path);
|
||||
}
|
||||
|
||||
static void clear_provider(DecorProvider *p)
|
||||
{
|
||||
if (p == NULL) {
|
||||
return;
|
||||
}
|
||||
NLUA_CLEAR_REF(p->redraw_start);
|
||||
NLUA_CLEAR_REF(p->redraw_buf);
|
||||
NLUA_CLEAR_REF(p->redraw_win);
|
||||
NLUA_CLEAR_REF(p->redraw_line);
|
||||
NLUA_CLEAR_REF(p->redraw_end);
|
||||
p->active = false;
|
||||
}
|
||||
|
||||
/// Set or change decoration provider for a namespace
|
||||
///
|
||||
/// This is a very general purpose interface for having lua callbacks
|
||||
@ -2937,8 +2925,8 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts,
|
||||
Error *err)
|
||||
FUNC_API_SINCE(7) FUNC_API_LUA_ONLY
|
||||
{
|
||||
DecorProvider *p = get_provider((NS)ns_id, true);
|
||||
clear_provider(p);
|
||||
DecorProvider *p = get_decor_provider((NS)ns_id, true);
|
||||
decor_provider_clear(p);
|
||||
|
||||
// regardless of what happens, it seems good idea to redraw
|
||||
redraw_all_later(NOT_VALID); // TODO(bfredl): too soon?
|
||||
@ -2981,5 +2969,5 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts,
|
||||
p->active = true;
|
||||
return;
|
||||
error:
|
||||
clear_provider(p);
|
||||
decor_provider_clear(p);
|
||||
}
|
||||
|
@ -176,7 +176,7 @@ void buf_updates_unload(buf_T *buf, bool can_reload)
|
||||
if (keep) {
|
||||
kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i);
|
||||
} else {
|
||||
free_update_callbacks(cb);
|
||||
buffer_update_callbacks_free(cb);
|
||||
}
|
||||
}
|
||||
kv_size(buf->update_callbacks) = j;
|
||||
@ -290,7 +290,7 @@ void buf_updates_send_changes(buf_T *buf,
|
||||
textlock--;
|
||||
|
||||
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
|
||||
free_update_callbacks(cb);
|
||||
buffer_update_callbacks_free(cb);
|
||||
keep = false;
|
||||
}
|
||||
api_free_object(res);
|
||||
@ -342,7 +342,7 @@ void buf_updates_send_splice(
|
||||
textlock--;
|
||||
|
||||
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
|
||||
free_update_callbacks(cb);
|
||||
buffer_update_callbacks_free(cb);
|
||||
keep = false;
|
||||
}
|
||||
}
|
||||
@ -378,7 +378,7 @@ void buf_updates_changedtick(buf_T *buf)
|
||||
textlock--;
|
||||
|
||||
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
|
||||
free_update_callbacks(cb);
|
||||
buffer_update_callbacks_free(cb);
|
||||
keep = false;
|
||||
}
|
||||
api_free_object(res);
|
||||
@ -406,8 +406,11 @@ void buf_updates_changedtick_single(buf_T *buf, uint64_t channel_id)
|
||||
rpc_send_event(channel_id, "nvim_buf_changedtick_event", args);
|
||||
}
|
||||
|
||||
static void free_update_callbacks(BufUpdateCallbacks cb)
|
||||
void buffer_update_callbacks_free(BufUpdateCallbacks cb)
|
||||
{
|
||||
api_free_luaref(cb.on_lines);
|
||||
api_free_luaref(cb.on_bytes);
|
||||
api_free_luaref(cb.on_changedtick);
|
||||
api_free_luaref(cb.on_reload);
|
||||
api_free_luaref(cb.on_detach);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
||||
|
||||
#include "nvim/vim.h"
|
||||
#include "nvim/lua/executor.h"
|
||||
#include "nvim/extmark.h"
|
||||
#include "nvim/decoration.h"
|
||||
#include "nvim/screen.h"
|
||||
@ -365,3 +366,52 @@ void decor_add_ephemeral(int start_row, int start_col, int end_row, int end_col,
|
||||
decor_add(&decor_state, start_row, start_col, end_row, end_col, decor, true,
|
||||
priority);
|
||||
}
|
||||
|
||||
|
||||
DecorProvider *get_decor_provider(NS ns_id, bool force)
|
||||
{
|
||||
ssize_t i;
|
||||
for (i = 0; i < (ssize_t)kv_size(decor_providers); i++) {
|
||||
DecorProvider *item = &kv_A(decor_providers, i);
|
||||
if (item->ns_id == ns_id) {
|
||||
return item;
|
||||
} else if (item->ns_id > ns_id) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!force) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (ssize_t j = (ssize_t)kv_size(decor_providers)-1; j >= i; j++) {
|
||||
// allocates if needed:
|
||||
(void)kv_a(decor_providers, (size_t)j+1);
|
||||
kv_A(decor_providers, (size_t)j+1) = kv_A(decor_providers, j);
|
||||
}
|
||||
DecorProvider *item = &kv_a(decor_providers, (size_t)i);
|
||||
*item = DECORATION_PROVIDER_INIT(ns_id);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
void decor_provider_clear(DecorProvider *p)
|
||||
{
|
||||
if (p == NULL) {
|
||||
return;
|
||||
}
|
||||
NLUA_CLEAR_REF(p->redraw_start);
|
||||
NLUA_CLEAR_REF(p->redraw_buf);
|
||||
NLUA_CLEAR_REF(p->redraw_win);
|
||||
NLUA_CLEAR_REF(p->redraw_line);
|
||||
NLUA_CLEAR_REF(p->redraw_end);
|
||||
p->active = false;
|
||||
}
|
||||
|
||||
void decor_free_all_mem(void)
|
||||
{
|
||||
for (size_t i = 0; i < kv_size(decor_providers); i++) {
|
||||
decor_provider_clear(&kv_A(decor_providers, i));
|
||||
}
|
||||
kv_destroy(decor_providers);
|
||||
}
|
||||
|
@ -6263,6 +6263,7 @@ void common_function(typval_T *argvars, typval_T *rettv,
|
||||
// function(dict.MyFunc, [arg])
|
||||
arg_pt = argvars[0].vval.v_partial;
|
||||
s = partial_name(arg_pt);
|
||||
// TODO(bfredl): do the entire nlua_is_table_from_lua dance
|
||||
} else {
|
||||
// function('MyFunc', [arg], dict)
|
||||
s = (char_u *)tv_get_string(&argvars[0]);
|
||||
@ -7362,7 +7363,6 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg)
|
||||
char_u *name = nlua_register_table_as_callable(arg);
|
||||
|
||||
if (name != NULL) {
|
||||
func_ref(name);
|
||||
callback->data.funcref = vim_strsave(name);
|
||||
callback->type = kCallbackFuncref;
|
||||
} else {
|
||||
|
@ -810,6 +810,7 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
return;
|
||||
}
|
||||
|
||||
bool owned = false;
|
||||
char_u *func;
|
||||
partial_T *partial = NULL;
|
||||
dict_T *selfdict = NULL;
|
||||
@ -820,6 +821,7 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
func = partial_name(partial);
|
||||
} else if (nlua_is_table_from_lua(&argvars[0])) {
|
||||
func = nlua_register_table_as_callable(&argvars[0]);
|
||||
owned = true;
|
||||
} else {
|
||||
func = (char_u *)tv_get_string(&argvars[0]);
|
||||
}
|
||||
@ -837,6 +839,9 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
}
|
||||
|
||||
func_call(func, &argvars[1], partial, selfdict, rettv);
|
||||
if (owned) {
|
||||
func_unref(func);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -219,6 +219,7 @@ list_T *tv_list_alloc(const ptrdiff_t len)
|
||||
list->lv_used_next = gc_first_list;
|
||||
gc_first_list = list;
|
||||
list_log(list, NULL, (void *)(uintptr_t)len, "alloc");
|
||||
list->lua_table_ref = LUA_NOREF;
|
||||
return list;
|
||||
}
|
||||
|
||||
@ -302,7 +303,7 @@ void tv_list_free_list(list_T *const l)
|
||||
}
|
||||
list_log(l, NULL, NULL, "freelist");
|
||||
|
||||
nlua_free_typval_list(l);
|
||||
NLUA_CLEAR_REF(l->lua_table_ref);
|
||||
xfree(l);
|
||||
}
|
||||
|
||||
@ -1404,6 +1405,8 @@ dict_T *tv_dict_alloc(void)
|
||||
d->dv_copyID = 0;
|
||||
QUEUE_INIT(&d->watchers);
|
||||
|
||||
d->lua_table_ref = LUA_NOREF;
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
@ -1454,7 +1457,7 @@ void tv_dict_free_dict(dict_T *const d)
|
||||
d->dv_used_next->dv_used_prev = d->dv_used_prev;
|
||||
}
|
||||
|
||||
nlua_free_typval_dict(d);
|
||||
NLUA_CLEAR_REF(d->lua_table_ref);
|
||||
xfree(d);
|
||||
}
|
||||
|
||||
|
@ -151,7 +151,7 @@ int hl_get_syn_attr(int ns_id, int idx, HlAttrs at_en)
|
||||
|
||||
void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id)
|
||||
{
|
||||
DecorProvider *p = get_provider(ns_id, true);
|
||||
DecorProvider *p = get_decor_provider(ns_id, true);
|
||||
if ((attrs.rgb_ae_attr & HL_DEFAULT)
|
||||
&& map_has(ColorKey, ColorItem)(ns_hl, ColorKey(ns_id, hl_id))) {
|
||||
return;
|
||||
@ -175,7 +175,7 @@ int ns_get_hl(NS ns_id, int hl_id, bool link, bool nodefault)
|
||||
ns_id = ns_hl_active;
|
||||
}
|
||||
|
||||
DecorProvider *p = get_provider(ns_id, true);
|
||||
DecorProvider *p = get_decor_provider(ns_id, true);
|
||||
ColorItem it = map_get(ColorKey, ColorItem)(ns_hl, ColorKey(ns_id, hl_id));
|
||||
// TODO(bfredl): map_ref true even this?
|
||||
bool valid_cache = it.version >= p->hl_valid;
|
||||
|
@ -400,7 +400,6 @@ nlua_pop_typval_table_processing_end:
|
||||
case LUA_TFUNCTION: {
|
||||
LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState));
|
||||
state->lua_callable.func_ref = nlua_ref(lstate, -1);
|
||||
state->lua_callable.table_ref = LUA_NOREF;
|
||||
|
||||
char_u *name = register_cfunc(
|
||||
&nlua_CFunction_func_call,
|
||||
@ -412,6 +411,7 @@ nlua_pop_typval_table_processing_end:
|
||||
break;
|
||||
}
|
||||
case LUA_TUSERDATA: {
|
||||
// TODO(bfredl): check mt.__call and convert to function?
|
||||
nlua_pushref(lstate, nlua_nil_ref);
|
||||
bool is_nil = lua_rawequal(lstate, -2, -1);
|
||||
lua_pop(lstate, 1);
|
||||
|
@ -11,7 +11,6 @@
|
||||
|
||||
typedef struct {
|
||||
LuaRef func_ref;
|
||||
LuaRef table_ref;
|
||||
} LuaCallable;
|
||||
|
||||
typedef struct {
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include <lualib.h>
|
||||
#include <lauxlib.h>
|
||||
|
||||
#include "nvim/assert.h"
|
||||
#include "nvim/version.h"
|
||||
#include "nvim/misc1.h"
|
||||
#include "nvim/getchar.h"
|
||||
@ -18,6 +19,7 @@
|
||||
#include "nvim/vim.h"
|
||||
#include "nvim/ex_getln.h"
|
||||
#include "nvim/ex_cmds2.h"
|
||||
#include "nvim/map.h"
|
||||
#include "nvim/message.h"
|
||||
#include "nvim/memline.h"
|
||||
#include "nvim/buffer_defs.h"
|
||||
@ -32,9 +34,7 @@
|
||||
#include "nvim/event/time.h"
|
||||
#include "nvim/event/loop.h"
|
||||
|
||||
#ifdef WIN32
|
||||
#include "nvim/os/os.h"
|
||||
#endif
|
||||
|
||||
#include "nvim/lua/converter.h"
|
||||
#include "nvim/lua/executor.h"
|
||||
@ -63,6 +63,11 @@ typedef struct {
|
||||
} \
|
||||
}
|
||||
|
||||
#if __has_feature(address_sanitizer)
|
||||
PMap(handle_T) *nlua_ref_markers = NULL;
|
||||
# define NLUA_TRACK_REFS
|
||||
#endif
|
||||
|
||||
/// Convert lua error into a Vim error message
|
||||
///
|
||||
/// @param lstate Lua interpreter state.
|
||||
@ -547,6 +552,13 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
|
||||
static lua_State *nlua_init(void)
|
||||
FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
{
|
||||
#ifdef NLUA_TRACK_REFS
|
||||
const char *env = os_getenv("NVIM_LUA_NOTRACK");
|
||||
if (!env || !*env) {
|
||||
nlua_ref_markers = pmap_new(handle_T)();
|
||||
}
|
||||
#endif
|
||||
|
||||
lua_State *lstate = luaL_newstate();
|
||||
if (lstate == NULL) {
|
||||
EMSG(_("E970: Failed to initialize lua interpreter"));
|
||||
@ -554,9 +566,13 @@ static lua_State *nlua_init(void)
|
||||
}
|
||||
luaL_openlibs(lstate);
|
||||
nlua_state_init(lstate);
|
||||
|
||||
return lstate;
|
||||
}
|
||||
|
||||
// only to be used by nlua_enter and nlua_free_all_mem!
|
||||
static lua_State *global_lstate = NULL;
|
||||
|
||||
/// Enter lua interpreter
|
||||
///
|
||||
/// Calls nlua_init() if needed. Is responsible for pre-lua call initalization
|
||||
@ -567,26 +583,39 @@ static lua_State *nlua_init(void)
|
||||
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_pop(lstate, 1);
|
||||
// stack: (empty)
|
||||
last_p_rtp = (const void *)p_rtp;
|
||||
}
|
||||
return lstate;
|
||||
}
|
||||
|
||||
void nlua_free_all_mem(void)
|
||||
{
|
||||
if (!global_lstate) {
|
||||
return;
|
||||
}
|
||||
lua_State *lstate = global_lstate;
|
||||
|
||||
nlua_unref(lstate, nlua_nil_ref);
|
||||
nlua_unref(lstate, nlua_empty_dict_ref);
|
||||
|
||||
#ifdef NLUA_TRACK_REFS
|
||||
if (nlua_refcount) {
|
||||
fprintf(stderr, "%d lua references were leaked!", nlua_refcount);
|
||||
}
|
||||
|
||||
if (nlua_ref_markers) {
|
||||
// in case there are leaked luarefs, leak the associated memory
|
||||
// to get LeakSanitizer stacktraces on exit
|
||||
pmap_free(handle_T)(nlua_ref_markers);
|
||||
}
|
||||
#endif
|
||||
|
||||
nlua_refcount = 0;
|
||||
lua_close(lstate);
|
||||
}
|
||||
|
||||
static void nlua_print_event(void **argv)
|
||||
{
|
||||
char *str = argv[0];
|
||||
@ -866,17 +895,35 @@ static int nlua_getenv(lua_State *lstate)
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/// add the value to the registry
|
||||
LuaRef nlua_ref(lua_State *lstate, int index)
|
||||
{
|
||||
lua_pushvalue(lstate, index);
|
||||
return luaL_ref(lstate, LUA_REGISTRYINDEX);
|
||||
LuaRef ref = luaL_ref(lstate, LUA_REGISTRYINDEX);
|
||||
if (ref > 0) {
|
||||
nlua_refcount++;
|
||||
#ifdef NLUA_TRACK_REFS
|
||||
if (nlua_ref_markers) {
|
||||
// dummy allocation to make LeakSanitizer track our luarefs
|
||||
pmap_put(handle_T)(nlua_ref_markers, ref, xmalloc(3));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
|
||||
/// remove the value from the registry
|
||||
void nlua_unref(lua_State *lstate, LuaRef ref)
|
||||
{
|
||||
if (ref > 0) {
|
||||
nlua_refcount--;
|
||||
#ifdef NLUA_TRACK_REFS
|
||||
// NB: don't remove entry from map to track double-unref
|
||||
if (nlua_ref_markers) {
|
||||
xfree(pmap_get(handle_T)(nlua_ref_markers, ref));
|
||||
}
|
||||
#endif
|
||||
luaL_unref(lstate, LUA_REGISTRYINDEX, ref);
|
||||
}
|
||||
}
|
||||
@ -893,19 +940,11 @@ void nlua_pushref(lua_State *lstate, LuaRef ref)
|
||||
lua_rawgeti(lstate, LUA_REGISTRYINDEX, ref);
|
||||
}
|
||||
|
||||
|
||||
/// Gets a new reference to an object stored at original_ref
|
||||
///
|
||||
/// NOTE: It does not copy the value, it creates a new ref to the lua object.
|
||||
/// Leaves the stack unchanged.
|
||||
LuaRef nlua_newref(lua_State *lstate, LuaRef original_ref)
|
||||
{
|
||||
nlua_pushref(lstate, original_ref);
|
||||
LuaRef new_ref = nlua_ref(lstate, -1);
|
||||
lua_pop(lstate, 1);
|
||||
|
||||
return new_ref;
|
||||
}
|
||||
|
||||
LuaRef api_new_luaref(LuaRef original_ref)
|
||||
{
|
||||
if (original_ref == LUA_NOREF) {
|
||||
@ -913,7 +952,10 @@ LuaRef api_new_luaref(LuaRef original_ref)
|
||||
}
|
||||
|
||||
lua_State *const lstate = nlua_enter();
|
||||
return nlua_newref(lstate, original_ref);
|
||||
nlua_pushref(lstate, original_ref);
|
||||
LuaRef new_ref = nlua_ref(lstate, -1);
|
||||
lua_pop(lstate, 1);
|
||||
return new_ref;
|
||||
}
|
||||
|
||||
|
||||
@ -1023,25 +1065,13 @@ int typval_exec_lua_callable(
|
||||
typval_T *rettv
|
||||
)
|
||||
{
|
||||
int offset = 0;
|
||||
LuaRef cb = lua_cb.func_ref;
|
||||
|
||||
if (cb == LUA_NOREF) {
|
||||
// This shouldn't happen.
|
||||
luaL_error(lstate, "Invalid function passed to VimL");
|
||||
return ERROR_OTHER;
|
||||
}
|
||||
|
||||
nlua_pushref(lstate, cb);
|
||||
|
||||
if (lua_cb.table_ref != LUA_NOREF) {
|
||||
offset += 1;
|
||||
nlua_pushref(lstate, lua_cb.table_ref);
|
||||
}
|
||||
|
||||
PUSH_ALL_TYPVALS(lstate, argvars, argcount, false);
|
||||
|
||||
if (lua_pcall(lstate, argcount + offset, 1, 0)) {
|
||||
if (lua_pcall(lstate, argcount, 1, 0)) {
|
||||
nlua_print(lstate);
|
||||
return ERROR_OTHER;
|
||||
}
|
||||
@ -1508,6 +1538,8 @@ static int regex_match_line(lua_State *lstate)
|
||||
return nret;
|
||||
}
|
||||
|
||||
// Required functions for lua c functions as VimL callbacks
|
||||
|
||||
int nlua_CFunction_func_call(
|
||||
int argcount,
|
||||
typval_T *argvars,
|
||||
@ -1517,53 +1549,40 @@ int nlua_CFunction_func_call(
|
||||
lua_State *const lstate = nlua_enter();
|
||||
LuaCFunctionState *funcstate = (LuaCFunctionState *)state;
|
||||
|
||||
return typval_exec_lua_callable(
|
||||
lstate,
|
||||
funcstate->lua_callable,
|
||||
argcount,
|
||||
argvars,
|
||||
rettv);
|
||||
return typval_exec_lua_callable(lstate, funcstate->lua_callable,
|
||||
argcount, argvars, rettv);
|
||||
}
|
||||
/// Required functions for lua c functions as VimL callbacks
|
||||
|
||||
void nlua_CFunction_func_free(void *state)
|
||||
{
|
||||
lua_State *const lstate = nlua_enter();
|
||||
LuaCFunctionState *funcstate = (LuaCFunctionState *)state;
|
||||
|
||||
nlua_unref(lstate, funcstate->lua_callable.func_ref);
|
||||
nlua_unref(lstate, funcstate->lua_callable.table_ref);
|
||||
xfree(funcstate);
|
||||
}
|
||||
|
||||
bool nlua_is_table_from_lua(typval_T *const arg)
|
||||
{
|
||||
if (arg->v_type != VAR_DICT && arg->v_type != VAR_LIST) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (arg->v_type == VAR_DICT) {
|
||||
return arg->vval.v_dict->lua_table_ref > 0
|
||||
&& arg->vval.v_dict->lua_table_ref != LUA_NOREF;
|
||||
return arg->vval.v_dict->lua_table_ref != LUA_NOREF;
|
||||
} else if (arg->v_type == VAR_LIST) {
|
||||
return arg->vval.v_list->lua_table_ref > 0
|
||||
&& arg->vval.v_list->lua_table_ref != LUA_NOREF;
|
||||
}
|
||||
|
||||
return arg->vval.v_list->lua_table_ref != LUA_NOREF;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
char_u *nlua_register_table_as_callable(typval_T *const arg)
|
||||
{
|
||||
if (!nlua_is_table_from_lua(arg)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LuaRef table_ref;
|
||||
LuaRef table_ref = LUA_NOREF;
|
||||
if (arg->v_type == VAR_DICT) {
|
||||
table_ref = arg->vval.v_dict->lua_table_ref;
|
||||
} else if (arg->v_type == VAR_LIST) {
|
||||
table_ref = arg->vval.v_list->lua_table_ref;
|
||||
} else {
|
||||
}
|
||||
|
||||
if (table_ref == LUA_NOREF) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -1573,55 +1592,34 @@ char_u *nlua_register_table_as_callable(typval_T *const arg)
|
||||
int top = lua_gettop(lstate);
|
||||
#endif
|
||||
|
||||
nlua_pushref(lstate, table_ref);
|
||||
nlua_pushref(lstate, table_ref); // [table]
|
||||
if (!lua_getmetatable(lstate, -1)) {
|
||||
lua_pop(lstate, 1);
|
||||
assert(top == lua_gettop(lstate));
|
||||
return NULL;
|
||||
}
|
||||
} // [table, mt]
|
||||
|
||||
lua_getfield(lstate, -1, "__call");
|
||||
lua_getfield(lstate, -1, "__call"); // [table, mt, mt.__call]
|
||||
if (!lua_isfunction(lstate, -1)) {
|
||||
lua_pop(lstate, 3);
|
||||
assert(top == lua_gettop(lstate));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LuaRef new_table_ref = nlua_newref(lstate, table_ref);
|
||||
lua_pop(lstate, 2); // [table]
|
||||
|
||||
LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState));
|
||||
state->lua_callable.func_ref = nlua_ref(lstate, -1);
|
||||
state->lua_callable.table_ref = new_table_ref;
|
||||
|
||||
char_u *name = register_cfunc(
|
||||
&nlua_CFunction_func_call,
|
||||
&nlua_CFunction_func_free,
|
||||
state);
|
||||
char_u *name = register_cfunc(&nlua_CFunction_func_call,
|
||||
&nlua_CFunction_func_free, state);
|
||||
|
||||
|
||||
lua_pop(lstate, 3);
|
||||
lua_pop(lstate, 1); // []
|
||||
assert(top == lua_gettop(lstate));
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/// Helper function to free a list_T
|
||||
void nlua_free_typval_list(list_T *const l)
|
||||
{
|
||||
if (l->lua_table_ref != LUA_NOREF && l->lua_table_ref > 0) {
|
||||
lua_State *const lstate = nlua_enter();
|
||||
nlua_unref(lstate, l->lua_table_ref);
|
||||
l->lua_table_ref = LUA_NOREF;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Helper function to free a dict_T
|
||||
void nlua_free_typval_dict(dict_T *const d)
|
||||
{
|
||||
if (d->lua_table_ref != LUA_NOREF && d->lua_table_ref > 0) {
|
||||
lua_State *const lstate = nlua_enter();
|
||||
nlua_unref(lstate, d->lua_table_ref);
|
||||
d->lua_table_ref = LUA_NOREF;
|
||||
}
|
||||
}
|
||||
|
||||
void nlua_execute_log_keystroke(int c)
|
||||
{
|
||||
char_u buf[NUMBUFLEN];
|
||||
|
@ -16,6 +16,8 @@ void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL;
|
||||
EXTERN LuaRef nlua_nil_ref INIT(= LUA_NOREF);
|
||||
EXTERN LuaRef nlua_empty_dict_ref INIT(= LUA_NOREF);
|
||||
|
||||
EXTERN int nlua_refcount INIT(= 0);
|
||||
|
||||
#define set_api_error(s, err) \
|
||||
do { \
|
||||
Error *err_ = (err); \
|
||||
|
@ -19,6 +19,8 @@
|
||||
#include "nvim/ui.h"
|
||||
#include "nvim/sign.h"
|
||||
#include "nvim/api/vim.h"
|
||||
#include "nvim/lua/executor.h"
|
||||
#include "nvim/decoration.h"
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
# define malloc(size) mem_malloc(size)
|
||||
@ -695,6 +697,10 @@ void free_all_mem(void)
|
||||
list_free_log();
|
||||
|
||||
check_quickfix_busy();
|
||||
|
||||
decor_free_all_mem();
|
||||
|
||||
nlua_free_all_mem();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -29,6 +29,16 @@ teardown(function()
|
||||
os.remove(fake_lsp_logfile)
|
||||
end)
|
||||
|
||||
local function clear_notrace()
|
||||
-- problem: here be dragons
|
||||
-- solution: don't look for dragons to closely
|
||||
clear {env={
|
||||
NVIM_LUA_NOTRACK="1";
|
||||
VIMRUNTIME=os.getenv"VIMRUNTIME";
|
||||
}}
|
||||
end
|
||||
|
||||
|
||||
local function fake_lsp_server_setup(test_name, timeout_ms, options)
|
||||
exec_lua([=[
|
||||
lsp = require('vim.lsp')
|
||||
@ -36,6 +46,7 @@ local function fake_lsp_server_setup(test_name, timeout_ms, options)
|
||||
TEST_RPC_CLIENT_ID = lsp.start_client {
|
||||
cmd_env = {
|
||||
NVIM_LOG_FILE = logfile;
|
||||
NVIM_LUA_NOTRACK = "1";
|
||||
};
|
||||
cmd = {
|
||||
vim.v.progpath, '-Es', '-u', 'NONE', '--headless',
|
||||
@ -65,7 +76,7 @@ end
|
||||
|
||||
local function test_rpc_server(config)
|
||||
if config.test_name then
|
||||
clear()
|
||||
clear_notrace()
|
||||
fake_lsp_server_setup(config.test_name, config.timeout_ms or 1e3, config.options)
|
||||
end
|
||||
local client = setmetatable({}, {
|
||||
@ -120,7 +131,7 @@ end
|
||||
describe('LSP', function()
|
||||
describe('server_name specified', function()
|
||||
before_each(function()
|
||||
clear()
|
||||
clear_notrace()
|
||||
-- Run an instance of nvim on the file which contains our "scripts".
|
||||
-- Pass TEST_NAME to pick the script.
|
||||
local test_name = "basic_init"
|
||||
@ -250,6 +261,10 @@ describe('LSP', function()
|
||||
end)
|
||||
|
||||
it('should succeed with manual shutdown', function()
|
||||
if 'openbsd' == helpers.uname() then
|
||||
pending('hangs the build on openbsd #14028, re-enable with freeze timeout #14204')
|
||||
return
|
||||
end
|
||||
local expected_callbacks = {
|
||||
{NIL, "shutdown", {}, 1, NIL};
|
||||
{NIL, "test", {}, 1};
|
||||
@ -314,7 +329,7 @@ describe('LSP', function()
|
||||
}
|
||||
end)
|
||||
it('workspace/configuration returns NIL per section if client was started without config.settings', function()
|
||||
clear()
|
||||
clear_notrace()
|
||||
fake_lsp_server_setup('workspace/configuration no settings')
|
||||
eq({ NIL, NIL, }, exec_lua [[
|
||||
local params = {
|
||||
@ -941,7 +956,7 @@ end)
|
||||
|
||||
describe('LSP', function()
|
||||
before_each(function()
|
||||
clear()
|
||||
clear_notrace()
|
||||
end)
|
||||
|
||||
local function make_edit(y_0, x_0, y_1, x_1, text)
|
||||
|
Loading…
Reference in New Issue
Block a user