Bug 1414768 - Handle same-compartment wrappers in TypedArray methods. r=bz
authorJason Orendorff <jorendorff@mozilla.com>
Tue, 07 Nov 2017 16:59:00 -0600
changeset 449045 8bb6aeb63d5d3c5b9e2a084ff78b82f82b6a7f28
parent 449044 70c6fd6643cf3151c693d23dc1ed0ac9ce3faf3b
child 449046 b6df8ef5dec1491bdea535579016578ae9483ced
push id8527
push userCallek@gmail.com
push dateThu, 11 Jan 2018 21:05:50 +0000
treeherdermozilla-beta@95342d212a7a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs1414768
milestone59.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 1414768 - Handle same-compartment wrappers in TypedArray methods. r=bz CallTypedArrayMethodIfWrapped (and the CallNonGeneric machinery throughout the engine) unwraps the `this` argument, but the other arguments are only rewrapped for the target compartment. The pattern being used before this patch to get the length of a TypedArray or possible TypedArray wrapper is: callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength") The first O is the `this` value and the second is an argument. If O is a cross-compartment wrapper, this works fine. The first O is unwrapped, revealing the actual TypedArray object; the second O is rewrapped for that TypedArray's compartment, producing the same TypedArray. However, if O is a same-compartment wrapper, this doesn't work. The first O is unwrapped, revealing the actual TypedArray object in the same compartment; rewrapping the other O does nothing to it, since it is already an object in the target compartment. Thus TypedArrayLength receives a `this` value that's an unwrapped TypedArray, but an argument that is still a wrapper. The fix is to have CallTypedArrayMethodIfWrapped targets only expect `this` to be an unwrapped TypedArray.
js/src/builtin/TypedArray.js
js/src/jit-test/tests/proxy/testWrapWithProtoIter.js
js/src/jit-test/tests/proxy/testWrapWithProtoTypedArray.js
--- a/js/src/builtin/TypedArray.js
+++ b/js/src/builtin/TypedArray.js
@@ -28,39 +28,44 @@ function IsDetachedBuffer(buffer) {
     // alternative is to give SharedArrayBuffer the same layout as ArrayBuffer.
     if (IsSharedArrayBuffer(buffer))
         return false;
 
     var flags = UnsafeGetInt32FromReservedSlot(buffer, JS_ARRAYBUFFER_FLAGS_SLOT);
     return (flags & JS_ARRAYBUFFER_DETACHED_FLAG) !== 0;
 }
 
+function TypedArrayLengthMethod() {
+    return TypedArrayLength(this);
+}
+
 function GetAttachedArrayBuffer(tarray) {
     var buffer = ViewedArrayBufferIfReified(tarray);
     if (IsDetachedBuffer(buffer))
         ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
     return buffer;
 }
 
+function GetAttachedArrayBufferMethod() {
+    return GetAttachedArrayBuffer(this);
+}
+
 // 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");
+    callFunction(CallTypedArrayMethodIfWrapped, arg, "GetAttachedArrayBufferMethod");
     return false;
 }
 
 // ES2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e
 // 22.2.3.5.1 Runtime Semantics: ValidateTypedArray ( O )
 function ValidateTypedArray(obj, error) {
     if (IsObject(obj)) {
         /* Steps 3-5 (non-wrapped typed arrays). */
@@ -91,18 +96,18 @@ function TypedArrayCreateWithLength(cons
     // Step 2.
     var isTypedArray = ValidateTypedArray(newTypedArray, JSMSG_NON_TYPED_ARRAY_RETURNED);
 
     // Step 3.
     var len;
     if (isTypedArray) {
         len = TypedArrayLength(newTypedArray);
     } else {
-        len = callFunction(CallTypedArrayMethodIfWrapped, newTypedArray, newTypedArray,
-                           "TypedArrayLength");
+        len = callFunction(CallTypedArrayMethodIfWrapped, newTypedArray,
+                           "TypedArrayLengthMethod");
     }
 
     if (len < length)
         ThrowTypeError(JSMSG_SHORT_TYPED_ARRAY_RETURNED, length, len);
 
     // Step 4.
     return newTypedArray;
 }
@@ -252,25 +257,24 @@ function TypedArrayEntries() {
 function TypedArrayEvery(callbackfn/*, thisArg*/) {
     // 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.
+    // If we got here, `this` is either a typed array or a wrapper for one.
 
     // Steps 3-5.
     var len;
     if (isTypedArray)
         len = TypedArrayLength(O);
     else
-        len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength");
+        len = callFunction(CallTypedArrayMethodIfWrapped, O, "TypedArrayLengthMethod");
 
     // 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.
@@ -356,25 +360,24 @@ function TypedArrayFilter(callbackfn/*, 
     // Step 1.
     var O = this;
 
     // Step 2.
     // 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.
+    // If we got here, `this` is either a typed array or a wrapper for one.
 
     // Step 3.
     var len;
     if (isTypedArray)
         len = TypedArrayLength(O);
     else
-        len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength");
+        len = callFunction(CallTypedArrayMethodIfWrapped, O, "TypedArrayLengthMethod");
 
     // Step 4.
     if (arguments.length === 0)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, "%TypedArray%.prototype.filter");
     if (!IsCallable(callbackfn))
         ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
 
     // Step 5.
@@ -418,25 +421,24 @@ function TypedArrayFilter(callbackfn/*, 
 function TypedArrayFind(predicate/*, thisArg*/) {
     // 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.
+    // If we got here, `this` is either a typed array or a wrapper for one.
 
     // Steps 3-5.
     var len;
     if (isTypedArray)
         len = TypedArrayLength(O);
     else
-        len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength");
+        len = callFunction(CallTypedArrayMethodIfWrapped, O, "TypedArrayLengthMethod");
 
     // 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.
@@ -460,25 +462,24 @@ function TypedArrayFind(predicate/*, thi
 function TypedArrayFindIndex(predicate/*, thisArg*/) {
     // 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.
+    // If we got here, `this` is either a typed array or a wrapper for one.
 
     // Steps 3-5.
     var len;
     if (isTypedArray)
         len = TypedArrayLength(O);
     else
-        len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength");
+        len = callFunction(CallTypedArrayMethodIfWrapped, O, "TypedArrayLengthMethod");
 
     // 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.
@@ -500,25 +501,24 @@ function TypedArrayFindIndex(predicate/*
 function TypedArrayForEach(callbackfn/*, thisArg*/) {
     // 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.
+    // If we got here, `this` is either a typed array or a wrapper for one.
 
     // Step 3-4.
     var len;
     if (isTypedArray)
         len = TypedArrayLength(O);
     else
-        len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength");
+        len = callFunction(CallTypedArrayMethodIfWrapped, O, "TypedArrayLengthMethod");
 
     // 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.
@@ -698,25 +698,24 @@ function TypedArrayMap(callbackfn/*, thi
     // Step 1.
     var O = this;
 
     // Step 2.
     // 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.
+    // If we got here, `this` is either a typed array or a wrapper for one.
 
     // Step 3.
     var len;
     if (isTypedArray)
         len = TypedArrayLength(O);
     else
-        len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength");
+        len = callFunction(CallTypedArrayMethodIfWrapped, O, "TypedArrayLengthMethod");
 
     // Step 4.
     if (arguments.length === 0)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, "%TypedArray%.prototype.map");
     if (!IsCallable(callbackfn))
         ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
 
     // Step 5.
@@ -742,25 +741,24 @@ function TypedArrayMap(callbackfn/*, thi
 function TypedArrayReduce(callbackfn/*, initialValue*/) {
     // 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.
+    // If we got here, `this` is either a typed array or a wrapper for one.
 
     // Steps 3-5.
     var len;
     if (isTypedArray)
         len = TypedArrayLength(O);
     else
-        len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength");
+        len = callFunction(CallTypedArrayMethodIfWrapped, O, "TypedArrayLengthMethod");
 
     // 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.
@@ -788,25 +786,24 @@ function TypedArrayReduce(callbackfn/*, 
 function TypedArrayReduceRight(callbackfn/*, initialValue*/) {
     // 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.
+    // If we got here, `this` is either a typed array or a wrapper for one.
 
     // Steps 3-5.
     var len;
     if (isTypedArray)
         len = TypedArrayLength(O);
     else
-        len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength");
+        len = callFunction(CallTypedArrayMethodIfWrapped, O, "TypedArrayLengthMethod");
 
     // 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.
@@ -1061,25 +1058,24 @@ function TypedArraySlice(start, end) {
 function TypedArraySome(callbackfn/*, thisArg*/) {
     // 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.
+    // If we got here, `this` is either a typed array or a wrapper for one.
 
     // Steps 3-5.
     var len;
     if (isTypedArray)
         len = TypedArrayLength(O);
     else
-        len = callFunction(CallTypedArrayMethodIfWrapped, O, O, "TypedArrayLength");
+        len = callFunction(CallTypedArrayMethodIfWrapped, O, "TypedArrayLengthMethod");
 
     // 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.
@@ -1171,25 +1167,25 @@ function TypedArraySort(comparefn) {
 
     // Step 3.
     var isTypedArray = IsObject(obj) && IsTypedArray(obj);
 
     var buffer;
     if (isTypedArray) {
         buffer = GetAttachedArrayBuffer(obj);
     } else {
-        buffer = callFunction(CallTypedArrayMethodIfWrapped, obj, obj, "GetAttachedArrayBuffer");
+        buffer = callFunction(CallTypedArrayMethodIfWrapped, obj, "GetAttachedArrayBufferMethod");
     }
 
     // Step 4.
     var len;
     if (isTypedArray) {
         len = TypedArrayLength(obj);
     } else {
-        len = callFunction(CallTypedArrayMethodIfWrapped, obj, obj, "TypedArrayLength");
+        len = callFunction(CallTypedArrayMethodIfWrapped, obj, "TypedArrayLengthMethod");
     }
 
     // Arrays with less than two elements remain unchanged when sorted.
     if (len <= 1)
         return obj;
 
     if (comparefn === undefined) {
         if (IsUint8TypedArray(obj)) {
@@ -1216,17 +1212,17 @@ function TypedArraySort(comparefn) {
         // Step a.
         var v = comparefn(x, y);
 
         // Step b.
         var length;
         if (isTypedArray) {
             length = TypedArrayLength(obj);
         } else {
-            length = callFunction(CallTypedArrayMethodIfWrapped, obj, obj, "TypedArrayLength");
+            length = callFunction(CallTypedArrayMethodIfWrapped, obj, "TypedArrayLengthMethod");
         }
 
         // It's faster for us to check the typed array's length than to check
         // for detached buffers.
         if (length === 0) {
             assert(PossiblyWrappedTypedArrayHasDetachedBuffer(obj),
                    "Length can only change from non-zero to zero when the buffer was detached");
             ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
@@ -1248,25 +1244,24 @@ function TypedArraySort(comparefn) {
 function TypedArrayToLocaleString(locales = undefined, options = undefined) {
     // ValidateTypedArray, then step 1.
     var array = this;
 
     // This function is not generic.
     // We want to make sure that we have an attached buffer, per spec prose.
     var isTypedArray = IsTypedArrayEnsuringArrayBuffer(array);
 
-    // If we got here, `this` is either a typed array or a cross-compartment
-    // wrapper for one.
+    // If we got here, `this` is either a typed array or a wrapper for one.
 
     // Step 2.
     var len;
     if (isTypedArray)
         len = TypedArrayLength(array);
     else
-        len = callFunction(CallTypedArrayMethodIfWrapped, array, array, "TypedArrayLength");
+        len = callFunction(CallTypedArrayMethodIfWrapped, array, "TypedArrayLengthMethod");
 
     // Step 4.
     if (len === 0)
         return "";
 
     // Step 5.
     var firstElement = array[0];
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/proxy/testWrapWithProtoIter.js
@@ -0,0 +1,1 @@
+[...wrapWithProto(new Int8Array(), new Int8Array())]
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/proxy/testWrapWithProtoTypedArray.js
@@ -0,0 +1,19 @@
+let a = wrapWithProto(new Int8Array([1, 3, 5, 6, 9]), new Int8Array());
+
+assertEq([...a].toString(), "1,3,5,6,9");
+assertEq(a.every(e => e < 100), true);
+assertEq(a.filter(e => e % 2 == 1).toString(), "1,3,5,9");
+assertEq(a.find(e => e > 3), 5);
+assertEq(a.findIndex(e => e % 2 == 0), 3);
+assertEq(a.map(e => e * 10).toString(), "10,30,50,60,90");
+assertEq(a.reduce((a, b) => a + b, ""), "13569");
+assertEq(a.reduceRight((acc, e) => "(" + e + acc + ")", ""), "(1(3(5(6(9)))))");
+assertEq(a.some(e => e % 2 == 0), true);
+
+let s = "";
+assertEq(a.forEach(e => s += e), undefined);
+assertEq(s, "13569");
+
+a.sort((a, b) => b - a);
+assertEq(a.toString(), "9,6,5,3,1");
+