[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