} 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;
}
}
+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)
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),
};
} 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);
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),
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[] = {
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;
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;
}
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;
#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,
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;
}
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;
}
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;
}
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;
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;
}
}
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;
}
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;
}
}
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;
}
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;
}
}
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;
}
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;
}
}
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;
}
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;
}
}
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;
}
/* "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;
}
{ 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") },
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") },
{ 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"),