Bug 1111243 - Implement ES6 proxy behavior for IsArray. r=efaust
authorTom Schuster <evilpies@gmail.com>
Sun, 25 Jan 2015 21:42:10 +0100
changeset 225616 c1fb4bf7b043ffc16bcf943ee040938d4f354add
parent 225611 fa91879c842853961a8da90185821384c0d87926
child 225617 cd52e9f67e754a5e18c37225aacff421f27a5ef8
push id28172
push usercbook@mozilla.com
push dateMon, 26 Jan 2015 13:09:46 +0000
treeherdermozilla-central@4368f3945690 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersefaust
bugs1111243
milestone38.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 1111243 - Implement ES6 proxy behavior for IsArray. r=efaust
browser/devtools/app-manager/app-projects.js
js/public/Class.h
js/src/jsarray.cpp
js/src/jsobjinlines.h
js/src/json.cpp
js/src/proxy/ScriptedDirectProxyHandler.cpp
js/src/proxy/ScriptedDirectProxyHandler.h
js/src/tests/ecma_6/TypedArray/entries.js
js/src/tests/ecma_6/TypedArray/every-and-some.js
js/src/tests/ecma_6/TypedArray/fill.js
js/src/tests/ecma_6/TypedArray/includes.js
js/src/tests/ecma_6/TypedArray/indexOf-and-lastIndexOf.js
js/src/tests/ecma_6/TypedArray/join.js
js/src/tests/ecma_6/TypedArray/keys.js
js/src/tests/ecma_6/TypedArray/reduce-and-reduceRight.js
js/src/tests/ecma_6/TypedArray/reverse.js
js/src/tests/ecma_6/TypedArray/values.js
js/src/vm/RegExpObject.h
--- a/browser/devtools/app-manager/app-projects.js
+++ b/browser/devtools/app-manager/app-projects.js
@@ -79,16 +79,18 @@ const IDB = {
     };
 
     return deferred.promise;
   },
 
   add: function(project) {
     let deferred = promise.defer();
 
+    project = JSON.parse(JSON.stringify(project));
+
     if (!project.location) {
       // We need to make sure this object has a `.location` property.
       deferred.reject("Missing location property on project object.");
     } else {
       let transaction = IDB._db.transaction(["projects"], "readwrite");
       let objectStore = transaction.objectStore("projects");
       let request = objectStore.add(project);
       request.onerror = function(event) {
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -579,17 +579,21 @@ Valueify(const JSClass *c)
 
 /*
  * Enumeration describing possible values of the [[Class]] internal property
  * value of objects.
  */
 enum ESClassValue {
     ESClass_Object, ESClass_Array, ESClass_Number, ESClass_String,
     ESClass_Boolean, ESClass_RegExp, ESClass_ArrayBuffer, ESClass_SharedArrayBuffer,
-    ESClass_Date, ESClass_Set, ESClass_Map
+    ESClass_Date, ESClass_Set, ESClass_Map,
+
+    // Special snowflake for the ES6 IsArray method.
+    // Please don't use it without calling that function.
+    ESClass_IsArray
 };
 
 /*
  * Return whether the given object has the given [[Class]] internal property
  * value. Beware, this query says nothing about the js::Class of the JSObject
  * so the caller must not assume anything about obj's representation (e.g., obj
  * may be a proxy).
  */
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -2671,17 +2671,18 @@ js::array_concat(JSContext *cx, unsigned
 
     /* Loop over [0, argc] to concat args into narr, expanding all Arrays. */
     for (unsigned i = 0; i <= argc; i++) {
         if (!CheckForInterrupt(cx))
             return false;
         HandleValue v = HandleValue::fromMarkedLocation(&p[i]);
         if (v.isObject()) {
             RootedObject obj(cx, &v.toObject());
-            if (ObjectClassIs(obj, ESClass_Array, cx)) {
+            // This should be IsConcatSpreadable
+            if (IsArray(obj, cx)) {
                 uint32_t alength;
                 if (!GetLengthProperty(cx, obj, &alength))
                     return false;
                 RootedValue tmp(cx);
                 for (uint32_t slot = 0; slot < alength; slot++) {
                     bool hole;
                     if (!CheckForInterrupt(cx) || !GetElement(cx, obj, slot, &hole, &tmp))
                         return false;
@@ -3034,17 +3035,21 @@ array_filter(JSContext *cx, unsigned arg
     args.rval().setObject(*arr);
     return true;
 }
 
 static bool
 array_isArray(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    bool isArray = args.length() > 0 && IsObjectWithClass(args[0], ESClass_Array, cx);
+    bool isArray = false;
+    if (args.get(0).isObject()) {
+        RootedObject obj(cx, &args[0].toObject());
+        isArray = IsArray(obj, cx);
+    }
     args.rval().setBoolean(isArray);
     return true;
 }
 
 static bool
 IsArrayConstructor(const Value &v)
 {
     // This must only return true if v is *the* Array constructor for the
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -714,17 +714,20 @@ GuessArrayGCKind(size_t numSlots)
 inline bool
 ObjectClassIs(HandleObject obj, ESClassValue classValue, JSContext *cx)
 {
     if (MOZ_UNLIKELY(obj->is<ProxyObject>()))
         return Proxy::objectClassIs(obj, classValue, cx);
 
     switch (classValue) {
       case ESClass_Object: return obj->is<PlainObject>();
-      case ESClass_Array: return obj->is<ArrayObject>();
+      case ESClass_Array:
+      case ESClass_IsArray:
+        // There difference between those is only relevant for proxies.
+        return obj->is<ArrayObject>();
       case ESClass_Number: return obj->is<NumberObject>();
       case ESClass_String: return obj->is<StringObject>();
       case ESClass_Boolean: return obj->is<BooleanObject>();
       case ESClass_RegExp: return obj->is<RegExpObject>();
       case ESClass_ArrayBuffer: return obj->is<ArrayBufferObject>();
       case ESClass_SharedArrayBuffer: return obj->is<SharedArrayBufferObject>();
       case ESClass_Date: return obj->is<DateObject>();
       case ESClass_Set: return obj->is<SetObject>();
@@ -737,16 +740,26 @@ inline bool
 IsObjectWithClass(const Value &v, ESClassValue classValue, JSContext *cx)
 {
     if (!v.isObject())
         return false;
     RootedObject obj(cx, &v.toObject());
     return ObjectClassIs(obj, classValue, cx);
 }
 
+// ES6 7.2.2
+inline bool
+IsArray(HandleObject obj, JSContext *cx)
+{
+    if (obj->is<ArrayObject>())
+        return true;
+
+    return ObjectClassIs(obj, ESClass_IsArray, cx);
+}
+
 inline bool
 Unbox(JSContext *cx, HandleObject obj, MutableHandleValue vp)
 {
     if (MOZ_UNLIKELY(obj->is<ProxyObject>()))
         return Proxy::boxedValue_unbox(cx, obj, vp);
 
     if (obj->is<BooleanObject>())
         vp.setBoolean(obj->as<BooleanObject>().unbox());
--- a/js/src/json.cpp
+++ b/js/src/json.cpp
@@ -317,17 +317,17 @@ JO(JSContext *cx, HandleObject obj, Stri
 
     if (!scx->sb.append('{'))
         return false;
 
     /* Steps 5-7. */
     Maybe<AutoIdVector> ids;
     const AutoIdVector *props;
     if (scx->replacer && !scx->replacer->isCallable()) {
-        MOZ_ASSERT(JS_IsArrayObject(cx, scx->replacer));
+        MOZ_ASSERT(IsArray(scx->replacer, cx));
         props = &scx->propertyList;
     } else {
         MOZ_ASSERT_IF(scx->replacer, scx->propertyList.length() == 0);
         ids.emplace(cx);
         if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY, ids.ptr()))
             return false;
         props = ids.ptr();
     }
@@ -502,17 +502,17 @@ Str(JSContext *cx, const Value &v, Strin
     }
 
     /* Step 10. */
     MOZ_ASSERT(v.isObject());
     RootedObject obj(cx, &v.toObject());
 
     scx->depth++;
     bool ok;
-    if (ObjectClassIs(obj, ESClass_Array, cx))
+    if (IsArray(obj, cx))
         ok = JA(cx, obj, scx);
     else
         ok = JO(cx, obj, scx);
     scx->depth--;
 
     return ok;
 }
 
@@ -524,17 +524,17 @@ js_Stringify(JSContext *cx, MutableHandl
     RootedObject replacer(cx, replacer_);
     RootedValue space(cx, space_);
 
     /* Step 4. */
     AutoIdVector propertyList(cx);
     if (replacer) {
         if (replacer->isCallable()) {
             /* Step 4a(i): use replacer to transform values.  */
-        } else if (ObjectClassIs(replacer, ESClass_Array, cx)) {
+        } else if (IsArray(replacer, cx)) {
             /*
              * Step 4b: The spec algorithm is unhelpfully vague about the exact
              * steps taken when the replacer is an array, regarding the exact
              * sequence of [[Get]] calls for the array's elements, when its
              * overall length is calculated, whether own or own plus inherited
              * properties are considered, and so on.  A rewrite was proposed in
              * <https://mail.mozilla.org/pipermail/es5-discuss/2011-April/003976.html>,
              * whose steps are copied below, and which are implemented here.
@@ -555,17 +555,18 @@ js_Stringify(JSContext *cx, MutableHandl
              *      6. If item is not undefined and item is not currently an
              *         element of PropertyList then,
              *         a. Append item to the end of PropertyList.
              *      7. Let i be i + 1.
              */
 
             /* Step 4b(ii). */
             uint32_t len;
-            JS_ALWAYS_TRUE(GetLengthProperty(cx, replacer, &len));
+            if (!GetLengthProperty(cx, replacer, &len))
+                return false;
             if (replacer->is<ArrayObject>() && !replacer->isIndexed())
                 len = Min(len, replacer->as<ArrayObject>().getDenseInitializedLength());
 
             // Cap the initial size to a moderately small value.  This avoids
             // ridiculous over-allocation if an array with bogusly-huge length
             // is passed in.  If we end up having to add elements past this
             // size, the set will naturally resize to accommodate them.
             const uint32_t MaxInitialSize = 1024;
@@ -688,17 +689,17 @@ Walk(JSContext *cx, HandleObject holder,
     RootedValue val(cx);
     if (!GetProperty(cx, holder, holder, name, &val))
         return false;
 
     /* Step 2. */
     if (val.isObject()) {
         RootedObject obj(cx, &val.toObject());
 
-        if (ObjectClassIs(obj, ESClass_Array, cx)) {
+        if (IsArray(obj, cx)) {
             /* Step 2a(ii). */
             uint32_t length;
             if (!GetLengthProperty(cx, obj, &length))
                 return false;
 
             /* Step 2a(i), 2a(iii-iv). */
             RootedId id(cx);
             RootedValue newElement(cx);
--- a/js/src/proxy/ScriptedDirectProxyHandler.cpp
+++ b/js/src/proxy/ScriptedDirectProxyHandler.cpp
@@ -1090,16 +1090,60 @@ ScriptedDirectProxyHandler::construct(JS
     if (!args.rval().isObject()) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_PROXY_CONSTRUCT_OBJECT);
         return false;
     }
     return true;
 }
 
 bool
+ScriptedDirectProxyHandler::nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
+                                       CallArgs args) const
+{
+    ReportIncompatible(cx, args);
+    return false;
+}
+
+bool
+ScriptedDirectProxyHandler::objectClassIs(HandleObject proxy, ESClassValue classValue,
+                                          JSContext *cx) const
+{
+    // Special case IsArray. In every other instance ES wants to have exactly
+    // one object type and not a proxy around it, so return false.
+    if (classValue != ESClass_IsArray)
+        return false;
+
+    // In ES6 IsArray is supposed to poke at the Proxy target, instead we do this here.
+    // The reason for this is that we have proxies for which looking at the target might
+    // be impossible. So instead we use our little objectClassIs function that just works
+    // already across different wrappers.
+    RootedObject target(cx, proxy->as<ProxyObject>().target());
+    if (!target)
+        return false;
+
+    return IsArray(target, cx);
+}
+
+bool
+ScriptedDirectProxyHandler::regexp_toShared(JSContext *cx, HandleObject proxy,
+                                            RegExpGuard *g) const
+{
+    MOZ_CRASH("Should not end up in ScriptedDirectProxyHandler::regexp_toShared");
+    return false;
+}
+
+bool
+ScriptedDirectProxyHandler::boxedValue_unbox(JSContext *cx, HandleObject proxy,
+                                             MutableHandleValue vp) const
+{
+    MOZ_CRASH("Should not end up in ScriptedDirectProxyHandler::boxedValue_unbox");
+    return false;
+}
+
+bool
 ScriptedDirectProxyHandler::isCallable(JSObject *obj) const
 {
     MOZ_ASSERT(obj->as<ProxyObject>().handler() == &ScriptedDirectProxyHandler::singleton);
     uint32_t callConstruct = obj->as<ProxyObject>().extra(IS_CALLCONSTRUCT_EXTRA).toPrivateUint32();
     return !!(callConstruct & IS_CALLABLE);
 }
 
 bool
--- a/js/src/proxy/ScriptedDirectProxyHandler.h
+++ b/js/src/proxy/ScriptedDirectProxyHandler.h
@@ -59,16 +59,26 @@ class ScriptedDirectProxyHandler : publi
     // Kick getOwnEnumerablePropertyKeys out to ownPropertyKeys and then
     // filter. [[GetOwnProperty]] could potentially change the enumerability of
     // the target's properties.
     virtual bool getOwnEnumerablePropertyKeys(JSContext *cx, HandleObject proxy,
                                               AutoIdVector &props) const MOZ_OVERRIDE {
         return BaseProxyHandler::getOwnEnumerablePropertyKeys(cx, proxy, props);
     }
 
+    // A scripted proxy should not be treated as generic in most contexts.
+    virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
+                            CallArgs args) const MOZ_OVERRIDE;
+    virtual bool objectClassIs(HandleObject obj, ESClassValue classValue,
+                               JSContext *cx) const MOZ_OVERRIDE;
+    virtual bool regexp_toShared(JSContext *cx, HandleObject proxy,
+                                 RegExpGuard *g) const MOZ_OVERRIDE;
+    virtual bool boxedValue_unbox(JSContext *cx, HandleObject proxy,
+                                  MutableHandleValue vp) const MOZ_OVERRIDE;
+
     virtual bool isCallable(JSObject *obj) const MOZ_OVERRIDE;
     virtual bool isConstructor(JSObject *obj) const MOZ_OVERRIDE;
 
     virtual bool isScripted() const MOZ_OVERRIDE { return true; }
 
     static const char family;
     static const ScriptedDirectProxyHandler singleton;
 
--- a/js/src/tests/ecma_6/TypedArray/entries.js
+++ b/js/src/tests/ecma_6/TypedArray/entries.js
@@ -30,20 +30,19 @@ for (var constructor of constructors) {
     if (typeof newGlobal === "function") {
         var entries = newGlobal()[constructor.name].prototype.entries;
         assertDeepEq([...entries.call(new constructor(2))], [[0, 0], [1, 0]]);
         arr = newGlobal()[constructor.name](2);
         assertEq([...constructor.prototype.entries.call(arr)].toString(), "0,0,1,0");
     }
 
     // Throws if `this` isn't a TypedArray.
-    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
+    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
+                            new Proxy(new constructor(), {})];
     invalidReceivers.forEach(invalidReceiver => {
         assertThrowsInstanceOf(() => {
             constructor.prototype.entries.call(invalidReceiver);
         }, TypeError, "Assert that entries fails if this value is not a TypedArray");
     });
-    // FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
-    constructor.prototype.entries.call(new Proxy(new constructor(), {}));
 }
 
 if (typeof reportCompare === "function")
     reportCompare(true, true);
--- a/js/src/tests/ecma_6/TypedArray/every-and-some.js
+++ b/js/src/tests/ecma_6/TypedArray/every-and-some.js
@@ -106,24 +106,23 @@ for (var constructor of constructors) {
     if (typeof newGlobal === "function") {
         var every = newGlobal()[constructor.name].prototype.every;
         var sum = 0;
         assertEq(every.call(new constructor([1, 2, 3]), v => sum += v), true);
         assertEq(sum, 6);
     }
 
     // Throws if `this` isn't a TypedArray.
-    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
+    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
+                            new Proxy(new constructor(), {})];
     invalidReceivers.forEach(invalidReceiver => {
         assertThrowsInstanceOf(() => {
             constructor.prototype.every.call(invalidReceiver, () => true);
         }, TypeError, "Assert that every fails if this value is not a TypedArray");
     });
-    // FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
-    constructor.prototype.every.call(new Proxy(new constructor(), {}), () => true);
 
     // Test that the length getter is never called.
     assertEq(Object.defineProperty(new constructor([1, 2, 3]), "length", {
         get() {
             throw new Error("length accessor called");
         }
     }).every(() => true), true);
 }
@@ -232,30 +231,29 @@ for (var constructor of constructors) {
         assertEq(some.call(new constructor([1, 2, 3]), v => {
             sum += v;
             return false;
         }), false);
         assertEq(sum, 6);
     }
 
     // Throws if `this` isn't a TypedArray.
-    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
+    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
+                            new Proxy(new constructor(), {})];
     invalidReceivers.forEach(invalidReceiver => {
         assertThrowsInstanceOf(() => {
             constructor.prototype.some.call(invalidReceiver, () => true);
         }, TypeError, "Assert that some fails if this value is not a TypedArray");
     });
-    // FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
-    constructor.prototype.some.call(new Proxy(new constructor(), {}), () => false);
 
     // Test that the length getter is never called.
     assertEq(Object.defineProperty(new constructor([1, 2, 3]), "length", {
         get() {
             throw new Error("length accessor called");
         }
     }).some(() => false), false);
 }
 
 assertEq(new Float32Array([undefined, , NaN]).some(v => v === v), false);
 assertEq(new Float64Array([undefined, , NaN]).some(v => v === v), false);
 
 if (typeof reportCompare === "function")
-    reportCompare(true, true);
\ No newline at end of file
+    reportCompare(true, true);
--- a/js/src/tests/ecma_6/TypedArray/fill.js
+++ b/js/src/tests/ecma_6/TypedArray/fill.js
@@ -48,24 +48,23 @@ for (var constructor of constructors) {
 
     // Called from other globals.
     if (typeof newGlobal === "function") {
         var fill = newGlobal()[constructor.name].prototype.fill;
         assertDeepEq(fill.call(new constructor([3, 2, 1]), 2), new constructor([2, 2, 2]));
     }
 
     // Throws if `this` isn't a TypedArray.
-    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./]
+    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
+                            new Proxy(new constructor(), {})];
     invalidReceivers.forEach(invalidReceiver => {
         assertThrowsInstanceOf(() => {
             constructor.prototype.fill.call(invalidReceiver, 1);
         }, TypeError);
     });
-    // FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
-    constructor.prototype.fill.call(new Proxy(new constructor(), {}));
 
     // Test that the length getter is never called.
     Object.defineProperty(new constructor([1, 2, 3]), "length", {
         get() {
             throw new Error("length accessor called");
         }
     }).fill(1);
 }
--- a/js/src/tests/ecma_6/TypedArray/includes.js
+++ b/js/src/tests/ecma_6/TypedArray/includes.js
@@ -30,24 +30,23 @@ for (var constructor of constructors) {
 
     // Called from other globals.
     if (typeof newGlobal === "function") {
         var includes = newGlobal()[constructor.name].prototype.includes;
         assertEq(includes.call(new constructor([1, 2, 3]), 2), true);
     }
 
     // Throws if `this` isn't a TypedArray.
-    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
+    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
+                            new Proxy(new constructor(), {})];
     invalidReceivers.forEach(invalidReceiver => {
         assertThrowsInstanceOf(() => {
             constructor.prototype.includes.call(invalidReceiver);
         }, TypeError, "Assert that reverse fails if this value is not a TypedArray");
     });
-    // FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
-    constructor.prototype.includes.call(new Proxy(new constructor(), {}));
 
     // Test that the length getter is never called.
     assertEq(Object.defineProperty(new constructor([1, 2, 3]), "length", {
         get() {
             throw new Error("length accessor called");
         }
     }).includes(2), true);
 }
--- a/js/src/tests/ecma_6/TypedArray/indexOf-and-lastIndexOf.js
+++ b/js/src/tests/ecma_6/TypedArray/indexOf-and-lastIndexOf.js
@@ -36,24 +36,23 @@ for (var constructor of constructors) {
     assertEq(new constructor([1, 2, 3, 4, 5]).indexOf(1, 1), -1);
     assertEq(new constructor([1, 2, 3, 4, 5]).indexOf(1, -100), 0);
     assertEq(new constructor([1, 2, 3, 4, 5]).indexOf(3, 100), -1);
     assertEq(new constructor([1, 2, 3, 4, 5]).indexOf(5, -1), 4);
     assertEq(new constructor([1, 2, 1, 2, 1]).indexOf(1, 2), 2);
     assertEq(new constructor([1, 2, 1, 2, 1]).indexOf(1, -2), 4);
 
     // Throws if `this` isn't a TypedArray.
-    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
+    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
+                            new Proxy(new constructor(), {})];
     invalidReceivers.forEach(invalidReceiver => {
         assertThrowsInstanceOf(() => {
             constructor.prototype.indexOf.call(invalidReceiver);
         }, TypeError, "Assert that indexOf fails if this value is not a TypedArray");
     });
-    // FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
-    constructor.prototype.indexOf.call(new Proxy(new constructor(), {}));
 
     // test that this.length is never called
     assertEq(Object.defineProperty(new constructor([0, 1, 2, 3, 5]), "length", {
         get() {
             throw new Error("length accessor called");
         }
     }).indexOf(1), 1);
 }
@@ -89,24 +88,23 @@ for (var constructor of constructors) {
     assertEq(new constructor([1, 2, 3, 4, 5]).lastIndexOf(1, 1), 0);
     assertEq(new constructor([1, 2, 3, 4, 5]).lastIndexOf(1, -100), -1);
     assertEq(new constructor([1, 2, 3, 4, 5]).lastIndexOf(3, 100), 2);
     assertEq(new constructor([1, 2, 3, 4, 5]).lastIndexOf(5, -1), 4);
     assertEq(new constructor([1, 2, 1, 2, 1]).lastIndexOf(1, 2), 2);
     assertEq(new constructor([1, 2, 1, 2, 1]).lastIndexOf(1, -2), 2);
 
     // Throws if `this` isn't a TypedArray.
-    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
+    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
+                            new Proxy(new constructor(), {})];
     invalidReceivers.forEach(invalidReceiver => {
         assertThrowsInstanceOf(() => {
             constructor.prototype.lastIndexOf.call(invalidReceiver);
         }, TypeError, "Assert that lastIndexOf fails if this value is not a TypedArray");
     });
-    // FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
-    constructor.prototype.lastIndexOf.call(new Proxy(new constructor(), {}));
 
     // Test that the length getter is never called.
     assertEq(Object.defineProperty(new constructor([0, 1, 2, 3, 5]), "length", {
         get() {
             throw new Error("length accessor called");
         }
     }).lastIndexOf(1), 1);
 }
--- a/js/src/tests/ecma_6/TypedArray/join.js
+++ b/js/src/tests/ecma_6/TypedArray/join.js
@@ -31,24 +31,23 @@ for (var constructor of constructors) {
 
     // Called from other globals.
     if (typeof newGlobal === "function") {
         var join = newGlobal()[constructor.name].prototype.join;
         assertEq(join.call(new constructor([1, 2, 3]), "\t"), "1\t2\t3");
     }
 
     // Throws if `this` isn't a TypedArray.
-    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
+    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
+                            new Proxy(new constructor(), {})];
     invalidReceivers.forEach(invalidReceiver => {
         assertThrowsInstanceOf(() => {
             constructor.prototype.join.call(invalidReceiver);
         }, TypeError, "Assert that join fails if this value is not a TypedArray");
     });
-    // FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
-    constructor.prototype.join.call(new Proxy(new constructor(), {}));
 
     // Test that the length getter is never called.
     assertEq(Object.defineProperty(new constructor([1, 2, 3]), "length", {
         get() {
             throw new Error("length accessor called");
         }
     }).join("\0"), "1\0002\0003");
 }
--- a/js/src/tests/ecma_6/TypedArray/keys.js
+++ b/js/src/tests/ecma_6/TypedArray/keys.js
@@ -30,20 +30,19 @@ for (var constructor of constructors) {
     if (typeof newGlobal === "function") {
         var keys = newGlobal()[constructor.name].prototype.keys;
         assertDeepEq([...keys.call(new constructor(2))], [0, 1]);
         arr = newGlobal()[constructor.name](2);
         assertEq([...constructor.prototype.keys.call(arr)].toString(), "0,1");
     }
 
     // Throws if `this` isn't a TypedArray.
-    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
+    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
+                            new Proxy(new constructor(), {})];
     invalidReceivers.forEach(invalidReceiver => {
         assertThrowsInstanceOf(() => {
             constructor.prototype.keys.call(invalidReceiver);
         }, TypeError, "Assert that keys fails if this value is not a TypedArray");
     });
-    // FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
-    constructor.prototype.keys.call(new Proxy(new constructor(), {}));
 }
 
 if (typeof reportCompare === "function")
     reportCompare(true, true);
--- a/js/src/tests/ecma_6/TypedArray/reduce-and-reduceRight.js
+++ b/js/src/tests/ecma_6/TypedArray/reduce-and-reduceRight.js
@@ -82,24 +82,23 @@ for (var constructor of constructors) {
 
     // Called from other globals.
     if (typeof newGlobal === "function") {
         var reduce = newGlobal()[constructor.name].prototype.reduce;
         assertEq(reduce.call(arr, (previous, current) => Math.min(previous, current)), 1);
     }
 
     // Throws if `this` isn't a TypedArray.
-    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
+    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
+                            new Proxy(new constructor(3), {})];
     invalidReceivers.forEach(invalidReceiver => {
         assertThrowsInstanceOf(() => {
             constructor.prototype.reduce.call(invalidReceiver, () => {});
         }, TypeError, "Assert that reduce fails if this value is not a TypedArray");
     });
-    // FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
-    constructor.prototype.reduce.call(new Proxy(new constructor(3), {}), () => {});
 
     // Test that the length getter is never called.
     assertEq(Object.defineProperty(arr, "length", {
         get() {
             throw new Error("length accessor called");
         }
     }).reduce((previous, current) => Math.max(previous, current)), 5);
 }
@@ -176,27 +175,26 @@ for (var constructor of constructors) {
 
     // Called from other globals.
     if (typeof newGlobal === "function") {
         var reduceRight = newGlobal()[constructor.name].prototype.reduceRight;
         assertEq(reduceRight.call(arr, (previous, current) => Math.min(previous, current)), 1);
     }
 
     // Throws if `this` isn't a TypedArray.
-    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
+    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
+                            new Proxy(new constructor(3), {})];
     invalidReceivers.forEach(invalidReceiver => {
         assertThrowsInstanceOf(() => {
             constructor.prototype.reduceRight.call(invalidReceiver, () => {});
         }, TypeError, "Assert that reduceRight fails if this value is not a TypedArray");
     });
-    // FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
-    constructor.prototype.reduceRight.call(new Proxy(new constructor(3), {}), () => {});
 
     // Test that the length getter is never called.
     assertEq(Object.defineProperty(arr, "length", {
         get() {
             throw new Error("length accessor called");
         }
     }).reduceRight((previous, current) => Math.max(previous, current)), 5);
 }
 
 if (typeof reportCompare === "function")
-    reportCompare(true, true);
\ No newline at end of file
+    reportCompare(true, true);
--- a/js/src/tests/ecma_6/TypedArray/reverse.js
+++ b/js/src/tests/ecma_6/TypedArray/reverse.js
@@ -25,24 +25,23 @@ for (var constructor of constructors) {
 
     // Called from other globals.
     if (typeof newGlobal === "function") {
         var reverse = newGlobal()[constructor.name].prototype.reverse;
         assertDeepEq(reverse.call(new constructor([3, 2, 1])), new constructor([1, 2, 3]));
     }
 
     // Throws if `this` isn't a TypedArray.
-    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
+    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
+                            new Proxy(new constructor(), {})];
     invalidReceivers.forEach(invalidReceiver => {
         assertThrowsInstanceOf(() => {
             constructor.prototype.reverse.call(invalidReceiver);
         }, TypeError, "Assert that reverse fails if this value is not a TypedArray");
     });
-    // FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
-    constructor.prototype.reverse.call(new Proxy(new constructor(), {}));
 
     // Test that the length getter is never called.
     Object.defineProperty(new constructor([1, 2, 3]), "length", {
         get() {
             throw new Error("length accessor called");
         }
     }).reverse();
 }
--- a/js/src/tests/ecma_6/TypedArray/values.js
+++ b/js/src/tests/ecma_6/TypedArray/values.js
@@ -31,20 +31,19 @@ for (var constructor of constructors) {
     if (typeof newGlobal === "function") {
         var values = newGlobal()[constructor.name].prototype.values;
         assertDeepEq([...values.call(new constructor([42, 36]))], [42, 36]);
         arr = newGlobal()[constructor.name]([42, 36]);
         assertEq([...constructor.prototype.values.call(arr)].toString(), "42,36");
     }
 
     // Throws if `this` isn't a TypedArray.
-    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
+    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
+                            new Proxy(new constructor(), {})];
     invalidReceivers.forEach(invalidReceiver => {
         assertThrowsInstanceOf(() => {
             constructor.prototype.values.call(invalidReceiver);
         }, TypeError, "Assert that values fails if this value is not a TypedArray");
     });
-    // FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
-    constructor.prototype.values.call(new Proxy(new constructor(), {}));
 }
 
 if (typeof reportCompare === "function")
     reportCompare(true, true);
--- a/js/src/vm/RegExpObject.h
+++ b/js/src/vm/RegExpObject.h
@@ -484,16 +484,17 @@ bool
 ParseRegExpFlags(JSContext *cx, JSString *flagStr, RegExpFlag *flagsOut);
 
 /* Assuming ObjectClassIs(obj, ESClass_RegExp), return a RegExpShared for obj. */
 inline bool
 RegExpToShared(JSContext *cx, HandleObject obj, RegExpGuard *g)
 {
     if (obj->is<RegExpObject>())
         return obj->as<RegExpObject>().getShared(cx, g);
+    MOZ_ASSERT(Proxy::objectClassIs(obj, ESClass_RegExp, cx));
     return Proxy::regexp_toShared(cx, obj, g);
 }
 
 template<XDRMode mode>
 bool
 XDRScriptRegExpObject(XDRState<mode> *xdr, MutableHandle<RegExpObject*> objp);
 
 extern JSObject *