Bug 1256376. Fix forEach on typed arrays to work over Xrays from web extension sandboxes. r=till
authorBoris Zbarsky <bzbarsky@mit.edu>
Tue, 22 Mar 2016 13:49:58 -0400
changeset 289960 da845f5c14210af5d052ce394567e824f0e3ce82
parent 289959 a45be9c4dd5a282f58d192817c1f2452967593d0
child 289961 1695f5baaf5391868b611d919af60701899c8c94
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstill
bugs1256376
milestone48.0a1
Bug 1256376. Fix forEach on typed arrays to work over Xrays from web extension sandboxes. r=till
js/src/builtin/TypedArray.js
js/src/vm/SelfHosting.cpp
js/xpconnect/tests/chrome/test_xrayToJS.xul
--- a/js/src/builtin/TypedArray.js
+++ b/js/src/builtin/TypedArray.js
@@ -37,16 +37,35 @@ function IsDetachedBuffer(buffer) {
 
 function GetAttachedArrayBuffer(tarray) {
     var buffer = ViewedArrayBufferIfReified(tarray);
     if (IsDetachedBuffer(buffer))
         ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
     return buffer;
 }
 
+// A function which ensures that the argument is either a typed array or a
+// cross-compartment wrapper for a typed array and that the typed array involved
+// has an attached array buffer.  If one of those conditions doesn't hold (wrong
+// kind of argument, or detached array buffer), an exception is thrown.  The
+// return value is `true` if the argument is a typed array, `false` if it's a
+// cross-compartment wrapper for a typed array.
+function IsTypedArrayEnsuringArrayBuffer(arg) {
+    if (IsObject(arg) && IsTypedArray(arg)) {
+        GetAttachedArrayBuffer(arg);
+        return true;
+    }
+
+    // This is a bit hacky but gets the job done: the first `arg` is used to
+    // test for a wrapped typed array, the second as an argument to
+    // GetAttachedArrayBuffer.
+    callFunction(CallTypedArrayMethodIfWrapped, arg, arg, "GetAttachedArrayBuffer");
+    return false;
+}
+
 // ES6 draft 20150304 %TypedArray%.prototype.copyWithin
 function TypedArrayCopyWithin(target, start, end = undefined) {
     // This function is not generic.
     if (!IsObject(this) || !IsTypedArray(this)) {
         return callFunction(CallTypedArrayMethodIfWrapped, this, target, start, end,
                             "TypedArrayCopyWithin");
     }
 
@@ -117,53 +136,51 @@ function TypedArrayCopyWithin(target, st
 // ES6 draft rev30 (2014/12/24) 22.2.3.6 %TypedArray%.prototype.entries()
 function TypedArrayEntries() {
     // Step 1.
     var O = this;
 
     // We need to be a bit careful here, because in the Xray case we want to
     // create the iterator in our current compartment.
     //
-    // Before doing that, though, we want to check that we have a typed
-    // array and it does not have a detached array buffer.  We do the latter by
-    // just calling GetAttachedArrayBuffer() and letting it throw if there isn't
-    // one.  In the case when we're not sure we have a typed array (e.g. we
-    // might have a cross-compartment wrapper for one), we can go ahead and call
-    // GetAttachedArrayBuffer via CallTypedArrayMethodIfWrapped; that will throw
-    // if we're not actually a wrapped typed array, or if we have a detached
-    // array buffer.
+    // Before doing that, though, we want to check that we have a typed array
+    // and it does not have a detached array buffer.  We do the latter by just
+    // calling GetAttachedArrayBuffer() and letting it throw if there isn't one.
+    // In the case when we're not sure we have a typed array (e.g. we might have
+    // a cross-compartment wrapper for one), we can go ahead and call
+    // GetAttachedArrayBuffer via IsTypedArrayEnsuringArrayBuffer; that will
+    // throw if we're not actually a wrapped typed array, or if we have a
+    // detached array buffer.
 
-    // Step 2-3.
-    if (!IsObject(O) || !IsTypedArray(O)) {
-        // And also step 4-6.
-        callFunction(CallTypedArrayMethodIfWrapped, O, O, "GetAttachedArrayBuffer");
-    } else {
-        // Step 4-6.
-        GetAttachedArrayBuffer(O);
-    }
+    // Step 2-6.
+    IsTypedArrayEnsuringArrayBuffer(O);
 
     // Step 7.
     return CreateArrayIterator(O, ITEM_KIND_KEY_AND_VALUE);
 }
 
 // ES6 draft rev30 (2014/12/24) 22.2.3.7 %TypedArray%.prototype.every(callbackfn[, thisArg]).
 function TypedArrayEvery(callbackfn, thisArg = undefined) {
-    // This function is not generic.
-    if (!IsObject(this) || !IsTypedArray(this)) {
-        return callFunction(CallTypedArrayMethodIfWrapped, this, callbackfn, thisArg,
-                            "TypedArrayEvery");
-    }
-
-    GetAttachedArrayBuffer(this);
-
     // Steps 1-2.
     var O = this;
 
+    // This function is not generic.
+    // We want to make sure that we have an attached buffer, per spec prose.
+    var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O);
+
+    // If we got here, `this` is either a typed array or a cross-compartment
+    // wrapper for one.
+
     // Steps 3-5.
-    var len = TypedArrayLength(O);
+    var len;
+    if (isTypedArray) {
+        len = TypedArrayLength(O);
+    } else {
+        len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength");
+    }
 
     // Step 6.
     if (arguments.length === 0)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, "%TypedArray%.prototype.every");
     if (!IsCallable(callbackfn))
         ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
 
     // Step 7.
@@ -230,25 +247,29 @@ function TypedArrayFill(value, start = 0
 
 // ES6 draft 32 (2015-02-02) 22.2.3.9 %TypedArray%.prototype.filter(callbackfn[, thisArg])
 function TypedArrayFilter(callbackfn, thisArg = undefined) {
     // Step 1.
     var O = this;
 
     // Steps 2-3.
     // This function is not generic.
-    if (!IsObject(O) || !IsTypedArray(O)) {
-        return callFunction(CallTypedArrayMethodIfWrapped, this, callbackfn, thisArg,
-                           "TypedArrayFilter");
-    }
+    // We want to make sure that we have an attached buffer, per spec prose.
+    var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O);
 
-    GetAttachedArrayBuffer(O);
+    // If we got here, `this` is either a typed array or a cross-compartment
+    // wrapper for one.
 
     // Step 4.
-    var len = TypedArrayLength(O);
+    var len;
+    if (isTypedArray) {
+        len = TypedArrayLength(O);
+    } else {
+        len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength");
+    }
 
     // Step 5.
     if (arguments.length === 0)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, "%TypedArray%.prototype.filter");
     if (!IsCallable(callbackfn))
         ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
 
     // Step 6.
@@ -291,29 +312,33 @@ function TypedArrayFilter(callbackfn, th
     }
 
     // Step 18.
     return A;
 }
 
 // ES6 draft rev28 (2014/10/14) 22.2.3.10 %TypedArray%.prototype.find(predicate[, thisArg]).
 function TypedArrayFind(predicate, thisArg = undefined) {
-    // This function is not generic.
-    if (!IsObject(this) || !IsTypedArray(this)) {
-        return callFunction(CallTypedArrayMethodIfWrapped, this, predicate, thisArg,
-                            "TypedArrayFind");
-    }
-
-    GetAttachedArrayBuffer(this);
-
     // Steps 1-2.
     var O = this;
 
+    // This function is not generic.
+    // We want to make sure that we have an attached buffer, per spec prose.
+    var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O);
+
+    // If we got here, `this` is either a typed array or a cross-compartment
+    // wrapper for one.
+
     // Steps 3-5.
-    var len = TypedArrayLength(O);
+    var len;
+    if (isTypedArray) {
+        len = TypedArrayLength(O);
+    } else {
+        len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength");
+    }
 
     // Step 6.
     if (arguments.length === 0)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, "%TypedArray%.prototype.find");
     if (!IsCallable(predicate))
         ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, predicate));
 
     // Step 7.
@@ -330,29 +355,33 @@ function TypedArrayFind(predicate, thisA
     }
 
     // Step 10.
     return undefined;
 }
 
 // ES6 draft rev28 (2014/10/14) 22.2.3.11 %TypedArray%.prototype.findIndex(predicate[, thisArg]).
 function TypedArrayFindIndex(predicate, thisArg = undefined) {
-    // This function is not generic.
-    if (!IsObject(this) || !IsTypedArray(this)) {
-        return callFunction(CallTypedArrayMethodIfWrapped, this, predicate, thisArg,
-                            "TypedArrayFindIndex");
-    }
-
-    GetAttachedArrayBuffer(this);
-
     // Steps 1-2.
     var O = this;
 
+    // This function is not generic.
+    // We want to make sure that we have an attached buffer, per spec prose.
+    var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O);
+
+    // If we got here, `this` is either a typed array or a cross-compartment
+    // wrapper for one.
+
     // Steps 3-5.
-    var len = TypedArrayLength(O);
+    var len;
+    if (isTypedArray) {
+        len = TypedArrayLength(O);
+    } else {
+        len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength");
+    }
 
     // Step 6.
     if (arguments.length === 0)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, "%TypedArray%.prototype.findIndex");
     if (!IsCallable(predicate))
         ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, predicate));
 
     // Step 7.
@@ -367,29 +396,33 @@ function TypedArrayFindIndex(predicate, 
     }
 
     // Step 10.
     return -1;
 }
 
 // ES6 draft rev31 (2015-01-15) 22.1.3.10 %TypedArray%.prototype.forEach(callbackfn[,thisArg])
 function TypedArrayForEach(callbackfn, thisArg = undefined) {
-    // This function is not generic.
-    if (!IsObject(this) || !IsTypedArray(this)) {
-        return callFunction(CallTypedArrayMethodIfWrapped, this, callbackfn, thisArg,
-                            "TypedArrayForEach");
-    }
-
-    GetAttachedArrayBuffer(this);
-
     // Step 1-2.
     var O = this;
 
+    // This function is not generic.
+    // We want to make sure that we have an attached buffer, per spec prose.
+    var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O);
+
+    // If we got here, `this` is either a typed array or a cross-compartment
+    // wrapper for one.
+
     // Step 3-4.
-    var len = TypedArrayLength(O);
+    var len;
+    if (isTypedArray) {
+        len = TypedArrayLength(O);
+    } else {
+        len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength");
+    }
 
     // Step 5.
     if (arguments.length === 0)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'TypedArray.prototype.forEach');
     if (!IsCallable(callbackfn))
         ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
 
     // Step 6.
@@ -511,20 +544,17 @@ function TypedArrayJoin(separator) {
 // ES6 draft (2016/1/11) 22.2.3.15 %TypedArray%.prototype.keys()
 function TypedArrayKeys() {
     // Step 1.
     var O = this;
 
     // See the big comment in TypedArrayEntries for what we're doing here.
 
     // Step 2.
-    if (!IsObject(O) || !IsTypedArray(O))
-        callFunction(CallTypedArrayMethodIfWrapped, O, O, "GetAttachedArrayBuffer");
-    else
-        GetAttachedArrayBuffer(O);
+    IsTypedArrayEnsuringArrayBuffer(O);
 
     // Step 3.
     return CreateArrayIterator(O, ITEM_KIND_KEY);
 }
 
 // ES6 draft rev29 (2014/12/06) 22.2.3.16 %TypedArray%.prototype.lastIndexOf(searchElement [,fromIndex]).
 function TypedArrayLastIndexOf(searchElement, fromIndex = undefined) {
     // This function is not generic.
@@ -564,25 +594,29 @@ function TypedArrayLastIndexOf(searchEle
 
 // ES6 draft rev32 (2015-02-02) 22.2.3.18 %TypedArray%.prototype.map(callbackfn [, thisArg]).
 function TypedArrayMap(callbackfn, thisArg = undefined) {
     // Step 1.
     var O = this;
 
     // Steps 2-3.
     // This function is not generic.
-    if (!IsObject(O) || !IsTypedArray(O)) {
-        return callFunction(CallTypedArrayMethodIfWrapped, this, callbackfn, thisArg,
-                            "TypedArrayMap");
-    }
+    // We want to make sure that we have an attached buffer, per spec prose.
+    var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O);
 
-    GetAttachedArrayBuffer(O);
+    // If we got here, `this` is either a typed array or a cross-compartment
+    // wrapper for one.
 
     // Step 4.
-    var len = TypedArrayLength(O);
+    var len;
+    if (isTypedArray) {
+        len = TypedArrayLength(O);
+    } else {
+        len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength");
+    }
 
     // Step 5.
     if (arguments.length === 0)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, '%TypedArray%.prototype.map');
     if (!IsCallable(callbackfn))
         ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
 
     // Step 6.
@@ -606,27 +640,33 @@ function TypedArrayMap(callbackfn, thisA
     }
 
     // Step 14.
     return A;
 }
 
 // ES6 draft rev30 (2014/12/24) 22.2.3.19 %TypedArray%.prototype.reduce(callbackfn[, initialValue]).
 function TypedArrayReduce(callbackfn/*, initialValue*/) {
-    // This function is not generic.
-    if (!IsObject(this) || !IsTypedArray(this))
-        return callFunction(CallTypedArrayMethodIfWrapped, this, callbackfn, "TypedArrayReduce");
-
-    GetAttachedArrayBuffer(this);
-
     // Steps 1-2.
     var O = this;
 
+    // This function is not generic.
+    // We want to make sure that we have an attached buffer, per spec prose.
+    var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O);
+
+    // If we got here, `this` is either a typed array or a cross-compartment
+    // wrapper for one.
+
     // Steps 3-5.
-    var len = TypedArrayLength(O);
+    var len;
+    if (isTypedArray) {
+        len = TypedArrayLength(O);
+    } else {
+        len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength");
+    }
 
     // Step 6.
     if (arguments.length === 0)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, "%TypedArray%.prototype.reduce");
     if (!IsCallable(callbackfn))
         ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
 
     // Step 7.
@@ -647,27 +687,33 @@ function TypedArrayReduce(callbackfn/*, 
     }
 
     // Step 12.
     return accumulator;
 }
 
 // ES6 draft rev30 (2014/12/24) 22.2.3.20 %TypedArray%.prototype.reduceRight(callbackfn[, initialValue]).
 function TypedArrayReduceRight(callbackfn/*, initialValue*/) {
-    // This function is not generic.
-    if (!IsObject(this) || !IsTypedArray(this))
-        return callFunction(CallTypedArrayMethodIfWrapped, this, callbackfn, "TypedArrayReduceRight");
-
-    GetAttachedArrayBuffer(this);
-
     // Steps 1-2.
     var O = this;
 
+    // This function is not generic.
+    // We want to make sure that we have an attached buffer, per spec prose.
+    var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O);
+
+    // If we got here, `this` is either a typed array or a cross-compartment
+    // wrapper for one.
+
     // Steps 3-5.
-    var len = TypedArrayLength(O);
+    var len;
+    if (isTypedArray) {
+        len = TypedArrayLength(O);
+    } else {
+        len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength");
+    }
 
     // Step 6.
     if (arguments.length === 0)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, "%TypedArray%.prototype.reduceRight");
     if (!IsCallable(callbackfn))
         ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
 
     // Step 7.
@@ -908,29 +954,33 @@ function TypedArraySlice(start, end) {
     }
 
     // Step 19.
     return A;
 }
 
 // ES6 draft rev30 (2014/12/24) 22.2.3.25 %TypedArray%.prototype.some(callbackfn[, thisArg]).
 function TypedArraySome(callbackfn, thisArg = undefined) {
-    // This function is not generic.
-    if (!IsObject(this) || !IsTypedArray(this)) {
-        return callFunction(CallTypedArrayMethodIfWrapped, this, callbackfn, thisArg,
-                            "TypedArraySome");
-    }
-
-    GetAttachedArrayBuffer(this);
-
     // Steps 1-2.
     var O = this;
 
+    // This function is not generic.
+    // We want to make sure that we have an attached buffer, per spec prose.
+    var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O);
+
+    // If we got here, `this` is either a typed array or a cross-compartment
+    // wrapper for one.
+
     // Steps 3-5.
-    var len = TypedArrayLength(O);
+    var len;
+    if (isTypedArray) {
+        len = TypedArrayLength(O);
+    } else {
+        len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength");
+    }
 
     // Step 6.
     if (arguments.length === 0)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, "%TypedArray%.prototype.some");
     if (!IsCallable(callbackfn))
         ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
 
     // Step 7.
@@ -981,26 +1031,22 @@ function TypedArrayCompare(x, y) {
     if (Number_isNaN(x) || Number_isNaN(y))
         return Number_isNaN(x) ? 1 : -1;
 
 }
 
 // ES6 draft 20151210 22.2.3.26 %TypedArray%.prototype.sort ( comparefn ).
 function TypedArraySort(comparefn) {
     // This function is not generic.
-    if (!IsObject(this) || !IsTypedArray(this)) {
-        return callFunction(CallTypedArrayMethodIfWrapped, this, comparefn,
-                            "TypedArraySort");
-    }
 
     // Step 1.
     var obj = this;
 
     // Step 2.
-    var buffer = GetAttachedArrayBuffer(obj);
+    var isTypedArray = IsTypedArrayEnsuringArrayBuffer(obj);
 
     // Step 3.
     var len = TypedArrayLength(obj);
 
     if (comparefn === undefined) {
         comparefn = TypedArrayCompare;
         // CountingSort doesn't invoke the comparefn
         if (IsUint8TypedArray(obj)) {
@@ -1013,30 +1059,40 @@ function TypedArraySort(comparefn) {
             return RadixSort(obj, len, 2 /* nbytes */, true /* signed */, false /* floating */, comparefn);
         } else if (IsUint32TypedArray(obj)) {
             return RadixSort(obj, len, 4 /* nbytes */, false /* signed */, false /* floating */, comparefn);
         } else if (IsInt32TypedArray(obj)) {
             return RadixSort(obj, len, 4 /* nbytes */, true /* signed */, false /* floating */, comparefn);
         } else if (IsFloat32TypedArray(obj)) {
             return RadixSort(obj, len, 4 /* nbytes */, true /* signed */, true /* floating */, comparefn);
         }
-        // To satisfy step 2 from TypedArray SortCompare described in 22.2.3.26
-        // the user supplied comparefn is wrapped.
-        var wrappedCompareFn = comparefn;
-        comparefn = function(x, y) {
-            // Step a.
-            var v = wrappedCompareFn(x, y);
-            // Step b.
-            if (IsDetachedBuffer(buffer))
-                ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
-            // Step c. is redundant, see:
-            // https://bugzilla.mozilla.org/show_bug.cgi?id=1121937#c36
-            // Step d.
-            return v;
+    }
+
+    // To satisfy step 2 from TypedArray SortCompare described in 22.2.3.26
+    // the user supplied comparefn is wrapped.
+    var wrappedCompareFn = comparefn;
+    comparefn = function(x, y) {
+        // Step a.
+        var v = wrappedCompareFn(x, y);
+        // Step b.
+        var bufferDetached;
+        if (isTypedArray) {
+            bufferDetached = IsDetachedBuffer(buffer);
+        } else {
+            // This is totally cheating and only works because we know `this`
+            // and `buffer` are same-compartment".
+            bufferDetached = callFunction(CallTypedArrayMethodIfWrapped, this,
+                                          buffer, "IsDetachedBuffer");
         }
+        if (bufferDetached)
+            ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
+        // Step c. is redundant, see:
+        // https://bugzilla.mozilla.org/show_bug.cgi?id=1121937#c36
+        // Step d.
+        return v;
     }
 
     return QuickSort(obj, len, comparefn);
 }
 
 // ES6 draft 20150304 %TypedArray%.prototype.subarray
 function TypedArraySubarray(begin, end) {
     // Step 1.
@@ -1084,25 +1140,17 @@ function TypedArraySubarray(begin, end) 
 }
 
 // ES6 draft rev30 (2014/12/24) 22.2.3.30 %TypedArray%.prototype.values()
 function TypedArrayValues() {
     // Step 1.
     var O = this;
 
     // See the big comment in TypedArrayEntries for what we're doing here.
-
-    // Steps 2-6.
-    if (!IsObject(O) || !IsTypedArray(O)) {
-        // And also steps 4-6.
-        callFunction(CallTypedArrayMethodIfWrapped, O, O, "GetAttachedArrayBuffer");
-    } else {
-        // Steps 4-6.
-        GetAttachedArrayBuffer(O);
-    }
+    IsTypedArrayEnsuringArrayBuffer(O);
 
     // Step 7.
     return CreateArrayIterator(O, ITEM_KIND_VALUE);
 }
 _SetCanonicalName(TypedArrayValues, "values");
 
 // Proposed for ES7:
 // https://github.com/tc39/Array.prototype.includes/blob/7c023c19a0/spec.md
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -1517,26 +1517,41 @@ js::IsCallSelfHostedNonGenericMethod(Nat
 bool
 js::ReportIncompatibleSelfHostedMethod(JSContext* cx, const CallArgs& args)
 {
     // The contract for this function is the same as CallSelfHostedNonGenericMethod.
     // The normal ReportIncompatible function doesn't work for selfhosted functions,
     // because they always call the different CallXXXMethodIfWrapped methods,
     // which would be reported as the called function instead.
 
-    // Lookup the selfhosted method that was invoked.
+    // Lookup the selfhosted method that was invoked.  But skip over
+    // IsTypedArrayEnsuringArrayBuffer frames, because those are never the
+    // actual self-hosted callee from external code.  We can't just skip
+    // self-hosted things until we find a non-self-hosted one because of cases
+    // like array.sort(somethingSelfHosted), where we want to report the error
+    // in the somethingSelfHosted, not in the sort() call.
     ScriptFrameIter iter(cx);
     MOZ_ASSERT(iter.isFunctionFrame());
 
-    JSAutoByteString funNameBytes;
-    if (const char* funName = GetFunctionNameBytes(cx, iter.callee(cx), &funNameBytes)) {
-        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_METHOD,
-                             funName, "method", InformalValueTypeName(args.thisv()));
+    while (!iter.done()) {
+        MOZ_ASSERT(iter.callee(cx)->isSelfHostedOrIntrinsic() &&
+                   !iter.callee(cx)->isBoundFunction());
+        JSAutoByteString funNameBytes;
+        const char* funName = GetFunctionNameBytes(cx, iter.callee(cx), &funNameBytes);
+        if (!funName)
+            return false;
+        if (strcmp(funName, "IsTypedArrayEnsuringArrayBuffer") != 0) {
+            JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_METHOD,
+                                 funName, "method", InformalValueTypeName(args.thisv()));
+            return false;
+        }
+        ++iter;
     }
 
+    MOZ_ASSERT_UNREACHABLE();
     return false;
 }
 
 
 /**
  * Returns the default locale as a well-formed, but not necessarily canonicalized,
  * BCP-47 language tag.
  */
@@ -1624,19 +1639,21 @@ intrinsic_IsConstructing(JSContext* cx, 
 }
 
 static bool
 intrinsic_ConstructorForTypedArray(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 1);
     MOZ_ASSERT(args[0].isObject());
-    MOZ_ASSERT(args[0].toObject().is<TypedArrayObject>());
 
     RootedObject object(cx, &args[0].toObject());
+    object = CheckedUnwrap(object);
+    MOZ_ASSERT(object->is<TypedArrayObject>());
+
     JSProtoKey protoKey = StandardProtoKeyOrNull(object);
     MOZ_ASSERT(protoKey);
 
     // While it may seem like an invariant that in any compartment,
     // seeing a typed array object implies that the TypedArray constructor
     // for that type is initialized on the compartment's global, this is not
     // the case. When we construct a typed array given a cross-compartment
     // ArrayBuffer, we put the constructed TypedArray in the same compartment
--- a/js/xpconnect/tests/chrome/test_xrayToJS.xul
+++ b/js/xpconnect/tests/chrome/test_xrayToJS.xul
@@ -434,16 +434,57 @@ https://bugzilla.mozilla.org/show_bug.cg
     arraysEqual([...arrayLike.entries()], [...equivalentArray.entries()],
                 \`\${reason}; entries\`);
     arraysEqual([...arrayLike.keys()], [...equivalentArray.keys()],
                 \`\${reason}; keys\`);
     if (arrayLike.values) {
       arraysEqual([...arrayLike.values()], equivalentArray,
                   \`\${reason}; values\`);
     }
+
+    var forEachCopy = [];
+    arrayLike.forEach(function(arg) { forEachCopy.push(arg); });
+    arraysEqual(forEachCopy, equivalentArray, \`\${reason}; forEach copy\`);
+
+    var everyCopy = [];
+    arrayLike.every(function(arg) { everyCopy.push(arg); return true; });
+    arraysEqual(everyCopy, equivalentArray, \`\${reason}; every() copy\`);
+
+    var filterCopy = [];
+    var filterResult = arrayLike.filter(function(arg) {
+      filterCopy.push(arg);
+      return true;
+    });
+    arraysEqual(filterCopy, equivalentArray, \`\${reason}; filter copy\`);
+    arraysEqual([...filterResult], equivalentArray, \`\${reason}; filter result\`);
+
+    var findCopy = [];
+    arrayLike.find(function(arg) { findCopy.push(arg); return false; });
+    arraysEqual(findCopy, equivalentArray, \`\${reason}; find() copy\`);
+
+    var findIndexCopy = [];
+    arrayLike.findIndex(function(arg) { findIndexCopy.push(arg); return false; });
+    arraysEqual(findIndexCopy, equivalentArray, \`\${reason}; findIndex() copy\`);
+
+    var mapCopy = [];
+    var mapResult = arrayLike.map(function(arg) { mapCopy.push(arg); return arg});
+    arraysEqual(mapCopy, equivalentArray, \`\${reason}; map() copy\`);
+    arraysEqual([...mapResult], equivalentArray, \`\${reason}; map() result\`);
+
+    var reduceCopy = [];
+    arrayLike.reduce(function(_, arg) { reduceCopy.push(arg); }, 0);
+    arraysEqual(reduceCopy, equivalentArray, \`\${reason}; reduce() copy\`);
+
+    var reduceRightCopy = [];
+    arrayLike.reduceRight(function(_, arg) { reduceRightCopy.unshift(arg); }, 0);
+    arraysEqual(reduceRightCopy, equivalentArray, \`\${reason}; reduceRight() copy\`);
+
+    var someCopy = [];
+    arrayLike.some(function(arg) { someCopy.push(arg); return false; });
+    arraysEqual(someCopy, equivalentArray, \`\${reason}; some() copy\`);
   }`;
   eval(testArrayIteratorsSource);
 
   function testDate() {
     // toGMTString is handled oddly in the engine. We don't bother to support
     // it over Xrays.
     let propsToSkip = ['toGMTString'];