changeset 10559:4d8be28b5913 v8.0.0169

patch 8.0.0169: json_decode() may run out of stack space commit https://github.com/vim/vim/commit/8b2f19536ff979046f0d241850f4176a1ce4bca9 Author: Bram Moolenaar <Bram@vim.org> Date: Tue Jan 10 19:44:18 2017 +0100 patch 8.0.0169: json_decode() may run out of stack space Problem: For complicated string json_decode() may run out of stack space. Solution: Change the recursive solution into an iterative solution.
author Christian Brabandt <cb@256bit.org>
date Tue, 10 Jan 2017 19:45:03 +0100
parents eb3603e558be
children 1c09563f6fa4
files src/json.c src/version.c
diffstat 2 files changed, 401 insertions(+), 298 deletions(-) [+]
line wrap: on
line diff
--- a/src/json.c
+++ b/src/json.c
@@ -378,187 +378,6 @@ json_skip_white(js_read_T *reader)
 }
 
     static int
-json_decode_array(js_read_T *reader, typval_T *res, int options)
-{
-    char_u	*p;
-    typval_T	item;
-    listitem_T	*li;
-    int		ret;
-
-    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)
-    {
-	json_skip_white(reader);
-	p = reader->js_buf + reader->js_used;
-	if (*p == NUL)
-	    return MAYBE;
-	if (*p == ']')
-	{
-	    ++reader->js_used; /* consume the ']' */
-	    break;
-	}
-
-	ret = json_decode_item(reader, res == NULL ? NULL : &item, options);
-	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 != ']')
-	{
-	    if (*p == NUL)
-		return MAYBE;
-	    EMSG(_(e_invarg));
-	    return FAIL;
-	}
-    }
-    return OK;
-}
-
-    static int
-json_decode_object(js_read_T *reader, typval_T *res, int options)
-{
-    char_u	*p;
-    typval_T	tvkey;
-    typval_T	item;
-    dictitem_T	*di;
-    char_u	buf[NUMBUFLEN];
-    char_u	*key = NULL;
-    int		ret;
-
-    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)
-    {
-	json_skip_white(reader);
-	p = reader->js_buf + reader->js_used;
-	if (*p == NUL)
-	    return MAYBE;
-	if (*p == '}')
-	{
-	    ++reader->js_used; /* consume the '}' */
-	    break;
-	}
-
-	if ((options & JSON_JS) && reader->js_buf[reader->js_used] != '"')
-	{
-	    /* accept a key that is not in quotes */
-	    key = p = reader->js_buf + reader->js_used;
-	    while (*p != NUL && *p != ':' && *p > ' ')
-		++p;
-	    tvkey.v_type = VAR_STRING;
-	    tvkey.vval.v_string = vim_strnsave(key, (int)(p - key));
-	    reader->js_used += (int)(p - key);
-	    key = tvkey.vval.v_string;
-	}
-	else
-	{
-	    ret = json_decode_item(reader, res == NULL ? NULL : &tvkey,
-								     options);
-	    if (ret != OK)
-		return ret;
-	    if (res != NULL)
-	    {
-		key = get_tv_string_buf_chk(&tvkey, buf);
-		if (key == NULL || *key == NUL)
-		{
-		    clear_tv(&tvkey);
-		    EMSG(_(e_invarg));
-		    return FAIL;
-		}
-	    }
-	}
-
-	json_skip_white(reader);
-	p = reader->js_buf + reader->js_used;
-	if (*p != ':')
-	{
-	    if (res != NULL)
-		clear_tv(&tvkey);
-	    if (*p == NUL)
-		return MAYBE;
-	    EMSG(_(e_invarg));
-	    return FAIL;
-	}
-	++reader->js_used;
-	json_skip_white(reader);
-
-	ret = json_decode_item(reader, res == NULL ? NULL : &item, options);
-	if (ret != OK)
-	{
-	    if (res != NULL)
-		clear_tv(&tvkey);
-	    return ret;
-	}
-
-	if (res != NULL && dict_find(res->vval.v_dict, key, -1) != NULL)
-	{
-	    EMSG2(_("E937: Duplicate key in JSON: \"%s\""), key);
-	    clear_tv(&tvkey);
-	    clear_tv(&item);
-	    return FAIL;
-	}
-
-	if (res != NULL)
-	{
-	    di = dictitem_alloc(key);
-	    clear_tv(&tvkey);
-	    if (di == NULL)
-	    {
-		clear_tv(&item);
-		return FAIL;
-	    }
-	    di->di_tv = item;
-	    di->di_tv.v_lock = 0;
-	    if (dict_add(res->vval.v_dict, di) == FAIL)
-	    {
-		dictitem_free(di);
-		return FAIL;
-	    }
-	}
-
-	json_skip_white(reader);
-	p = reader->js_buf + reader->js_used;
-	if (*p == ',')
-	    ++reader->js_used;
-	else if (*p != '}')
-	{
-	    if (*p == NUL)
-		return MAYBE;
-	    EMSG(_(e_invarg));
-	    return FAIL;
-	}
-    }
-    return OK;
-}
-
-    static int
 json_decode_string(js_read_T *reader, typval_T *res)
 {
     garray_T    ga;
@@ -723,6 +542,19 @@ json_decode_string(js_read_T *reader, ty
     return MAYBE;
 }
 
+typedef enum {
+    JSON_ARRAY,		/* parsing items in an array */
+    JSON_OBJECT_KEY,	/* parsing key of an object */
+    JSON_OBJECT		/* parsing item in an object, after the key */
+} json_decode_T;
+
+typedef struct {
+    json_decode_T jd_type;
+    typval_T	  jd_tv;	/* the list or dict */
+    typval_T	  jd_key_tv;
+    char_u	  *jd_key;
+} json_dec_item_T;
+
 /*
  * Decode one item and put it in "res".  If "res" is NULL only advance.
  * Must already have skipped white space.
@@ -735,157 +567,426 @@ json_decode_item(js_read_T *reader, typv
 {
     char_u	*p;
     int		len;
+    int		retval;
+    garray_T	stack;
+    typval_T	item;
+    typval_T	*cur_item;
+    json_dec_item_T *top_item;
+    char_u	key_buf[NUMBUFLEN];
+
+    ga_init2(&stack, sizeof(json_dec_item_T), 100);
+    cur_item = res;
+    init_tv(&item);
 
     fill_numbuflen(reader);
     p = reader->js_buf + reader->js_used;
-    switch (*p)
+    for (;;)
     {
-	case '[': /* array */
-	    return json_decode_array(reader, res, options);
-
-	case '{': /* object */
-	    return json_decode_object(reader, res, options);
-
-	case '"': /* string */
-	    return json_decode_string(reader, res);
-
-	case ',': /* comma: empty item */
-	    if ((options & JSON_JS) == 0)
+	top_item = NULL;
+	if (stack.ga_len > 0)
+	{
+	    top_item = ((json_dec_item_T *)stack.ga_data) + stack.ga_len - 1;
+	    json_skip_white(reader);
+	    p = reader->js_buf + reader->js_used;
+	    if (*p == NUL)
 	    {
-		EMSG(_(e_invarg));
-		return FAIL;
+		retval = MAYBE;
+		if (top_item->jd_type == JSON_OBJECT)
+		    /* did get the key, clear it */
+		    clear_tv(&top_item->jd_key_tv);
+		goto theend;
+	    }
+	    if (top_item->jd_type == JSON_OBJECT_KEY
+					    || top_item->jd_type == JSON_ARRAY)
+	    {
+		/* Check for end of object or array. */
+		if (*p == (top_item->jd_type == JSON_ARRAY ? ']' : '}'))
+		{
+		    ++reader->js_used; /* consume the ']' or '}' */
+		    --stack.ga_len;
+		    if (stack.ga_len == 0)
+		    {
+			retval = OK;
+			goto theend;
+		    }
+		    if (cur_item != NULL)
+			cur_item = &top_item->jd_tv;
+		    goto item_end;
+		}
 	    }
-	    /* FALLTHROUGH */
-	case NUL: /* empty */
-	    if (res != NULL)
+	}
+
+	if (top_item != NULL && top_item->jd_type == JSON_OBJECT_KEY
+		&& (options & JSON_JS)
+		&& reader->js_buf[reader->js_used] != '"')
+	{
+	    char_u *key;
+
+	    /* accept an object key that is not in quotes */
+	    key = p = reader->js_buf + reader->js_used;
+	    while (*p != NUL && *p != ':' && *p > ' ')
+		++p;
+	    cur_item->v_type = VAR_STRING;
+	    cur_item->vval.v_string = vim_strnsave(key, (int)(p - key));
+	    reader->js_used += (int)(p - key);
+	    top_item->jd_key = cur_item->vval.v_string;
+	}
+	else
+	{
+	    switch (*p)
 	    {
-		res->v_type = VAR_SPECIAL;
-		res->vval.v_number = VVAL_NONE;
-	    }
-	    return OK;
+		case '[': /* start of array */
+		    if (ga_grow(&stack, 1) == FAIL)
+		    {
+			retval = FAIL;
+			break;
+		    }
+		    if (cur_item != NULL && rettv_list_alloc(cur_item) == FAIL)
+		    {
+			cur_item->v_type = VAR_SPECIAL;
+			cur_item->vval.v_number = VVAL_NONE;
+			retval = FAIL;
+			break;
+		    }
 
-	default:
-	    if (VIM_ISDIGIT(*p) || *p == '-')
-	    {
-#ifdef FEAT_FLOAT
-		char_u  *sp = p;
+		    ++reader->js_used; /* consume the '[' */
+		    top_item = ((json_dec_item_T *)stack.ga_data)
+								+ stack.ga_len;
+		    top_item->jd_type = JSON_ARRAY;
+		    ++stack.ga_len;
+		    if (cur_item != NULL)
+		    {
+			top_item->jd_tv = *cur_item;
+			cur_item = &item;
+		    }
+		    continue;
 
-		if (*sp == '-')
-		{
-		    ++sp;
-		    if (*sp == NUL)
-			return MAYBE;
-		    if (!VIM_ISDIGIT(*sp))
+		case '{': /* start of object */
+		    if (ga_grow(&stack, 1) == FAIL)
+		    {
+			retval = FAIL;
+			break;
+		    }
+		    if (cur_item != NULL && rettv_dict_alloc(cur_item) == FAIL)
+		    {
+			cur_item->v_type = VAR_SPECIAL;
+			cur_item->vval.v_number = VVAL_NONE;
+			retval = FAIL;
+			break;
+		    }
+
+		    ++reader->js_used; /* consume the '{' */
+		    top_item = ((json_dec_item_T *)stack.ga_data)
+								+ stack.ga_len;
+		    top_item->jd_type = JSON_OBJECT_KEY;
+		    ++stack.ga_len;
+		    if (cur_item != NULL)
+		    {
+			top_item->jd_tv = *cur_item;
+			cur_item = &top_item->jd_key_tv;
+		    }
+		    continue;
+
+		case '"': /* string */
+		    retval = json_decode_string(reader, cur_item);
+		    break;
+
+		case ',': /* comma: empty item */
+		    if ((options & JSON_JS) == 0)
 		    {
 			EMSG(_(e_invarg));
-			return FAIL;
+			retval = FAIL;
+			break;
+		    }
+		    /* FALLTHROUGH */
+		case NUL: /* empty */
+		    if (cur_item != NULL)
+		    {
+			cur_item->v_type = VAR_SPECIAL;
+			cur_item->vval.v_number = VVAL_NONE;
+		    }
+		    retval = OK;
+		    break;
+
+		default:
+		    if (VIM_ISDIGIT(*p) || *p == '-')
+		    {
+#ifdef FEAT_FLOAT
+			char_u  *sp = p;
+
+			if (*sp == '-')
+			{
+			    ++sp;
+			    if (*sp == NUL)
+			    {
+				retval = MAYBE;
+				break;
+			    }
+			    if (!VIM_ISDIGIT(*sp))
+			    {
+				EMSG(_(e_invarg));
+				retval = FAIL;
+				break;
+			    }
+			}
+			sp = skipdigits(sp);
+			if (*sp == '.' || *sp == 'e' || *sp == 'E')
+			{
+			    if (cur_item == NULL)
+			    {
+				float_T f;
+
+				len = string2float(p, &f);
+			    }
+			    else
+			    {
+				cur_item->v_type = VAR_FLOAT;
+				len = string2float(p, &cur_item->vval.v_float);
+			    }
+			}
+			else
+#endif
+			{
+			    varnumber_T nr;
+
+			    vim_str2nr(reader->js_buf + reader->js_used,
+				    NULL, &len, 0, /* what */
+				    &nr, NULL, 0);
+			    if (cur_item != NULL)
+			    {
+				cur_item->v_type = VAR_NUMBER;
+				cur_item->vval.v_number = nr;
+			    }
+			}
+			reader->js_used += len;
+			retval = OK;
+			break;
 		    }
-		}
-		sp = skipdigits(sp);
-		if (*sp == '.' || *sp == 'e' || *sp == 'E')
+		    if (STRNICMP((char *)p, "false", 5) == 0)
+		    {
+			reader->js_used += 5;
+			if (cur_item != NULL)
+			{
+			    cur_item->v_type = VAR_SPECIAL;
+			    cur_item->vval.v_number = VVAL_FALSE;
+			}
+			retval = OK;
+			break;
+		    }
+		    if (STRNICMP((char *)p, "true", 4) == 0)
+		    {
+			reader->js_used += 4;
+			if (cur_item != NULL)
+			{
+			    cur_item->v_type = VAR_SPECIAL;
+			    cur_item->vval.v_number = VVAL_TRUE;
+			}
+			retval = OK;
+			break;
+		    }
+		    if (STRNICMP((char *)p, "null", 4) == 0)
+		    {
+			reader->js_used += 4;
+			if (cur_item != NULL)
+			{
+			    cur_item->v_type = VAR_SPECIAL;
+			    cur_item->vval.v_number = VVAL_NULL;
+			}
+			retval = OK;
+			break;
+		    }
+#ifdef FEAT_FLOAT
+		    if (STRNICMP((char *)p, "NaN", 3) == 0)
+		    {
+			reader->js_used += 3;
+			if (cur_item != NULL)
+			{
+			    cur_item->v_type = VAR_FLOAT;
+			    cur_item->vval.v_float = NAN;
+			}
+			retval = OK;
+			break;
+		    }
+		    if (STRNICMP((char *)p, "Infinity", 8) == 0)
+		    {
+			reader->js_used += 8;
+			if (cur_item != NULL)
+			{
+			    cur_item->v_type = VAR_FLOAT;
+			    cur_item->vval.v_float = INFINITY;
+			}
+			retval = OK;
+			break;
+		    }
+#endif
+		    /* check for truncated name */
+		    len = (int)(reader->js_end - (reader->js_buf + reader->js_used));
+		    if (
+			    (len < 5 && STRNICMP((char *)p, "false", len) == 0)
+#ifdef FEAT_FLOAT
+			    || (len < 8 && STRNICMP((char *)p, "Infinity", len) == 0)
+			    || (len < 3 && STRNICMP((char *)p, "NaN", len) == 0)
+#endif
+			    || (len < 4 && (STRNICMP((char *)p, "true", len) == 0
+				       ||  STRNICMP((char *)p, "null", len) == 0)))
+
+			retval = MAYBE;
+		    else
+			retval = FAIL;
+		    break;
+	    }
+
+	    /* We are finished when retval is FAIL or MAYBE and when at the
+	     * toplevel. */
+	    if (retval == FAIL)
+		break;
+	    if (retval == MAYBE || stack.ga_len == 0)
+		goto theend;
+
+	    if (top_item != NULL && top_item->jd_type == JSON_OBJECT_KEY
+		    && cur_item != NULL)
+	    {
+		top_item->jd_key = get_tv_string_buf_chk(cur_item, key_buf);
+		if (top_item->jd_key == NULL || *top_item->jd_key == NUL)
 		{
-		    if (res == NULL)
+		    clear_tv(cur_item);
+		    EMSG(_(e_invarg));
+		    retval = FAIL;
+		    goto theend;
+		}
+	    }
+	}
+
+item_end:
+	top_item = ((json_dec_item_T *)stack.ga_data) + stack.ga_len - 1;
+	switch (top_item->jd_type)
+	{
+	    case JSON_ARRAY:
+		if (res != NULL)
+		{
+		    listitem_T	*li = listitem_alloc();
+
+		    if (li == NULL)
 		    {
-			float_T f;
+			clear_tv(cur_item);
+			retval = FAIL;
+			goto theend;
+		    }
+		    li->li_tv = *cur_item;
+		    list_append(top_item->jd_tv.vval.v_list, li);
+		}
+		if (cur_item != NULL)
+		    cur_item = &item;
 
-			len = string2float(p, &f);
-		    }
+		json_skip_white(reader);
+		p = reader->js_buf + reader->js_used;
+		if (*p == ',')
+		    ++reader->js_used;
+		else if (*p != ']')
+		{
+		    if (*p == NUL)
+			retval = MAYBE;
 		    else
 		    {
-			res->v_type = VAR_FLOAT;
-			len = string2float(p, &res->vval.v_float);
+			EMSG(_(e_invarg));
+			retval = FAIL;
 		    }
+		    goto theend;
 		}
-		else
-#endif
+		break;
+
+	    case JSON_OBJECT_KEY:
+		json_skip_white(reader);
+		p = reader->js_buf + reader->js_used;
+		if (*p != ':')
 		{
-		    varnumber_T nr;
+		    if (cur_item != NULL)
+			clear_tv(cur_item);
+		    if (*p == NUL)
+			retval = MAYBE;
+		    else
+		    {
+			EMSG(_(e_invarg));
+			retval = FAIL;
+		    }
+		    goto theend;
+		}
+		++reader->js_used;
+		json_skip_white(reader);
+		top_item->jd_type = JSON_OBJECT;
+		if (cur_item != NULL)
+		    cur_item = &item;
+		break;
 
-		    vim_str2nr(reader->js_buf + reader->js_used,
-			    NULL, &len, 0, /* what */
-			    &nr, NULL, 0);
-		    if (res != NULL)
+	    case JSON_OBJECT:
+		if (cur_item != NULL
+			&& dict_find(top_item->jd_tv.vval.v_dict,
+						 top_item->jd_key, -1) != NULL)
+		{
+		    EMSG2(_("E937: Duplicate key in JSON: \"%s\""),
+							     top_item->jd_key);
+		    clear_tv(&top_item->jd_key_tv);
+		    clear_tv(cur_item);
+		    retval = FAIL;
+		    goto theend;
+		}
+
+		if (cur_item != NULL)
+		{
+		    dictitem_T *di = dictitem_alloc(top_item->jd_key);
+
+		    clear_tv(&top_item->jd_key_tv);
+		    if (di == NULL)
 		    {
-			res->v_type = VAR_NUMBER;
-			res->vval.v_number = nr;
+			clear_tv(cur_item);
+			retval = FAIL;
+			goto theend;
+		    }
+		    di->di_tv = *cur_item;
+		    di->di_tv.v_lock = 0;
+		    if (dict_add(top_item->jd_tv.vval.v_dict, di) == FAIL)
+		    {
+			dictitem_free(di);
+			retval = FAIL;
+			goto theend;
 		    }
 		}
-		reader->js_used += len;
-		return OK;
-	    }
-	    if (STRNICMP((char *)p, "false", 5) == 0)
-	    {
-		reader->js_used += 5;
-		if (res != NULL)
+
+		json_skip_white(reader);
+		p = reader->js_buf + reader->js_used;
+		if (*p == ',')
+		    ++reader->js_used;
+		else if (*p != '}')
 		{
-		    res->v_type = VAR_SPECIAL;
-		    res->vval.v_number = VVAL_FALSE;
-		}
-		return OK;
-	    }
-	    if (STRNICMP((char *)p, "true", 4) == 0)
-	    {
-		reader->js_used += 4;
-		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;
-		if (res != NULL)
-		{
-		    res->v_type = VAR_SPECIAL;
-		    res->vval.v_number = VVAL_NULL;
+		    if (*p == NUL)
+			retval = MAYBE;
+		    else
+		    {
+			EMSG(_(e_invarg));
+			retval = FAIL;
+		    }
+		    goto theend;
 		}
-		return OK;
-	    }
-#ifdef FEAT_FLOAT
-	    if (STRNICMP((char *)p, "NaN", 3) == 0)
-	    {
-		reader->js_used += 3;
-		if (res != NULL)
-		{
-		    res->v_type = VAR_FLOAT;
-		    res->vval.v_float = NAN;
-		}
-		return OK;
-	    }
-	    if (STRNICMP((char *)p, "Infinity", 8) == 0)
-	    {
-		reader->js_used += 8;
-		if (res != NULL)
-		{
-		    res->v_type = VAR_FLOAT;
-		    res->vval.v_float = INFINITY;
-		}
-		return OK;
-	    }
-#endif
-	    /* check for truncated name */
-	    len = (int)(reader->js_end - (reader->js_buf + reader->js_used));
-	    if (
-		    (len < 5 && STRNICMP((char *)p, "false", len) == 0)
-#ifdef FEAT_FLOAT
-		    || (len < 8 && STRNICMP((char *)p, "Infinity", len) == 0)
-		    || (len < 3 && STRNICMP((char *)p, "NaN", len) == 0)
-#endif
-		    || (len < 4 && (STRNICMP((char *)p, "true", len) == 0
-			       ||  STRNICMP((char *)p, "null", len) == 0)))
-		return MAYBE;
-	    break;
+		top_item->jd_type = JSON_OBJECT_KEY;
+		if (cur_item != NULL)
+		    cur_item = &top_item->jd_key_tv;
+		break;
+	}
     }
 
+    /* Get here when parsing failed. */
     if (res != NULL)
     {
+	clear_tv(res);
 	res->v_type = VAR_SPECIAL;
 	res->vval.v_number = VVAL_NONE;
     }
     EMSG(_(e_invarg));
-    return FAIL;
+
+theend:
+    ga_clear(&stack);
+    clear_tv(&item);
+    return retval;
 }
 
 /*
--- a/src/version.c
+++ b/src/version.c
@@ -765,6 +765,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    169,
+/**/
     168,
 /**/
     167,