author | Jason Orendorff <jorendorff@mozilla.com> |
Mon, 23 Jun 2014 10:57:03 -0500 | |
changeset 190294 | dfefe211d083191c0ca99f865958d0839cbdc31e |
parent 190293 | 4a04ca5ed7d316f63648a99579575b45d3434a66 |
child 190295 | adc814d90d4d16b83d65034b0f3487f173816452 |
push id | 27004 |
push user | emorley@mozilla.com |
push date | Tue, 24 Jun 2014 15:52:34 +0000 |
treeherder | mozilla-central@7b174d47f3cc [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | Waldo |
bugs | 645416 |
milestone | 33.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/js/src/builtin/Object.cpp +++ b/js/src/builtin/Object.cpp @@ -884,54 +884,84 @@ obj_is(JSContext *cx, unsigned argc, Val bool same; if (!SameValue(cx, args.get(0), args.get(1), &same)) return false; args.rval().setBoolean(same); return true; } -static bool -obj_getOwnPropertyNames(JSContext *cx, unsigned argc, Value *vp) +bool +js::IdToStringOrSymbol(JSContext *cx, HandleId id, MutableHandleValue result) { - CallArgs args = CallArgsFromVp(argc, vp); - RootedObject obj(cx); - if (!GetFirstArgumentAsObject(cx, args, "Object.getOwnPropertyNames", &obj)) + if (JSID_IS_INT(id)) { + JSString *str = Int32ToString<CanGC>(cx, JSID_TO_INT(id)); + if (!str) + return false; + result.setString(str); + } else if (JSID_IS_ATOM(id)) { + result.setString(JSID_TO_STRING(id)); + } else { + result.setSymbol(JSID_TO_SYMBOL(id)); + } + return true; +} + +/* ES6 draft rev 25 (2014 May 22) 19.1.2.8.1 */ +static bool +GetOwnPropertyKeys(JSContext *cx, const CallArgs &args, unsigned flags, const char *fnname) +{ + // steps 1-2 + RootedObject obj(cx, ToObject(cx, args.get(0))); + if (!obj) return false; + // steps 3-10 AutoIdVector keys(cx); - if (!GetPropertyNames(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &keys)) + if (!GetPropertyNames(cx, obj, flags, &keys)) return false; + // step 11 AutoValueVector vals(cx); if (!vals.resize(keys.length())) return false; for (size_t i = 0, len = keys.length(); i < len; i++) { - jsid id = keys[i]; - if (JSID_IS_INT(id)) { - JSString *str = Int32ToString<CanGC>(cx, JSID_TO_INT(id)); - if (!str) - return false; - vals[i].setString(str); - } else if (JSID_IS_ATOM(id)) { - vals[i].setString(JSID_TO_STRING(id)); - } else { - vals[i].setSymbol(JSID_TO_SYMBOL(id)); - } + MOZ_ASSERT_IF(JSID_IS_SYMBOL(keys[i]), flags & JSITER_SYMBOLS); + MOZ_ASSERT_IF(!JSID_IS_SYMBOL(keys[i]), !(flags & JSITER_SYMBOLSONLY)); + if (!IdToStringOrSymbol(cx, keys[i], vals[i])) + return false; } JSObject *aobj = NewDenseCopiedArray(cx, vals.length(), vals.begin()); if (!aobj) return false; args.rval().setObject(*aobj); return true; } +static bool +obj_getOwnPropertyNames(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return GetOwnPropertyKeys(cx, args, JSITER_OWNONLY | JSITER_HIDDEN, + "Object.getOwnPropertyNames"); +} + +/* ES6 draft rev 25 (2014 May 22) 19.1.2.8 */ +static bool +obj_getOwnPropertySymbols(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return GetOwnPropertyKeys(cx, args, + JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS | JSITER_SYMBOLSONLY, + "Object.getOwnPropertySymbols"); +} + /* ES5 15.2.3.6: Object.defineProperty(O, P, Attributes) */ static bool obj_defineProperty(JSContext *cx, unsigned argc, Value *vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedObject obj(cx); if (!GetFirstArgumentAsObject(cx, args, "Object.defineProperty", &obj)) return false; @@ -1174,16 +1204,17 @@ const JSFunctionSpec js::object_static_m JS_FN("setPrototypeOf", obj_setPrototypeOf, 2,0), JS_FN("getOwnPropertyDescriptor", obj_getOwnPropertyDescriptor,2,0), JS_FN("keys", obj_keys, 1,0), JS_FN("is", obj_is, 2,0), JS_FN("defineProperty", obj_defineProperty, 3,0), JS_FN("defineProperties", obj_defineProperties, 2,0), JS_FN("create", obj_create, 2,0), JS_FN("getOwnPropertyNames", obj_getOwnPropertyNames, 1,0), + JS_FN("getOwnPropertySymbols", obj_getOwnPropertySymbols, 1,0), JS_FN("isExtensible", obj_isExtensible, 1,0), JS_FN("preventExtensions", obj_preventExtensions, 1,0), JS_FN("freeze", obj_freeze, 1,0), JS_FN("isFrozen", obj_isFrozen, 1,0), JS_FN("seal", obj_seal, 1,0), JS_FN("isSealed", obj_isSealed, 1,0), JS_FS_END };
--- a/js/src/builtin/Object.h +++ b/js/src/builtin/Object.h @@ -16,16 +16,24 @@ namespace js { extern const JSFunctionSpec object_methods[]; extern const JSPropertySpec object_properties[]; extern const JSFunctionSpec object_static_methods[]; // Object constructor native. Exposed only so the JIT can know its address. bool obj_construct(JSContext *cx, unsigned argc, JS::Value *vp); +/* + * Like IdToValue, but convert int jsids to strings. This is used when + * exposing a jsid to script for Object.getOwnProperty{Names,Symbols} + * or scriptable proxy traps. + */ +bool +IdToStringOrSymbol(JSContext *cx, JS::HandleId id, JS::MutableHandleValue result); + #if JS_HAS_TOSOURCE // Object.prototype.toSource. Function.prototype.toSource and uneval use this. JSString * ObjectToSource(JSContext *cx, JS::HandleObject obj); #endif // JS_HAS_TOSOURCE extern bool WatchHandler(JSContext *cx, JSObject *obj, jsid id, JS::Value old,
--- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -820,16 +820,17 @@ IsObjectInContextCompartment(JSObject *o * XDR_BYTECODE_VERSION. */ #define JSITER_ENUMERATE 0x1 /* for-in compatible hidden default iterator */ #define JSITER_FOREACH 0x2 /* get obj[key] for each property */ #define JSITER_KEYVALUE 0x4 /* obsolete destructuring for-in wants [key, value] */ #define JSITER_OWNONLY 0x8 /* iterate over obj's own properties only */ #define JSITER_HIDDEN 0x10 /* also enumerate non-enumerable properties */ #define JSITER_SYMBOLS 0x20 /* also include symbol property keys */ +#define JSITER_SYMBOLSONLY 0x40 /* exclude string property keys */ JS_FRIEND_API(bool) RunningWithTrustedPrincipals(JSContext *cx); inline uintptr_t GetNativeStackLimit(JSContext *cx) { StackKind kind = RunningWithTrustedPrincipals(cx) ? StackForTrustedScript
--- a/js/src/jsiter.cpp +++ b/js/src/jsiter.cpp @@ -114,73 +114,81 @@ Enumerate(JSContext *cx, HandleObject po // It's not necessary to add properties to the hash table at the end of // the prototype chain, but custom enumeration behaviors might return // duplicated properties, so always add in such cases. if ((pobj->is<ProxyObject>() || pobj->getProto() || pobj->getOps()->enumerate) && !ht.add(p, id)) return false; } // Symbol-keyed properties and nonenumerable properties are skipped unless - // the caller specifically asks for them. - if (JSID_IS_SYMBOL(id) && !(flags & JSITER_SYMBOLS)) + // the caller specifically asks for them. A caller can also filter out + // non-symbols by asking for JSITER_SYMBOLSONLY. + if (JSID_IS_SYMBOL(id) ? !(flags & JSITER_SYMBOLS) : (flags & JSITER_SYMBOLSONLY)) return true; if (!enumerable && !(flags & JSITER_HIDDEN)) return true; return props->append(id); } static bool EnumerateNativeProperties(JSContext *cx, HandleObject pobj, unsigned flags, IdSet &ht, AutoIdVector *props) { - /* Collect any dense elements from this object. */ - size_t initlen = pobj->getDenseInitializedLength(); - const Value *vp = pobj->getDenseElements(); - for (size_t i = 0; i < initlen; ++i, ++vp) { - if (!vp->isMagic(JS_ELEMENTS_HOLE)) { - /* Dense arrays never get so large that i would not fit into an integer id. */ - if (!Enumerate(cx, pobj, INT_TO_JSID(i), /* enumerable = */ true, flags, ht, props)) + bool enumerateSymbols; + if (flags & JSITER_SYMBOLSONLY) { + enumerateSymbols = true; + } else { + /* Collect any dense elements from this object. */ + size_t initlen = pobj->getDenseInitializedLength(); + const Value *vp = pobj->getDenseElements(); + for (size_t i = 0; i < initlen; ++i, ++vp) { + if (!vp->isMagic(JS_ELEMENTS_HOLE)) { + /* Dense arrays never get so large that i would not fit into an integer id. */ + if (!Enumerate(cx, pobj, INT_TO_JSID(i), /* enumerable = */ true, flags, ht, props)) + return false; + } + } + + /* Collect any typed array elements from this object. */ + if (pobj->is<TypedArrayObject>()) { + size_t len = pobj->as<TypedArrayObject>().length(); + for (size_t i = 0; i < len; i++) { + if (!Enumerate(cx, pobj, INT_TO_JSID(i), /* enumerable = */ true, flags, ht, props)) + return false; + } + } + + size_t initialLength = props->length(); + + /* Collect all unique property names from this object's shape. */ + bool symbolsFound = false; + Shape::Range<NoGC> r(pobj->lastProperty()); + for (; !r.empty(); r.popFront()) { + Shape &shape = r.front(); + jsid id = shape.propid(); + + if (JSID_IS_SYMBOL(id)) { + symbolsFound = true; + continue; + } + + if (!Enumerate(cx, pobj, id, shape.enumerable(), flags, ht, props)) return false; } - } + ::Reverse(props->begin() + initialLength, props->end()); - /* Collect any typed array elements from this object. */ - if (pobj->is<TypedArrayObject>()) { - size_t len = pobj->as<TypedArrayObject>().length(); - for (size_t i = 0; i < len; i++) { - if (!Enumerate(cx, pobj, INT_TO_JSID(i), /* enumerable = */ true, flags, ht, props)) - return false; - } + enumerateSymbols = symbolsFound && (flags & JSITER_SYMBOLS); } - size_t initialLength = props->length(); - - /* Collect all unique property names from this object's shape. */ - Shape::Range<NoGC> r(pobj->lastProperty()); - bool symbolsFound = false; - for (; !r.empty(); r.popFront()) { - Shape &shape = r.front(); - jsid id = shape.propid(); - - if (JSID_IS_SYMBOL(id)) { - symbolsFound = true; - continue; - } - - if (!Enumerate(cx, pobj, id, shape.enumerable(), flags, ht, props)) - return false; - } - ::Reverse(props->begin() + initialLength, props->end()); - - if (symbolsFound && (flags & JSITER_SYMBOLS)) { + if (enumerateSymbols) { // Do a second pass to collect symbols. ES6 draft rev 25 (2014 May 22) // 9.1.12 requires that all symbols appear after all strings in the // result. - initialLength = props->length(); + size_t initialLength = props->length(); for (Shape::Range<NoGC> r(pobj->lastProperty()); !r.empty(); r.popFront()) { Shape &shape = r.front(); jsid id = shape.propid(); if (JSID_IS_SYMBOL(id)) { if (!Enumerate(cx, pobj, id, shape.enumerable(), flags, ht, props)) return false; } } @@ -383,17 +391,19 @@ js::VectorToIdArray(JSContext *cx, AutoI ida->vector[i].init(v[i]); *idap = ida; return true; } JS_FRIEND_API(bool) js::GetPropertyNames(JSContext *cx, JSObject *obj, unsigned flags, AutoIdVector *props) { - return Snapshot(cx, obj, flags & (JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS), props); + return Snapshot(cx, obj, + flags & (JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS | JSITER_SYMBOLSONLY), + props); } size_t sCustomIteratorCount = 0; static inline bool GetCustomIterator(JSContext *cx, HandleObject obj, unsigned flags, MutableHandleValue vp) { JS_CHECK_RECURSION(cx, return false);
--- a/js/src/jsproxy.cpp +++ b/js/src/jsproxy.cpp @@ -646,42 +646,29 @@ GetDerivedTrap(JSContext *cx, HandleObje static bool Trap(JSContext *cx, HandleObject handler, HandleValue fval, unsigned argc, Value* argv, MutableHandleValue rval) { return Invoke(cx, ObjectValue(*handler), fval, argc, argv, rval); } static bool -IdToExposableValue(JSContext *cx, HandleId id, MutableHandleValue value) -{ - value.set(IdToValue(id)); // Re-use out-param to avoid Rooted overhead. - if (value.isSymbol()) - return true; - JSString *name = ToString<CanGC>(cx, value); - if (!name) - return false; - value.setString(name); - return true; -} - -static bool Trap1(JSContext *cx, HandleObject handler, HandleValue fval, HandleId id, MutableHandleValue rval) { - if (!IdToExposableValue(cx, id, rval)) // Re-use out-param to avoid Rooted overhead. + if (!IdToStringOrSymbol(cx, id, rval)) return false; return Trap(cx, handler, fval, 1, rval.address(), rval); } static bool Trap2(JSContext *cx, HandleObject handler, HandleValue fval, HandleId id, Value v_, MutableHandleValue rval) { RootedValue v(cx, v_); - if (!IdToExposableValue(cx, id, rval)) // Re-use out-param to avoid Rooted overhead. + if (!IdToStringOrSymbol(cx, id, rval)) return false; JS::AutoValueArray<2> argv(cx); argv[0].set(rval); argv[1].set(v); return Trap(cx, handler, fval, 2, argv.begin(), rval); } static bool @@ -945,17 +932,17 @@ ScriptedIndirectProxyHandler::hasOwn(JSC } bool ScriptedIndirectProxyHandler::get(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id, MutableHandleValue vp) { RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy)); RootedValue idv(cx); - if (!IdToExposableValue(cx, id, &idv)) + if (!IdToStringOrSymbol(cx, id, &idv)) return false; JS::AutoValueArray<2> argv(cx); argv[0].setObjectOrNull(receiver); argv[1].set(idv); RootedValue fval(cx); if (!GetDerivedTrap(cx, handler, cx->names().get, &fval)) return false; if (!IsCallable(fval)) @@ -964,17 +951,17 @@ ScriptedIndirectProxyHandler::get(JSCont } bool ScriptedIndirectProxyHandler::set(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id, bool strict, MutableHandleValue vp) { RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy)); RootedValue idv(cx); - if (!IdToExposableValue(cx, id, &idv)) + if (!IdToStringOrSymbol(cx, id, &idv)) return false; JS::AutoValueArray<3> argv(cx); argv[0].setObjectOrNull(receiver); argv[1].set(idv); argv[2].set(vp); RootedValue fval(cx); if (!GetDerivedTrap(cx, handler, cx->names().set, &fval)) return false; @@ -1460,17 +1447,17 @@ ScriptedDirectProxyHandler::getOwnProper return false; // step 7 if (trap.isUndefined()) return DirectProxyHandler::getOwnPropertyDescriptor(cx, proxy, id, desc); // step 8-9 RootedValue propKey(cx); - if (!IdToExposableValue(cx, id, &propKey)) + if (!IdToStringOrSymbol(cx, id, &propKey)) return false; Value argv[] = { ObjectValue(*target), propKey }; RootedValue trapResult(cx); if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult)) @@ -1583,17 +1570,17 @@ ScriptedDirectProxyHandler::defineProper // step 8-9, with 9 blatantly defied. // FIXME: This is incorrect with respect to [[Origin]]. See bug 601379. RootedValue descObj(cx); if (!NewPropertyDescriptorObject(cx, desc, &descObj)) return false; // step 10, 12 RootedValue propKey(cx); - if (!IdToExposableValue(cx, id, &propKey)) + if (!IdToStringOrSymbol(cx, id, &propKey)) return false; Value argv[] = { ObjectValue(*target), propKey, descObj }; RootedValue trapResult(cx); @@ -1706,17 +1693,17 @@ ScriptedDirectProxyHandler::delete_(JSCo return false; // step 7 if (trap.isUndefined()) return DirectProxyHandler::delete_(cx, proxy, id, bp); // step 8 RootedValue value(cx); - if (!IdToExposableValue(cx, id, &value)) + if (!IdToStringOrSymbol(cx, id, &value)) return false; Value argv[] = { ObjectValue(*target), value }; RootedValue trapResult(cx); if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult)) return false; @@ -1804,17 +1791,17 @@ ScriptedDirectProxyHandler::has(JSContex return false; // step 4 if (trap.isUndefined()) return DirectProxyHandler::has(cx, proxy, id, bp); // step 5 RootedValue value(cx); - if (!IdToExposableValue(cx, id, &value)) + if (!IdToStringOrSymbol(cx, id, &value)) return false; Value argv[] = { ObjectOrNullValue(target), value }; RootedValue trapResult(cx); if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult)) return false; @@ -1866,17 +1853,17 @@ ScriptedDirectProxyHandler::get(JSContex return false; // step 4 if (trap.isUndefined()) return DirectProxyHandler::get(cx, proxy, receiver, id, vp); // step 5 RootedValue value(cx); - if (!IdToExposableValue(cx, id, &value)) + if (!IdToStringOrSymbol(cx, id, &value)) return false; Value argv[] = { ObjectOrNullValue(target), value, ObjectOrNullValue(receiver) }; RootedValue trapResult(cx); if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult)) @@ -1929,17 +1916,17 @@ ScriptedDirectProxyHandler::set(JSContex return false; // step 4 if (trap.isUndefined()) return DirectProxyHandler::set(cx, proxy, receiver, id, strict, vp); // step 5 RootedValue value(cx); - if (!IdToExposableValue(cx, id, &value)) + if (!IdToStringOrSymbol(cx, id, &value)) return false; Value argv[] = { ObjectOrNullValue(target), value, vp.get(), ObjectValue(*receiver) }; RootedValue trapResult(cx);
new file mode 100644 --- /dev/null +++ b/js/src/tests/ecma_6/Object/getOwnPropertySymbols-proxy.js @@ -0,0 +1,28 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ */ + +// getOwnPropertySymbols(proxy) calls the getOwnPropertyNames hook (only). + +var symbols = [Symbol(), Symbol("moon"), Symbol.for("sun"), Symbol.iterator]; +var hits = 0; + +function HandlerProxy() { + return new Proxy({}, { + get: function (t, key) { + if (key !== "ownKeys") + throw new Error("tried to access handler[" + uneval(key) + "]"); + hits++; + return t => symbols; + } + }); +} + +function OwnKeysProxy() { + return new Proxy({}, new HandlerProxy); +} + +assertDeepEq(Object.getOwnPropertySymbols(new OwnKeysProxy), symbols); +assertEq(hits, 1); + +if (typeof reportCompare === "function") + reportCompare(0, 0);
new file mode 100644 --- /dev/null +++ b/js/src/tests/ecma_6/Object/getOwnPropertySymbols.js @@ -0,0 +1,46 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ */ + +assertDeepEq(Object.getOwnPropertySymbols({}), []); + +// String keys are ignored. +assertEq(Object.getOwnPropertySymbols({a: 1, b: 2}).length, 0); +assertEq(Object.getOwnPropertySymbols([0, 1, 2, 3]).length, 0); + +// Symbol keys are observed. +var iterable = {}; +Object.defineProperty(iterable, Symbol.iterator, { + value: () => [][Symbol.iterator]() +}); +assertDeepEq(Object.getOwnPropertySymbols(iterable), [Symbol.iterator]); +assertDeepEq(Object.getOwnPropertySymbols(new Proxy(iterable, {})), [Symbol.iterator]); + +// Test on an object with a thousand own properties. +var obj = {}; +for (var i = 0; i < 1000; i++) { + obj[Symbol.for("x" + i)] = 1; +} +assertEq(Object.getOwnPropertyNames(obj).length, 0); +var symbols = Object.getOwnPropertySymbols(obj); +assertEq(symbols.length, 1000); +assertEq(symbols.indexOf(Symbol.for("x0")) !== -1, true); +assertEq(symbols.indexOf(Symbol.for("x241")) !== -1, true); +assertEq(symbols.indexOf(Symbol.for("x999")) !== -1, true); +assertEq(Object.getOwnPropertySymbols(new Proxy(obj, {})).length, 1000); + +// The prototype chain is not consulted. +assertEq(Object.getOwnPropertySymbols(Object.create(obj)).length, 0); +assertEq(Object.getOwnPropertySymbols(new Proxy(Object.create(obj), {})).length, 0); + +// Primitives are coerced to objects; but there are never any symbol-keyed +// properties on the resulting wrapper objects. +assertThrowsInstanceOf(() => Object.getOwnPropertySymbols(), TypeError); +assertThrowsInstanceOf(() => Object.getOwnPropertySymbols(undefined), TypeError); +assertThrowsInstanceOf(() => Object.getOwnPropertySymbols(null), TypeError); +for (var primitive of [true, 1, 3.14, "hello", Symbol()]) + assertEq(Object.getOwnPropertySymbols(primitive).length, 0); + +assertEq(Object.getOwnPropertySymbols.length, 1); + +if (typeof reportCompare === "function") + reportCompare(0, 0);