From: Vadim Zhestikov Date: Tue, 11 Nov 2025 18:01:01 +0000 (-0800) Subject: Added Symbol.toPrimitive to Date and Symbol prototypes. X-Git-Tag: 0.9.5~28 X-Git-Url: http://www.kaiwu.me/postgresql/commit/?a=commitdiff_plain;h=d4cedc045c8b920e546a7d13300acc04ecec0271;p=njs.git Added Symbol.toPrimitive to Date and Symbol prototypes. --- diff --git a/src/njs_date.c b/src/njs_date.c index 03e866a1..4577d9bd 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 d2b49180..641a1028 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 46634a61..3fffdf86 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 09aa9dfc..7d4c0a39 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 2ffa04e7..61a46c0a 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 80c08b45..ba3814d0 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 19f07db4..fe4e3a31 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 70da4630..626f85cf 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 @@ again: /* "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 c0f1f1d3..0c5e55cc 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"),