From c5c75513b81398f05a4e63b2f7207ae74de25ecc Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 25 Dec 2016 19:37:13 +0300 Subject: [PATCH 01/28] eval/typval_encode: Make partial conversions not recursive Is known to crash in the current state. Ref #5825. --- src/nvim/api/private/helpers.c | 13 ++-- src/nvim/eval.c | 30 ++++---- src/nvim/eval/encode.c | 131 +++++++++++++++------------------ src/nvim/eval/typval_encode.h | 131 +++++++++++++++++++++++++++++++-- 4 files changed, 207 insertions(+), 98 deletions(-) diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index b004cfc7a1..0d1d88055f 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -357,11 +357,12 @@ void set_option_to(void *to, int type, String name, Object value, Error *err) #define TYPVAL_ENCODE_CONV_EXT_STRING(str, len, type) \ TYPVAL_ENCODE_CONV_NIL() -#define TYPVAL_ENCODE_CONV_FUNC(fun) \ +#define TYPVAL_ENCODE_CONV_FUNC_START(fun, is_partial, pt) \ TYPVAL_ENCODE_CONV_NIL() -#define TYPVAL_ENCODE_CONV_PARTIAL(partial) \ - TYPVAL_ENCODE_CONV_NIL() +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(len) +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(len) +#define TYPVAL_ENCODE_CONV_FUNC_END() #define TYPVAL_ENCODE_CONV_EMPTY_LIST() \ kv_push(edata->stack, ARRAY_OBJ(((Array) { .capacity = 0, .size = 0 }))) @@ -484,8 +485,10 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, object, EncodedData *const, edata) #undef TYPVAL_ENCODE_CONV_EXT_STRING #undef TYPVAL_ENCODE_CONV_NUMBER #undef TYPVAL_ENCODE_CONV_FLOAT -#undef TYPVAL_ENCODE_CONV_FUNC -#undef TYPVAL_ENCODE_CONV_PARTIAL +#undef TYPVAL_ENCODE_CONV_FUNC_START +#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS +#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF +#undef TYPVAL_ENCODE_CONV_FUNC_END #undef TYPVAL_ENCODE_CONV_EMPTY_LIST #undef TYPVAL_ENCODE_CONV_LIST_START #undef TYPVAL_ENCODE_CONV_EMPTY_DICT diff --git a/src/nvim/eval.c b/src/nvim/eval.c index ea42a58cdd..dbc279685a 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -19070,22 +19070,24 @@ void free_tv(typval_T *varp) #define TYPVAL_ENCODE_CONV_EXT_STRING(ignored1, ignored2, ignored3) -#define TYPVAL_ENCODE_CONV_FUNC(fun) \ +#define TYPVAL_ENCODE_CONV_FUNC_START(fun, is_partial, pt) \ do { \ - func_unref(fun); \ - if (fun != empty_string) { \ - xfree(fun); \ + if (is_partial) { \ + partial_unref(pt); \ + tv->vval.v_partial = NULL; \ + } else { \ + func_unref(fun); \ + if (fun != empty_string) { \ + xfree(fun); \ + } \ + tv->vval.v_string = NULL; \ } \ - tv->vval.v_string = NULL; \ tv->v_lock = VAR_UNLOCKED; \ } while (0) -#define TYPVAL_ENCODE_CONV_PARTIAL(pt) \ - do { \ - partial_unref(pt); \ - pt = NULL; \ - tv->v_lock = VAR_UNLOCKED; \ - } while (0) +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(len) +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(len) +#define TYPVAL_ENCODE_CONV_FUNC_END() #define TYPVAL_ENCODE_CONV_EMPTY_LIST() \ do { \ @@ -19162,8 +19164,10 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, nothing, void *, ignored) #undef TYPVAL_ENCODE_CONV_STRING #undef TYPVAL_ENCODE_CONV_STR_STRING #undef TYPVAL_ENCODE_CONV_EXT_STRING -#undef TYPVAL_ENCODE_CONV_FUNC -#undef TYPVAL_ENCODE_CONV_PARTIAL +#undef TYPVAL_ENCODE_CONV_FUNC_START +#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS +#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF +#undef TYPVAL_ENCODE_CONV_FUNC_END #undef TYPVAL_ENCODE_CONV_EMPTY_LIST #undef TYPVAL_ENCODE_CONV_EMPTY_DICT #undef TYPVAL_ENCODE_CONV_LIST_START diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 5af4893975..7e59998bf0 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -108,9 +108,12 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack, { garray_T msg_ga; ga_init(&msg_ga, (int)sizeof(char), 80); - char *const key_msg = _("key %s"); - char *const key_pair_msg = _("key %s at index %i from special map"); - char *const idx_msg = _("index %i"); + const char *const key_msg = _("key %s"); + const char *const key_pair_msg = _("key %s at index %i from special map"); + const char *const idx_msg = _("index %i"); + const char *const partial_arg_msg = _("partial"); + const char *const partial_arg_i_msg = _("argument %i"); + const char *const partial_self_msg = _("partial self dictionary"); for (size_t i = 0; i < kv_size(*mpstack); i++) { if (i != 0) { ga_concat(&msg_ga, ", "); @@ -154,6 +157,29 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack, } break; } + case kMPConvPartial: { + switch (v.data.p.stage) { + case kMPConvPartialArgs: { + assert(false); + break; + } + case kMPConvPartialSelf: { + ga_concat(&msg_ga, partial_arg_msg); + break; + } + case kMPConvPartialEnd: { + ga_concat(&msg_ga, partial_self_msg); + break; + } + } + break; + } + case kMPConvPartialList: { + const int idx = (int)(v.data.a.arg - v.data.a.argv) - 1; + vim_snprintf((char *)IObuff, IOSIZE, partial_arg_i_msg, idx); + ga_concat(&msg_ga, IObuff); + break; + } } } EMSG3(msg, objname, (kv_size(*mpstack) == 0 @@ -308,67 +334,29 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, } \ } while (0) -#define TYPVAL_ENCODE_CONV_FUNC(fun) \ +#define TYPVAL_ENCODE_CONV_FUNC_START(fun, is_partial, pt) \ do { \ ga_concat(gap, "function("); \ TYPVAL_ENCODE_CONV_STRING(fun, STRLEN(fun)); \ - ga_append(gap, ')'); \ } while (0) -#define TYPVAL_ENCODE_CONV_PARTIAL(pt) \ +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(len) \ do { \ - int i; \ - ga_concat(gap, "function("); \ - if (pt->pt_name != NULL) { \ - size_t len; \ - char_u *p; \ - len = 3; \ - len += STRLEN(pt->pt_name); \ - for (p = pt->pt_name; *p != NUL; mb_ptr_adv(p)) { \ - if (*p == '\'') { \ - len++; \ - } \ - } \ - char_u *r, *s; \ - s = r = xmalloc(len); \ - if (r != NULL) { \ - *r++ = '\''; \ - for (p = pt->pt_name; *p != NUL; ) { \ - if (*p == '\'') { \ - *r++ = '\''; \ - } \ - MB_COPY_CHAR(p, r); \ - } \ - *r++ = '\''; \ - *r++ = NUL; \ - } \ - ga_concat(gap, s); \ - xfree(s); \ - } \ - if (pt->pt_argc > 0) { \ - ga_concat(gap, ", ["); \ - for (i = 0; i < pt->pt_argc; i++) { \ - if (i > 0) { \ - ga_concat(gap, ", "); \ - } \ - char *tofree = encode_tv2string(&pt->pt_argv[i], NULL); \ - ga_concat(gap, tofree); \ - xfree(tofree); \ - } \ - ga_append(gap, ']'); \ - } \ - if (pt->pt_dict != NULL) { \ - typval_T dtv; \ - ga_concat(gap, ", "); \ - dtv.v_type = VAR_DICT; \ - dtv.vval.v_dict = pt->pt_dict; \ - char *tofree = encode_tv2string(&dtv, NULL); \ - ga_concat(gap, tofree); \ - xfree(tofree); \ - } \ - ga_append(gap, ')'); \ + if (len != 0) { \ + ga_concat(gap, ", "); \ + } \ } while (0) +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(len) \ + do { \ + if ((ptrdiff_t)len != -1) { \ + ga_concat(gap, ", "); \ + } \ + } while (0) + +#define TYPVAL_ENCODE_CONV_FUNC_END() \ + ga_append(gap, ')') + #define TYPVAL_ENCODE_CONV_EMPTY_LIST() \ ga_concat(gap, "[]") @@ -709,18 +697,12 @@ static inline int convert_to_json_string(garray_T *const gap, return FAIL; \ } while (0) -#undef TYPVAL_ENCODE_CONV_FUNC -#define TYPVAL_ENCODE_CONV_FUNC(fun) \ +#undef TYPVAL_ENCODE_CONV_FUNC_START +#define TYPVAL_ENCODE_CONV_FUNC_START(fun, is_partial, pt) \ return conv_error(_("E474: Error while dumping %s, %s: " \ "attempt to dump function reference"), \ mpstack, objname) -#undef TYPVAL_ENCODE_CONV_PARTIAL -#define TYPVAL_ENCODE_CONV_PARTIAL(pt) \ - return conv_error(_("E474: Error while dumping %s, %s: " \ - "attempt to dump partial"), \ - mpstack, objname) - /// Check whether given key can be used in json_encode() /// /// @param[in] tv Key to check. @@ -777,8 +759,10 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, json, garray_T *const, gap) #undef TYPVAL_ENCODE_CONV_EXT_STRING #undef TYPVAL_ENCODE_CONV_NUMBER #undef TYPVAL_ENCODE_CONV_FLOAT -#undef TYPVAL_ENCODE_CONV_FUNC -#undef TYPVAL_ENCODE_CONV_PARTIAL +#undef TYPVAL_ENCODE_CONV_FUNC_START +#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS +#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF +#undef TYPVAL_ENCODE_CONV_FUNC_END #undef TYPVAL_ENCODE_CONV_EMPTY_LIST #undef TYPVAL_ENCODE_CONV_LIST_START #undef TYPVAL_ENCODE_CONV_EMPTY_DICT @@ -902,15 +886,14 @@ char *encode_tv2json(typval_T *tv, size_t *len) #define TYPVAL_ENCODE_CONV_FLOAT(flt) \ msgpack_pack_double(packer, (double) (flt)) -#define TYPVAL_ENCODE_CONV_FUNC(fun) \ +#define TYPVAL_ENCODE_CONV_FUNC_START(fun, is_partial, pt) \ return conv_error(_("E5004: Error while dumping %s, %s: " \ "attempt to dump function reference"), \ mpstack, objname) -#define TYPVAL_ENCODE_CONV_PARTIAL(partial) \ - return conv_error(_("E5004: Error while dumping %s, %s: " \ - "attempt to dump partial"), \ - mpstack, objname) +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(len) +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(len) +#define TYPVAL_ENCODE_CONV_FUNC_END() #define TYPVAL_ENCODE_CONV_EMPTY_LIST() \ msgpack_pack_array(packer, 0) @@ -967,8 +950,10 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(, msgpack, msgpack_packer *const, packer) #undef TYPVAL_ENCODE_CONV_EXT_STRING #undef TYPVAL_ENCODE_CONV_NUMBER #undef TYPVAL_ENCODE_CONV_FLOAT -#undef TYPVAL_ENCODE_CONV_FUNC -#undef TYPVAL_ENCODE_CONV_PARTIAL +#undef TYPVAL_ENCODE_CONV_FUNC_START +#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS +#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF +#undef TYPVAL_ENCODE_CONV_FUNC_END #undef TYPVAL_ENCODE_CONV_EMPTY_LIST #undef TYPVAL_ENCODE_CONV_LIST_START #undef TYPVAL_ENCODE_CONV_EMPTY_DICT diff --git a/src/nvim/eval/typval_encode.h b/src/nvim/eval/typval_encode.h index b79158b30c..149dd22ff7 100644 --- a/src/nvim/eval/typval_encode.h +++ b/src/nvim/eval/typval_encode.h @@ -69,10 +69,30 @@ /// /// @param fun Function name. -/// @def TYPVAL_ENCODE_CONV_PARTIAL -/// @brief Macros used to convert a partial +/// @def TYPVAL_ENCODE_CONV_FUNC_START +/// @brief Macros used when starting to convert a funcref or a partial /// -/// @param pt Partial name. +/// @param fun Function name. +/// @param is_partial True if converted function is a partial. +/// @param pt Pointer to partial or NULL. + +/// @def TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS +/// @brief Macros used before starting to convert partial arguments +/// +/// @param len Number of arguments. Zero for absent arguments or when +/// converting a funcref. + +/// @def TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF +/// @brief Macros used before starting to convert self dictionary +/// +/// @param len Number of arguments. May be zero for empty dictionary or -1 for +/// missing self dictionary, also when converting function +/// reference. + +/// @def TYPVAL_ENCODE_CONV_FUNC_END +/// @brief Macros used after converting a funcref or a partial +/// +/// Accepts no arguments, but still must be a function-like macros. /// @def TYPVAL_ENCODE_CONV_EMPTY_LIST /// @brief Macros used to convert an empty list @@ -151,11 +171,20 @@ /// Type of the stack entry typedef enum { - kMPConvDict, ///< Convert dict_T *dictionary. - kMPConvList, ///< Convert list_T *list. + kMPConvDict, ///< Convert dict_T *dictionary. + kMPConvList, ///< Convert list_T *list. kMPConvPairs, ///< Convert mapping represented as a list_T* of pairs. + kMPConvPartial, ///< Convert partial_T* partial. + kMPConvPartialList, ///< Convert argc/argv pair coming from a partial. } MPConvStackValType; +/// Stage at which partial is being converted +typedef enum { + kMPConvPartialArgs, ///< About to convert arguments. + kMPConvPartialSelf, ///< About to convert self dictionary. + kMPConvPartialEnd, ///< Already converted everything. +} MPConvPartialStage; + /// Structure representing current VimL to messagepack conversion state typedef struct { MPConvStackValType type; ///< Type of the stack entry. @@ -170,6 +199,15 @@ typedef struct { list_T *list; ///< Currently converted list. listitem_T *li; ///< Currently converted list item. } l; ///< State of list or generic mapping conversion. + struct { + MPConvPartialStage stage; ///< Stage at which partial is being converted. + partial_T *pt; ///< Currently converted partial. + } p; ///< State of partial conversion. + struct { + typval_T *arg; ///< Currently converted argument. + typval_T *argv; ///< Start of the argument list. + size_t todo; ///< Number of items left to process. + } a; ///< State of list or generic mapping conversion. } data; ///< Data to convert. } MPConvStackVal; @@ -250,11 +288,26 @@ static int name##_convert_one_value(firstargtype firstargname, \ break; \ } \ case VAR_FUNC: { \ - TYPVAL_ENCODE_CONV_FUNC(tv->vval.v_string); \ + TYPVAL_ENCODE_CONV_FUNC_START(tv->vval.v_string, false, NULL); \ + TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(0); \ + TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(-1); \ + TYPVAL_ENCODE_CONV_FUNC_END(); \ break; \ } \ case VAR_PARTIAL: { \ - TYPVAL_ENCODE_CONV_PARTIAL(tv->vval.v_partial); \ + partial_T *const pt = tv->vval.v_partial; \ + (void)pt; \ + TYPVAL_ENCODE_CONV_FUNC_START(pt->pt_name, true, pt); \ + _mp_push(*mpstack, ((MPConvStackVal) { \ + .type = kMPConvPartial, \ + .tv = tv, \ + .data = { \ + .p = { \ + .stage = kMPConvPartialArgs, \ + .pt = tv->vval.v_partial, \ + }, \ + }, \ + })); \ break; \ } \ case VAR_LIST: { \ @@ -562,6 +615,70 @@ scope int encode_vim_to_##name(firstargtype firstargname, typval_T *const tv, \ cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; \ break; \ } \ + case kMPConvPartial: { \ + partial_T *const pt = cur_mpsv->data.p.pt; \ + switch (cur_mpsv->data.p.stage) { \ + case kMPConvPartialArgs: { \ + TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(pt->pt_argc); \ + cur_mpsv->data.p.stage = kMPConvPartialSelf; \ + if (pt->pt_argc > 0) { \ + TYPVAL_ENCODE_CONV_LIST_START(pt->pt_argc); \ + _mp_push(mpstack, ((MPConvStackVal) { \ + .type = kMPConvPartialList, \ + .tv = tv, \ + .data = { \ + .a = { \ + .arg = pt->pt_argv, \ + .argv = pt->pt_argv, \ + .todo = (size_t)pt->pt_argc, \ + }, \ + }, \ + })); \ + } \ + break; \ + } \ + case kMPConvPartialSelf: { \ + cur_mpsv->data.p.stage = kMPConvPartialEnd; \ + dict_T *const dict = pt->pt_dict; \ + if (dict != NULL) { \ + TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(dict->dv_hashtab.ht_used); \ + TYPVAL_ENCODE_CONV_DICT_START(dict->dv_hashtab.ht_used); \ + _mp_push(mpstack, ((MPConvStackVal) { \ + .type = kMPConvDict, \ + .tv = tv, \ + .data = { \ + .d = { \ + .dict = dict, \ + .hi = dict->dv_hashtab.ht_array, \ + .todo = dict->dv_hashtab.ht_used, \ + }, \ + }, \ + })); \ + } else { \ + TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(-1); \ + } \ + break; \ + } \ + case kMPConvPartialEnd: { \ + TYPVAL_ENCODE_CONV_FUNC_END(); \ + (void) _mp_pop(mpstack); \ + break; \ + } \ + } \ + continue; \ + } \ + case kMPConvPartialList: { \ + if (!cur_mpsv->data.a.todo) { \ + (void) _mp_pop(mpstack); \ + TYPVAL_ENCODE_CONV_LIST_END(); \ + continue; \ + } else if (cur_mpsv->data.a.argv != cur_mpsv->data.a.arg) { \ + TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(); \ + } \ + cur_tv = cur_mpsv->data.a.arg++; \ + cur_mpsv->data.a.todo--; \ + break; \ + } \ } \ assert(cur_tv != NULL); \ if (name##_convert_one_value(firstargname, &mpstack, cur_tv, copyID, \ From b3163d06b340b95ca85421cff8a64c5bc1935f5e Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 25 Dec 2016 20:54:06 +0300 Subject: [PATCH 02/28] eval/typval_encode: Refactor big-big macros into .c.h file This makes gdb backtraces much more meaningful: specifically I now know at which line it crashes in place of seeing that it crashes at TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS macros invocation. --- src/nvim/api/private/helpers.c | 17 +- src/nvim/eval.c | 15 +- src/nvim/eval/encode.c | 58 ++- src/nvim/eval/typval_encode.c.h | 656 ++++++++++++++++++++++++++++++++ src/nvim/eval/typval_encode.h | 489 ++---------------------- 5 files changed, 763 insertions(+), 472 deletions(-) create mode 100644 src/nvim/eval/typval_encode.c.h diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 0d1d88055f..ceb950c489 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -19,7 +19,6 @@ #include "nvim/option.h" #include "nvim/option_defs.h" #include "nvim/version.h" -#include "nvim/eval/typval_encode.h" #include "nvim/lib/kvec.h" /// Helper structure for vim_to_object @@ -476,9 +475,17 @@ static inline void typval_encode_dict_end(EncodedData *const edata) #define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \ TYPVAL_ENCODE_CONV_NIL() -// object_convert_one_value() +#define TYPVAL_ENCODE_SCOPE static +#define TYPVAL_ENCODE_NAME object +#define TYPVAL_ENCODE_FIRST_ARG_TYPE EncodedData *const +#define TYPVAL_ENCODE_FIRST_ARG_NAME edata +// _object_convert_one_value() // encode_vim_to_object() -TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, object, EncodedData *const, edata) +#include "nvim/eval/typval_encode.c.h" +#undef TYPVAL_ENCODE_SCOPE +#undef TYPVAL_ENCODE_NAME +#undef TYPVAL_ENCODE_FIRST_ARG_TYPE +#undef TYPVAL_ENCODE_FIRST_ARG_NAME #undef TYPVAL_ENCODE_CONV_STRING #undef TYPVAL_ENCODE_CONV_STR_STRING @@ -513,7 +520,9 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, object, EncodedData *const, edata) Object vim_to_object(typval_T *obj) { EncodedData edata = { .stack = KV_INITIAL_VALUE }; - encode_vim_to_object(&edata, obj, "vim_to_object argument"); + const int evo_ret = encode_vim_to_object(&edata, obj, + "vim_to_object argument"); + assert(evo_ret == OK); Object ret = kv_A(edata.stack, 0); assert(kv_size(edata.stack) == 1); kv_destroy(edata.stack); diff --git a/src/nvim/eval.c b/src/nvim/eval.c index dbc279685a..8f66976f1c 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -19151,9 +19151,17 @@ void free_tv(typval_T *varp) #define TYPVAL_ENCODE_CONV_RECURSE(ignored1, ignored2) -// nothing_convert_one_value() +#define TYPVAL_ENCODE_SCOPE static +#define TYPVAL_ENCODE_NAME nothing +#define TYPVAL_ENCODE_FIRST_ARG_TYPE const void *const +#define TYPVAL_ENCODE_FIRST_ARG_NAME ignored +// _nothing_convert_one_value() // encode_vim_to_nothing() -TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, nothing, void *, ignored) +#include "nvim/eval/typval_encode.c.h" +#undef TYPVAL_ENCODE_SCOPE +#undef TYPVAL_ENCODE_NAME +#undef TYPVAL_ENCODE_FIRST_ARG_TYPE +#undef TYPVAL_ENCODE_FIRST_ARG_NAME #undef TYPVAL_ENCODE_ALLOW_SPECIALS #undef TYPVAL_ENCODE_CONV_NIL @@ -19186,7 +19194,8 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, nothing, void *, ignored) void clear_tv(typval_T *varp) { if (varp != NULL && varp->v_type != VAR_UNKNOWN) { - encode_vim_to_nothing(varp, varp, "clear_tv argument"); + const int evn_ret = encode_vim_to_nothing(varp, varp, "clear_tv argument"); + assert(evn_ret == OK); } } diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 7e59998bf0..38f0863195 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -425,9 +425,17 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, #define TYPVAL_ENCODE_ALLOW_SPECIALS false -// string_convert_one_value() +#define TYPVAL_ENCODE_SCOPE static +#define TYPVAL_ENCODE_NAME string +#define TYPVAL_ENCODE_FIRST_ARG_TYPE garray_T *const +#define TYPVAL_ENCODE_FIRST_ARG_NAME gap +// _string_convert_one_value() // encode_vim_to_string() -TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, string, garray_T *const, gap) +#include "nvim/eval/typval_encode.c.h" +#undef TYPVAL_ENCODE_SCOPE +#undef TYPVAL_ENCODE_NAME +#undef TYPVAL_ENCODE_FIRST_ARG_TYPE +#undef TYPVAL_ENCODE_FIRST_ARG_NAME #undef TYPVAL_ENCODE_CONV_RECURSE #define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \ @@ -457,9 +465,17 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, string, garray_T *const, gap) return OK; \ } while (0) -// echo_convert_one_value() +#define TYPVAL_ENCODE_SCOPE +#define TYPVAL_ENCODE_NAME echo +#define TYPVAL_ENCODE_FIRST_ARG_TYPE garray_T *const +#define TYPVAL_ENCODE_FIRST_ARG_NAME gap +// _echo_convert_one_value() // encode_vim_to_echo() -TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(, echo, garray_T *const, gap) +#include "nvim/eval/typval_encode.c.h" +#undef TYPVAL_ENCODE_SCOPE +#undef TYPVAL_ENCODE_NAME +#undef TYPVAL_ENCODE_FIRST_ARG_TYPE +#undef TYPVAL_ENCODE_FIRST_ARG_NAME #undef TYPVAL_ENCODE_CONV_RECURSE #define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \ @@ -750,9 +766,17 @@ bool encode_check_json_key(const typval_T *const tv) } \ } while (0) -// json_convert_one_value() +#define TYPVAL_ENCODE_SCOPE static +#define TYPVAL_ENCODE_NAME json +#define TYPVAL_ENCODE_FIRST_ARG_TYPE garray_T *const +#define TYPVAL_ENCODE_FIRST_ARG_NAME gap +// _json_convert_one_value() // encode_vim_to_json() -TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, json, garray_T *const, gap) +#include "nvim/eval/typval_encode.c.h" +#undef TYPVAL_ENCODE_SCOPE +#undef TYPVAL_ENCODE_NAME +#undef TYPVAL_ENCODE_FIRST_ARG_TYPE +#undef TYPVAL_ENCODE_FIRST_ARG_NAME #undef TYPVAL_ENCODE_CONV_STRING #undef TYPVAL_ENCODE_CONV_STR_STRING @@ -791,7 +815,9 @@ char *encode_tv2string(typval_T *tv, size_t *len) { garray_T ga; ga_init(&ga, (int)sizeof(char), 80); - encode_vim_to_string(&ga, tv, "encode_tv2string() argument"); + const int evs_ret = encode_vim_to_string(&ga, tv, + "encode_tv2string() argument"); + (void)evs_ret; did_echo_string_emsg = false; if (len != NULL) { *len = (size_t) ga.ga_len; @@ -817,7 +843,8 @@ char *encode_tv2echo(typval_T *tv, size_t *len) ga_concat(&ga, tv->vval.v_string); } } else { - encode_vim_to_echo(&ga, tv, ":echo argument"); + const int eve_ret = encode_vim_to_echo(&ga, tv, ":echo argument"); + (void)eve_ret; } if (len != NULL) { *len = (size_t) ga.ga_len; @@ -838,7 +865,8 @@ char *encode_tv2json(typval_T *tv, size_t *len) { garray_T ga; ga_init(&ga, (int)sizeof(char), 80); - encode_vim_to_json(&ga, tv, "encode_tv2json() argument"); + const int evj_ret = encode_vim_to_json(&ga, tv, "encode_tv2json() argument"); + (void)evj_ret; did_echo_string_emsg = false; if (len != NULL) { *len = (size_t) ga.ga_len; @@ -941,9 +969,17 @@ char *encode_tv2json(typval_T *tv, size_t *len) #define TYPVAL_ENCODE_ALLOW_SPECIALS true -// msgpack_convert_one_value() +#define TYPVAL_ENCODE_SCOPE +#define TYPVAL_ENCODE_NAME msgpack +#define TYPVAL_ENCODE_FIRST_ARG_TYPE msgpack_packer *const +#define TYPVAL_ENCODE_FIRST_ARG_NAME packer +// _msgpack_convert_one_value() // encode_vim_to_msgpack() -TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(, msgpack, msgpack_packer *const, packer) +#include "nvim/eval/typval_encode.c.h" +#undef TYPVAL_ENCODE_SCOPE +#undef TYPVAL_ENCODE_NAME +#undef TYPVAL_ENCODE_FIRST_ARG_TYPE +#undef TYPVAL_ENCODE_FIRST_ARG_NAME #undef TYPVAL_ENCODE_CONV_STRING #undef TYPVAL_ENCODE_CONV_STR_STRING diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h new file mode 100644 index 0000000000..d45986396a --- /dev/null +++ b/src/nvim/eval/typval_encode.c.h @@ -0,0 +1,656 @@ +/// @file eval/typval_encode.c.h +/// +/// Contains set of macros used to convert (possibly recursive) typval_T into +/// something else. For these macros to work the following macros must be +/// defined: + +/// @def TYPVAL_ENCODE_CONV_NIL +/// @brief Macros used to convert NIL value +/// +/// Is called both for special dictionary (unless #TYPVAL_ENCODE_ALLOW_SPECIALS +/// is false) and `v:null`. Accepts no arguments, but still must be +/// a function-like macros. + +/// @def TYPVAL_ENCODE_CONV_BOOL +/// @brief Macros used to convert boolean value +/// +/// Is called both for special dictionary (unless #TYPVAL_ENCODE_ALLOW_SPECIALS +/// is false) and `v:true`/`v:false`. +/// +/// @param num Boolean value to convert. Value is an expression which +/// evaluates to some integer. + +/// @def TYPVAL_ENCODE_CONV_NUMBER +/// @brief Macros used to convert integer +/// +/// @param num Integer to convert, must accept both varnumber_T and int64_t. + +/// @def TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER +/// @brief Macros used to convert unsigned integer +/// +/// Not used if #TYPVAL_ENCODE_ALLOW_SPECIALS is false, but still must be +/// defined. +/// +/// @param num Integer to convert, must accept uint64_t. + +/// @def TYPVAL_ENCODE_CONV_FLOAT +/// @brief Macros used to convert floating-point number +/// +/// @param flt Number to convert, must accept float_T. + +/// @def TYPVAL_ENCODE_CONV_STRING +/// @brief Macros used to convert plain string +/// +/// Is used to convert VAR_STRING objects as well as BIN strings represented as +/// special dictionary. +/// +/// @param buf String to convert. Is a char[] buffer, not NUL-terminated. +/// @param len String length. + +/// @def TYPVAL_ENCODE_CONV_STR_STRING +/// @brief Like #TYPVAL_ENCODE_CONV_STRING, but for STR strings +/// +/// Is used to convert dictionary keys and STR strings represented as special +/// dictionaries. + +/// @def TYPVAL_ENCODE_CONV_EXT_STRING +/// @brief Macros used to convert EXT string +/// +/// Is used to convert EXT strings represented as special dictionaries. Never +/// actually used if #TYPVAL_ENCODE_ALLOW_SPECIALS is false, but still must be +/// defined. +/// +/// @param buf String to convert. Is a char[] buffer, not NUL-terminated. +/// @param len String length. +/// @param type EXT type. + +/// @def TYPVAL_ENCODE_CONV_FUNC +/// @brief Macros used to convert a function reference +/// +/// @param fun Function name. + +/// @def TYPVAL_ENCODE_CONV_FUNC_START +/// @brief Macros used when starting to convert a funcref or a partial +/// +/// @param fun Function name. +/// @param is_partial True if converted function is a partial. +/// @param pt Pointer to partial or NULL. + +/// @def TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS +/// @brief Macros used before starting to convert partial arguments +/// +/// @param len Number of arguments. Zero for absent arguments or when +/// converting a funcref. + +/// @def TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF +/// @brief Macros used before starting to convert self dictionary +/// +/// @param len Number of arguments. May be zero for empty dictionary or -1 for +/// missing self dictionary, also when converting function +/// reference. + +/// @def TYPVAL_ENCODE_CONV_FUNC_END +/// @brief Macros used after converting a funcref or a partial +/// +/// Accepts no arguments, but still must be a function-like macros. + +/// @def TYPVAL_ENCODE_CONV_EMPTY_LIST +/// @brief Macros used to convert an empty list +/// +/// Accepts no arguments, but still must be a function-like macros. + +/// @def TYPVAL_ENCODE_CONV_EMPTY_DICT +/// @brief Macros used to convert an empty dictionary +/// +/// Accepts no arguments, but still must be a function-like macros. + +/// @def TYPVAL_ENCODE_CONV_LIST_START +/// @brief Macros used before starting to convert non-empty list +/// +/// @param len List length. Is an expression which evaluates to an integer. + +/// @def TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS +/// @brief Macros used after finishing converting non-last list item +/// +/// Accepts no arguments, but still must be a function-like macros. + +/// @def TYPVAL_ENCODE_CONV_LIST_END +/// @brief Macros used after converting non-empty list +/// +/// Accepts no arguments, but still must be a function-like macros. + +/// @def TYPVAL_ENCODE_CONV_DICT_START +/// @brief Macros used before starting to convert non-empty dictionary +/// +/// @param len Dictionary length. Is an expression which evaluates to an +/// integer. + +/// @def TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK +/// @brief Macros used to check special dictionary key +/// +/// @param label Label for goto in case check was not successfull. +/// @param key typval_T key to check. + +/// @def TYPVAL_ENCODE_CONV_DICT_AFTER_KEY +/// @brief Macros used after finishing converting dictionary key +/// +/// Accepts no arguments, but still must be a function-like macros. + +/// @def TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS +/// @brief Macros used after finishing converting non-last dictionary value +/// +/// Accepts no arguments, but still must be a function-like macros. + +/// @def TYPVAL_ENCODE_CONV_DICT_END +/// @brief Macros used after converting non-empty dictionary +/// +/// Accepts no arguments, but still must be a function-like macros. + +/// @def TYPVAL_ENCODE_CONV_RECURSE +/// @brief Macros used when self-containing container is detected +/// +/// @param val Container for which this situation was detected. +/// @param conv_type Type of the stack entry, @see MPConvStackValType. + +/// @def TYPVAL_ENCODE_ALLOW_SPECIALS +/// @brief Macros that specifies whether special dictionaries are special +/// +/// Must be something that evaluates to boolean, most likely `true` or `false`. +/// If it is false then special dictionaries are not treated specially. + +/// @def TYPVAL_ENCODE_SCOPE +/// @brief Scope of the main function: either nothing or `static` + +/// @def TYPVAL_ENCODE_NAME +/// @brief Name of the target converter +/// +/// After including this file it will define function +/// `encode_vim_to_{TYPVAL_ENCODE_NAME}` with scope #TYPVAL_ENCODE_SCOPE and +/// static function `_{TYPVAL_ENCODE_NAME}_convert_one_value`. + +/// @def TYPVAL_ENCODE_FIRST_ARG_TYPE +/// @brief Type of the first argument, which will be used to return the results +/// +/// Is expected to be a pointer type. + +/// @def TYPVAL_ENCODE_FIRST_ARG_NAME +/// @brief Name of the first argument +/// +/// This name will only be used by one of the above macros which are defined by +/// the caller. Functions defined here do not use first argument directly. +#ifndef NVIM_EVAL_TYPVAL_ENCODE_C_H +#define NVIM_EVAL_TYPVAL_ENCODE_C_H +#undef NVIM_EVAL_TYPVAL_ENCODE_C_H + +#include +#include +#include + +#include "nvim/lib/kvec.h" +#include "nvim/eval_defs.h" +#include "nvim/eval/encode.h" +#include "nvim/func_attr.h" +#include "nvim/eval/typval_encode.h" + +static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( + TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, + MPConvStack *const mpstack, typval_T *const tv, const int copyID, + const char *const objname) + REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT; + +/// Convert single value +/// +/// Only scalar values are converted immediately, everything else is pushed onto +/// the stack. +/// +/// @param TYPVAL_ENCODE_FIRST_ARG_NAME First argument, defined by the +/// includer. Only meaningful to macros +/// defined by the includer. +/// @param[out] mpstack Stack with values to convert. Values which are not +/// converted completely by this function (i.e. +/// non-scalars) are pushed here. +/// @param tv Converted value. +/// @param[in] copyID CopyID. +/// @param[in] objname Object name, used for error reporting. +/// +/// @return OK in case of success, FAIL in case of failure. +static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( + TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, + MPConvStack *const mpstack, typval_T *const tv, const int copyID, + const char *const objname) +{ + switch (tv->v_type) { + case VAR_STRING: { + TYPVAL_ENCODE_CONV_STRING(tv->vval.v_string, tv_strlen(tv)); + break; + } + case VAR_NUMBER: { + TYPVAL_ENCODE_CONV_NUMBER(tv->vval.v_number); + break; + } + case VAR_FLOAT: { + TYPVAL_ENCODE_CONV_FLOAT(tv->vval.v_float); + break; + } + case VAR_FUNC: { + TYPVAL_ENCODE_CONV_FUNC_START(tv->vval.v_string, false, NULL); + TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(0); + TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(-1); + TYPVAL_ENCODE_CONV_FUNC_END(); + break; + } + case VAR_PARTIAL: { + partial_T *const pt = tv->vval.v_partial; + (void)pt; + TYPVAL_ENCODE_CONV_FUNC_START(pt->pt_name, true, pt); + _mp_push(*mpstack, ((MPConvStackVal) { + .type = kMPConvPartial, + .tv = tv, + .data = { + .p = { + .stage = kMPConvPartialArgs, + .pt = tv->vval.v_partial, + }, + }, + })); + break; + } + case VAR_LIST: { + if (tv->vval.v_list == NULL || tv->vval.v_list->lv_len == 0) { + TYPVAL_ENCODE_CONV_EMPTY_LIST(); + break; + } + _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(tv->vval.v_list, lv_copyID, copyID, + kMPConvList); + TYPVAL_ENCODE_CONV_LIST_START(tv->vval.v_list->lv_len); + _mp_push(*mpstack, ((MPConvStackVal) { + .type = kMPConvList, + .tv = tv, + .data = { + .l = { + .list = tv->vval.v_list, + .li = tv->vval.v_list->lv_first, + }, + }, + })); + break; + } + case VAR_SPECIAL: { + switch (tv->vval.v_special) { + case kSpecialVarNull: { + TYPVAL_ENCODE_CONV_NIL(); + break; + } + case kSpecialVarTrue: + case kSpecialVarFalse: { + TYPVAL_ENCODE_CONV_BOOL(tv->vval.v_special == kSpecialVarTrue); + break; + } + } + break; + } + case VAR_DICT: { + if (tv->vval.v_dict == NULL + || tv->vval.v_dict->dv_hashtab.ht_used == 0) { + TYPVAL_ENCODE_CONV_EMPTY_DICT(); + break; + } + const dictitem_T *type_di; + const dictitem_T *val_di; + if (TYPVAL_ENCODE_ALLOW_SPECIALS + && tv->vval.v_dict->dv_hashtab.ht_used == 2 + && (type_di = dict_find((dict_T *) tv->vval.v_dict, + (char_u *) "_TYPE", -1)) != NULL + && type_di->di_tv.v_type == VAR_LIST + && (val_di = dict_find((dict_T *) tv->vval.v_dict, + (char_u *) "_VAL", -1)) != NULL) { + size_t i; + for (i = 0; i < ARRAY_SIZE(eval_msgpack_type_lists); i++) { + if (type_di->di_tv.vval.v_list == eval_msgpack_type_lists[i]) { + break; + } + } + if (i == ARRAY_SIZE(eval_msgpack_type_lists)) { + goto _convert_one_value_regular_dict; + } + switch ((MessagePackType) i) { + case kMPNil: { + TYPVAL_ENCODE_CONV_NIL(); + break; + } + case kMPBoolean: { + if (val_di->di_tv.v_type != VAR_NUMBER) { + goto _convert_one_value_regular_dict; + } + TYPVAL_ENCODE_CONV_BOOL(val_di->di_tv.vval.v_number); + break; + } + case kMPInteger: { + const list_T *val_list; + varnumber_T sign; + varnumber_T highest_bits; + varnumber_T high_bits; + varnumber_T low_bits; + /* List of 4 integers; first is signed (should be 1 or -1, but */ + /* this is not checked), second is unsigned and have at most */ + /* one (sign is -1) or two (sign is 1) non-zero bits (number of */ + /* bits is not checked), other unsigned and have at most 31 */ + /* non-zero bits (number of bits is not checked).*/ + if (val_di->di_tv.v_type != VAR_LIST + || (val_list = val_di->di_tv.vval.v_list) == NULL + || val_list->lv_len != 4 + || val_list->lv_first->li_tv.v_type != VAR_NUMBER + || (sign = val_list->lv_first->li_tv.vval.v_number) == 0 + || val_list->lv_first->li_next->li_tv.v_type != VAR_NUMBER + || (highest_bits = + val_list->lv_first->li_next->li_tv.vval.v_number) < 0 + || val_list->lv_last->li_prev->li_tv.v_type != VAR_NUMBER + || (high_bits = + val_list->lv_last->li_prev->li_tv.vval.v_number) < 0 + || val_list->lv_last->li_tv.v_type != VAR_NUMBER + || (low_bits = val_list->lv_last->li_tv.vval.v_number) < 0) { + goto _convert_one_value_regular_dict; + } + uint64_t number = ((uint64_t) (((uint64_t) highest_bits) << 62) + | (uint64_t) (((uint64_t) high_bits) << 31) + | (uint64_t) low_bits); + if (sign > 0) { + TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(number); + } else { + TYPVAL_ENCODE_CONV_NUMBER(-number); + } + break; + } + case kMPFloat: { + if (val_di->di_tv.v_type != VAR_FLOAT) { + goto _convert_one_value_regular_dict; + } + TYPVAL_ENCODE_CONV_FLOAT(val_di->di_tv.vval.v_float); + break; + } + case kMPString: + case kMPBinary: { + const bool is_string = ((MessagePackType) i == kMPString); + if (val_di->di_tv.v_type != VAR_LIST) { + goto _convert_one_value_regular_dict; + } + size_t len; + char *buf; + if (!encode_vim_list_to_buf(val_di->di_tv.vval.v_list, &len, + &buf)) { + goto _convert_one_value_regular_dict; + } + if (is_string) { + TYPVAL_ENCODE_CONV_STR_STRING(buf, len); + } else { + TYPVAL_ENCODE_CONV_STRING(buf, len); + } + xfree(buf); + break; + } + case kMPArray: { + if (val_di->di_tv.v_type != VAR_LIST) { + goto _convert_one_value_regular_dict; + } + _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(val_di->di_tv.vval.v_list, + lv_copyID, copyID, kMPConvList); + TYPVAL_ENCODE_CONV_LIST_START(val_di->di_tv.vval.v_list->lv_len); + _mp_push(*mpstack, ((MPConvStackVal) { + .tv = tv, + .type = kMPConvList, + .data = { + .l = { + .list = val_di->di_tv.vval.v_list, + .li = val_di->di_tv.vval.v_list->lv_first, + }, + }, + })); + break; + } + case kMPMap: { + if (val_di->di_tv.v_type != VAR_LIST) { + goto _convert_one_value_regular_dict; + } + list_T *const val_list = val_di->di_tv.vval.v_list; + if (val_list == NULL || val_list->lv_len == 0) { + TYPVAL_ENCODE_CONV_EMPTY_DICT(); + break; + } + for (const listitem_T *li = val_list->lv_first; li != NULL; + li = li->li_next) { + if (li->li_tv.v_type != VAR_LIST + || li->li_tv.vval.v_list->lv_len != 2) { + goto _convert_one_value_regular_dict; + } + } + _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(val_list, lv_copyID, copyID, + kMPConvPairs); + TYPVAL_ENCODE_CONV_DICT_START(val_list->lv_len); + _mp_push(*mpstack, ((MPConvStackVal) { + .tv = tv, + .type = kMPConvPairs, + .data = { + .l = { + .list = val_list, + .li = val_list->lv_first, + }, + }, + })); + break; + } + case kMPExt: { + const list_T *val_list; + varnumber_T type; + if (val_di->di_tv.v_type != VAR_LIST + || (val_list = val_di->di_tv.vval.v_list) == NULL + || val_list->lv_len != 2 + || (val_list->lv_first->li_tv.v_type != VAR_NUMBER) + || (type = val_list->lv_first->li_tv.vval.v_number) > INT8_MAX + || type < INT8_MIN + || (val_list->lv_last->li_tv.v_type != VAR_LIST)) { + goto _convert_one_value_regular_dict; + } + size_t len; + char *buf; + if (!encode_vim_list_to_buf(val_list->lv_last->li_tv.vval.v_list, + &len, &buf)) { + goto _convert_one_value_regular_dict; + } + TYPVAL_ENCODE_CONV_EXT_STRING(buf, len, type); + xfree(buf); + break; + } + } + break; + } +_convert_one_value_regular_dict: + _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(tv->vval.v_dict, dv_copyID, copyID, + kMPConvDict); + TYPVAL_ENCODE_CONV_DICT_START(tv->vval.v_dict->dv_hashtab.ht_used); + _mp_push(*mpstack, ((MPConvStackVal) { + .tv = tv, + .type = kMPConvDict, + .data = { + .d = { + .dict = tv->vval.v_dict, + .hi = tv->vval.v_dict->dv_hashtab.ht_array, + .todo = tv->vval.v_dict->dv_hashtab.ht_used, + }, + }, + })); + break; + } + case VAR_UNKNOWN: { + EMSG2(_(e_intern2), STR(_TYPVAL_ENCODE_CONVERT_ONE_VALUE) "()"); + return FAIL; + } + } + return OK; +} + +TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( + TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, + typval_T *const tv, const char *const objname) + REAL_FATTR_WARN_UNUSED_RESULT; + +/// Convert the whole typval +/// +/// @param TYPVAL_ENCODE_FIRST_ARG_NAME First argument, defined by the +/// includer. Only meaningful to macros +/// defined by the includer. +/// @param tv Converted value. +/// @param[in] objname Object name, used for error reporting. +/// +/// @return OK in case of success, FAIL in case of failure. +TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( + TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, + typval_T *const tv, const char *const objname) +{ + const int copyID = get_copyID(); + MPConvStack mpstack; + _mp_init(mpstack); + if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, + tv, copyID, objname) + == FAIL) { + goto encode_vim_to__error_ret; + } + while (_mp_size(mpstack)) { + MPConvStackVal *cur_mpsv = &_mp_last(mpstack); + typval_T *cur_tv = NULL; + switch (cur_mpsv->type) { + case kMPConvDict: { + if (!cur_mpsv->data.d.todo) { + (void) _mp_pop(mpstack); + cur_mpsv->data.d.dict->dv_copyID = copyID - 1; + TYPVAL_ENCODE_CONV_DICT_END(); + continue; + } else if (cur_mpsv->data.d.todo + != cur_mpsv->data.d.dict->dv_hashtab.ht_used) { + TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(); + } + while (HASHITEM_EMPTY(cur_mpsv->data.d.hi)) { + cur_mpsv->data.d.hi++; + } + dictitem_T *const di = HI2DI(cur_mpsv->data.d.hi); + cur_mpsv->data.d.todo--; + cur_mpsv->data.d.hi++; + TYPVAL_ENCODE_CONV_STR_STRING(&di->di_key[0], + strlen((char *) &di->di_key[0])); + TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(); + cur_tv = &di->di_tv; + break; + } + case kMPConvList: { + if (cur_mpsv->data.l.li == NULL) { + (void) _mp_pop(mpstack); + cur_mpsv->data.l.list->lv_copyID = copyID - 1; + TYPVAL_ENCODE_CONV_LIST_END(); + continue; + } else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) { + TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(); + } + cur_tv = &cur_mpsv->data.l.li->li_tv; + cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; + break; + } + case kMPConvPairs: { + if (cur_mpsv->data.l.li == NULL) { + (void) _mp_pop(mpstack); + cur_mpsv->data.l.list->lv_copyID = copyID - 1; + TYPVAL_ENCODE_CONV_DICT_END(); + continue; + } else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) { + TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(); + } + const list_T *const kv_pair = cur_mpsv->data.l.li->li_tv.vval.v_list; + TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK( + encode_vim_to__error_ret, kv_pair->lv_first->li_tv); + if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE( + TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, + &kv_pair->lv_first->li_tv, copyID, objname) == FAIL) { + goto encode_vim_to__error_ret; + } + TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(); + cur_tv = &kv_pair->lv_last->li_tv; + cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; + break; + } + case kMPConvPartial: { + partial_T *const pt = cur_mpsv->data.p.pt; + switch (cur_mpsv->data.p.stage) { + case kMPConvPartialArgs: { + TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(pt->pt_argc); + cur_mpsv->data.p.stage = kMPConvPartialSelf; + if (pt->pt_argc > 0) { + TYPVAL_ENCODE_CONV_LIST_START(pt->pt_argc); + _mp_push(mpstack, ((MPConvStackVal) { + .type = kMPConvPartialList, + .tv = tv, + .data = { + .a = { + .arg = pt->pt_argv, + .argv = pt->pt_argv, + .todo = (size_t)pt->pt_argc, + }, + }, + })); + } + break; + } + case kMPConvPartialSelf: { + cur_mpsv->data.p.stage = kMPConvPartialEnd; + dict_T *const dict = pt->pt_dict; + if (dict != NULL) { + TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(dict->dv_hashtab.ht_used); + TYPVAL_ENCODE_CONV_DICT_START(dict->dv_hashtab.ht_used); + _mp_push(mpstack, ((MPConvStackVal) { + .type = kMPConvDict, + .tv = tv, + .data = { + .d = { + .dict = dict, + .hi = dict->dv_hashtab.ht_array, + .todo = dict->dv_hashtab.ht_used, + }, + }, + })); + } else { + TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(-1); + } + break; + } + case kMPConvPartialEnd: { + TYPVAL_ENCODE_CONV_FUNC_END(); + (void) _mp_pop(mpstack); + break; + } + } + continue; + } + case kMPConvPartialList: { + if (!cur_mpsv->data.a.todo) { + (void) _mp_pop(mpstack); + TYPVAL_ENCODE_CONV_LIST_END(); + continue; + } else if (cur_mpsv->data.a.argv != cur_mpsv->data.a.arg) { + TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(); + } + cur_tv = cur_mpsv->data.a.arg++; + cur_mpsv->data.a.todo--; + break; + } + } + assert(cur_tv != NULL); + if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE( + TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, cur_tv, copyID, objname) + == FAIL) { + goto encode_vim_to__error_ret; + } + } + _mp_destroy(mpstack); + return OK; +encode_vim_to__error_ret: + _mp_destroy(mpstack); + return FAIL; +} +#endif // NVIM_EVAL_TYPVAL_ENCODE_C_H diff --git a/src/nvim/eval/typval_encode.h b/src/nvim/eval/typval_encode.h index 149dd22ff7..8fb37de93e 100644 --- a/src/nvim/eval/typval_encode.h +++ b/src/nvim/eval/typval_encode.h @@ -1,4 +1,4 @@ -/// @file eval/typval_convert.h +/// @file eval/typval_encode.h /// /// Contains set of macros used to convert (possibly recursive) typval_T into /// something else. For these macros to work the following macros must be @@ -162,11 +162,11 @@ #include #include +#include #include #include "nvim/lib/kvec.h" #include "nvim/eval_defs.h" -#include "nvim/eval/encode.h" #include "nvim/func_attr.h" /// Type of the stack entry @@ -222,21 +222,9 @@ typedef kvec_withinit_t(MPConvStackVal, 8) MPConvStack; #define _mp_pop kv_pop #define _mp_last kv_last -/// Code for checking whether container references itself -/// -/// @param[in,out] val Container to check. -/// @param copyID_attr Name of the container attribute that holds copyID. -/// After checking whether value of this attribute is -/// copyID (variable) it is set to copyID. -/// @param conv_type Type of the conversion, @see MPConvStackValType. -#define _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(val, copyID_attr, conv_type) \ - do { \ - if ((val)->copyID_attr == copyID) { \ - TYPVAL_ENCODE_CONV_RECURSE((val), conv_type); \ - return OK; \ - } \ - (val)->copyID_attr = copyID; \ - } while (0) +static inline size_t tv_strlen(const typval_T *const tv) + REAL_FATTR_ALWAYS_INLINE REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT + REAL_FATTR_NONNULL_ALL; /// Length of the string stored in typval_T /// @@ -246,8 +234,6 @@ typedef kvec_withinit_t(MPConvStackVal, 8) MPConvStack; /// @return Length of the string stored in typval_T, including 0 for NULL /// string. static inline size_t tv_strlen(const typval_T *const tv) - FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT - FUNC_ATTR_NONNULL_ALL { assert(tv->v_type == VAR_STRING); return (tv->vval.v_string == NULL @@ -255,442 +241,37 @@ static inline size_t tv_strlen(const typval_T *const tv) : strlen((char *) tv->vval.v_string)); } -/// Define functions to convert a VimL value: -/// `{name}_convert_one_value(...)` -/// `encode_vim_to_{name}(...)` +/// Code for checking whether container references itself /// -/// @param scope Scope of the main function: either nothing or `static`. -/// @param name Name of the target converter. -/// @param firstargtype Type of the first argument. It will be used to return -/// the results. -/// @param firstargname Name of the first argument. -#define TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(scope, name, firstargtype, \ - firstargname) \ -/* Returns OK or FAIL */ \ -static int name##_convert_one_value(firstargtype firstargname, \ - MPConvStack *const mpstack, \ - typval_T *const tv, \ - const int copyID, \ - const char *const objname) \ - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT \ -{ \ - switch (tv->v_type) { \ - case VAR_STRING: { \ - TYPVAL_ENCODE_CONV_STRING(tv->vval.v_string, tv_strlen(tv)); \ - break; \ - } \ - case VAR_NUMBER: { \ - TYPVAL_ENCODE_CONV_NUMBER(tv->vval.v_number); \ - break; \ - } \ - case VAR_FLOAT: { \ - TYPVAL_ENCODE_CONV_FLOAT(tv->vval.v_float); \ - break; \ - } \ - case VAR_FUNC: { \ - TYPVAL_ENCODE_CONV_FUNC_START(tv->vval.v_string, false, NULL); \ - TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(0); \ - TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(-1); \ - TYPVAL_ENCODE_CONV_FUNC_END(); \ - break; \ - } \ - case VAR_PARTIAL: { \ - partial_T *const pt = tv->vval.v_partial; \ - (void)pt; \ - TYPVAL_ENCODE_CONV_FUNC_START(pt->pt_name, true, pt); \ - _mp_push(*mpstack, ((MPConvStackVal) { \ - .type = kMPConvPartial, \ - .tv = tv, \ - .data = { \ - .p = { \ - .stage = kMPConvPartialArgs, \ - .pt = tv->vval.v_partial, \ - }, \ - }, \ - })); \ - break; \ - } \ - case VAR_LIST: { \ - if (tv->vval.v_list == NULL || tv->vval.v_list->lv_len == 0) { \ - TYPVAL_ENCODE_CONV_EMPTY_LIST(); \ - break; \ +/// @param[in,out] val Container to check. +/// @param copyID_attr Name of the container attribute that holds copyID. +/// After checking whether value of this attribute is +/// copyID (variable) it is set to copyID. +/// @param[in] copyID CopyID used by the caller. +/// @param conv_type Type of the conversion, @see MPConvStackValType. +#define _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(val, copyID_attr, copyID, \ + conv_type) \ + do { \ + if ((val)->copyID_attr == (copyID)) { \ + TYPVAL_ENCODE_CONV_RECURSE((val), conv_type); \ + return OK; \ } \ - _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(tv->vval.v_list, lv_copyID, \ - kMPConvList); \ - TYPVAL_ENCODE_CONV_LIST_START(tv->vval.v_list->lv_len); \ - _mp_push(*mpstack, ((MPConvStackVal) { \ - .type = kMPConvList, \ - .tv = tv, \ - .data = { \ - .l = { \ - .list = tv->vval.v_list, \ - .li = tv->vval.v_list->lv_first, \ - }, \ - }, \ - })); \ - break; \ - } \ - case VAR_SPECIAL: { \ - switch (tv->vval.v_special) { \ - case kSpecialVarNull: { \ - TYPVAL_ENCODE_CONV_NIL(); \ - break; \ - } \ - case kSpecialVarTrue: \ - case kSpecialVarFalse: { \ - TYPVAL_ENCODE_CONV_BOOL(tv->vval.v_special == kSpecialVarTrue); \ - break; \ - } \ - } \ - break; \ - } \ - case VAR_DICT: { \ - if (tv->vval.v_dict == NULL \ - || tv->vval.v_dict->dv_hashtab.ht_used == 0) { \ - TYPVAL_ENCODE_CONV_EMPTY_DICT(); \ - break; \ - } \ - const dictitem_T *type_di; \ - const dictitem_T *val_di; \ - if (TYPVAL_ENCODE_ALLOW_SPECIALS \ - && tv->vval.v_dict->dv_hashtab.ht_used == 2 \ - && (type_di = dict_find((dict_T *) tv->vval.v_dict, \ - (char_u *) "_TYPE", -1)) != NULL \ - && type_di->di_tv.v_type == VAR_LIST \ - && (val_di = dict_find((dict_T *) tv->vval.v_dict, \ - (char_u *) "_VAL", -1)) != NULL) { \ - size_t i; \ - for (i = 0; i < ARRAY_SIZE(eval_msgpack_type_lists); i++) { \ - if (type_di->di_tv.vval.v_list == eval_msgpack_type_lists[i]) { \ - break; \ - } \ - } \ - if (i == ARRAY_SIZE(eval_msgpack_type_lists)) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - switch ((MessagePackType) i) { \ - case kMPNil: { \ - TYPVAL_ENCODE_CONV_NIL(); \ - break; \ - } \ - case kMPBoolean: { \ - if (val_di->di_tv.v_type != VAR_NUMBER) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - TYPVAL_ENCODE_CONV_BOOL(val_di->di_tv.vval.v_number); \ - break; \ - } \ - case kMPInteger: { \ - const list_T *val_list; \ - varnumber_T sign; \ - varnumber_T highest_bits; \ - varnumber_T high_bits; \ - varnumber_T low_bits; \ - /* List of 4 integers; first is signed (should be 1 or -1, but */ \ - /* this is not checked), second is unsigned and have at most */ \ - /* one (sign is -1) or two (sign is 1) non-zero bits (number of */ \ - /* bits is not checked), other unsigned and have at most 31 */ \ - /* non-zero bits (number of bits is not checked).*/ \ - if (val_di->di_tv.v_type != VAR_LIST \ - || (val_list = val_di->di_tv.vval.v_list) == NULL \ - || val_list->lv_len != 4 \ - || val_list->lv_first->li_tv.v_type != VAR_NUMBER \ - || (sign = val_list->lv_first->li_tv.vval.v_number) == 0 \ - || val_list->lv_first->li_next->li_tv.v_type != VAR_NUMBER \ - || (highest_bits = \ - val_list->lv_first->li_next->li_tv.vval.v_number) < 0 \ - || val_list->lv_last->li_prev->li_tv.v_type != VAR_NUMBER \ - || (high_bits = \ - val_list->lv_last->li_prev->li_tv.vval.v_number) < 0 \ - || val_list->lv_last->li_tv.v_type != VAR_NUMBER \ - || (low_bits = val_list->lv_last->li_tv.vval.v_number) < 0) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - uint64_t number = ((uint64_t) (((uint64_t) highest_bits) << 62) \ - | (uint64_t) (((uint64_t) high_bits) << 31) \ - | (uint64_t) low_bits); \ - if (sign > 0) { \ - TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(number); \ - } else { \ - TYPVAL_ENCODE_CONV_NUMBER(-number); \ - } \ - break; \ - } \ - case kMPFloat: { \ - if (val_di->di_tv.v_type != VAR_FLOAT) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - TYPVAL_ENCODE_CONV_FLOAT(val_di->di_tv.vval.v_float); \ - break; \ - } \ - case kMPString: \ - case kMPBinary: { \ - const bool is_string = ((MessagePackType) i == kMPString); \ - if (val_di->di_tv.v_type != VAR_LIST) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - size_t len; \ - char *buf; \ - if (!encode_vim_list_to_buf(val_di->di_tv.vval.v_list, &len, \ - &buf)) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - if (is_string) { \ - TYPVAL_ENCODE_CONV_STR_STRING(buf, len); \ - } else { \ - TYPVAL_ENCODE_CONV_STRING(buf, len); \ - } \ - xfree(buf); \ - break; \ - } \ - case kMPArray: { \ - if (val_di->di_tv.v_type != VAR_LIST) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(val_di->di_tv.vval.v_list, \ - lv_copyID, kMPConvList); \ - TYPVAL_ENCODE_CONV_LIST_START(val_di->di_tv.vval.v_list->lv_len); \ - _mp_push(*mpstack, ((MPConvStackVal) { \ - .tv = tv, \ - .type = kMPConvList, \ - .data = { \ - .l = { \ - .list = val_di->di_tv.vval.v_list, \ - .li = val_di->di_tv.vval.v_list->lv_first, \ - }, \ - }, \ - })); \ - break; \ - } \ - case kMPMap: { \ - if (val_di->di_tv.v_type != VAR_LIST) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - list_T *const val_list = val_di->di_tv.vval.v_list; \ - if (val_list == NULL || val_list->lv_len == 0) { \ - TYPVAL_ENCODE_CONV_EMPTY_DICT(); \ - break; \ - } \ - for (const listitem_T *li = val_list->lv_first; li != NULL; \ - li = li->li_next) { \ - if (li->li_tv.v_type != VAR_LIST \ - || li->li_tv.vval.v_list->lv_len != 2) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - } \ - _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(val_list, lv_copyID, \ - kMPConvPairs); \ - TYPVAL_ENCODE_CONV_DICT_START(val_list->lv_len); \ - _mp_push(*mpstack, ((MPConvStackVal) { \ - .tv = tv, \ - .type = kMPConvPairs, \ - .data = { \ - .l = { \ - .list = val_list, \ - .li = val_list->lv_first, \ - }, \ - }, \ - })); \ - break; \ - } \ - case kMPExt: { \ - const list_T *val_list; \ - varnumber_T type; \ - if (val_di->di_tv.v_type != VAR_LIST \ - || (val_list = val_di->di_tv.vval.v_list) == NULL \ - || val_list->lv_len != 2 \ - || (val_list->lv_first->li_tv.v_type != VAR_NUMBER) \ - || (type = val_list->lv_first->li_tv.vval.v_number) > INT8_MAX \ - || type < INT8_MIN \ - || (val_list->lv_last->li_tv.v_type != VAR_LIST)) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - size_t len; \ - char *buf; \ - if (!encode_vim_list_to_buf(val_list->lv_last->li_tv.vval.v_list, \ - &len, &buf)) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - TYPVAL_ENCODE_CONV_EXT_STRING(buf, len, type); \ - xfree(buf); \ - break; \ - } \ - } \ - break; \ - } \ -name##_convert_one_value_regular_dict: \ - _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(tv->vval.v_dict, dv_copyID, \ - kMPConvDict); \ - TYPVAL_ENCODE_CONV_DICT_START(tv->vval.v_dict->dv_hashtab.ht_used); \ - _mp_push(*mpstack, ((MPConvStackVal) { \ - .tv = tv, \ - .type = kMPConvDict, \ - .data = { \ - .d = { \ - .dict = tv->vval.v_dict, \ - .hi = tv->vval.v_dict->dv_hashtab.ht_array, \ - .todo = tv->vval.v_dict->dv_hashtab.ht_used, \ - }, \ - }, \ - })); \ - break; \ - } \ - case VAR_UNKNOWN: { \ - EMSG2(_(e_intern2), #name "_convert_one_value()"); \ - return FAIL; \ - } \ - } \ - return OK; \ -} \ -\ -scope int encode_vim_to_##name(firstargtype firstargname, typval_T *const tv, \ - const char *const objname) \ - FUNC_ATTR_WARN_UNUSED_RESULT \ -{ \ - const int copyID = get_copyID(); \ - MPConvStack mpstack; \ - _mp_init(mpstack); \ - if (name##_convert_one_value(firstargname, &mpstack, tv, copyID, objname) \ - == FAIL) { \ - goto encode_vim_to_##name##_error_ret; \ - } \ - while (_mp_size(mpstack)) { \ - MPConvStackVal *cur_mpsv = &_mp_last(mpstack); \ - typval_T *cur_tv = NULL; \ - switch (cur_mpsv->type) { \ - case kMPConvDict: { \ - if (!cur_mpsv->data.d.todo) { \ - (void) _mp_pop(mpstack); \ - cur_mpsv->data.d.dict->dv_copyID = copyID - 1; \ - TYPVAL_ENCODE_CONV_DICT_END(); \ - continue; \ - } else if (cur_mpsv->data.d.todo \ - != cur_mpsv->data.d.dict->dv_hashtab.ht_used) { \ - TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(); \ - } \ - while (HASHITEM_EMPTY(cur_mpsv->data.d.hi)) { \ - cur_mpsv->data.d.hi++; \ - } \ - dictitem_T *const di = HI2DI(cur_mpsv->data.d.hi); \ - cur_mpsv->data.d.todo--; \ - cur_mpsv->data.d.hi++; \ - TYPVAL_ENCODE_CONV_STR_STRING(&di->di_key[0], \ - strlen((char *) &di->di_key[0])); \ - TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(); \ - cur_tv = &di->di_tv; \ - break; \ - } \ - case kMPConvList: { \ - if (cur_mpsv->data.l.li == NULL) { \ - (void) _mp_pop(mpstack); \ - cur_mpsv->data.l.list->lv_copyID = copyID - 1; \ - TYPVAL_ENCODE_CONV_LIST_END(); \ - continue; \ - } else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) { \ - TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(); \ - } \ - cur_tv = &cur_mpsv->data.l.li->li_tv; \ - cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; \ - break; \ - } \ - case kMPConvPairs: { \ - if (cur_mpsv->data.l.li == NULL) { \ - (void) _mp_pop(mpstack); \ - cur_mpsv->data.l.list->lv_copyID = copyID - 1; \ - TYPVAL_ENCODE_CONV_DICT_END(); \ - continue; \ - } else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) { \ - TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(); \ - } \ - const list_T *const kv_pair = cur_mpsv->data.l.li->li_tv.vval.v_list; \ - TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK( \ - encode_vim_to_##name##_error_ret, kv_pair->lv_first->li_tv); \ - if (name##_convert_one_value(firstargname, &mpstack, \ - &kv_pair->lv_first->li_tv, copyID, \ - objname) == FAIL) { \ - goto encode_vim_to_##name##_error_ret; \ - } \ - TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(); \ - cur_tv = &kv_pair->lv_last->li_tv; \ - cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; \ - break; \ - } \ - case kMPConvPartial: { \ - partial_T *const pt = cur_mpsv->data.p.pt; \ - switch (cur_mpsv->data.p.stage) { \ - case kMPConvPartialArgs: { \ - TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(pt->pt_argc); \ - cur_mpsv->data.p.stage = kMPConvPartialSelf; \ - if (pt->pt_argc > 0) { \ - TYPVAL_ENCODE_CONV_LIST_START(pt->pt_argc); \ - _mp_push(mpstack, ((MPConvStackVal) { \ - .type = kMPConvPartialList, \ - .tv = tv, \ - .data = { \ - .a = { \ - .arg = pt->pt_argv, \ - .argv = pt->pt_argv, \ - .todo = (size_t)pt->pt_argc, \ - }, \ - }, \ - })); \ - } \ - break; \ - } \ - case kMPConvPartialSelf: { \ - cur_mpsv->data.p.stage = kMPConvPartialEnd; \ - dict_T *const dict = pt->pt_dict; \ - if (dict != NULL) { \ - TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(dict->dv_hashtab.ht_used); \ - TYPVAL_ENCODE_CONV_DICT_START(dict->dv_hashtab.ht_used); \ - _mp_push(mpstack, ((MPConvStackVal) { \ - .type = kMPConvDict, \ - .tv = tv, \ - .data = { \ - .d = { \ - .dict = dict, \ - .hi = dict->dv_hashtab.ht_array, \ - .todo = dict->dv_hashtab.ht_used, \ - }, \ - }, \ - })); \ - } else { \ - TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(-1); \ - } \ - break; \ - } \ - case kMPConvPartialEnd: { \ - TYPVAL_ENCODE_CONV_FUNC_END(); \ - (void) _mp_pop(mpstack); \ - break; \ - } \ - } \ - continue; \ - } \ - case kMPConvPartialList: { \ - if (!cur_mpsv->data.a.todo) { \ - (void) _mp_pop(mpstack); \ - TYPVAL_ENCODE_CONV_LIST_END(); \ - continue; \ - } else if (cur_mpsv->data.a.argv != cur_mpsv->data.a.arg) { \ - TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(); \ - } \ - cur_tv = cur_mpsv->data.a.arg++; \ - cur_mpsv->data.a.todo--; \ - break; \ - } \ - } \ - assert(cur_tv != NULL); \ - if (name##_convert_one_value(firstargname, &mpstack, cur_tv, copyID, \ - objname) == FAIL) { \ - goto encode_vim_to_##name##_error_ret; \ - } \ - } \ - _mp_destroy(mpstack); \ - return OK; \ -encode_vim_to_##name##_error_ret: \ - _mp_destroy(mpstack); \ - return FAIL; \ -} + (val)->copyID_attr = (copyID); \ + } while (0) + +#define _TYPVAL_ENCODE_ENCODE_INNER_2(name) encode_vim_to_##name +#define _TYPVAL_ENCODE_ENCODE_INNER(name) _TYPVAL_ENCODE_ENCODE_INNER_2(name) + +/// 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) + +/// Name of the …convert_one_value function +#define _TYPVAL_ENCODE_CONVERT_ONE_VALUE \ + _TYPVAL_ENCODE_CONVERT_ONE_VALUE_INNER(TYPVAL_ENCODE_NAME) #endif // NVIM_EVAL_TYPVAL_ENCODE_H From 27343bc5b2b56cba76059c117fb97b558bc7bc78 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 25 Dec 2016 21:02:27 +0300 Subject: [PATCH 03/28] eval/typval_encode: Fix crashes --- src/nvim/eval/typval_encode.c.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index d45986396a..8005f7cfcf 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -579,9 +579,9 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( partial_T *const pt = cur_mpsv->data.p.pt; switch (cur_mpsv->data.p.stage) { case kMPConvPartialArgs: { - TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(pt->pt_argc); + TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(pt == NULL ? 0 : pt->pt_argc); cur_mpsv->data.p.stage = kMPConvPartialSelf; - if (pt->pt_argc > 0) { + if (pt != NULL && pt->pt_argc > 0) { TYPVAL_ENCODE_CONV_LIST_START(pt->pt_argc); _mp_push(mpstack, ((MPConvStackVal) { .type = kMPConvPartialList, @@ -599,7 +599,7 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( } case kMPConvPartialSelf: { cur_mpsv->data.p.stage = kMPConvPartialEnd; - dict_T *const dict = pt->pt_dict; + dict_T *const dict = pt == NULL ? NULL : pt->pt_dict; if (dict != NULL) { TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(dict->dv_hashtab.ht_used); TYPVAL_ENCODE_CONV_DICT_START(dict->dv_hashtab.ht_used); From 38ab553eb25d67567a0e51f75e7a3aeaa631404c Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 25 Dec 2016 21:18:04 +0300 Subject: [PATCH 04/28] clint: Allow including .c.h files multiple times Except when they are system just in case. There should be no .c.h system files though, but if there will be it is unlikely that they inherit the same convention. --- src/clint.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/clint.py b/src/clint.py index efc5f18378..07733d211e 100755 --- a/src/clint.py +++ b/src/clint.py @@ -3001,9 +3001,10 @@ def CheckIncludeLine(filename, clean_lines, linenum, include_state, error): include = match.group(2) is_system = (match.group(1) == '<') if include in include_state: - error(filename, linenum, 'build/include', 4, - '"%s" already included at %s:%s' % - (include, filename, include_state[include])) + if is_system or not include.endswith('.c.h'): + error(filename, linenum, 'build/include', 4, + '"%s" already included at %s:%s' % + (include, filename, include_state[include])) else: include_state[include] = linenum From dd27fcfda5c650be1cd57806e674f6f723f60659 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 25 Dec 2016 21:20:31 +0300 Subject: [PATCH 05/28] eval/typval_encode: Fix linter errors --- src/nvim/eval/typval_encode.c.h | 53 +++++++++++++++++---------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index 8005f7cfcf..597fd71030 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -299,11 +299,11 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( const dictitem_T *val_di; if (TYPVAL_ENCODE_ALLOW_SPECIALS && tv->vval.v_dict->dv_hashtab.ht_used == 2 - && (type_di = dict_find((dict_T *) tv->vval.v_dict, - (char_u *) "_TYPE", -1)) != NULL + && (type_di = dict_find((dict_T *)tv->vval.v_dict, + (char_u *)"_TYPE", -1)) != NULL && type_di->di_tv.v_type == VAR_LIST - && (val_di = dict_find((dict_T *) tv->vval.v_dict, - (char_u *) "_VAL", -1)) != NULL) { + && (val_di = dict_find((dict_T *)tv->vval.v_dict, + (char_u *)"_VAL", -1)) != NULL) { size_t i; for (i = 0; i < ARRAY_SIZE(eval_msgpack_type_lists); i++) { if (type_di->di_tv.vval.v_list == eval_msgpack_type_lists[i]) { @@ -313,7 +313,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( if (i == ARRAY_SIZE(eval_msgpack_type_lists)) { goto _convert_one_value_regular_dict; } - switch ((MessagePackType) i) { + switch ((MessagePackType)i) { case kMPNil: { TYPVAL_ENCODE_CONV_NIL(); break; @@ -331,11 +331,11 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( varnumber_T highest_bits; varnumber_T high_bits; varnumber_T low_bits; - /* List of 4 integers; first is signed (should be 1 or -1, but */ - /* this is not checked), second is unsigned and have at most */ - /* one (sign is -1) or two (sign is 1) non-zero bits (number of */ - /* bits is not checked), other unsigned and have at most 31 */ - /* non-zero bits (number of bits is not checked).*/ + // List of 4 integers; first is signed (should be 1 or -1, but + // this is not checked), second is unsigned and have at most + // one (sign is -1) or two (sign is 1) non-zero bits (number of + // bits is not checked), other unsigned and have at most 31 + // non-zero bits (number of bits is not checked). if (val_di->di_tv.v_type != VAR_LIST || (val_list = val_di->di_tv.vval.v_list) == NULL || val_list->lv_len != 4 @@ -351,9 +351,9 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( || (low_bits = val_list->lv_last->li_tv.vval.v_number) < 0) { goto _convert_one_value_regular_dict; } - uint64_t number = ((uint64_t) (((uint64_t) highest_bits) << 62) - | (uint64_t) (((uint64_t) high_bits) << 31) - | (uint64_t) low_bits); + uint64_t number = ((uint64_t)(((uint64_t)highest_bits) << 62) + | (uint64_t)(((uint64_t)high_bits) << 31) + | (uint64_t)low_bits); if (sign > 0) { TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(number); } else { @@ -370,7 +370,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( } case kMPString: case kMPBinary: { - const bool is_string = ((MessagePackType) i == kMPString); + const bool is_string = ((MessagePackType)i == kMPString); if (val_di->di_tv.v_type != VAR_LIST) { goto _convert_one_value_regular_dict; } @@ -520,7 +520,7 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( switch (cur_mpsv->type) { case kMPConvDict: { if (!cur_mpsv->data.d.todo) { - (void) _mp_pop(mpstack); + (void)_mp_pop(mpstack); cur_mpsv->data.d.dict->dv_copyID = copyID - 1; TYPVAL_ENCODE_CONV_DICT_END(); continue; @@ -535,14 +535,14 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( cur_mpsv->data.d.todo--; cur_mpsv->data.d.hi++; TYPVAL_ENCODE_CONV_STR_STRING(&di->di_key[0], - strlen((char *) &di->di_key[0])); + strlen((char *)&di->di_key[0])); TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(); cur_tv = &di->di_tv; break; } case kMPConvList: { if (cur_mpsv->data.l.li == NULL) { - (void) _mp_pop(mpstack); + (void)_mp_pop(mpstack); cur_mpsv->data.l.list->lv_copyID = copyID - 1; TYPVAL_ENCODE_CONV_LIST_END(); continue; @@ -555,7 +555,7 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( } case kMPConvPairs: { if (cur_mpsv->data.l.li == NULL) { - (void) _mp_pop(mpstack); + (void)_mp_pop(mpstack); cur_mpsv->data.l.list->lv_copyID = copyID - 1; TYPVAL_ENCODE_CONV_DICT_END(); continue; @@ -565,9 +565,11 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( const list_T *const kv_pair = cur_mpsv->data.l.li->li_tv.vval.v_list; TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK( encode_vim_to__error_ret, kv_pair->lv_first->li_tv); - if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE( - TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, - &kv_pair->lv_first->li_tv, copyID, objname) == FAIL) { + if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, + &mpstack, + &kv_pair->lv_first->li_tv, + copyID, + objname) == FAIL) { goto encode_vim_to__error_ret; } TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(); @@ -621,7 +623,7 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( } case kMPConvPartialEnd: { TYPVAL_ENCODE_CONV_FUNC_END(); - (void) _mp_pop(mpstack); + (void)_mp_pop(mpstack); break; } } @@ -629,7 +631,7 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( } case kMPConvPartialList: { if (!cur_mpsv->data.a.todo) { - (void) _mp_pop(mpstack); + (void)_mp_pop(mpstack); TYPVAL_ENCODE_CONV_LIST_END(); continue; } else if (cur_mpsv->data.a.argv != cur_mpsv->data.a.arg) { @@ -641,9 +643,8 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( } } assert(cur_tv != NULL); - if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE( - TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, cur_tv, copyID, objname) - == FAIL) { + if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, + cur_tv, copyID, objname) == FAIL) { goto encode_vim_to__error_ret; } } From affa3c2baa60acbffd32234326d38cced2d3f30c Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 25 Dec 2016 22:09:07 +0300 Subject: [PATCH 06/28] api/helpers: Fix unused variable error in release build --- src/nvim/api/private/helpers.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index ceb950c489..a195b8c0a1 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -522,6 +522,7 @@ Object vim_to_object(typval_T *obj) EncodedData edata = { .stack = KV_INITIAL_VALUE }; const int evo_ret = encode_vim_to_object(&edata, obj, "vim_to_object argument"); + (void)evo_ret; assert(evo_ret == OK); Object ret = kv_A(edata.stack, 0); assert(kv_size(edata.stack) == 1); From 759e736b0ab03034dc61d6a30e1b8b1f17ed9695 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 25 Dec 2016 23:29:35 +0300 Subject: [PATCH 07/28] eval/typval_encode: Fix infinite loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Occurs when trying to dump a partial with attached self dictionary which references that partial. “Infinite” loop should normally result in Neovim killed by OOM killer. Also moved the place when partials are unreferenced by clear_tv: from …FUNC_START to …FUNC_END. --- src/nvim/eval.c | 42 ++++++++---- src/nvim/eval/typval_encode.c.h | 96 ++++++++++++++++++++++------ src/nvim/eval/typval_encode.h | 22 +++++-- test/functional/eval/string_spec.lua | 13 ++++ 4 files changed, 134 insertions(+), 39 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 8f66976f1c..9546edc87f 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -19072,22 +19072,30 @@ void free_tv(typval_T *varp) #define TYPVAL_ENCODE_CONV_FUNC_START(fun, is_partial, pt) \ do { \ - if (is_partial) { \ - partial_unref(pt); \ - tv->vval.v_partial = NULL; \ - } else { \ + if (!is_partial) { \ func_unref(fun); \ if (fun != empty_string) { \ xfree(fun); \ } \ tv->vval.v_string = NULL; \ + tv->v_lock = VAR_UNLOCKED; \ } \ - tv->v_lock = VAR_UNLOCKED; \ } while (0) #define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(len) #define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(len) -#define TYPVAL_ENCODE_CONV_FUNC_END() +#define TYPVAL_ENCODE_CONV_FUNC_END() \ + do { \ + if (cur_mpsv->type == kMPConvPartial) { \ + typval_T *const cur_tv = cur_mpsv->tv; \ + partial_T *const pt = cur_mpsv->data.p.pt; \ + partial_unref(pt); \ + if (cur_tv != NULL) { \ + cur_tv->vval.v_partial = NULL; \ + cur_tv->v_lock = VAR_UNLOCKED; \ + } \ + } \ + } while (0) #define TYPVAL_ENCODE_CONV_EMPTY_LIST() \ do { \ @@ -19118,10 +19126,14 @@ void free_tv(typval_T *varp) #define TYPVAL_ENCODE_CONV_LIST_END() \ do { \ typval_T *const cur_tv = cur_mpsv->tv; \ - assert(cur_tv->v_type == VAR_LIST); \ - list_unref(cur_tv->vval.v_list); \ - cur_tv->vval.v_list = NULL; \ - cur_tv->v_lock = VAR_UNLOCKED; \ + list_T *const list = cur_mpsv->data.l.list; \ + list_unref(list); \ + if (cur_tv != NULL) { \ + assert(list == cur_tv->vval.v_list); \ + assert(cur_tv->v_type == VAR_LIST); \ + cur_tv->vval.v_list = NULL; \ + cur_tv->v_lock = VAR_UNLOCKED; \ + } \ } while (0) #define TYPVAL_ENCODE_CONV_DICT_START(ignored) \ @@ -19143,10 +19155,14 @@ void free_tv(typval_T *varp) #define TYPVAL_ENCODE_CONV_DICT_END() \ do { \ typval_T *const cur_tv = cur_mpsv->tv; \ - assert(cur_tv->v_type == VAR_DICT); \ + dict_T *const dict = cur_mpsv->data.d.dict; \ dict_unref(cur_tv->vval.v_dict); \ - cur_tv->vval.v_dict = NULL; \ - cur_tv->v_lock = VAR_UNLOCKED; \ + if (cur_tv != NULL) { \ + assert(dict == cur_tv->vval.v_dict); \ + assert(cur_tv->v_type == VAR_DICT); \ + cur_tv->vval.v_dict = NULL; \ + cur_tv->v_lock = VAR_UNLOCKED; \ + } \ } while (0) #define TYPVAL_ENCODE_CONV_RECURSE(ignored1, ignored2) diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index 597fd71030..95dc227450 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -166,7 +166,8 @@ /// /// After including this file it will define function /// `encode_vim_to_{TYPVAL_ENCODE_NAME}` with scope #TYPVAL_ENCODE_SCOPE and -/// static function `_{TYPVAL_ENCODE_NAME}_convert_one_value`. +/// static functions `_typval_encode_{TYPVAL_ENCODE_NAME}_convert_one_value` and +/// `_typval_encode_{TYPVAL_ENCODE_NAME}_check_self_reference`. /// @def TYPVAL_ENCODE_FIRST_ARG_TYPE /// @brief Type of the first argument, which will be used to return the results @@ -192,11 +193,50 @@ #include "nvim/func_attr.h" #include "nvim/eval/typval_encode.h" +static inline int _TYPVAL_ENCODE_CHECK_SELF_REFERENCE( + TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, + void *const val, int *const val_copyID, + const MPConvStack *const mpstack, const int copyID, + const MPConvStackValType conv_type, + const char *const objname) + REAL_FATTR_NONNULL_ARG(2, 3, 4, 7) REAL_FATTR_WARN_UNUSED_RESULT + REAL_FATTR_ALWAYS_INLINE; + +/// Function for checking whether container references itself +/// +/// @param TYPVAL_ENCODE_FIRST_ARG_NAME First argument. +/// @param[in,out] val Container to check. +/// @param val_copyID Pointer to the container attribute that holds copyID. +/// After checking whether value of this attribute is +/// copyID (variable) it is set to copyID. +/// @param[in] mpstack Stack with values to convert. Read-only, used for error +/// reporting. +/// @param[in] copyID CopyID used by the caller. +/// @param[in] conv_type Type of the conversion, @see MPConvStackValType. +/// @param[in] objname Object name, used for error reporting. +/// +/// @return NOTDONE in case of success, what to return in case of failure. +static inline int _TYPVAL_ENCODE_CHECK_SELF_REFERENCE( + TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, + void *const val, int *const val_copyID, + const MPConvStack *const mpstack, const int copyID, + const MPConvStackValType conv_type, + const char *const objname) +{ + if (*val_copyID == copyID) { + TYPVAL_ENCODE_CONV_RECURSE(val, conv_type); + return OK; + } + *val_copyID = copyID; + return NOTDONE; +} + static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, - MPConvStack *const mpstack, typval_T *const tv, const int copyID, + MPConvStack *const mpstack, MPConvStackVal *const cur_mpsv, + typval_T *const tv, const int copyID, const char *const objname) - REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT; + REAL_FATTR_NONNULL_ARG(2, 4, 6) REAL_FATTR_WARN_UNUSED_RESULT; /// Convert single value /// @@ -206,9 +246,10 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( /// @param TYPVAL_ENCODE_FIRST_ARG_NAME First argument, defined by the /// includer. Only meaningful to macros /// defined by the includer. -/// @param[out] mpstack Stack with values to convert. Values which are not -/// converted completely by this function (i.e. -/// non-scalars) are pushed here. +/// @param mpstack Stack with values to convert. Values which are not +/// converted completely by this function (i.e. +/// non-scalars) are pushed here. +/// @param cur_mpsv Currently converted value from stack. /// @param tv Converted value. /// @param[in] copyID CopyID. /// @param[in] objname Object name, used for error reporting. @@ -216,7 +257,8 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( /// @return OK in case of success, FAIL in case of failure. static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, - MPConvStack *const mpstack, typval_T *const tv, const int copyID, + MPConvStack *const mpstack, MPConvStackVal *const cur_mpsv, + typval_T *const tv, const int copyID, const char *const objname) { switch (tv->v_type) { @@ -260,8 +302,8 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( TYPVAL_ENCODE_CONV_EMPTY_LIST(); break; } - _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(tv->vval.v_list, lv_copyID, copyID, - kMPConvList); + _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_list, lv_copyID, copyID, + kMPConvList); TYPVAL_ENCODE_CONV_LIST_START(tv->vval.v_list->lv_len); _mp_push(*mpstack, ((MPConvStackVal) { .type = kMPConvList, @@ -392,8 +434,9 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( if (val_di->di_tv.v_type != VAR_LIST) { goto _convert_one_value_regular_dict; } - _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(val_di->di_tv.vval.v_list, - lv_copyID, copyID, kMPConvList); + _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_di->di_tv.vval.v_list, + lv_copyID, copyID, + kMPConvList); TYPVAL_ENCODE_CONV_LIST_START(val_di->di_tv.vval.v_list->lv_len); _mp_push(*mpstack, ((MPConvStackVal) { .tv = tv, @@ -423,8 +466,8 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( goto _convert_one_value_regular_dict; } } - _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(val_list, lv_copyID, copyID, - kMPConvPairs); + _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_list, lv_copyID, copyID, + kMPConvPairs); TYPVAL_ENCODE_CONV_DICT_START(val_list->lv_len); _mp_push(*mpstack, ((MPConvStackVal) { .tv = tv, @@ -464,8 +507,8 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( break; } _convert_one_value_regular_dict: - _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(tv->vval.v_dict, dv_copyID, copyID, - kMPConvDict); + _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_dict, dv_copyID, copyID, + kMPConvDict); TYPVAL_ENCODE_CONV_DICT_START(tv->vval.v_dict->dv_hashtab.ht_used); _mp_push(*mpstack, ((MPConvStackVal) { .tv = tv, @@ -491,7 +534,7 @@ _convert_one_value_regular_dict: TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, typval_T *const tv, const char *const objname) - REAL_FATTR_WARN_UNUSED_RESULT; + REAL_FATTR_NONNULL_ARG(2, 3) REAL_FATTR_WARN_UNUSED_RESULT; /// Convert the whole typval /// @@ -510,6 +553,7 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( MPConvStack mpstack; _mp_init(mpstack); if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, + NULL, tv, copyID, objname) == FAIL) { goto encode_vim_to__error_ret; @@ -566,7 +610,7 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK( encode_vim_to__error_ret, kv_pair->lv_first->li_tv); if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, - &mpstack, + &mpstack, cur_mpsv, &kv_pair->lv_first->li_tv, copyID, objname) == FAIL) { @@ -587,7 +631,7 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( TYPVAL_ENCODE_CONV_LIST_START(pt->pt_argc); _mp_push(mpstack, ((MPConvStackVal) { .type = kMPConvPartialList, - .tv = tv, + .tv = NULL, .data = { .a = { .arg = pt->pt_argv, @@ -604,10 +648,21 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( dict_T *const dict = pt == NULL ? NULL : pt->pt_dict; if (dict != NULL) { TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(dict->dv_hashtab.ht_used); + const int te_csr_ret = _TYPVAL_ENCODE_CHECK_SELF_REFERENCE( + TYPVAL_ENCODE_FIRST_ARG_NAME, + dict, &dict->dv_copyID, &mpstack, copyID, kMPConvDict, + objname); + if (te_csr_ret != NOTDONE) { + if (te_csr_ret == FAIL) { + goto encode_vim_to__error_ret; + } else { + continue; + } + } TYPVAL_ENCODE_CONV_DICT_START(dict->dv_hashtab.ht_used); _mp_push(mpstack, ((MPConvStackVal) { .type = kMPConvDict, - .tv = tv, + .tv = NULL, .data = { .d = { .dict = dict, @@ -644,7 +699,8 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( } assert(cur_tv != NULL); if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, - cur_tv, copyID, objname) == FAIL) { + cur_mpsv, cur_tv, copyID, objname) + == FAIL) { goto encode_vim_to__error_ret; } } diff --git a/src/nvim/eval/typval_encode.h b/src/nvim/eval/typval_encode.h index 8fb37de93e..9208cdc752 100644 --- a/src/nvim/eval/typval_encode.h +++ b/src/nvim/eval/typval_encode.h @@ -249,16 +249,26 @@ static inline size_t tv_strlen(const typval_T *const tv) /// copyID (variable) it is set to copyID. /// @param[in] copyID CopyID used by the caller. /// @param conv_type Type of the conversion, @see MPConvStackValType. -#define _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(val, copyID_attr, copyID, \ - conv_type) \ +#define _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val, copyID_attr, copyID, \ + conv_type) \ do { \ - if ((val)->copyID_attr == (copyID)) { \ - TYPVAL_ENCODE_CONV_RECURSE((val), conv_type); \ - return OK; \ + const int te_csr_ret = _TYPVAL_ENCODE_CHECK_SELF_REFERENCE( \ + TYPVAL_ENCODE_FIRST_ARG_NAME, \ + (val), &(val)->copyID_attr, mpstack, copyID, conv_type, objname); \ + if (te_csr_ret != NOTDONE) { \ + return te_csr_ret; \ } \ - (val)->copyID_attr = (copyID); \ } 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) + +/// 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) diff --git a/test/functional/eval/string_spec.lua b/test/functional/eval/string_spec.lua index 9e2dc4e111..1f963c674e 100644 --- a/test/functional/eval/string_spec.lua +++ b/test/functional/eval/string_spec.lua @@ -9,6 +9,7 @@ local redir_exec = helpers.redir_exec local funcs = helpers.funcs local write_file = helpers.write_file local NIL = helpers.NIL +local source = helpers.source describe('string() function', function() before_each(clear) @@ -137,6 +138,18 @@ describe('string() function', function() it('dumps references to script functions', function() eq('function(\'1_Test2\')', eval('string(Test2_f)')) end) + + it('dumps partials with self referencing a partial', function() + source([[ + function TestDict() dict + endfunction + let d = {} + let TestDictRef = function('TestDict', d) + let d.tdr = TestDictRef + ]]) + eq("\nE724: unable to correctly dump variable with self-referencing container\nfunction('TestDict', {'tdr': function('TestDict', {E724@1})})", + redir_exec('echo string(d.tdr)')) + end) end) describe('used to represent lists', function() From 901e7805ee1488205d3ad1bf5dd93f2799ba8090 Mon Sep 17 00:00:00 2001 From: ZyX Date: Mon, 26 Dec 2016 02:15:45 +0300 Subject: [PATCH 08/28] eval: Fix case when cur_mpsv is NULL Should only happen when clearing VAR_FUNC typval which is not placed inside a container. --- src/nvim/eval.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 9546edc87f..8f134de8df 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -19086,7 +19086,8 @@ void free_tv(typval_T *varp) #define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(len) #define TYPVAL_ENCODE_CONV_FUNC_END() \ do { \ - if (cur_mpsv->type == kMPConvPartial) { \ + assert(cur_mpsv != NULL || tv->v_type == VAR_FUNC); \ + if (cur_mpsv != NULL && cur_mpsv->type == kMPConvPartial) { \ typval_T *const cur_tv = cur_mpsv->tv; \ partial_T *const pt = cur_mpsv->data.p.pt; \ partial_unref(pt); \ From e2d81cc479b53a54f76221d345ec04c3a913387c Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 3 Jan 2017 04:11:34 +0300 Subject: [PATCH 09/28] eval: Do not free partial lists as lists --- src/nvim/eval.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 8f134de8df..80551c3314 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -19126,6 +19126,9 @@ void free_tv(typval_T *varp) #define TYPVAL_ENCODE_CONV_LIST_END() \ do { \ + if (cur_mpsv->type == kMPConvPartialList) { \ + break; \ + } \ typval_T *const cur_tv = cur_mpsv->tv; \ list_T *const list = cur_mpsv->data.l.list; \ list_unref(list); \ From 67b53361bacf1f14951f0b8c2419dbdb99b19459 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 3 Jan 2017 04:29:25 +0300 Subject: [PATCH 10/28] eval/typval_encode: Rename some \*tv variables Renames `tv` function argument to `top_tv` and `cur_tv` variable to `tv`, so `tv` will mean something more or less the same in both _TYPVAL_ENCODE_CONVERT_ONE_VALUE and _TYPVAL_ENCODE_ENCODE functions. --- src/nvim/eval/typval_encode.c.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index 95dc227450..5c807bb169 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -541,26 +541,26 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( /// @param TYPVAL_ENCODE_FIRST_ARG_NAME First argument, defined by the /// includer. Only meaningful to macros /// defined by the includer. -/// @param tv Converted value. +/// @param top_tv Converted value. /// @param[in] objname Object name, used for error reporting. /// /// @return OK in case of success, FAIL in case of failure. TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, - typval_T *const tv, const char *const objname) + typval_T *const top_tv, const char *const objname) { const int copyID = get_copyID(); MPConvStack mpstack; _mp_init(mpstack); if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, NULL, - tv, copyID, objname) + top_tv, copyID, objname) == FAIL) { goto encode_vim_to__error_ret; } while (_mp_size(mpstack)) { MPConvStackVal *cur_mpsv = &_mp_last(mpstack); - typval_T *cur_tv = NULL; + typval_T *tv = NULL; switch (cur_mpsv->type) { case kMPConvDict: { if (!cur_mpsv->data.d.todo) { @@ -581,7 +581,7 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( TYPVAL_ENCODE_CONV_STR_STRING(&di->di_key[0], strlen((char *)&di->di_key[0])); TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(); - cur_tv = &di->di_tv; + tv = &di->di_tv; break; } case kMPConvList: { @@ -593,7 +593,7 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( } else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) { TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(); } - cur_tv = &cur_mpsv->data.l.li->li_tv; + tv = &cur_mpsv->data.l.li->li_tv; cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; break; } @@ -617,7 +617,7 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( goto encode_vim_to__error_ret; } TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(); - cur_tv = &kv_pair->lv_last->li_tv; + tv = &kv_pair->lv_last->li_tv; cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; break; } @@ -692,14 +692,14 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( } else if (cur_mpsv->data.a.argv != cur_mpsv->data.a.arg) { TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(); } - cur_tv = cur_mpsv->data.a.arg++; + tv = cur_mpsv->data.a.arg++; cur_mpsv->data.a.todo--; break; } } - assert(cur_tv != NULL); + assert(tv != NULL); if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, - cur_mpsv, cur_tv, copyID, objname) + cur_mpsv, tv, copyID, objname) == FAIL) { goto encode_vim_to__error_ret; } From 5ba24318e2e16da57d2463df83ef0086446221c4 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 3 Jan 2017 04:30:35 +0300 Subject: [PATCH 11/28] eval: Do not free partial contents if partial is still referenced Should fix some tests, including core/job_partial tests. --- src/nvim/eval.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 80551c3314..7b1365785f 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -19072,13 +19072,20 @@ void free_tv(typval_T *varp) #define TYPVAL_ENCODE_CONV_FUNC_START(fun, is_partial, pt) \ do { \ - if (!is_partial) { \ + partial_T *const pt_ = (pt); \ + tv->v_lock = VAR_UNLOCKED; \ + if (is_partial) { \ + if (pt_ != NULL && pt_->pt_refcount > 1) { \ + pt_->pt_refcount--; \ + tv->vval.v_partial = NULL; \ + return OK; \ + } \ + } else { \ func_unref(fun); \ if (fun != empty_string) { \ xfree(fun); \ } \ tv->vval.v_string = NULL; \ - tv->v_lock = VAR_UNLOCKED; \ } \ } while (0) @@ -19114,10 +19121,10 @@ void free_tv(typval_T *varp) #define TYPVAL_ENCODE_CONV_LIST_START(ignored) \ do { \ + tv->v_lock = VAR_UNLOCKED; \ if (tv->vval.v_list->lv_refcount > 1) { \ tv->vval.v_list->lv_refcount--; \ tv->vval.v_list = NULL; \ - tv->v_lock = VAR_UNLOCKED; \ return OK; \ } \ } while (0) @@ -19136,16 +19143,15 @@ void free_tv(typval_T *varp) assert(list == cur_tv->vval.v_list); \ assert(cur_tv->v_type == VAR_LIST); \ cur_tv->vval.v_list = NULL; \ - cur_tv->v_lock = VAR_UNLOCKED; \ } \ } while (0) #define TYPVAL_ENCODE_CONV_DICT_START(ignored) \ do { \ + tv->v_lock = VAR_UNLOCKED; \ if (tv->vval.v_dict->dv_refcount > 1) { \ tv->vval.v_dict->dv_refcount--; \ tv->vval.v_dict = NULL; \ - tv->v_lock = VAR_UNLOCKED; \ return OK; \ } \ } while (0) @@ -19165,7 +19171,6 @@ void free_tv(typval_T *varp) assert(dict == cur_tv->vval.v_dict); \ assert(cur_tv->v_type == VAR_DICT); \ cur_tv->vval.v_dict = NULL; \ - cur_tv->v_lock = VAR_UNLOCKED; \ } \ } while (0) From ff8944105d50f34ca114c4ebeaee6df8cb029dd5 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 3 Jan 2017 06:35:32 +0300 Subject: [PATCH 12/28] eval/typval_encode: Refactor arguments to argument macroses Fixed local test failures somewhere in process. --- src/nvim/api/private/helpers.c | 42 +++---- src/nvim/eval.c | 212 +++++++++++++++++++------------- src/nvim/eval/encode.c | 116 ++++++++--------- src/nvim/eval/typval_encode.c.h | 165 ++++++++++++++++--------- src/nvim/eval/typval_encode.h | 9 ++ 5 files changed, 317 insertions(+), 227 deletions(-) diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index a195b8c0a1..5bd76bc238 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -326,21 +326,21 @@ void set_option_to(void *to, int type, String name, Object value, Error *err) #define TYPVAL_ENCODE_ALLOW_SPECIALS false -#define TYPVAL_ENCODE_CONV_NIL() \ +#define TYPVAL_ENCODE_CONV_NIL(tv) \ kv_push(edata->stack, NIL) -#define TYPVAL_ENCODE_CONV_BOOL(num) \ +#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \ kv_push(edata->stack, BOOLEAN_OBJ((Boolean)(num))) -#define TYPVAL_ENCODE_CONV_NUMBER(num) \ +#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \ kv_push(edata->stack, INTEGER_OBJ((Integer)(num))) #define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER TYPVAL_ENCODE_CONV_NUMBER -#define TYPVAL_ENCODE_CONV_FLOAT(flt) \ +#define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \ kv_push(edata->stack, FLOATING_OBJ((Float)(flt))) -#define TYPVAL_ENCODE_CONV_STRING(str, len) \ +#define TYPVAL_ENCODE_CONV_STRING(tv, str, len) \ do { \ const size_t len_ = (size_t)(len); \ const char *const str_ = (const char *)(str); \ @@ -353,20 +353,20 @@ void set_option_to(void *to, int type, String name, Object value, Error *err) #define TYPVAL_ENCODE_CONV_STR_STRING TYPVAL_ENCODE_CONV_STRING -#define TYPVAL_ENCODE_CONV_EXT_STRING(str, len, type) \ +#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, str, len, type) \ TYPVAL_ENCODE_CONV_NIL() -#define TYPVAL_ENCODE_CONV_FUNC_START(fun, is_partial, pt) \ +#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ TYPVAL_ENCODE_CONV_NIL() -#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(len) -#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(len) -#define TYPVAL_ENCODE_CONV_FUNC_END() +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, len) +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, len) +#define TYPVAL_ENCODE_CONV_FUNC_END(tv) -#define TYPVAL_ENCODE_CONV_EMPTY_LIST() \ +#define TYPVAL_ENCODE_CONV_EMPTY_LIST(tv) \ kv_push(edata->stack, ARRAY_OBJ(((Array) { .capacity = 0, .size = 0 }))) -#define TYPVAL_ENCODE_CONV_EMPTY_DICT() \ +#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv) \ kv_push(edata->stack, \ DICTIONARY_OBJ(((Dictionary) { .capacity = 0, .size = 0 }))) @@ -381,7 +381,7 @@ static inline void typval_encode_list_start(EncodedData *const edata, }))); } -#define TYPVAL_ENCODE_CONV_LIST_START(len) \ +#define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \ typval_encode_list_start(edata, (size_t)(len)) static inline void typval_encode_between_list_items(EncodedData *const edata) @@ -394,7 +394,7 @@ static inline void typval_encode_between_list_items(EncodedData *const edata) list->data.array.items[list->data.array.size++] = item; } -#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS() \ +#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(tv) \ typval_encode_between_list_items(edata) static inline void typval_encode_list_end(EncodedData *const edata) @@ -407,7 +407,7 @@ static inline void typval_encode_list_end(EncodedData *const edata) #endif } -#define TYPVAL_ENCODE_CONV_LIST_END() \ +#define TYPVAL_ENCODE_CONV_LIST_END(tv) \ typval_encode_list_end(edata) static inline void typval_encode_dict_start(EncodedData *const edata, @@ -421,10 +421,10 @@ static inline void typval_encode_dict_start(EncodedData *const edata, }))); } -#define TYPVAL_ENCODE_CONV_DICT_START(len) \ +#define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) \ typval_encode_dict_start(edata, (size_t)(len)) -#define TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK(label, kv_pair) +#define TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(label, kv_pair) static inline void typval_encode_after_key(EncodedData *const edata) FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL @@ -443,7 +443,7 @@ static inline void typval_encode_after_key(EncodedData *const edata) } } -#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY() \ +#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(tv, dict) \ typval_encode_after_key(edata) static inline void typval_encode_between_dict_items(EncodedData *const edata) @@ -456,7 +456,7 @@ static inline void typval_encode_between_dict_items(EncodedData *const edata) dict->data.dictionary.items[dict->data.dictionary.size++].value = val; } -#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS() \ +#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, dict) \ typval_encode_between_dict_items(edata) static inline void typval_encode_dict_end(EncodedData *const edata) @@ -469,7 +469,7 @@ static inline void typval_encode_dict_end(EncodedData *const edata) #endif } -#define TYPVAL_ENCODE_CONV_DICT_END() \ +#define TYPVAL_ENCODE_CONV_DICT_END(tv, dict) \ typval_encode_dict_end(edata) #define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \ @@ -506,7 +506,7 @@ static inline void typval_encode_dict_end(EncodedData *const edata) #undef TYPVAL_ENCODE_CONV_DICT_END #undef TYPVAL_ENCODE_CONV_DICT_AFTER_KEY #undef TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS -#undef TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK +#undef TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK #undef TYPVAL_ENCODE_CONV_LIST_END #undef TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS #undef TYPVAL_ENCODE_CONV_RECURSE diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 7b1365785f..79134d748a 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -19034,147 +19034,185 @@ void free_tv(typval_T *varp) #define TYPVAL_ENCODE_ALLOW_SPECIALS false -#define TYPVAL_ENCODE_CONV_NIL() \ +#define TYPVAL_ENCODE_CONV_NIL(tv) \ do { \ tv->vval.v_special = kSpecialVarFalse; \ tv->v_lock = VAR_UNLOCKED; \ } while (0) -#define TYPVAL_ENCODE_CONV_BOOL(ignored) \ - TYPVAL_ENCODE_CONV_NIL() +#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \ + TYPVAL_ENCODE_CONV_NIL(tv) -#define TYPVAL_ENCODE_CONV_NUMBER(ignored) \ +#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \ do { \ - (void)ignored; \ + (void)num; \ tv->vval.v_number = 0; \ tv->v_lock = VAR_UNLOCKED; \ } while (0) -#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(ignored) \ - assert(false) +#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, num) -#define TYPVAL_ENCODE_CONV_FLOAT(ignored) \ +#define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \ do { \ tv->vval.v_float = 0; \ tv->v_lock = VAR_UNLOCKED; \ } while (0) -#define TYPVAL_ENCODE_CONV_STRING(str, ignored) \ +#define TYPVAL_ENCODE_CONV_STRING(tv, buf, len) \ do { \ - xfree(str); \ + xfree(buf); \ tv->vval.v_string = NULL; \ tv->v_lock = VAR_UNLOCKED; \ } while (0) -#define TYPVAL_ENCODE_CONV_STR_STRING(ignored1, ignored2) +#define TYPVAL_ENCODE_CONV_STR_STRING(tv, buf, len) -#define TYPVAL_ENCODE_CONV_EXT_STRING(ignored1, ignored2, ignored3) +#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type) -#define TYPVAL_ENCODE_CONV_FUNC_START(fun, is_partial, pt) \ +static inline int _nothing_conv_func_start(typval_T *const tv, + char_u *const fun) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ARG(1) +{ + tv->v_lock = VAR_UNLOCKED; + if (tv->v_type == VAR_PARTIAL) { + partial_T *const pt_ = tv->vval.v_partial; + if (pt_ != NULL && pt_->pt_refcount > 1) { + pt_->pt_refcount--; + tv->vval.v_partial = NULL; + return OK; + } + } else { + func_unref(fun); + if (fun != empty_string) { + xfree(fun); + } + tv->vval.v_string = NULL; + } + return NOTDONE; +} +#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ do { \ - partial_T *const pt_ = (pt); \ - tv->v_lock = VAR_UNLOCKED; \ - if (is_partial) { \ - if (pt_ != NULL && pt_->pt_refcount > 1) { \ - pt_->pt_refcount--; \ - tv->vval.v_partial = NULL; \ - return OK; \ - } \ - } else { \ - func_unref(fun); \ - if (fun != empty_string) { \ - xfree(fun); \ - } \ - tv->vval.v_string = NULL; \ + if (_nothing_conv_func_start(tv, fun) != NOTDONE) { \ + return OK; \ } \ } while (0) -#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(len) -#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(len) -#define TYPVAL_ENCODE_CONV_FUNC_END() \ - do { \ - assert(cur_mpsv != NULL || tv->v_type == VAR_FUNC); \ - if (cur_mpsv != NULL && cur_mpsv->type == kMPConvPartial) { \ - typval_T *const cur_tv = cur_mpsv->tv; \ - partial_T *const pt = cur_mpsv->data.p.pt; \ - partial_unref(pt); \ - if (cur_tv != NULL) { \ - cur_tv->vval.v_partial = NULL; \ - cur_tv->v_lock = VAR_UNLOCKED; \ - } \ - } \ - } while (0) +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, len) +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, len) -#define TYPVAL_ENCODE_CONV_EMPTY_LIST() \ +static inline void _nothing_conv_func_end(typval_T *const tv) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL +{ + if (tv->v_type == VAR_PARTIAL) { + partial_T *const pt = tv->vval.v_partial; + if (pt == NULL) { + return; + } + // Dictionaly should already be freed by the time. + assert(pt->pt_dict == NULL); + // As well as all arguments. + pt->pt_argc = 0; + assert(pt->pt_refcount <= 1); + partial_unref(pt); + tv->vval.v_partial = NULL; + assert(tv->v_lock == VAR_UNLOCKED); + } +} +#define TYPVAL_ENCODE_CONV_FUNC_END(tv) _nothing_conv_func_end(tv) + +#define TYPVAL_ENCODE_CONV_EMPTY_LIST(tv) \ do { \ list_unref(tv->vval.v_list); \ tv->vval.v_list = NULL; \ tv->v_lock = VAR_UNLOCKED; \ } while (0) -#define TYPVAL_ENCODE_CONV_EMPTY_DICT() \ +#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv) \ do { \ dict_unref(tv->vval.v_dict); \ tv->vval.v_dict = NULL; \ tv->v_lock = VAR_UNLOCKED; \ } while (0) -#define TYPVAL_ENCODE_CONV_LIST_START(ignored) \ +static inline int _nothing_conv_list_start(typval_T *const tv) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (tv == NULL) { + return NOTDONE; + } + tv->v_lock = VAR_UNLOCKED; + if (tv->vval.v_list->lv_refcount > 1) { + tv->vval.v_list->lv_refcount--; + tv->vval.v_list = NULL; + return OK; + } + return NOTDONE; +} +#define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \ do { \ - tv->v_lock = VAR_UNLOCKED; \ - if (tv->vval.v_list->lv_refcount > 1) { \ - tv->vval.v_list->lv_refcount--; \ - tv->vval.v_list = NULL; \ + if (_nothing_conv_list_start(tv) != NOTDONE) { \ return OK; \ } \ } while (0) -#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS() +#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(tv) -#define TYPVAL_ENCODE_CONV_LIST_END() \ - do { \ - if (cur_mpsv->type == kMPConvPartialList) { \ - break; \ - } \ - typval_T *const cur_tv = cur_mpsv->tv; \ - list_T *const list = cur_mpsv->data.l.list; \ - list_unref(list); \ - if (cur_tv != NULL) { \ - assert(list == cur_tv->vval.v_list); \ - assert(cur_tv->v_type == VAR_LIST); \ - cur_tv->vval.v_list = NULL; \ - } \ - } while (0) +static inline void _nothing_conv_list_end(typval_T *const tv) + FUNC_ATTR_ALWAYS_INLINE +{ + if (tv == NULL) { + return; + } + assert(tv->v_type == VAR_LIST); + list_T *const list = tv->vval.v_list; + list_unref(list); + tv->vval.v_list = NULL; +} +#define TYPVAL_ENCODE_CONV_LIST_END(tv) _nothing_conv_list_end(tv) -#define TYPVAL_ENCODE_CONV_DICT_START(ignored) \ +static inline int _nothing_conv_dict_start(typval_T *const tv, + dict_T **const dictp, + const void *const nodictvar) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (tv != NULL) { + tv->v_lock = VAR_UNLOCKED; + } + if ((const void *)dictp != nodictvar && (*dictp)->dv_refcount > 1) { + (*dictp)->dv_refcount--; + *dictp = NULL; + return OK; + } + return NOTDONE; +} +#define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) \ do { \ - tv->v_lock = VAR_UNLOCKED; \ - if (tv->vval.v_dict->dv_refcount > 1) { \ - tv->vval.v_dict->dv_refcount--; \ - tv->vval.v_dict = NULL; \ + if (_nothing_conv_dict_start(tv, (dict_T **)&dict, \ + (void *)&TYPVAL_ENCODE_NODICT_VAR) \ + != NOTDONE) { \ return OK; \ } \ } while (0) -#define TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK(ignored1, ignored2) +#define TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(tv, dict) +#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(tv, dict) +#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, dict) -#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY() +static inline void _nothing_conv_dict_end(typval_T *const tv, + dict_T **const dictp, + const void *const nodictvar) + FUNC_ATTR_ALWAYS_INLINE +{ + if ((const void *)dictp != nodictvar) { + dict_unref(*dictp); + *dictp = NULL; + } +} +#define TYPVAL_ENCODE_CONV_DICT_END(tv, dict) \ + _nothing_conv_dict_end(tv, (dict_T **)&dict, \ + (void *)&TYPVAL_ENCODE_NODICT_VAR) -#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS() - -#define TYPVAL_ENCODE_CONV_DICT_END() \ - do { \ - typval_T *const cur_tv = cur_mpsv->tv; \ - dict_T *const dict = cur_mpsv->data.d.dict; \ - dict_unref(cur_tv->vval.v_dict); \ - if (cur_tv != NULL) { \ - assert(dict == cur_tv->vval.v_dict); \ - assert(cur_tv->v_type == VAR_DICT); \ - cur_tv->vval.v_dict = NULL; \ - } \ - } while (0) - -#define TYPVAL_ENCODE_CONV_RECURSE(ignored1, ignored2) +#define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) #define TYPVAL_ENCODE_SCOPE static #define TYPVAL_ENCODE_NAME nothing @@ -19207,7 +19245,7 @@ void free_tv(typval_T *varp) #undef TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS #undef TYPVAL_ENCODE_CONV_LIST_END #undef TYPVAL_ENCODE_CONV_DICT_START -#undef TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK +#undef TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK #undef TYPVAL_ENCODE_CONV_DICT_AFTER_KEY #undef TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS #undef TYPVAL_ENCODE_CONV_DICT_END diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 38f0863195..73133b7b29 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -280,7 +280,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, : OK); } -#define TYPVAL_ENCODE_CONV_STRING(buf, len) \ +#define TYPVAL_ENCODE_CONV_STRING(tv, buf, len) \ do { \ const char *const buf_ = (const char *) buf; \ if (buf == NULL) { \ @@ -299,19 +299,19 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, } \ } while (0) -#define TYPVAL_ENCODE_CONV_STR_STRING(buf, len) \ - TYPVAL_ENCODE_CONV_STRING(buf, len) +#define TYPVAL_ENCODE_CONV_STR_STRING(tv, buf, len) \ + TYPVAL_ENCODE_CONV_STRING(tv, buf, len) -#define TYPVAL_ENCODE_CONV_EXT_STRING(buf, len, type) +#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type) -#define TYPVAL_ENCODE_CONV_NUMBER(num) \ +#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \ do { \ char numbuf[NUMBUFLEN]; \ vim_snprintf(numbuf, ARRAY_SIZE(numbuf), "%" PRId64, (int64_t) (num)); \ ga_concat(gap, numbuf); \ } while (0) -#define TYPVAL_ENCODE_CONV_FLOAT(flt) \ +#define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \ do { \ const float_T flt_ = (flt); \ switch (fpclassify(flt_)) { \ @@ -334,65 +334,65 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, } \ } while (0) -#define TYPVAL_ENCODE_CONV_FUNC_START(fun, is_partial, pt) \ +#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ do { \ ga_concat(gap, "function("); \ - TYPVAL_ENCODE_CONV_STRING(fun, STRLEN(fun)); \ + TYPVAL_ENCODE_CONV_STRING(tv, fun, STRLEN(fun)); \ } while (0) -#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(len) \ +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, len) \ do { \ if (len != 0) { \ ga_concat(gap, ", "); \ } \ } while (0) -#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(len) \ +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, len) \ do { \ if ((ptrdiff_t)len != -1) { \ ga_concat(gap, ", "); \ } \ } while (0) -#define TYPVAL_ENCODE_CONV_FUNC_END() \ +#define TYPVAL_ENCODE_CONV_FUNC_END(tv) \ ga_append(gap, ')') -#define TYPVAL_ENCODE_CONV_EMPTY_LIST() \ +#define TYPVAL_ENCODE_CONV_EMPTY_LIST(tv) \ ga_concat(gap, "[]") -#define TYPVAL_ENCODE_CONV_LIST_START(len) \ +#define TYPVAL_ENCODE_CONV_LIST_START(tv, dict) \ ga_append(gap, '[') -#define TYPVAL_ENCODE_CONV_EMPTY_DICT() \ +#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv) \ ga_concat(gap, "{}") -#define TYPVAL_ENCODE_CONV_NIL() \ +#define TYPVAL_ENCODE_CONV_NIL(tv) \ ga_concat(gap, "v:null") -#define TYPVAL_ENCODE_CONV_BOOL(num) \ +#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \ ga_concat(gap, ((num)? "v:true": "v:false")) -#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(num) +#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, num) -#define TYPVAL_ENCODE_CONV_DICT_START(len) \ +#define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) \ ga_append(gap, '{') -#define TYPVAL_ENCODE_CONV_DICT_END() \ +#define TYPVAL_ENCODE_CONV_DICT_END(tv, dict) \ ga_append(gap, '}') -#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY() \ +#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(tv, dict) \ ga_concat(gap, ": ") -#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS() \ +#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, dict) \ ga_concat(gap, ", ") -#define TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK(label, key) +#define TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(label, key) -#define TYPVAL_ENCODE_CONV_LIST_END() \ +#define TYPVAL_ENCODE_CONV_LIST_END(tv) \ ga_append(gap, ']') -#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS() \ - TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS() +#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(tv) \ + TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, NULL) #define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \ do { \ @@ -493,15 +493,15 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, #define TYPVAL_ENCODE_ALLOW_SPECIALS true #undef TYPVAL_ENCODE_CONV_NIL -#define TYPVAL_ENCODE_CONV_NIL() \ +#define TYPVAL_ENCODE_CONV_NIL(tv) \ ga_concat(gap, "null") #undef TYPVAL_ENCODE_CONV_BOOL -#define TYPVAL_ENCODE_CONV_BOOL(num) \ +#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \ ga_concat(gap, ((num)? "true": "false")) #undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER -#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(num) \ +#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, num) \ do { \ char numbuf[NUMBUFLEN]; \ vim_snprintf(numbuf, ARRAY_SIZE(numbuf), "%" PRIu64, (num)); \ @@ -509,7 +509,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, } while (0) #undef TYPVAL_ENCODE_CONV_FLOAT -#define TYPVAL_ENCODE_CONV_FLOAT(flt) \ +#define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \ do { \ const float_T flt_ = (flt); \ switch (fpclassify(flt_)) { \ @@ -698,7 +698,7 @@ static inline int convert_to_json_string(garray_T *const gap, } #undef TYPVAL_ENCODE_CONV_STRING -#define TYPVAL_ENCODE_CONV_STRING(buf, len) \ +#define TYPVAL_ENCODE_CONV_STRING(tv, buf, len) \ do { \ if (convert_to_json_string(gap, (const char *) (buf), (len)) != OK) { \ return FAIL; \ @@ -706,7 +706,7 @@ static inline int convert_to_json_string(garray_T *const gap, } while (0) #undef TYPVAL_ENCODE_CONV_EXT_STRING -#define TYPVAL_ENCODE_CONV_EXT_STRING(buf, len, type) \ +#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type) \ do { \ xfree(buf); \ EMSG(_("E474: Unable to convert EXT string to JSON")); \ @@ -714,7 +714,7 @@ static inline int convert_to_json_string(garray_T *const gap, } while (0) #undef TYPVAL_ENCODE_CONV_FUNC_START -#define TYPVAL_ENCODE_CONV_FUNC_START(fun, is_partial, pt) \ +#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ return conv_error(_("E474: Error while dumping %s, %s: " \ "attempt to dump function reference"), \ mpstack, objname) @@ -757,8 +757,8 @@ bool encode_check_json_key(const typval_T *const tv) return true; } -#undef TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK -#define TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK(label, key) \ +#undef TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK +#define TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(label, key) \ do { \ if (!encode_check_json_key(&key)) { \ EMSG(_("E474: Invalid key in special dictionary")); \ @@ -797,7 +797,7 @@ bool encode_check_json_key(const typval_T *const tv) #undef TYPVAL_ENCODE_CONV_DICT_END #undef TYPVAL_ENCODE_CONV_DICT_AFTER_KEY #undef TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS -#undef TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK +#undef TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK #undef TYPVAL_ENCODE_CONV_LIST_END #undef TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS #undef TYPVAL_ENCODE_CONV_RECURSE @@ -875,7 +875,7 @@ char *encode_tv2json(typval_T *tv, size_t *len) return (char *) ga.ga_data; } -#define TYPVAL_ENCODE_CONV_STRING(buf, len) \ +#define TYPVAL_ENCODE_CONV_STRING(tv, buf, len) \ do { \ if (buf == NULL) { \ msgpack_pack_bin(packer, 0); \ @@ -886,7 +886,7 @@ char *encode_tv2json(typval_T *tv, size_t *len) } \ } while (0) -#define TYPVAL_ENCODE_CONV_STR_STRING(buf, len) \ +#define TYPVAL_ENCODE_CONV_STR_STRING(tv, buf, len) \ do { \ if (buf == NULL) { \ msgpack_pack_str(packer, 0); \ @@ -897,7 +897,7 @@ char *encode_tv2json(typval_T *tv, size_t *len) } \ } while (0) -#define TYPVAL_ENCODE_CONV_EXT_STRING(buf, len, type) \ +#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type) \ do { \ if (buf == NULL) { \ msgpack_pack_ext(packer, 0, (int8_t) type); \ @@ -908,34 +908,34 @@ char *encode_tv2json(typval_T *tv, size_t *len) } \ } while (0) -#define TYPVAL_ENCODE_CONV_NUMBER(num) \ +#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \ msgpack_pack_int64(packer, (int64_t) (num)) -#define TYPVAL_ENCODE_CONV_FLOAT(flt) \ +#define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \ msgpack_pack_double(packer, (double) (flt)) -#define TYPVAL_ENCODE_CONV_FUNC_START(fun, is_partial, pt) \ +#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ return conv_error(_("E5004: Error while dumping %s, %s: " \ "attempt to dump function reference"), \ mpstack, objname) -#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(len) -#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(len) -#define TYPVAL_ENCODE_CONV_FUNC_END() +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, len) +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, len) +#define TYPVAL_ENCODE_CONV_FUNC_END(tv) -#define TYPVAL_ENCODE_CONV_EMPTY_LIST() \ +#define TYPVAL_ENCODE_CONV_EMPTY_LIST(tv) \ msgpack_pack_array(packer, 0) -#define TYPVAL_ENCODE_CONV_LIST_START(len) \ +#define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \ msgpack_pack_array(packer, (size_t) (len)) -#define TYPVAL_ENCODE_CONV_EMPTY_DICT() \ +#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv) \ msgpack_pack_map(packer, 0) -#define TYPVAL_ENCODE_CONV_NIL() \ +#define TYPVAL_ENCODE_CONV_NIL(tv) \ msgpack_pack_nil(packer) -#define TYPVAL_ENCODE_CONV_BOOL(num) \ +#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \ do { \ if ((num)) { \ msgpack_pack_true(packer); \ @@ -944,23 +944,23 @@ char *encode_tv2json(typval_T *tv, size_t *len) } \ } while (0) -#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(num) \ +#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, num) \ msgpack_pack_uint64(packer, (num)) -#define TYPVAL_ENCODE_CONV_DICT_START(len) \ +#define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) \ msgpack_pack_map(packer, (size_t) (len)) -#define TYPVAL_ENCODE_CONV_DICT_END() +#define TYPVAL_ENCODE_CONV_DICT_END(tv, dict) -#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY() +#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(tv, dict) -#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS() +#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, dict) -#define TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK(label, key) +#define TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(label, key) -#define TYPVAL_ENCODE_CONV_LIST_END() +#define TYPVAL_ENCODE_CONV_LIST_END(tv) -#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS() +#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(tv) #define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \ return conv_error(_("E5005: Unable to dump %s: " \ @@ -1000,7 +1000,7 @@ char *encode_tv2json(typval_T *tv, size_t *len) #undef TYPVAL_ENCODE_CONV_DICT_END #undef TYPVAL_ENCODE_CONV_DICT_AFTER_KEY #undef TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS -#undef TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK +#undef TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK #undef TYPVAL_ENCODE_CONV_LIST_END #undef TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS #undef TYPVAL_ENCODE_CONV_RECURSE diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index 5c807bb169..8caffaf7f5 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -8,8 +8,10 @@ /// @brief Macros used to convert NIL value /// /// Is called both for special dictionary (unless #TYPVAL_ENCODE_ALLOW_SPECIALS -/// is false) and `v:null`. Accepts no arguments, but still must be -/// a function-like macros. +/// is false) and `v:null`. +/// +/// @param tv Pointer to typval where value is stored. May not be NULL. May +/// point to special dictionary. /// @def TYPVAL_ENCODE_CONV_BOOL /// @brief Macros used to convert boolean value @@ -17,12 +19,16 @@ /// Is called both for special dictionary (unless #TYPVAL_ENCODE_ALLOW_SPECIALS /// is false) and `v:true`/`v:false`. /// +/// @param tv Pointer to typval where value is stored. May not be NULL. May +/// point to a special dictionary. /// @param num Boolean value to convert. Value is an expression which /// evaluates to some integer. /// @def TYPVAL_ENCODE_CONV_NUMBER /// @brief Macros used to convert integer /// +/// @param tv Pointer to typval where value is stored. May not be NULL. May +/// point to a special dictionary. /// @param num Integer to convert, must accept both varnumber_T and int64_t. /// @def TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER @@ -31,11 +37,15 @@ /// Not used if #TYPVAL_ENCODE_ALLOW_SPECIALS is false, but still must be /// defined. /// +/// @param tv Pointer to typval where value is stored. May not be NULL. Points +/// to a special dictionary. /// @param num Integer to convert, must accept uint64_t. /// @def TYPVAL_ENCODE_CONV_FLOAT /// @brief Macros used to convert floating-point number /// +/// @param tv Pointer to typval where value is stored. May not be NULL. May +/// point to a special dictionary. /// @param flt Number to convert, must accept float_T. /// @def TYPVAL_ENCODE_CONV_STRING @@ -44,6 +54,8 @@ /// Is used to convert VAR_STRING objects as well as BIN strings represented as /// special dictionary. /// +/// @param tv Pointer to typval where value is stored. May not be NULL. May +/// point to a special dictionary. /// @param buf String to convert. Is a char[] buffer, not NUL-terminated. /// @param len String length. @@ -52,6 +64,11 @@ /// /// Is used to convert dictionary keys and STR strings represented as special /// dictionaries. +/// +/// @param tv Pointer to typval where value is stored. May be NULL. May +/// point to a special dictionary. +/// @param buf String to convert. Is a char[] buffer, not NUL-terminated. +/// @param len String length. /// @def TYPVAL_ENCODE_CONV_EXT_STRING /// @brief Macros used to convert EXT string @@ -60,31 +77,29 @@ /// actually used if #TYPVAL_ENCODE_ALLOW_SPECIALS is false, but still must be /// defined. /// +/// @param tv Pointer to typval where value is stored. May not be NULL. Points +/// to a special dictionary. /// @param buf String to convert. Is a char[] buffer, not NUL-terminated. /// @param len String length. /// @param type EXT type. -/// @def TYPVAL_ENCODE_CONV_FUNC -/// @brief Macros used to convert a function reference -/// -/// @param fun Function name. - /// @def TYPVAL_ENCODE_CONV_FUNC_START /// @brief Macros used when starting to convert a funcref or a partial /// +/// @param tv Pointer to typval where value is stored. May not be NULL. /// @param fun Function name. -/// @param is_partial True if converted function is a partial. -/// @param pt Pointer to partial or NULL. /// @def TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS /// @brief Macros used before starting to convert partial arguments /// +/// @param tv Pointer to typval where value is stored. May not be NULL. /// @param len Number of arguments. Zero for absent arguments or when /// converting a funcref. /// @def TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF /// @brief Macros used before starting to convert self dictionary /// +/// @param tv Pointer to typval where value is stored. May not be NULL. /// @param len Number of arguments. May be zero for empty dictionary or -1 for /// missing self dictionary, also when converting function /// reference. @@ -92,40 +107,47 @@ /// @def TYPVAL_ENCODE_CONV_FUNC_END /// @brief Macros used after converting a funcref or a partial /// -/// Accepts no arguments, but still must be a function-like macros. +/// @param tv Pointer to typval where value is stored. May not be NULL. /// @def TYPVAL_ENCODE_CONV_EMPTY_LIST /// @brief Macros used to convert an empty list /// -/// Accepts no arguments, but still must be a function-like macros. +/// @param tv Pointer to typval where value is stored. May not be NULL. /// @def TYPVAL_ENCODE_CONV_EMPTY_DICT /// @brief Macros used to convert an empty dictionary /// -/// Accepts no arguments, but still must be a function-like macros. +/// @param tv Pointer to typval where value is stored. May not be NULL. May +/// point to a special dictionary. /// @def TYPVAL_ENCODE_CONV_LIST_START /// @brief Macros used before starting to convert non-empty list /// +/// @param tv Pointer to typval where value is stored. May be NULL. May +/// point to a special dictionary. /// @param len List length. Is an expression which evaluates to an integer. /// @def TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS /// @brief Macros used after finishing converting non-last list item /// -/// Accepts no arguments, but still must be a function-like macros. +/// @param tv Pointer to typval where list is stored. May be NULL. /// @def TYPVAL_ENCODE_CONV_LIST_END /// @brief Macros used after converting non-empty list /// -/// Accepts no arguments, but still must be a function-like macros. +/// @param tv Pointer to typval where list is stored. May be NULL. /// @def TYPVAL_ENCODE_CONV_DICT_START /// @brief Macros used before starting to convert non-empty 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 +/// (for dictionaries represented as special lists). /// @param len Dictionary length. Is an expression which evaluates to an /// integer. -/// @def TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK +/// @def TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK /// @brief Macros used to check special dictionary key /// /// @param label Label for goto in case check was not successfull. @@ -134,17 +156,26 @@ /// @def TYPVAL_ENCODE_CONV_DICT_AFTER_KEY /// @brief Macros used after finishing converting dictionary key /// -/// Accepts no arguments, but still must be a function-like macros. +/// @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 +/// (for dictionaries represented as special lists). /// @def TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS /// @brief Macros used after finishing converting non-last dictionary value /// -/// Accepts no arguments, but still must be a function-like macros. +/// @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 +/// (for dictionaries represented as special lists). /// @def TYPVAL_ENCODE_CONV_DICT_END /// @brief Macros used after converting non-empty dictionary /// -/// Accepts no arguments, but still must be a function-like macros. +/// @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 +/// (for dictionaries represented as special lists). /// @def TYPVAL_ENCODE_CONV_RECURSE /// @brief Macros used when self-containing container is detected @@ -193,6 +224,8 @@ #include "nvim/func_attr.h" #include "nvim/eval/typval_encode.h" +const dict_T *const TYPVAL_ENCODE_NODICT_VAR = NULL; + static inline int _TYPVAL_ENCODE_CHECK_SELF_REFERENCE( TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, void *const val, int *const val_copyID, @@ -263,28 +296,28 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( { switch (tv->v_type) { case VAR_STRING: { - TYPVAL_ENCODE_CONV_STRING(tv->vval.v_string, tv_strlen(tv)); + TYPVAL_ENCODE_CONV_STRING(tv, tv->vval.v_string, tv_strlen(tv)); break; } case VAR_NUMBER: { - TYPVAL_ENCODE_CONV_NUMBER(tv->vval.v_number); + TYPVAL_ENCODE_CONV_NUMBER(tv, tv->vval.v_number); break; } case VAR_FLOAT: { - TYPVAL_ENCODE_CONV_FLOAT(tv->vval.v_float); + TYPVAL_ENCODE_CONV_FLOAT(tv, tv->vval.v_float); break; } case VAR_FUNC: { - TYPVAL_ENCODE_CONV_FUNC_START(tv->vval.v_string, false, NULL); - TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(0); - TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(-1); - TYPVAL_ENCODE_CONV_FUNC_END(); + TYPVAL_ENCODE_CONV_FUNC_START(tv, tv->vval.v_string); + TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, 0); + TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, -1); + TYPVAL_ENCODE_CONV_FUNC_END(tv); break; } case VAR_PARTIAL: { partial_T *const pt = tv->vval.v_partial; (void)pt; - TYPVAL_ENCODE_CONV_FUNC_START(pt->pt_name, true, pt); + TYPVAL_ENCODE_CONV_FUNC_START(tv, pt->pt_name); _mp_push(*mpstack, ((MPConvStackVal) { .type = kMPConvPartial, .tv = tv, @@ -299,12 +332,12 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( } case VAR_LIST: { if (tv->vval.v_list == NULL || tv->vval.v_list->lv_len == 0) { - TYPVAL_ENCODE_CONV_EMPTY_LIST(); + TYPVAL_ENCODE_CONV_EMPTY_LIST(tv); break; } _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_list, lv_copyID, copyID, kMPConvList); - TYPVAL_ENCODE_CONV_LIST_START(tv->vval.v_list->lv_len); + TYPVAL_ENCODE_CONV_LIST_START(tv, tv->vval.v_list->lv_len); _mp_push(*mpstack, ((MPConvStackVal) { .type = kMPConvList, .tv = tv, @@ -320,12 +353,12 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( case VAR_SPECIAL: { switch (tv->vval.v_special) { case kSpecialVarNull: { - TYPVAL_ENCODE_CONV_NIL(); + TYPVAL_ENCODE_CONV_NIL(tv); break; } case kSpecialVarTrue: case kSpecialVarFalse: { - TYPVAL_ENCODE_CONV_BOOL(tv->vval.v_special == kSpecialVarTrue); + TYPVAL_ENCODE_CONV_BOOL(tv, tv->vval.v_special == kSpecialVarTrue); break; } } @@ -334,7 +367,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( case VAR_DICT: { if (tv->vval.v_dict == NULL || tv->vval.v_dict->dv_hashtab.ht_used == 0) { - TYPVAL_ENCODE_CONV_EMPTY_DICT(); + TYPVAL_ENCODE_CONV_EMPTY_DICT(tv); break; } const dictitem_T *type_di; @@ -357,14 +390,14 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( } switch ((MessagePackType)i) { case kMPNil: { - TYPVAL_ENCODE_CONV_NIL(); + TYPVAL_ENCODE_CONV_NIL(tv); break; } case kMPBoolean: { if (val_di->di_tv.v_type != VAR_NUMBER) { goto _convert_one_value_regular_dict; } - TYPVAL_ENCODE_CONV_BOOL(val_di->di_tv.vval.v_number); + TYPVAL_ENCODE_CONV_BOOL(tv, val_di->di_tv.vval.v_number); break; } case kMPInteger: { @@ -397,9 +430,9 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( | (uint64_t)(((uint64_t)high_bits) << 31) | (uint64_t)low_bits); if (sign > 0) { - TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(number); + TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, number); } else { - TYPVAL_ENCODE_CONV_NUMBER(-number); + TYPVAL_ENCODE_CONV_NUMBER(tv, -number); } break; } @@ -407,7 +440,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( if (val_di->di_tv.v_type != VAR_FLOAT) { goto _convert_one_value_regular_dict; } - TYPVAL_ENCODE_CONV_FLOAT(val_di->di_tv.vval.v_float); + TYPVAL_ENCODE_CONV_FLOAT(tv, val_di->di_tv.vval.v_float); break; } case kMPString: @@ -423,9 +456,9 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( goto _convert_one_value_regular_dict; } if (is_string) { - TYPVAL_ENCODE_CONV_STR_STRING(buf, len); + TYPVAL_ENCODE_CONV_STR_STRING(tv, buf, len); } else { - TYPVAL_ENCODE_CONV_STRING(buf, len); + TYPVAL_ENCODE_CONV_STRING(tv, buf, len); } xfree(buf); break; @@ -437,7 +470,8 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_di->di_tv.vval.v_list, lv_copyID, copyID, kMPConvList); - TYPVAL_ENCODE_CONV_LIST_START(val_di->di_tv.vval.v_list->lv_len); + TYPVAL_ENCODE_CONV_LIST_START(tv, + val_di->di_tv.vval.v_list->lv_len); _mp_push(*mpstack, ((MPConvStackVal) { .tv = tv, .type = kMPConvList, @@ -456,7 +490,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( } list_T *const val_list = val_di->di_tv.vval.v_list; if (val_list == NULL || val_list->lv_len == 0) { - TYPVAL_ENCODE_CONV_EMPTY_DICT(); + TYPVAL_ENCODE_CONV_EMPTY_DICT(tv); break; } for (const listitem_T *li = val_list->lv_first; li != NULL; @@ -468,7 +502,8 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( } _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_list, lv_copyID, copyID, kMPConvPairs); - TYPVAL_ENCODE_CONV_DICT_START(val_list->lv_len); + TYPVAL_ENCODE_CONV_DICT_START(tv, TYPVAL_ENCODE_NODICT_VAR, + val_list->lv_len); _mp_push(*mpstack, ((MPConvStackVal) { .tv = tv, .type = kMPConvPairs, @@ -499,7 +534,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( &len, &buf)) { goto _convert_one_value_regular_dict; } - TYPVAL_ENCODE_CONV_EXT_STRING(buf, len, type); + TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type); xfree(buf); break; } @@ -509,7 +544,8 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( _convert_one_value_regular_dict: _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_dict, dv_copyID, copyID, kMPConvDict); - TYPVAL_ENCODE_CONV_DICT_START(tv->vval.v_dict->dv_hashtab.ht_used); + TYPVAL_ENCODE_CONV_DICT_START(tv, tv->vval.v_dict, + tv->vval.v_dict->dv_hashtab.ht_used); _mp_push(*mpstack, ((MPConvStackVal) { .tv = tv, .type = kMPConvDict, @@ -566,11 +602,12 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( if (!cur_mpsv->data.d.todo) { (void)_mp_pop(mpstack); cur_mpsv->data.d.dict->dv_copyID = copyID - 1; - TYPVAL_ENCODE_CONV_DICT_END(); + TYPVAL_ENCODE_CONV_DICT_END(cur_mpsv->tv, cur_mpsv->tv->vval.v_dict); continue; } else if (cur_mpsv->data.d.todo != cur_mpsv->data.d.dict->dv_hashtab.ht_used) { - TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(); + TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(cur_mpsv->tv, + cur_mpsv->tv->vval.v_dict); } while (HASHITEM_EMPTY(cur_mpsv->data.d.hi)) { cur_mpsv->data.d.hi++; @@ -578,9 +615,10 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( dictitem_T *const di = HI2DI(cur_mpsv->data.d.hi); cur_mpsv->data.d.todo--; cur_mpsv->data.d.hi++; - TYPVAL_ENCODE_CONV_STR_STRING(&di->di_key[0], + TYPVAL_ENCODE_CONV_STR_STRING(NULL, &di->di_key[0], strlen((char *)&di->di_key[0])); - TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(); + TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(cur_mpsv->tv, + cur_mpsv->tv->vval.v_dict); tv = &di->di_tv; break; } @@ -588,10 +626,10 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( if (cur_mpsv->data.l.li == NULL) { (void)_mp_pop(mpstack); cur_mpsv->data.l.list->lv_copyID = copyID - 1; - TYPVAL_ENCODE_CONV_LIST_END(); + TYPVAL_ENCODE_CONV_LIST_END(cur_mpsv->tv); continue; } else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) { - TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(); + TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(cur_mpsv->tv); } tv = &cur_mpsv->data.l.li->li_tv; cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; @@ -601,13 +639,14 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( if (cur_mpsv->data.l.li == NULL) { (void)_mp_pop(mpstack); cur_mpsv->data.l.list->lv_copyID = copyID - 1; - TYPVAL_ENCODE_CONV_DICT_END(); + TYPVAL_ENCODE_CONV_DICT_END(cur_mpsv->tv, TYPVAL_ENCODE_NODICT_VAR); continue; } else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) { - TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(); + TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS( + cur_mpsv->tv, TYPVAL_ENCODE_NODICT_VAR); } const list_T *const kv_pair = cur_mpsv->data.l.li->li_tv.vval.v_list; - TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK( + TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK( encode_vim_to__error_ret, kv_pair->lv_first->li_tv); if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, cur_mpsv, @@ -616,19 +655,22 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( objname) == FAIL) { goto encode_vim_to__error_ret; } - TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(); + TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(cur_mpsv->tv, + TYPVAL_ENCODE_NODICT_VAR); tv = &kv_pair->lv_last->li_tv; cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; break; } case kMPConvPartial: { partial_T *const pt = cur_mpsv->data.p.pt; + tv = cur_mpsv->tv; switch (cur_mpsv->data.p.stage) { case kMPConvPartialArgs: { - TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(pt == NULL ? 0 : pt->pt_argc); + TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, + pt == NULL ? 0 : pt->pt_argc); cur_mpsv->data.p.stage = kMPConvPartialSelf; if (pt != NULL && pt->pt_argc > 0) { - TYPVAL_ENCODE_CONV_LIST_START(pt->pt_argc); + TYPVAL_ENCODE_CONV_LIST_START(NULL, pt->pt_argc); _mp_push(mpstack, ((MPConvStackVal) { .type = kMPConvPartialList, .tv = NULL, @@ -647,7 +689,7 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( cur_mpsv->data.p.stage = kMPConvPartialEnd; dict_T *const dict = pt == NULL ? NULL : pt->pt_dict; if (dict != NULL) { - TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(dict->dv_hashtab.ht_used); + TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, dict->dv_hashtab.ht_used); const int te_csr_ret = _TYPVAL_ENCODE_CHECK_SELF_REFERENCE( TYPVAL_ENCODE_FIRST_ARG_NAME, dict, &dict->dv_copyID, &mpstack, copyID, kMPConvDict, @@ -659,7 +701,8 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( continue; } } - TYPVAL_ENCODE_CONV_DICT_START(dict->dv_hashtab.ht_used); + TYPVAL_ENCODE_CONV_DICT_START(NULL, pt->pt_dict, + dict->dv_hashtab.ht_used); _mp_push(mpstack, ((MPConvStackVal) { .type = kMPConvDict, .tv = NULL, @@ -672,12 +715,12 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( }, })); } else { - TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(-1); + TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, -1); } break; } case kMPConvPartialEnd: { - TYPVAL_ENCODE_CONV_FUNC_END(); + TYPVAL_ENCODE_CONV_FUNC_END(tv); (void)_mp_pop(mpstack); break; } @@ -687,10 +730,10 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( case kMPConvPartialList: { if (!cur_mpsv->data.a.todo) { (void)_mp_pop(mpstack); - TYPVAL_ENCODE_CONV_LIST_END(); + TYPVAL_ENCODE_CONV_LIST_END(NULL); continue; } else if (cur_mpsv->data.a.argv != cur_mpsv->data.a.arg) { - TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(); + TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(NULL); } tv = cur_mpsv->data.a.arg++; cur_mpsv->data.a.todo--; diff --git a/src/nvim/eval/typval_encode.h b/src/nvim/eval/typval_encode.h index 9208cdc752..5813059cda 100644 --- a/src/nvim/eval/typval_encode.h +++ b/src/nvim/eval/typval_encode.h @@ -284,4 +284,13 @@ static inline size_t tv_strlen(const typval_T *const tv) #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) + +/// Name of the dummy const dict_T *const variable +#define TYPVAL_ENCODE_NODICT_VAR \ + _TYPVAL_ENCODE_NODICT_VAR_INNER(TYPVAL_ENCODE_NAME) + #endif // NVIM_EVAL_TYPVAL_ENCODE_H From a52238707c5438fb3aaa945ebb550cdbd8660cf3 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 3 Jan 2017 06:36:58 +0300 Subject: [PATCH 13/28] eval/encode: Fix 4 new linter failures --- src/nvim/eval/encode.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 73133b7b29..ef1e77459a 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -909,10 +909,10 @@ char *encode_tv2json(typval_T *tv, size_t *len) } while (0) #define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \ - msgpack_pack_int64(packer, (int64_t) (num)) + msgpack_pack_int64(packer, (int64_t)(num)) #define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \ - msgpack_pack_double(packer, (double) (flt)) + msgpack_pack_double(packer, (double)(flt)) #define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ return conv_error(_("E5004: Error while dumping %s, %s: " \ @@ -927,7 +927,7 @@ char *encode_tv2json(typval_T *tv, size_t *len) msgpack_pack_array(packer, 0) #define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \ - msgpack_pack_array(packer, (size_t) (len)) + msgpack_pack_array(packer, (size_t)(len)) #define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv) \ msgpack_pack_map(packer, 0) @@ -948,7 +948,7 @@ char *encode_tv2json(typval_T *tv, size_t *len) msgpack_pack_uint64(packer, (num)) #define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) \ - msgpack_pack_map(packer, (size_t) (len)) + msgpack_pack_map(packer, (size_t)(len)) #define TYPVAL_ENCODE_CONV_DICT_END(tv, dict) From a5bdd64a5e2b2e5f21ecdf9a04bbf6fdd787f8a9 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 3 Jan 2017 07:14:54 +0300 Subject: [PATCH 14/28] eval: Fix unused variable error in release builds --- src/nvim/eval.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 79134d748a..d1ae94706f 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -19258,6 +19258,7 @@ void clear_tv(typval_T *varp) { if (varp != NULL && varp->v_type != VAR_UNKNOWN) { const int evn_ret = encode_vim_to_nothing(varp, varp, "clear_tv argument"); + (void)evn_ret; assert(evn_ret == OK); } } From 9c84f3ba3e517b6c6b196387ac50d40daeacf165 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 3 Jan 2017 07:41:05 +0300 Subject: [PATCH 15/28] eval/typval_encode: Provide proper values as dict argument --- src/nvim/eval/typval_encode.c.h | 8 +++++--- src/nvim/eval/typval_encode.h | 3 +++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index 8caffaf7f5..74d0aac083 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -552,6 +552,7 @@ _convert_one_value_regular_dict: .data = { .d = { .dict = tv->vval.v_dict, + .dictp = &tv->vval.v_dict, .hi = tv->vval.v_dict->dv_hashtab.ht_array, .todo = tv->vval.v_dict->dv_hashtab.ht_used, }, @@ -602,12 +603,12 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( if (!cur_mpsv->data.d.todo) { (void)_mp_pop(mpstack); cur_mpsv->data.d.dict->dv_copyID = copyID - 1; - TYPVAL_ENCODE_CONV_DICT_END(cur_mpsv->tv, cur_mpsv->tv->vval.v_dict); + TYPVAL_ENCODE_CONV_DICT_END(cur_mpsv->tv, *cur_mpsv->data.d.dictp); continue; } else if (cur_mpsv->data.d.todo != cur_mpsv->data.d.dict->dv_hashtab.ht_used) { TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(cur_mpsv->tv, - cur_mpsv->tv->vval.v_dict); + *cur_mpsv->data.d.dictp); } while (HASHITEM_EMPTY(cur_mpsv->data.d.hi)) { cur_mpsv->data.d.hi++; @@ -618,7 +619,7 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( TYPVAL_ENCODE_CONV_STR_STRING(NULL, &di->di_key[0], strlen((char *)&di->di_key[0])); TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(cur_mpsv->tv, - cur_mpsv->tv->vval.v_dict); + *cur_mpsv->data.d.dictp); tv = &di->di_tv; break; } @@ -709,6 +710,7 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( .data = { .d = { .dict = dict, + .dictp = &pt->pt_dict, .hi = dict->dv_hashtab.ht_array, .todo = dict->dv_hashtab.ht_used, }, diff --git a/src/nvim/eval/typval_encode.h b/src/nvim/eval/typval_encode.h index 5813059cda..c2629caa39 100644 --- a/src/nvim/eval/typval_encode.h +++ b/src/nvim/eval/typval_encode.h @@ -192,6 +192,9 @@ typedef struct { union { struct { dict_T *dict; ///< Currently converted dictionary. + dict_T **dictp; ///< Location where that dictionary is stored. + ///< Normally it is &.tv->vval.v_dict, but not when + ///< converting partials. hashitem_T *hi; ///< Currently converted dictionary item. size_t todo; ///< Amount of items left to process. } d; ///< State of dictionary conversion. From 06cca5dc59ae3bcfb81796b331a3f882ba1f0ca2 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 3 Jan 2017 07:44:54 +0300 Subject: [PATCH 16/28] eval/typval_encode: Handle NULL partials properly --- src/nvim/eval/typval_encode.c.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index 74d0aac083..69f805498a 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -87,7 +87,7 @@ /// @brief Macros used when starting to convert a funcref or a partial /// /// @param tv Pointer to typval where value is stored. May not be NULL. -/// @param fun Function name. +/// @param fun Function name. May be NULL. /// @def TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS /// @brief Macros used before starting to convert partial arguments @@ -317,7 +317,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( case VAR_PARTIAL: { partial_T *const pt = tv->vval.v_partial; (void)pt; - TYPVAL_ENCODE_CONV_FUNC_START(tv, pt->pt_name); + TYPVAL_ENCODE_CONV_FUNC_START(tv, (pt == NULL ? NULL : pt->pt_name)); _mp_push(*mpstack, ((MPConvStackVal) { .type = kMPConvPartial, .tv = tv, From f21725946c575884e04e79942850e7d0bc040ef9 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 3 Jan 2017 16:13:21 +0300 Subject: [PATCH 17/28] eval/encode: Fail when stringifying NULL functions --- src/nvim/eval/encode.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index ef1e77459a..828bde3124 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -336,8 +336,14 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, #define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ do { \ - ga_concat(gap, "function("); \ - TYPVAL_ENCODE_CONV_STRING(tv, fun, STRLEN(fun)); \ + const char *const fun_ = (const char *)(fun); \ + if (fun_ == NULL) { \ + EMSG2(_(e_intern2), "string(): NULL function name"); \ + ga_concat(gap, "function(NULL"); \ + } else { \ + ga_concat(gap, "function("); \ + TYPVAL_ENCODE_CONV_STRING(tv, fun_, strlen(fun_)); \ + }\ } while (0) #define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, len) \ From efc624c2fe029a4ab494672c43366e8c57c19108 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 3 Jan 2017 17:28:57 +0300 Subject: [PATCH 18/28] eval: Fix errorneous early exit when converting lists and dictionaries --- src/nvim/eval.c | 4 ++-- src/nvim/eval/typval_encode.c.h | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index d1ae94706f..a0f511ab23 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -19151,7 +19151,7 @@ static inline int _nothing_conv_list_start(typval_T *const tv) #define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \ do { \ if (_nothing_conv_list_start(tv) != NOTDONE) { \ - return OK; \ + goto typval_encode_stop_converting_one_item; \ } \ } while (0) @@ -19190,7 +19190,7 @@ static inline int _nothing_conv_dict_start(typval_T *const tv, if (_nothing_conv_dict_start(tv, (dict_T **)&dict, \ (void *)&TYPVAL_ENCODE_NODICT_VAR) \ != NOTDONE) { \ - return OK; \ + goto typval_encode_stop_converting_one_item; \ } \ } while (0) diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index 69f805498a..5795d339f0 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -565,7 +565,10 @@ _convert_one_value_regular_dict: return FAIL; } } +typval_encode_stop_converting_one_item: return OK; + // Prevent “unused label” warnings. + goto typval_encode_stop_converting_one_item; } TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( @@ -595,6 +598,9 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( == FAIL) { goto encode_vim_to__error_ret; } +/// Label common for this and convert_one_value functions, used for escaping +/// from macros like TYPVAL_ENCODE_CONV_DICT_START. +typval_encode_stop_converting_one_item: while (_mp_size(mpstack)) { MPConvStackVal *cur_mpsv = &_mp_last(mpstack); typval_T *tv = NULL; @@ -754,5 +760,7 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( encode_vim_to__error_ret: _mp_destroy(mpstack); return FAIL; + // Prevent “unused label” warnings. + goto typval_encode_stop_converting_one_item; } #endif // NVIM_EVAL_TYPVAL_ENCODE_C_H From 136b382e64390bf3129a7cd127b7704c983ec300 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 3 Jan 2017 22:51:29 +0300 Subject: [PATCH 19/28] tests: Add tests for partials dumping Also fixed dumping of partials by encode_vim_to_object and added code which is able to work with partials and dictionaries to test/unit/eval/helpers.lua (mostly copied from #5119, except for partials handling). --- src/nvim/api/private/helpers.c | 7 +- test/functional/eval/json_functions_spec.lua | 6 + .../eval/msgpack_functions_spec.lua | 19 ++ test/functional/eval/string_spec.lua | 60 ++++- test/unit/api/private_helpers_spec.lua | 18 ++ test/unit/eval/helpers.lua | 225 ++++++++++++++---- test/unit/helpers.lua | 2 +- 7 files changed, 289 insertions(+), 48 deletions(-) diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 5bd76bc238..de566e37ad 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -354,10 +354,13 @@ void set_option_to(void *to, int type, String name, Object value, Error *err) #define TYPVAL_ENCODE_CONV_STR_STRING TYPVAL_ENCODE_CONV_STRING #define TYPVAL_ENCODE_CONV_EXT_STRING(tv, str, len, type) \ - TYPVAL_ENCODE_CONV_NIL() + TYPVAL_ENCODE_CONV_NIL(tv) #define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ - TYPVAL_ENCODE_CONV_NIL() + do { \ + TYPVAL_ENCODE_CONV_NIL(tv); \ + goto typval_encode_stop_converting_one_item; \ + } while (0) #define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, len) #define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, len) diff --git a/test/functional/eval/json_functions_spec.lua b/test/functional/eval/json_functions_spec.lua index 4a6758019b..fc0a19bdfa 100644 --- a/test/functional/eval/json_functions_spec.lua +++ b/test/functional/eval/json_functions_spec.lua @@ -672,6 +672,12 @@ describe('json_encode() function', function() exc_exec('call json_encode(function("tr"))')) end) + it('fails to dump a partial', function() + execute('function T() dict\nendfunction') + eq('Vim(call):E474: Error while dumping encode_tv2json() argument, itself: attempt to dump function reference', + exc_exec('call json_encode(function("T", [1, 2], {}))')) + end) + it('fails to dump a function reference in a list', function() eq('Vim(call):E474: Error while dumping encode_tv2json() argument, index 0: attempt to dump function reference', exc_exec('call json_encode([function("tr")])')) diff --git a/test/functional/eval/msgpack_functions_spec.lua b/test/functional/eval/msgpack_functions_spec.lua index 3fb54c2ee7..44c01d2226 100644 --- a/test/functional/eval/msgpack_functions_spec.lua +++ b/test/functional/eval/msgpack_functions_spec.lua @@ -493,6 +493,12 @@ describe('msgpackparse() function', function() exc_exec('call msgpackparse(function("tr"))')) end) + it('fails to parse a partial', function() + execute('function T() dict\nendfunction') + eq('Vim(call):E686: Argument of msgpackparse() must be a List', + exc_exec('call msgpackparse(function("T", [1, 2], {}))')) + end) + it('fails to parse a float', function() eq('Vim(call):E686: Argument of msgpackparse() must be a List', exc_exec('call msgpackparse(0.0)')) @@ -570,6 +576,13 @@ describe('msgpackdump() function', function() exc_exec('call msgpackdump([Todump])')) end) + it('fails to dump a partial', function() + execute('function T() dict\nendfunction') + execute('let Todump = function("T", [1, 2], {})') + eq('Vim(call):E5004: Error while dumping msgpackdump() argument, index 0, itself: attempt to dump function reference', + exc_exec('call msgpackdump([Todump])')) + end) + it('fails to dump a function reference in a list', function() execute('let todump = [function("tr")]') eq('Vim(call):E5004: Error while dumping msgpackdump() argument, index 0, index 0: attempt to dump function reference', @@ -675,6 +688,12 @@ describe('msgpackdump() function', function() exc_exec('call msgpackdump(function("tr"))')) end) + it('fails to dump a partial', function() + execute('function T() dict\nendfunction') + eq('Vim(call):E686: Argument of msgpackdump() must be a List', + exc_exec('call msgpackdump(function("T", [1, 2], {}))')) + end) + it('fails to dump a float', function() eq('Vim(call):E686: Argument of msgpackdump() must be a List', exc_exec('call msgpackdump(0.0)')) diff --git a/test/functional/eval/string_spec.lua b/test/functional/eval/string_spec.lua index 1f963c674e..35caef702b 100644 --- a/test/functional/eval/string_spec.lua +++ b/test/functional/eval/string_spec.lua @@ -10,6 +10,7 @@ local funcs = helpers.funcs local write_file = helpers.write_file local NIL = helpers.NIL local source = helpers.source +local dedent = helpers.dedent describe('string() function', function() before_each(clear) @@ -111,10 +112,10 @@ describe('string() function', function() function Test1() endfunction - function s:Test2() + function s:Test2() dict endfunction - function g:Test3() + function g:Test3() dict endfunction let g:Test2_f = function('s:Test2') @@ -150,6 +151,61 @@ describe('string() function', function() eq("\nE724: unable to correctly dump variable with self-referencing container\nfunction('TestDict', {'tdr': function('TestDict', {E724@1})})", redir_exec('echo string(d.tdr)')) end) + + it('dumps automatically created partials', function() + eq('function(\'1_Test2\', {\'f\': function(\'1_Test2\')})', + eval('string({"f": Test2_f}.f)')) + eq('function(\'1_Test2\', [1], {\'f\': function(\'1_Test2\', [1])})', + eval('string({"f": function(Test2_f, [1])}.f)')) + end) + + it('dumps manually created partials', function() + eq('function(\'Test3\', [1, 2], {})', + eval('string(function("Test3", [1, 2], {}))')) + eq('function(\'Test3\', {})', + eval('string(function("Test3", {}))')) + eq('function(\'Test3\', [1, 2])', + eval('string(function("Test3", [1, 2]))')) + end) + + it('does not crash or halt when dumping partials with reference cycles in self', + function() + meths.set_var('d', {v=true}) + eq(dedent([[ + + E724: unable to correctly dump variable with self-referencing container + {'p': function('1_Test2', {E724@0}), 'f': function('1_Test2'), 'v': v:true}]]), + redir_exec('echo string(extend(extend(g:d, {"f": g:Test2_f}), {"p": g:d.f}))')) + end) + + it('does not crash or halt when dumping partials with reference cycles in arguments', + function() + meths.set_var('l', {}) + eval('add(l, l)') + -- Regression: the below line used to crash (add returns original list and + -- there was error in dumping partials). Tested explicitly in + -- test/unit/api/private_helpers_spec.lua. + eval('add(l, function("Test1", l))') + eq(dedent([=[ + + E724: unable to correctly dump variable with self-referencing container + function('Test1', [[{E724@2}, function('Test1', [{E724@2}])], function('Test1', [[{E724@4}, function('Test1', [{E724@4}])]])])]=]), + redir_exec('echo string(function("Test1", l))')) + end) + + it('does not crash or halt when dumping partials with reference cycles in self and arguments', + function() + meths.set_var('d', {v=true}) + meths.set_var('l', {}) + eval('add(l, l)') + eval('add(l, function("Test1", l))') + eval('add(l, function("Test1", d))') + eq(dedent([=[ + + E724: unable to correctly dump variable with self-referencing container + {'p': function('1_Test2', [[{E724@3}, function('Test1', [{E724@3}]), function('Test1', {E724@0})], function('Test1', [[{E724@5}, function('Test1', [{E724@5}]), function('Test1', {E724@0})]]), function('Test1', {E724@0})], {E724@0}), 'f': function('1_Test2'), 'v': v:true}]=]), + redir_exec('echo string(extend(extend(g:d, {"f": g:Test2_f}), {"p": function(g:d.f, l)}))')) + end) end) describe('used to represent lists', function() diff --git a/test/unit/api/private_helpers_spec.lua b/test/unit/api/private_helpers_spec.lua index 1d7c03787b..e108d46370 100644 --- a/test/unit/api/private_helpers_spec.lua +++ b/test/unit/api/private_helpers_spec.lua @@ -7,6 +7,7 @@ local NULL = helpers.NULL local eq = helpers.eq local lua2typvalt = eval_helpers.lua2typvalt +local typvalt2lua = eval_helpers.typvalt2lua local typvalt = eval_helpers.typvalt local nil_value = api_helpers.nil_value @@ -14,6 +15,7 @@ local list_type = api_helpers.list_type local int_type = api_helpers.int_type local type_key = api_helpers.type_key local obj2lua = api_helpers.obj2lua +local func_type = api_helpers.func_type local api = cimport('./src/nvim/api/private/helpers.h') @@ -85,4 +87,20 @@ describe('vim_to_object', function() eq(nil, tt.vval.v_dict) eq({}, obj2lua(api.vim_to_object(tt))) end) + + it('regression: partials in a list', function() + local llist = { + { + [type_key]=func_type, + value='printf', + args={'%s'}, + dict={v=1}, + }, + {}, + } + local ffi=require'ffi' + local list = lua2typvalt(llist) + eq(llist, typvalt2lua(list)) + eq({nil_value, {}}, obj2lua(api.vim_to_object(list))) + end) end) diff --git a/test/unit/eval/helpers.lua b/test/unit/eval/helpers.lua index 45fbf8da5c..0656257361 100644 --- a/test/unit/eval/helpers.lua +++ b/test/unit/eval/helpers.lua @@ -9,6 +9,7 @@ local eval = cimport('./src/nvim/eval.h', './src/nvim/eval_defs.h') local null_string = {[true]='NULL string'} local null_list = {[true]='NULL list'} +local null_dict = {[true]='NULL dict'} local type_key = {[true]='type key'} local list_type = {[true]='list type'} local dict_type = {[true]='dict type'} @@ -18,27 +19,28 @@ local flt_type = {[true]='flt type'} local nil_value = {[true]='nil'} +local lua2typvalt + +local function li_alloc(nogc) + local gcfunc = eval.listitem_free + if nogc then gcfunc = nil end + local li = ffi.gc(eval.listitem_alloc(), gcfunc) + li.li_next = nil + li.li_prev = nil + li.li_tv = {v_type=eval.VAR_UNKNOWN, v_lock=eval.VAR_UNLOCKED} + return li +end + local function list(...) local ret = ffi.gc(eval.list_alloc(), eval.list_unref) eq(0, ret.lv_refcount) ret.lv_refcount = 1 for i = 1, select('#', ...) do local val = select(i, ...) - local typ = type(val) - if typ == 'string' then - eval.list_append_string(ret, to_cstr(val)) - elseif typ == 'table' and val == null_string then - eval.list_append_string(ret, nil) - elseif typ == 'table' and val == null_list then - eval.list_append_list(ret, nil) - elseif typ == 'table' and val[type_key] == list_type then - local itemlist = ffi.gc(list(table.unpack(val)), nil) - eq(1, itemlist.lv_refcount) - itemlist.lv_refcount = 0 - eval.list_append_list(ret, itemlist) - else - assert(false, 'Not implemented yet') - end + local li_tv = ffi.gc(lua2typvalt(val), nil) + local li = li_alloc(true) + li.li_tv = li_tv + eval.tv_list_append(ret, li) end return ret end @@ -49,22 +51,27 @@ local special_tab = { [eval.kSpecialVarTrue] = true, } +local ptr2key = function(ptr) + return tostring(ptr) +end + local lst2tbl local dct2tbl +local typvalt2lua local typvalt2lua_tab typvalt2lua_tab = { - [tonumber(eval.VAR_SPECIAL)] = function(t) + [tonumber(eval.VAR_SPECIAL)] = function(t, processed) return special_tab[t.vval.v_special] end, - [tonumber(eval.VAR_NUMBER)] = function(t) + [tonumber(eval.VAR_NUMBER)] = function(t, processed) return {[type_key]=int_type, value=tonumber(t.vval.v_number)} end, - [tonumber(eval.VAR_FLOAT)] = function(t) + [tonumber(eval.VAR_FLOAT)] = function(t, processed) return tonumber(t.vval.v_float) end, - [tonumber(eval.VAR_STRING)] = function(t) + [tonumber(eval.VAR_STRING)] = function(t, processed) local str = t.vval.v_string if str == nil then return null_string @@ -72,32 +79,64 @@ typvalt2lua_tab = { return ffi.string(str) end end, - [tonumber(eval.VAR_LIST)] = function(t) - return lst2tbl(t.vval.v_list) + [tonumber(eval.VAR_LIST)] = function(t, processed) + return lst2tbl(t.vval.v_list, processed) end, - [tonumber(eval.VAR_DICT)] = function(t) - return dct2tbl(t.vval.v_dict) + [tonumber(eval.VAR_DICT)] = function(t, processed) + return dct2tbl(t.vval.v_dict, processed) end, - [tonumber(eval.VAR_FUNC)] = function(t) - return {[type_key]=func_type, value=typvalt2lua_tab[eval.VAR_STRING](t)} + [tonumber(eval.VAR_FUNC)] = function(t, processed) + return {[type_key]=func_type, value=typvalt2lua_tab[eval.VAR_STRING](t, processed or {})} + end, + [tonumber(eval.VAR_PARTIAL)] = function(t, processed) + local p_key = ptr2key(t) + if processed[p_key] then + return processed[p_key] + end + local pt = t.vval.v_partial + local value, auto, dict, argv = nil, nil, nil, nil + if pt ~= nil then + value = ffi.string(pt.pt_name) + auto = pt.pt_auto and true or nil + argv = {} + for i = 1, pt.pt_argc do + argv[i] = typvalt2lua(pt.pt_argv[i - 1], processed) + end + if pt.pt_dict ~= nil then + dict = dct2tbl(pt.pt_dict) + end + end + return { + [type_key]=func_type, + value=value, + auto=auto, + args=argv, + dict=dict, + } end, } -local typvalt2lua = function(t) - return ((typvalt2lua_tab[tonumber(t.v_type)] or function(t_inner) +typvalt2lua = function(t, processed) + return ((typvalt2lua_tab[tonumber(t.v_type)] or function(t_inner, processed) assert(false, 'Converting ' .. tonumber(t_inner.v_type) .. ' was not implemented yet') - end)(t)) + end)(t, processed or {})) end -lst2tbl = function(l) - local ret = {[type_key]=list_type} +lst2tbl = function(l, processed) if l == nil then - return ret + return null_list end + processed = processed or {} + local p_key = ptr2key(l) + if processed[p_key] then + return processed[p_key] + 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) + ret[#ret + 1] = typvalt2lua(li.li_tv, processed) li = li.li_next end if ret[1] then @@ -106,16 +145,54 @@ lst2tbl = function(l) return ret end -dct2tbl = function(d) - local ret = {d=d} - assert(false, 'Converting dictionaries is not implemented yet') +local function dict_iter(d) + local init_s = { + todo=d.dv_hashtab.ht_used, + hi=d.dv_hashtab.ht_array, + } + 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 + local key = ffi.string(s.hi.hi_key) + local di = ffi.cast('dictitem_T*', + s.hi.hi_key - ffi.offsetof('dictitem_T', 'di_key')) + s.todo = s.todo - 1 + s.hi = s.hi + 1 + return key, di + end + s.hi = s.hi + 1 + end + end + return f, init_s, nil +end + +local function first_di(d) + local f, init_s, v = dict_iter(d) + return select(2, f(init_s, v)) +end + +dct2tbl = function(d, processed) + if d == nil then + return null_dict + end + processed = processed or {} + local p_key = ptr2key(d) + if processed[p_key] then + return processed[p_key] + end + local ret = {} + processed[p_key] = ret + for k, di in dict_iter(d) do + ret[k] = typvalt2lua(di.di_tv, processed) + end return ret end -local lua2typvalt - local typvalt = function(typ, vval) - if type(typ) == 'string' then + if typ == nil then + typ = eval.VAR_UNKNOWN + elseif type(typ) == 'string' then typ = eval[typ] end return ffi.gc(ffi.new('typval_T', {v_type=typ, vval=vval}), eval.clear_tv) @@ -164,12 +241,68 @@ local lua2typvalt_type_tab = { end return ret end, + [func_type] = function(l, processed) + if processed[l] then + processed[l].pt_refcount = processed[l].pt_refcount + 1 + return typvalt(eval.VAR_PARTIAL, {v_partial=processed[l]}) + end + if l.args or l.dict then + local pt = ffi.gc(ffi.cast('partial_T*', eval.xmalloc(ffi.sizeof('partial_T'))), nil) + processed[l] = pt + local argv = nil + if l.args and #l.args > 0 then + argv = ffi.gc(ffi.cast('typval_T*', eval.xmalloc(ffi.sizeof('typval_T') * #l.args)), nil) + for i, arg in ipairs(l.args) do + local arg_tv = ffi.gc(lua2typvalt(arg, processed), nil) + eval.copy_tv(arg_tv, argv[i - 1]) + eval.clear_tv(arg_tv) + end + end + local dict = nil + if l.dict then + local dict_tv = ffi.gc(lua2typvalt(l.dict, processed), nil) + assert(dict_tv.v_type == eval.VAR_DICT) + dict = dict_tv.vval.v_dict + end + pt.pt_refcount = 1 + pt.pt_name = eval.xmemdupz(to_cstr(l.value), #l.value) + pt.pt_auto = not not l.auto + pt.pt_argc = l.args and #l.args or 0 + pt.pt_argv = argv + pt.pt_dict = dict + return typvalt(eval.VAR_PARTIAL, {v_partial=pt}) + else + return typvalt(eval.VAR_FUNC, { + v_string=eval.xmemdupz(to_cstr(l.value), #l.value) + }) + end + end, } +local special_vals = { + [null_string] = {eval.VAR_STRING, {v_string=ffi.cast('char_u*', nil)}}, + [null_list] = {eval.VAR_LIST, {v_list=ffi.cast('list_T*', nil)}}, + [null_dict] = {eval.VAR_DICT, {v_dict=ffi.cast('dict_T*', nil)}}, + [nil_value] = {eval.VAR_SPECIAL, {v_special=eval.kSpecialVarNull}}, + [true] = {eval.VAR_SPECIAL, {v_special=eval.kSpecialVarTrue}}, + [false] = {eval.VAR_SPECIAL, {v_special=eval.kSpecialVarFalse}}, +} + +for k, v in pairs(special_vals) do + local tmp = function(typ, vval) + special_vals[k] = function() + return typvalt(typ, vval) + end + end + tmp(v[1], v[2]) +end + lua2typvalt = function(l, processed) processed = processed or {} if l == nil or l == nil_value then - return typvalt(eval.VAR_SPECIAL, {v_special=eval.kSpecialVarNull}) + return special_vals[nil_value]() + elseif special_vals[l] then + return special_vals[l]() elseif type(l) == 'table' then if l[type_key] then return lua2typvalt_type_tab[l[type_key]](l, processed) @@ -182,18 +315,19 @@ lua2typvalt = function(l, processed) end elseif type(l) == 'number' then return typvalt(eval.VAR_FLOAT, {v_float=l}) - elseif type(l) == 'boolean' then - return typvalt(eval.VAR_SPECIAL, { - v_special=(l and eval.kSpecialVarTrue or eval.kSpecialVarFalse) - }) elseif type(l) == 'string' then return typvalt(eval.VAR_STRING, {v_string=eval.xmemdupz(to_cstr(l), #l)}) + elseif type(l) == 'cdata' then + local tv = typvalt(eval.VAR_UNKNOWN) + eval.tv_copy(l, tv) + return tv end end return { null_string=null_string, null_list=null_list, + null_dict=null_dict, list_type=list_type, dict_type=dict_type, func_type=func_type, @@ -212,4 +346,9 @@ return { typvalt2lua=typvalt2lua, typvalt=typvalt, + + li_alloc=li_alloc, + + dict_iter=dict_iter, + first_di=first_di, } diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index 3564f76442..9025c64570 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -136,7 +136,7 @@ end local cstr = ffi.typeof('char[?]') local function to_cstr(string) - return cstr((string.len(string)) + 1, string) + return cstr(#string + 1, string) end -- initialize some global variables, this is still necessary to unit test From 3c64b814d23e697dba873ef5c2854e305c48eec0 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 3 Jan 2017 23:58:12 +0300 Subject: [PATCH 20/28] unittests: Fix linter errors --- test/unit/api/private_helpers_spec.lua | 1 - test/unit/eval/helpers.lua | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/test/unit/api/private_helpers_spec.lua b/test/unit/api/private_helpers_spec.lua index e108d46370..8c54ea6a2a 100644 --- a/test/unit/api/private_helpers_spec.lua +++ b/test/unit/api/private_helpers_spec.lua @@ -98,7 +98,6 @@ describe('vim_to_object', function() }, {}, } - local ffi=require'ffi' local list = lua2typvalt(llist) eq(llist, typvalt2lua(list)) eq({nil_value, {}}, obj2lua(api.vim_to_object(list))) diff --git a/test/unit/eval/helpers.lua b/test/unit/eval/helpers.lua index 0656257361..712530a0aa 100644 --- a/test/unit/eval/helpers.lua +++ b/test/unit/eval/helpers.lua @@ -62,16 +62,16 @@ local typvalt2lua local typvalt2lua_tab typvalt2lua_tab = { - [tonumber(eval.VAR_SPECIAL)] = function(t, processed) + [tonumber(eval.VAR_SPECIAL)] = function(t) return special_tab[t.vval.v_special] end, - [tonumber(eval.VAR_NUMBER)] = function(t, processed) + [tonumber(eval.VAR_NUMBER)] = function(t) return {[type_key]=int_type, value=tonumber(t.vval.v_number)} end, - [tonumber(eval.VAR_FLOAT)] = function(t, processed) + [tonumber(eval.VAR_FLOAT)] = function(t) return tonumber(t.vval.v_float) end, - [tonumber(eval.VAR_STRING)] = function(t, processed) + [tonumber(eval.VAR_STRING)] = function(t) local str = t.vval.v_string if str == nil then return null_string @@ -117,7 +117,7 @@ typvalt2lua_tab = { } typvalt2lua = function(t, processed) - return ((typvalt2lua_tab[tonumber(t.v_type)] or function(t_inner, processed) + return ((typvalt2lua_tab[tonumber(t.v_type)] or function(t_inner) assert(false, 'Converting ' .. tonumber(t_inner.v_type) .. ' was not implemented yet') end)(t, processed or {})) end From 358097ac5fd12bb9bd3beca8ef672f189f313beb Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 4 Jan 2017 19:01:04 +0300 Subject: [PATCH 21/28] eval/encode: Always check the return value of encode_vim_to_\* --- src/nvim/eval/encode.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 828bde3124..d11db0296a 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -366,7 +366,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, #define TYPVAL_ENCODE_CONV_EMPTY_LIST(tv) \ ga_concat(gap, "[]") -#define TYPVAL_ENCODE_CONV_LIST_START(tv, dict) \ +#define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \ ga_append(gap, '[') #define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv) \ @@ -824,6 +824,7 @@ char *encode_tv2string(typval_T *tv, size_t *len) const int evs_ret = encode_vim_to_string(&ga, tv, "encode_tv2string() argument"); (void)evs_ret; + assert(evs_ret == OK); did_echo_string_emsg = false; if (len != NULL) { *len = (size_t) ga.ga_len; @@ -851,6 +852,7 @@ char *encode_tv2echo(typval_T *tv, size_t *len) } else { const int eve_ret = encode_vim_to_echo(&ga, tv, ":echo argument"); (void)eve_ret; + assert(eve_ret == OK); } if (len != NULL) { *len = (size_t) ga.ga_len; @@ -872,13 +874,15 @@ char *encode_tv2json(typval_T *tv, size_t *len) garray_T ga; ga_init(&ga, (int)sizeof(char), 80); const int evj_ret = encode_vim_to_json(&ga, tv, "encode_tv2json() argument"); - (void)evj_ret; + if (!evj_ret) { + ga_clear(&ga); + } did_echo_string_emsg = false; if (len != NULL) { - *len = (size_t) ga.ga_len; + *len = (size_t)ga.ga_len; } ga_append(&ga, '\0'); - return (char *) ga.ga_data; + return (char *)ga.ga_data; } #define TYPVAL_ENCODE_CONV_STRING(tv, buf, len) \ From c93ce07628d3e27bfd903743c1248c2f8e45aa2d Mon Sep 17 00:00:00 2001 From: ZyX Date: Wed, 4 Jan 2017 19:04:15 +0300 Subject: [PATCH 22/28] *: Remove `// fname()` comments near typval_encode includes --- src/nvim/api/private/helpers.c | 2 -- src/nvim/eval.c | 2 -- src/nvim/eval/encode.c | 8 -------- 3 files changed, 12 deletions(-) diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index de566e37ad..d3564b4ede 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -482,8 +482,6 @@ static inline void typval_encode_dict_end(EncodedData *const edata) #define TYPVAL_ENCODE_NAME object #define TYPVAL_ENCODE_FIRST_ARG_TYPE EncodedData *const #define TYPVAL_ENCODE_FIRST_ARG_NAME edata -// _object_convert_one_value() -// encode_vim_to_object() #include "nvim/eval/typval_encode.c.h" #undef TYPVAL_ENCODE_SCOPE #undef TYPVAL_ENCODE_NAME diff --git a/src/nvim/eval.c b/src/nvim/eval.c index a0f511ab23..ac00d04630 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -19218,8 +19218,6 @@ static inline void _nothing_conv_dict_end(typval_T *const tv, #define TYPVAL_ENCODE_NAME nothing #define TYPVAL_ENCODE_FIRST_ARG_TYPE const void *const #define TYPVAL_ENCODE_FIRST_ARG_NAME ignored -// _nothing_convert_one_value() -// encode_vim_to_nothing() #include "nvim/eval/typval_encode.c.h" #undef TYPVAL_ENCODE_SCOPE #undef TYPVAL_ENCODE_NAME diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index d11db0296a..e3fd857529 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -435,8 +435,6 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, #define TYPVAL_ENCODE_NAME string #define TYPVAL_ENCODE_FIRST_ARG_TYPE garray_T *const #define TYPVAL_ENCODE_FIRST_ARG_NAME gap -// _string_convert_one_value() -// encode_vim_to_string() #include "nvim/eval/typval_encode.c.h" #undef TYPVAL_ENCODE_SCOPE #undef TYPVAL_ENCODE_NAME @@ -475,8 +473,6 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, #define TYPVAL_ENCODE_NAME echo #define TYPVAL_ENCODE_FIRST_ARG_TYPE garray_T *const #define TYPVAL_ENCODE_FIRST_ARG_NAME gap -// _echo_convert_one_value() -// encode_vim_to_echo() #include "nvim/eval/typval_encode.c.h" #undef TYPVAL_ENCODE_SCOPE #undef TYPVAL_ENCODE_NAME @@ -776,8 +772,6 @@ bool encode_check_json_key(const typval_T *const tv) #define TYPVAL_ENCODE_NAME json #define TYPVAL_ENCODE_FIRST_ARG_TYPE garray_T *const #define TYPVAL_ENCODE_FIRST_ARG_NAME gap -// _json_convert_one_value() -// encode_vim_to_json() #include "nvim/eval/typval_encode.c.h" #undef TYPVAL_ENCODE_SCOPE #undef TYPVAL_ENCODE_NAME @@ -983,8 +977,6 @@ char *encode_tv2json(typval_T *tv, size_t *len) #define TYPVAL_ENCODE_NAME msgpack #define TYPVAL_ENCODE_FIRST_ARG_TYPE msgpack_packer *const #define TYPVAL_ENCODE_FIRST_ARG_NAME packer -// _msgpack_convert_one_value() -// encode_vim_to_msgpack() #include "nvim/eval/typval_encode.c.h" #undef TYPVAL_ENCODE_SCOPE #undef TYPVAL_ENCODE_NAME From 7f11ec00fd483b70d463de5f7661966ffc10a5d1 Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 6 Jan 2017 22:38:46 +0300 Subject: [PATCH 23/28] eval/typval_encode.h: Remove documentation that is located in *.c.h --- src/nvim/eval/typval_encode.h | 159 +--------------------------------- 1 file changed, 2 insertions(+), 157 deletions(-) diff --git a/src/nvim/eval/typval_encode.h b/src/nvim/eval/typval_encode.h index c2629caa39..6517efa961 100644 --- a/src/nvim/eval/typval_encode.h +++ b/src/nvim/eval/typval_encode.h @@ -1,162 +1,7 @@ /// @file eval/typval_encode.h /// -/// Contains set of macros used to convert (possibly recursive) typval_T into -/// something else. For these macros to work the following macros must be -/// defined: - -/// @def TYPVAL_ENCODE_CONV_NIL -/// @brief Macros used to convert NIL value -/// -/// Is called both for special dictionary (unless #TYPVAL_ENCODE_ALLOW_SPECIALS -/// is false) and `v:null`. Accepts no arguments, but still must be -/// a function-like macros. - -/// @def TYPVAL_ENCODE_CONV_BOOL -/// @brief Macros used to convert boolean value -/// -/// Is called both for special dictionary (unless #TYPVAL_ENCODE_ALLOW_SPECIALS -/// is false) and `v:true`/`v:false`. -/// -/// @param num Boolean value to convert. Value is an expression which -/// evaluates to some integer. - -/// @def TYPVAL_ENCODE_CONV_NUMBER -/// @brief Macros used to convert integer -/// -/// @param num Integer to convert, must accept both varnumber_T and int64_t. - -/// @def TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER -/// @brief Macros used to convert unsigned integer -/// -/// Not used if #TYPVAL_ENCODE_ALLOW_SPECIALS is false, but still must be -/// defined. -/// -/// @param num Integer to convert, must accept uint64_t. - -/// @def TYPVAL_ENCODE_CONV_FLOAT -/// @brief Macros used to convert floating-point number -/// -/// @param flt Number to convert, must accept float_T. - -/// @def TYPVAL_ENCODE_CONV_STRING -/// @brief Macros used to convert plain string -/// -/// Is used to convert VAR_STRING objects as well as BIN strings represented as -/// special dictionary. -/// -/// @param buf String to convert. Is a char[] buffer, not NUL-terminated. -/// @param len String length. - -/// @def TYPVAL_ENCODE_CONV_STR_STRING -/// @brief Like #TYPVAL_ENCODE_CONV_STRING, but for STR strings -/// -/// Is used to convert dictionary keys and STR strings represented as special -/// dictionaries. - -/// @def TYPVAL_ENCODE_CONV_EXT_STRING -/// @brief Macros used to convert EXT string -/// -/// Is used to convert EXT strings represented as special dictionaries. Never -/// actually used if #TYPVAL_ENCODE_ALLOW_SPECIALS is false, but still must be -/// defined. -/// -/// @param buf String to convert. Is a char[] buffer, not NUL-terminated. -/// @param len String length. -/// @param type EXT type. - -/// @def TYPVAL_ENCODE_CONV_FUNC -/// @brief Macros used to convert a function reference -/// -/// @param fun Function name. - -/// @def TYPVAL_ENCODE_CONV_FUNC_START -/// @brief Macros used when starting to convert a funcref or a partial -/// -/// @param fun Function name. -/// @param is_partial True if converted function is a partial. -/// @param pt Pointer to partial or NULL. - -/// @def TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS -/// @brief Macros used before starting to convert partial arguments -/// -/// @param len Number of arguments. Zero for absent arguments or when -/// converting a funcref. - -/// @def TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF -/// @brief Macros used before starting to convert self dictionary -/// -/// @param len Number of arguments. May be zero for empty dictionary or -1 for -/// missing self dictionary, also when converting function -/// reference. - -/// @def TYPVAL_ENCODE_CONV_FUNC_END -/// @brief Macros used after converting a funcref or a partial -/// -/// Accepts no arguments, but still must be a function-like macros. - -/// @def TYPVAL_ENCODE_CONV_EMPTY_LIST -/// @brief Macros used to convert an empty list -/// -/// Accepts no arguments, but still must be a function-like macros. - -/// @def TYPVAL_ENCODE_CONV_EMPTY_DICT -/// @brief Macros used to convert an empty dictionary -/// -/// Accepts no arguments, but still must be a function-like macros. - -/// @def TYPVAL_ENCODE_CONV_LIST_START -/// @brief Macros used before starting to convert non-empty list -/// -/// @param len List length. Is an expression which evaluates to an integer. - -/// @def TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS -/// @brief Macros used after finishing converting non-last list item -/// -/// Accepts no arguments, but still must be a function-like macros. - -/// @def TYPVAL_ENCODE_CONV_LIST_END -/// @brief Macros used after converting non-empty list -/// -/// Accepts no arguments, but still must be a function-like macros. - -/// @def TYPVAL_ENCODE_CONV_DICT_START -/// @brief Macros used before starting to convert non-empty dictionary -/// -/// @param len Dictionary length. Is an expression which evaluates to an -/// integer. - -/// @def TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK -/// @brief Macros used to check special dictionary key -/// -/// @param label Label for goto in case check was not successfull. -/// @param key typval_T key to check. - -/// @def TYPVAL_ENCODE_CONV_DICT_AFTER_KEY -/// @brief Macros used after finishing converting dictionary key -/// -/// Accepts no arguments, but still must be a function-like macros. - -/// @def TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS -/// @brief Macros used after finishing converting non-last dictionary value -/// -/// Accepts no arguments, but still must be a function-like macros. - -/// @def TYPVAL_ENCODE_CONV_DICT_END -/// @brief Macros used after converting non-empty dictionary -/// -/// Accepts no arguments, but still must be a function-like macros. - -/// @def TYPVAL_ENCODE_CONV_RECURSE -/// @brief Macros used when self-containing container is detected -/// -/// @param val Container for which this situation was detected. -/// @param conv_type Type of the stack entry, @see MPConvStackValType. - -/// @def TYPVAL_ENCODE_ALLOW_SPECIALS -/// @brief Macros that specifies whether special dictionaries are special -/// -/// Must be something that evaluates to boolean, most likely `true` or `false`. -/// If it is false then special dictionaries are not treated specially. +/// Contains common definitions for eval/typval_encode.c.h. Most of time should +/// not be included directly. #ifndef NVIM_EVAL_TYPVAL_ENCODE_H #define NVIM_EVAL_TYPVAL_ENCODE_H From 6584fb723ad9fcdc96bbefdf79c638b840bc5655 Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 6 Jan 2017 22:48:50 +0300 Subject: [PATCH 24/28] eval/typval_encode: Use TYPVAL_ENCODE_CONV_EMPTY_DICT for partials --- src/nvim/api/private/helpers.c | 2 +- src/nvim/eval.c | 11 +++++--- src/nvim/eval/encode.c | 4 +-- src/nvim/eval/typval_encode.c.h | 50 ++++++++++++++++++++------------- 4 files changed, 40 insertions(+), 27 deletions(-) diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index d3564b4ede..701a1cbf2b 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -369,7 +369,7 @@ void set_option_to(void *to, int type, String name, Object value, Error *err) #define TYPVAL_ENCODE_CONV_EMPTY_LIST(tv) \ kv_push(edata->stack, ARRAY_OBJ(((Array) { .capacity = 0, .size = 0 }))) -#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv) \ +#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ kv_push(edata->stack, \ DICTIONARY_OBJ(((Dictionary) { .capacity = 0, .size = 0 }))) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index ac00d04630..e31e3118fa 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -19127,11 +19127,14 @@ static inline void _nothing_conv_func_end(typval_T *const tv) tv->v_lock = VAR_UNLOCKED; \ } while (0) -#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv) \ +#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ do { \ - dict_unref(tv->vval.v_dict); \ - tv->vval.v_dict = NULL; \ - tv->v_lock = VAR_UNLOCKED; \ + assert((void *)&dict != (void *)&TYPVAL_ENCODE_NODICT_VAR); \ + dict_unref((dict_T *)dict); \ + *((dict_T **)&dict) = NULL; \ + if (tv != NULL) { \ + ((typval_T *)tv)->v_lock = VAR_UNLOCKED; \ + } \ } while (0) static inline int _nothing_conv_list_start(typval_T *const tv) diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index e3fd857529..071fbc3923 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -369,7 +369,7 @@ 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_EMPTY_DICT(tv) \ +#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ ga_concat(gap, "{}") #define TYPVAL_ENCODE_CONV_NIL(tv) \ @@ -933,7 +933,7 @@ 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_EMPTY_DICT(tv) \ +#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ msgpack_pack_map(packer, 0) #define TYPVAL_ENCODE_CONV_NIL(tv) \ diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index 5795d339f0..668bdfb26a 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -117,8 +117,10 @@ /// @def TYPVAL_ENCODE_CONV_EMPTY_DICT /// @brief Macros used to convert an empty dictionary /// -/// @param tv Pointer to typval where value is stored. May not be NULL. May +/// @param tv Pointer to typval where value is stored. May be NULL. May /// point to a special dictionary. +/// @param dict Converted dictionary, lvalue or #TYPVAL_ENCODE_NODICT_VAR +/// (for dictionaries represented as special lists). /// @def TYPVAL_ENCODE_CONV_LIST_START /// @brief Macros used before starting to convert non-empty list @@ -142,7 +144,7 @@ /// /// @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 +/// @param dict Converted dictionary, lvalue or #TYPVAL_ENCODE_NODICT_VAR /// (for dictionaries represented as special lists). /// @param len Dictionary length. Is an expression which evaluates to an /// integer. @@ -158,7 +160,7 @@ /// /// @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 +/// @param dict Converted dictionary, lvalue or #TYPVAL_ENCODE_NODICT_VAR /// (for dictionaries represented as special lists). /// @def TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS @@ -166,7 +168,7 @@ /// /// @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 +/// @param dict Converted dictionary, lvalue or #TYPVAL_ENCODE_NODICT_VAR /// (for dictionaries represented as special lists). /// @def TYPVAL_ENCODE_CONV_DICT_END @@ -174,7 +176,7 @@ /// /// @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 +/// @param dict Converted dictionary, lvalue or #TYPVAL_ENCODE_NODICT_VAR /// (for dictionaries represented as special lists). /// @def TYPVAL_ENCODE_CONV_RECURSE @@ -224,6 +226,10 @@ #include "nvim/func_attr.h" #include "nvim/eval/typval_encode.h" +/// Dummy variable used because some macros need lvalue +/// +/// Must not be written to, if needed one must check that address of the +/// macros argument is (not) equal to `&TYPVAL_ENCODE_NODICT_VAR`. const dict_T *const TYPVAL_ENCODE_NODICT_VAR = NULL; static inline int _TYPVAL_ENCODE_CHECK_SELF_REFERENCE( @@ -367,7 +373,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( case VAR_DICT: { if (tv->vval.v_dict == NULL || tv->vval.v_dict->dv_hashtab.ht_used == 0) { - TYPVAL_ENCODE_CONV_EMPTY_DICT(tv); + TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, tv->vval.v_dict); break; } const dictitem_T *type_di; @@ -490,7 +496,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( } list_T *const val_list = val_di->di_tv.vval.v_list; if (val_list == NULL || val_list->lv_len == 0) { - TYPVAL_ENCODE_CONV_EMPTY_DICT(tv); + TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, TYPVAL_ENCODE_NODICT_VAR); break; } for (const listitem_T *li = val_list->lv_first; li != NULL; @@ -708,20 +714,24 @@ typval_encode_stop_converting_one_item: continue; } } - TYPVAL_ENCODE_CONV_DICT_START(NULL, pt->pt_dict, - dict->dv_hashtab.ht_used); - _mp_push(mpstack, ((MPConvStackVal) { - .type = kMPConvDict, - .tv = NULL, - .data = { - .d = { - .dict = dict, - .dictp = &pt->pt_dict, - .hi = dict->dv_hashtab.ht_array, - .todo = dict->dv_hashtab.ht_used, + if (dict->dv_hashtab.ht_used == 0) { + TYPVAL_ENCODE_CONV_EMPTY_DICT(NULL, pt->pt_dict); + } else { + TYPVAL_ENCODE_CONV_DICT_START(NULL, pt->pt_dict, + dict->dv_hashtab.ht_used); + _mp_push(mpstack, ((MPConvStackVal) { + .type = kMPConvDict, + .tv = NULL, + .data = { + .d = { + .dict = dict, + .dictp = &pt->pt_dict, + .hi = dict->dv_hashtab.ht_array, + .todo = dict->dv_hashtab.ht_used, + }, }, - }, - })); + })); + } } else { TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, -1); } From 10c6e68db6a96efd5ed4d06a48f029c18880a3c1 Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 6 Jan 2017 22:57:34 +0300 Subject: [PATCH 25/28] eval: Work with reference cycles in partials (self) properly --- src/nvim/eval.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index e31e3118fa..5fb6c04eb2 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -19100,7 +19100,7 @@ static inline int _nothing_conv_func_start(typval_T *const tv, #define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, len) #define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, len) -static inline void _nothing_conv_func_end(typval_T *const tv) +static inline void _nothing_conv_func_end(typval_T *const tv, const int copyID) FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL { if (tv->v_type == VAR_PARTIAL) { @@ -19109,7 +19109,9 @@ static inline void _nothing_conv_func_end(typval_T *const tv) return; } // Dictionaly should already be freed by the time. - assert(pt->pt_dict == NULL); + // If it was not freed then it is a part of the reference cycle. + assert(pt->pt_dict == NULL || pt->pt_dict->dv_copyID == copyID); + pt->pt_dict = NULL; // As well as all arguments. pt->pt_argc = 0; assert(pt->pt_refcount <= 1); @@ -19118,7 +19120,7 @@ static inline void _nothing_conv_func_end(typval_T *const tv) assert(tv->v_lock == VAR_UNLOCKED); } } -#define TYPVAL_ENCODE_CONV_FUNC_END(tv) _nothing_conv_func_end(tv) +#define TYPVAL_ENCODE_CONV_FUNC_END(tv) _nothing_conv_func_end(tv, copyID) #define TYPVAL_ENCODE_CONV_EMPTY_LIST(tv) \ do { \ From 1052009b3733fb7c4f2fa03faea8fb177f596dd6 Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 6 Jan 2017 23:32:04 +0300 Subject: [PATCH 26/28] eval/typval_encode: Dump empty dictionary before checking for refcycle Otherwise copyID will stay forever on empty dictionaries. --- src/nvim/eval/typval_encode.c.h | 34 ++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index 668bdfb26a..3e1170b8fa 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -703,6 +703,10 @@ typval_encode_stop_converting_one_item: dict_T *const dict = pt == NULL ? NULL : pt->pt_dict; if (dict != NULL) { TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, dict->dv_hashtab.ht_used); + if (dict->dv_hashtab.ht_used == 0) { + TYPVAL_ENCODE_CONV_EMPTY_DICT(NULL, pt->pt_dict); + continue; + } const int te_csr_ret = _TYPVAL_ENCODE_CHECK_SELF_REFERENCE( TYPVAL_ENCODE_FIRST_ARG_NAME, dict, &dict->dv_copyID, &mpstack, copyID, kMPConvDict, @@ -714,24 +718,20 @@ typval_encode_stop_converting_one_item: continue; } } - if (dict->dv_hashtab.ht_used == 0) { - TYPVAL_ENCODE_CONV_EMPTY_DICT(NULL, pt->pt_dict); - } else { - TYPVAL_ENCODE_CONV_DICT_START(NULL, pt->pt_dict, - dict->dv_hashtab.ht_used); - _mp_push(mpstack, ((MPConvStackVal) { - .type = kMPConvDict, - .tv = NULL, - .data = { - .d = { - .dict = dict, - .dictp = &pt->pt_dict, - .hi = dict->dv_hashtab.ht_array, - .todo = dict->dv_hashtab.ht_used, - }, + TYPVAL_ENCODE_CONV_DICT_START(NULL, pt->pt_dict, + dict->dv_hashtab.ht_used); + _mp_push(mpstack, ((MPConvStackVal) { + .type = kMPConvDict, + .tv = NULL, + .data = { + .d = { + .dict = dict, + .dictp = &pt->pt_dict, + .hi = dict->dv_hashtab.ht_array, + .todo = dict->dv_hashtab.ht_used, }, - })); - } + }, + })); } else { TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, -1); } From 527636f1585c8d1f2a404af09210566c1f08d39a Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 6 Jan 2017 23:52:31 +0300 Subject: [PATCH 27/28] functests: Add tests for new fixes --- test/functional/eval/string_spec.lua | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/functional/eval/string_spec.lua b/test/functional/eval/string_spec.lua index 35caef702b..f6279e85e8 100644 --- a/test/functional/eval/string_spec.lua +++ b/test/functional/eval/string_spec.lua @@ -178,6 +178,18 @@ describe('string() function', function() redir_exec('echo string(extend(extend(g:d, {"f": g:Test2_f}), {"p": g:d.f}))')) end) + it('does not show errors when dumping partials referencing the same dictionary', + function() + command('let d = {}') + -- Regression for “eval/typval_encode: Dump empty dictionary before + -- checking for refcycle”, results in error. + eq('[function(\'tr\', {}), function(\'tr\', {})]', eval('string([function("tr", d), function("tr", d)])')) + -- Regression for “eval: Work with reference cycles in partials (self) + -- properly”, results in crash. + eval('extend(d, {"a": 1})') + eq('[function(\'tr\', {\'a\': 1}), function(\'tr\', {\'a\': 1})]', eval('string([function("tr", d), function("tr", d)])')) + end) + it('does not crash or halt when dumping partials with reference cycles in arguments', function() meths.set_var('l', {}) @@ -243,6 +255,13 @@ describe('string() function', function() eq('{}', eval('string({})')) end) + it('dumps list with two same empty dictionaries, also in partials', function() + command('let d = {}') + eq('[{}, {}]', eval('string([d, d])')) + eq('[function(\'tr\', {}), {}]', eval('string([function("tr", d), d])')) + eq('[{}, function(\'tr\', {})]', eval('string([d, function("tr", d)])')) + end) + it('dumps non-empty dictionary', function() eq('{\'t\'\'est\': 1}', funcs.string({['t\'est']=1})) end) From dd48d7b94b8a0f5b4612a3821330d508d5b4241c Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 7 Jan 2017 00:17:31 +0300 Subject: [PATCH 28/28] eval: Fix typo --- src/nvim/eval.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 5fb6c04eb2..45f317fbea 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -19108,7 +19108,7 @@ static inline void _nothing_conv_func_end(typval_T *const tv, const int copyID) if (pt == NULL) { return; } - // Dictionaly should already be freed by the time. + // Dictionary should already be freed by the time. // If it was not freed then it is a part of the reference cycle. assert(pt->pt_dict == NULL || pt->pt_dict->dv_copyID == copyID); pt->pt_dict = NULL;