Backout bbcf9c812bb5:388e4a23e7fb (bug 1090636), for bustage on a CLOSED TREE
authorNathan Froyd <froydnj@mozilla.com>
Fri, 12 Dec 2014 11:49:15 -0500
changeset 219417 cc019512abab61ebf8d13eed58fe9d5013fd952b
parent 219416 bbcf9c812bb5ae72fdb04cc849d4d611e9e15132
child 219418 2954a37bc2df49c799e3ffbc29b8abaad56ccb6b
push id27962
push userkwierso@gmail.com
push dateSat, 13 Dec 2014 00:25:28 +0000
treeherderautoland@fa8bd3194aca [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1090636
milestone37.0a1
backs outbbcf9c812bb5ae72fdb04cc849d4d611e9e15132
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
Backout bbcf9c812bb5:388e4a23e7fb (bug 1090636), for bustage on a CLOSED TREE
js/src/jit-test/tests/auto-regress/bug568855.js
js/src/jit-test/tests/baseline/bug842319.js
js/src/jit-test/tests/basic/bug763440.js
js/src/jit-test/tests/jaeger/bug673788.js
js/src/jit-test/tests/jaeger/bug704138.js
js/src/jit-test/tests/proxy/testDirectProxySet10.js
js/src/jit-test/tests/proxy/testDirectProxySet9.js
js/src/jit-test/tests/proxy/testDirectProxySetArray3.js
js/src/jit-test/tests/proxy/testDirectProxySetArray4.js
js/src/jit-test/tests/proxy/testDirectProxySetInherited.js
js/src/jit-test/tests/proxy/testDirectProxySetNested2.js
js/src/jit-test/tests/proxy/testDirectProxySetReceiverLookup.js
js/src/jsapi.cpp
js/src/proxy/Proxy.cpp
js/src/tests/ecma_6/Reflect/browser.js
js/src/tests/ecma_6/Reflect/set.js
js/src/tests/ecma_6/Reflect/shell.js
js/src/vm/NativeObject-inl.h
js/src/vm/NativeObject.cpp
--- a/js/src/jit-test/tests/auto-regress/bug568855.js
+++ b/js/src/jit-test/tests/auto-regress/bug568855.js
@@ -1,10 +1,7 @@
 // Binary: cache/js-dbg-32-fadb38356e0f-linux
 // Flags: -j
 //
-this.__proto__ = Proxy.create({
-    has:function(){return false},
-    set:function(){}
-});
+this.__proto__ = Proxy.create({has:function(){return false}});
 (function(){
   eval("(function(){ for(var j=0;j<6;++j) if(j%2==1) p=0; })")();
 })()
--- a/js/src/jit-test/tests/baseline/bug842319.js
+++ b/js/src/jit-test/tests/baseline/bug842319.js
@@ -1,8 +1,8 @@
-// |jit-test| error: TypeError
+// |jit-test| error: InternalError
 Array.prototype.__proto__ = Proxy.create({
     getPropertyDescriptor: function(name) {
 	return 0;
     },
 }, null);
 var statusitems = [];
 statusitems[0] = '';
--- a/js/src/jit-test/tests/basic/bug763440.js
+++ b/js/src/jit-test/tests/basic/bug763440.js
@@ -4,18 +4,17 @@ gcPreserveCode()
 function TestCase(n, d, e, a) {
   this.name=n;
 }
 function reportCompare (expected, actual, description) {
   new TestCase
 }
 reportCompare(true, eval++, "Function.prototype.isGenerator present");
 var p = Proxy.create({
-    has : function(id) {},
-    set : function() {}
+    has : function(id) {}
 });
 Object.prototype.__proto__ = p;
 new TestCase;
 var expect = '';
 reportCompare(expect, actual, summary);
 gczeal(4);
 try {
   evalcx(".");
--- a/js/src/jit-test/tests/jaeger/bug673788.js
+++ b/js/src/jit-test/tests/jaeger/bug673788.js
@@ -1,11 +1,10 @@
 // |jit-test| error: ReferenceError
 p = Proxy.create({
-  has: function() {},
-  set: function() {}
+  has: function() {}
 })
 Object.prototype.__proto__ = p
 n = [];
 (function() {
   var a = [];
   if (b) t = a.s()
 })()
--- a/js/src/jit-test/tests/jaeger/bug704138.js
+++ b/js/src/jit-test/tests/jaeger/bug704138.js
@@ -1,17 +1,16 @@
 function TestCase(n, d, e, a)
   this.name=n;
 function reportCompare (expected, actual, description) {
   new TestCase
 }
 reportCompare(true, "isGenerator" in Function, "Function.prototype.isGenerator present");
 var p = Proxy.create({
-    has : function(id) {},
-    set : function(obj, id, v, rec) {}
+    has : function(id) {}
 });
 function test() {
     Object.prototype.__proto__=null
     if (new TestCase)
         Object.prototype.__proto__=p
 }
 test();
 new TestCase;
deleted file mode 100644
--- a/js/src/jit-test/tests/proxy/testDirectProxySet10.js
+++ /dev/null
@@ -1,60 +0,0 @@
-// Assigning to a non-existing property of a plain object defines that
-// property on that object, even if a proxy is on the proto chain.
-
-// Create an object that behaves just like obj except it throws (instead of
-// returning undefined) if you try to get a property that doesn't exist.
-function throwIfNoSuchProperty(obj) {
-    return new Proxy(obj, {
-        get(t, id) {
-            if (id in t)
-                return t[id];
-            throw new Error("no such handler method: " + id);
-        }
-    });
-}
-
-// Use a touchy object as our proxy handler in this test.
-var hits = 0, savedDesc = undefined;
-var touchyHandler = throwIfNoSuchProperty({
-    set: undefined
-});
-var target = {};
-var proto = new Proxy(target, touchyHandler);
-var receiver = Object.create(proto);
-
-// This assignment `receiver.x = 2` results in a series of [[Set]] calls,
-// starting with:
-//
-// - receiver.[[Set]]()
-//     - receiver is an ordinary object.
-//     - This looks for an own property "x" on receiver. There is none.
-//     - So it walks the prototype chain, doing a tail-call to:
-// - proto.[[Set]]()
-//     - proto is a proxy.
-//     - This does handler.[[Get]]("set") to look for a set trap
-//         (that's why we need `set: undefined` on the handler, above)
-//     - Since there's no "set" handler, it tail-calls:
-// - target.[[Set]]()
-//     - ordinary
-//     - no own property "x"
-//     - tail call to:
-// - Object.prototype.[[Set]]()
-//     - ordinary
-//     - no own property "x"
-//     - We're at the end of the line: there's nothing left on the proto chain.
-//     - So at last we call:
-// - receiver.[[DefineOwnProperty]]()
-//     - ordinary
-//     - creates the property
-//
-// Got all that? Let's try it.
-//
-receiver.x = 2;
-assertEq(receiver.x, 2);
-
-var desc = Object.getOwnPropertyDescriptor(receiver, "x");
-assertEq(desc.enumerable, true);
-assertEq(desc.configurable, true);
-assertEq(desc.writable, true);
-assertEq(desc.value, 2);
-
deleted file mode 100644
--- a/js/src/jit-test/tests/proxy/testDirectProxySet9.js
+++ /dev/null
@@ -1,19 +0,0 @@
-// Assigning to a proxy with no set handler calls the defineProperty handler
-// when an existing own data property already exists on the target.
-
-var t = {x: 1};
-var p = new Proxy(t, {
-    defineProperty(t, id, desc) {
-        hits++;
-
-        // ES6 draft rev 28 (2014 Oct 14) 9.1.9 step 5.e.i.
-        // Since the property already exists, the system only changes
-        // the value. desc is otherwise empty.
-        assertEq(Object.getOwnPropertyNames(desc).join(","), "value");
-        assertEq(desc.value, 42);
-    }
-});
-var hits = 0;
-p.x = 42;
-assertEq(hits, 1);
-assertEq(t.x, 1);
deleted file mode 100644
--- a/js/src/jit-test/tests/proxy/testDirectProxySetArray3.js
+++ /dev/null
@@ -1,20 +0,0 @@
-// Assigning to the length property of a proxy to an array
-// calls the proxy's defineProperty handler.
-
-var a = [0, 1, 2, 3];
-var p = new Proxy(a, {
-    defineProperty(t, id, desc) {
-        hits++;
-
-        // ES6 draft rev 28 (2014 Oct 14) 9.1.9 step 5.e.i.
-        // Since the property already exists, the system only changes
-        // the value. desc is otherwise empty.
-        assertEq(Object.getOwnPropertyNames(desc).join(","), "value");
-        assertEq(desc.value, 2);
-    }
-});
-var hits = 0;
-p.length = 2;
-assertEq(hits, 1);
-assertEq(a.length, 4);
-assertEq(a[2], 2);
deleted file mode 100644
--- a/js/src/jit-test/tests/proxy/testDirectProxySetArray4.js
+++ /dev/null
@@ -1,23 +0,0 @@
-// Assigning to an existing array element via a proxy with no set handler
-// calls the defineProperty handler.
-
-function test(arr) {
-    var p = new Proxy(arr, {
-        defineProperty(t, id, desc) {
-            hits++;
-
-            // ES6 draft rev 28 (2014 Oct 14) 9.1.9 step 5.e.i.
-            // Since the property already exists, the system only changes
-            // the value. desc is otherwise empty.
-            assertEq(Object.getOwnPropertyNames(desc).join(","), "value");
-            assertEq(desc.value, "ponies");
-        }
-    });
-    var hits = 0;
-    p[0] = "ponies";
-    assertEq(hits, 1);
-    assertEq(arr[0], 123);
-}
-
-test([123]);
-test(new Int32Array([123]));
deleted file mode 100644
--- a/js/src/jit-test/tests/proxy/testDirectProxySetInherited.js
+++ /dev/null
@@ -1,23 +0,0 @@
-// When assigning to an object with a proxy is on the prototype chain,
-// the proxy's set handler is called.
-
-var C = {};
-var B = new Proxy(C, {
-    get() { throw "FAIL"; },
-    getOwnPropertyDescriptor() { throw "FAIL"; },
-    has() { throw "FAIL"; },
-    defineProperty() { throw "FAIL"; },
-    set(target, id, value, receiver) {
-        hits++;
-        assertEq(target, C);
-        assertEq(id, "x");
-        assertEq(value, 3);
-        assertEq(receiver, A);
-        return true;
-    }
-});
-var A = Object.create(B);
-
-var hits = 0;
-A.x = 3;
-assertEq(hits, 1);
deleted file mode 100644
--- a/js/src/jit-test/tests/proxy/testDirectProxySetNested2.js
+++ /dev/null
@@ -1,16 +0,0 @@
-// The receiver argument is passed through prototype chains and proxies with no "set" handler.
-
-var hits;
-var a = new Proxy({}, {
-    set(t, id, value, receiver) {
-        assertEq(id, "prop");
-        assertEq(value, 3);
-        assertEq(receiver, b);
-        hits++;
-    }
-});
-var b = Object.create(Object.create(new Proxy(Object.create(new Proxy(a, {})), {})));
-hits = 0;
-b.prop = 3;
-assertEq(hits, 1);
-assertEq(b.prop, undefined);
deleted file mode 100644
--- a/js/src/jit-test/tests/proxy/testDirectProxySetReceiverLookup.js
+++ /dev/null
@@ -1,57 +0,0 @@
-// Assigning to a proxy with no set handler causes the proxy's
-// getOwnPropertyDescriptor handler to be called just before defineProperty
-// in some cases. (ES6 draft rev 28, 2014 Oct 14, 9.1.9 step 5.c.)
-
-var attrs = ["configurable", "enumerable", "writable", "value", "get", "set"];
-
-function test(target, id, existingDesc, expectedDesc) {
-    var log = "";
-    var p = new Proxy(target, {
-        getOwnPropertyDescriptor(t, idarg) {
-            assertEq(idarg, id);
-            log += "g";
-            return existingDesc;
-        },
-        defineProperty(t, idarg, desc) {
-            assertEq(idarg, id);
-            for (var attr of attrs) {
-                var args = uneval([target, id, existingDesc]).slice(1, -1);
-                assertEq(attr in desc, attr in expectedDesc,
-                         `test(${args}), checking existence of desc.${attr}`);
-                assertEq(desc[attr], expectedDesc[attr],
-                         `test(${args}), checking value of desc.${attr}`);
-            }
-            log += "d";
-            return true;
-        }
-    });
-    p[id] = "pizza";
-    assertEq(log, "gd");
-}
-
-var fullDesc = {
-    configurable: true,
-    enumerable: true,
-    writable: true,
-    value: "pizza"
-};
-var valueOnlyDesc = {
-    value: "pizza"
-};
-var sealedDesc = {
-    configurable: false,
-    enumerable: true,
-    writable: true,
-    value: "pizza"
-};
-
-test({}, "x", undefined, fullDesc);
-test({}, "x", fullDesc, valueOnlyDesc);
-test({x: 1}, "x", undefined, fullDesc);
-test({x: 1}, "x", fullDesc, valueOnlyDesc);
-test(Object.seal({x: 1}), "x", sealedDesc, valueOnlyDesc);
-test(Object.create({x: 1}), "x", undefined, fullDesc);
-test([0, 1, 2], "2", undefined, fullDesc);
-test([0, 1, 2], "2", fullDesc, valueOnlyDesc);
-test([0, 1, 2], "3", undefined, fullDesc);
-test([0, 1, 2], "3", fullDesc, valueOnlyDesc);
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -2709,20 +2709,33 @@ JS_AlreadyHasOwnPropertyById(JSContext *
         RootedShape prop(cx);
 
         if (!LookupPropertyById(cx, obj, id, &obj2, &prop))
             return false;
         *foundp = (obj == obj2);
         return true;
     }
 
-    RootedNativeObject nativeObj(cx, &obj->as<NativeObject>());
-    RootedShape prop(cx);
-    NativeLookupOwnPropertyNoResolve(cx, nativeObj, id, &prop);
-    *foundp = !!prop;
+    // Check for an existing native property on the object. Be careful not to
+    // call any lookup or resolve hooks.
+    if (JSID_IS_INT(id)) {
+        uint32_t index = JSID_TO_INT(id);
+
+        if (obj->as<NativeObject>().containsDenseElement(index)) {
+            *foundp = true;
+            return true;
+        }
+
+        if (IsAnyTypedArray(obj) && index < AnyTypedArrayLength(obj)) {
+            *foundp = true;
+            return true;
+        }
+    }
+
+    *foundp = obj->as<NativeObject>().contains(cx, id);
     return true;
 }
 
 JS_PUBLIC_API(bool)
 JS_AlreadyHasOwnElement(JSContext *cx, HandleObject obj, uint32_t index, bool *foundp)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
--- a/js/src/proxy/Proxy.cpp
+++ b/js/src/proxy/Proxy.cpp
@@ -324,21 +324,46 @@ Proxy::set(JSContext *cx, HandleObject p
            MutableHandleValue vp)
 {
     JS_CHECK_RECURSION(cx, return false);
     const BaseProxyHandler *handler = proxy->as<ProxyObject>().handler();
     AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true);
     if (!policy.allowed())
         return policy.returnValue();
 
-    // Special case. See the comment on BaseProxyHandler::mHasPrototype.
-    if (handler->hasPrototype())
-        return handler->BaseProxyHandler::set(cx, proxy, receiver, id, strict, vp);
+    // If the proxy doesn't require that we consult its prototype for the
+    // non-own cases, we can sink to the |set| trap.
+    if (!handler->hasPrototype())
+        return handler->set(cx, proxy, receiver, id, strict, vp);
+
+    // If we have an existing (own or non-own) property with a setter, we want
+    // to invoke that.
+    Rooted<PropertyDescriptor> desc(cx);
+    if (!Proxy::getPropertyDescriptor(cx, proxy, id, &desc))
+        return false;
+    if (desc.object() && desc.setter()) {
+        MOZ_ASSERT(desc.setter() != JS_StrictPropertyStub);
+        return CallSetter(cx, receiver, id, desc.setter(), desc.attributes(), strict, vp);
+    }
 
-    return handler->set(cx, proxy, receiver, id, strict, vp);
+    if (desc.isReadonly()) {
+        return strict ? Throw(cx, id, JSMSG_READ_ONLY) : true;
+    }
+
+    // Ok. Either there was no pre-existing property, or it was a value prop
+    // that we're going to shadow. Either way, define a new own property.
+    unsigned attrs =
+        (desc.object() == proxy)
+        ? JSPROP_IGNORE_ENUMERATE | JSPROP_IGNORE_READONLY | JSPROP_IGNORE_PERMANENT
+        : JSPROP_ENUMERATE;
+    const Class *clasp = receiver->getClass();
+    MOZ_ASSERT(clasp->getProperty != JS_PropertyStub);
+    MOZ_ASSERT(clasp->setProperty != JS_StrictPropertyStub);
+    return JSObject::defineGeneric(cx, receiver, id, vp, clasp->getProperty, clasp->setProperty,
+                                   attrs);
 }
 
 bool
 Proxy::getOwnEnumerablePropertyKeys(JSContext *cx, HandleObject proxy, AutoIdVector &props)
 {
     JS_CHECK_RECURSION(cx, return false);
     const BaseProxyHandler *handler = proxy->as<ProxyObject>().handler();
     AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::ENUMERATE, true);
deleted file mode 100644
deleted file mode 100644
--- a/js/src/tests/ecma_6/Reflect/set.js
+++ /dev/null
@@ -1,16 +0,0 @@
-if (this.Reflect && Reflect.set)
-    throw new Error("Congrats on implementing Reflect.set! Uncomment this test for 1 karma point.");
-
-/*
-// Per spec, this should first call p.[[Set]]("0", 42, a) and
-// then (since p has no own properties) a.[[Set]]("0", 42, a).
-// Obviously the latter should not define a property on p.
-
-var a = [0, 1, 2, 3];
-var p = Object.create(a);
-Reflect.set(p, "0", 42, a);
-assertEq(p.hasOwnProperty("0"), false);
-assertEq(a[0], 42);
-*/
-
-reportCompare(0, 0, 'ok');
deleted file mode 100644
--- a/js/src/vm/NativeObject-inl.h
+++ b/js/src/vm/NativeObject-inl.h
@@ -569,46 +569,16 @@ LookupOwnPropertyInline(ExclusiveContext
             return true;
         }
     }
 
     *donep = false;
     return true;
 }
 
-/*
- * Simplified version of LookupOwnPropertyInline that doesn't call resolve
- * hooks.
- */
-static inline void
-NativeLookupOwnPropertyNoResolve(ExclusiveContext *cx, HandleNativeObject obj, HandleId id,
-                                 MutableHandleShape result)
-{
-    // Check for a native dense element.
-    if (JSID_IS_INT(id) && obj->containsDenseElement(JSID_TO_INT(id))) {
-        MarkDenseOrTypedArrayElementFound<CanGC>(result);
-        return;
-    }
-
-    // Check for a typed array element.
-    if (IsAnyTypedArray(obj)) {
-        uint64_t index;
-        if (IsTypedArrayIndex(id, &index)) {
-            if (index < AnyTypedArrayLength(obj))
-                MarkDenseOrTypedArrayElementFound<CanGC>(result);
-            else
-                result.set(nullptr);
-            return;
-        }
-    }
-
-    // Check for a native property.
-    result.set(obj->lookup(cx, id));
-}
-
 template <AllowGC allowGC>
 static MOZ_ALWAYS_INLINE bool
 LookupPropertyInline(ExclusiveContext *cx,
                      typename MaybeRooted<NativeObject*, allowGC>::HandleType obj,
                      typename MaybeRooted<jsid, allowGC>::HandleType id,
                      typename MaybeRooted<JSObject*, allowGC>::MutableHandleType objp,
                      typename MaybeRooted<Shape*, allowGC>::MutableHandleType propp)
 {
--- a/js/src/vm/NativeObject.cpp
+++ b/js/src/vm/NativeObject.cpp
@@ -1467,47 +1467,42 @@ js::DefineNativeProperty(ExclusiveContex
                                                                           ? setter
                                                                           : shape->setter());
                 if (!shape)
                     return false;
                 shouldDefine = false;
             }
         }
     } else if (!(attrs & JSPROP_IGNORE_VALUE)) {
-        // If we did a normal lookup here, it would cause resolve hook recursion in
-        // the following case. Suppose the first script we run in a lazy global is
-        // |parseInt()|.
-        //   - js_InitNumber is called to resolve parseInt.
-        //   - js_InitNumber tries to define the Number constructor on the global.
-        //   - We end up here.
-        //   - This lookup for 'Number' triggers the global resolve hook.
-        //   - js_InitNumber is called again, this time to resolve Number.
-        //   - It creates a second Number constructor, which trips an assertion.
-        //
-        // Therefore we do a special lookup that does not call the resolve hook.
-        NativeLookupOwnPropertyNoResolve(cx, obj, id, &shape);
-
+        /*
+         * We might still want to ignore redefining some of our attributes, if the
+         * request came through a proxy after Object.defineProperty(), but only if we're redefining
+         * a data property.
+         * FIXME: All this logic should be removed when Proxies use PropDesc, but we need to
+         *        remove JSPropertyOp getters and setters first.
+         * FIXME: This is still wrong for various array types, and will set the wrong attributes
+         *        by accident, but we can't use NativeLookupOwnProperty in this case, because of resolve
+         *        loops.
+         */
+        shape = obj->lookup(cx, id);
         if (shape) {
             if (shape->isAccessorDescriptor() &&
                 !CheckAccessorRedefinition(cx, obj, shape, getter, setter, id, attrs))
             {
                 return false;
             }
-
-            // If any other JSPROP_IGNORE_* attributes are present, copy the
-            // corresponding JSPROP_* attributes from the existing property.
-            if (IsImplicitDenseOrTypedArrayElement(shape))
-                attrs = ApplyAttributes(attrs, true, true, !IsAnyTypedArray(obj));
-            else
+            if (shape->isDataDescriptor())
                 attrs = ApplyOrDefaultAttributes(attrs, shape);
         }
     } else {
-        // We have been asked merely to update some attributes. If the
-        // property already exists and it's a data property, we can just
-        // call JSObject::changeProperty.
+        /*
+         * We have been asked merely to update some attributes by a caller of
+         * Object.defineProperty, laundered through the proxy system, and returning here. We can
+         * get away with just using JSObject::changeProperty here.
+         */
         if (!NativeLookupOwnProperty(cx, obj, id, &shape))
             return false;
 
         if (shape) {
             // Don't forget about arrays.
             if (IsImplicitDenseOrTypedArrayElement(shape)) {
                 if (obj->is<TypedArrayObject>()) {
                     /*
@@ -1701,16 +1696,77 @@ NativeGetInline(JSContext *cx,
 
 bool
 js::NativeGet(JSContext *cx, HandleObject obj, HandleNativeObject pobj, HandleShape shape,
               MutableHandleValue vp)
 {
     return NativeGetInline<CanGC>(cx, obj, obj, pobj, shape, vp);
 }
 
+template <ExecutionMode mode>
+static bool
+NativeSet(typename ExecutionModeTraits<mode>::ContextType cxArg, HandleNativeObject obj,
+          HandleObject receiver, HandleShape shape, bool strict, MutableHandleValue vp)
+{
+    MOZ_ASSERT(cxArg->isThreadLocal(obj));
+    MOZ_ASSERT(obj->isNative());
+
+    if (shape->hasSlot()) {
+        /* If shape has a stub setter, just store vp. */
+        if (shape->hasDefaultSetter()) {
+            if (mode == ParallelExecution) {
+                if (!obj->setSlotIfHasType(shape, vp))
+                    return false;
+            } else {
+                // Global properties declared with 'var' will be initially
+                // defined with an undefined value, so don't treat the initial
+                // assignments to such properties as overwrites.
+                bool overwriting = !obj->is<GlobalObject>() || !obj->getSlot(shape->slot()).isUndefined();
+                obj->setSlotWithType(cxArg->asExclusiveContext(), shape, vp, overwriting);
+            }
+
+            return true;
+        }
+    }
+
+    if (mode == ParallelExecution)
+        return false;
+    JSContext *cx = cxArg->asJSContext();
+
+    if (!shape->hasSlot()) {
+        /*
+         * Allow API consumers to create shared properties with stub setters.
+         * Such properties effectively function as data descriptors which are
+         * not writable, so attempting to set such a property should do nothing
+         * or throw if we're in strict mode.
+         */
+        if (!shape->hasGetterValue() && shape->hasDefaultSetter())
+            return js_ReportGetterOnlyAssignment(cx, strict);
+    }
+
+    RootedValue ovp(cx, vp);
+
+    uint32_t sample = cx->runtime()->propertyRemovals;
+    if (!shape->set(cx, obj, receiver, strict, vp))
+        return false;
+
+    /*
+     * Update any slot for the shape with the value produced by the setter,
+     * unless the setter deleted the shape.
+     */
+    if (shape->hasSlot() &&
+        (MOZ_LIKELY(cx->runtime()->propertyRemovals == sample) ||
+         obj->contains(cx, shape)))
+    {
+        obj->setSlot(shape->slot(), vp);
+    }
+
+    return true;
+}
+
 /*
  * Given pc pointing after a property accessing bytecode, return true if the
  * access is "object-detecting" in the sense used by web scripts, e.g., when
  * checking whether document.all is defined.
  */
 static bool
 Detecting(JSContext *cx, JSScript *script, jsbytecode *pc)
 {
@@ -1913,294 +1969,181 @@ MaybeReportUndeclaredVarAssignment(JSCon
     return !!bytes &&
            JS_ReportErrorFlagsAndNumber(cx,
                                         (JSREPORT_WARNING | JSREPORT_STRICT
                                          | JSREPORT_STRICT_MODE_ERROR),
                                         js_GetErrorMessage, nullptr,
                                         JSMSG_UNDECLARED_VAR, bytes.ptr());
 }
 
-
-/*** [[Set]] *************************************************************************************/
-
 /*
  * When a [[Set]] operation finds no existing property with the given id
  * or finds a writable data property on the prototype chain, we end up here.
  * Finish the [[Set]] by defining a new property on receiver.
  *
- * This implements ES6 draft rev 28, 9.1.9 [[Set]] steps 5.c-f, but it
- * is really old code and there are a few barnacles.
+ * This should follow ES6 draft rev 28, 9.1.9 [[Set]] steps 5.c-f, but it
+ * is really old code and we're not there yet.
  */
 template <ExecutionMode mode>
 static bool
 SetPropertyByDefining(typename ExecutionModeTraits<mode>::ContextType cxArg,
-                      HandleNativeObject obj, HandleObject receiver, HandleId id,
-                      HandleValue v, bool strict, bool objHasOwn)
+                      HandleObject receiver, HandleId id, HandleValue v, bool strict)
 {
-    // Step 5.c-d: Test whether receiver has an existing own property
-    // receiver[id]. The spec calls [[GetOwnProperty]]; js::HasOwnProperty is
-    // the same thing except faster in the non-proxy case. Sometimes we can
-    // even optimize away the HasOwnProperty call.
-    bool existing;
-    if (receiver == obj) {
-        // The common case. The caller has necessarily done a property lookup
-        // on obj and passed us the answer as objHasOwn.
-#ifdef DEBUG
-        // Check that objHasOwn is correct. This could fail if receiver or a
-        // native object on its prototype chain has a nondeterministic resolve
-        // hook. We shouldn't have any that are quite that badly behaved.
-        if (mode == SequentialExecution) {
-            if (!HasOwnProperty(cxArg->asJSContext(), receiver, id, &existing))
-                return false;
-            MOZ_ASSERT(existing == objHasOwn);
-        }
-#endif
-        existing = objHasOwn;
-    } else if (mode == ParallelExecution) {
-        // Not the fastest possible implementation, but the fastest possible
-        // without refactoring LookupPropertyPure or duplicating code.
-        NativeObject *npobj;
-        Shape *shape;
-        if (!LookupPropertyPure(cxArg, receiver, id, &npobj, &shape))
+    // If receiver is inextensible, stop. (According to the specification, this
+    // is supposed to be enforced by [[DefineOwnProperty]], but we haven't
+    // implemented that yet.)
+    bool extensible;
+    if (mode == ParallelExecution) {
+        if (receiver->is<ProxyObject>())
             return false;
-        existing = (npobj == receiver);
+        extensible = receiver->nonProxyIsExtensible();
     } else {
-        if (!HasOwnProperty(cxArg->asJSContext(), receiver, id, &existing))
+        if (!JSObject::isExtensible(cxArg->asJSContext(), receiver, &extensible))
             return false;
     }
-
-    // If the property doesn't already exist, check for an inextensible
-    // receiver. (According to the specification, this is supposed to be
-    // enforced by [[DefineOwnProperty]], but we haven't implemented that yet.)
-    if (!existing) {
-        bool extensible;
-        if (mode == ParallelExecution) {
-            if (receiver->is<ProxyObject>())
-                return false;
-            extensible = receiver->nonProxyIsExtensible();
-        } else {
-            if (!JSObject::isExtensible(cxArg->asJSContext(), receiver, &extensible))
-                return false;
+    if (!extensible) {
+        // Error in strict mode code, warn with extra warnings option,
+        // otherwise do nothing.
+        if (strict)
+            return receiver->reportNotExtensible(cxArg);
+        if (mode == SequentialExecution &&
+            cxArg->asJSContext()->compartment()->options().extraWarnings(cxArg->asJSContext()))
+        {
+            return receiver->reportNotExtensible(cxArg, JSREPORT_STRICT | JSREPORT_WARNING);
         }
-        if (!extensible) {
-            // Error in strict mode code, warn with extra warnings option,
-            // otherwise do nothing.
-            if (strict)
-                return receiver->reportNotExtensible(cxArg);
-            if (mode == SequentialExecution &&
-                cxArg->asJSContext()->compartment()->options().extraWarnings(cxArg->asJSContext()))
-            {
-                return receiver->reportNotExtensible(cxArg, JSREPORT_STRICT | JSREPORT_WARNING);
-            }
-            return true;
-        }
+        return true;
     }
 
     // Invalidate SpiderMonkey-specific caches or bail.
     const Class *clasp = receiver->getClass();
     if (mode == ParallelExecution) {
         if (receiver->isDelegate())
             return false;
 
         if (clasp->getProperty || !types::HasTypePropertyId(receiver, id, v))
             return false;
     } else {
         // Purge the property cache of now-shadowed id in receiver's scope chain.
         if (!PurgeScopeChain(cxArg->asJSContext(), receiver, id))
             return false;
     }
 
-    // Step 5.e-f. Define the new data property.
-    unsigned attrs =
-        existing
-        ? JSPROP_IGNORE_ENUMERATE | JSPROP_IGNORE_READONLY | JSPROP_IGNORE_PERMANENT
-        : JSPROP_ENUMERATE;
+    // Define the new data property.
     JSPropertyOp getter = clasp->getProperty;
     JSStrictPropertyOp setter = clasp->setProperty;
     MOZ_ASSERT(getter != JS_PropertyStub);
     MOZ_ASSERT(setter != JS_StrictPropertyStub);
     if (!receiver->is<NativeObject>()) {
         if (mode == ParallelExecution)
             return false;
         return JSObject::defineGeneric(cxArg->asJSContext(), receiver, id, v, getter, setter,
-                                       attrs);
+                                       JSPROP_ENUMERATE);
     }
     Rooted<NativeObject*> nativeReceiver(cxArg, &receiver->as<NativeObject>());
     return DefinePropertyOrElement<mode>(cxArg, nativeReceiver, id, getter, setter,
-                                         attrs, v, true, strict);
+                                         JSPROP_ENUMERATE, v, true, strict);
 }
 
 /*
  * Implement "the rest of" assignment to a property when no property receiver[id]
  * was found anywhere on the prototype chain.
  *
  * FIXME: This should be updated to follow ES6 draft rev 28, section 9.1.9,
  * steps 4.d.i and 5.
  *
  * Note that receiver is not necessarily native.
  */
 template <ExecutionMode mode>
 static bool
 SetNonexistentProperty(typename ExecutionModeTraits<mode>::ContextType cxArg,
-                       HandleNativeObject obj, HandleObject receiver, HandleId id,
-                       baseops::QualifiedBool qualified, HandleValue v, bool strict)
+                       HandleObject receiver, HandleId id, baseops::QualifiedBool qualified,
+                       HandleValue v, bool strict)
 {
     // We should never add properties to lexical blocks.
     MOZ_ASSERT(!receiver->is<BlockObject>());
 
     if (receiver->isUnqualifiedVarObj() && !qualified) {
         if (mode == ParallelExecution)
             return false;
 
         if (!MaybeReportUndeclaredVarAssignment(cxArg->asJSContext(), JSID_TO_STRING(id)))
             return false;
     }
 
-    return SetPropertyByDefining<mode>(cxArg, obj, receiver, id, v, strict, false);
+    return SetPropertyByDefining<mode>(cxArg, receiver, id, v, strict);
 }
 
-/*
- * Set an existing own property obj[index] that's a dense element or typed
- * array element.
- */
 template <ExecutionMode mode>
-static bool
-SetDenseOrTypedArrayElement(typename ExecutionModeTraits<mode>::ContextType cxArg,
-                            HandleNativeObject obj, uint32_t index, MutableHandleValue vp,
-                            bool strict)
-{
-    if (IsAnyTypedArray(obj)) {
-        double d;
-        if (mode == ParallelExecution) {
-            // Bail if converting the value might invoke user-defined
-            // conversions.
-            if (vp.isObject())
-                return false;
-            if (!NonObjectToNumber(cxArg, vp, &d))
-                return false;
-        } else {
-            if (!ToNumber(cxArg->asJSContext(), vp, &d))
-                return false;
-        }
-
-        // Silently do nothing for out-of-bounds sets, for consistency with
-        // current behavior.  (ES6 currently says to throw for this in
-        // strict mode code, so we may eventually need to change.)
-        uint32_t len = AnyTypedArrayLength(obj);
-        if (index < len) {
-            if (obj->is<TypedArrayObject>())
-                TypedArrayObject::setElement(obj->as<TypedArrayObject>(), index, d);
-            else
-                SharedTypedArrayObject::setElement(obj->as<SharedTypedArrayObject>(), index, d);
-        }
-        return true;
-    }
-
-    bool definesPast;
-    if (!WouldDefinePastNonwritableLength(cxArg, obj, index, strict, &definesPast))
-        return false;
-    if (definesPast) {
-        /* Bail out of parallel execution if we are strict to throw. */
-        if (mode == ParallelExecution)
-            return !strict;
-        return true;
-    }
-
-    if (!obj->maybeCopyElementsForWrite(cxArg))
-        return false;
-
-    if (mode == ParallelExecution)
-        return obj->setDenseElementIfHasType(index, vp);
-
-    obj->setDenseElementWithType(cxArg->asJSContext(), index, vp);
-    return true;
-}
-
-/*
- * Finish assignment to a shapeful property of a native object obj. This
- * conforms to no standard and there is a lot of legacy baggage here.
- */
-template <ExecutionMode mode>
-static bool
-NativeSet(typename ExecutionModeTraits<mode>::ContextType cxArg, HandleNativeObject obj,
-          HandleObject receiver, HandleShape shape, bool strict, MutableHandleValue vp)
+bool
+baseops::SetPropertyHelper(typename ExecutionModeTraits<mode>::ContextType cxArg,
+                           HandleNativeObject obj, HandleObject receiver, HandleId id,
+                           QualifiedBool qualified, MutableHandleValue vp, bool strict)
 {
     MOZ_ASSERT(cxArg->isThreadLocal(obj));
-    MOZ_ASSERT(obj->isNative());
+
+    if (MOZ_UNLIKELY(obj->watched())) {
+        if (mode == ParallelExecution)
+            return false;
 
-    if (shape->hasSlot()) {
-        /* If shape has a stub setter, just store vp. */
-        if (shape->hasDefaultSetter()) {
-            if (mode == ParallelExecution) {
-                if (!obj->setSlotIfHasType(shape, vp))
-                    return false;
-            } else {
-                // Global properties declared with 'var' will be initially
-                // defined with an undefined value, so don't treat the initial
-                // assignments to such properties as overwrites.
-                bool overwriting = !obj->is<GlobalObject>() || !obj->getSlot(shape->slot()).isUndefined();
-                obj->setSlotWithType(cxArg->asExclusiveContext(), shape, vp, overwriting);
-            }
-
-            return true;
-        }
+        /* Fire watchpoints, if any. */
+        JSContext *cx = cxArg->asJSContext();
+        WatchpointMap *wpmap = cx->compartment()->watchpointMap;
+        if (wpmap && !wpmap->triggerWatchpoint(cx, obj, id, vp))
+            return false;
     }
 
-    if (mode == ParallelExecution)
-        return false;
-    JSContext *cx = cxArg->asJSContext();
-
-    if (!shape->hasSlot()) {
-        /*
-         * Allow API consumers to create shared properties with stub setters.
-         * Such properties effectively function as data descriptors which are
-         * not writable, so attempting to set such a property should do nothing
-         * or throw if we're in strict mode.
-         */
-        if (!shape->hasGetterValue() && shape->hasDefaultSetter())
-            return js_ReportGetterOnlyAssignment(cx, strict);
+    RootedObject pobj(cxArg);
+    RootedShape shape(cxArg);
+    if (mode == ParallelExecution) {
+        NativeObject *npobj;
+        if (!LookupPropertyPure(cxArg, obj, id, &npobj, shape.address()))
+            return false;
+        pobj = npobj;
+    } else {
+        JSContext *cx = cxArg->asJSContext();
+        if (!LookupNativeProperty(cx, obj, id, &pobj, &shape))
+            return false;
     }
 
-    RootedValue ovp(cx, vp);
+    if (!shape)
+        return SetNonexistentProperty<mode>(cxArg, receiver, id, qualified, vp, strict);
 
-    uint32_t sample = cx->runtime()->propertyRemovals;
-    if (!shape->set(cx, obj, receiver, strict, vp))
-        return false;
+    if (!pobj->isNative()) {
+        if (pobj->is<ProxyObject>()) {
+            if (mode == ParallelExecution)
+                return false;
+
+            JSContext *cx = cxArg->asJSContext();
+            Rooted<PropertyDescriptor> pd(cx);
+            if (!Proxy::getPropertyDescriptor(cx, pobj, id, &pd))
+                return false;
 
-    /*
-     * Update any slot for the shape with the value produced by the setter,
-     * unless the setter deleted the shape.
-     */
-    if (shape->hasSlot() &&
-        (MOZ_LIKELY(cx->runtime()->propertyRemovals == sample) ||
-         obj->contains(cx, shape)))
-    {
-        obj->setSlot(shape->slot(), vp);
+            if ((pd.attributes() & (JSPROP_SHARED | JSPROP_SHADOWABLE)) == JSPROP_SHARED) {
+                return !pd.setter() ||
+                       CallSetter(cx, receiver, id, pd.setter(), pd.attributes(), strict, vp);
+            }
+
+            if (pd.isReadonly()) {
+                if (strict)
+                    return JSObject::reportReadOnly(cx, id, JSREPORT_ERROR);
+                if (cx->compartment()->options().extraWarnings(cx))
+                    return JSObject::reportReadOnly(cx, id, JSREPORT_STRICT | JSREPORT_WARNING);
+                return true;
+            }
+        }
+
+        return SetPropertyByDefining<mode>(cxArg, receiver, id, vp, strict);
     }
 
-    return true;
-}
-
-/*
- * Implement "the rest of" assignment to receiver[id] when an existing property
- * (shape) has been found on a native object (pobj).
- *
- * It is necessary to pass both id and shape because shape could be an implicit
- * dense or typed array element (i.e. not actually a pointer to a Shape).
- */
-template <ExecutionMode mode>
-static bool
-SetExistingProperty(typename ExecutionModeTraits<mode>::ContextType cxArg,
-                    HandleNativeObject obj, HandleObject receiver, HandleId id,
-                    HandleNativeObject pobj, HandleShape shape, MutableHandleValue vp, bool strict)
-{
+    /* Now shape is non-null and obj[id] was found directly in pobj, which is native. */
+    unsigned attrs = JSPROP_ENUMERATE;
     if (IsImplicitDenseOrTypedArrayElement(shape)) {
         /* ES5 8.12.4 [[Put]] step 2, for a dense data property on pobj. */
-        if (pobj == receiver)
-            return SetDenseOrTypedArrayElement<mode>(cxArg, pobj, JSID_TO_INT(id), vp, strict);
+        if (pobj != obj)
+            shape = nullptr;
     } else {
         /* ES5 8.12.4 [[Put]] step 2. */
         if (shape->isAccessorDescriptor()) {
             if (shape->hasDefaultSetter()) {
                 /* Bail out of parallel execution if we are strict to throw. */
                 if (mode == ParallelExecution)
                     return !strict;
 
@@ -2223,141 +2166,96 @@ SetExistingProperty(typename ExecutionMo
                 if (strict)
                     return JSObject::reportReadOnly(cx, id, JSREPORT_ERROR);
                 if (cx->compartment()->options().extraWarnings(cx))
                     return JSObject::reportReadOnly(cx, id, JSREPORT_STRICT | JSREPORT_WARNING);
                 return true;
             }
         }
 
-        if (pobj == receiver) {
-            if (pobj->is<ArrayObject>() && id == NameToId(cxArg->names().length)) {
-                Rooted<ArrayObject*> arr(cxArg, &pobj->as<ArrayObject>());
-                return ArraySetLength<mode>(cxArg, arr, id, shape->attributes(), vp, strict);
-            }
-            return NativeSet<mode>(cxArg, obj, receiver, shape, strict, vp);
-        }
+        if (pobj == obj) {
+            attrs = shape->attributes();
+        } else {
+            // We found id in a prototype object: prepare to share or shadow.
+
+            if (!shape->shadowable()) {
+                if (shape->hasDefaultSetter() && !shape->hasGetterValue())
+                    return true;
 
-        // pobj[id] is not an own property of receiver. Call the setter or shadow it.
-        if (!shape->shadowable() &&
-            !(pobj->is<ArrayObject>() && id == NameToId(cxArg->names().length)))
-        {
-            // Weird special case: slotless property with default setter.
-            if (shape->hasDefaultSetter() && !shape->hasGetterValue())
-                return true;
+                if (mode == ParallelExecution)
+                    return false;
 
-            // We're setting an accessor property.
-            if (mode == ParallelExecution)
-                return false;
-            return shape->set(cxArg->asJSContext(), obj, receiver, strict, vp);
+                return shape->set(cxArg->asJSContext(), obj, receiver, strict, vp);
+            }
+
+            // Forget we found the proto-property since we're shadowing it.
+            shape = nullptr;
         }
     }
 
-    // Shadow pobj[id] by defining a new data property receiver[id].
-    return SetPropertyByDefining<mode>(cxArg, obj, receiver, id, vp, strict, obj == pobj);
-}
+    if (IsImplicitDenseOrTypedArrayElement(shape)) {
+        uint32_t index = JSID_TO_INT(id);
+
+        if (IsAnyTypedArray(obj)) {
+            double d;
+            if (mode == ParallelExecution) {
+                // Bail if converting the value might invoke user-defined
+                // conversions.
+                if (vp.isObject())
+                    return false;
+                if (!NonObjectToNumber(cxArg, vp, &d))
+                    return false;
+            } else {
+                if (!ToNumber(cxArg->asJSContext(), vp, &d))
+                    return false;
+            }
 
-template <ExecutionMode mode>
-bool
-baseops::SetPropertyHelper(typename ExecutionModeTraits<mode>::ContextType cxArg,
-                           HandleNativeObject obj, HandleObject receiver, HandleId id,
-                           QualifiedBool qualified, MutableHandleValue vp, bool strict)
-{
-    MOZ_ASSERT(cxArg->isThreadLocal(obj));
+            // Silently do nothing for out-of-bounds sets, for consistency with
+            // current behavior.  (ES6 currently says to throw for this in
+            // strict mode code, so we may eventually need to change.)
+            uint32_t len = AnyTypedArrayLength(obj);
+            if (index < len) {
+                if (obj->is<TypedArrayObject>())
+                    TypedArrayObject::setElement(obj->as<TypedArrayObject>(), index, d);
+                else
+                    SharedTypedArrayObject::setElement(obj->as<SharedTypedArrayObject>(), index, d);
+            }
+            return true;
+        }
 
-    // Fire watchpoints, if any.
-    if (MOZ_UNLIKELY(obj->watched())) {
-        if (mode == ParallelExecution)
+        bool definesPast;
+        if (!WouldDefinePastNonwritableLength(cxArg, obj, index, strict, &definesPast))
+            return false;
+        if (definesPast) {
+            /* Bail out of parallel execution if we are strict to throw. */
+            if (mode == ParallelExecution)
+                return !strict;
+            return true;
+        }
+
+        if (!obj->maybeCopyElementsForWrite(cxArg))
             return false;
 
-        JSContext *cx = cxArg->asJSContext();
-        WatchpointMap *wpmap = cx->compartment()->watchpointMap;
-        if (wpmap && !wpmap->triggerWatchpoint(cx, obj, id, vp))
-            return false;
+        if (mode == ParallelExecution)
+            return obj->setDenseElementIfHasType(index, vp);
+
+        obj->setDenseElementWithType(cxArg->asJSContext(), index, vp);
+        return true;
     }
 
-    // Step numbers below reference ES6 rev 27 9.1.9, the [[Set]] internal
-    // method for ordinary objects. We substitute our own names for these names
-    // used in the spec: O -> pobj, P -> id, V -> *vp, ownDesc -> shape.
-    RootedShape shape(cxArg);
-    RootedNativeObject pobj(cxArg, obj);
-
-    // This loop isn't explicit in the spec algorithm. See the comment on step
-    // 4.c.i below.
-    for (;;) {
-        // Steps 2-3. ('done' is a SpiderMonkey-specific thing, used below.)
-        bool done;
-        if (mode == ParallelExecution) {
-            // Use the loop in LookupPropertyPure instead of this loop.
-            // It amounts to the same thing.
-            NativeObject *ancestor;
-            if (!LookupPropertyPure(cxArg, pobj, id, &ancestor, shape.address()))
-                return false;
-            done = true;
-            if (shape)
-                pobj = ancestor;
-        } else {
-            RootedObject ancestor(cxArg);
-            if (!LookupOwnPropertyInline<CanGC>(cxArg->asJSContext(), pobj, id, &ancestor,
-                                                &shape, &done))
-            {
-                return false;
-            }
-            if (!done || ancestor != pobj)
-                shape = nullptr;
-        }
-
-        if (shape) {
-            // Steps 5-6.
-            return SetExistingProperty<mode>(cxArg, obj, receiver, id, pobj, shape, vp, strict);
-        }
+    if (obj->is<ArrayObject>() && id == NameToId(cxArg->names().length)) {
+        Rooted<ArrayObject*> arr(cxArg, &obj->as<ArrayObject>());
+        return ArraySetLength<mode>(cxArg, arr, id, attrs, vp, strict);
+    }
 
-        // Steps 4.a-b. The check for 'done' on this next line is tricky.
-        // done can be true in exactly these unlikely-sounding cases:
-        // - We're looking up an element, and pobj is a TypedArray that
-        //   doesn't have that many elements.
-        // - We're being called from a resolve hook to assign to the property
-        //   being resolved.
-        // - We're running in parallel mode so we've already done the whole
-        //   lookup (in LookupPropertyPure above).
-        // What they all have in common is we do not want to keep walking
-        // the prototype chain.
-        RootedObject proto(cxArg, done ? nullptr : pobj->getProto());
-        if (!proto) {
-            // Step 4.d.i (and step 5).
-            return SetNonexistentProperty<mode>(cxArg, obj, receiver, id, qualified, vp, strict);
-        }
+    if (shape)
+        return NativeSet<mode>(cxArg, obj, receiver, shape, strict, vp);
 
-        // Step 4.c.i. If the prototype is also native, this step is a
-        // recursive tail call, and we don't need to go through all the
-        // plumbing of JSObject::setGeneric; the top of the loop is where
-        // we're going to end up anyway. But if pobj is non-native,
-        // that optimization would be incorrect.
-        if (!proto->isNative()) {
-            if (mode == ParallelExecution)
-                return false;
-
-            // Unqualified assignments are not specified to go through [[Set]]
-            // at all, but they do go through this function. So check for
-            // unqualified assignment to a nonexistent global (a strict error).
-            if (!qualified) {
-                RootedObject pobj(cxArg);
-                if (!JSObject::lookupGeneric(cxArg->asJSContext(), proto, id, &pobj, &shape))
-                    return false;
-                if (!shape) {
-                    return SetNonexistentProperty<mode>(cxArg, obj, receiver, id, qualified, vp,
-                                                        strict);
-                }
-            }
-
-            return JSObject::setGeneric(cxArg->asJSContext(), proto, receiver, id, vp,
-                                        strict);
-        }
-        pobj = &proto->as<NativeObject>();
-    }
+    MOZ_ASSERT(attrs == JSPROP_ENUMERATE);
+    return SetPropertyByDefining<mode>(cxArg, receiver, id, vp, strict);
 }
 
 template bool
 baseops::SetPropertyHelper<SequentialExecution>(JSContext *cx, HandleNativeObject obj,
                                                 HandleObject receiver, HandleId id,
                                                 QualifiedBool qualified,
                                                 MutableHandleValue vp, bool strict);
 template bool