# HG changeset patch # User Christian Brabandt # Date 1454434204 -3600 # Node ID 98a96e0ca73be7fe46c6f0475dbd2e8c4463d4c0 # Parent a3d3ab0f7b2782792915117c3bf4e7e29bb7d1a3 commit https://github.com/vim/vim/commit/56ead341a75e1a0395eee94a3280c67e2278a57e Author: Bram Moolenaar Date: Tue Feb 2 18:20:08 2016 +0100 patch 7.4.1238 Problem: Can't handle two messages right after each other. Solution: Find the end of the JSON. Read more when incomplete. Add a C test for the JSON decoding. diff --git a/src/Makefile b/src/Makefile --- a/src/Makefile +++ b/src/Makefile @@ -1545,11 +1545,13 @@ EXTRA_SRC = hangulin.c if_lua.c if_mzsch $(GRESOURCE_SRC) # Unittest files +JSON_TEST_SRC = json_test.c +JSON_TEST_TARGET = json_test$(EXEEXT) MEMFILE_TEST_SRC = memfile_test.c MEMFILE_TEST_TARGET = memfile_test$(EXEEXT) -UNITTEST_SRC = $(MEMFILE_TEST_SRC) -UNITTEST_TARGETS = $(MEMFILE_TEST_TARGET) +UNITTEST_SRC = $(JSON_TEST_SRC) $(MEMFILE_TEST_SRC) +UNITTEST_TARGETS = $(JSON_TEST_TARGET) $(MEMFILE_TEST_TARGET) # All sources, also the ones that are not configured ALL_SRC = $(BASIC_SRC) $(ALL_GUI_SRC) $(UNITTEST_SRC) $(EXTRA_SRC) @@ -1588,7 +1590,6 @@ OBJ_COMMON = \ $(HANGULIN_OBJ) \ objects/if_cscope.o \ objects/if_xcmdsrv.o \ - objects/json.o \ objects/mark.o \ objects/memline.o \ objects/menu.o \ @@ -1914,6 +1915,7 @@ types.vim: $(TAGS_SRC) $(TAGS_INCL) ctags --c-kinds=gstu -o- $(TAGS_SRC) $(TAGS_INCL) |\ awk 'BEGIN{printf("syntax keyword Type\t")}\ {printf("%s ", $$1)}END{print ""}' > $@ + echo "syn keyword Constant OK FAIL TRUE FALSE MAYBE" >> $@ # Execute the test scripts. Run these after compiling Vim, before installing. # This doesn't depend on $(VIMTARGET), because that won't work when configure @@ -1948,6 +1950,12 @@ unittest unittests: $(UNITTEST_TARGETS) ./$$t || exit 1; echo $$t passed; \ done +run_json_test: $(JSON_TEST_TARGET) + ./$(JSON_TEST_TARGET) + +run_memfile_test: $(MEMFILE_TEST_TARGET) + ./$(MEMFILE_TEST_TARGET) + # Run individual OLD style test, assuming that Vim was already compiled. test1 \ test_autocmd_option \ @@ -2040,6 +2048,13 @@ testclean: # Unittests # It's build just like Vim to satisfy all dependencies. +$(JSON_TEST_TARGET): auto/config.mk objects $(JSON_TEST_OBJ) + $(CCC) version.c -o objects/version.o + @LINK="$(PURIFY) $(SHRPENV) $(CClink) $(ALL_LIB_DIRS) $(LDFLAGS) \ + -o $(JSON_TEST_TARGET) $(JSON_TEST_OBJ) $(ALL_LIBS)" \ + MAKE="$(MAKE)" LINK_AS_NEEDED=$(LINK_AS_NEEDED) \ + sh $(srcdir)/link.sh + $(MEMFILE_TEST_TARGET): auto/config.mk objects $(MEMFILE_TEST_OBJ) $(CCC) version.c -o objects/version.o @LINK="$(PURIFY) $(SHRPENV) $(CClink) $(ALL_LIB_DIRS) $(LDFLAGS) \ @@ -2811,6 +2826,9 @@ objects/integration.o: integration.c objects/json.o: json.c $(CCC) -o $@ json.c +objects/json_test.o: json_test.c + $(CCC) -o $@ json_test.c + objects/main.o: main.c $(CCC) -o $@ main.c @@ -3301,6 +3319,10 @@ objects/gui_at_fs.o: gui_at_fs.c vim.h a objects/pty.o: pty.c vim.h auto/config.h feature.h os_unix.h auto/osdef.h ascii.h \ keymap.h term.h macros.h option.h structs.h regexp.h gui.h gui_beval.h \ proto/gui_beval.pro alloc.h ex_cmds.h proto.h globals.h farsi.h arabic.h +objects/json_test.o: json_test.c main.c vim.h auto/config.h feature.h os_unix.h \ + auto/osdef.h ascii.h keymap.h term.h macros.h option.h structs.h \ + regexp.h gui.h gui_beval.h proto/gui_beval.pro alloc.h ex_cmds.h proto.h \ + globals.h farsi.h arabic.h farsi.c arabic.c json.c objects/memfile_test.o: memfile_test.c main.c vim.h auto/config.h feature.h \ os_unix.h auto/osdef.h ascii.h keymap.h term.h macros.h option.h \ structs.h regexp.h gui.h gui_beval.h proto/gui_beval.pro alloc.h \ diff --git a/src/channel.c b/src/channel.c --- a/src/channel.c +++ b/src/channel.c @@ -540,9 +540,8 @@ channel_read_json(int ch_idx) /* TODO: make reader work properly */ /* reader.js_buf = channel_peek(ch_idx); */ reader.js_buf = channel_get_all(ch_idx); - reader.js_eof = TRUE; - /* reader.js_eof = FALSE; */ reader.js_used = 0; + reader.js_fill = NULL; /* reader.js_fill = channel_fill; */ reader.js_cookie = &ch_idx; if (json_decode(&reader, &listtv) == OK) diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -14100,9 +14100,9 @@ f_jsondecode(typval_T *argvars, typval_T js_read_T reader; reader.js_buf = get_tv_string(&argvars[0]); - reader.js_eof = TRUE; + reader.js_fill = NULL; reader.js_used = 0; - if (json_decode(&reader, rettv) == FAIL) + if (json_decode_all(&reader, rettv) != OK) EMSG(_(e_invarg)); } diff --git a/src/json.c b/src/json.c --- a/src/json.c +++ b/src/json.c @@ -17,7 +17,7 @@ #if defined(FEAT_EVAL) || defined(PROTO) static int json_encode_item(garray_T *gap, typval_T *val, int copyID); -static void json_decode_item(js_read_T *reader, typval_T *res); +static int json_decode_item(js_read_T *reader, typval_T *res); /* * Encode "val" into a JSON format string. @@ -235,36 +235,59 @@ json_encode_item(garray_T *gap, typval_T } /* + * When "reader" has less than NUMBUFLEN bytes available, call the fill + * callback to get more. + */ + static void +fill_numbuflen(js_read_T *reader) +{ + if (reader->js_fill != NULL && (int)(reader->js_end - reader->js_buf) + - reader->js_used < NUMBUFLEN) + { + if (reader->js_fill(reader)) + reader->js_end = reader->js_buf + STRLEN(reader->js_buf); + } +} + +/* * Skip white space in "reader". + * Also tops up readahead when needed. */ static void json_skip_white(js_read_T *reader) { int c; - while ((c = reader->js_buf[reader->js_used]) == ' ' - || c == TAB || c == NL || c == CAR) + for (;;) + { + c = reader->js_buf[reader->js_used]; + if (reader->js_fill != NULL && c == NUL) + { + if (reader->js_fill(reader)) + reader->js_end = reader->js_buf + STRLEN(reader->js_buf); + continue; + } + if (c != ' ' && c != TAB && c != NL && c != CAR) + break; ++reader->js_used; + } + fill_numbuflen(reader); } -/* - * Make sure there are at least enough characters buffered to read a number. - */ - static void -json_fill_buffer(js_read_T *reader UNUSED) -{ - /* TODO */ -} - - static void + static int json_decode_array(js_read_T *reader, typval_T *res) { char_u *p; typval_T item; listitem_T *li; + int ret; - if (rettv_list_alloc(res) == FAIL) - goto failsilent; + if (res != NULL && rettv_list_alloc(res) == FAIL) + { + res->v_type = VAR_SPECIAL; + res->vval.v_number = VVAL_NONE; + return FAIL; + } ++reader->js_used; /* consume the '[' */ while (TRUE) @@ -272,38 +295,43 @@ json_decode_array(js_read_T *reader, typ json_skip_white(reader); p = reader->js_buf + reader->js_used; if (*p == NUL) - goto fail; + return MAYBE; if (*p == ']') { ++reader->js_used; /* consume the ']' */ - return; + break; } - if (!reader->js_eof && (int)(reader->js_end - p) < NUMBUFLEN) - json_fill_buffer(reader); - - json_decode_item(reader, &item); - li = listitem_alloc(); - if (li == NULL) - return; - li->li_tv = item; - list_append(res->vval.v_list, li); + ret = json_decode_item(reader, res == NULL ? NULL : &item); + if (ret != OK) + return ret; + if (res != NULL) + { + li = listitem_alloc(); + if (li == NULL) + { + clear_tv(&item); + return FAIL; + } + li->li_tv = item; + list_append(res->vval.v_list, li); + } json_skip_white(reader); p = reader->js_buf + reader->js_used; if (*p == ',') ++reader->js_used; else if (*p != ']') - goto fail; + { + if (*p == NUL) + return MAYBE; + return FAIL; + } } -fail: - EMSG(_(e_invarg)); -failsilent: - res->v_type = VAR_SPECIAL; - res->vval.v_number = VVAL_NONE; + return OK; } - static void + static int json_decode_object(js_read_T *reader, typval_T *res) { char_u *p; @@ -312,9 +340,14 @@ json_decode_object(js_read_T *reader, ty dictitem_T *di; char_u buf[NUMBUFLEN]; char_u *key; + int ret; - if (rettv_dict_alloc(res) == FAIL) - goto failsilent; + if (res != NULL && rettv_dict_alloc(res) == FAIL) + { + res->v_type = VAR_SPECIAL; + res->vval.v_number = VVAL_NONE; + return FAIL; + } ++reader->js_used; /* consume the '{' */ while (TRUE) @@ -322,243 +355,387 @@ json_decode_object(js_read_T *reader, ty json_skip_white(reader); p = reader->js_buf + reader->js_used; if (*p == NUL) - goto fail; + return MAYBE; if (*p == '}') { ++reader->js_used; /* consume the '}' */ - return; + break; } - if (!reader->js_eof && (int)(reader->js_end - p) < NUMBUFLEN) - json_fill_buffer(reader); - json_decode_item(reader, &tvkey); - key = get_tv_string_buf_chk(&tvkey, buf); - if (key == NULL || *key == NUL) + ret = json_decode_item(reader, res == NULL ? NULL : &tvkey); + if (ret != OK) + return ret; + if (res != NULL) { - /* "key" is NULL when get_tv_string_buf_chk() gave an errmsg */ - if (key != NULL) - EMSG(_(e_emptykey)); - clear_tv(&tvkey); - goto failsilent; + key = get_tv_string_buf_chk(&tvkey, buf); + if (key == NULL || *key == NUL) + { + clear_tv(&tvkey); + return FAIL; + } } json_skip_white(reader); p = reader->js_buf + reader->js_used; if (*p != ':') { - clear_tv(&tvkey); - goto fail; + if (res != NULL) + clear_tv(&tvkey); + if (*p == NUL) + return MAYBE; + return FAIL; } ++reader->js_used; json_skip_white(reader); - if (!reader->js_eof && (int)(reader->js_end - p) < NUMBUFLEN) - json_fill_buffer(reader); - json_decode_item(reader, &item); + ret = json_decode_item(reader, res == NULL ? NULL : &item); + if (ret != OK) + { + if (res != NULL) + clear_tv(&tvkey); + return ret; + } - di = dictitem_alloc(key); - clear_tv(&tvkey); - if (di == NULL) + if (res != NULL) { - clear_tv(&item); - goto fail; + di = dictitem_alloc(key); + clear_tv(&tvkey); + if (di == NULL) + { + clear_tv(&item); + return FAIL; + } + di->di_tv = item; + if (dict_add(res->vval.v_dict, di) == FAIL) + { + dictitem_free(di); + return FAIL; + } } - di->di_tv = item; - if (dict_add(res->vval.v_dict, di) == FAIL) - dictitem_free(di); json_skip_white(reader); p = reader->js_buf + reader->js_used; if (*p == ',') ++reader->js_used; else if (*p != '}') - goto fail; + { + if (*p == NUL) + return MAYBE; + return FAIL; + } } -fail: - EMSG(_(e_invarg)); -failsilent: - res->v_type = VAR_SPECIAL; - res->vval.v_number = VVAL_NONE; + return OK; } - static void + static int json_decode_string(js_read_T *reader, typval_T *res) { garray_T ga; int len; - char_u *p = reader->js_buf + reader->js_used + 1; + char_u *p; int c; long nr; char_u buf[NUMBUFLEN]; - ga_init2(&ga, 1, 200); + if (res != NULL) + ga_init2(&ga, 1, 200); - /* TODO: fill buffer when needed. */ - while (*p != NUL && *p != '"') + p = reader->js_buf + reader->js_used + 1; /* skip over " */ + while (*p != '"') { + if (*p == NUL || p[1] == NUL +#ifdef FEAT_MBYTE + || utf_ptr2len(p) < utf_byte2len(*p) +#endif + ) + { + if (reader->js_fill == NULL) + break; + len = (int)(reader->js_end - p); + reader->js_used = (int)(p - reader->js_buf); + if (!reader->js_fill(reader)) + break; /* didn't get more */ + p = reader->js_buf + reader->js_used; + reader->js_end = reader->js_buf + STRLEN(reader->js_buf); + continue; + } + if (*p == '\\') { c = -1; switch (p[1]) { + case '\\': c = '\\'; break; + case '"': c = '"'; break; case 'b': c = BS; break; case 't': c = TAB; break; case 'n': c = NL; break; case 'f': c = FF; break; case 'r': c = CAR; break; case 'u': + if (reader->js_fill != NULL + && (int)(reader->js_end - p) < NUMBUFLEN) + { + reader->js_used = (int)(p - reader->js_buf); + if (reader->js_fill(reader)) + { + p = reader->js_buf + reader->js_used; + reader->js_end = reader->js_buf + + STRLEN(reader->js_buf); + } + } vim_str2nr(p + 2, NULL, &len, STR2NR_HEX + STR2NR_FORCE, &nr, NULL, 4); p += len + 2; + if (res != NULL) + { #ifdef FEAT_MBYTE - buf[(*mb_char2bytes)((int)nr, buf)] = NUL; - ga_concat(&ga, buf); + buf[(*mb_char2bytes)((int)nr, buf)] = NUL; + ga_concat(&ga, buf); #else - ga_append(&ga, nr); + ga_append(&ga, nr); #endif + } break; - default: c = p[1]; break; + default: + /* not a special char, skip over \ */ + ++p; + continue; } if (c > 0) { p += 2; - ga_append(&ga, c); + if (res != NULL) + ga_append(&ga, c); } } else { len = MB_PTR2LEN(p); - if (ga_grow(&ga, len) == OK) + if (res != NULL) { + if (ga_grow(&ga, len) == FAIL) + { + ga_clear(&ga); + return FAIL; + } mch_memmove((char *)ga.ga_data + ga.ga_len, p, (size_t)len); ga.ga_len += len; } p += len; } - if (!reader->js_eof && (int)(reader->js_end - p) < NUMBUFLEN) - { - reader->js_used = (int)(p - reader->js_buf); - json_fill_buffer(reader); - p = reader->js_buf + reader->js_used; - } } + reader->js_used = (int)(p - reader->js_buf); if (*p == '"') { ++reader->js_used; - res->v_type = VAR_STRING; - if (ga.ga_data == NULL) - res->vval.v_string = NULL; - else - res->vval.v_string = vim_strsave(ga.ga_data); + if (res != NULL) + { + res->v_type = VAR_STRING; + if (ga.ga_data == NULL) + res->vval.v_string = NULL; + else + res->vval.v_string = vim_strsave(ga.ga_data); + } + return OK; } - else + if (res != NULL) { - EMSG(_(e_invarg)); res->v_type = VAR_SPECIAL; res->vval.v_number = VVAL_NONE; + ga_clear(&ga); } - ga_clear(&ga); + return MAYBE; } /* - * Decode one item and put it in "result". + * Decode one item and put it in "res". If "res" is NULL only advance. * Must already have skipped white space. + * + * Return FAIL for a decoding error. + * Return MAYBE for an incomplete message. */ - static void + static int json_decode_item(js_read_T *reader, typval_T *res) { - char_u *p = reader->js_buf + reader->js_used; + char_u *p; + int len; + fill_numbuflen(reader); + p = reader->js_buf + reader->js_used; switch (*p) { case '[': /* array */ - json_decode_array(reader, res); - return; + return json_decode_array(reader, res); case '{': /* object */ - json_decode_object(reader, res); - return; + return json_decode_object(reader, res); case '"': /* string */ - json_decode_string(reader, res); - return; + return json_decode_string(reader, res); case ',': /* comma: empty item */ case NUL: /* empty */ - res->v_type = VAR_SPECIAL; - res->vval.v_number = VVAL_NONE; - return; + if (res != NULL) + { + res->v_type = VAR_SPECIAL; + res->vval.v_number = VVAL_NONE; + } + return OK; default: if (VIM_ISDIGIT(*p) || *p == '-') { - int len; char_u *sp = p; + #ifdef FEAT_FLOAT if (*sp == '-') + { ++sp; + if (*sp == NUL) + return MAYBE; + if (!VIM_ISDIGIT(*sp)) + return FAIL; + } sp = skipdigits(sp); if (*sp == '.' || *sp == 'e' || *sp == 'E') { - res->v_type = VAR_FLOAT; - len = string2float(p, &res->vval.v_float); + if (res == NULL) + { + float_T f; + + len = string2float(p, &f); + } + else + { + res->v_type = VAR_FLOAT; + len = string2float(p, &res->vval.v_float); + } } else #endif { long nr; - res->v_type = VAR_NUMBER; vim_str2nr(reader->js_buf + reader->js_used, NULL, &len, 0, /* what */ &nr, NULL, 0); - res->vval.v_number = nr; + if (res != NULL) + { + res->v_type = VAR_NUMBER; + res->vval.v_number = nr; + } } reader->js_used += len; - return; + return OK; } if (STRNICMP((char *)p, "false", 5) == 0) { reader->js_used += 5; - res->v_type = VAR_SPECIAL; - res->vval.v_number = VVAL_FALSE; - return; + if (res != NULL) + { + res->v_type = VAR_SPECIAL; + res->vval.v_number = VVAL_FALSE; + } + return OK; } if (STRNICMP((char *)p, "true", 4) == 0) { reader->js_used += 4; - res->v_type = VAR_SPECIAL; - res->vval.v_number = VVAL_TRUE; - return; + if (res != NULL) + { + res->v_type = VAR_SPECIAL; + res->vval.v_number = VVAL_TRUE; + } + return OK; } if (STRNICMP((char *)p, "null", 4) == 0) { reader->js_used += 4; - res->v_type = VAR_SPECIAL; - res->vval.v_number = VVAL_NULL; - return; + if (res != NULL) + { + res->v_type = VAR_SPECIAL; + res->vval.v_number = VVAL_NULL; + } + return OK; } + /* check for truncated name */ + len = (int)(reader->js_end - (reader->js_buf + reader->js_used)); + if ((len < 5 && STRNICMP((char *)p, "false", len) == 0) + || (len < 4 && (STRNICMP((char *)p, "true", len) == 0 + || STRNICMP((char *)p, "null", len) == 0))) + return MAYBE; break; } - EMSG(_(e_invarg)); - res->v_type = VAR_SPECIAL; - res->vval.v_number = VVAL_NONE; + if (res != NUL) + { + res->v_type = VAR_SPECIAL; + res->vval.v_number = VVAL_NONE; + } + return FAIL; } /* * Decode the JSON from "reader" and store the result in "res". - * Return OK or FAIL; + * Return FAIL if not the whole message was consumed. */ int -json_decode(js_read_T *reader, typval_T *res) +json_decode_all(js_read_T *reader, typval_T *res) { + int ret; + + /* We get the end once, to avoid calling strlen() many times. */ + reader->js_end = reader->js_buf + STRLEN(reader->js_buf); json_skip_white(reader); - json_decode_item(reader, res); + ret = json_decode_item(reader, res); + if (ret != OK) + return FAIL; json_skip_white(reader); if (reader->js_buf[reader->js_used] != NUL) return FAIL; return OK; } + +/* + * Decode the JSON from "reader" and store the result in "res". + * Return FAIL if the message has a decoding error or the message is + * truncated. Consumes the message anyway. + */ + int +json_decode(js_read_T *reader, typval_T *res) +{ + int ret; + + /* We get the end once, to avoid calling strlen() many times. */ + reader->js_end = reader->js_buf + STRLEN(reader->js_buf); + json_skip_white(reader); + ret = json_decode_item(reader, res); + json_skip_white(reader); + + return ret == OK ? OK : FAIL; +} + +/* + * Decode the JSON from "reader" to find the end of the message. + * Return FAIL if the message has a decoding error. + * Return MAYBE if the message is truncated, need to read more. + * This only works reliable if the message contains an object, array or + * string. A number might be trucated without knowing. + * Does not advance the reader. + */ + int +json_find_end(js_read_T *reader) +{ + int used_save = reader->js_used; + int ret; + + /* We get the end once, to avoid calling strlen() many times. */ + reader->js_end = reader->js_buf + STRLEN(reader->js_buf); + json_skip_white(reader); + ret = json_decode_item(reader, NULL); + reader->js_used = used_save; + return ret; +} #endif diff --git a/src/json_test.c b/src/json_test.c new file mode 100644 --- /dev/null +++ b/src/json_test.c @@ -0,0 +1,193 @@ +/* vi:set ts=8 sts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * json_test.c: Unittests for json.c + */ + +#undef NDEBUG +#include + +/* Must include main.c because it contains much more than just main() */ +#define NO_VIM_MAIN +#include "main.c" + +/* This file has to be included because the tested functions are static */ +#include "json.c" + +/* + * Test json_find_end() with imcomplete items. + */ + static void +test_decode_find_end(void) +{ + js_read_T reader; + + reader.js_fill = NULL; + reader.js_used = 0; + + /* string and incomplete string */ + reader.js_buf = (char_u *)"\"hello\""; + assert(json_find_end(&reader) == OK); + reader.js_buf = (char_u *)" \"hello\" "; + assert(json_find_end(&reader) == OK); + reader.js_buf = (char_u *)"\"hello"; + assert(json_find_end(&reader) == MAYBE); + + /* number and dash (incomplete number) */ + reader.js_buf = (char_u *)"123"; + assert(json_find_end(&reader) == OK); + reader.js_buf = (char_u *)"-"; + assert(json_find_end(&reader) == MAYBE); + + /* false, true and null, also incomplete */ + reader.js_buf = (char_u *)"false"; + assert(json_find_end(&reader) == OK); + reader.js_buf = (char_u *)"f"; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"fa"; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"fal"; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"fals"; + assert(json_find_end(&reader) == MAYBE); + + reader.js_buf = (char_u *)"true"; + assert(json_find_end(&reader) == OK); + reader.js_buf = (char_u *)"t"; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"tr"; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"tru"; + assert(json_find_end(&reader) == MAYBE); + + reader.js_buf = (char_u *)"null"; + assert(json_find_end(&reader) == OK); + reader.js_buf = (char_u *)"n"; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"nu"; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"nul"; + assert(json_find_end(&reader) == MAYBE); + + /* object without white space */ + reader.js_buf = (char_u *)"{\"a\":123}"; + assert(json_find_end(&reader) == OK); + reader.js_buf = (char_u *)"{\"a\":123"; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"{\"a\":"; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"{\"a\""; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"{\"a"; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"{\""; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"{"; + assert(json_find_end(&reader) == MAYBE); + + /* object with white space */ + reader.js_buf = (char_u *)" { \"a\" : 123 } "; + assert(json_find_end(&reader) == OK); + reader.js_buf = (char_u *)" { \"a\" : 123 "; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)" { \"a\" : "; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)" { \"a\" "; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)" { \"a "; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)" { "; + assert(json_find_end(&reader) == MAYBE); + + /* array without white space */ + reader.js_buf = (char_u *)"[\"a\",123]"; + assert(json_find_end(&reader) == OK); + reader.js_buf = (char_u *)"[\"a\",123"; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"[\"a\","; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"[\"a\""; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"[\"a"; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"[\""; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)"["; + assert(json_find_end(&reader) == MAYBE); + + /* array with white space */ + reader.js_buf = (char_u *)" [ \"a\" , 123 ] "; + assert(json_find_end(&reader) == OK); + reader.js_buf = (char_u *)" [ \"a\" , 123 "; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)" [ \"a\" , "; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)" [ \"a\" "; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)" [ \"a "; + assert(json_find_end(&reader) == MAYBE); + reader.js_buf = (char_u *)" [ "; + assert(json_find_end(&reader) == MAYBE); +} + + static int +fill_from_cookie(js_read_T *reader) +{ + reader->js_buf = reader->js_cookie; + return TRUE; +} + +/* + * Test json_find_end with an incomplete array, calling the fill function. + */ + static void +test_fill_called_on_find_end(void) +{ + js_read_T reader; + + reader.js_fill = fill_from_cookie; + reader.js_used = 0; + reader.js_buf = (char_u *)" [ \"a\" , 123 "; + reader.js_cookie = " [ \"a\" , 123 ] "; + assert(json_find_end(&reader) == OK); + reader.js_buf = (char_u *)" [ \"a\" , "; + assert(json_find_end(&reader) == OK); + reader.js_buf = (char_u *)" [ \"a\" "; + assert(json_find_end(&reader) == OK); + reader.js_buf = (char_u *)" [ \"a"; + assert(json_find_end(&reader) == OK); + reader.js_buf = (char_u *)" [ "; + assert(json_find_end(&reader) == OK); +} + +/* + * Test json_find_end with an incomplete string, calling the fill function. + */ + static void +test_fill_called_on_string(void) +{ + js_read_T reader; + + reader.js_fill = fill_from_cookie; + reader.js_used = 0; + reader.js_buf = (char_u *)" \"foo"; + reader.js_end = reader.js_buf + STRLEN(reader.js_buf); + reader.js_cookie = " \"foobar\" "; + assert(json_decode_string(&reader, NULL) == OK); +} + + int +main(void) +{ + test_decode_find_end(); + test_fill_called_on_find_end(); + test_fill_called_on_string(); + return 0; +} diff --git a/src/memfile_test.c b/src/memfile_test.c --- a/src/memfile_test.c +++ b/src/memfile_test.c @@ -25,8 +25,6 @@ #define index_to_key(i) ((i) ^ 15167) #define TEST_COUNT 50000 -static void test_mf_hash(void); - /* * Test mf_hash_*() functions. */ diff --git a/src/proto/json.pro b/src/proto/json.pro --- a/src/proto/json.pro +++ b/src/proto/json.pro @@ -1,5 +1,7 @@ /* json.c */ char_u *json_encode(typval_T *val); char_u *json_encode_nr_expr(int nr, typval_T *val); +int json_decode_all(js_read_T *reader, typval_T *res); int json_decode(js_read_T *reader, typval_T *res); +int json_find_end(js_read_T *reader); /* vim: set ft=c : */ diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -2687,12 +2687,14 @@ typedef struct { /* * Structure used for reading in json_decode(). */ -typedef struct +struct js_reader { char_u *js_buf; /* text to be decoded */ - char_u *js_end; /* NUL in js_buf when js_eof is FALSE */ + char_u *js_end; /* NUL in js_buf */ int js_used; /* bytes used from js_buf */ - int js_eof; /* when TRUE js_buf is all there is */ - int (*js_fill)(void *); /* function to fill the buffer */ - void *js_cookie; /* passed to js_fill */ -} js_read_T; + int (*js_fill)(struct js_reader *); + /* function to fill the buffer or NULL; + * return TRUE when the buffer was filled */ + void *js_cookie; /* can be used by js_fill */ +}; +typedef struct js_reader js_read_T; diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -743,6 +743,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1238, +/**/ 1237, /**/ 1236,