From 28e6f13ae94640754683cc54b63220a8780ddfc8 Mon Sep 17 00:00:00 2001 From: Vadim Zhestikov Date: Mon, 10 Nov 2025 08:56:28 -0800 Subject: [PATCH 1/3] Fixed Object prototype methods according to the spec. --- src/njs_object.c | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/src/njs_object.c b/src/njs_object.c index 26e8182b9..0f277fea5 100644 --- a/src/njs_object.c +++ b/src/njs_object.c @@ -2596,14 +2596,6 @@ njs_object_prototype_has_own_property(njs_vm_t *vm, njs_value_t *args, njs_value_t *value, *property, lvalue; njs_property_query_t pq; - value = njs_argument(args, 0); - - if (njs_is_null_or_undefined(value)) { - njs_type_error(vm, "cannot convert %s argument to object", - njs_type_string(value->type)); - return NJS_ERROR; - } - property = njs_lvalue_arg(&lvalue, args, nargs, 1); if (njs_slow_path(!njs_is_key(property))) { @@ -2613,6 +2605,14 @@ njs_object_prototype_has_own_property(njs_vm_t *vm, njs_value_t *args, } } + value = njs_argument(args, 0); + + if (njs_is_null_or_undefined(value)) { + njs_type_error(vm, "cannot convert %s argument to object", + njs_type_string(value->type)); + return NJS_ERROR; + } + njs_property_query_init(&pq, NJS_PROPERTY_QUERY_GET, 1); ret = njs_property_query_val(vm, &pq, value, property); @@ -2642,14 +2642,6 @@ njs_object_prototype_prop_is_enumerable(njs_vm_t *vm, njs_value_t *args, njs_object_prop_t *prop; njs_property_query_t pq; - value = njs_argument(args, 0); - - if (njs_is_null_or_undefined(value)) { - njs_type_error(vm, "cannot convert %s argument to object", - njs_type_string(value->type)); - return NJS_ERROR; - } - property = njs_lvalue_arg(&lvalue, args, nargs, 1); if (njs_slow_path(!njs_is_key(property))) { @@ -2659,6 +2651,14 @@ njs_object_prototype_prop_is_enumerable(njs_vm_t *vm, njs_value_t *args, } } + value = njs_argument(args, 0); + + if (njs_is_null_or_undefined(value)) { + njs_type_error(vm, "cannot convert %s argument to object", + njs_type_string(value->type)); + return NJS_ERROR; + } + njs_property_query_init(&pq, NJS_PROPERTY_QUERY_GET, 1); ret = njs_property_query_val(vm, &pq, value, property); @@ -2689,15 +2689,20 @@ njs_object_prototype_is_prototype_of(njs_vm_t *vm, njs_value_t *args, njs_value_t *prototype, *value; njs_object_t *object, *proto; + value = njs_arg(args, nargs, 1); + + if (njs_slow_path(!njs_is_object(value))) { + goto ret_false; + } + if (njs_slow_path(njs_is_null_or_undefined(njs_argument(args, 0)))) { njs_type_error(vm, "cannot convert undefined to object"); return NJS_ERROR; } prototype = &args[0]; - value = njs_arg(args, nargs, 1); - if (njs_is_object(prototype) && njs_is_object(value)) { + if (njs_is_object(prototype)) { proto = njs_object(prototype); object = njs_object(value); @@ -2712,6 +2717,8 @@ njs_object_prototype_is_prototype_of(njs_vm_t *vm, njs_value_t *args, } while (object != NULL); } +ret_false: + njs_set_boolean(retval, 0); return NJS_OK; From 85d71c946e760ebf0b6d3c0aaef0d01b421f8022 Mon Sep 17 00:00:00 2001 From: Vadim Zhestikov Date: Mon, 10 Nov 2025 09:50:48 -0800 Subject: [PATCH 2/3] Added Object.hasOwn(). --- src/njs_atom_defs.h | 1 + src/njs_object.c | 49 ++++++++++++++++++------- src/test/njs_unit_test.c | 77 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 12 deletions(-) diff --git a/src/njs_atom_defs.h b/src/njs_atom_defs.h index 6328c34b8..35dcbe124 100644 --- a/src/njs_atom_defs.h +++ b/src/njs_atom_defs.h @@ -286,6 +286,7 @@ NJS_DEF_STRING(getMonth, "getMonth", 0, 0) NJS_DEF_STRING(global, "global", 0, 0) NJS_DEF_STRING(globalThis, "globalThis", 0, 0) NJS_DEF_STRING(groups, "groups", 0, 0) +NJS_DEF_STRING(hasOwn, "hasOwn", 0, 0) NJS_DEF_STRING(hasOwnProperty, "hasOwnProperty", 0, 0) NJS_DEF_STRING(hasInstance, "hasInstance", 0, 0) NJS_DEF_STRING(hypot, "hypot", 0, 0) diff --git a/src/njs_object.c b/src/njs_object.c index 0f277fea5..97f17d583 100644 --- a/src/njs_object.c +++ b/src/njs_object.c @@ -31,6 +31,9 @@ static njs_int_t njs_object_define_properties(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); static njs_int_t njs_object_set_prototype(njs_vm_t *vm, njs_object_t *object, const njs_value_t *value); +static njs_int_t njs_object_prototype_has_own_property(njs_vm_t *vm, + njs_value_t *args, njs_uint_t nargs, njs_index_t magic, + njs_value_t *retval); njs_object_t * @@ -2274,8 +2277,10 @@ static const njs_object_prop_init_t njs_object_constructor_properties[] = NJS_DECLARE_PROP_NATIVE(STRING_assign, njs_object_assign, 2, 0), NJS_DECLARE_PROP_NATIVE(STRING_is, njs_object_is, 2, 0), -}; + NJS_DECLARE_PROP_NATIVE(STRING_hasOwn, + njs_object_prototype_has_own_property, 2, 1), +}; const njs_object_init_t njs_object_constructor_init = { njs_object_constructor_properties, @@ -2590,27 +2595,47 @@ njs_object_to_string(njs_vm_t *vm, njs_value_t *this, njs_value_t *retval) static njs_int_t njs_object_prototype_has_own_property(njs_vm_t *vm, njs_value_t *args, - njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) + njs_uint_t nargs, njs_index_t magic, njs_value_t *retval) { njs_int_t ret; njs_value_t *value, *property, lvalue; njs_property_query_t pq; - property = njs_lvalue_arg(&lvalue, args, nargs, 1); + if (magic == 0) { + /* hasOwnProperty. */ - if (njs_slow_path(!njs_is_key(property))) { - ret = njs_value_to_key(vm, property, property); - if (njs_slow_path(ret != NJS_OK)) { + property = njs_lvalue_arg(&lvalue, args, nargs, 1 + magic); + if (njs_slow_path(!njs_is_key(property))) { + ret = njs_value_to_key(vm, property, property); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + } + + value = njs_argument(args, magic); + if (njs_is_null_or_undefined(value)) { + njs_type_error(vm, "cannot convert %s argument to object", + njs_type_string(value->type)); return NJS_ERROR; } - } - value = njs_argument(args, 0); + } else { + /* hasOwn. */ - if (njs_is_null_or_undefined(value)) { - njs_type_error(vm, "cannot convert %s argument to object", - njs_type_string(value->type)); - return NJS_ERROR; + value = njs_lvalue_arg(&lvalue, args, nargs, magic); + if (njs_is_null_or_undefined(value)) { + njs_type_error(vm, "cannot convert %s argument to object", + njs_type_string(value->type)); + return NJS_ERROR; + } + + property = njs_lvalue_arg(&lvalue, args, nargs, 1 + magic); + if (njs_slow_path(!njs_is_key(property))) { + ret = njs_value_to_key(vm, property, property); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + } } njs_property_query_init(&pq, NJS_PROPERTY_QUERY_GET, 1); diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index 67c57d19e..c0f1f1d36 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -15108,6 +15108,83 @@ static njs_unit_test_t njs_test[] = { njs_str("Object.prototype.hasOwnProperty('hasOwnProperty')"), njs_str("true") }, + { njs_str("var o = {a:1}; Object.hasOwn(o, 'a')"), + njs_str("true") }, + + { njs_str("var o = Object.create({a:2}); Object.hasOwn(o, 'a')"), + njs_str("false") }, + + { njs_str("var o = {a:1}; Object.hasOwn(o, 'b')"), + njs_str("false") }, + + { njs_str("var o = Object.create(null); o.a = 1; Object.hasOwn(o, 'a')"), + njs_str("true") }, + + { njs_str("var o = Object.create(null); Object.hasOwn(o, 'hasOwnProperty')"), + njs_str("false") }, + + { njs_str("var o = {hasOwnProperty: 1}; Object.hasOwn(o, 'hasOwnProperty')"), + njs_str("true") }, + + { njs_str("var o = {a:1}; o.hasOwnProperty = function(){return false}; " + "Object.hasOwn(o, 'a')"), + njs_str("true") }, + + { njs_str("Object.hasOwn(null, 'a')"), + njs_str("TypeError: cannot convert null argument to object") }, + + { njs_str("Object.hasOwn(undefined, 'a')"), + njs_str("TypeError: cannot convert undefined argument to object") }, + + { njs_str("Object.hasOwn(1, 'toString')"), + njs_str("true") }, + + { njs_str("Object.hasOwn('abc', '0')"), + njs_str("true") }, + + { njs_str("Object.hasOwn('abc', '3')"), + njs_str("false") }, + + { njs_str("Object.hasOwn('abc', 'length')"), + njs_str("true") }, + + { njs_str("Object.hasOwn([1,2], '0')"), + njs_str("true") }, + + { njs_str("Object.hasOwn([,2], '0')"), + njs_str("false") }, + + { njs_str("Object.hasOwn([,2], '1')"), + njs_str("true") }, + + { njs_str("Object.hasOwn([], 'length')"), + njs_str("true") }, + + { njs_str("var s = Symbol('test'); var o = {}; o[s] = 1; Object.hasOwn(o, s)"), + njs_str("true") }, + + { njs_str("var s1 = Symbol('test'); var s2 = Symbol('test'); " + "var o = {}; o[s1] = 1; Object.hasOwn(o, s2)"), + njs_str("false") }, + + { njs_str("Object.hasOwn({}, Symbol.iterator)"), + njs_str("false") }, + + { njs_str("Object.hasOwn()"), + njs_str("TypeError: cannot convert ") }, + + { njs_str("Object.hasOwn({})"), + njs_str("false") }, + + { njs_str("var o = {}; Object.hasOwn(o)"), + njs_str("false") }, + + { njs_str("Object.hasOwn.length"), + njs_str("2") }, + + { njs_str("Object.hasOwn.name"), + njs_str("hasOwn") }, + { njs_str("var o = {};" "Object.defineProperty(o, 'a', {get:undefined, set:undefined}).a"), njs_str("undefined") }, From 968dcd240ef1b200c8e03fa45ab37c194d7e409b Mon Sep 17 00:00:00 2001 From: Vadim Zhestikov Date: Tue, 11 Nov 2025 10:01:01 -0800 Subject: [PATCH 3/3] Added Symbol.toPrimitive to Date and Symbol prototypes. --- src/njs_date.c | 68 +++++++++++++++++- src/njs_object.h | 3 +- src/njs_symbol.c | 6 ++ src/njs_value.c | 71 ++++++++++++++---- src/njs_value.h | 5 ++ src/njs_value_conversion.h | 5 +- src/njs_vm.c | 3 +- src/njs_vmcode.c | 35 +++++---- src/test/njs_unit_test.c | 143 ++++++++++++++++++++++++++++++++++++- 9 files changed, 305 insertions(+), 34 deletions(-) diff --git a/src/njs_date.c b/src/njs_date.c index 03e866a15..4577d9bdd 100644 --- a/src/njs_date.c +++ b/src/njs_date.c @@ -411,7 +411,8 @@ njs_date_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } else if (nargs == 2) { if (njs_is_object(&args[1])) { if (!njs_is_date(&args[1])) { - ret = njs_value_to_primitive(vm, &args[1], &args[1], 0); + ret = njs_value_to_primitive(vm, &args[1], &args[1], + NJS_HINT_NONE); if (njs_slow_path(ret != NJS_OK)) { return ret; } @@ -1381,6 +1382,65 @@ njs_date_prototype_set_time(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } +static njs_int_t +njs_date_prototype_to_primitive(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) +{ + njs_int_t ret; + njs_uint_t hint; + + if (njs_slow_path(!njs_is_date(&args[0]))) { + njs_type_error(vm, "cannot convert %s to date", + njs_type_string(args[0].type)); + + return NJS_ERROR; + } + + if (njs_slow_path(nargs <= 1)) { + goto error; + } + + if (njs_slow_path(!njs_is_string(&args[1]))) { + ret = njs_value_to_string(vm, &args[1], &args[1]); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + } + + ret = njs_atom_atomize_key(vm, &args[1]); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + switch (args[1].atom_id) { + case NJS_ATOM_STRING_number: + hint = NJS_HINT_NUMBER; + break; + + case NJS_ATOM_STRING_string: + case NJS_ATOM_STRING_default: + hint = NJS_HINT_STRING; + break; + + default: +error: + njs_type_error(vm, "invalid hint"); + return NJS_ERROR; + + } + + ret = njs_value_to_primitive(vm, &args[0], &args[0], + hint | NJS_HINT_FORCE_ORDINARY); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + njs_value_assign(retval, &args[0]); + + return NJS_OK; +} + + static njs_int_t njs_date_prototype_set_fields(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t magic, njs_value_t *retval) @@ -1643,6 +1703,12 @@ static const njs_object_prop_init_t njs_date_prototype_properties[] = NJS_DECLARE_PROP_NATIVE(STRING_setUTCFullYear, njs_date_prototype_set_fields, 3, njs_date_magic2(NJS_DATE_YR, 3, 0)), + + /* NJS_DECLARE_PROP_NATIVE, but not writable */ + NJS_DECLARE_PROP_VALUE(SYMBOL_toPrimitive, + njs_native_function2(njs_date_prototype_to_primitive, + 1, 0), + NJS_OBJECT_PROP_VALUE_C), }; diff --git a/src/njs_object.h b/src/njs_object.h index d2b49180c..641a10287 100644 --- a/src/njs_object.h +++ b/src/njs_object.h @@ -186,7 +186,8 @@ njs_value_to_key2(njs_vm_t *vm, njs_value_t *dst, njs_value_t *value, } else { if (convert) { - ret = njs_value_to_primitive(vm, &primitive, value, 1); + ret = njs_value_to_primitive(vm, &primitive, value, + NJS_HINT_STRING); } else { ret = njs_object_to_string(vm, value, &primitive); diff --git a/src/njs_symbol.c b/src/njs_symbol.c index 46634a611..3fffdf869 100644 --- a/src/njs_symbol.c +++ b/src/njs_symbol.c @@ -310,6 +310,12 @@ static const njs_object_prop_init_t njs_symbol_prototype_properties[] = NJS_DECLARE_PROP_NATIVE(STRING_valueOf, njs_symbol_prototype_value_of, 0, 0), + /* NJS_DECLARE_PROP_NATIVE, but not writable */ + NJS_DECLARE_PROP_VALUE(SYMBOL_toPrimitive, + njs_native_function2(njs_symbol_prototype_value_of, + 1, 0), + NJS_OBJECT_PROP_VALUE_C), + NJS_DECLARE_PROP_NATIVE(STRING_toString, njs_symbol_prototype_to_string, 0, 0), diff --git a/src/njs_value.c b/src/njs_value.c index 09aa9dfcd..7d4c0a39d 100644 --- a/src/njs_value.c +++ b/src/njs_value.c @@ -30,19 +30,14 @@ const njs_value_t njs_value_zero = njs_value(NJS_NUMBER, 0, 0.0); const njs_value_t njs_value_nan = njs_value(NJS_NUMBER, 0, NAN); const njs_value_t njs_value_invalid = njs_value(NJS_INVALID, 0, 0.0); -/* - * A hint value is 0 for numbers and 1 for strings. The value chooses - * method calls order specified by ECMAScript 5.1: "valueOf", "toString" - * for numbers and "toString", "valueOf" for strings. - */ njs_int_t njs_value_to_primitive(njs_vm_t *vm, njs_value_t *dst, njs_value_t *value, njs_uint_t hint) { njs_int_t ret; - njs_uint_t tries; - njs_value_t method, retval; + njs_uint_t tries, force_ordinary; + njs_value_t method, retval, arguments[2]; njs_flathsh_query_t fhq; static const uint32_t atoms[] = { @@ -50,6 +45,12 @@ njs_value_to_primitive(njs_vm_t *vm, njs_value_t *dst, njs_value_t *value, NJS_ATOM_STRING_toString, }; + static const njs_uint_t atom_by_hint[] = { + NJS_ATOM_STRING_number, + NJS_ATOM_STRING_string, + NJS_ATOM_STRING_default, + }; + if (njs_is_primitive(value)) { *dst = *value; return NJS_OK; @@ -58,16 +59,57 @@ njs_value_to_primitive(njs_vm_t *vm, njs_value_t *dst, njs_value_t *value, tries = 0; fhq.proto = &njs_object_hash_proto; + if (!njs_is_object(value)) { + goto error; + } + + force_ordinary = hint & NJS_HINT_FORCE_ORDINARY; + hint &= ~NJS_HINT_FORCE_ORDINARY; + + if (force_ordinary) { + goto ordinary; + } + + fhq.key_hash = NJS_ATOM_SYMBOL_toPrimitive; + + ret = njs_object_property(vm, njs_object(value), &fhq, &method); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + + if (njs_is_function(&method)) { + arguments[0] = *value; + + njs_atom_to_value(vm, &arguments[1], atom_by_hint[hint]); + + ret = njs_function_apply(vm, njs_function(&method), arguments, 2, + &retval); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + if (njs_is_primitive(&retval)) { + goto done; + } + + goto error; + } + +ordinary: + + if (hint != NJS_HINT_STRING) { + hint = NJS_HINT_NUMBER; + } + for ( ;; ) { ret = NJS_ERROR; - if (njs_is_object(value) && tries < 2) { + if (tries < 2) { hint ^= tries++; fhq.key_hash = atoms[hint]; ret = njs_object_property(vm, njs_object(value), &fhq, &method); - if (njs_slow_path(ret == NJS_ERROR)) { return ret; } @@ -75,25 +117,28 @@ njs_value_to_primitive(njs_vm_t *vm, njs_value_t *dst, njs_value_t *value, if (njs_is_function(&method)) { ret = njs_function_apply(vm, njs_function(&method), value, 1, &retval); - if (njs_slow_path(ret != NJS_OK)) { return ret; } if (njs_is_primitive(&retval)) { - break; + goto done; } } /* Try the second method. */ continue; - } + } + +error: njs_type_error(vm, "Cannot convert object to primitive value"); - return ret; + return NJS_ERROR; } +done: + *dst = retval; return NJS_OK; diff --git a/src/njs_value.h b/src/njs_value.h index 2ffa04e7d..61a46c0a8 100644 --- a/src/njs_value.h +++ b/src/njs_value.h @@ -940,6 +940,11 @@ njs_set_object_value(njs_value_t *value, njs_object_value_t *object_value) #define njs_set_invalid(value) \ (value)->type = NJS_INVALID +#define NJS_HINT_NUMBER 0 +#define NJS_HINT_STRING 1 +#define NJS_HINT_NONE 2 +#define NJS_HINT_FORCE_ORDINARY (1 << 3) + njs_int_t njs_value_to_primitive(njs_vm_t *vm, njs_value_t *dst, njs_value_t *value, njs_uint_t hint); njs_array_t *njs_value_enumerate(njs_vm_t *vm, njs_value_t *value, diff --git a/src/njs_value_conversion.h b/src/njs_value_conversion.h index 80c08b450..ba3814d0b 100644 --- a/src/njs_value_conversion.h +++ b/src/njs_value_conversion.h @@ -15,7 +15,7 @@ njs_value_to_number(njs_vm_t *vm, njs_value_t *value, double *dst) njs_value_t primitive; if (njs_slow_path(!njs_is_primitive(value))) { - ret = njs_value_to_primitive(vm, &primitive, value, 0); + ret = njs_value_to_primitive(vm, &primitive, value, NJS_HINT_NUMBER); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } @@ -172,7 +172,8 @@ njs_value_to_chain(njs_vm_t *vm, njs_chb_t *chain, njs_value_t *value) value = njs_object_value(value); } else { - ret = njs_value_to_primitive(vm, &primitive, value, 1); + ret = njs_value_to_primitive(vm, &primitive, value, + NJS_HINT_STRING); if (njs_slow_path(ret != NJS_OK)) { return ret; } diff --git a/src/njs_vm.c b/src/njs_vm.c index 19f07db40..fe4e3a317 100644 --- a/src/njs_vm.c +++ b/src/njs_vm.c @@ -1560,7 +1560,8 @@ njs_value_to_string(njs_vm_t *vm, njs_value_t *dst, njs_value_t *value) value = njs_object_value(value); } else { - ret = njs_value_to_primitive(vm, &primitive, value, 1); + ret = njs_value_to_primitive(vm, &primitive, value, + NJS_HINT_STRING); if (njs_slow_path(ret != NJS_OK)) { return ret; } diff --git a/src/njs_vmcode.c b/src/njs_vmcode.c index 70da4630f..626f85cff 100644 --- a/src/njs_vmcode.c +++ b/src/njs_vmcode.c @@ -88,7 +88,6 @@ njs_vmcode_interpreter(njs_vm_t *vm, u_char *pc, njs_value_t *rval, int32_t i32; uint32_t u32; njs_str_t string; - njs_uint_t hint; njs_bool_t valid, lambda_call; njs_value_t *retval, *value1, *value2; njs_value_t *src, *s1, *s2, dst; @@ -461,7 +460,8 @@ NEXT_LBL; njs_vmcode_operand(vm, vmcode->operand2, value1); if (njs_slow_path(!njs_is_primitive(value1))) { - ret = njs_value_to_primitive(vm, &primitive1, value1, 0); + ret = njs_value_to_primitive(vm, &primitive1, value1, + NJS_HINT_NUMBER); if (ret != NJS_OK) { goto error; } @@ -470,7 +470,8 @@ NEXT_LBL; } if (njs_slow_path(!njs_is_primitive(value2))) { - ret = njs_value_to_primitive(vm, &primitive2, value2, 0); + ret = njs_value_to_primitive(vm, &primitive2, value2, + NJS_HINT_NUMBER); if (ret != NJS_OK) { goto error; } @@ -500,7 +501,8 @@ NEXT_LBL; njs_vmcode_operand(vm, vmcode->operand2, value1); if (njs_slow_path(!njs_is_primitive(value1))) { - ret = njs_value_to_primitive(vm, &primitive1, value1, 0); + ret = njs_value_to_primitive(vm, &primitive1, value1, + NJS_HINT_NUMBER); if (ret != NJS_OK) { goto error; } @@ -509,7 +511,8 @@ NEXT_LBL; } if (njs_slow_path(!njs_is_primitive(value2))) { - ret = njs_value_to_primitive(vm, &primitive2, value2, 0); + ret = njs_value_to_primitive(vm, &primitive2, value2, + NJS_HINT_NUMBER); if (ret != NJS_OK) { goto error; } @@ -539,7 +542,8 @@ NEXT_LBL; njs_vmcode_operand(vm, vmcode->operand2, value1); if (njs_slow_path(!njs_is_primitive(value1))) { - ret = njs_value_to_primitive(vm, &primitive1, value1, 0); + ret = njs_value_to_primitive(vm, &primitive1, value1, + NJS_HINT_NUMBER); if (ret != NJS_OK) { goto error; } @@ -548,7 +552,8 @@ NEXT_LBL; } if (njs_slow_path(!njs_is_primitive(value2))) { - ret = njs_value_to_primitive(vm, &primitive2, value2, 0); + ret = njs_value_to_primitive(vm, &primitive2, value2, + NJS_HINT_NUMBER); if (ret != NJS_OK) { goto error; } @@ -578,7 +583,8 @@ NEXT_LBL; njs_vmcode_operand(vm, vmcode->operand2, value1); if (njs_slow_path(!njs_is_primitive(value1))) { - ret = njs_value_to_primitive(vm, &primitive1, value1, 0); + ret = njs_value_to_primitive(vm, &primitive1, value1, + NJS_HINT_NUMBER); if (ret != NJS_OK) { goto error; } @@ -587,7 +593,8 @@ NEXT_LBL; } if (njs_slow_path(!njs_is_primitive(value2))) { - ret = njs_value_to_primitive(vm, &primitive2, value2, 0); + ret = njs_value_to_primitive(vm, &primitive2, value2, + NJS_HINT_NUMBER); if (ret != NJS_OK) { goto error; } @@ -617,8 +624,8 @@ NEXT_LBL; njs_vmcode_operand(vm, vmcode->operand2, value1); if (njs_slow_path(!njs_is_primitive(value1))) { - hint = njs_is_date(value1); - ret = njs_value_to_primitive(vm, &primitive1, value1, hint); + ret = njs_value_to_primitive(vm, &primitive1, value1, + NJS_HINT_NONE); if (ret != NJS_OK) { goto error; } @@ -627,8 +634,8 @@ NEXT_LBL; } if (njs_slow_path(!njs_is_primitive(value2))) { - hint = njs_is_date(value2); - ret = njs_value_to_primitive(vm, &primitive2, value2, hint); + ret = njs_value_to_primitive(vm, &primitive2, value2, + NJS_HINT_NONE); if (ret != NJS_OK) { goto error; } @@ -2478,7 +2485,7 @@ njs_values_equal(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2) /* "hv" is an object and "lv" is either a string or a symbol or a numeric. */ - ret = njs_value_to_primitive(vm, &primitive, hv, 0); + ret = njs_value_to_primitive(vm, &primitive, hv, NJS_HINT_NONE); if (ret != NJS_OK) { return ret; } diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index c0f1f1d36..0c5e55cc0 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -13727,6 +13727,36 @@ static njs_unit_test_t njs_test[] = { njs_str("Symbol.prototype.toString()"), njs_str("TypeError: unexpected value type:object") }, + { njs_str("Symbol.prototype[Symbol.toPrimitive].length"), + njs_str("1") }, + + { njs_str("Symbol.prototype[Symbol.toPrimitive].name"), + njs_str("[Symbol.toPrimitive]") }, + + { njs_str("Symbol.prototype[Symbol.toPrimitive].call(undefined)"), + njs_str("TypeError: unexpected value type") }, + + { njs_str("Symbol.prototype[Symbol.toPrimitive].call(null)"), + njs_str("TypeError: unexpected value type") }, + + { njs_str("Symbol.prototype[Symbol.toPrimitive].call(86)"), + njs_str("TypeError: unexpected value type") }, + + { njs_str("Symbol.prototype[Symbol.toPrimitive].call('')"), + njs_str("TypeError: unexpected value type") }, + + { njs_str("Symbol.prototype[Symbol.toPrimitive].call(true)"), + njs_str("TypeError: unexpected value type") }, + + { njs_str("Symbol.prototype[Symbol.toPrimitive].call({})"), + njs_str("TypeError: unexpected value type") }, + + { njs_str("Object(Symbol.toPrimitive)[Symbol.toPrimitive]() === Symbol.toPrimitive"), + njs_str("true") }, + + { njs_str("Symbol.toPrimitive[Symbol.toPrimitive]() === Symbol.toPrimitive"), + njs_str("true") }, + { njs_str("new Symbol()"), njs_str("TypeError: Symbol is not a constructor") }, @@ -16580,13 +16610,13 @@ static njs_unit_test_t njs_test[] = njs_str("-62198755200000") }, { njs_str("var d = new Date(); d == Date.parse(d.toISOString())"), - njs_str("true") }, + njs_str("false") }, { njs_str("var s = Date(); s === Date(Date.parse(s))"), njs_str("true") }, { njs_str("var n = Date.now(); n == new Date(n)"), - njs_str("true") }, + njs_str("false") }, { njs_str("var d = new Date(2011,0); d.getFullYear()"), njs_str("2011") }, @@ -16882,6 +16912,115 @@ static njs_unit_test_t njs_test[] = { njs_str("Date.constructor === Function"), njs_str("true") }, + { njs_str("var y = Object.defineProperty({}, Symbol.toPrimitive, {" + " get: function() {" + " throw new Error('expected');" + " }" + "});" + "new Date(y);"), + njs_str("Error: expected") }, + + { njs_str("var y = {};" + "y[Symbol.toPrimitive] = function() {" + " throw new Error('expected');" + "};" + "new Date(y);"), + njs_str("Error: expected") }, + + { njs_str("var y = {};" + "var callCount = 0;" + "var thisVal, args;" + "y[Symbol.toPrimitive] = function() {" + " callCount += 1;" + " thisVal = this;" + " args = arguments;" + "};" + "new Date(y);" + "callCount === 1 && thisVal === y && args.length === 1 && args[0] === 'default'"), + njs_str("true") }, + + { njs_str("var y = {};" + "var retVal;" + "y[Symbol.toPrimitive] = function() {" + " return retVal;" + "};" + "retVal = {};" + "new Date(y);"), + njs_str("TypeError") }, + + { njs_str("var badToPrimitive = {};" + "badToPrimitive[Symbol.toPrimitive] = function() {" + " throw new Error('expected');" + "};" + "new Date(badToPrimitive);"), + njs_str("Error: expected") }, + + { njs_str("var spyToPrimitive = {};" + "var callCount = 0;" + "var thisValue, args;" + "spyToPrimitive[Symbol.toPrimitive] = function() {" + " thisValue = this;" + " args = arguments;" + " callCount += 1;" + "};" + "new Date(spyToPrimitive);" + "callCount === 1 && thisValue === spyToPrimitive && args.length === 1 && args[0] === 'default';"), + njs_str("true") }, + + { njs_str("var poisonedObject = {};" + "var poisonedDate = new Date();" + "Object.defineProperty(poisonedObject, Symbol.toPrimitive, {" + " get: function() {" + " throw new Error('expected');" + " }" + "});" + "Object.defineProperty(poisonedDate, Symbol.toPrimitive, {" + " get: function() {" + " throw new Error('unexpected');" + " }" + "});" + "Date(poisonedObject);" + "new Date(poisonedDate);" + "new Date(poisonedObject);"), + njs_str("Error: expected") }, + + { njs_str("var faultyToPrimitive = {};" + "var returnValue;" + "faultyToPrimitive[Symbol.toPrimitive] = function() {" + " return returnValue;" + "};" + "returnValue = {};" + "new Date(faultyToPrimitive);"), + njs_str("TypeError") }, + + { njs_str("var stringToPrimitive = {};" + "stringToPrimitive[Symbol.toPrimitive] = function() {" + " return '2016-06-05T18:40:00.000Z';" + "};" + "new Date(stringToPrimitive).getTime() === 1465152000000"), + njs_str("true") }, + + { njs_str("Date.prototype[Symbol.toPrimitive].length === 1"), + njs_str("true") }, + + { njs_str("Date.prototype[Symbol.toPrimitive].name"), + njs_str("[Symbol.toPrimitive]") }, + + { njs_str("Date.prototype[Symbol.toPrimitive].call(undefined, 'string')"), + njs_str("TypeError: cannot convert") }, + + { njs_str("Date.prototype[Symbol.toPrimitive].call(null, 'string')"), + njs_str("TypeError: cannot conver") }, + + { njs_str("Date.prototype[Symbol.toPrimitive].call(86, 'string')"), + njs_str("TypeError: cannot convert") }, + + { njs_str("Date.prototype[Symbol.toPrimitive].call('', 'string')"), + njs_str("TypeError: cannot convert") }, + + { njs_str("Date.prototype[Symbol.toPrimitive].call(true, 'string')"), + njs_str("TypeError: cannot convert") }, + /* eval(). */ { njs_str("eval.name"),