changeset 7883:98a96e0ca73b v7.4.1238

commit https://github.com/vim/vim/commit/56ead341a75e1a0395eee94a3280c67e2278a57e Author: Bram Moolenaar <Bram@vim.org> 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.
author Christian Brabandt <cb@256bit.org>
date Tue, 02 Feb 2016 18:30:04 +0100
parents a3d3ab0f7b27
children 1cc576119beb
files src/Makefile src/channel.c src/eval.c src/json.c src/json_test.c src/memfile_test.c src/proto/json.pro src/structs.h src/version.c
diffstat 9 files changed, 534 insertions(+), 139 deletions(-) [+]
line wrap: on
line diff
--- 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 \
--- 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)
--- 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));
 }
 
--- 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
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 <assert.h>
+
+/* 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;
+}
--- 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.
  */
--- 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 : */
--- 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;
--- 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,