From 3a044d5bdb4b977ada69eec89c594b974b012b93 Mon Sep 17 00:00:00 2001 From: Dmitry Volyntsev Date: Fri, 22 Nov 2019 19:03:23 +0300 Subject: [PATCH] Introduced Symbol.toStringTag support for builtin objects. This closes #255 issue on Github. --- 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 | 51 ++++++++++++++++++++++++++--- src/njs_object.h | 21 ++++++++++++ src/njs_symbol.c | 7 ++++ src/test/njs_unit_test.c | 71 ++++++++++++++++++++++++++++++---------- 9 files changed, 188 insertions(+), 61 deletions(-) diff --git a/src/njs_builtin.c b/src/njs_builtin.c index 5d11f3d0..d8aa8d32 100644 --- a/src/njs_builtin.c +++ b/src/njs_builtin.c @@ -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"), diff --git a/src/njs_crypto.c b/src/njs_crypto.c index fc485efd..322adbd9 100644 --- a/src/njs_crypto.c +++ b/src/njs_crypto.c @@ -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, }, diff --git a/src/njs_json.c b/src/njs_json.c index 6e5f7951..2eeca862 100644 --- a/src/njs_json.c +++ b/src/njs_json.c @@ -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); diff --git a/src/njs_lvlhsh.h b/src/njs_lvlhsh.h index b913dcd5..ab58de2d 100644 --- a/src/njs_lvlhsh.h +++ b/src/njs_lvlhsh.h @@ -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 --git a/src/njs_math.c b/src/njs_math.c index edeb1422..b8429194 100644 --- a/src/njs_math.c +++ b/src/njs_math.c @@ -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"), diff --git a/src/njs_object.c b/src/njs_object.c index 088aef68..3f1ac400 100644 --- a/src/njs_object.c +++ b/src/njs_object.c @@ -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; } diff --git a/src/njs_object.h b/src/njs_object.h index b64f9947..8d365e37 100644 --- a/src/njs_object.h +++ b/src/njs_object.h @@ -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; diff --git a/src/njs_symbol.c b/src/njs_symbol.c index 015f63c6..ce94dfd9 100644 --- a/src/njs_symbol.c +++ b/src/njs_symbol.c @@ -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__"), diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index 2c40adbe..6a651a91 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -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')"), -- 2.47.3