Bug 1054756, part 3 - Implement Symbol.toPrimitive. Replace existing convert hooks with methods. r=jandem.

JSClass::convert is no longer used after this, but to minimize the noise, it will be deleted in a separate patch. However all non-nullptr convert hook implementations must be replaced with [@@toPrimitive] methods in this patch to avoid changing the behavior.

The changes in XrayWrapper.cpp fix a pre-existing bug: when an Xray wrapper tries to emit the "Silently denied access" warning, if id is a symbol, the existing code triggers an error trying to convert it to a string for the warning message. Implementing Symbol.toPrimitive revealed this bug; the fix is straightforward.
This commit is contained in:
Jason Orendorff 2015-03-20 14:02:55 -05:00
parent c2929f3aa3
commit 36289913db
32 changed files with 684 additions and 393 deletions

View File

@ -183,9 +183,6 @@ static bool
NPObjWrapper_Resolve(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
bool *resolvedp);
static bool
NPObjWrapper_Convert(JSContext *cx, JS::Handle<JSObject*> obj, JSType type, JS::MutableHandle<JS::Value> vp);
static void
NPObjWrapper_Finalize(js::FreeOp *fop, JSObject *obj);
@ -198,6 +195,9 @@ NPObjWrapper_Call(JSContext *cx, unsigned argc, JS::Value *vp);
static bool
NPObjWrapper_Construct(JSContext *cx, unsigned argc, JS::Value *vp);
static bool
NPObjWrapper_toPrimitive(JSContext *cx, unsigned argc, JS::Value *vp);
static bool
CreateNPObjectMember(NPP npp, JSContext *cx, JSObject *obj, NPObject* npobj,
JS::Handle<jsid> id, NPVariant* getPropertyResult,
@ -214,7 +214,7 @@ const static js::Class sNPObjectJSWrapperClass =
nullptr,
NPObjWrapper_Resolve,
nullptr, /* mayResolve */
NPObjWrapper_Convert,
nullptr, /* convert */
NPObjWrapper_Finalize,
NPObjWrapper_Call,
nullptr, /* hasInstance */
@ -251,7 +251,8 @@ typedef struct NPObjectMemberPrivate {
} NPObjectMemberPrivate;
static bool
NPObjectMember_Convert(JSContext *cx, JS::Handle<JSObject*> obj, JSType type, JS::MutableHandle<JS::Value> vp);
NPObjectMember_GetProperty(JSContext *cx, JS::HandleObject obj, JS::HandleId id,
JS::MutableHandleValue vp);
static void
NPObjectMember_Finalize(JSFreeOp *fop, JSObject *obj);
@ -262,11 +263,14 @@ NPObjectMember_Call(JSContext *cx, unsigned argc, JS::Value *vp);
static void
NPObjectMember_Trace(JSTracer *trc, JSObject *obj);
static bool
NPObjectMember_toPrimitive(JSContext *cx, unsigned argc, JS::Value *vp);
static const JSClass sNPObjectMemberClass =
{
"NPObject Ambiguous Member class", JSCLASS_HAS_PRIVATE,
nullptr, nullptr, NPObjectMember_GetProperty, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, NPObjectMember_Convert,
NPObjectMember_Finalize, NPObjectMember_Call,
nullptr, nullptr, NPObjectMember_Trace
};
@ -1392,6 +1396,20 @@ NPObjWrapper_GetProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<js
return false;
}
if (JSID_IS_SYMBOL(id)) {
JS::RootedSymbol sym(cx, JSID_TO_SYMBOL(id));
if (JS::GetSymbolCode(sym) == JS::SymbolCode::toPrimitive) {
JS::RootedObject obj(cx, JS_GetFunctionObject(
JS_NewFunction(
cx, NPObjWrapper_toPrimitive, 1, 0,
"Symbol.toPrimitive")));
if (!obj)
return false;
vp.setObject(*obj);
return true;
}
}
// Find out what plugin (NPP) is the owner of the object we're
// manipulating, and make it own any JSObject wrappers created here.
NPP npp = LookupNPP(npobj);
@ -1713,42 +1731,6 @@ NPObjWrapper_Resolve(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid>
return true;
}
static bool
NPObjWrapper_Convert(JSContext *cx, JS::Handle<JSObject*> obj, JSType hint, JS::MutableHandle<JS::Value> vp)
{
MOZ_ASSERT(hint == JSTYPE_NUMBER || hint == JSTYPE_STRING || hint == JSTYPE_VOID);
// Plugins do not simply use the default [[DefaultValue]] behavior, because
// that behavior involves calling toString or valueOf on objects which
// weren't designed to accommodate this. Usually this wouldn't be a problem,
// because the absence of either property, or the presence of either property
// with a value that isn't callable, will cause that property to simply be
// ignored. But there is a problem in one specific case: Java, specifically
// java.lang.Integer. The Integer class has static valueOf methods, none of
// which are nullary, so the JS-reflected method will behave poorly when
// called with no arguments. We work around this problem by giving plugins a
// [[DefaultValue]] which uses only toString and not valueOf.
JS::Rooted<JS::Value> v(cx, JS::UndefinedValue());
if (!JS_GetProperty(cx, obj, "toString", &v))
return false;
if (!v.isPrimitive() && JS::IsCallable(v.toObjectOrNull())) {
if (!JS_CallFunctionValue(cx, obj, v, JS::HandleValueArray::empty(), vp))
return false;
if (vp.isPrimitive())
return true;
}
JS_ReportErrorNumber(cx, js::GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
JS_GetClass(obj)->name,
hint == JSTYPE_VOID
? "primitive type"
: hint == JSTYPE_NUMBER
? "number"
: "string");
return false;
}
static void
NPObjWrapper_Finalize(js::FreeOp *fop, JSObject *obj)
{
@ -1805,6 +1787,43 @@ NPObjWrapper_Construct(JSContext *cx, unsigned argc, JS::Value *vp)
return CallNPMethodInternal(cx, obj, args.length(), args.array(), vp, true);
}
static bool
NPObjWrapper_toPrimitive(JSContext *cx, unsigned argc, JS::Value *vp)
{
// Plugins do not simply use the default OrdinaryToPrimitive behavior,
// because that behavior involves calling toString or valueOf on objects
// which weren't designed to accommodate this. Usually this wouldn't be a
// problem, because the absence of either property, or the presence of either
// property with a value that isn't callable, will cause that property to
// simply be ignored. But there is a problem in one specific case: Java,
// specifically java.lang.Integer. The Integer class has static valueOf
// methods, none of which are nullary, so the JS-reflected method will behave
// poorly when called with no arguments. We work around this problem by
// giving plugins a [Symbol.toPrimitive]() method which uses only toString
// and not valueOf.
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
JS::RootedValue thisv(cx, args.thisv());
if (thisv.isPrimitive())
return true;
JS::RootedObject obj(cx, &thisv.toObject());
JS::RootedValue v(cx);
if (!JS_GetProperty(cx, obj, "toString", &v))
return false;
if (v.isObject() && JS::IsCallable(&v.toObject())) {
if (!JS_CallFunctionValue(cx, obj, v, JS::HandleValueArray::empty(), args.rval()))
return false;
if (args.rval().isPrimitive())
return true;
}
JS_ReportErrorNumber(cx, js::GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
JS_GetClass(obj)->name,
"primitive type");
return false;
}
bool
nsNPObjWrapper::IsWrapper(JSObject *obj)
{
@ -2123,38 +2142,24 @@ CreateNPObjectMember(NPP npp, JSContext *cx, JSObject *obj, NPObject* npobj,
}
static bool
NPObjectMember_Convert(JSContext *cx, JS::Handle<JSObject*> obj, JSType type, JS::MutableHandle<JS::Value> vp)
NPObjectMember_GetProperty(JSContext *cx, JS::HandleObject obj, JS::HandleId id,
JS::MutableHandleValue vp)
{
NPObjectMemberPrivate *memberPrivate =
(NPObjectMemberPrivate *)::JS_GetInstancePrivate(cx, obj,
&sNPObjectMemberClass,
nullptr);
if (!memberPrivate) {
NS_ERROR("no Ambiguous Member Private data!");
return false;
if (JSID_IS_SYMBOL(id)) {
JS::RootedSymbol sym(cx, JSID_TO_SYMBOL(id));
if (JS::GetSymbolCode(sym) == JS::SymbolCode::toPrimitive) {
JS::RootedObject obj(cx, JS_GetFunctionObject(
JS_NewFunction(
cx, NPObjectMember_toPrimitive, 1, 0,
"Symbol.toPrimitive")));
if (!obj)
return false;
vp.setObject(*obj);
return true;
}
}
switch (type) {
case JSTYPE_VOID:
case JSTYPE_STRING:
case JSTYPE_NUMBER:
vp.set(memberPrivate->fieldValue);
if (vp.isObject()) {
JS::Rooted<JSObject*> objVal(cx, &vp.toObject());
return JS_DefaultValue(cx, objVal, type, vp);
}
return true;
case JSTYPE_BOOLEAN:
case JSTYPE_OBJECT:
vp.set(memberPrivate->fieldValue);
return true;
case JSTYPE_FUNCTION:
// Leave this to NPObjectMember_Call.
return true;
default:
NS_ERROR("illegal operation on JSObject prototype object");
return false;
}
return true;
}
static void
@ -2275,6 +2280,36 @@ NPObjectMember_Trace(JSTracer *trc, JSObject *obj)
}
}
static bool
NPObjectMember_toPrimitive(JSContext *cx, unsigned argc, JS::Value *vp)
{
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
JS::RootedValue thisv(cx, args.thisv());
if (thisv.isPrimitive()) {
args.rval().set(thisv);
return true;
}
JS::RootedObject obj(cx, &thisv.toObject());
NPObjectMemberPrivate *memberPrivate =
(NPObjectMemberPrivate *)::JS_GetInstancePrivate(cx, obj,
&sNPObjectMemberClass,
&args);
if (!memberPrivate)
return false;
JSType type;
if (!JS::GetFirstArgumentAsTypeHint(cx, args, &type))
return false;
args.rval().set(memberPrivate->fieldValue);
if (args.rval().isObject()) {
JS::Rooted<JSObject*> objVal(cx, &args.rval().toObject());
return JS_DefaultValue(cx, objVal, type, args.rval());
}
return true;
}
// static
bool
nsJSObjWrapper::HasOwnProperty(NPObject *npobj, NPIdentifier npid)

View File

@ -740,18 +740,6 @@ workerdebuggersandbox_resolve(JSContext *cx, JS::Handle<JSObject *> obj,
return JS_ResolveStandardClass(cx, obj, id, resolvedp);
}
static bool
workerdebuggersandbox_convert(JSContext *cx, JS::Handle<JSObject *> obj,
JSType type, JS::MutableHandle<JS::Value> vp)
{
if (type == JSTYPE_OBJECT) {
vp.setObject(*obj);
return true;
}
return JS::OrdinaryToPrimitive(cx, obj, type, vp);
}
static void
workerdebuggersandbox_finalize(js::FreeOp *fop, JSObject *obj)
{
@ -775,7 +763,7 @@ const js::Class workerdebuggersandbox_class = {
workerdebuggersandbox_enumerate,
workerdebuggersandbox_resolve,
nullptr, /* mayResolve */
workerdebuggersandbox_convert,
nullptr, /* convert */
workerdebuggersandbox_finalize,
nullptr,
nullptr,

View File

@ -18,15 +18,7 @@ using namespace js;
const Class SymbolObject::class_ = {
"Symbol",
JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) | JSCLASS_HAS_CACHED_PROTO(JSProto_Symbol),
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* getProperty */
nullptr, /* setProperty */
nullptr, /* enumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
convert
JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) | JSCLASS_HAS_CACHED_PROTO(JSProto_Symbol)
};
SymbolObject*
@ -47,6 +39,7 @@ const JSPropertySpec SymbolObject::properties[] = {
const JSFunctionSpec SymbolObject::methods[] = {
JS_FN(js_toString_str, toString, 0, 0),
JS_FN(js_valueOf_str, valueOf, 0, 0),
JS_SYM_FN(toPrimitive, toPrimitive, 1, JSPROP_READONLY),
JS_FS_END
};
@ -124,14 +117,6 @@ SymbolObject::construct(JSContext* cx, unsigned argc, Value* vp)
return true;
}
// Stand-in for Symbol.prototype[@@toPrimitive], ES6 rev 26 (2014 Jul 18) 19.4.3.4
bool
SymbolObject::convert(JSContext* cx, HandleObject obj, JSType hint, MutableHandleValue vp)
{
vp.setSymbol(obj->as<SymbolObject>().unbox());
return true;
}
// ES6 rev 24 (2014 Apr 27) 19.4.2.2
bool
SymbolObject::for_(JSContext* cx, unsigned argc, Value* vp)
@ -230,6 +215,17 @@ SymbolObject::valueOf(JSContext* cx, unsigned argc, Value* vp)
return CallNonGenericMethod<IsSymbol, valueOf_impl>(cx, args);
}
// ES6 19.4.3.4
bool
SymbolObject::toPrimitive(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
// The specification gives exactly the same algorithm for @@toPrimitive as
// for valueOf, so reuse the valueOf implementation.
return CallNonGenericMethod<IsSymbol, valueOf_impl>(cx, args);
}
JSObject*
js::InitSymbolClass(JSContext* cx, HandleObject obj)
{

View File

@ -41,8 +41,6 @@ class SymbolObject : public NativeObject
static bool construct(JSContext* cx, unsigned argc, Value* vp);
static bool convert(JSContext* cx, HandleObject obj, JSType type, MutableHandleValue vp);
// Static methods.
static bool for_(JSContext* cx, unsigned argc, Value* vp);
static bool keyFor(JSContext* cx, unsigned argc, Value* vp);
@ -52,6 +50,7 @@ class SymbolObject : public NativeObject
static bool toString(JSContext* cx, unsigned argc, Value* vp);
static bool valueOf_impl(JSContext* cx, const CallArgs& args);
static bool valueOf(JSContext* cx, unsigned argc, Value* vp);
static bool toPrimitive(JSContext* cx, unsigned argc, Value* vp);
static const JSPropertySpec properties[];
static const JSFunctionSpec methods[];

View File

@ -5224,6 +5224,8 @@ ArrayType::Getter(JSContext* cx, HandleObject obj, HandleId idval, MutableHandle
size_t length = GetLength(typeObj);
bool ok = jsidToSize(cx, idval, true, &index);
int32_t dummy;
if (!ok && JSID_IS_SYMBOL(idval))
return true;
if (!ok && JSID_IS_STRING(idval) &&
!StringToInteger(cx, JSID_TO_STRING(idval), &dummy)) {
// String either isn't a number, or doesn't fit in size_t.
@ -5262,6 +5264,8 @@ ArrayType::Setter(JSContext* cx, HandleObject obj, HandleId idval, MutableHandle
size_t length = GetLength(typeObj);
bool ok = jsidToSize(cx, idval, true, &index);
int32_t dummy;
if (!ok && JSID_IS_SYMBOL(idval))
return true;
if (!ok && JSID_IS_STRING(idval) &&
!StringToInteger(cx, JSID_TO_STRING(idval), &dummy)) {
// String either isn't a number, or doesn't fit in size_t.

View File

@ -54,6 +54,8 @@ MSG_DEF(JSMSG_CANT_TRUNCATE_ARRAY, 0, JSEXN_TYPEERR, "can't delete non-confi
MSG_DEF(JSMSG_NOT_FUNCTION, 1, JSEXN_TYPEERR, "{0} is not a function")
MSG_DEF(JSMSG_NOT_CONSTRUCTOR, 1, JSEXN_TYPEERR, "{0} is not a constructor")
MSG_DEF(JSMSG_CANT_CONVERT_TO, 2, JSEXN_TYPEERR, "can't convert {0} to {1}")
MSG_DEF(JSMSG_TOPRIMITIVE_NOT_CALLABLE, 2, JSEXN_TYPEERR, "can't convert {0} to {1}: its [Symbol.toPrimitive] property is not a function")
MSG_DEF(JSMSG_TOPRIMITIVE_RETURNED_OBJECT, 2, JSEXN_TYPEERR, "can't convert {0} to {1}: its [Symbol.toPrimitive] method returned an object")
MSG_DEF(JSMSG_NO_PROPERTIES, 1, JSEXN_TYPEERR, "{0} has no properties")
MSG_DEF(JSMSG_BAD_REGEXP_FLAG, 1, JSEXN_SYNTAXERR, "invalid regular expression flag {0}")
MSG_DEF(JSMSG_ARG_INDEX_OUT_OF_RANGE, 1, JSEXN_RANGEERR, "argument {0} accesses an index that is out of range")

View File

@ -62,7 +62,6 @@ UNIFIED_SOURCES += [
'testNullRoot.cpp',
'testObjectEmulatingUndefined.cpp',
'testOOM.cpp',
'testOps.cpp',
'testParseJSON.cpp',
'testPersistentRooted.cpp',
'testPreserveJitCode.cpp',

View File

@ -1,65 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
*
* Tests for operators and implicit type conversion.
*/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "jsapi-tests/tests.h"
static bool
my_convert(JSContext* context, JS::HandleObject obj, JSType type, JS::MutableHandleValue rval)
{
if (type == JSTYPE_VOID || type == JSTYPE_STRING || type == JSTYPE_NUMBER || type == JSTYPE_BOOLEAN) {
rval.set(JS_NumberValue(123));
return true;
}
return false;
}
static const JSClass myClass = {
"MyClass",
0,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, my_convert
};
static bool
createMyObject(JSContext* context, unsigned argc, JS::Value* vp)
{
JS_BeginRequest(context);
//JS_GC(context); //<- if we make GC here, all is ok
JSObject* myObject = JS_NewObject(context, &myClass);
*vp = JS::ObjectOrNullValue(myObject);
JS_EndRequest(context);
return true;
}
static const JSFunctionSpec s_functions[] =
{
JS_FN("createMyObject", createMyObject, 0, 0),
JS_FS_END
};
BEGIN_TEST(testOps_bug559006)
{
CHECK(JS_DefineFunctions(cx, global, s_functions));
EXEC("function main() { while(1) return 0 + createMyObject(); }");
for (int i = 0; i < 9; i++) {
JS::RootedValue rval(cx);
CHECK(JS_CallFunctionName(cx, global, "main", JS::HandleValueArray::empty(),
&rval));
CHECK(rval.isInt32(123));
}
return true;
}
END_TEST(testOps_bug559006)

View File

@ -11,6 +11,7 @@
using JS::RootedObject;
using JS::RootedScript;
using JS::RootedString;
using namespace js;
// A helper JS::ubi::Node concrete implementation that can be used to make mock
// graphs for testing traversals with.

View File

@ -1808,7 +1808,51 @@ JS_DefaultValue(JSContext* cx, HandleObject obj, JSType hint, MutableHandleValue
CHECK_REQUEST(cx);
MOZ_ASSERT(obj != nullptr);
MOZ_ASSERT(hint == JSTYPE_VOID || hint == JSTYPE_STRING || hint == JSTYPE_NUMBER);
return ToPrimitive(cx, obj, hint, vp);
vp.setObject(*obj);
return ToPrimitiveSlow(cx, hint, vp);
}
JS_PUBLIC_API(bool)
JS::GetFirstArgumentAsTypeHint(JSContext* cx, CallArgs args, JSType *result)
{
if (!args.get(0).isString()) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE,
"Symbol.toPrimitive",
"\"string\", \"number\", or \"default\"",
InformalValueTypeName(args.get(0)));
return false;
}
RootedString str(cx, args.get(0).toString());
bool match;
if (!EqualStrings(cx, str, cx->names().default_, &match))
return false;
if (match) {
*result = JSTYPE_VOID;
return true;
}
if (!EqualStrings(cx, str, cx->names().string, &match))
return false;
if (match) {
*result = JSTYPE_STRING;
return true;
}
if (!EqualStrings(cx, str, cx->names().number, &match))
return false;
if (match) {
*result = JSTYPE_NUMBER;
return true;
}
JSAutoByteString bytes;
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE,
"Symbol.toPrimitive",
"\"string\", \"number\", or \"default\"",
ValueToSourceForError(cx, args.get(0), bytes));
return false;
}
JS_PUBLIC_API(bool)

View File

@ -1898,15 +1898,32 @@ JS_StringToId(JSContext* cx, JS::HandleString s, JS::MutableHandleId idp);
extern JS_PUBLIC_API(bool)
JS_IdToValue(JSContext* cx, jsid id, JS::MutableHandle<JS::Value> vp);
/*
* Invoke the [[DefaultValue]] hook (see ES5 8.6.2) with the provided hint on
* the specified object, computing a primitive default value for the object.
* The hint must be JSTYPE_STRING, JSTYPE_NUMBER, or JSTYPE_VOID (no hint). On
* success the resulting value is stored in *vp.
/**
* Convert obj to a primitive value. On success, store the result in vp and
* return true.
*
* The hint argument must be JSTYPE_STRING, JSTYPE_NUMBER, or JSTYPE_VOID (no
* hint).
*
* Implements: ES6 7.1.1 ToPrimitive(input, [PreferredType]).
*/
extern JS_PUBLIC_API(bool)
JS_DefaultValue(JSContext* cx, JS::Handle<JSObject*> obj, JSType hint,
JS::MutableHandle<JS::Value> vp);
JS_DefaultValue(JSContext* cx, JS::HandleObject obj, JSType hint,
JS::MutableHandleValue vp);
namespace JS {
/**
* If args.get(0) is one of the strings "string", "number", or "default", set
* *result to JSTYPE_STRING, JSTYPE_NUMBER, or JSTYPE_VOID accordingly and
* return true. Otherwise, return false with a TypeError pending.
*
* This can be useful in implementing a @@toPrimitive method.
*/
extern JS_PUBLIC_API(bool)
GetFirstArgumentAsTypeHint(JSContext* cx, CallArgs args, JSType *result);
} /* namespace JS */
extern JS_PUBLIC_API(bool)
JS_PropertyStub(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
@ -2113,7 +2130,7 @@ struct JSFunctionSpec {
JS_FNSPEC(name, call, nullptr, nargs, (flags) | JSFUN_STUB_GSOPS, nullptr)
#define JS_INLINABLE_FN(name,call,nargs,flags,native) \
JS_FNSPEC(name, call, &js::jit::JitInfo_##native, nargs, (flags) | JSFUN_STUB_GSOPS, nullptr)
#define JS_SYM_FN(name,call,nargs,flags) \
#define JS_SYM_FN(symbol,call,nargs,flags) \
JS_SYM_FNSPEC(symbol, call, nullptr, nargs, (flags) | JSFUN_STUB_GSOPS, nullptr)
#define JS_FNINFO(name,call,info,nargs,flags) \
JS_FNSPEC(name, call, info, nargs, flags, nullptr)
@ -4384,15 +4401,16 @@ GetSymbolDescription(HandleSymbol symbol);
/* Well-known symbols. */
enum class SymbolCode : uint32_t {
iterator, // Symbol.iterator
match, // Symbol.match
species, // Symbol.species
iterator, // well-known symbols
match,
species,
toPrimitive,
InSymbolRegistry = 0xfffffffe, // created by Symbol.for() or JS::GetSymbolFor()
UniqueSymbol = 0xffffffff // created by Symbol() or JS::NewSymbol()
};
/* For use in loops that iterate over the well-known symbols. */
const size_t WellKnownSymbolLimit = 3;
const size_t WellKnownSymbolLimit = 4;
/*
* Return the SymbolCode telling what sort of symbol `symbol` is.

View File

@ -518,15 +518,6 @@ MakeTime(double hour, double min, double sec, double ms)
* end of ECMA 'support' functions
*/
static bool
date_convert(JSContext* cx, HandleObject obj, JSType hint, MutableHandleValue vp)
{
MOZ_ASSERT(hint == JSTYPE_NUMBER || hint == JSTYPE_STRING || hint == JSTYPE_VOID);
MOZ_ASSERT(obj->is<DateObject>());
return JS::OrdinaryToPrimitive(cx, obj, hint == JSTYPE_VOID ? JSTYPE_STRING : hint, vp);
}
/* for use by date_parse */
static const char* const wtb[] = {
@ -2912,6 +2903,30 @@ js::date_valueOf(JSContext* cx, unsigned argc, Value* vp)
return CallNonGenericMethod<IsDate, date_valueOf_impl>(cx, args);
}
// ES6 20.3.4.45 Date.prototype[@@toPrimitive]
static bool
date_toPrimitive(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
// Steps 1-2.
if (!args.thisv().isObject()) {
ReportIncompatible(cx, args);
return false;
}
// Steps 3-5.
JSType hint;
if (!GetFirstArgumentAsTypeHint(cx, args, &hint))
return false;
if (hint == JSTYPE_VOID)
hint = JSTYPE_STRING;
args.rval().set(args.thisv());
RootedObject obj(cx, &args.thisv().toObject());
return OrdinaryToPrimitive(cx, obj, hint, args.rval());
}
static const JSFunctionSpec date_static_methods[] = {
JS_FN("UTC", date_UTC, 7,0),
JS_FN("parse", date_parse, 1,0),
@ -2975,6 +2990,7 @@ static const JSFunctionSpec date_methods[] = {
#endif
JS_FN(js_toString_str, date_toString, 0,0),
JS_FN(js_valueOf_str, date_valueOf, 0,0),
JS_SYM_FN(toPrimitive, date_toPrimitive, 1,JSPROP_READONLY),
JS_FS_END
};
@ -3166,7 +3182,7 @@ const Class DateObject::class_ = {
nullptr, /* enumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
date_convert,
nullptr, /* convert */
nullptr, /* finalize */
nullptr, /* call */
nullptr, /* hasInstance */

View File

@ -332,7 +332,7 @@ namespace js {
nullptr, /* enumerate */ \
nullptr, /* resolve */ \
nullptr, /* mayResolve */ \
js::proxy_Convert, \
nullptr, /* convert */ \
js::proxy_Finalize, /* finalize */ \
nullptr, /* call */ \
js::proxy_HasInstance, /* hasInstance */ \

View File

@ -2860,13 +2860,16 @@ js::HasDataProperty(JSContext* cx, NativeObject* obj, jsid id, Value* vp)
return false;
}
/*** ToPrimitive *************************************************************/
/*
* Gets |obj[id]|. If that value's not callable, returns true and stores a
* non-primitive value in *vp. If it's callable, calls it with no arguments
* and |obj| as |this|, returning the result in *vp.
* Gets |obj[id]|. If that value's not callable, returns true and stores an
* object value in *vp. If it's callable, calls it with no arguments and |obj|
* as |this|, returning the result in *vp.
*
* This is a mini-abstraction for ES5 8.12.8 [[DefaultValue]], either steps 1-2
* or steps 3-4.
* This is a mini-abstraction for ES6 draft rev 36 (2015 Mar 17),
* 7.1.1, second algorithm (OrdinaryToPrimitive), steps 5.a-c.
*/
static bool
MaybeCallMethod(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp)
@ -2880,6 +2883,29 @@ MaybeCallMethod(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue
return Invoke(cx, ObjectValue(*obj), vp, 0, nullptr, vp);
}
static bool
ReportCantConvert(JSContext* cx, unsigned errorNumber, HandleObject obj, JSType hint)
{
const Class* clasp = obj->getClass();
// Avoid recursive death when decompiling in ReportValueError.
RootedString str(cx);
if (hint == JSTYPE_STRING) {
str = JS_AtomizeAndPinString(cx, clasp->name);
if (!str)
return false;
} else {
str = nullptr;
}
RootedValue val(cx, ObjectValue(*obj));
ReportValueError2(cx, errorNumber, JSDVG_SEARCH_STACK, val, str,
hint == JSTYPE_VOID
? "primitive type"
: hint == JSTYPE_STRING ? "string" : "number");
return false;
}
bool
JS::OrdinaryToPrimitive(JSContext* cx, HandleObject obj, JSType hint, MutableHandleValue vp)
{
@ -2911,10 +2937,10 @@ JS::OrdinaryToPrimitive(JSContext* cx, HandleObject obj, JSType hint, MutableHan
if (vp.isPrimitive())
return true;
} else {
id = NameToId(cx->names().valueOf);
/* Optimize new String(...).valueOf(). */
if (clasp == &StringObject::class_) {
id = NameToId(cx->names().valueOf);
StringObject* nobj = &obj->as<StringObject>();
if (ClassMethodIsNative(cx, nobj, &StringObject::class_, id, str_toString)) {
vp.setString(nobj->unbox());
@ -2924,7 +2950,6 @@ JS::OrdinaryToPrimitive(JSContext* cx, HandleObject obj, JSType hint, MutableHan
/* Optimize new Number(...).valueOf(). */
if (clasp == &NumberObject::class_) {
id = NameToId(cx->names().valueOf);
NumberObject* nobj = &obj->as<NumberObject>();
if (ClassMethodIsNative(cx, nobj, &NumberObject::class_, id, num_valueOf)) {
vp.setNumber(nobj->unbox());
@ -2932,7 +2957,6 @@ JS::OrdinaryToPrimitive(JSContext* cx, HandleObject obj, JSType hint, MutableHan
}
}
id = NameToId(cx->names().valueOf);
if (!MaybeCallMethod(cx, obj, id, vp))
return false;
if (vp.isPrimitive())
@ -2945,64 +2969,52 @@ JS::OrdinaryToPrimitive(JSContext* cx, HandleObject obj, JSType hint, MutableHan
return true;
}
/* Avoid recursive death when decompiling in ReportValueError. */
RootedString str(cx);
if (hint == JSTYPE_STRING) {
str = JS_AtomizeAndPinString(cx, clasp->name);
if (!str)
return ReportCantConvert(cx, JSMSG_CANT_CONVERT_TO, obj, hint);
}
bool
js::ToPrimitiveSlow(JSContext* cx, JSType preferredType, MutableHandleValue vp)
{
// Step numbers refer to the first algorithm listed in ES6 draft rev 36
// (2015 Mar 17) 7.1.1 ToPrimitive.
MOZ_ASSERT(preferredType == JSTYPE_VOID ||
preferredType == JSTYPE_STRING ||
preferredType == JSTYPE_NUMBER);
RootedObject obj(cx, &vp.toObject());
// Steps 4-5.
RootedId id(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().toPrimitive));
RootedValue method(cx);
if (!GetProperty(cx, obj, obj, id, &method))
return false;
// Step 6.
if (!method.isUndefined()) {
// Step 6 of GetMethod. Invoke() below would do this check and throw a
// TypeError anyway, but this produces a better error message.
if (!IsCallable(method))
return ReportCantConvert(cx, JSMSG_TOPRIMITIVE_NOT_CALLABLE, obj, preferredType);
// Steps 1-3.
RootedValue hint(cx, StringValue(preferredType == JSTYPE_STRING ? cx->names().string :
preferredType == JSTYPE_NUMBER ? cx->names().number :
cx->names().default_));
// Steps 6.a-b.
if (!Invoke(cx, vp, method, 1, hint.address(), vp))
return false;
} else {
str = nullptr;
// Steps 6.c-d.
if (vp.isObject())
return ReportCantConvert(cx, JSMSG_TOPRIMITIVE_RETURNED_OBJECT, obj, preferredType);
return true;
}
RootedValue val(cx, ObjectValue(*obj));
ReportValueError2(cx, JSMSG_CANT_CONVERT_TO, JSDVG_SEARCH_STACK, val, str,
hint == JSTYPE_VOID
? "primitive type"
: hint == JSTYPE_STRING ? "string" : "number");
return false;
return OrdinaryToPrimitive(cx, obj, preferredType, vp);
}
bool
js::ToPrimitive(JSContext* cx, HandleObject obj, JSType hint, MutableHandleValue vp)
{
bool ok;
if (JSConvertOp op = obj->getClass()->convert)
ok = op(cx, obj, hint, vp);
else
ok = JS::OrdinaryToPrimitive(cx, obj, hint, vp);
MOZ_ASSERT_IF(ok, vp.isPrimitive());
return ok;
}
bool
js::ToPrimitiveSlow(JSContext* cx, MutableHandleValue vp)
{
JSObject* obj = &vp.toObject();
/* Optimize new String(...).valueOf(). */
if (obj->is<StringObject>()) {
jsid id = NameToId(cx->names().valueOf);
StringObject* nobj = &obj->as<StringObject>();
if (ClassMethodIsNative(cx, nobj, &StringObject::class_, id, str_toString)) {
vp.setString(nobj->unbox());
return true;
}
}
/* Optimize new Number(...).valueOf(). */
if (obj->is<NumberObject>()) {
jsid id = NameToId(cx->names().valueOf);
NumberObject* nobj = &obj->as<NumberObject>();
if (ClassMethodIsNative(cx, nobj, &NumberObject::class_, id, num_valueOf)) {
vp.setNumber(nobj->unbox());
return true;
}
}
RootedObject objRoot(cx, obj);
return ToPrimitive(cx, objRoot, JSTYPE_VOID, vp);
}
/* * */
bool
js::IsDelegate(JSContext* cx, HandleObject obj, const js::Value& v, bool* result)

View File

@ -1008,29 +1008,24 @@ WatchProperty(JSContext* cx, HandleObject obj, HandleId id, HandleObject callabl
extern bool
UnwatchProperty(JSContext* cx, HandleObject obj, HandleId id);
/* ES6 draft rev 36 (2015 March 17) 7.1.1 ToPrimitive(vp, preferredType) */
/* ES6 draft rev 36 (2015 March 17) 7.1.1 ToPrimitive(vp[, preferredType]) */
extern bool
ToPrimitiveSlow(JSContext* cx, MutableHandleValue vp);
ToPrimitiveSlow(JSContext* cx, JSType hint, MutableHandleValue vp);
inline bool
ToPrimitive(JSContext* cx, MutableHandleValue vp)
{
if (vp.isPrimitive())
return true;
return ToPrimitiveSlow(cx, vp);
return ToPrimitiveSlow(cx, JSTYPE_VOID, vp);
}
extern bool
ToPrimitive(JSContext* cx, HandleObject obj, JSType hint, MutableHandleValue vp);
inline bool
ToPrimitive(JSContext* cx, JSType preferredType, MutableHandleValue vp)
{
MOZ_ASSERT(preferredType != JSTYPE_VOID); // Use the other ToPrimitive!
if (vp.isPrimitive())
return true;
RootedObject obj(cx, &vp.toObject());
return ToPrimitive(cx, obj, preferredType, vp);
return ToPrimitiveSlow(cx, preferredType, vp);
}
/*

View File

@ -513,13 +513,6 @@ Proxy::boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp
return proxy->as<ProxyObject>().handler()->boxedValue_unbox(cx, proxy, vp);
}
bool
Proxy::defaultValue(JSContext* cx, HandleObject proxy, JSType hint, MutableHandleValue vp)
{
JS_CHECK_RECURSION(cx, return false);
return proxy->as<ProxyObject>().handler()->defaultValue(cx, proxy, hint, vp);
}
JSObject * const TaggedProto::LazyProto = reinterpret_cast<JSObject*>(0x1);
/* static */ bool
@ -680,13 +673,6 @@ js::proxy_WeakmapKeyDelegate(JSObject* obj)
return obj->as<ProxyObject>().handler()->weakmapKeyDelegate(obj);
}
bool
js::proxy_Convert(JSContext* cx, HandleObject proxy, JSType hint, MutableHandleValue vp)
{
MOZ_ASSERT(proxy->is<ProxyObject>());
return Proxy::defaultValue(cx, proxy, hint, vp);
}
void
js::proxy_Finalize(FreeOp* fop, JSObject* obj)
{

View File

@ -0,0 +1,62 @@
// ES6 20.3.4.45 Date.prototype[@@toPrimitive](hint)
// The toPrimitive method throws if the this value isn't an object.
var toPrimitive = Date.prototype[Symbol.toPrimitive];
assertThrowsInstanceOf(() => toPrimitive.call(undefined, "default"), TypeError);
assertThrowsInstanceOf(() => toPrimitive.call(3, "default"), TypeError);
// It doesn't have to be a Date object, though.
var obj = {
toString() { return "str"; },
valueOf() { return "val"; }
};
assertEq(toPrimitive.call(obj, "number"), "val");
assertEq(toPrimitive.call(obj, "string"), "str");
assertEq(toPrimitive.call(obj, "default"), "str");
// It throws if the hint argument is missing or not one of the three allowed values.
assertThrowsInstanceOf(() => toPrimitive.call(obj), TypeError);
assertThrowsInstanceOf(() => toPrimitive.call(obj, undefined), TypeError);
assertThrowsInstanceOf(() => toPrimitive.call(obj, "boolean"), TypeError);
assertThrowsInstanceOf(() => toPrimitive.call(obj, ["number"]), TypeError);
assertThrowsInstanceOf(() => toPrimitive.call(obj, {toString() { throw "FAIL"; }}), TypeError);
// The next few tests cover the OrdinaryToPrimitive algorithm, specified in
// ES6 7.1.1 ToPrimitive(input [, PreferredType]).
// Date.prototype.toString or .valueOf can be overridden.
var dateobj = new Date();
Date.prototype.toString = function () {
assertEq(this, dateobj);
return 14;
};
Date.prototype.valueOf = function () {
return "92";
};
assertEq(dateobj[Symbol.toPrimitive]("number"), "92");
assertEq(dateobj[Symbol.toPrimitive]("string"), 14);
assertEq(dateobj[Symbol.toPrimitive]("default"), 14);
assertEq(dateobj == 14, true); // equality comparison: passes "default"
// If this.toString is a non-callable value, this.valueOf is called instead.
Date.prototype.toString = {};
assertEq(dateobj[Symbol.toPrimitive]("string"), "92");
assertEq(dateobj[Symbol.toPrimitive]("default"), "92");
// And vice versa.
Date.prototype.toString = function () { return 15; };
Date.prototype.valueOf = "ponies";
assertEq(dateobj[Symbol.toPrimitive]("number"), 15);
// If neither is callable, it throws a TypeError.
Date.prototype.toString = "ponies";
assertThrowsInstanceOf(() => dateobj[Symbol.toPrimitive]("default"), TypeError);
// Surface features.
assertEq(toPrimitive.name, "[Symbol.toPrimitive]");
var desc = Object.getOwnPropertyDescriptor(Date.prototype, Symbol.toPrimitive);
assertEq(desc.configurable, true);
assertEq(desc.enumerable, false);
assertEq(desc.writable, false);
reportCompare(0, 0);

View File

@ -0,0 +1,57 @@
// Check all the algorithms that call ToPrimitive. Confirm that they're passing
// the correct hint, per spec.
var STRING = "xyzzy";
var NUMBER = 42;
function assertCallsToPrimitive(f, expectedHint, expectedResult) {
var hint = undefined;
var testObj = {
[Symbol.toPrimitive](h) {
assertEq(hint, undefined);
hint = h;
return h === "number" ? NUMBER : STRING;
}
};
var result = f(testObj);
assertEq(hint, expectedHint, String(f));
assertEq(result, expectedResult, String(f));
}
// ToNumber
assertCallsToPrimitive(Number, "number", NUMBER);
// ToString
assertCallsToPrimitive(String, "string", STRING);
// ToPropertyKey
var obj = {[STRING]: "pass"};
assertCallsToPrimitive(key => obj[key], "string", "pass");
// Abstract Relational Comparison
assertCallsToPrimitive(x => x >= 42, "number", true);
assertCallsToPrimitive(x => x > "42", "number", false);
// Abstract Equality Comparison
assertCallsToPrimitive(x => x != STRING, "default", false);
assertCallsToPrimitive(x => STRING == x, "default", true);
assertCallsToPrimitive(x => x == NUMBER, "default", false);
assertCallsToPrimitive(x => NUMBER != x, "default", true);
// Addition
assertCallsToPrimitive(x => 1 + x, "default", "1" + STRING);
assertCallsToPrimitive(x => "" + x, "default", STRING);
// Date constructor
assertCallsToPrimitive(x => (new Date(x)).valueOf(), "default", Number(STRING));
// Date.prototype.toJSON
var expected = "a suffusion of yellow";
function testJSON(x) {
x.toJSON = Date.prototype.toJSON;
x.toISOString = function () { return expected; };
return JSON.stringify(x);
}
assertCallsToPrimitive(testJSON, "number", JSON.stringify(expected));
reportCompare(0, 0);

View File

@ -0,0 +1,101 @@
// ES6 7.1.1 ToPrimitive(input [, PreferredType]) specifies a new extension
// point in the language. Objects can override the behavior of ToPrimitive
// somewhat by supporting the method obj[@@toPrimitive](hint).
//
// (Rationale: ES5 had a [[DefaultValue]] internal method, overridden only by
// Date objects. The change in ES6 is to make [[DefaultValue]] a plain old
// method. This allowed ES6 to eliminate the [[DefaultValue]] internal method,
// simplifying the meta-object protocol and thus proxies.)
// obj[Symbol.toPrimitive]() is called whenever the ToPrimitive algorithm is invoked.
var expectedThis, expectedHint;
var obj = {
[Symbol.toPrimitive](hint, ...rest) {
assertEq(this, expectedThis);
assertEq(hint, expectedHint);
assertEq(rest.length, 0);
return 2015;
}
};
expectedThis = obj;
expectedHint = "string";
assertEq(String(obj), "2015");
expectedHint = "number";
assertEq(Number(obj), 2015);
// It is called even through proxies.
var proxy = new Proxy(obj, {});
expectedThis = proxy;
expectedHint = "default";
assertEq("ES" + proxy, "ES2015");
// It is called even through additional proxies and the prototype chain.
proxy = new Proxy(Object.create(proxy), {});
expectedThis = proxy;
expectedHint = "default";
assertEq("ES" + (proxy + 1), "ES2016");
// It is not called if the operand is already a primitive.
var ok = true;
for (var constructor of [Boolean, Number, String, Symbol]) {
constructor.prototype[Symbol.toPrimitive] = function () {
ok = false;
throw "FAIL";
};
}
assertEq(Number(true), 1);
assertEq(Number(77.7), 77.7);
assertEq(Number("123"), 123);
assertThrowsInstanceOf(() => Number(Symbol.iterator), TypeError);
assertEq(String(true), "true");
assertEq(String(77.7), "77.7");
assertEq(String("123"), "123");
assertEq(String(Symbol.iterator), "Symbol(Symbol.iterator)");
assertEq(ok, true);
// Converting a primitive symbol to another primitive type throws even if you
// delete the @@toPrimitive method from Symbol.prototype.
delete Symbol.prototype[Symbol.toPrimitive];
var sym = Symbol("ok");
assertThrowsInstanceOf(() => `${sym}`, TypeError);
assertThrowsInstanceOf(() => Number(sym), TypeError);
assertThrowsInstanceOf(() => "" + sym, TypeError);
// However, having deleted that method, converting a Symbol wrapper object does
// work: it calls Symbol.prototype.toString().
obj = Object(sym);
assertEq(String(obj), "Symbol(ok)");
assertEq(`${obj}`, "Symbol(ok)");
// Deleting valueOf as well makes numeric conversion also call toString().
delete Symbol.prototype.valueOf;
delete Object.prototype.valueOf;
assertEq(Number(obj), NaN);
Symbol.prototype.toString = function () { return "2060"; };
assertEq(Number(obj), 2060);
// Deleting Date.prototype[Symbol.toPrimitive] changes the result of addition
// involving Date objects.
var d = new Date;
assertEq(0 + d, 0 + d.toString());
delete Date.prototype[Symbol.toPrimitive];
assertEq(0 + d, 0 + d.valueOf());
// If @@toPrimitive, .toString, and .valueOf are all missing, we get a
// particular sequence of property accesses, followed by a TypeError exception.
var log = [];
function doGet(target, propertyName, receiver) {
log.push(propertyName);
}
var handler = new Proxy({}, {
get(target, trapName, receiver) {
if (trapName !== "get")
throw `FAIL: system tried to access handler method: ${uneval(trapName)}`;
return doGet;
}
});
proxy = new Proxy(Object.create(null), handler);
assertThrowsInstanceOf(() => proxy == 0, TypeError);
assertDeepEq(log, [Symbol.toPrimitive, "valueOf", "toString"]);
reportCompare(0, 0);

View File

@ -38,14 +38,15 @@ var keys = [
valueOf() { return "fallback"; }
},
expected: "fallback"
},
{
value: {
[Symbol.toPrimitive](hint) { return hint; }
},
expected: "string"
}
];
if ("toPrimitive" in Symbol) {
throw new Error("Congratulations on implementing Symbol.toPrimitive! " +
"Please add an object with an @@toPrimitive method in the list above.");
}
for (var {value, expected} of keys) {
if (expected === undefined)
expected = value;

View File

@ -7,27 +7,7 @@ var symbols = [
Symbol.iterator
];
if (Symbol.toPrimitive in Symbol.prototype) {
// We should test that deleting Symbol.prototype[@@toPrimitive] changes the
// behavior of ToPrimitive on Symbol objects, but @@toPrimitive is not
// implemented yet.
throw new Error("Congratulations on implementing @@toPrimitive! Please update this test.");
}
for (var sym of symbols) {
// 7.1.1 ToPrimitive
var symobj = Object(sym);
assertThrowsInstanceOf(() => Number(symobj), TypeError);
assertThrowsInstanceOf(() => String(symobj), TypeError);
assertThrowsInstanceOf(() => symobj < 0, TypeError);
assertThrowsInstanceOf(() => 0 < symobj, TypeError);
assertThrowsInstanceOf(() => symobj + 1, TypeError);
assertThrowsInstanceOf(() => "" + symobj, TypeError);
assertEq(sym == symobj, true);
assertEq(sym === symobj, false);
assertEq(symobj == 0, false);
assertEq(0 != symobj, true);
function testSymbolConversions(sym) {
// 7.1.2 ToBoolean
assertEq(Boolean(sym), true);
assertEq(!sym, false);
@ -41,12 +21,11 @@ for (var sym of symbols) {
// 7.1.12 ToString
assertThrowsInstanceOf(() => "" + sym, TypeError);
assertThrowsInstanceOf(() => sym + "", TypeError);
assertThrowsInstanceOf(() => "" + [1, 2, Symbol()], TypeError);
assertThrowsInstanceOf(() => ["simple", "thimble", Symbol()].join(), TypeError);
assertThrowsInstanceOf(() => "" + [1, 2, sym], TypeError);
assertThrowsInstanceOf(() => ["simple", "thimble", sym].join(), TypeError);
// 21.1.1.1 String()
assertEq(String(sym), sym.toString());
assertThrowsInstanceOf(() => String(Object(sym)), TypeError);
// 21.1.1.2 new String()
assertThrowsInstanceOf(() => new String(sym), TypeError);
@ -62,5 +41,49 @@ for (var sym of symbols) {
assertEq(f.call(sym) === f.call(sym), false); // new object each time
}
for (var sym of symbols) {
testSymbolConversions(sym);
// 7.1.1 ToPrimitive
var symobj = Object(sym);
assertThrowsInstanceOf(() => Number(symobj), TypeError);
assertThrowsInstanceOf(() => String(symobj), TypeError);
assertThrowsInstanceOf(() => symobj < 0, TypeError);
assertThrowsInstanceOf(() => 0 < symobj, TypeError);
assertThrowsInstanceOf(() => symobj + 1, TypeError);
assertThrowsInstanceOf(() => "" + symobj, TypeError);
assertEq(sym == symobj, true);
assertEq(sym === symobj, false);
assertEq(symobj == 0, false);
assertEq(0 != symobj, true);
// 7.1.12 ToString
assertThrowsInstanceOf(() => String(Object(sym)), TypeError);
}
// Deleting Symbol.prototype[@@toPrimitive] does not change the behavior of
// conversions from a symbol to other types.
delete Symbol.prototype[Symbol.toPrimitive];
assertEq(Symbol.toPrimitive in Symbol.prototype, false);
testSymbolConversions(symbols[0]);
// It does change the behavior of ToPrimitive on Symbol objects, though.
// It causes the default algorithm (OrdinaryToPrimitive) to be used.
var VALUEOF_CALLED = 117.25;
Symbol.prototype.valueOf = function () { return VALUEOF_CALLED; };
Symbol.prototype.toString = function () { return "toString called"; };
for (var sym of symbols) {
var symobj = Object(sym);
assertEq(Number(symobj), VALUEOF_CALLED);
assertEq(String(symobj), "toString called");
assertEq(symobj < 0, VALUEOF_CALLED < 0);
assertEq(0 < symobj, 0 < VALUEOF_CALLED);
assertEq(symobj + 1, VALUEOF_CALLED + 1);
assertEq("" + symobj, "" + VALUEOF_CALLED);
assertEq(symobj == 0, false);
assertEq(0 != symobj, true);
}
if (typeof reportCompare === "function")
reportCompare(0, 0);

View File

@ -0,0 +1,39 @@
// ES6 19.4.3.4 Symbol.prototype[@@toPrimitive](hint)
// This method gets the primitive symbol from a Symbol wrapper object.
var sym = Symbol.for("truth")
var obj = Object(sym);
assertEq(obj[Symbol.toPrimitive]("default"), sym);
// The hint argument is ignored.
assertEq(obj[Symbol.toPrimitive]("number"), sym);
assertEq(obj[Symbol.toPrimitive]("string"), sym);
assertEq(obj[Symbol.toPrimitive](), sym);
assertEq(obj[Symbol.toPrimitive](Math.atan2), sym);
// The this value can also be a primitive symbol.
assertEq(sym[Symbol.toPrimitive](), sym);
// Or a wrapper to a Symbol object in another compartment.
var obj2 = newGlobal().Object(sym);
assertEq(obj2[Symbol.toPrimitive]("default"), sym);
// Otherwise a TypeError is thrown.
var symbolToPrimitive = Symbol.prototype[Symbol.toPrimitive];
var nonSymbols = [
undefined, null, true, 13, NaN, "justice", {}, [sym],
symbolToPrimitive,
new Proxy(obj, {})
];
for (var value of nonSymbols) {
assertThrowsInstanceOf(() => symbolToPrimitive.call(value, "string"), TypeError);
}
// Surface features:
assertEq(symbolToPrimitive.name, "[Symbol.toPrimitive]");
var desc = Object.getOwnPropertyDescriptor(Symbol.prototype, Symbol.toPrimitive);
assertEq(desc.configurable, true);
assertEq(desc.enumerable, false);
assertEq(desc.writable, false);
reportCompare(0, 0);

View File

@ -268,8 +268,8 @@
macro(iterator, iterator, "iterator") \
macro(match, match, "match") \
macro(species, species, "species") \
macro(toPrimitive, toPrimitive, "toPrimitive") \
/* Same goes for the descriptions of the well-known symbols. */ \
macro(Symbol_create, Symbol_create, "Symbol.create") \
macro(Symbol_hasInstance, Symbol_hasInstance, "Symbol.hasInstance") \
macro(Symbol_isConcatSpreadable, Symbol_isConcatSpreadable, "Symbol.isConcatSpreadable") \
macro(Symbol_iterator, Symbol_iterator, "Symbol.iterator") \

View File

@ -444,6 +444,7 @@ struct WellKnownSymbols
js::ImmutableSymbolPtr iterator;
js::ImmutableSymbolPtr match;
js::ImmutableSymbolPtr species;
js::ImmutableSymbolPtr toPrimitive;
const ImmutableSymbolPtr& get(size_t u) const {
MOZ_ASSERT(u < JS::WellKnownSymbolLimit);

View File

@ -33,7 +33,7 @@ static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 310;
static const uint32_t XDR_BYTECODE_VERSION =
uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND);
static_assert(JSErr_Limit == 413,
static_assert(JSErr_Limit == 415,
"GREETINGS, POTENTIAL SUBTRAHEND INCREMENTER! If you added or "
"removed MSG_DEFs from js.msg, you should increment "
"XDR_BYTECODE_VERSION_SUBTRAHEND and update this assertion's "

View File

@ -440,17 +440,6 @@ sandbox_moved(JSObject* obj, const JSObject* old)
static_cast<SandboxPrivate*>(sop)->ObjectMoved(obj, old);
}
static bool
sandbox_convert(JSContext* cx, HandleObject obj, JSType type, MutableHandleValue vp)
{
if (type == JSTYPE_OBJECT) {
vp.setObject(*obj);
return true;
}
return OrdinaryToPrimitive(cx, obj, type, vp);
}
static bool
writeToProto_setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
JS::MutableHandleValue vp, JS::ObjectOpResult& result)
@ -569,7 +558,8 @@ static const js::Class SandboxClass = {
nullptr, nullptr, nullptr, nullptr,
sandbox_enumerate, sandbox_resolve,
nullptr, /* mayResolve */
sandbox_convert, sandbox_finalize,
nullptr, /* convert */
sandbox_finalize,
nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook,
JS_NULL_CLASS_SPEC,
{
@ -590,7 +580,8 @@ static const js::Class SandboxWriteToProtoClass = {
sandbox_addProperty, nullptr, nullptr, nullptr,
sandbox_enumerate, sandbox_resolve,
nullptr, /* mayResolve */
sandbox_convert, sandbox_finalize,
nullptr, /* convert */
sandbox_finalize,
nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook,
JS_NULL_CLASS_SPEC,
{

View File

@ -776,7 +776,6 @@ XPCWrappedNative::Init(const XPCNativeScriptableCreateInfo* sci)
jsclazz->name &&
jsclazz->flags &&
jsclazz->resolve &&
jsclazz->convert &&
jsclazz->finalize, "bad class");
// XXXbz JS_GetObjectPrototype wants an object, even though it then asserts

View File

@ -96,6 +96,44 @@ XPC_WN_Shared_ToSource(JSContext* cx, unsigned argc, Value* vp)
return true;
}
static bool
XPC_WN_Shared_toPrimitive(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject obj(cx);
if (!JS_ValueToObject(cx, args.thisv(), &obj))
return false;
XPCCallContext ccx(JS_CALLER, cx, obj);
XPCWrappedNative* wrapper = ccx.GetWrapper();
THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper);
JSType hint;
if (!GetFirstArgumentAsTypeHint(cx, args, &hint))
return false;
if (hint == JSTYPE_NUMBER) {
args.rval().set(JS_GetNaNValue(cx));
return true;
}
MOZ_ASSERT(hint == JSTYPE_STRING || hint == JSTYPE_VOID);
ccx.SetName(ccx.GetRuntime()->GetStringID(XPCJSRuntime::IDX_TO_STRING));
ccx.SetArgsAndResultPtr(0, nullptr, args.rval().address());
XPCNativeMember* member = ccx.GetMember();
if (member && member->IsMethod()) {
if (!XPCWrappedNative::CallMethod(ccx))
return false;
if (args.rval().isPrimitive())
return true;
}
// else...
return ToStringGuts(ccx);
}
/***************************************************************************/
// A "double wrapped object" is a user JSObject that has been wrapped as a
@ -233,15 +271,17 @@ DefinePropertyIfFound(XPCCallContext& ccx,
{
call = XPC_WN_Shared_ToString;
name = rt->GetStringName(XPCJSRuntime::IDX_TO_STRING);
id = rt->GetStringID(XPCJSRuntime::IDX_TO_STRING);
} else if (id == rt->GetStringID(XPCJSRuntime::IDX_TO_SOURCE)) {
call = XPC_WN_Shared_ToSource;
name = rt->GetStringName(XPCJSRuntime::IDX_TO_SOURCE);
id = rt->GetStringID(XPCJSRuntime::IDX_TO_SOURCE);
}
else
} else if (id == SYMBOL_TO_JSID(
JS::GetWellKnownSymbol(ccx, JS::SymbolCode::toPrimitive)))
{
call = XPC_WN_Shared_toPrimitive;
name = "[Symbol.toPrimitive]";
} else {
call = nullptr;
}
if (call) {
RootedFunction fun(ccx, JS_NewFunction(ccx, call, 0, 0, name));
@ -450,63 +490,6 @@ XPC_WN_CannotModifySetPropertyStub(JSContext* cx, HandleObject obj, HandleId id,
return Throw(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN, cx);
}
static bool
XPC_WN_Shared_Convert(JSContext* cx, HandleObject obj, JSType type, MutableHandleValue vp)
{
if (type == JSTYPE_OBJECT) {
vp.setObject(*obj);
return true;
}
XPCCallContext ccx(JS_CALLER, cx, obj);
XPCWrappedNative* wrapper = ccx.GetWrapper();
THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper);
switch (type) {
case JSTYPE_FUNCTION:
{
if (!ccx.GetTearOff()) {
XPCNativeScriptableInfo* si = wrapper->GetScriptableInfo();
if (si && (si->GetFlags().WantCall() ||
si->GetFlags().WantConstruct())) {
vp.setObject(*obj);
return true;
}
}
}
return Throw(NS_ERROR_XPC_CANT_CONVERT_WN_TO_FUN, cx);
case JSTYPE_NUMBER:
vp.set(JS_GetNaNValue(cx));
return true;
case JSTYPE_BOOLEAN:
vp.setBoolean(true);
return true;
case JSTYPE_VOID:
case JSTYPE_STRING:
{
ccx.SetName(ccx.GetRuntime()->GetStringID(XPCJSRuntime::IDX_TO_STRING));
ccx.SetArgsAndResultPtr(0, nullptr, vp.address());
XPCNativeMember* member = ccx.GetMember();
if (member && member->IsMethod()) {
if (!XPCWrappedNative::CallMethod(ccx))
return false;
if (vp.isPrimitive())
return true;
}
// else...
return ToStringGuts(ccx);
}
default:
NS_ERROR("bad type in conversion");
return false;
}
NS_NOTREACHED("huh?");
return false;
}
static bool
XPC_WN_Shared_Enumerate(JSContext* cx, HandleObject obj)
{
@ -652,7 +635,7 @@ const XPCWrappedNativeJSClass XPC_WN_NoHelper_JSClass = {
XPC_WN_Shared_Enumerate, // enumerate
XPC_WN_NoHelper_Resolve, // resolve
nullptr, // mayResolve
XPC_WN_Shared_Convert, // convert
nullptr, // convert
XPC_WN_NoHelper_Finalize, // finalize
/* Optionally non-null members start here. */
@ -1046,8 +1029,6 @@ XPCNativeScriptableShared::PopulateJSClass()
// We have to figure out resolve strategy at call time
mJSClass.base.resolve = XPC_WN_Helper_Resolve;
mJSClass.base.convert = XPC_WN_Shared_Convert;
if (mFlags.WantFinalize())
mJSClass.base.finalize = XPC_WN_Helper_Finalize;
else
@ -1521,7 +1502,7 @@ const js::Class XPC_WN_Tearoff_JSClass = {
XPC_WN_TearOff_Enumerate, // enumerate;
XPC_WN_TearOff_Resolve, // resolve;
nullptr, // mayResolve;
XPC_WN_Shared_Convert, // convert;
nullptr, // convert;
XPC_WN_TearOff_Finalize, // finalize;
/* Optionally non-null members start here. */

View File

@ -40,8 +40,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1042436
Cu.evalInSandbox('contentObjWithGetter.getterProp; contentObjWithGetter.valueProp; contentObjWithGetter.getterProp;',
chromeSb, "1.7", "http://phony.example.com/file.js", 99);
},
[{ errorMessage: /property someExpandoProperty \(reason: object is not safely Xrayable/, sourceName: /test_bug1042436/, isWarning: true },
{ errorMessage: /property getterProp \(reason: property has accessor/, sourceName: /phony/, lineNumber: 99, isWarning: true } ],
[{ errorMessage: /property "someExpandoProperty" \(reason: object is not safely Xrayable/, sourceName: /test_bug1042436/, isWarning: true },
{ errorMessage: /property "getterProp" \(reason: property has accessor/, sourceName: /phony/, lineNumber: 99, isWarning: true } ],
SimpleTest.finish.bind(SimpleTest));
]]>

View File

@ -33,7 +33,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1065185
endMonitor();
break;
case 1:
doMonitor([/access to property a/i]);
doMonitor([/access to property "a"/i]);
window[0].wrappedJSObject.probe = { a: 2 };
is(window[0].eval('probe.a'), undefined, "Non-exposed prop undefined");
is(window[0].eval('probe.a'), undefined, "Non-exposed prop undefined again");

View File

@ -159,7 +159,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=933681
"setUTCMilliseconds", "toUTCString", "toLocaleFormat", "toLocaleString",
"toLocaleDateString", "toLocaleTimeString", "toDateString", "toTimeString",
"toISOString", "toJSON", "toSource", "toString", "valueOf", "constructor",
"toGMTString"];
"toGMTString", Symbol.toPrimitive];
gPrototypeProperties['Object'] =
["constructor", "toSource", "toString", "toLocaleString", "valueOf", "watch",
"unwatch", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable",

View File

@ -196,14 +196,20 @@ ReportWrapperDenial(JSContext* cx, HandleId id, WrapperDenialType type, const ch
#endif
nsAutoJSString propertyName;
if (!propertyName.init(cx, id))
RootedValue idval(cx);
if (!JS_IdToValue(cx, id, &idval))
return false;
JSString* str = JS_ValueToSource(cx, idval);
if (!str)
return false;
if (!propertyName.init(cx, str))
return false;
AutoFilename filename;
unsigned line = 0, column = 0;
DescribeScriptedCaller(cx, &filename, &line, &column);
// Warn to the terminal for the logs.
NS_WARNING(nsPrintfCString("Silently denied access to property |%s|: %s (@%s:%u:%u)",
NS_WARNING(nsPrintfCString("Silently denied access to property %s: %s (@%s:%u:%u)",
NS_LossyConvertUTF16toASCII(propertyName).get(), reason,
filename.get(), line, column).get());