]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 7.4.1238 v7.4.1238
authorBram Moolenaar <Bram@vim.org>
Tue, 2 Feb 2016 17:20:08 +0000 (18:20 +0100)
committerBram Moolenaar <Bram@vim.org>
Tue, 2 Feb 2016 17:20:08 +0000 (18:20 +0100)
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.

src/Makefile
src/channel.c
src/eval.c
src/json.c
src/json_test.c [new file with mode: 0644]
src/memfile_test.c
src/proto/json.pro
src/structs.h
src/version.c

index 8fd59fb089127950a4b4b9c72261dca5688fb17a..31664b671678a0c2bee36de5499875a16bc6bb01 100644 (file)
@@ -1545,11 +1545,13 @@ EXTRA_SRC = hangulin.c if_lua.c if_mzsch.c auto/if_perl.c if_perlsfio.c \
            $(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 auto/config.h feature.h os_unix.h \
 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 \
index 8cb9a352eec21152314d680b59f093b696ddd8b5..db13c8a9d7e31666a3c1c0116cc6cdbb20efc20d 100644 (file)
@@ -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)
index 01a4512e2e3e43c332f7d2e78a5709ba4e06071b..9a4e1bbe213a29c589f2043dc1890e8bb44a7f2d 100644 (file)
@@ -14100,9 +14100,9 @@ f_jsondecode(typval_T *argvars, typval_T *rettv)
     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));
 }
 
index 9c78e15e8558faf15cb00454ea409c281ccce014..72b065f9ac2180f9cb0a8b2fd9ad585780d2a046 100644 (file)
@@ -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 *val, int copyID)
 }
 
 /*
- * Skip white space in "reader".
+ * When "reader" has less than NUMBUFLEN bytes available, call the fill
+ * callback to get more.
  */
     static void
-json_skip_white(js_read_T *reader)
+fill_numbuflen(js_read_T *reader)
 {
-    int c;
-
-    while ((c = reader->js_buf[reader->js_used]) == ' '
-                                          || c == TAB || c == NL || c == CAR)
-       ++reader->js_used;
+    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);
+    }
 }
 
 /*
- * Make sure there are at least enough characters buffered to read a number.
+ * Skip white space in "reader".
+ * Also tops up readahead when needed.
  */
     static void
-json_fill_buffer(js_read_T *reader UNUSED)
+json_skip_white(js_read_T *reader)
 {
-    /* TODO */
+    int c;
+
+    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);
 }
 
-    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, typval_T *res)
        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, typval_T *res)
     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, typval_T *res)
        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 (file)
index 0000000..0416543
--- /dev/null
@@ -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;
+}
index 3fc13516dc7d916fccae737b78ab1b57da9f25b4..b742cd67054a68cb560aef3b445ebb4221064ec8 100644 (file)
@@ -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.
  */
index 0b39e842302dd3007309edf1750ddc0e8b811b46..5d2e7ba900a03d84e90ea42e8d1a69ab0b71c13c 100644 (file)
@@ -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 : */
index 62a4bd5e37e60c54fb75a4566208c849291d283f..26f403f2a8581166f34aa30ffb4b4257f8f89033 100644 (file)
@@ -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;
index 53949e63d99d93d4aa49e8c7a82424fc894d96aa..8fabfc91adf8a97e1fa462884e949f25d03bbeaa 100644 (file)
@@ -742,6 +742,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1238,
 /**/
     1237,
 /**/