Bug 645416, part 27 - Implement Object.getOwnPropertySymbols(). r=Waldo.
authorJason Orendorff <jorendorff@mozilla.com>
Mon, 23 Jun 2014 10:57:03 -0500
changeset 190294 dfefe211d083191c0ca99f865958d0839cbdc31e
parent 190293 4a04ca5ed7d316f63648a99579575b45d3434a66
child 190295 adc814d90d4d16b83d65034b0f3487f173816452
push id27004
push useremorley@mozilla.com
push dateTue, 24 Jun 2014 15:52:34 +0000
treeherdermozilla-central@7b174d47f3cc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersWaldo
bugs645416
milestone33.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
Bug 645416, part 27 - Implement Object.getOwnPropertySymbols(). r=Waldo.
js/src/builtin/Object.cpp
js/src/builtin/Object.h
js/src/jsfriendapi.h
js/src/jsiter.cpp
js/src/jsproxy.cpp
js/src/tests/ecma_6/Object/getOwnPropertySymbols-proxy.js
js/src/tests/ecma_6/Object/getOwnPropertySymbols.js
--- 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);