From abd42d3ec574fe2022ee3732cb83e224b27fda49 Mon Sep 17 00:00:00 2001 From: Jason Orendorff Date: Fri, 20 Mar 2015 14:02:55 -0500 Subject: [PATCH] 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. --HG-- extra : commitid : B48u39i6pxl extra : rebase_source : bddefbd7bc131007303a5a00dd9c0bb8fec1b122 --- dom/plugins/base/nsJSNPRuntime.cpp | 177 +++++++++++------- dom/workers/WorkerScope.cpp | 14 +- js/src/builtin/SymbolObject.cpp | 30 ++- js/src/builtin/SymbolObject.h | 3 +- js/src/ctypes/CTypes.cpp | 4 + js/src/js.msg | 2 + js/src/jsapi-tests/moz.build | 1 - js/src/jsapi-tests/testOps.cpp | 65 ------- js/src/jsapi-tests/testUbiNode.cpp | 1 + js/src/jsapi.cpp | 46 ++++- js/src/jsapi.h | 42 +++-- js/src/jsdate.cpp | 36 +++- js/src/jsfriendapi.h | 2 +- js/src/jsobj.cpp | 132 +++++++------ js/src/jsobj.h | 13 +- js/src/proxy/Proxy.cpp | 14 -- js/src/tests/ecma_6/Date/toPrimitive.js | 62 ++++++ .../ecma_6/Object/toPrimitive-callers.js | 57 ++++++ js/src/tests/ecma_6/Object/toPrimitive.js | 101 ++++++++++ js/src/tests/ecma_6/Reflect/propertyKeys.js | 11 +- js/src/tests/ecma_6/Symbol/conversions.js | 71 ++++--- js/src/tests/ecma_6/Symbol/toPrimitive.js | 39 ++++ js/src/vm/CommonPropertyNames.h | 2 +- js/src/vm/Runtime.h | 1 + js/src/vm/Xdr.h | 2 +- js/xpconnect/src/Sandbox.cpp | 17 +- js/xpconnect/src/XPCWrappedNative.cpp | 1 - js/xpconnect/src/XPCWrappedNativeJSOps.cpp | 113 +++++------ js/xpconnect/tests/chrome/test_bug1042436.xul | 4 +- .../tests/chrome/test_bug1065185.html | 2 +- js/xpconnect/tests/chrome/test_xrayToJS.xul | 2 +- js/xpconnect/wrappers/XrayWrapper.cpp | 10 +- 32 files changed, 684 insertions(+), 393 deletions(-) delete mode 100644 js/src/jsapi-tests/testOps.cpp create mode 100644 js/src/tests/ecma_6/Date/toPrimitive.js create mode 100644 js/src/tests/ecma_6/Object/toPrimitive-callers.js create mode 100644 js/src/tests/ecma_6/Object/toPrimitive.js create mode 100644 js/src/tests/ecma_6/Symbol/toPrimitive.js diff --git a/dom/plugins/base/nsJSNPRuntime.cpp b/dom/plugins/base/nsJSNPRuntime.cpp index 7e5cdae3fd0f..5f8f4dd36dc7 100644 --- a/dom/plugins/base/nsJSNPRuntime.cpp +++ b/dom/plugins/base/nsJSNPRuntime.cpp @@ -183,9 +183,6 @@ static bool NPObjWrapper_Resolve(JSContext *cx, JS::Handle obj, JS::Handle id, bool *resolvedp); -static bool -NPObjWrapper_Convert(JSContext *cx, JS::Handle obj, JSType type, JS::MutableHandle 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 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 obj, JSType type, JS::MutableHandle 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 obj, JS::Handle obj, JS::Handle return true; } -static bool -NPObjWrapper_Convert(JSContext *cx, JS::Handle obj, JSType hint, JS::MutableHandle 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 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 obj, JSType type, JS::MutableHandle 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 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 objVal(cx, &args.rval().toObject()); + return JS_DefaultValue(cx, objVal, type, args.rval()); + } + return true; +} + // static bool nsJSObjWrapper::HasOwnProperty(NPObject *npobj, NPIdentifier npid) diff --git a/dom/workers/WorkerScope.cpp b/dom/workers/WorkerScope.cpp index ff6f1c9543a1..7d480b6e0a32 100644 --- a/dom/workers/WorkerScope.cpp +++ b/dom/workers/WorkerScope.cpp @@ -740,18 +740,6 @@ workerdebuggersandbox_resolve(JSContext *cx, JS::Handle obj, return JS_ResolveStandardClass(cx, obj, id, resolvedp); } -static bool -workerdebuggersandbox_convert(JSContext *cx, JS::Handle obj, - JSType type, JS::MutableHandle 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, diff --git a/js/src/builtin/SymbolObject.cpp b/js/src/builtin/SymbolObject.cpp index 1a2d8bb43a63..b96c89defe9d 100644 --- a/js/src/builtin/SymbolObject.cpp +++ b/js/src/builtin/SymbolObject.cpp @@ -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().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(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(cx, args); +} + JSObject* js::InitSymbolClass(JSContext* cx, HandleObject obj) { diff --git a/js/src/builtin/SymbolObject.h b/js/src/builtin/SymbolObject.h index 6efffa017ef1..287fd30eaec0 100644 --- a/js/src/builtin/SymbolObject.h +++ b/js/src/builtin/SymbolObject.h @@ -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[]; diff --git a/js/src/ctypes/CTypes.cpp b/js/src/ctypes/CTypes.cpp index 326cbb4294b5..d80b9c75d39d 100644 --- a/js/src/ctypes/CTypes.cpp +++ b/js/src/ctypes/CTypes.cpp @@ -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. diff --git a/js/src/js.msg b/js/src/js.msg index 33e5931c2065..b7f41bd6a8fd 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -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") diff --git a/js/src/jsapi-tests/moz.build b/js/src/jsapi-tests/moz.build index 6c2b3a49cd89..d88adb788997 100644 --- a/js/src/jsapi-tests/moz.build +++ b/js/src/jsapi-tests/moz.build @@ -62,7 +62,6 @@ UNIFIED_SOURCES += [ 'testNullRoot.cpp', 'testObjectEmulatingUndefined.cpp', 'testOOM.cpp', - 'testOps.cpp', 'testParseJSON.cpp', 'testPersistentRooted.cpp', 'testPreserveJitCode.cpp', diff --git a/js/src/jsapi-tests/testOps.cpp b/js/src/jsapi-tests/testOps.cpp deleted file mode 100644 index dd48076f7ce9..000000000000 --- a/js/src/jsapi-tests/testOps.cpp +++ /dev/null @@ -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) - diff --git a/js/src/jsapi-tests/testUbiNode.cpp b/js/src/jsapi-tests/testUbiNode.cpp index c30a59c71959..5c66f23bb36d 100644 --- a/js/src/jsapi-tests/testUbiNode.cpp +++ b/js/src/jsapi-tests/testUbiNode.cpp @@ -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. diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 28890f51e433..de17408df0c2 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -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) diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 04f073181fdb..7d12770f7bbb 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -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 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 obj, JSType hint, - JS::MutableHandle 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. diff --git a/js/src/jsdate.cpp b/js/src/jsdate.cpp index fdf1fb8d6b84..00100881cd6e 100644 --- a/js/src/jsdate.cpp +++ b/js/src/jsdate.cpp @@ -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()); - - 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(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 */ diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h index 3cb4f840431a..17ddbf173a1d 100644 --- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -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 */ \ diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 0bc856b47e3f..4a6ebcb03f88 100644 --- a/js/src/jsobj.cpp +++ b/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(); 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(); 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()) { - jsid id = NameToId(cx->names().valueOf); - StringObject* nobj = &obj->as(); - if (ClassMethodIsNative(cx, nobj, &StringObject::class_, id, str_toString)) { - vp.setString(nobj->unbox()); - return true; - } - } - - /* Optimize new Number(...).valueOf(). */ - if (obj->is()) { - jsid id = NameToId(cx->names().valueOf); - NumberObject* nobj = &obj->as(); - 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) diff --git a/js/src/jsobj.h b/js/src/jsobj.h index b195a15649c6..6a90b6533416 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -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); } /* diff --git a/js/src/proxy/Proxy.cpp b/js/src/proxy/Proxy.cpp index 72ab518de5bc..811e2046c195 100644 --- a/js/src/proxy/Proxy.cpp +++ b/js/src/proxy/Proxy.cpp @@ -513,13 +513,6 @@ Proxy::boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp return proxy->as().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().handler()->defaultValue(cx, proxy, hint, vp); -} - JSObject * const TaggedProto::LazyProto = reinterpret_cast(0x1); /* static */ bool @@ -680,13 +673,6 @@ js::proxy_WeakmapKeyDelegate(JSObject* obj) return obj->as().handler()->weakmapKeyDelegate(obj); } -bool -js::proxy_Convert(JSContext* cx, HandleObject proxy, JSType hint, MutableHandleValue vp) -{ - MOZ_ASSERT(proxy->is()); - return Proxy::defaultValue(cx, proxy, hint, vp); -} - void js::proxy_Finalize(FreeOp* fop, JSObject* obj) { diff --git a/js/src/tests/ecma_6/Date/toPrimitive.js b/js/src/tests/ecma_6/Date/toPrimitive.js new file mode 100644 index 000000000000..11346b1ae887 --- /dev/null +++ b/js/src/tests/ecma_6/Date/toPrimitive.js @@ -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); diff --git a/js/src/tests/ecma_6/Object/toPrimitive-callers.js b/js/src/tests/ecma_6/Object/toPrimitive-callers.js new file mode 100644 index 000000000000..ffeef2225cf2 --- /dev/null +++ b/js/src/tests/ecma_6/Object/toPrimitive-callers.js @@ -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); diff --git a/js/src/tests/ecma_6/Object/toPrimitive.js b/js/src/tests/ecma_6/Object/toPrimitive.js new file mode 100644 index 000000000000..0904bd19a98e --- /dev/null +++ b/js/src/tests/ecma_6/Object/toPrimitive.js @@ -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); diff --git a/js/src/tests/ecma_6/Reflect/propertyKeys.js b/js/src/tests/ecma_6/Reflect/propertyKeys.js index 26358ea55063..9dc8423d8de3 100644 --- a/js/src/tests/ecma_6/Reflect/propertyKeys.js +++ b/js/src/tests/ecma_6/Reflect/propertyKeys.js @@ -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; diff --git a/js/src/tests/ecma_6/Symbol/conversions.js b/js/src/tests/ecma_6/Symbol/conversions.js index 47e1cc4bb2c1..e6265f09c9eb 100644 --- a/js/src/tests/ecma_6/Symbol/conversions.js +++ b/js/src/tests/ecma_6/Symbol/conversions.js @@ -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); diff --git a/js/src/tests/ecma_6/Symbol/toPrimitive.js b/js/src/tests/ecma_6/Symbol/toPrimitive.js new file mode 100644 index 000000000000..06262f99c696 --- /dev/null +++ b/js/src/tests/ecma_6/Symbol/toPrimitive.js @@ -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); diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index 16276f406fa7..ca6eb3658391 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -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") \ diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h index 585d2551eadb..5cb9848036cf 100644 --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -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); diff --git a/js/src/vm/Xdr.h b/js/src/vm/Xdr.h index d8730ac2298d..e65fc7cb8b13 100644 --- a/js/src/vm/Xdr.h +++ b/js/src/vm/Xdr.h @@ -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 " diff --git a/js/xpconnect/src/Sandbox.cpp b/js/xpconnect/src/Sandbox.cpp index a7ed281b4ed7..8b18189073d4 100644 --- a/js/xpconnect/src/Sandbox.cpp +++ b/js/xpconnect/src/Sandbox.cpp @@ -440,17 +440,6 @@ sandbox_moved(JSObject* obj, const JSObject* old) static_cast(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, { diff --git a/js/xpconnect/src/XPCWrappedNative.cpp b/js/xpconnect/src/XPCWrappedNative.cpp index 3294c3e6f517..5af9d300e336 100644 --- a/js/xpconnect/src/XPCWrappedNative.cpp +++ b/js/xpconnect/src/XPCWrappedNative.cpp @@ -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 diff --git a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp index e292faaa54b9..39596138009a 100644 --- a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp +++ b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp @@ -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. */ diff --git a/js/xpconnect/tests/chrome/test_bug1042436.xul b/js/xpconnect/tests/chrome/test_bug1042436.xul index 5208df0d0c6a..355ffcfa42a0 100644 --- a/js/xpconnect/tests/chrome/test_bug1042436.xul +++ b/js/xpconnect/tests/chrome/test_bug1042436.xul @@ -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)); ]]> diff --git a/js/xpconnect/tests/chrome/test_bug1065185.html b/js/xpconnect/tests/chrome/test_bug1065185.html index 20e9cb0704a8..cdd65326f9c8 100644 --- a/js/xpconnect/tests/chrome/test_bug1065185.html +++ b/js/xpconnect/tests/chrome/test_bug1065185.html @@ -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"); diff --git a/js/xpconnect/tests/chrome/test_xrayToJS.xul b/js/xpconnect/tests/chrome/test_xrayToJS.xul index 0a6574206251..799748a48ce1 100644 --- a/js/xpconnect/tests/chrome/test_xrayToJS.xul +++ b/js/xpconnect/tests/chrome/test_xrayToJS.xul @@ -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", diff --git a/js/xpconnect/wrappers/XrayWrapper.cpp b/js/xpconnect/wrappers/XrayWrapper.cpp index 95698e67af81..b50c0d4746cb 100644 --- a/js/xpconnect/wrappers/XrayWrapper.cpp +++ b/js/xpconnect/wrappers/XrayWrapper.cpp @@ -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());