[njs] Fixed JSON.stringify() with Number() and String() objects.

Dmitry Volyntsev xeioex at nginx.com
Wed Feb 26 14:49:01 UTC 2020


details:   https://hg.nginx.org/njs/rev/061afad25256
branches:  
changeset: 1338:061afad25256
user:      Alexander Borisov <alexander.borisov at nginx.com>
date:      Tue Feb 25 16:03:20 2020 +0300
description:
Fixed JSON.stringify() with Number() and String() objects.

According to the specification.

diffstat:

 src/njs_json.c           |  136 ++++++++++++++++++++++++++++++----------------
 src/test/njs_unit_test.c |   58 +++++++++++++++----
 2 files changed, 133 insertions(+), 61 deletions(-)

diffs (318 lines):

diff -r 112c7f54f402 -r 061afad25256 src/njs_json.c
--- a/src/njs_json.c	Wed Feb 26 16:22:12 2020 +0300
+++ b/src/njs_json.c	Tue Feb 25 16:03:20 2020 +0300
@@ -94,7 +94,8 @@ static njs_int_t njs_json_stringify_repl
 static njs_int_t njs_json_stringify_array(njs_vm_t *vm,
     njs_json_stringify_t *stringify);
 
-static void njs_json_append_value(njs_chb_t *chain, const njs_value_t *value);
+static njs_int_t njs_json_append_value(njs_vm_t *vm, njs_chb_t *chain,
+    njs_value_t *value);
 static void njs_json_append_string(njs_chb_t *chain, const njs_value_t *value,
     char quote);
 static void njs_json_append_number(njs_chb_t *chain, const njs_value_t *value);
@@ -188,7 +189,7 @@ njs_json_stringify(njs_vm_t *vm, njs_val
     njs_index_t unused)
 {
     size_t                length;
-    double                num;
+    int64_t               i64;
     njs_int_t             i;
     njs_int_t             ret;
     njs_value_t           *replacer, *space;
@@ -217,45 +218,67 @@ njs_json_stringify(njs_vm_t *vm, njs_val
         njs_set_undefined(&stringify->replacer);
     }
 
-    stringify->space.length = 0;
-
     space = njs_arg(args, nargs, 3);
 
-    if (njs_is_string(space) || njs_is_number(space)) {
-        if (njs_is_string(space)) {
-            length = njs_string_prop(&prop, space);
-
-            if (njs_is_byte_string(&prop)) {
-                njs_internal_error(vm, "space argument cannot be"
-                                   " a byte string");
-                return NJS_ERROR;
-            }
-
-            if (length > 10) {
-                p = njs_string_offset(prop.start, prop.start + prop.size, 10);
-
-            } else {
-                p = prop.start + prop.size;
-            }
-
-            stringify->space.start = prop.start;
-            stringify->space.length = p - prop.start;
+    switch (space->type) {
+    case NJS_OBJECT_STRING:
+        ret = njs_value_to_string(vm, space, space);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return ret;
+        }
+
+        /* Fall through. */
+
+    case NJS_STRING:
+        length = njs_string_prop(&prop, space);
+
+        if (njs_is_byte_string(&prop)) {
+            njs_internal_error(vm, "space argument cannot be"
+                               " a byte string");
+            return NJS_ERROR;
+        }
+
+        if (length > 10) {
+            p = njs_string_offset(prop.start, prop.start + prop.size, 10);
 
         } else {
-            num = njs_number(space);
-
-            if (!isnan(num) && !isinf(num) && num > 0) {
-                num = njs_min(num, 10);
-
-                stringify->space.length = (size_t) num;
-                stringify->space.start = stringify->space_buf;
-
-                for (i = 0; i < (int) num; i++) {
-                    stringify->space.start[i] = ' ';
-                }
+            p = prop.start + prop.size;
+        }
+
+        stringify->space.start = prop.start;
+        stringify->space.length = p - prop.start;
+
+        break;
+
+    case NJS_OBJECT_NUMBER:
+        ret = njs_value_to_numeric(vm, space, space);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return ret;
+        }
+
+        /* Fall through. */
+
+    case NJS_NUMBER:
+        i64 = njs_min(njs_number_to_integer(njs_number(space)), 10);
+
+        if (i64 > 0) {
+            stringify->space.length = i64;
+            stringify->space.start = stringify->space_buf;
+
+            for (i = 0; i < i64; i++) {
+                stringify->space.start[i] = ' ';
             }
+
+            break;
         }
-    }
+
+        /* Fall through. */
+
+    default:
+        stringify->space.length = 0;
+
+        break;
+     }
 
     return njs_json_stringify_iterator(vm, stringify, njs_arg(args, nargs, 1));
 
@@ -1238,7 +1261,10 @@ njs_json_stringify_iterator(njs_vm_t *vm
                 break;
             }
 
-            njs_json_append_value(&chain, value);
+            ret = njs_json_append_value(vm, &chain, value);
+            if (njs_slow_path(ret != NJS_OK)) {
+                return ret;
+            }
 
             break;
 
@@ -1288,7 +1314,10 @@ njs_json_stringify_iterator(njs_vm_t *vm
             }
 
             state->written = 1;
-            njs_json_append_value(&chain, value);
+            ret = njs_json_append_value(vm, &chain, value);
+            if (njs_slow_path(ret != NJS_OK)) {
+                return ret;
+            }
 
             break;
         }
@@ -1471,10 +1500,6 @@ njs_json_stringify_array(njs_vm_t *vm, n
         }
 
         switch (value->type) {
-        case NJS_OBJECT_NUMBER:
-            value = njs_object_value(value);
-            /* Fall through. */
-
         case NJS_NUMBER:
             ret = njs_number_to_string(vm, &num_value, value);
             if (njs_slow_path(ret != NJS_OK)) {
@@ -1484,9 +1509,14 @@ njs_json_stringify_array(njs_vm_t *vm, n
             value = &num_value;
             break;
 
+        case NJS_OBJECT_NUMBER:
         case NJS_OBJECT_STRING:
-            value = njs_object_value(value);
-            break;
+            ret = njs_value_to_string(vm, value, value);
+            if (njs_slow_path(ret != NJS_OK)) {
+                return NJS_ERROR;
+            }
+
+            /* Fall through. */
 
         case NJS_STRING:
             break;
@@ -1513,12 +1543,18 @@ njs_json_stringify_array(njs_vm_t *vm, n
 }
 
 
-static void
-njs_json_append_value(njs_chb_t *chain, const njs_value_t *value)
+static njs_int_t
+njs_json_append_value(njs_vm_t *vm, njs_chb_t *chain, njs_value_t *value)
 {
+    njs_int_t  ret;
+
     switch (value->type) {
     case NJS_OBJECT_STRING:
-        value = njs_object_value(value);
+         ret = njs_value_to_string(vm, value, value);
+         if (njs_slow_path(ret != NJS_OK)) {
+             return ret;
+         }
+
         /* Fall through. */
 
     case NJS_STRING:
@@ -1526,7 +1562,11 @@ njs_json_append_value(njs_chb_t *chain, 
         break;
 
     case NJS_OBJECT_NUMBER:
-        value = njs_object_value(value);
+         ret = njs_value_to_numeric(vm, value, value);
+         if (njs_slow_path(ret != NJS_OK)) {
+             return ret;
+         }
+
         /* Fall through. */
 
     case NJS_NUMBER:
@@ -1555,6 +1595,8 @@ njs_json_append_value(njs_chb_t *chain, 
     default:
         njs_chb_append_literal(chain, "null");
     }
+
+    return NJS_OK;
 }
 
 
diff -r 112c7f54f402 -r 061afad25256 src/test/njs_unit_test.c
--- a/src/test/njs_unit_test.c	Wed Feb 26 16:22:12 2020 +0300
+++ b/src/test/njs_unit_test.c	Tue Feb 25 16:03:20 2020 +0300
@@ -15454,6 +15454,10 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("JSON.stringify(new String('abc'))"),
       njs_str("\"abc\"") },
 
+    { njs_str("var s = new String('abc'); s.toString = () => 'xxx'; "
+              "JSON.stringify(s)"),
+      njs_str("\"xxx\"") },
+
     { njs_str("JSON.stringify(123)"),
       njs_str("123") },
 
@@ -15466,6 +15470,10 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("JSON.stringify(new Number(123))"),
       njs_str("123") },
 
+    { njs_str("var n = new Number(8.5); n.valueOf = () => 42;"
+              "JSON.stringify(n)"),
+      njs_str("42") },
+
     { njs_str("JSON.stringify(true)"),
       njs_str("true") },
 
@@ -15662,30 +15670,44 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("JSON.stringify([{a:1,b:{c:2}},1], undefined, '#')"),
       njs_str("[\n#{\n##\"a\": 1,\n##\"b\": {\n###\"c\": 2\n##}\n#},\n#1\n]") },
 
-    { njs_str("JSON.stringify([1], undefined, 'AAAAABBBBBC')"),
+    { njs_str("JSON.stringify([1], null, 'AAAAABBBBBC')"),
+      njs_str("[\nAAAAABBBBB1\n]") },
+
+    { njs_str("var s = new String('A'); s.toString = () => 'AAAAABBBBBC';"
+              "JSON.stringify([1], null, s)"),
       njs_str("[\nAAAAABBBBB1\n]") },
 
-    { njs_str("JSON.stringify([1], undefined, 11)"),
+    { njs_str("JSON.stringify([1], null, '!βββββ').length"),
+      njs_str("11") },
+
+    { njs_str("JSON.stringify([1], null, 'ABC') === JSON.stringify([1], null, new String('ABC'))"),
+      njs_str("true") },
+
+    { njs_str("JSON.stringify([1], null, '!!βββββββββββββββββ').length"),
+      njs_str("15") },
+
+    { njs_str("JSON.stringify([1], null, String.bytesFrom([0x9d])).length"),
+      njs_str("InternalError: space argument cannot be a byte string") },
+
+    { njs_str("JSON.stringify([1], null, 11)"),
       njs_str("[\n          1\n]") },
 
+    { njs_str("JSON.stringify([1], null, 5) === JSON.stringify([1], null, 5.9)"),
+      njs_str("true") },
+
+    { njs_str("JSON.stringify([1], null, 5) === JSON.stringify([1], null, new Number(5))"),
+      njs_str("true") },
+
+    { njs_str("var s = new Number(23); s.valueOf = () => 5;"
+              "JSON.stringify([1], null, s)"),
+      njs_str("[\n     1\n]") },
+
     { njs_str("JSON.stringify([{a:1,b:{c:2}},1], undefined, -1)"),
       njs_str("[{\"a\":1,\"b\":{\"c\":2}},1]") },
 
     { njs_str("JSON.stringify([{a:1,b:{c:2}},1], undefined, new Date())"),
       njs_str("[{\"a\":1,\"b\":{\"c\":2}},1]") },
 
-    { njs_str("JSON.stringify([], null, '!βββββ').length"),
-      njs_str("10") },
-
-    { njs_str("JSON.stringify([], null, '!!βββββββββββββββββ').length"),
-      njs_str("14") },
-
-    { njs_str("JSON.stringify([], null, '!βββββββββββββββββ').length"),
-      njs_str("14") },
-
-    { njs_str("JSON.stringify([], null, String.bytesFrom([0x9d])).length"),
-      njs_str("InternalError: space argument cannot be a byte string") },
-
     { njs_str("var o = Object.defineProperty({}, 'a', { get() { return ()=> 1}, enumerable: true });"
               "JSON.stringify(o)"),
       njs_str("{}") },
@@ -15806,6 +15828,14 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("JSON.stringify({'1':1,'2':2,'3':3}, [1, new Number(2)])"),
       njs_str("{\"1\":1,\"2\":2}") },
 
+    { njs_str("var s = new String('str'); s.toString = () => 'xxx';"
+              "JSON.stringify({str:1,xxx:2}, [s])"),
+      njs_str("{\"xxx\":2}") },
+
+    { njs_str("var n = new String(123); n.toString = () => '42';"
+              "JSON.stringify({123:1,42:2}, [n])"),
+      njs_str("{\"42\":2}") },
+
     { njs_str("var objs = []; var o = JSON.stringify({a:1},"
                  "   function(k, v) {objs.push(this); return v});"
                  "JSON.stringify(objs)"),


More information about the nginx-devel mailing list