[njs] Introduced Symbol.toStringTag support for builtin objects.

Dmitry Volyntsev xeioex at nginx.com
Fri Nov 22 16:34:15 UTC 2019


details:   https://hg.nginx.org/njs/rev/133c31ef36e5
branches:  
changeset: 1251:133c31ef36e5
user:      Dmitry Volyntsev <xeioex at nginx.com>
date:      Fri Nov 22 19:03:23 2019 +0300
description:
Introduced Symbol.toStringTag support for builtin objects.

This closes #255 issue on Github.

diffstat:

 src/njs_builtin.c        |  21 ++++++++++++++
 src/njs_crypto.c         |  34 ++--------------------
 src/njs_json.c           |  33 ++++++++++++++++-----
 src/njs_lvlhsh.h         |   4 ++
 src/njs_math.c           |   7 ++++
 src/njs_object.c         |  61 ++++++++++++++++++++++++++++++++++------
 src/njs_object.h         |  21 ++++++++++++++
 src/njs_symbol.c         |   7 ++++
 src/test/njs_unit_test.c |  71 +++++++++++++++++++++++++++++++++++------------
 9 files changed, 193 insertions(+), 66 deletions(-)

diffs (561 lines):

diff -r f98304d4019b -r 133c31ef36e5 src/njs_builtin.c
--- a/src/njs_builtin.c	Fri Nov 22 11:02:55 2019 +0300
+++ b/src/njs_builtin.c	Fri Nov 22 19:03:23 2019 +0300
@@ -956,6 +956,13 @@ njs_top_level_constructor(njs_vm_t *vm, 
 
 static const njs_object_prop_t  njs_global_this_object_properties[] =
 {
+    {
+        .type = NJS_PROPERTY,
+        .name = njs_wellknown_symbol(NJS_SYMBOL_TO_STRING_TAG),
+        .value = njs_string("global"),
+        .configurable = 1,
+    },
+
     /* Global constants. */
 
     {
@@ -1313,6 +1320,13 @@ static const njs_object_prop_t  njs_njs_
 {
     {
         .type = NJS_PROPERTY,
+        .name = njs_wellknown_symbol(NJS_SYMBOL_TO_STRING_TAG),
+        .value = njs_string("njs"),
+        .configurable = 1,
+    },
+
+    {
+        .type = NJS_PROPERTY,
         .name = njs_string("version"),
         .value = njs_string(NJS_VERSION),
         .configurable = 1,
@@ -1510,6 +1524,13 @@ njs_process_object_ppid(njs_vm_t *vm, nj
 static const njs_object_prop_t  njs_process_object_properties[] =
 {
     {
+        .type = NJS_PROPERTY,
+        .name = njs_wellknown_symbol(NJS_SYMBOL_TO_STRING_TAG),
+        .value = njs_string("process"),
+        .configurable = 1,
+    },
+
+    {
         .type = NJS_PROPERTY_HANDLER,
         .name = njs_string("argv"),
         .value = njs_prop_handler(njs_process_object_argv),
diff -r f98304d4019b -r 133c31ef36e5 src/njs_crypto.c
--- a/src/njs_crypto.c	Fri Nov 22 11:02:55 2019 +0300
+++ b/src/njs_crypto.c	Fri Nov 22 19:03:23 2019 +0300
@@ -299,18 +299,6 @@ njs_hash_prototype_digest(njs_vm_t *vm, 
 }
 
 
-static njs_int_t
-njs_hash_prototype_to_string(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
-    njs_index_t unused)
-{
-    static const njs_value_t  string = njs_string("[object Hash]");
-
-    vm->retval = string;
-
-    return NJS_OK;
-}
-
-
 static const njs_object_prop_t  njs_hash_prototype_properties[] =
 {
     {
@@ -322,9 +310,8 @@ static const njs_object_prop_t  njs_hash
 
     {
         .type = NJS_PROPERTY,
-        .name = njs_string("toString"),
-        .value = njs_native_function(njs_hash_prototype_to_string, 0),
-        .writable = 1,
+        .name = njs_wellknown_symbol(NJS_SYMBOL_TO_STRING_TAG),
+        .value = njs_string("Hash"),
         .configurable = 1,
     },
 
@@ -598,18 +585,6 @@ njs_hmac_prototype_digest(njs_vm_t *vm, 
 }
 
 
-static njs_int_t
-njs_hmac_prototype_to_string(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
-    njs_index_t unused)
-{
-    static const njs_value_t  string = njs_string("[object Hmac]");
-
-    vm->retval = string;
-
-    return NJS_OK;
-}
-
-
 static const njs_object_prop_t  njs_hmac_prototype_properties[] =
 {
     {
@@ -621,9 +596,8 @@ static const njs_object_prop_t  njs_hmac
 
     {
         .type = NJS_PROPERTY,
-        .name = njs_string("toString"),
-        .value = njs_native_function(njs_hmac_prototype_to_string, 0),
-        .writable = 1,
+        .name = njs_wellknown_symbol(NJS_SYMBOL_TO_STRING_TAG),
+        .value = njs_string("Hmac"),
         .configurable = 1,
     },
 
diff -r f98304d4019b -r 133c31ef36e5 src/njs_json.c
--- a/src/njs_json.c	Fri Nov 22 11:02:55 2019 +0300
+++ b/src/njs_json.c	Fri Nov 22 19:03:23 2019 +0300
@@ -205,7 +205,7 @@ njs_vm_json_parse(njs_vm_t *vm, njs_valu
 {
     njs_function_t  *parse;
 
-    parse = njs_function(&njs_json_object_properties[0].value);
+    parse = njs_function(&njs_json_object_properties[1].value);
 
     return njs_vm_call(vm, parse, args, nargs);
 }
@@ -285,7 +285,7 @@ njs_vm_json_stringify(njs_vm_t *vm, njs_
 {
     njs_function_t  *stringify;
 
-    stringify = njs_function(&njs_json_object_properties[1].value);
+    stringify = njs_function(&njs_json_object_properties[2].value);
 
     return njs_vm_call(vm, stringify, args, nargs);
 }
@@ -1133,7 +1133,7 @@ njs_json_pop_stringify_state(njs_json_st
 
 
 #define njs_json_stringify_append(str, len)                                   \
-    ret = njs_json_buf_append(stringify, str, len);                           \
+    ret = njs_json_buf_append(stringify, (char *) str, len);                  \
     if (ret != NJS_OK) {                                                      \
         goto memory_error;                                                    \
     }
@@ -1143,7 +1143,7 @@ njs_json_pop_stringify_state(njs_json_st
     if (stringify->space.length != 0) {                                       \
         njs_json_stringify_append("\n", 1);                                   \
         for (i = 0; i < (njs_int_t) (times) - 1; i++) {                       \
-            njs_json_stringify_append((char *) stringify->space.start,        \
+            njs_json_stringify_append(stringify->space.start,                 \
                                       stringify->space.length);               \
         }                                                                     \
     }
@@ -1857,7 +1857,13 @@ njs_json_buf_pullup(njs_json_stringify_t
 
 static const njs_object_prop_t  njs_json_object_properties[] =
 {
-    /* JSON.parse(). */
+    {
+        .type = NJS_PROPERTY,
+        .name = njs_wellknown_symbol(NJS_SYMBOL_TO_STRING_TAG),
+        .value = njs_string("JSON"),
+        .configurable = 1,
+    },
+
     {
         .type = NJS_PROPERTY,
         .name = njs_string("parse"),
@@ -1866,7 +1872,6 @@ static const njs_object_prop_t  njs_json
         .configurable = 1,
     },
 
-    /* JSON.stringify(). */
     {
         .type = NJS_PROPERTY,
         .name = njs_string("stringify"),
@@ -2161,9 +2166,10 @@ njs_vm_value_dump(njs_vm_t *vm, njs_str_
     njs_int_t             i;
     njs_int_t             ret;
     njs_str_t             str;
-    njs_value_t           *key, *val, ext_val;
+    njs_value_t           *key, *val, tag, ext_val;
     njs_object_t          *object;
     njs_json_state_t      *state;
+    njs_string_prop_t     string;
     njs_object_prop_t     *prop;
     njs_lvlhsh_query_t    lhq;
     njs_json_stringify_t  *stringify, dump_stringify;
@@ -2210,6 +2216,17 @@ njs_vm_value_dump(njs_vm_t *vm, njs_str_
         switch (state->type) {
         case NJS_JSON_OBJECT:
             if (state->index == 0) {
+                ret = njs_object_string_tag(vm, &state->value, &tag);
+                if (njs_slow_path(ret == NJS_ERROR)) {
+                    return ret;
+                }
+
+                if (ret == NJS_OK) {
+                    (void) njs_string_prop(&string, &tag);
+                    njs_json_stringify_append(string.start, string.size)
+                    njs_json_stringify_append(" ", 1);
+                }
+
                 njs_json_stringify_append("{", 1);
                 njs_json_stringify_indent(stringify->depth + 1);
             }
@@ -2283,7 +2300,7 @@ njs_vm_value_dump(njs_vm_t *vm, njs_str_
 
             state->written = 1;
             njs_key_string_get(vm, key, &lhq.key);
-            njs_json_stringify_append((char *) lhq.key.start, lhq.key.length);
+            njs_json_stringify_append(lhq.key.start, lhq.key.length);
             njs_json_stringify_append(":", 1);
             if (stringify->space.length != 0) {
                 njs_json_stringify_append(" ", 1);
diff -r f98304d4019b -r 133c31ef36e5 src/njs_lvlhsh.h
--- a/src/njs_lvlhsh.h	Fri Nov 22 11:02:55 2019 +0300
+++ b/src/njs_lvlhsh.h	Fri Nov 22 19:03:23 2019 +0300
@@ -105,6 +105,10 @@ struct njs_lvlhsh_query_s {
 #define njs_lvlhsh_init(lh)                                                   \
     (lh)->slot = NULL
 
+
+#define njs_lvlhsh_eq(lhl, lhr)                                               \
+    ((lhl)->slot == (lhr)->slot)
+
 /*
  * njs_lvlhsh_find() finds a hash element.  If the element has been
  * found then it is stored in the lhq->value and njs_lvlhsh_find()
diff -r f98304d4019b -r 133c31ef36e5 src/njs_math.c
--- a/src/njs_math.c	Fri Nov 22 11:02:55 2019 +0300
+++ b/src/njs_math.c	Fri Nov 22 19:03:23 2019 +0300
@@ -986,6 +986,13 @@ static const njs_object_prop_t  njs_math
 {
     {
         .type = NJS_PROPERTY,
+        .name = njs_wellknown_symbol(NJS_SYMBOL_TO_STRING_TAG),
+        .value = njs_string("Math"),
+        .configurable = 1,
+    },
+
+    {
+        .type = NJS_PROPERTY,
         .name = njs_string("E"),
         .value = njs_value(NJS_NUMBER, 1, M_E),
     },
diff -r f98304d4019b -r 133c31ef36e5 src/njs_object.c
--- a/src/njs_object.c	Fri Nov 22 11:02:55 2019 +0300
+++ b/src/njs_object.c	Fri Nov 22 19:03:23 2019 +0300
@@ -2274,12 +2274,18 @@ static const njs_value_t  njs_object_reg
 static const njs_value_t  njs_object_date_string = njs_string("[object Date]");
 static const njs_value_t  njs_object_error_string =
                                      njs_string("[object Error]");
+static const njs_value_t  njs_object_arguments_string =
+                                     njs_long_string("[object Arguments]");
 
 
 njs_int_t
 njs_object_prototype_to_string(njs_vm_t *vm, njs_value_t *args,
     njs_uint_t nargs, njs_index_t unused)
 {
+    u_char             *p;
+    njs_int_t          ret;
+    njs_value_t        tag, *value;
+    njs_string_prop_t  string;
     const njs_value_t  *name;
 
     static const njs_value_t  *class_name[NJS_VALUE_TYPE_MAX] = {
@@ -2315,21 +2321,56 @@ njs_object_prototype_to_string(njs_vm_t 
         &njs_object_object_string,
     };
 
-    name = class_name[args[0].type];
-
-    if (njs_is_error(&args[0])) {
-        name = &njs_object_error_string;
-    }
-
-    if (njs_fast_path(name != NULL)) {
+    value = njs_argument(args, 0);
+    name = class_name[value->type];
+
+    if (njs_is_null_or_undefined(value)) {
         vm->retval = *name;
 
         return NJS_OK;
     }
 
-    njs_internal_error(vm, "Unknown value type");
-
-    return NJS_ERROR;
+    if (njs_is_error(value)) {
+        name = &njs_object_error_string;
+    }
+
+    if (njs_is_object(value)
+        && njs_lvlhsh_eq(&njs_object(value)->shared_hash,
+                         &vm->shared->arguments_object_instance_hash))
+    {
+        name = &njs_object_arguments_string;
+    }
+
+    ret = njs_object_string_tag(vm, value, &tag);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return ret;
+    }
+
+    if (ret == NJS_DECLINED) {
+        if (njs_slow_path(name == NULL)) {
+            njs_internal_error(vm, "Unknown value type");
+
+            return NJS_ERROR;
+        }
+
+        vm->retval = *name;
+
+        return NJS_OK;
+    }
+
+    (void) njs_string_prop(&string, &tag);
+
+    p = njs_string_alloc(vm, &vm->retval, string.size + njs_length("[object ]"),
+                         string.length + njs_length("[object ]"));
+    if (njs_slow_path(p == NULL)) {
+        return NJS_ERROR;
+    }
+
+    p = njs_cpymem(p, "[object ", 8);
+    p = njs_cpymem(p, string.start, string.size);
+    *p = ']';
+
+    return NJS_OK;
 }
 
 
diff -r f98304d4019b -r 133c31ef36e5 src/njs_object.h
--- a/src/njs_object.h	Fri Nov 22 11:02:55 2019 +0300
+++ b/src/njs_object.h	Fri Nov 22 19:03:23 2019 +0300
@@ -238,6 +238,27 @@ njs_object_length_set(njs_vm_t *vm, njs_
 }
 
 
+njs_inline njs_int_t
+njs_object_string_tag(njs_vm_t *vm, njs_value_t *value, njs_value_t *tag)
+{
+    njs_int_t  ret;
+
+    static const njs_value_t  to_string_tag =
+                                njs_wellknown_symbol(NJS_SYMBOL_TO_STRING_TAG);
+
+    ret = njs_value_property(vm, value, njs_value_arg(&to_string_tag), tag);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
+
+    if (!njs_is_string(tag)) {
+        return NJS_DECLINED;
+    }
+
+    return NJS_OK;
+}
+
+
 extern const njs_object_type_init_t  njs_obj_type_init;
 
 
diff -r f98304d4019b -r 133c31ef36e5 src/njs_symbol.c
--- a/src/njs_symbol.c	Fri Nov 22 11:02:55 2019 +0300
+++ b/src/njs_symbol.c	Fri Nov 22 19:03:23 2019 +0300
@@ -384,6 +384,13 @@ njs_symbol_prototype_description(njs_vm_
 static const njs_object_prop_t  njs_symbol_prototype_properties[] =
 {
     {
+        .type = NJS_PROPERTY,
+        .name = njs_wellknown_symbol(NJS_SYMBOL_TO_STRING_TAG),
+        .value = njs_string("Symbol"),
+        .configurable = 1,
+    },
+
+    {
         .type = NJS_PROPERTY_HANDLER,
         .name = njs_string("__proto__"),
         .value = njs_prop_handler(njs_primitive_prototype_get_proto),
diff -r f98304d4019b -r 133c31ef36e5 src/test/njs_unit_test.c
--- a/src/test/njs_unit_test.c	Fri Nov 22 11:02:55 2019 +0300
+++ b/src/test/njs_unit_test.c	Fri Nov 22 19:03:23 2019 +0300
@@ -8152,6 +8152,9 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("(function () {arguments = [];})"),
       njs_str("SyntaxError: Identifier \"arguments\" is forbidden as left-hand in assignment in 1") },
 
+    { njs_str("(function(){return arguments;})()"),
+      njs_str("[object Arguments]") },
+
     { njs_str("(function(){return arguments[0];})(1,2,3)"),
       njs_str("1") },
 
@@ -8368,16 +8371,16 @@ static njs_unit_test_t  njs_test[] =
     /* arrow functions + global this. */
 
     { njs_str("(() => this)()"),
-      njs_str("[object Object]") },
+      njs_str("[object global]") },
 
     { njs_str("(() => this).call('abc')"),
-      njs_str("[object Object]") },
+      njs_str("[object global]") },
 
     { njs_str("(() => this).apply('abc')"),
-      njs_str("[object Object]") },
+      njs_str("[object global]") },
 
     { njs_str("(() => this).bind('abc')()"),
-      njs_str("[object Object]") },
+      njs_str("[object global]") },
 
     { njs_str("(function() { return (() => this); })()()"),
       njs_str("undefined") },
@@ -9338,7 +9341,7 @@ static njs_unit_test_t  njs_test[] =
     /* global this. */
 
     { njs_str("this"),
-      njs_str("[object Object]") },
+      njs_str("[object global]") },
 
     { njs_str("Object.getOwnPropertyDescriptor(this, 'NaN').value"),
       njs_str("NaN") },
@@ -9415,7 +9418,7 @@ static njs_unit_test_t  njs_test[] =
       njs_str("TypeError: right argument is not callable") },
 
     { njs_str("njs"),
-      njs_str("[object Object]") },
+      njs_str("[object njs]") },
 
     { njs_str("var o = Object(); o"),
       njs_str("[object Object]") },
@@ -10041,7 +10044,7 @@ static njs_unit_test_t  njs_test[] =
       njs_str("true") },
 
     { njs_str("Object.prototype.toString.call(Symbol.prototype)"),
-      njs_str("[object Object]") },
+      njs_str("[object Symbol]") },
 
     { njs_str("Symbol.prototype.toString()"),
       njs_str("TypeError: unexpected value type:object") },
@@ -10312,6 +10315,20 @@ static njs_unit_test_t  njs_test[] =
               "while(n--) o[Symbol()] = 'test'; o[''];"),
       njs_str("undefined") },
 
+    { njs_str("["
+              " Object.prototype,"
+              " Symbol.prototype,"
+              " Math,"
+              " JSON,"
+              " process,"
+              " njs,"
+              " this,"
+               "]"
+              ".map(v=>Object.getOwnPropertyDescriptor(v, Symbol.toStringTag))"
+              ".map(d=>{if (d && !d.writable && !d.enumerable && d.configurable) return d.value})"
+              ".map(v=>njs.dump(v))"),
+      njs_str("undefined,Symbol,Math,JSON,process,njs,global") },
+
     /* String */
 
     { njs_str("String()"),
@@ -10649,6 +10666,10 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("Object.prototype.toString.call(true)"),
       njs_str("[object Boolean]") },
 
+    { njs_str("Boolean.prototype[Symbol.toStringTag] = 'XXX';"
+              "Object.prototype.toString.call(true)"),
+      njs_str("[object XXX]") },
+
     { njs_str("Object.prototype.toString.call(1)"),
       njs_str("[object Number]") },
 
@@ -10661,6 +10682,10 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("Object.prototype.toString.call([])"),
       njs_str("[object Array]") },
 
+    { njs_str("var a = []; a[Symbol.toStringTag] = 'XXX';"
+              "Object.prototype.toString.call(a)"),
+      njs_str("[object XXX]") },
+
     { njs_str("Object.prototype.toString.call(new Object(true))"),
       njs_str("[object Boolean]") },
 
@@ -10682,9 +10707,19 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("Object.prototype.toString.call(function(){})"),
       njs_str("[object Function]") },
 
+    { njs_str("var f = ()=>1; f[Symbol.toStringTag] = 'α'.repeat(32);"
+              "var toStr = Object.prototype.toString.call(f); [toStr, toStr.length]"),
+      njs_str("[object αααααααααααααααααααααααααααααααα],41") },
+
     { njs_str("Object.prototype.toString.call(/./)"),
       njs_str("[object RegExp]") },
 
+    { njs_str("Object.prototype.toString.call(Math)"),
+      njs_str("[object Math]") },
+
+    { njs_str("Object.prototype.toString.call(JSON)"),
+      njs_str("[object JSON]") },
+
     { njs_str("var p = { a:5 }; var o = Object.create(p); o.a"),
       njs_str("5") },
 
@@ -13571,10 +13606,8 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("Math.trunc()"),
       njs_str("NaN") },
 
-    /* ES5FIX: "[object Math]". */
-
     { njs_str("Math"),
-      njs_str("[object Object]") },
+      njs_str("[object Math]") },
 
     { njs_str("Math.x = function (x) {return 2*x;}; Math.x(3)"),
       njs_str("6") },
@@ -13810,6 +13843,9 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("this.Math = 1; Math"),
       njs_str("1") },
 
+    { njs_str("JSON"),
+      njs_str("[object JSON]") },
+
     { njs_str("JSON === JSON"),
       njs_str("true") },
 
@@ -14535,7 +14571,7 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("njs.dump($r.header)"),
       njs_str("{type:\"object\",props:[\"getter\",\"keys\"]}") },
 
-    { njs_str("njs.dump(njs) == `{version:'${njs.version}'}`"),
+    { njs_str("njs.dump(njs) == `njs {version:'${njs.version}'}`"),
       njs_str("true") },
 
     { njs_str("njs.dump(-0)"),
@@ -14760,11 +14796,9 @@ static njs_unit_test_t  njs_test[] =
 
     /* require('crypto').createHash() */
 
-    { njs_str("require('crypto').createHash('sha1')"),
-      njs_str("[object Hash]") },
-
-    { njs_str("Object.prototype.toString.call(require('crypto').createHash('sha1'))"),
-      njs_str("[object Object]") },
+    { njs_str("var h = require('crypto').createHash('sha1');"
+              "[Object.prototype.toString.call(h), njs.dump(h),h]"),
+      njs_str("[object Hash],Hash {},[object Hash]") },
 
     { njs_str("var h = require('crypto').createHash('sha1');"
               "var Hash = h.constructor; "
@@ -14853,8 +14887,9 @@ static njs_unit_test_t  njs_test[] =
 
     /* require('crypto').createHmac() */
 
-    { njs_str("require('crypto').createHmac('sha1', '')"),
-      njs_str("[object Hmac]") },
+    { njs_str("var h = require('crypto').createHmac('sha1', '');"
+              "[Object.prototype.toString.call(h), njs.dump(h),h]"),
+      njs_str("[object Hmac],Hmac {},[object Hmac]") },
 
     { njs_str("var h = require('crypto').createHmac('md5', '');"
                  "h.digest('hex')"),


More information about the nginx-devel mailing list