mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
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:
parent
ba9a46caa1
commit
de8a5d035d
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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[];
|
||||
|
@ -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.
|
||||
|
@ -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")
|
||||
|
@ -62,7 +62,6 @@ UNIFIED_SOURCES += [
|
||||
'testNullRoot.cpp',
|
||||
'testObjectEmulatingUndefined.cpp',
|
||||
'testOOM.cpp',
|
||||
'testOps.cpp',
|
||||
'testParseJSON.cpp',
|
||||
'testPersistentRooted.cpp',
|
||||
'testPreserveJitCode.cpp',
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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 */
|
||||
|
@ -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 */ \
|
||||
|
132
js/src/jsobj.cpp
132
js/src/jsobj.cpp
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -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)
|
||||
{
|
||||
|
62
js/src/tests/ecma_6/Date/toPrimitive.js
Normal file
62
js/src/tests/ecma_6/Date/toPrimitive.js
Normal 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);
|
57
js/src/tests/ecma_6/Object/toPrimitive-callers.js
Normal file
57
js/src/tests/ecma_6/Object/toPrimitive-callers.js
Normal 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);
|
101
js/src/tests/ecma_6/Object/toPrimitive.js
Normal file
101
js/src/tests/ecma_6/Object/toPrimitive.js
Normal 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);
|
@ -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;
|
||||
|
@ -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);
|
||||
|
39
js/src/tests/ecma_6/Symbol/toPrimitive.js
Normal file
39
js/src/tests/ecma_6/Symbol/toPrimitive.js
Normal 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);
|
@ -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") \
|
||||
|
@ -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);
|
||||
|
@ -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 "
|
||||
|
@ -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,
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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. */
|
||||
|
@ -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));
|
||||
|
||||
]]>
|
||||
|
@ -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");
|
||||
|
@ -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",
|
||||
|
@ -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());
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user