From 8fd3d31329ba428a8624da869c24c5df38623ea3 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 7 Jan 2017 13:52:51 +0300 Subject: [PATCH 01/11] unittest: Allow mocking allocator calls --- src/nvim/memory.c | 55 +++++++++++++++++++-------- src/nvim/memory.h | 10 +++++ test/unit/eval/helpers.lua | 54 ++++++++++++++++++++++++--- test/unit/helpers.lua | 76 ++++++++++++++++++++++++++++++++++---- 4 files changed, 167 insertions(+), 28 deletions(-) diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 1884d55999..3c0d001848 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -17,10 +17,31 @@ // Force je_ prefix on jemalloc functions. # define JEMALLOC_NO_DEMANGLE # include -# define malloc(size) je_malloc(size) -# define calloc(count, size) je_calloc(count, size) -# define realloc(ptr, size) je_realloc(ptr, size) -# define free(ptr) je_free(ptr) +#endif + +#ifdef UNIT_TESTING +# define malloc(size) mem_malloc(size) +# define calloc(count, size) mem_calloc(count, size) +# define realloc(ptr, size) mem_realloc(ptr, size) +# define free(ptr) mem_free(ptr) +# ifdef HAVE_JEMALLOC +MemMalloc mem_malloc = &je_malloc; +MemFree mem_free = &je_free; +MemCalloc mem_calloc = &je_calloc; +MemRealloc mem_realloc = &je_realloc; +# else +MemMalloc mem_malloc = &malloc; +MemFree mem_free = &free; +MemCalloc mem_calloc = &calloc; +MemRealloc mem_realloc = &realloc; +# endif +#else +# ifdef HAVE_JEMALLOC +# define malloc(size) je_malloc(size) +# define calloc(count, size) je_calloc(count, size) +# define realloc(ptr, size) je_realloc(ptr, size) +# define free(ptr) je_free(ptr) +# endif #endif #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -353,15 +374,15 @@ char *xstpncpy(char *restrict dst, const char *restrict src, size_t maxlen) size_t xstrlcpy(char *restrict dst, const char *restrict src, size_t size) FUNC_ATTR_NONNULL_ALL { - size_t ret = strlen(src); + size_t ret = strlen(src); - if (size) { - size_t len = (ret >= size) ? size - 1 : ret; - memcpy(dst, src, len); - dst[len] = '\0'; - } + if (size) { + size_t len = (ret >= size) ? size - 1 : ret; + memcpy(dst, src, len); + dst[len] = '\0'; + } - return ret; + return ret; } /// strdup() wrapper @@ -371,6 +392,7 @@ size_t xstrlcpy(char *restrict dst, const char *restrict src, size_t size) /// @return pointer to a copy of the string char *xstrdup(const char *str) FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET + FUNC_ATTR_NONNULL_ALL { return xmemdupz(str, strlen(str)); } @@ -401,6 +423,7 @@ void *xmemrchr(const void *src, uint8_t c, size_t len) /// @return pointer to a copy of the string char *xstrndup(const char *str, size_t len) FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET + FUNC_ATTR_NONNULL_ALL { char *p = memchr(str, '\0', len); return xmemdupz(str, p ? (size_t)(p - str) : len); @@ -488,13 +511,13 @@ void time_to_bytes(time_t time_, uint8_t buf[8]) void free_all_mem(void) { buf_T *buf, *nextbuf; + static bool entered = false; - // When we cause a crash here it is caught and Vim tries to exit cleanly. - // Don't try freeing everything again. - if (entered_free_all_mem) { + /* When we cause a crash here it is caught and Vim tries to exit cleanly. + * Don't try freeing everything again. */ + if (entered) return; - } - entered_free_all_mem = true; + entered = true; // Don't want to trigger autocommands from here on. block_autocmds(); diff --git a/src/nvim/memory.h b/src/nvim/memory.h index 62cc78360c..0b422957d5 100644 --- a/src/nvim/memory.h +++ b/src/nvim/memory.h @@ -5,6 +5,16 @@ #include // for size_t #include // for time_t +typedef void *(*MemMalloc)(size_t); +typedef void (*MemFree)(void *); +typedef void *(*MemCalloc)(size_t, size_t); +typedef void *(*MemRealloc)(void *, size_t); + +extern MemMalloc mem_malloc; +extern MemFree mem_free; +extern MemCalloc mem_calloc; +extern MemRealloc mem_realloc; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "memory.h.generated.h" #endif diff --git a/test/unit/eval/helpers.lua b/test/unit/eval/helpers.lua index 712530a0aa..1feeb5c4f7 100644 --- a/test/unit/eval/helpers.lua +++ b/test/unit/eval/helpers.lua @@ -122,6 +122,32 @@ typvalt2lua = function(t, processed) end)(t, processed or {})) end +local function list_iter(l) + local init_s = { + idx=0, + li=l.lv_first, + } + local function f(s, _) + -- (listitem_T *) NULL is equal to nil, but yet it is not false. + if s.li == nil then + return nil + end + local ret_li = s.li + s.li = s.li.li_next + s.idx = s.idx + 1 + return s.idx, ret_li + end + return f, init_s, nil +end + +local function list_items(l) + local ret = {} + for i, li in list_iter(l) do + ret[i] = li + end + return ret +end + lst2tbl = function(l, processed) if l == nil then return null_list @@ -133,11 +159,8 @@ lst2tbl = function(l, processed) end local ret = {[type_key]=list_type} processed[p_key] = ret - local li = l.lv_first - -- (listitem_T *) NULL is equal to nil, but yet it is not false. - while li ~= nil do - ret[#ret + 1] = typvalt2lua(li.li_tv, processed) - li = li.li_next + for i, li in list_iter(l) do + ret[i] = typvalt2lua(li.li_tv, processed) end if ret[1] then ret[type_key] = nil @@ -324,6 +347,22 @@ lua2typvalt = function(l, processed) end end +local function void(ptr) + return ffi.cast('void*', ptr) +end + +local alloc_logging_helpers = { + list = function(l) return {func='calloc', args={1, ffi.sizeof('list_T')}, ret=void(l)} end, + li = function(li) return {func='malloc', args={ffi.sizeof('listitem_T')}, ret=void(li)} end, + dict = function(d) return {func='malloc', args={ffi.sizeof('dict_T')}, ret=void(d)} end, + di = function(di, size) + return {func='malloc', args={ffi.offsetof('dictitem_T', 'di_key') + size + 1}, ret=void(di)} + end, + str = function(s, size) return {func='malloc', args={size + 1}, ret=void(s)} end, + + freed = function(p) return {func='free', args={p and void(p)}} end, +} + return { null_string=null_string, null_list=null_list, @@ -350,5 +389,10 @@ return { li_alloc=li_alloc, dict_iter=dict_iter, + list_iter=list_iter, first_di=first_di, + + alloc_logging_helpers=alloc_logging_helpers, + + list_items=list_items, } diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index 45bbaaeb10..58a04e9c43 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -9,6 +9,12 @@ local neq = global_helpers.neq local eq = global_helpers.eq local ok = global_helpers.ok +-- C constants. +local NULL = ffi.cast('void*', 0) + +local OK = 1 +local FAIL = 0 + -- add some standard header locations for _, p in ipairs(Paths.include_paths) do Preprocess.add_to_include_path(p) @@ -118,6 +124,67 @@ local function cppimport(path) return cimport(Paths.test_include_path .. '/' .. path) end +local function alloc_log_new(eq) + local log = { + log={}, + lib=cimport('./src/nvim/memory.h'), + original_functions={}, + null={['\0:is_null']=true}, + } + local allocator_functions = {'malloc', 'free', 'calloc', 'realloc'} + function log:save_original_functions() + for _, funcname in ipairs(allocator_functions) do + self.original_functions[funcname] = self.lib['mem_' .. funcname] + end + end + function log:set_mocks() + for _, k in ipairs(allocator_functions) do + do + local kk = k + self.lib['mem_' .. k] = function(...) + local log_entry = {func=kk, args={...}} + self.log[#self.log + 1] = log_entry + if kk == 'free' then + self.original_functions[kk](...) + else + log_entry.ret = self.original_functions[kk](...) + end + for i, v in ipairs(log_entry.args) do + if v == nil then + -- XXX This thing thinks that {NULL} ~= {NULL}. + log_entry.args[i] = self.null + end + end + if self.hook then self:hook(log_entry) end + if log_entry.ret then + return log_entry.ret + end + end + end + end + end + function log:clear() + self.log = {} + end + function log:check(exp) + eq(exp, self.log) + self:clear() + end + function log:restore_original_functions() + for k, v in pairs(self.original_functions) do + self.lib['mem_' .. k] = v + end + end + function log:before_each() + log:save_original_functions() + log:set_mocks() + end + function log:after_each() + log:restore_original_functions() + end + return log +end + cimport('./src/nvim/types.h') -- take a pointer to a C-allocated string and return an interned @@ -142,12 +209,6 @@ do main.event_init() end --- C constants. -local NULL = ffi.cast('void*', 0) - -local OK = 1 -local FAIL = 0 - return { cimport = cimport, cppimport = cppimport, @@ -161,5 +222,6 @@ return { to_cstr = to_cstr, NULL = NULL, OK = OK, - FAIL = FAIL + FAIL = FAIL, + alloc_log_new = alloc_log_new, } From 41cbb7891da4e39217ae3ca3a0a1f9bbcd1e9cee Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 7 Jan 2017 14:48:05 +0300 Subject: [PATCH 02/11] unittest: Add failing test of freeing lists --- test/unit/eval/tv_clear_spec.lua | 79 ++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 test/unit/eval/tv_clear_spec.lua diff --git a/test/unit/eval/tv_clear_spec.lua b/test/unit/eval/tv_clear_spec.lua new file mode 100644 index 0000000000..d6ef0caa1a --- /dev/null +++ b/test/unit/eval/tv_clear_spec.lua @@ -0,0 +1,79 @@ +local helpers = require('test.unit.helpers') +local eval_helpers = require('test.unit.eval.helpers') + +local alloc_log_new = helpers.alloc_log_new +local cimport = helpers.cimport +local ffi = helpers.ffi +local eq = helpers.eq + +local a = eval_helpers.alloc_logging_helpers +local type_key = eval_helpers.type_key +local list_type = eval_helpers.list_type +local list_items = eval_helpers.list_items +local lua2typvalt = eval_helpers.lua2typvalt + +local lib = cimport('./src/nvim/eval_defs.h', './src/nvim/eval.h') + +local alloc_log = alloc_log_new(eq) + +before_each(function() + alloc_log:before_each() +end) + +after_each(function() + alloc_log:after_each() +end) + +describe('clear_tv()', function() + it('successfully frees all lists in [&l [1], *l, *l]', function() + local l_inner = {1} + local list = {l_inner, l_inner, l_inner} + local list_tv = ffi.gc(lua2typvalt(list), nil) + local list_p = list_tv.vval.v_list + local lis = list_items(list_p) + local list_inner_p = lis[1].li_tv.vval.v_list + local lis_inner = list_items(list_inner_p) + alloc_log:check({ + a.list(list_p), + a.list(list_inner_p), + a.li(lis_inner[1]), + a.li(lis[1]), + a.li(lis[2]), + a.li(lis[3]), + }) + eq(3, list_inner_p.lv_refcount) + lib.clear_tv(list_tv) + alloc_log:check({ + a.freed(lis_inner[1]), + a.freed(list_inner_p), + a.freed(lis[1]), + a.freed(lis[2]), + a.freed(lis[3]), + a.freed(list_p), + }) + end) + it('successfully frees all lists in [&l [], *l, *l]', function() + local l_inner = {[type_key]=list_type} + local list = {l_inner, l_inner, l_inner} + local list_tv = ffi.gc(lua2typvalt(list), nil) + local list_p = list_tv.vval.v_list + local lis = list_items(list_p) + local list_inner_p = lis[1].li_tv.vval.v_list + alloc_log:check({ + a.list(list_p), + a.list(list_inner_p), + a.li(lis[1]), + a.li(lis[2]), + a.li(lis[3]), + }) + eq(3, list_inner_p.lv_refcount) + lib.clear_tv(list_tv) + alloc_log:check({ + a.freed(list_inner_p), + a.freed(lis[1]), + a.freed(lis[2]), + a.freed(lis[3]), + a.freed(list_p), + }) + end) +end) From 7d0a892b374a8e5e025905a28e54b98b4f62f533 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 7 Jan 2017 14:59:42 +0300 Subject: [PATCH 03/11] eval/typval_encode.h: Use only a single macros with _INNER[_2] hack --- src/nvim/eval/typval_encode.h | 43 ++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/src/nvim/eval/typval_encode.h b/src/nvim/eval/typval_encode.h index 6517efa961..ba325b8f55 100644 --- a/src/nvim/eval/typval_encode.h +++ b/src/nvim/eval/typval_encode.h @@ -108,37 +108,38 @@ static inline size_t tv_strlen(const typval_T *const tv) } \ } while (0) -#define _TYPVAL_ENCODE_CHECK_SELF_REFERENCE_INNER_2(name) \ - _typval_encode_##name##_check_self_reference -#define _TYPVAL_ENCODE_CHECK_SELF_REFERENCE_INNER(name) \ - _TYPVAL_ENCODE_CHECK_SELF_REFERENCE_INNER_2(name) +#define _TYPVAL_ENCODE_FUNC_NAME_INNER_2(pref, name, suf) \ + pref##name##suf +#define _TYPVAL_ENCODE_FUNC_NAME_INNER(pref, name, suf) \ + _TYPVAL_ENCODE_FUNC_NAME_INNER_2(pref, name, suf) + +/// Construct function name, possibly using macros +/// +/// Is used to expand macros that may appear in arguments. +/// +/// @note Expands all arguments, even if only one is needed. +/// +/// @param[in] pref Prefix. +/// @param[in] suf Suffix. +/// +/// @return Concat: pref + #TYPVAL_ENCODE_NAME + suf. +#define _TYPVAL_ENCODE_FUNC_NAME(pref, suf) \ + _TYPVAL_ENCODE_FUNC_NAME_INNER(pref, TYPVAL_ENCODE_NAME, suf) /// Self reference checker function name #define _TYPVAL_ENCODE_CHECK_SELF_REFERENCE \ - _TYPVAL_ENCODE_CHECK_SELF_REFERENCE_INNER(TYPVAL_ENCODE_NAME) - -#define _TYPVAL_ENCODE_ENCODE_INNER_2(name) encode_vim_to_##name -#define _TYPVAL_ENCODE_ENCODE_INNER(name) _TYPVAL_ENCODE_ENCODE_INNER_2(name) + _TYPVAL_ENCODE_FUNC_NAME(_typval_encode_, _check_self_reference) /// Entry point function name -#define _TYPVAL_ENCODE_ENCODE _TYPVAL_ENCODE_ENCODE_INNER(TYPVAL_ENCODE_NAME) - -#define _TYPVAL_ENCODE_CONVERT_ONE_VALUE_INNER_2(name) \ - _typval_encode_##name##_convert_one_value -#define _TYPVAL_ENCODE_CONVERT_ONE_VALUE_INNER(name) \ - _TYPVAL_ENCODE_CONVERT_ONE_VALUE_INNER_2(name) +#define _TYPVAL_ENCODE_ENCODE \ + _TYPVAL_ENCODE_FUNC_NAME(encode_vim_to_, ) /// Name of the …convert_one_value function #define _TYPVAL_ENCODE_CONVERT_ONE_VALUE \ - _TYPVAL_ENCODE_CONVERT_ONE_VALUE_INNER(TYPVAL_ENCODE_NAME) - -#define _TYPVAL_ENCODE_NODICT_VAR_INNER_2(name) \ - _typval_encode_##name##_nodict_var -#define _TYPVAL_ENCODE_NODICT_VAR_INNER(name) \ - _TYPVAL_ENCODE_NODICT_VAR_INNER_2(name) + _TYPVAL_ENCODE_FUNC_NAME(_typval_encode_, _convert_one_value) /// Name of the dummy const dict_T *const variable #define TYPVAL_ENCODE_NODICT_VAR \ - _TYPVAL_ENCODE_NODICT_VAR_INNER(TYPVAL_ENCODE_NAME) + _TYPVAL_ENCODE_FUNC_NAME(_typval_encode_, _nodict_var) #endif // NVIM_EVAL_TYPVAL_ENCODE_H From a970c1a95785cce9176274ffd4b783c79372afed Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 7 Jan 2017 15:15:14 +0300 Subject: [PATCH 04/11] eval: Make sure that copyID is reset when needed Works by making value pushed on stack represent the exhausted list. Fixes #5901, except for dictionaries which need similar adjustment. --- src/nvim/api/private/helpers.c | 3 +++ src/nvim/eval.c | 15 +++++++++------ src/nvim/eval/encode.c | 6 ++++++ src/nvim/eval/typval_encode.c.h | 11 +++++++++++ 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 701a1cbf2b..4eff21bc26 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -387,6 +387,8 @@ static inline void typval_encode_list_start(EncodedData *const edata, #define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \ typval_encode_list_start(edata, (size_t)(len)) +#define TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, mpsv) + static inline void typval_encode_between_list_items(EncodedData *const edata) FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL { @@ -499,6 +501,7 @@ static inline void typval_encode_dict_end(EncodedData *const edata) #undef TYPVAL_ENCODE_CONV_FUNC_END #undef TYPVAL_ENCODE_CONV_EMPTY_LIST #undef TYPVAL_ENCODE_CONV_LIST_START +#undef TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START #undef TYPVAL_ENCODE_CONV_EMPTY_DICT #undef TYPVAL_ENCODE_CONV_NIL #undef TYPVAL_ENCODE_CONV_BOOL diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 4501c2e0a6..f355b3eac4 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -19147,23 +19147,25 @@ static inline void _nothing_conv_func_end(typval_T *const tv, const int copyID) } \ } while (0) -static inline int _nothing_conv_list_start(typval_T *const tv) +static inline int _nothing_conv_real_list_after_start( + typval_T *const tv, MPConvStackVal *const mpsv) FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT { - if (tv == NULL) { - return NOTDONE; - } + assert(tv != NULL); tv->v_lock = VAR_UNLOCKED; if (tv->vval.v_list->lv_refcount > 1) { tv->vval.v_list->lv_refcount--; tv->vval.v_list = NULL; + mpsv->data.l.li = NULL; return OK; } return NOTDONE; } -#define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \ +#define TYPVAL_ENCODE_CONV_LIST_START(tv, len) + +#define TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, mpsv) \ do { \ - if (_nothing_conv_list_start(tv) != NOTDONE) { \ + if (_nothing_conv_real_list_after_start(tv, &mpsv) != NOTDONE) { \ goto typval_encode_stop_converting_one_item; \ } \ } while (0) @@ -19253,6 +19255,7 @@ static inline void _nothing_conv_dict_end(typval_T *const tv, #undef TYPVAL_ENCODE_CONV_EMPTY_LIST #undef TYPVAL_ENCODE_CONV_EMPTY_DICT #undef TYPVAL_ENCODE_CONV_LIST_START +#undef TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START #undef TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS #undef TYPVAL_ENCODE_CONV_LIST_END #undef TYPVAL_ENCODE_CONV_DICT_START diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 071fbc3923..48741abea2 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -369,6 +369,8 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, #define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \ ga_append(gap, '[') +#define TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, mpsv) + #define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ ga_concat(gap, "{}") @@ -789,6 +791,7 @@ bool encode_check_json_key(const typval_T *const tv) #undef TYPVAL_ENCODE_CONV_FUNC_END #undef TYPVAL_ENCODE_CONV_EMPTY_LIST #undef TYPVAL_ENCODE_CONV_LIST_START +#undef TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START #undef TYPVAL_ENCODE_CONV_EMPTY_DICT #undef TYPVAL_ENCODE_CONV_NIL #undef TYPVAL_ENCODE_CONV_BOOL @@ -933,6 +936,8 @@ char *encode_tv2json(typval_T *tv, size_t *len) #define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \ msgpack_pack_array(packer, (size_t)(len)) +#define TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, mpsv) + #define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ msgpack_pack_map(packer, 0) @@ -994,6 +999,7 @@ char *encode_tv2json(typval_T *tv, size_t *len) #undef TYPVAL_ENCODE_CONV_FUNC_END #undef TYPVAL_ENCODE_CONV_EMPTY_LIST #undef TYPVAL_ENCODE_CONV_LIST_START +#undef TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START #undef TYPVAL_ENCODE_CONV_EMPTY_DICT #undef TYPVAL_ENCODE_CONV_NIL #undef TYPVAL_ENCODE_CONV_BOOL diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index 3e1170b8fa..bade067ea6 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -129,6 +129,16 @@ /// point to a special dictionary. /// @param len List length. Is an expression which evaluates to an integer. +/// @def TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START +/// @brief Macros used after pushing list onto the stack +/// +/// Only used for real list_T* lists, not for special dictionaries or partial +/// arguments. +/// +/// @param tv Pointer to typval where value is stored. May be NULL. May +/// point to a special dictionary. +/// @param mpsv Pushed MPConvStackVal value. + /// @def TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS /// @brief Macros used after finishing converting non-last list item /// @@ -354,6 +364,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( }, }, })); + TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, _mp_last(*mpstack)); break; } case VAR_SPECIAL: { From 728367a196c8396c8b186f1dbe2cf878a7fec038 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 7 Jan 2017 15:26:34 +0300 Subject: [PATCH 05/11] unittest: Add dict_items function --- src/nvim/eval.c | 2 +- src/nvim/globals.h | 4 ---- src/nvim/hashtab.c | 12 ++++++++++++ src/nvim/hashtab.h | 9 +++++++-- test/unit/eval/helpers.lua | 31 +++++++++++++++++++++++++------ 5 files changed, 45 insertions(+), 13 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index f355b3eac4..a96cc82260 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -6445,7 +6445,7 @@ void dict_free(dict_T *d) { */ dictitem_T *dictitem_alloc(char_u *key) FUNC_ATTR_NONNULL_RET { - dictitem_T *di = xmalloc(sizeof(dictitem_T) + STRLEN(key)); + dictitem_T *di = xmalloc(offsetof(dictitem_T, di_key) + STRLEN(key) + 1); #ifndef __clang_analyzer__ STRCPY(di->di_key, key); #endif diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 872ff8d5b8..2b9abb1cc5 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -409,10 +409,6 @@ EXTERN struct caller_scope { } provider_caller_scope; EXTERN int provider_call_nesting INIT(= 0); -/* Magic number used for hashitem "hi_key" value indicating a deleted item. - * Only the address is used. */ -EXTERN char_u hash_removed; - EXTERN int t_colors INIT(= 256); // int value of T_CCO diff --git a/src/nvim/hashtab.c b/src/nvim/hashtab.c index fa4077f22f..376f33e23e 100644 --- a/src/nvim/hashtab.c +++ b/src/nvim/hashtab.c @@ -36,6 +36,8 @@ # include "hashtab.c.generated.h" #endif +char hash_removed; + /// Initialize an empty hash table. void hash_init(hashtab_T *ht) { @@ -380,3 +382,13 @@ hash_T hash_hash(char_u *key) return hash; } + +/// Function to get HI_KEY_REMOVED value +/// +/// Used for testing because luajit ffi does not allow getting addresses of +/// globals. +const char_u *_hash_key_removed(void) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return HI_KEY_REMOVED; +} diff --git a/src/nvim/hashtab.h b/src/nvim/hashtab.h index 7233d8c47c..0da2b13f2e 100644 --- a/src/nvim/hashtab.h +++ b/src/nvim/hashtab.h @@ -5,14 +5,19 @@ #include "nvim/types.h" +/// Magic number used for hashitem "hi_key" value indicating a deleted item +/// +/// Only the address is used. +extern char hash_removed; + /// Type for hash number (hash calculation result). typedef size_t hash_T; /// The address of "hash_removed" is used as a magic number /// for hi_key to indicate a removed item. -#define HI_KEY_REMOVED &hash_removed +#define HI_KEY_REMOVED ((char_u *)&hash_removed) #define HASHITEM_EMPTY(hi) ((hi)->hi_key == NULL \ - || (hi)->hi_key == &hash_removed) + || (hi)->hi_key == (char_u *)&hash_removed) /// A hastable item. /// diff --git a/test/unit/eval/helpers.lua b/test/unit/eval/helpers.lua index 1feeb5c4f7..c3c27e4fed 100644 --- a/test/unit/eval/helpers.lua +++ b/test/unit/eval/helpers.lua @@ -5,7 +5,8 @@ local to_cstr = helpers.to_cstr local ffi = helpers.ffi local eq = helpers.eq -local eval = cimport('./src/nvim/eval.h', './src/nvim/eval_defs.h') +local eval = cimport('./src/nvim/eval.h', './src/nvim/eval_defs.h', + './src/nvim/hashtab.h') local null_string = {[true]='NULL string'} local null_list = {[true]='NULL list'} @@ -168,7 +169,9 @@ lst2tbl = function(l, processed) return ret end -local function dict_iter(d) +local hi_key_removed = eval._hash_key_removed() + +local function dict_iter(d, return_hi) local init_s = { todo=d.dv_hashtab.ht_used, hi=d.dv_hashtab.ht_array, @@ -176,13 +179,18 @@ local function dict_iter(d) local function f(s, _) if s.todo == 0 then return nil end while s.todo > 0 do - if s.hi.hi_key ~= nil and s.hi ~= eval.hash_removed then + if s.hi.hi_key ~= nil and s.hi.hi_key ~= hi_key_removed then local key = ffi.string(s.hi.hi_key) - local di = ffi.cast('dictitem_T*', - s.hi.hi_key - ffi.offsetof('dictitem_T', 'di_key')) + local ret + if return_hi then + ret = s.hi + else + ret = ffi.cast('dictitem_T*', + s.hi.hi_key - ffi.offsetof('dictitem_T', 'di_key')) + end s.todo = s.todo - 1 s.hi = s.hi + 1 - return key, di + return key, ret end s.hi = s.hi + 1 end @@ -195,6 +203,16 @@ local function first_di(d) return select(2, f(init_s, v)) end +local function dict_items(d) + local ret = {[0]=0} + for k, hi in dict_iter(d) do + ret[k] = hi + ret[0] = ret[0] + 1 + ret[ret[0]] = hi + end + return ret +end + dct2tbl = function(d, processed) if d == nil then return null_dict @@ -395,4 +413,5 @@ return { alloc_logging_helpers=alloc_logging_helpers, list_items=list_items, + dict_items=dict_items, } From 88a4820cc9f6764b285eef239e103e4fc39c5667 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 7 Jan 2017 15:45:27 +0300 Subject: [PATCH 06/11] unittest: Add failing test for freeing dictionaries in a list --- test/unit/eval/tv_clear_spec.lua | 48 ++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/test/unit/eval/tv_clear_spec.lua b/test/unit/eval/tv_clear_spec.lua index d6ef0caa1a..483afaee2b 100644 --- a/test/unit/eval/tv_clear_spec.lua +++ b/test/unit/eval/tv_clear_spec.lua @@ -10,6 +10,7 @@ local a = eval_helpers.alloc_logging_helpers local type_key = eval_helpers.type_key local list_type = eval_helpers.list_type local list_items = eval_helpers.list_items +local dict_items = eval_helpers.dict_items local lua2typvalt = eval_helpers.lua2typvalt local lib = cimport('./src/nvim/eval_defs.h', './src/nvim/eval.h') @@ -76,4 +77,51 @@ describe('clear_tv()', function() a.freed(list_p), }) end) + it('successfully frees all dictionaries in [&d {}, *d]', function() + local d_inner = {} + local list = {d_inner, d_inner} + local list_tv = ffi.gc(lua2typvalt(list), nil) + local list_p = list_tv.vval.v_list + local lis = list_items(list_p) + local dict_inner_p = lis[1].li_tv.vval.v_dict + alloc_log:check({ + a.list(list_p), + a.dict(dict_inner_p), + a.li(lis[1]), + a.li(lis[2]), + }) + eq(2, dict_inner_p.dv_refcount) + lib.clear_tv(list_tv) + alloc_log:check({ + a.freed(dict_inner_p), + a.freed(lis[1]), + a.freed(lis[2]), + a.freed(list_p), + }) + end) + it('successfully frees all dictionaries in [&d {a: 1}, *d]', function() + local d_inner = {a=1} + local list = {d_inner, d_inner} + local list_tv = ffi.gc(lua2typvalt(list), nil) + local list_p = list_tv.vval.v_list + local lis = list_items(list_p) + local dict_inner_p = lis[1].li_tv.vval.v_dict + local dis = dict_items(dict_inner_p) + alloc_log:check({ + a.list(list_p), + a.dict(dict_inner_p), + a.di(dis.a, 1), + a.li(lis[1]), + a.li(lis[2]), + }) + eq(2, dict_inner_p.dv_refcount) + lib.clear_tv(list_tv) + alloc_log:check({ + a.freed(dis.a), + a.freed(dict_inner_p), + a.freed(lis[1]), + a.freed(lis[2]), + a.freed(list_p), + }) + end) end) From 9a09ffa883ea34d07405af9617201958e5a4c861 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 7 Jan 2017 15:54:46 +0300 Subject: [PATCH 07/11] eval: Fix failing test --- src/nvim/api/private/helpers.c | 3 +++ src/nvim/eval.c | 18 +++++++++++------- src/nvim/eval/encode.c | 6 ++++++ src/nvim/eval/typval_encode.c.h | 15 +++++++++++++++ 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 4eff21bc26..7daa4d7207 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -429,6 +429,8 @@ static inline void typval_encode_dict_start(EncodedData *const edata, #define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) \ typval_encode_dict_start(edata, (size_t)(len)) +#define TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, dict, mpsv) + #define TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(label, kv_pair) static inline void typval_encode_after_key(EncodedData *const edata) @@ -507,6 +509,7 @@ static inline void typval_encode_dict_end(EncodedData *const edata) #undef TYPVAL_ENCODE_CONV_BOOL #undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER #undef TYPVAL_ENCODE_CONV_DICT_START +#undef TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START #undef TYPVAL_ENCODE_CONV_DICT_END #undef TYPVAL_ENCODE_CONV_DICT_AFTER_KEY #undef TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS diff --git a/src/nvim/eval.c b/src/nvim/eval.c index a96cc82260..b7a30a4c12 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -19185,9 +19185,9 @@ static inline void _nothing_conv_list_end(typval_T *const tv) } #define TYPVAL_ENCODE_CONV_LIST_END(tv) _nothing_conv_list_end(tv) -static inline int _nothing_conv_dict_start(typval_T *const tv, - dict_T **const dictp, - const void *const nodictvar) +static inline int _nothing_conv_real_dict_after_start( + typval_T *const tv, dict_T **const dictp, const void *const nodictvar, + MPConvStackVal *const mpsv) FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT { if (tv != NULL) { @@ -19196,15 +19196,18 @@ static inline int _nothing_conv_dict_start(typval_T *const tv, if ((const void *)dictp != nodictvar && (*dictp)->dv_refcount > 1) { (*dictp)->dv_refcount--; *dictp = NULL; + mpsv->data.d.todo = 0; return OK; } return NOTDONE; } -#define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) \ +#define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) + +#define TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, dict, mpsv) \ do { \ - if (_nothing_conv_dict_start(tv, (dict_T **)&dict, \ - (void *)&TYPVAL_ENCODE_NODICT_VAR) \ - != NOTDONE) { \ + if (_nothing_conv_real_dict_after_start( \ + tv, (dict_T **)&dict, (void *)&TYPVAL_ENCODE_NODICT_VAR, \ + &mpsv) != NOTDONE) { \ goto typval_encode_stop_converting_one_item; \ } \ } while (0) @@ -19259,6 +19262,7 @@ static inline void _nothing_conv_dict_end(typval_T *const tv, #undef TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS #undef TYPVAL_ENCODE_CONV_LIST_END #undef TYPVAL_ENCODE_CONV_DICT_START +#undef TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START #undef TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK #undef TYPVAL_ENCODE_CONV_DICT_AFTER_KEY #undef TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 48741abea2..ee66b7cf09 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -385,6 +385,8 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, #define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) \ ga_append(gap, '{') +#define TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, dict, mpsv) + #define TYPVAL_ENCODE_CONV_DICT_END(tv, dict) \ ga_append(gap, '}') @@ -797,6 +799,7 @@ bool encode_check_json_key(const typval_T *const tv) #undef TYPVAL_ENCODE_CONV_BOOL #undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER #undef TYPVAL_ENCODE_CONV_DICT_START +#undef TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START #undef TYPVAL_ENCODE_CONV_DICT_END #undef TYPVAL_ENCODE_CONV_DICT_AFTER_KEY #undef TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS @@ -959,6 +962,8 @@ char *encode_tv2json(typval_T *tv, size_t *len) #define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) \ msgpack_pack_map(packer, (size_t)(len)) +#define TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, dict, mpsv) + #define TYPVAL_ENCODE_CONV_DICT_END(tv, dict) #define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(tv, dict) @@ -1005,6 +1010,7 @@ char *encode_tv2json(typval_T *tv, size_t *len) #undef TYPVAL_ENCODE_CONV_BOOL #undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER #undef TYPVAL_ENCODE_CONV_DICT_START +#undef TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START #undef TYPVAL_ENCODE_CONV_DICT_END #undef TYPVAL_ENCODE_CONV_DICT_AFTER_KEY #undef TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index bade067ea6..365eb2dd77 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -152,6 +152,9 @@ /// @def TYPVAL_ENCODE_CONV_DICT_START /// @brief Macros used before starting to convert non-empty dictionary /// +/// Only used for real dict_T* dictionaries, not for special dictionaries. Also +/// used for partial self dictionary. +/// /// @param tv Pointer to typval where dictionary is stored. May be NULL. May /// point to a special dictionary. /// @param dict Converted dictionary, lvalue or #TYPVAL_ENCODE_NODICT_VAR @@ -159,6 +162,14 @@ /// @param len Dictionary length. Is an expression which evaluates to an /// integer. +/// @def TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START +/// @brief Macros used after pushing dictionary onto the stack +/// +/// @param tv Pointer to typval where dictionary is stored. May be NULL. +/// May not point to a special dictionary. +/// @param dict Converted dictionary, lvalue. +/// @param mpsv Pushed MPConvStackVal value. + /// @def TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK /// @brief Macros used to check special dictionary key /// @@ -575,6 +586,8 @@ _convert_one_value_regular_dict: }, }, })); + TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, tv->vval.v_dict, + _mp_last(*mpstack)); break; } case VAR_UNKNOWN: { @@ -743,6 +756,8 @@ typval_encode_stop_converting_one_item: }, }, })); + TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(NULL, pt->pt_dict, + _mp_last(mpstack)); } else { TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, -1); } From 35416e89bcef1de97881a14dc2d2408144bd8a7f Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 7 Jan 2017 17:52:53 +0300 Subject: [PATCH 08/11] memory: Restore entered_free_all_mem functionality --- src/nvim/eval.c | 7 +++++-- src/nvim/globals.h | 4 ---- src/nvim/memory.c | 15 ++++++++++----- src/nvim/memory.h | 4 ++++ 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index b7a30a4c12..a046b2a288 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -21665,9 +21665,12 @@ void func_unref(char_u *name) fp = find_func(name); if (fp == NULL) { #ifdef EXITFREE - if (!entered_free_all_mem) // NOLINT(readability/braces) -#endif + if (!entered_free_all_mem) { EMSG2(_(e_intern2), "func_unref()"); + } +#else + EMSG2(_(e_intern2), "func_unref()"); +#endif } else { user_func_unref(fp); } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 2b9abb1cc5..5ee04ad982 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -632,10 +632,6 @@ EXTERN int exiting INIT(= FALSE); /* TRUE when planning to exit Vim. Might * still keep on running if there is a changed * buffer. */ -#if defined(EXITFREE) -// true when in or after free_all_mem() -EXTERN bool entered_free_all_mem INIT(= false); -#endif // volatile because it is used in signal handler deathtrap(). EXTERN volatile int full_screen INIT(= false); // TRUE when doing full-screen output diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 3c0d001848..a5045dfffc 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -48,6 +48,11 @@ MemRealloc mem_realloc = &realloc; # include "memory.c.generated.h" #endif +#ifdef EXITFREE +/// Indicates that free_all_mem function was or is running +bool entered_free_all_mem = false; +#endif + /// Try to free memory. Used when trying to recover from out of memory errors. /// @see {xmalloc} void try_to_free_memory(void) @@ -511,13 +516,13 @@ void time_to_bytes(time_t time_, uint8_t buf[8]) void free_all_mem(void) { buf_T *buf, *nextbuf; - static bool entered = false; - /* When we cause a crash here it is caught and Vim tries to exit cleanly. - * Don't try freeing everything again. */ - if (entered) + // When we cause a crash here it is caught and Vim tries to exit cleanly. + // Don't try freeing everything again. + if (entered_free_all_mem) { return; - entered = true; + } + entered_free_all_mem = true; // Don't want to trigger autocommands from here on. block_autocmds(); diff --git a/src/nvim/memory.h b/src/nvim/memory.h index 0b422957d5..d82972cffc 100644 --- a/src/nvim/memory.h +++ b/src/nvim/memory.h @@ -15,6 +15,10 @@ extern MemFree mem_free; extern MemCalloc mem_calloc; extern MemRealloc mem_realloc; +#ifdef EXITFREE +extern bool entered_free_all_mem; +#endif + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "memory.h.generated.h" #endif From 6f267b3968d54f8bcd41d584a71bef9020ff773e Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 7 Jan 2017 17:58:29 +0300 Subject: [PATCH 09/11] memory: Document new additions to memory.h --- src/nvim/memory.c | 1 - src/nvim/memory.h | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/nvim/memory.c b/src/nvim/memory.c index a5045dfffc..92ead873ae 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -49,7 +49,6 @@ MemRealloc mem_realloc = &realloc; #endif #ifdef EXITFREE -/// Indicates that free_all_mem function was or is running bool entered_free_all_mem = false; #endif diff --git a/src/nvim/memory.h b/src/nvim/memory.h index d82972cffc..f65947d449 100644 --- a/src/nvim/memory.h +++ b/src/nvim/memory.h @@ -5,17 +5,34 @@ #include // for size_t #include // for time_t +/// `malloc()` function signature typedef void *(*MemMalloc)(size_t); + +/// `free()` function signature typedef void (*MemFree)(void *); + +/// `calloc()` function signature typedef void *(*MemCalloc)(size_t, size_t); + +/// `realloc()` function signature typedef void *(*MemRealloc)(void *, size_t); +#ifdef UNIT_TESTING +/// When unit testing: pointer to the `malloc()` function, may be altered extern MemMalloc mem_malloc; + +/// When unit testing: pointer to the `free()` function, may be altered extern MemFree mem_free; + +/// When unit testing: pointer to the `calloc()` function, may be altered extern MemCalloc mem_calloc; + +/// When unit testing: pointer to the `realloc()` function, may be altered extern MemRealloc mem_realloc; +#endif #ifdef EXITFREE +/// Indicates that free_all_mem function was or is running extern bool entered_free_all_mem; #endif From dea4bb33dc13a65e303b77ccda28601ebc246b85 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 7 Jan 2017 19:07:32 +0300 Subject: [PATCH 10/11] unittest,memory: Fix tests --- src/nvim/memory.h | 3 ++- test/unit/preprocess.lua | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/nvim/memory.h b/src/nvim/memory.h index f65947d449..250ac3e08f 100644 --- a/src/nvim/memory.h +++ b/src/nvim/memory.h @@ -1,9 +1,10 @@ #ifndef NVIM_MEMORY_H #define NVIM_MEMORY_H +#include // for bool #include // for uint8_t #include // for size_t -#include // for time_t +#include // for time_t /// `malloc()` function signature typedef void *(*MemMalloc)(size_t); diff --git a/test/unit/preprocess.lua b/test/unit/preprocess.lua index 8c2a5c73e5..1c9b290462 100644 --- a/test/unit/preprocess.lua +++ b/test/unit/preprocess.lua @@ -123,6 +123,7 @@ function Gcc:init_defines() self:define('INIT', {'...'}, '') self:define('_GNU_SOURCE') self:define('INCLUDE_GENERATED_DECLARATIONS') + self:define('UNIT_TESTING') -- Needed for FreeBSD self:define('_Thread_local', nil, '') -- Needed for macOS Sierra From 3967618fa524b3840649887661584de27f7daa87 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 7 Jan 2017 19:12:18 +0300 Subject: [PATCH 11/11] unittest: Fix linter errors --- test/unit/eval/tv_clear_spec.lua | 2 +- test/unit/helpers.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/eval/tv_clear_spec.lua b/test/unit/eval/tv_clear_spec.lua index 483afaee2b..96eccdbd71 100644 --- a/test/unit/eval/tv_clear_spec.lua +++ b/test/unit/eval/tv_clear_spec.lua @@ -15,7 +15,7 @@ local lua2typvalt = eval_helpers.lua2typvalt local lib = cimport('./src/nvim/eval_defs.h', './src/nvim/eval.h') -local alloc_log = alloc_log_new(eq) +local alloc_log = alloc_log_new() before_each(function() alloc_log:before_each() diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index 58a04e9c43..1bfdd32739 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -124,7 +124,7 @@ local function cppimport(path) return cimport(Paths.test_include_path .. '/' .. path) end -local function alloc_log_new(eq) +local function alloc_log_new() local log = { log={}, lib=cimport('./src/nvim/memory.h'),