Bug 645416, part 25 - Add support for enumerating symbol-keyed properties. r=Waldo.

Object.keys, Object.getOwnPropertyNames, and for-in loops skip symbol-keyed
properties per spec, but Object.defineProperties sees them, and a future
Reflect.ownKeys API will need to be able to see them.

This patch changes the comments on JSITER_FOREACH and JSITER_KEYVALUE, but not
the behavior. The comments were just wrong.

--HG--
extra : rebase_source : f1ad99d416df8a8acef5598bef2cde4b72dcdb31
This commit is contained in:
Jason Orendorff 2014-06-23 10:57:03 -05:00
Родитель 04be8d105f
Коммит 0582d104f2
12 изменённых файлов: 216 добавлений и 38 удалений

Просмотреть файл

@ -170,7 +170,7 @@ js::ObjectToSource(JSContext *cx, HandleObject obj)
MutableHandleString gsop[2] = {&str0, &str1};
AutoIdVector idv(cx);
if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, &idv))
if (!GetPropertyNames(cx, obj, JSITER_OWNONLY | JSITER_SYMBOLS, &idv))
return nullptr;
bool comma = false;

Просмотреть файл

@ -863,7 +863,7 @@ StructMetaTypeDescr::create(JSContext *cx,
{
// Obtain names of fields, which are the own properties of `fields`
AutoIdVector ids(cx);
if (!GetPropertyNames(cx, fields, JSITER_OWNONLY, &ids))
if (!GetPropertyNames(cx, fields, JSITER_OWNONLY | JSITER_SYMBOLS, &ids))
return nullptr;
// Iterate through each field. Collect values for the various

Просмотреть файл

@ -26,3 +26,8 @@ test("[0, 1, 2]");
test("[,,,,,]");
test("/a*a/");
test("function () {}");
test("(function () {\n" +
" var x = {};\n" +
" x[Symbol()] = 1; x[Symbol.for('moon')] = 2; x[Symbol.iterator] = 3;\n" +
" return x;\n" +
"})()");

Просмотреть файл

@ -4,4 +4,9 @@ var g = newGlobal();
var dbg = Debugger();
var gobj = dbg.addDebuggee(g);
g.p = {xyzzy: 8}; // makes a cross-compartment wrapper
assertEq(gobj.getOwnPropertyDescriptor("p").value.getOwnPropertyDescriptor("xyzzy").value, 8);
g.p[Symbol.for("plugh")] = 9;
var wp = gobj.getOwnPropertyDescriptor("p").value;
var names = wp.getOwnPropertyNames();
assertEq(names.length, 1);
assertEq(names[0], "xyzzy");

Просмотреть файл

@ -1,5 +1,5 @@
// Forward to the target if the trap is not defined
var names = Object.getOwnPropertyNames(Proxy(Object.create(Object.create(null, {
var objAB = Object.create(null, {
a: {
enumerable: true,
configurable: true
@ -8,7 +8,9 @@ var names = Object.getOwnPropertyNames(Proxy(Object.create(Object.create(null, {
enumerable: false,
configurable: true
}
}), {
});
var objCD = Object.create(objAB, {
c: {
enumerable: true,
configurable: true
@ -17,7 +19,11 @@ var names = Object.getOwnPropertyNames(Proxy(Object.create(Object.create(null, {
enumerable: false,
configurable: true
}
}), {}));
});
var outerProxy = new Proxy(objCD, {});
objCD[Symbol("moon")] = "something";
var names = Object.getOwnPropertyNames(outerProxy);
assertEq(names.length, 2);
assertEq(names[0], 'c');
assertEq(names[1], 'd');

Просмотреть файл

@ -820,10 +820,11 @@ IsObjectInContextCompartment(JSObject *obj, const JSContext *cx);
* XDR_BYTECODE_VERSION.
*/
#define JSITER_ENUMERATE 0x1 /* for-in compatible hidden default iterator */
#define JSITER_FOREACH 0x2 /* return [key, value] pair rather than key */
#define JSITER_KEYVALUE 0x4 /* destructuring for-in wants [key, value] */
#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 */
JS_FRIEND_API(bool)
RunningWithTrustedPrincipals(JSContext *cx);

Просмотреть файл

@ -96,36 +96,36 @@ static inline bool
Enumerate(JSContext *cx, HandleObject pobj, jsid id,
bool enumerable, unsigned flags, IdSet& ht, AutoIdVector *props)
{
/*
* We implement __proto__ using a property on |Object.prototype|, but
* because __proto__ is highly deserving of removal, we don't want it to
* show up in property enumeration, even if only for |Object.prototype|
* (think introspection by Prototype-like frameworks that add methods to
* the built-in prototypes). So exclude __proto__ if the object where the
* property was found has no [[Prototype]] and might be |Object.prototype|.
*/
// We implement __proto__ using a property on |Object.prototype|, but
// because __proto__ is highly deserving of removal, we don't want it to
// show up in property enumeration, even if only for |Object.prototype|
// (think introspection by Prototype-like frameworks that add methods to
// the built-in prototypes). So exclude __proto__ if the object where the
// property was found has no [[Prototype]] and might be |Object.prototype|.
if (MOZ_UNLIKELY(!pobj->getTaggedProto().isObject() && JSID_IS_ATOM(id, cx->names().proto)))
return true;
if (!(flags & JSITER_OWNONLY) || pobj->is<ProxyObject>() || pobj->getOps()->enumerate) {
/* If we've already seen this, we definitely won't add it. */
// If we've already seen this, we definitely won't add it.
IdSet::AddPtr p = ht.lookupForAdd(id);
if (MOZ_UNLIKELY(!!p))
return true;
/*
* 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.
*/
// 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;
}
if (enumerable || (flags & JSITER_HIDDEN))
return props->append(id);
// Symbol-keyed properties and nonenumerable properties are skipped unless
// the caller specifically asks for them.
if (JSID_IS_SYMBOL(id) && !(flags & JSITER_SYMBOLS))
return true;
if (!enumerable && !(flags & JSITER_HIDDEN))
return true;
return true;
return props->append(id);
}
static bool
@ -154,16 +154,39 @@ EnumerateNativeProperties(JSContext *cx, HandleObject pobj, unsigned flags, IdSe
size_t initialLength = props->length();
/* Collect all unique properties from this object's scope. */
/* 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 (!Enumerate(cx, pobj, shape.propid(), shape.enumerable(), flags, ht, props))
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)) {
// 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();
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;
}
}
::Reverse(props->begin() + initialLength, props->end());
}
return true;
}
@ -210,7 +233,8 @@ Snapshot(JSContext *cx, JSObject *pobj_, unsigned flags, AutoIdVector *props)
const Class *clasp = pobj->getClass();
if (pobj->isNative() &&
!pobj->getOps()->enumerate &&
!(clasp->flags & JSCLASS_NEW_ENUMERATE)) {
!(clasp->flags & JSCLASS_NEW_ENUMERATE))
{
if (!clasp->enumerate(cx, pobj))
return false;
if (!EnumerateNativeProperties(cx, pobj, flags, ht, props))
@ -220,6 +244,9 @@ Snapshot(JSContext *cx, JSObject *pobj_, unsigned flags, AutoIdVector *props)
AutoIdVector proxyProps(cx);
if (flags & JSITER_OWNONLY) {
if (flags & JSITER_HIDDEN) {
// This gets all property keys, both strings and
// symbols. The call to Enumerate in the loop below
// will filter out unwanted keys, per the flags.
if (!Proxy::getOwnPropertyNames(cx, pobj, proxyProps))
return false;
} else {
@ -230,11 +257,14 @@ Snapshot(JSContext *cx, JSObject *pobj_, unsigned flags, AutoIdVector *props)
if (!Proxy::enumerate(cx, pobj, proxyProps))
return false;
}
for (size_t n = 0, len = proxyProps.length(); n < len; n++) {
if (!Enumerate(cx, pobj, proxyProps[n], true, flags, ht, props))
return false;
}
/* Proxy objects enumerate the prototype on their own, so we are done here. */
// Proxy objects enumerate the prototype on their own, so we're
// done here.
break;
}
RootedValue state(cx);
@ -318,7 +348,7 @@ js::VectorToIdArray(JSContext *cx, AutoIdVector &props, JSIdArray **idap)
JS_FRIEND_API(bool)
js::GetPropertyNames(JSContext *cx, JSObject *obj, unsigned flags, AutoIdVector *props)
{
return Snapshot(cx, obj, flags & (JSITER_OWNONLY | JSITER_HIDDEN), props);
return Snapshot(cx, obj, flags & (JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS), props);
}
size_t sCustomIteratorCount = 0;

Просмотреть файл

@ -1116,7 +1116,7 @@ bool
js::ReadPropertyDescriptors(JSContext *cx, HandleObject props, bool checkAccessors,
AutoIdVector *ids, AutoPropDescVector *descs)
{
if (!GetPropertyNames(cx, props, JSITER_OWNONLY, ids))
if (!GetPropertyNames(cx, props, JSITER_OWNONLY | JSITER_SYMBOLS, ids))
return false;
RootedId id(cx);
@ -1215,7 +1215,7 @@ JSObject::sealOrFreeze(JSContext *cx, HandleObject obj, ImmutabilityType it)
return false;
AutoIdVector props(cx);
if (!GetPropertyNames(cx, obj, JSITER_HIDDEN | JSITER_OWNONLY, &props))
if (!GetPropertyNames(cx, obj, JSITER_HIDDEN | JSITER_OWNONLY | JSITER_SYMBOLS, &props))
return false;
/* preventExtensions must sparsify dense objects, so we can assign to holes without checks. */
@ -1321,7 +1321,7 @@ JSObject::isSealedOrFrozen(JSContext *cx, HandleObject obj, ImmutabilityType it,
}
AutoIdVector props(cx);
if (!GetPropertyNames(cx, obj, JSITER_HIDDEN | JSITER_OWNONLY, &props))
if (!GetPropertyNames(cx, obj, JSITER_HIDDEN | JSITER_OWNONLY | JSITER_SYMBOLS, &props))
return false;
RootedId id(cx);
@ -1858,7 +1858,7 @@ JS_CopyPropertiesFrom(JSContext *cx, HandleObject target, HandleObject obj)
JSAutoCompartment ac(cx, obj);
AutoIdVector props(cx);
if (!GetPropertyNames(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &props))
if (!GetPropertyNames(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, &props))
return false;
for (size_t i = 0; i < props.length(); ++i) {

Просмотреть файл

@ -239,6 +239,9 @@ BaseProxyHandler::keys(JSContext *cx, HandleObject proxy, AutoIdVector &props)
for (size_t j = 0, len = props.length(); j < len; j++) {
JS_ASSERT(i <= j);
id = props[j];
if (JSID_IS_SYMBOL(id))
continue;
AutoWaivePolicy policy(cx, proxy, id, BaseProxyHandler::GET);
if (!getOwnPropertyDescriptor(cx, proxy, id, &desc))
return false;
@ -419,7 +422,7 @@ DirectProxyHandler::getOwnPropertyNames(JSContext *cx, HandleObject proxy,
{
assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE);
RootedObject target(cx, proxy->as<ProxyObject>().target());
return GetPropertyNames(cx, target, JSITER_OWNONLY | JSITER_HIDDEN, &props);
return GetPropertyNames(cx, target, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, &props);
}
bool
@ -1068,7 +1071,8 @@ class ScriptedDirectProxyHandler : public DirectProxyHandler {
MutableHandle<PropertyDescriptor> desc) MOZ_OVERRIDE;
virtual bool defineProperty(JSContext *cx, HandleObject proxy, HandleId id,
MutableHandle<PropertyDescriptor> desc) MOZ_OVERRIDE;
virtual bool getOwnPropertyNames(JSContext *cx, HandleObject proxy, AutoIdVector &props);
virtual bool getOwnPropertyNames(JSContext *cx, HandleObject proxy, AutoIdVector &props)
MOZ_OVERRIDE;
virtual bool delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) MOZ_OVERRIDE;
virtual bool enumerate(JSContext *cx, HandleObject proxy, AutoIdVector &props) MOZ_OVERRIDE;
@ -1679,7 +1683,8 @@ ScriptedDirectProxyHandler::getOwnPropertyNames(JSContext *cx, HandleObject prox
// Here we add a bunch of extra sanity checks. It is unclear if they will also appear in
// the spec. See step 10-11
return ArrayToIdVector(cx, proxy, target, trapResult, props, JSITER_OWNONLY | JSITER_HIDDEN,
return ArrayToIdVector(cx, proxy, target, trapResult, props,
JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS,
cx->names().getOwnPropertyNames);
}

Просмотреть файл

@ -0,0 +1,41 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/ */
// ES6 draft rev 25 (2014 May 22), 9.1.12 "[[OwnPropertyKeys]] ()":
//
var log;
function LoggingProxy() {
return new Proxy({}, {
defineProperty: (t, key, desc) => {
log.push(key);
return true;
}
});
}
var keys = [
"before",
Symbol(),
"during",
Symbol.for("during"),
Symbol.iterator,
"after"
];
var descs = {};
for (var k of keys)
descs[k] = {configurable: true, value: 0};
function test(descsObj) {
log = [];
Object.defineProperties(LoggingProxy(), descs);
assertEq(log.length, keys.length);
assertDeepEq(log.map(k => typeof k), ["string", "string", "string", "symbol", "symbol", "symbol"]);
for (var key of keys)
assertEq(log.indexOf(key) !== -1, true);
}
test(descs);
test(new Proxy(descs, {}));
if (typeof reportCompare === "function")
reportCompare(0, 0);

Просмотреть файл

@ -0,0 +1,52 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/ */
// for-in loops skip properties with symbol keys, even enumerable properties.
var obj = {};
obj[Symbol.for("moon")] = "sun";
obj[Symbol("asleep")] = "awake";
obj[Symbol.iterator] = "List";
for (var x in obj)
throw "FAIL: " + uneval(x);
// This includes inherited properties.
var obj2 = Object.create(obj);
for (var x in obj2)
throw "FAIL: " + uneval(x);
// The same goes for proxies.
var p = new Proxy(obj, {});
for (var x in p)
throw "FAIL: " + uneval(x);
var p2 = new Proxy(obj2, {});
for (var x in p2)
throw "FAIL: " + uneval(x);
// Object.keys() and .getOwnPropertyNames() also skip symbols.
assertEq(Object.keys(obj).length, 0);
assertEq(Object.keys(p).length, 0);
assertEq(Object.keys(obj2).length, 0);
assertEq(Object.keys(p2).length, 0);
assertEq(Object.getOwnPropertyNames(obj).length, 0);
assertEq(Object.getOwnPropertyNames(p).length, 0);
assertEq(Object.getOwnPropertyNames(obj2).length, 0);
assertEq(Object.getOwnPropertyNames(p2).length, 0);
// Test interaction of Object.keys(), proxies, and symbol property keys.
var log = [];
var h = {
ownKeys: (t) => {
log.push("ownKeys");
return ["a", "0", Symbol.for("moon"), Symbol("asleep"), Symbol.iterator];
},
getOwnPropertyDescriptor: (t, key) => {
log.push("gopd", key);
return {configurable: true, enumerable: true, value: 0, writable: true};
}
};
p = new Proxy({}, h);
assertDeepEq(Object.keys(p), ["a", "0"]);
assertDeepEq(log, ["ownKeys", "gopd", "a", "gopd", "0"]);
if (typeof reportCompare === "function")
reportCompare(0, 0);

Просмотреть файл

@ -0,0 +1,33 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/ */
// ES6 does not specify enumeration order, but implementations mostly retain
// property insertion order -- and must, for web compatibility. This test checks
// that symbol-keyed properties do not interfere with that order.
var obj = {};
obj[Symbol("moon")] = 0;
obj.x = 1;
obj[Symbol.for("y")] = 2
obj.y = 3;
obj[Symbol.iterator] = function* () { yield 4; };
obj.z = 5;
Object.prototype[Symbol.for("comet")] = 6;
var keys = [];
for (var k in obj)
keys.push(k);
assertDeepEq(keys, ["x", "y", "z"]);
assertDeepEq(Object.keys(obj), ["x", "y", "z"]);
// Test with more properties.
for (var i = 0; i < 1000; i++)
obj[Symbol(i)] = i;
obj.w = 1000;
keys = []
for (var k in obj)
keys.push(k);
assertDeepEq(keys, ["x", "y", "z", "w"]);
if (typeof reportCompare === "function")
reportCompare(0, 0);