]> git.kaiwu.me - njs.git/commitdiff
Introduced Symbol.toStringTag support for builtin objects.
authorDmitry Volyntsev <xeioex@nginx.com>
Fri, 22 Nov 2019 16:03:23 +0000 (19:03 +0300)
committerDmitry Volyntsev <xeioex@nginx.com>
Fri, 22 Nov 2019 16:03:23 +0000 (19:03 +0300)
This closes #255 issue on Github.

src/njs_builtin.c
src/njs_crypto.c
src/njs_json.c
src/njs_lvlhsh.h
src/njs_math.c
src/njs_object.c
src/njs_object.h
src/njs_symbol.c
src/test/njs_unit_test.c

index 5d11f3d07a318c3f3ba69a34d744dbf02fe5a71d..d8aa8d32c65bdd4cce813a0f1831410585835a9f 100644 (file)
@@ -956,6 +956,13 @@ njs_top_level_constructor(njs_vm_t *vm, njs_object_prop_t *self,
 
 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. */
 
     {
@@ -1311,6 +1318,13 @@ static const njs_object_init_t  njs_global_this_init = {
 
 static const njs_object_prop_t  njs_njs_object_properties[] =
 {
+    {
+        .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"),
@@ -1509,6 +1523,13 @@ njs_process_object_ppid(njs_vm_t *vm, njs_object_prop_t *prop,
 
 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"),
index fc485efd995aa9355a58053c5aeccd0c5874be0d..322adbd9112f041688d0acaaf26f063f1b9c06b9 100644 (file)
@@ -299,18 +299,6 @@ njs_hash_prototype_digest(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
 }
 
 
-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_prototype_properties[] =
 
     {
         .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, njs_value_t *args, njs_uint_t nargs,
 }
 
 
-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_prototype_properties[] =
 
     {
         .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,
     },
 
index 6e5f79510a725faeaf244098de756832ac48ad8d..2eeca86245551aa76d7fe55057454756490c956b 100644 (file)
@@ -205,7 +205,7 @@ njs_vm_json_parse(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs)
 {
     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_value_t *args, njs_uint_t nargs)
 {
     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_stringify_t *stringify)
 
 
 #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_stringify_t *stringify)
     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 *stringify, njs_str_t *str)
 
 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_object_properties[] =
         .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_t *retval, const njs_value_t *value,
     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_t *retval, const njs_value_t *value,
         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_t *retval, const njs_value_t *value,
 
             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);
index b913dcd5d2bb542c4a77f2777d19317da9b3116c..ab58de2d0067642734c4b4703ace1a9a5cd19b7b 100644 (file)
@@ -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()
index edeb1422274f9333f7c3256312d798c6177902c5..b842919403ff67bedc917e387a338744a8f060d8 100644 (file)
@@ -984,6 +984,13 @@ njs_object_math_trunc(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
 
 static const njs_object_prop_t  njs_math_object_properties[] =
 {
+    {
+        .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"),
index 088aef683ec79c9f94e0e2f34532372e583a4f96..3f1ac4001f0b480b2168b4c3ba1b5bd940d7de55 100644 (file)
@@ -2274,12 +2274,18 @@ static const njs_value_t  njs_object_regexp_string =
 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 *vm, njs_value_t *args,
         &njs_object_object_string,
     };
 
-    name = class_name[args[0].type];
+    value = njs_argument(args, 0);
+    name = class_name[value->type];
+
+    if (njs_is_null_or_undefined(value)) {
+        vm->retval = *name;
+
+        return NJS_OK;
+    }
 
-    if (njs_is_error(&args[0])) {
+    if (njs_is_error(value)) {
         name = &njs_object_error_string;
     }
 
-    if (njs_fast_path(name != NULL)) {
+    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;
     }
 
-    njs_internal_error(vm, "Unknown value type");
+    (void) njs_string_prop(&string, &tag);
 
-    return NJS_ERROR;
+    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;
 }
 
 
index b64f9947330dd4967780c24fcac67af3fdf37acb..8d365e37ade9daea3ebf2dec9ee24a8434ab3e4a 100644 (file)
@@ -238,6 +238,27 @@ njs_object_length_set(njs_vm_t *vm, njs_value_t *value, uint32_t length)
 }
 
 
+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;
 
 
index 015f63c62265442ff88e85fafc705479f2783f45..ce94dfd931f77c6e57c35b8c78b702c3ca6915de 100644 (file)
@@ -383,6 +383,13 @@ njs_symbol_prototype_description(njs_vm_t *vm, njs_value_t *args,
 
 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__"),
index 2c40adbe8f838c944924ac32da15c49502bff507..6a651a91f9194be8e9f2fff4e5af9c0251b22a24 100644 (file)
@@ -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')"),