1 | // This is an open source non-commercial project. Dear PVS-Studio, please check |
2 | // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com |
3 | |
4 | #include <stddef.h> |
5 | |
6 | #include <msgpack.h> |
7 | |
8 | #include "nvim/eval/typval.h" |
9 | #include "nvim/eval.h" |
10 | #include "nvim/eval/decode.h" |
11 | #include "nvim/eval/encode.h" |
12 | #include "nvim/ascii.h" |
13 | #include "nvim/macros.h" |
14 | #include "nvim/message.h" |
15 | #include "nvim/globals.h" |
16 | #include "nvim/charset.h" // vim_str2nr |
17 | #include "nvim/lib/kvec.h" |
18 | #include "nvim/vim.h" // OK, FAIL |
19 | |
20 | /// Helper structure for container_struct |
21 | typedef struct { |
22 | size_t stack_index; ///< Index of current container in stack. |
23 | list_T *special_val; ///< _VAL key contents for special maps. |
24 | ///< When container is not a special dictionary it is |
25 | ///< NULL. |
26 | const char *s; ///< Location where container starts. |
27 | typval_T container; ///< Container. Either VAR_LIST, VAR_DICT or VAR_LIST |
28 | ///< which is _VAL from special dictionary. |
29 | } ContainerStackItem; |
30 | |
31 | /// Helper structure for values struct |
32 | typedef struct { |
33 | bool is_special_string; ///< Indicates that current value is a special |
34 | ///< dictionary with string. |
35 | bool didcomma; ///< True if previous token was comma. |
36 | bool didcolon; ///< True if previous token was colon. |
37 | typval_T val; ///< Actual value. |
38 | } ValuesStackItem; |
39 | |
40 | /// Vector containing values not yet saved in any container |
41 | typedef kvec_t(ValuesStackItem) ValuesStack; |
42 | |
43 | /// Vector containing containers, each next container is located inside previous |
44 | typedef kvec_t(ContainerStackItem) ContainerStack; |
45 | |
46 | #ifdef INCLUDE_GENERATED_DECLARATIONS |
47 | # include "eval/decode.c.generated.h" |
48 | #endif |
49 | |
50 | /// Create special dictionary |
51 | /// |
52 | /// @param[out] rettv Location where created dictionary will be saved. |
53 | /// @param[in] type Type of the dictionary. |
54 | /// @param[in] val Value associated with the _VAL key. |
55 | static inline void create_special_dict(typval_T *const rettv, |
56 | const MessagePackType type, |
57 | typval_T val) |
58 | FUNC_ATTR_NONNULL_ALL |
59 | { |
60 | dict_T *const dict = tv_dict_alloc(); |
61 | dictitem_T *const type_di = tv_dict_item_alloc_len(S_LEN("_TYPE" )); |
62 | type_di->di_tv.v_type = VAR_LIST; |
63 | type_di->di_tv.v_lock = VAR_UNLOCKED; |
64 | type_di->di_tv.vval.v_list = (list_T *)eval_msgpack_type_lists[type]; |
65 | tv_list_ref(type_di->di_tv.vval.v_list); |
66 | tv_dict_add(dict, type_di); |
67 | dictitem_T *const val_di = tv_dict_item_alloc_len(S_LEN("_VAL" )); |
68 | val_di->di_tv = val; |
69 | tv_dict_add(dict, val_di); |
70 | dict->dv_refcount++; |
71 | *rettv = (typval_T) { |
72 | .v_type = VAR_DICT, |
73 | .v_lock = VAR_UNLOCKED, |
74 | .vval = { .v_dict = dict }, |
75 | }; |
76 | } |
77 | |
78 | #define DICT_LEN(dict) (dict)->dv_hashtab.ht_used |
79 | |
80 | /// Helper function used for working with stack vectors used by JSON decoder |
81 | /// |
82 | /// @param[in,out] obj New object. Will either be put into the stack (and, |
83 | /// probably, also inside container) or freed. |
84 | /// @param[out] stack Object stack. |
85 | /// @param[out] container_stack Container objects stack. |
86 | /// @param[in,out] pp Position in string which is currently being parsed. Used |
87 | /// for error reporting and is also set when decoding is |
88 | /// restarted due to the necessity of converting regular |
89 | /// dictionary to a special map. |
90 | /// @param[out] next_map_special Is set to true when dictionary needs to be |
91 | /// converted to a special map, otherwise not |
92 | /// touched. Indicates that decoding has been |
93 | /// restarted. |
94 | /// @param[out] didcomma True if previous token was comma. Is set to recorded |
95 | /// value when decoder is restarted, otherwise unused. |
96 | /// @param[out] didcolon True if previous token was colon. Is set to recorded |
97 | /// value when decoder is restarted, otherwise unused. |
98 | /// |
99 | /// @return OK in case of success, FAIL in case of error. |
100 | static inline int json_decoder_pop(ValuesStackItem obj, |
101 | ValuesStack *const stack, |
102 | ContainerStack *const container_stack, |
103 | const char **const pp, |
104 | bool *const next_map_special, |
105 | bool *const didcomma, |
106 | bool *const didcolon) |
107 | FUNC_ATTR_NONNULL_ALL |
108 | { |
109 | if (kv_size(*container_stack) == 0) { |
110 | kv_push(*stack, obj); |
111 | return OK; |
112 | } |
113 | ContainerStackItem last_container = kv_last(*container_stack); |
114 | const char *val_location = *pp; |
115 | if (obj.val.v_type == last_container.container.v_type |
116 | // vval.v_list and vval.v_dict should have the same size and offset |
117 | && ((void *) obj.val.vval.v_list |
118 | == (void *) last_container.container.vval.v_list)) { |
119 | (void) kv_pop(*container_stack); |
120 | val_location = last_container.s; |
121 | last_container = kv_last(*container_stack); |
122 | } |
123 | if (last_container.container.v_type == VAR_LIST) { |
124 | if (tv_list_len(last_container.container.vval.v_list) != 0 |
125 | && !obj.didcomma) { |
126 | EMSG2(_("E474: Expected comma before list item: %s" ), val_location); |
127 | tv_clear(&obj.val); |
128 | return FAIL; |
129 | } |
130 | assert(last_container.special_val == NULL); |
131 | tv_list_append_owned_tv(last_container.container.vval.v_list, obj.val); |
132 | } else if (last_container.stack_index == kv_size(*stack) - 2) { |
133 | if (!obj.didcolon) { |
134 | EMSG2(_("E474: Expected colon before dictionary value: %s" ), |
135 | val_location); |
136 | tv_clear(&obj.val); |
137 | return FAIL; |
138 | } |
139 | ValuesStackItem key = kv_pop(*stack); |
140 | if (last_container.special_val == NULL) { |
141 | // These cases should have already been handled. |
142 | assert(!(key.is_special_string |
143 | || key.val.vval.v_string == NULL |
144 | || *key.val.vval.v_string == NUL)); |
145 | dictitem_T *const obj_di = tv_dict_item_alloc( |
146 | (const char *)key.val.vval.v_string); |
147 | tv_clear(&key.val); |
148 | if (tv_dict_add(last_container.container.vval.v_dict, obj_di) |
149 | == FAIL) { |
150 | assert(false); |
151 | } |
152 | obj_di->di_tv = obj.val; |
153 | } else { |
154 | list_T *const kv_pair = tv_list_alloc(2); |
155 | tv_list_append_list(last_container.special_val, kv_pair); |
156 | tv_list_append_owned_tv(kv_pair, key.val); |
157 | tv_list_append_owned_tv(kv_pair, obj.val); |
158 | } |
159 | } else { |
160 | // Object with key only |
161 | if (!obj.is_special_string && obj.val.v_type != VAR_STRING) { |
162 | EMSG2(_("E474: Expected string key: %s" ), *pp); |
163 | tv_clear(&obj.val); |
164 | return FAIL; |
165 | } else if (!obj.didcomma |
166 | && (last_container.special_val == NULL |
167 | && (DICT_LEN(last_container.container.vval.v_dict) != 0))) { |
168 | EMSG2(_("E474: Expected comma before dictionary key: %s" ), val_location); |
169 | tv_clear(&obj.val); |
170 | return FAIL; |
171 | } |
172 | // Handle empty key and key represented as special dictionary |
173 | if (last_container.special_val == NULL |
174 | && (obj.is_special_string |
175 | || obj.val.vval.v_string == NULL |
176 | || *obj.val.vval.v_string == NUL |
177 | || tv_dict_find(last_container.container.vval.v_dict, |
178 | (const char *)obj.val.vval.v_string, -1))) { |
179 | tv_clear(&obj.val); |
180 | |
181 | // Restart |
182 | (void) kv_pop(*container_stack); |
183 | ValuesStackItem last_container_val = |
184 | kv_A(*stack, last_container.stack_index); |
185 | while (kv_size(*stack) > last_container.stack_index) { |
186 | tv_clear(&(kv_pop(*stack).val)); |
187 | } |
188 | *pp = last_container.s; |
189 | *didcomma = last_container_val.didcomma; |
190 | *didcolon = last_container_val.didcolon; |
191 | *next_map_special = true; |
192 | return OK; |
193 | } |
194 | kv_push(*stack, obj); |
195 | } |
196 | return OK; |
197 | } |
198 | |
199 | #define LENP(p, e) \ |
200 | ((int) ((e) - (p))), (p) |
201 | #define OBJ(obj_tv, is_sp_string, didcomma_, didcolon_) \ |
202 | ((ValuesStackItem) { \ |
203 | .is_special_string = (is_sp_string), \ |
204 | .val = (obj_tv), \ |
205 | .didcomma = (didcomma_), \ |
206 | .didcolon = (didcolon_), \ |
207 | }) |
208 | |
209 | #define POP(obj_tv, is_sp_string) \ |
210 | do { \ |
211 | if (json_decoder_pop(OBJ(obj_tv, is_sp_string, *didcomma, *didcolon), \ |
212 | stack, container_stack, \ |
213 | &p, next_map_special, didcomma, didcolon) \ |
214 | == FAIL) { \ |
215 | goto parse_json_string_fail; \ |
216 | } \ |
217 | if (*next_map_special) { \ |
218 | goto parse_json_string_ret; \ |
219 | } \ |
220 | } while (0) |
221 | |
222 | /// Create a new special dictionary that ought to represent a MAP |
223 | /// |
224 | /// @param[out] ret_tv Address where new special dictionary is saved. |
225 | /// @param[in] len Expected number of items to be populated before list |
226 | /// becomes accessible from VimL. It is still valid to |
227 | /// underpopulate a list, value only controls how many elements |
228 | /// will be allocated in advance. @see ListLenSpecials. |
229 | /// |
230 | /// @return [allocated] list which should contain key-value pairs. Return value |
231 | /// may be safely ignored. |
232 | list_T *decode_create_map_special_dict(typval_T *const ret_tv, |
233 | const ptrdiff_t len) |
234 | FUNC_ATTR_NONNULL_ALL |
235 | { |
236 | list_T *const list = tv_list_alloc(len); |
237 | tv_list_ref(list); |
238 | create_special_dict(ret_tv, kMPMap, ((typval_T) { |
239 | .v_type = VAR_LIST, |
240 | .v_lock = VAR_UNLOCKED, |
241 | .vval = { .v_list = list }, |
242 | })); |
243 | return list; |
244 | } |
245 | |
246 | /// Convert char* string to typval_T |
247 | /// |
248 | /// Depending on whether string has (no) NUL bytes, it may use a special |
249 | /// dictionary or decode string to VAR_STRING. |
250 | /// |
251 | /// @param[in] s String to decode. |
252 | /// @param[in] len String length. |
253 | /// @param[in] hasnul Whether string has NUL byte, not or it was not yet |
254 | /// determined. |
255 | /// @param[in] binary If true, save special string type as kMPBinary, |
256 | /// otherwise kMPString. |
257 | /// @param[in] s_allocated If true, then `s` was allocated and can be saved in |
258 | /// a returned structure. If it is not saved there, it |
259 | /// will be freed. |
260 | /// |
261 | /// @return Decoded string. |
262 | typval_T decode_string(const char *const s, const size_t len, |
263 | const TriState hasnul, const bool binary, |
264 | const bool s_allocated) |
265 | FUNC_ATTR_WARN_UNUSED_RESULT |
266 | { |
267 | assert(s != NULL || len == 0); |
268 | const bool really_hasnul = (hasnul == kNone |
269 | ? ((s != NULL) && (memchr(s, NUL, len) != NULL)) |
270 | : (bool)hasnul); |
271 | if (really_hasnul) { |
272 | list_T *const list = tv_list_alloc(kListLenMayKnow); |
273 | tv_list_ref(list); |
274 | typval_T tv; |
275 | create_special_dict(&tv, binary ? kMPBinary : kMPString, ((typval_T) { |
276 | .v_type = VAR_LIST, |
277 | .v_lock = VAR_UNLOCKED, |
278 | .vval = { .v_list = list }, |
279 | })); |
280 | const int elw_ret = encode_list_write((void *)list, s, len); |
281 | if (s_allocated) { |
282 | xfree((void *)s); |
283 | } |
284 | if (elw_ret == -1) { |
285 | tv_clear(&tv); |
286 | return (typval_T) { .v_type = VAR_UNKNOWN, .v_lock = VAR_UNLOCKED }; |
287 | } |
288 | return tv; |
289 | } else { |
290 | return (typval_T) { |
291 | .v_type = VAR_STRING, |
292 | .v_lock = VAR_UNLOCKED, |
293 | .vval = { .v_string = (char_u *)( |
294 | (s == NULL || s_allocated) ? (char *)s : xmemdupz(s, len)) }, |
295 | }; |
296 | } |
297 | } |
298 | |
299 | /// Parse JSON double-quoted string |
300 | /// |
301 | /// @param[in] buf Buffer being converted. |
302 | /// @param[in] buf_len Length of the buffer. |
303 | /// @param[in,out] pp Pointer to the start of the string. Must point to '"'. |
304 | /// Is advanced to the closing '"'. Also see |
305 | /// json_decoder_pop(), it may set pp to another location |
306 | /// and alter next_map_special, didcomma and didcolon. |
307 | /// @param[out] stack Object stack. |
308 | /// @param[out] container_stack Container objects stack. |
309 | /// @param[out] next_map_special Is set to true when dictionary is converted |
310 | /// to a special map, otherwise not touched. |
311 | /// @param[out] didcomma True if previous token was comma. Is set to recorded |
312 | /// value when decoder is restarted, otherwise unused. |
313 | /// @param[out] didcolon True if previous token was colon. Is set to recorded |
314 | /// value when decoder is restarted, otherwise unused. |
315 | /// |
316 | /// @return OK in case of success, FAIL in case of error. |
317 | static inline int parse_json_string(const char *const buf, const size_t buf_len, |
318 | const char **const pp, |
319 | ValuesStack *const stack, |
320 | ContainerStack *const container_stack, |
321 | bool *const next_map_special, |
322 | bool *const didcomma, |
323 | bool *const didcolon) |
324 | FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE |
325 | { |
326 | const char *const e = buf + buf_len; |
327 | const char *p = *pp; |
328 | size_t len = 0; |
329 | const char *const s = ++p; |
330 | int ret = OK; |
331 | while (p < e && *p != '"') { |
332 | if (*p == '\\') { |
333 | p++; |
334 | if (p == e) { |
335 | emsgf(_("E474: Unfinished escape sequence: %.*s" ), |
336 | (int) buf_len, buf); |
337 | goto parse_json_string_fail; |
338 | } |
339 | switch (*p) { |
340 | case 'u': { |
341 | if (p + 4 >= e) { |
342 | emsgf(_("E474: Unfinished unicode escape sequence: %.*s" ), |
343 | (int) buf_len, buf); |
344 | goto parse_json_string_fail; |
345 | } else if (!ascii_isxdigit(p[1]) |
346 | || !ascii_isxdigit(p[2]) |
347 | || !ascii_isxdigit(p[3]) |
348 | || !ascii_isxdigit(p[4])) { |
349 | emsgf(_("E474: Expected four hex digits after \\u: %.*s" ), |
350 | LENP(p - 1, e)); |
351 | goto parse_json_string_fail; |
352 | } |
353 | // One UTF-8 character below U+10000 can take up to 3 bytes, |
354 | // above up to 6, but they are encoded using two \u escapes. |
355 | len += 3; |
356 | p += 5; |
357 | break; |
358 | } |
359 | case '\\': |
360 | case '/': |
361 | case '"': |
362 | case 't': |
363 | case 'b': |
364 | case 'n': |
365 | case 'r': |
366 | case 'f': { |
367 | len++; |
368 | p++; |
369 | break; |
370 | } |
371 | default: { |
372 | emsgf(_("E474: Unknown escape sequence: %.*s" ), LENP(p - 1, e)); |
373 | goto parse_json_string_fail; |
374 | } |
375 | } |
376 | } else { |
377 | uint8_t p_byte = (uint8_t) *p; |
378 | // unescaped = %x20-21 / %x23-5B / %x5D-10FFFF |
379 | if (p_byte < 0x20) { |
380 | emsgf(_("E474: ASCII control characters cannot be present " |
381 | "inside string: %.*s" ), LENP(p, e)); |
382 | goto parse_json_string_fail; |
383 | } |
384 | const int ch = utf_ptr2char((char_u *) p); |
385 | // All characters above U+007F are encoded using two or more bytes |
386 | // and thus cannot possibly be equal to *p. But utf_ptr2char({0xFF, |
387 | // 0}) will return 0xFF, even though 0xFF cannot start any UTF-8 |
388 | // code point at all. |
389 | // |
390 | // The only exception is U+00C3 which is represented as 0xC3 0x83. |
391 | if (ch >= 0x80 && p_byte == ch |
392 | && !(ch == 0xC3 && p + 1 < e && (uint8_t) p[1] == 0x83)) { |
393 | emsgf(_("E474: Only UTF-8 strings allowed: %.*s" ), LENP(p, e)); |
394 | goto parse_json_string_fail; |
395 | } else if (ch > 0x10FFFF) { |
396 | emsgf(_("E474: Only UTF-8 code points up to U+10FFFF " |
397 | "are allowed to appear unescaped: %.*s" ), LENP(p, e)); |
398 | goto parse_json_string_fail; |
399 | } |
400 | const size_t ch_len = (size_t) utf_char2len(ch); |
401 | assert(ch_len == (size_t) (ch ? utf_ptr2len((char_u *) p) : 1)); |
402 | len += ch_len; |
403 | p += ch_len; |
404 | } |
405 | } |
406 | if (p == e || *p != '"') { |
407 | emsgf(_("E474: Expected string end: %.*s" ), (int) buf_len, buf); |
408 | goto parse_json_string_fail; |
409 | } |
410 | if (len == 0) { |
411 | POP(((typval_T) { |
412 | .v_type = VAR_STRING, |
413 | .vval = { .v_string = NULL }, |
414 | }), false); |
415 | goto parse_json_string_ret; |
416 | } |
417 | char *str = xmalloc(len + 1); |
418 | int fst_in_pair = 0; |
419 | char *str_end = str; |
420 | bool hasnul = false; |
421 | #define PUT_FST_IN_PAIR(fst_in_pair, str_end) \ |
422 | do { \ |
423 | if (fst_in_pair != 0) { \ |
424 | str_end += utf_char2bytes(fst_in_pair, (char_u *) str_end); \ |
425 | fst_in_pair = 0; \ |
426 | } \ |
427 | } while (0) |
428 | for (const char *t = s; t < p; t++) { |
429 | if (t[0] != '\\' || t[1] != 'u') { |
430 | PUT_FST_IN_PAIR(fst_in_pair, str_end); |
431 | } |
432 | if (*t == '\\') { |
433 | t++; |
434 | switch (*t) { |
435 | case 'u': { |
436 | const char ubuf[] = { t[1], t[2], t[3], t[4] }; |
437 | t += 4; |
438 | uvarnumber_T ch; |
439 | vim_str2nr((char_u *)ubuf, NULL, NULL, |
440 | STR2NR_HEX | STR2NR_FORCE, NULL, &ch, 4); |
441 | if (ch == 0) { |
442 | hasnul = true; |
443 | } |
444 | if (SURROGATE_HI_START <= ch && ch <= SURROGATE_HI_END) { |
445 | PUT_FST_IN_PAIR(fst_in_pair, str_end); |
446 | fst_in_pair = (int) ch; |
447 | } else if (SURROGATE_LO_START <= ch && ch <= SURROGATE_LO_END |
448 | && fst_in_pair != 0) { |
449 | const int full_char = ( |
450 | (int) (ch - SURROGATE_LO_START) |
451 | + ((fst_in_pair - SURROGATE_HI_START) << 10) |
452 | + SURROGATE_FIRST_CHAR); |
453 | str_end += utf_char2bytes(full_char, (char_u *) str_end); |
454 | fst_in_pair = 0; |
455 | } else { |
456 | PUT_FST_IN_PAIR(fst_in_pair, str_end); |
457 | str_end += utf_char2bytes((int) ch, (char_u *) str_end); |
458 | } |
459 | break; |
460 | } |
461 | case '\\': |
462 | case '/': |
463 | case '"': |
464 | case 't': |
465 | case 'b': |
466 | case 'n': |
467 | case 'r': |
468 | case 'f': { |
469 | static const char escapes[] = { |
470 | ['\\'] = '\\', |
471 | ['/'] = '/', |
472 | ['"'] = '"', |
473 | ['t'] = TAB, |
474 | ['b'] = BS, |
475 | ['n'] = NL, |
476 | ['r'] = CAR, |
477 | ['f'] = FF, |
478 | }; |
479 | *str_end++ = escapes[(int) *t]; |
480 | break; |
481 | } |
482 | default: { |
483 | assert(false); |
484 | } |
485 | } |
486 | } else { |
487 | *str_end++ = *t; |
488 | } |
489 | } |
490 | PUT_FST_IN_PAIR(fst_in_pair, str_end); |
491 | #undef PUT_FST_IN_PAIR |
492 | *str_end = NUL; |
493 | typval_T obj = decode_string( |
494 | str, (size_t)(str_end - str), hasnul ? kTrue : kFalse, false, true); |
495 | if (obj.v_type == VAR_UNKNOWN) { |
496 | goto parse_json_string_fail; |
497 | } |
498 | POP(obj, obj.v_type != VAR_STRING); |
499 | goto parse_json_string_ret; |
500 | parse_json_string_fail: |
501 | ret = FAIL; |
502 | parse_json_string_ret: |
503 | *pp = p; |
504 | return ret; |
505 | } |
506 | |
507 | #undef POP |
508 | |
509 | /// Parse JSON number: both floating-point and integer |
510 | /// |
511 | /// Number format: `-?\d+(?:.\d+)?(?:[eE][+-]?\d+)?`. |
512 | /// |
513 | /// @param[in] buf Buffer being converted. |
514 | /// @param[in] buf_len Length of the buffer. |
515 | /// @param[in,out] pp Pointer to the start of the number. Must point to |
516 | /// a digit or a minus sign. Is advanced to the last |
517 | /// character of the number. Also see json_decoder_pop(), it |
518 | /// may set pp to another location and alter |
519 | /// next_map_special, didcomma and didcolon. |
520 | /// @param[out] stack Object stack. |
521 | /// @param[out] container_stack Container objects stack. |
522 | /// @param[out] next_map_special Is set to true when dictionary is converted |
523 | /// to a special map, otherwise not touched. |
524 | /// @param[out] didcomma True if previous token was comma. Is set to recorded |
525 | /// value when decoder is restarted, otherwise unused. |
526 | /// @param[out] didcolon True if previous token was colon. Is set to recorded |
527 | /// value when decoder is restarted, otherwise unused. |
528 | /// |
529 | /// @return OK in case of success, FAIL in case of error. |
530 | static inline int parse_json_number(const char *const buf, const size_t buf_len, |
531 | const char **const pp, |
532 | ValuesStack *const stack, |
533 | ContainerStack *const container_stack, |
534 | bool *const next_map_special, |
535 | bool *const didcomma, |
536 | bool *const didcolon) |
537 | FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE |
538 | { |
539 | const char *const e = buf + buf_len; |
540 | const char *p = *pp; |
541 | int ret = OK; |
542 | const char *const s = p; |
543 | const char *ints = NULL; |
544 | const char *fracs = NULL; |
545 | const char *exps = NULL; |
546 | const char *exps_s = NULL; |
547 | if (*p == '-') { |
548 | p++; |
549 | } |
550 | ints = p; |
551 | if (p >= e) { |
552 | goto parse_json_number_check; |
553 | } |
554 | while (p < e && ascii_isdigit(*p)) { |
555 | p++; |
556 | } |
557 | if (p != ints + 1 && *ints == '0') { |
558 | emsgf(_("E474: Leading zeroes are not allowed: %.*s" ), LENP(s, e)); |
559 | goto parse_json_number_fail; |
560 | } |
561 | if (p >= e || p == ints) { |
562 | goto parse_json_number_check; |
563 | } |
564 | if (*p == '.') { |
565 | p++; |
566 | fracs = p; |
567 | while (p < e && ascii_isdigit(*p)) { |
568 | p++; |
569 | } |
570 | if (p >= e || p == fracs) { |
571 | goto parse_json_number_check; |
572 | } |
573 | } |
574 | if (*p == 'e' || *p == 'E') { |
575 | p++; |
576 | exps_s = p; |
577 | if (p < e && (*p == '-' || *p == '+')) { |
578 | p++; |
579 | } |
580 | exps = p; |
581 | while (p < e && ascii_isdigit(*p)) { |
582 | p++; |
583 | } |
584 | } |
585 | parse_json_number_check: |
586 | if (p == ints) { |
587 | emsgf(_("E474: Missing number after minus sign: %.*s" ), LENP(s, e)); |
588 | goto parse_json_number_fail; |
589 | } else if (p == fracs || exps_s == fracs + 1) { |
590 | emsgf(_("E474: Missing number after decimal dot: %.*s" ), LENP(s, e)); |
591 | goto parse_json_number_fail; |
592 | } else if (p == exps) { |
593 | emsgf(_("E474: Missing exponent: %.*s" ), LENP(s, e)); |
594 | goto parse_json_number_fail; |
595 | } |
596 | typval_T tv = { |
597 | .v_type = VAR_NUMBER, |
598 | .v_lock = VAR_UNLOCKED, |
599 | }; |
600 | const size_t exp_num_len = (size_t) (p - s); |
601 | if (fracs || exps) { |
602 | // Convert floating-point number |
603 | const size_t num_len = string2float(s, &tv.vval.v_float); |
604 | if (exp_num_len != num_len) { |
605 | emsgf(_("E685: internal error: while converting number \"%.*s\" " |
606 | "to float string2float consumed %zu bytes in place of %zu" ), |
607 | (int) exp_num_len, s, num_len, exp_num_len); |
608 | } |
609 | tv.v_type = VAR_FLOAT; |
610 | } else { |
611 | // Convert integer |
612 | varnumber_T nr; |
613 | int num_len; |
614 | vim_str2nr((char_u *) s, NULL, &num_len, 0, &nr, NULL, (int) (p - s)); |
615 | if ((int) exp_num_len != num_len) { |
616 | emsgf(_("E685: internal error: while converting number \"%.*s\" " |
617 | "to integer vim_str2nr consumed %i bytes in place of %zu" ), |
618 | (int) exp_num_len, s, num_len, exp_num_len); |
619 | } |
620 | tv.vval.v_number = nr; |
621 | } |
622 | if (json_decoder_pop(OBJ(tv, false, *didcomma, *didcolon), |
623 | stack, container_stack, |
624 | &p, next_map_special, didcomma, didcolon) == FAIL) { |
625 | goto parse_json_number_fail; |
626 | } |
627 | if (*next_map_special) { |
628 | goto parse_json_number_ret; |
629 | } |
630 | p--; |
631 | goto parse_json_number_ret; |
632 | parse_json_number_fail: |
633 | ret = FAIL; |
634 | parse_json_number_ret: |
635 | *pp = p; |
636 | return ret; |
637 | } |
638 | |
639 | #define POP(obj_tv, is_sp_string) \ |
640 | do { \ |
641 | if (json_decoder_pop(OBJ(obj_tv, is_sp_string, didcomma, didcolon), \ |
642 | &stack, &container_stack, \ |
643 | &p, &next_map_special, &didcomma, &didcolon) \ |
644 | == FAIL) { \ |
645 | goto json_decode_string_fail; \ |
646 | } \ |
647 | if (next_map_special) { \ |
648 | goto json_decode_string_cycle_start; \ |
649 | } \ |
650 | } while (0) |
651 | |
652 | /// Convert JSON string into VimL object |
653 | /// |
654 | /// @param[in] buf String to convert. UTF-8 encoding is assumed. |
655 | /// @param[in] buf_len Length of the string. |
656 | /// @param[out] rettv Location where to save results. |
657 | /// |
658 | /// @return OK in case of success, FAIL otherwise. |
659 | int json_decode_string(const char *const buf, const size_t buf_len, |
660 | typval_T *const rettv) |
661 | FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT |
662 | { |
663 | const char *p = buf; |
664 | const char *const e = buf + buf_len; |
665 | while (p < e && (*p == ' ' || *p == TAB || *p == NL || *p == CAR)) { |
666 | p++; |
667 | } |
668 | if (p == e) { |
669 | EMSG(_("E474: Attempt to decode a blank string" )); |
670 | return FAIL; |
671 | } |
672 | int ret = OK; |
673 | ValuesStack stack = KV_INITIAL_VALUE; |
674 | ContainerStack container_stack = KV_INITIAL_VALUE; |
675 | rettv->v_type = VAR_UNKNOWN; |
676 | bool didcomma = false; |
677 | bool didcolon = false; |
678 | bool next_map_special = false; |
679 | for (; p < e; p++) { |
680 | json_decode_string_cycle_start: |
681 | assert(*p == '{' || next_map_special == false); |
682 | switch (*p) { |
683 | case '}': |
684 | case ']': { |
685 | if (kv_size(container_stack) == 0) { |
686 | emsgf(_("E474: No container to close: %.*s" ), LENP(p, e)); |
687 | goto json_decode_string_fail; |
688 | } |
689 | ContainerStackItem last_container = kv_last(container_stack); |
690 | if (*p == '}' && last_container.container.v_type != VAR_DICT) { |
691 | emsgf(_("E474: Closing list with curly bracket: %.*s" ), LENP(p, e)); |
692 | goto json_decode_string_fail; |
693 | } else if (*p == ']' && last_container.container.v_type != VAR_LIST) { |
694 | emsgf(_("E474: Closing dictionary with square bracket: %.*s" ), |
695 | LENP(p, e)); |
696 | goto json_decode_string_fail; |
697 | } else if (didcomma) { |
698 | emsgf(_("E474: Trailing comma: %.*s" ), LENP(p, e)); |
699 | goto json_decode_string_fail; |
700 | } else if (didcolon) { |
701 | emsgf(_("E474: Expected value after colon: %.*s" ), LENP(p, e)); |
702 | goto json_decode_string_fail; |
703 | } else if (last_container.stack_index != kv_size(stack) - 1) { |
704 | assert(last_container.stack_index < kv_size(stack) - 1); |
705 | emsgf(_("E474: Expected value: %.*s" ), LENP(p, e)); |
706 | goto json_decode_string_fail; |
707 | } |
708 | if (kv_size(stack) == 1) { |
709 | p++; |
710 | (void) kv_pop(container_stack); |
711 | goto json_decode_string_after_cycle; |
712 | } else { |
713 | if (json_decoder_pop(kv_pop(stack), &stack, &container_stack, &p, |
714 | &next_map_special, &didcomma, &didcolon) |
715 | == FAIL) { |
716 | goto json_decode_string_fail; |
717 | } |
718 | assert(!next_map_special); |
719 | break; |
720 | } |
721 | } |
722 | case ',': { |
723 | if (kv_size(container_stack) == 0) { |
724 | emsgf(_("E474: Comma not inside container: %.*s" ), LENP(p, e)); |
725 | goto json_decode_string_fail; |
726 | } |
727 | ContainerStackItem last_container = kv_last(container_stack); |
728 | if (didcomma) { |
729 | emsgf(_("E474: Duplicate comma: %.*s" ), LENP(p, e)); |
730 | goto json_decode_string_fail; |
731 | } else if (didcolon) { |
732 | emsgf(_("E474: Comma after colon: %.*s" ), LENP(p, e)); |
733 | goto json_decode_string_fail; |
734 | } else if (last_container.container.v_type == VAR_DICT |
735 | && last_container.stack_index != kv_size(stack) - 1) { |
736 | emsgf(_("E474: Using comma in place of colon: %.*s" ), LENP(p, e)); |
737 | goto json_decode_string_fail; |
738 | } else if (last_container.special_val == NULL |
739 | ? (last_container.container.v_type == VAR_DICT |
740 | ? (DICT_LEN(last_container.container.vval.v_dict) == 0) |
741 | : (tv_list_len(last_container.container.vval.v_list) |
742 | == 0)) |
743 | : (tv_list_len(last_container.special_val) == 0)) { |
744 | emsgf(_("E474: Leading comma: %.*s" ), LENP(p, e)); |
745 | goto json_decode_string_fail; |
746 | } |
747 | didcomma = true; |
748 | continue; |
749 | } |
750 | case ':': { |
751 | if (kv_size(container_stack) == 0) { |
752 | emsgf(_("E474: Colon not inside container: %.*s" ), LENP(p, e)); |
753 | goto json_decode_string_fail; |
754 | } |
755 | ContainerStackItem last_container = kv_last(container_stack); |
756 | if (last_container.container.v_type != VAR_DICT) { |
757 | emsgf(_("E474: Using colon not in dictionary: %.*s" ), LENP(p, e)); |
758 | goto json_decode_string_fail; |
759 | } else if (last_container.stack_index != kv_size(stack) - 2) { |
760 | emsgf(_("E474: Unexpected colon: %.*s" ), LENP(p, e)); |
761 | goto json_decode_string_fail; |
762 | } else if (didcomma) { |
763 | emsgf(_("E474: Colon after comma: %.*s" ), LENP(p, e)); |
764 | goto json_decode_string_fail; |
765 | } else if (didcolon) { |
766 | emsgf(_("E474: Duplicate colon: %.*s" ), LENP(p, e)); |
767 | goto json_decode_string_fail; |
768 | } |
769 | didcolon = true; |
770 | continue; |
771 | } |
772 | case ' ': |
773 | case TAB: |
774 | case NL: |
775 | case CAR: { |
776 | continue; |
777 | } |
778 | case 'n': { |
779 | if ((p + 3) >= e || strncmp(p + 1, "ull" , 3) != 0) { |
780 | emsgf(_("E474: Expected null: %.*s" ), LENP(p, e)); |
781 | goto json_decode_string_fail; |
782 | } |
783 | p += 3; |
784 | POP(((typval_T) { |
785 | .v_type = VAR_SPECIAL, |
786 | .v_lock = VAR_UNLOCKED, |
787 | .vval = { .v_special = kSpecialVarNull }, |
788 | }), false); |
789 | break; |
790 | } |
791 | case 't': { |
792 | if ((p + 3) >= e || strncmp(p + 1, "rue" , 3) != 0) { |
793 | emsgf(_("E474: Expected true: %.*s" ), LENP(p, e)); |
794 | goto json_decode_string_fail; |
795 | } |
796 | p += 3; |
797 | POP(((typval_T) { |
798 | .v_type = VAR_SPECIAL, |
799 | .v_lock = VAR_UNLOCKED, |
800 | .vval = { .v_special = kSpecialVarTrue }, |
801 | }), false); |
802 | break; |
803 | } |
804 | case 'f': { |
805 | if ((p + 4) >= e || strncmp(p + 1, "alse" , 4) != 0) { |
806 | emsgf(_("E474: Expected false: %.*s" ), LENP(p, e)); |
807 | goto json_decode_string_fail; |
808 | } |
809 | p += 4; |
810 | POP(((typval_T) { |
811 | .v_type = VAR_SPECIAL, |
812 | .v_lock = VAR_UNLOCKED, |
813 | .vval = { .v_special = kSpecialVarFalse }, |
814 | }), false); |
815 | break; |
816 | } |
817 | case '"': { |
818 | if (parse_json_string(buf, buf_len, &p, &stack, &container_stack, |
819 | &next_map_special, &didcomma, &didcolon) |
820 | == FAIL) { |
821 | // Error message was already given |
822 | goto json_decode_string_fail; |
823 | } |
824 | if (next_map_special) { |
825 | goto json_decode_string_cycle_start; |
826 | } |
827 | break; |
828 | } |
829 | case '-': |
830 | case '0': |
831 | case '1': |
832 | case '2': |
833 | case '3': |
834 | case '4': |
835 | case '5': |
836 | case '6': |
837 | case '7': |
838 | case '8': |
839 | case '9': { |
840 | if (parse_json_number(buf, buf_len, &p, &stack, &container_stack, |
841 | &next_map_special, &didcomma, &didcolon) |
842 | == FAIL) { |
843 | // Error message was already given |
844 | goto json_decode_string_fail; |
845 | } |
846 | if (next_map_special) { |
847 | goto json_decode_string_cycle_start; |
848 | } |
849 | break; |
850 | } |
851 | case '[': { |
852 | list_T *list = tv_list_alloc(kListLenMayKnow); |
853 | tv_list_ref(list); |
854 | typval_T tv = { |
855 | .v_type = VAR_LIST, |
856 | .v_lock = VAR_UNLOCKED, |
857 | .vval = { .v_list = list }, |
858 | }; |
859 | kv_push(container_stack, ((ContainerStackItem) { |
860 | .stack_index = kv_size(stack), |
861 | .s = p, |
862 | .container = tv, |
863 | .special_val = NULL, |
864 | })); |
865 | kv_push(stack, OBJ(tv, false, didcomma, didcolon)); |
866 | break; |
867 | } |
868 | case '{': { |
869 | typval_T tv; |
870 | list_T *val_list = NULL; |
871 | if (next_map_special) { |
872 | next_map_special = false; |
873 | val_list = decode_create_map_special_dict(&tv, kListLenMayKnow); |
874 | } else { |
875 | dict_T *dict = tv_dict_alloc(); |
876 | dict->dv_refcount++; |
877 | tv = (typval_T) { |
878 | .v_type = VAR_DICT, |
879 | .v_lock = VAR_UNLOCKED, |
880 | .vval = { .v_dict = dict }, |
881 | }; |
882 | } |
883 | kv_push(container_stack, ((ContainerStackItem) { |
884 | .stack_index = kv_size(stack), |
885 | .s = p, |
886 | .container = tv, |
887 | .special_val = val_list, |
888 | })); |
889 | kv_push(stack, OBJ(tv, false, didcomma, didcolon)); |
890 | break; |
891 | } |
892 | default: { |
893 | emsgf(_("E474: Unidentified byte: %.*s" ), LENP(p, e)); |
894 | goto json_decode_string_fail; |
895 | } |
896 | } |
897 | didcomma = false; |
898 | didcolon = false; |
899 | if (kv_size(container_stack) == 0) { |
900 | p++; |
901 | break; |
902 | } |
903 | } |
904 | json_decode_string_after_cycle: |
905 | for (; p < e; p++) { |
906 | switch (*p) { |
907 | case NL: |
908 | case ' ': |
909 | case TAB: |
910 | case CAR: { |
911 | break; |
912 | } |
913 | default: { |
914 | emsgf(_("E474: Trailing characters: %.*s" ), LENP(p, e)); |
915 | goto json_decode_string_fail; |
916 | } |
917 | } |
918 | } |
919 | if (kv_size(stack) == 1 && kv_size(container_stack) == 0) { |
920 | *rettv = kv_pop(stack).val; |
921 | goto json_decode_string_ret; |
922 | } |
923 | emsgf(_("E474: Unexpected end of input: %.*s" ), (int) buf_len, buf); |
924 | json_decode_string_fail: |
925 | ret = FAIL; |
926 | while (kv_size(stack)) { |
927 | tv_clear(&(kv_pop(stack).val)); |
928 | } |
929 | json_decode_string_ret: |
930 | kv_destroy(stack); |
931 | kv_destroy(container_stack); |
932 | return ret; |
933 | } |
934 | |
935 | #undef LENP |
936 | #undef POP |
937 | |
938 | #undef OBJ |
939 | |
940 | #undef DICT_LEN |
941 | |
942 | /// Convert msgpack object to a VimL one |
943 | int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) |
944 | FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT |
945 | { |
946 | switch (mobj.type) { |
947 | case MSGPACK_OBJECT_NIL: { |
948 | *rettv = (typval_T) { |
949 | .v_type = VAR_SPECIAL, |
950 | .v_lock = VAR_UNLOCKED, |
951 | .vval = { .v_special = kSpecialVarNull }, |
952 | }; |
953 | break; |
954 | } |
955 | case MSGPACK_OBJECT_BOOLEAN: { |
956 | *rettv = (typval_T) { |
957 | .v_type = VAR_SPECIAL, |
958 | .v_lock = VAR_UNLOCKED, |
959 | .vval = { |
960 | .v_special = mobj.via.boolean ? kSpecialVarTrue : kSpecialVarFalse |
961 | }, |
962 | }; |
963 | break; |
964 | } |
965 | case MSGPACK_OBJECT_POSITIVE_INTEGER: { |
966 | if (mobj.via.u64 <= VARNUMBER_MAX) { |
967 | *rettv = (typval_T) { |
968 | .v_type = VAR_NUMBER, |
969 | .v_lock = VAR_UNLOCKED, |
970 | .vval = { .v_number = (varnumber_T) mobj.via.u64 }, |
971 | }; |
972 | } else { |
973 | list_T *const list = tv_list_alloc(4); |
974 | tv_list_ref(list); |
975 | create_special_dict(rettv, kMPInteger, ((typval_T) { |
976 | .v_type = VAR_LIST, |
977 | .v_lock = VAR_UNLOCKED, |
978 | .vval = { .v_list = list }, |
979 | })); |
980 | uint64_t n = mobj.via.u64; |
981 | tv_list_append_number(list, 1); |
982 | tv_list_append_number(list, (varnumber_T)((n >> 62) & 0x3)); |
983 | tv_list_append_number(list, (varnumber_T)((n >> 31) & 0x7FFFFFFF)); |
984 | tv_list_append_number(list, (varnumber_T)(n & 0x7FFFFFFF)); |
985 | } |
986 | break; |
987 | } |
988 | case MSGPACK_OBJECT_NEGATIVE_INTEGER: { |
989 | if (mobj.via.i64 >= VARNUMBER_MIN) { // -V547 |
990 | *rettv = (typval_T) { |
991 | .v_type = VAR_NUMBER, |
992 | .v_lock = VAR_UNLOCKED, |
993 | .vval = { .v_number = (varnumber_T) mobj.via.i64 }, |
994 | }; |
995 | } else { |
996 | list_T *const list = tv_list_alloc(4); |
997 | tv_list_ref(list); |
998 | create_special_dict(rettv, kMPInteger, ((typval_T) { |
999 | .v_type = VAR_LIST, |
1000 | .v_lock = VAR_UNLOCKED, |
1001 | .vval = { .v_list = list }, |
1002 | })); |
1003 | uint64_t n = -((uint64_t)mobj.via.i64); |
1004 | tv_list_append_number(list, -1); |
1005 | tv_list_append_number(list, (varnumber_T)((n >> 62) & 0x3)); |
1006 | tv_list_append_number(list, (varnumber_T)((n >> 31) & 0x7FFFFFFF)); |
1007 | tv_list_append_number(list, (varnumber_T)(n & 0x7FFFFFFF)); |
1008 | } |
1009 | break; |
1010 | } |
1011 | #ifdef NVIM_MSGPACK_HAS_FLOAT32 |
1012 | case MSGPACK_OBJECT_FLOAT32: |
1013 | case MSGPACK_OBJECT_FLOAT64: |
1014 | #else |
1015 | case MSGPACK_OBJECT_FLOAT: |
1016 | #endif |
1017 | { |
1018 | *rettv = (typval_T) { |
1019 | .v_type = VAR_FLOAT, |
1020 | .v_lock = VAR_UNLOCKED, |
1021 | .vval = { .v_float = mobj.via.f64 }, |
1022 | }; |
1023 | break; |
1024 | } |
1025 | case MSGPACK_OBJECT_STR: { |
1026 | *rettv = decode_string(mobj.via.bin.ptr, mobj.via.bin.size, kTrue, false, |
1027 | false); |
1028 | if (rettv->v_type == VAR_UNKNOWN) { |
1029 | return FAIL; |
1030 | } |
1031 | break; |
1032 | } |
1033 | case MSGPACK_OBJECT_BIN: { |
1034 | *rettv = decode_string(mobj.via.bin.ptr, mobj.via.bin.size, kNone, true, |
1035 | false); |
1036 | if (rettv->v_type == VAR_UNKNOWN) { |
1037 | return FAIL; |
1038 | } |
1039 | break; |
1040 | } |
1041 | case MSGPACK_OBJECT_ARRAY: { |
1042 | list_T *const list = tv_list_alloc((ptrdiff_t)mobj.via.array.size); |
1043 | tv_list_ref(list); |
1044 | *rettv = (typval_T) { |
1045 | .v_type = VAR_LIST, |
1046 | .v_lock = VAR_UNLOCKED, |
1047 | .vval = { .v_list = list }, |
1048 | }; |
1049 | for (size_t i = 0; i < mobj.via.array.size; i++) { |
1050 | // Not populated yet, need to create list item to push. |
1051 | tv_list_append_owned_tv(list, (typval_T) { .v_type = VAR_UNKNOWN }); |
1052 | if (msgpack_to_vim(mobj.via.array.ptr[i], |
1053 | TV_LIST_ITEM_TV(tv_list_last(list))) |
1054 | == FAIL) { |
1055 | return FAIL; |
1056 | } |
1057 | } |
1058 | break; |
1059 | } |
1060 | case MSGPACK_OBJECT_MAP: { |
1061 | for (size_t i = 0; i < mobj.via.map.size; i++) { |
1062 | if (mobj.via.map.ptr[i].key.type != MSGPACK_OBJECT_STR |
1063 | || mobj.via.map.ptr[i].key.via.str.size == 0 |
1064 | || memchr(mobj.via.map.ptr[i].key.via.str.ptr, NUL, |
1065 | mobj.via.map.ptr[i].key.via.str.size) != NULL) { |
1066 | goto msgpack_to_vim_generic_map; |
1067 | } |
1068 | } |
1069 | dict_T *const dict = tv_dict_alloc(); |
1070 | dict->dv_refcount++; |
1071 | *rettv = (typval_T) { |
1072 | .v_type = VAR_DICT, |
1073 | .v_lock = VAR_UNLOCKED, |
1074 | .vval = { .v_dict = dict }, |
1075 | }; |
1076 | for (size_t i = 0; i < mobj.via.map.size; i++) { |
1077 | dictitem_T *const di = xmallocz(offsetof(dictitem_T, di_key) |
1078 | + mobj.via.map.ptr[i].key.via.str.size); |
1079 | memcpy(&di->di_key[0], mobj.via.map.ptr[i].key.via.str.ptr, |
1080 | mobj.via.map.ptr[i].key.via.str.size); |
1081 | di->di_tv.v_type = VAR_UNKNOWN; |
1082 | if (tv_dict_add(dict, di) == FAIL) { |
1083 | // Duplicate key: fallback to generic map |
1084 | tv_clear(rettv); |
1085 | xfree(di); |
1086 | goto msgpack_to_vim_generic_map; |
1087 | } |
1088 | if (msgpack_to_vim(mobj.via.map.ptr[i].val, &di->di_tv) == FAIL) { |
1089 | return FAIL; |
1090 | } |
1091 | } |
1092 | break; |
1093 | msgpack_to_vim_generic_map: {} |
1094 | list_T *const list = decode_create_map_special_dict( |
1095 | rettv, (ptrdiff_t)mobj.via.map.size); |
1096 | for (size_t i = 0; i < mobj.via.map.size; i++) { |
1097 | list_T *const kv_pair = tv_list_alloc(2); |
1098 | tv_list_append_list(list, kv_pair); |
1099 | |
1100 | typval_T key_tv = { .v_type = VAR_UNKNOWN }; |
1101 | if (msgpack_to_vim(mobj.via.map.ptr[i].key, &key_tv) == FAIL) { |
1102 | tv_clear(&key_tv); |
1103 | return FAIL; |
1104 | } |
1105 | tv_list_append_owned_tv(kv_pair, key_tv); |
1106 | |
1107 | typval_T val_tv = { .v_type = VAR_UNKNOWN }; |
1108 | if (msgpack_to_vim(mobj.via.map.ptr[i].val, &val_tv) == FAIL) { |
1109 | tv_clear(&val_tv); |
1110 | return FAIL; |
1111 | } |
1112 | tv_list_append_owned_tv(kv_pair, val_tv); |
1113 | } |
1114 | break; |
1115 | } |
1116 | case MSGPACK_OBJECT_EXT: { |
1117 | list_T *const list = tv_list_alloc(2); |
1118 | tv_list_ref(list); |
1119 | tv_list_append_number(list, mobj.via.ext.type); |
1120 | list_T *const ext_val_list = tv_list_alloc(kListLenMayKnow); |
1121 | tv_list_append_list(list, ext_val_list); |
1122 | create_special_dict(rettv, kMPExt, ((typval_T) { |
1123 | .v_type = VAR_LIST, |
1124 | .v_lock = VAR_UNLOCKED, |
1125 | .vval = { .v_list = list }, |
1126 | })); |
1127 | if (encode_list_write((void *) ext_val_list, mobj.via.ext.ptr, |
1128 | mobj.via.ext.size) == -1) { |
1129 | return FAIL; |
1130 | } |
1131 | break; |
1132 | } |
1133 | } |
1134 | return OK; |
1135 | } |
1136 | |