Bug 1355554 - Part 1: Handle cross-compartment wrappers in AtomicsObject. r=jorendorff
authorAndré Bargull <andre.bargull@gmail.com>
Wed, 20 May 2020 11:09:24 +0000
changeset 531214 f81d65748d1f3c14a87c266fab7eb3b0cb69addb
parent 531213 d6646e3a3564f3c42c861d84fc2f7841ae218ec0
child 531215 986b2ba8582f0e9adbc68890a946d9956a953f2f
push id37435
push userapavel@mozilla.com
push dateWed, 20 May 2020 15:28:23 +0000
treeherdermozilla-central@5415da14ec9a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs1355554
milestone78.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 1355554 - Part 1: Handle cross-compartment wrappers in AtomicsObject. r=jorendorff Differential Revision: https://phabricator.services.mozilla.com/D24074
js/src/builtin/AtomicsObject.cpp
js/src/jit-test/tests/atomics/cross-compartment-nukeccw.js
js/src/tests/non262/Atomics/browser.js
js/src/tests/non262/Atomics/cross-compartment.js
js/src/tests/non262/Atomics/shell.js
--- a/js/src/builtin/AtomicsObject.cpp
+++ b/js/src/builtin/AtomicsObject.cpp
@@ -64,16 +64,17 @@
 #include "js/Class.h"
 #include "js/PropertySpec.h"
 #include "js/Result.h"
 #include "vm/GlobalObject.h"
 #include "vm/Time.h"
 #include "vm/TypedArrayObject.h"
 #include "wasm/WasmInstance.h"
 
+#include "vm/Compartment-inl.h"
 #include "vm/JSObject-inl.h"
 
 using namespace js;
 
 static bool ReportBadArrayType(JSContext* cx) {
   JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                             JSMSG_ATOMICS_BAD_ARRAY);
   return false;
@@ -83,36 +84,35 @@ static bool ReportOutOfRange(JSContext* 
   // Use JSMSG_BAD_INDEX here, it is what ToIndex uses for some cases that it
   // reports directly.
   JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
   return false;
 }
 
 static bool GetSharedTypedArray(JSContext* cx, HandleValue v, bool waitable,
                                 MutableHandle<TypedArrayObject*> viewp) {
-  if (!v.isObject()) {
-    return ReportBadArrayType(cx);
+  auto* unwrapped = UnwrapAndTypeCheckValue<TypedArrayObject>(
+      cx, v, [cx]() { ReportBadArrayType(cx); });
+  if (!unwrapped) {
+    return false;
   }
-  if (!v.toObject().is<TypedArrayObject>()) {
+  if (!unwrapped->isSharedMemory()) {
     return ReportBadArrayType(cx);
   }
-  viewp.set(&v.toObject().as<TypedArrayObject>());
-  if (!viewp->isSharedMemory()) {
-    return ReportBadArrayType(cx);
-  }
+  viewp.set(unwrapped);
   if (waitable) {
-    switch (viewp->type()) {
+    switch (unwrapped->type()) {
       case Scalar::Int32:
       case Scalar::BigInt64:
         break;
       default:
         return ReportBadArrayType(cx);
     }
   } else {
-    switch (viewp->type()) {
+    switch (unwrapped->type()) {
       case Scalar::Int8:
       case Scalar::Uint8:
       case Scalar::Int16:
       case Scalar::Uint16:
       case Scalar::Int32:
       case Scalar::Uint32:
       case Scalar::BigInt64:
       case Scalar::BigUint64:
@@ -231,26 +231,26 @@ struct ArrayOps<uint64_t> {
     }
     result.setBigInt(bi);
     return Ok();
   }
 };
 
 template <template <typename> class F, typename... Args>
 bool perform(JSContext* cx, HandleValue objv, HandleValue idxv, Args... args) {
-  Rooted<TypedArrayObject*> view(cx, nullptr);
-  if (!GetSharedTypedArray(cx, objv, false, &view)) {
+  Rooted<TypedArrayObject*> unwrappedView(cx);
+  if (!GetSharedTypedArray(cx, objv, false, &unwrappedView)) {
     return false;
   }
   uint32_t offset;
-  if (!GetTypedArrayIndex(cx, idxv, view, &offset)) {
+  if (!GetTypedArrayIndex(cx, idxv, unwrappedView, &offset)) {
     return false;
   }
-  SharedMem<void*> viewData = view->dataPointerShared();
-  switch (view->type()) {
+  SharedMem<void*> viewData = unwrappedView->dataPointerShared();
+  switch (unwrappedView->type()) {
     case Scalar::Int8:
       return F<int8_t>::run(cx, viewData.cast<int8_t*>() + offset, args...);
     case Scalar::Uint8:
       return F<uint8_t>::run(cx, viewData.cast<uint8_t*>() + offset, args...);
     case Scalar::Int16:
       return F<int16_t>::run(cx, viewData.cast<int16_t*>() + offset, args...);
     case Scalar::Uint16:
       return F<uint16_t>::run(cx, viewData.cast<uint16_t*>() + offset, args...);
@@ -576,17 +576,18 @@ FutexThread::WaitResult js::atomics_wait
 
 FutexThread::WaitResult js::atomics_wait_impl(
     JSContext* cx, SharedArrayRawBuffer* sarb, uint32_t byteOffset,
     int64_t value, const mozilla::Maybe<mozilla::TimeDuration>& timeout) {
   return AtomicsWait(cx, sarb, byteOffset, value, timeout);
 }
 
 template <typename T>
-static bool DoAtomicsWait(JSContext* cx, Handle<TypedArrayObject*> view,
+static bool DoAtomicsWait(JSContext* cx,
+                          Handle<TypedArrayObject*> unwrappedView,
                           uint32_t offset, T value, HandleValue timeoutv,
                           MutableHandleValue r) {
   mozilla::Maybe<mozilla::TimeDuration> timeout;
   if (!timeoutv.isUndefined()) {
     double timeout_ms;
     if (!ToNumber(cx, timeoutv, &timeout_ms)) {
       return false;
     }
@@ -595,26 +596,28 @@ static bool DoAtomicsWait(JSContext* cx,
         timeout = mozilla::Some(mozilla::TimeDuration::FromSeconds(0.0));
       } else if (!mozilla::IsInfinite(timeout_ms)) {
         timeout =
             mozilla::Some(mozilla::TimeDuration::FromMilliseconds(timeout_ms));
       }
     }
   }
 
-  Rooted<SharedArrayBufferObject*> sab(cx, view->bufferShared());
+  Rooted<SharedArrayBufferObject*> unwrappedSab(cx,
+                                                unwrappedView->bufferShared());
   // The computation will not overflow because range checks have been
   // performed.
   uint32_t byteOffset =
       offset * sizeof(T) +
-      (view->dataPointerShared().cast<uint8_t*>().unwrap(/* arithmetic */) -
-       sab->dataPointerShared().unwrap(/* arithmetic */));
+      (unwrappedView->dataPointerShared().cast<uint8_t*>().unwrap(
+           /* arithmetic */) -
+       unwrappedSab->dataPointerShared().unwrap(/* arithmetic */));
 
-  switch (atomics_wait_impl(cx, sab->rawBufferObject(), byteOffset, value,
-                            timeout)) {
+  switch (atomics_wait_impl(cx, unwrappedSab->rawBufferObject(), byteOffset,
+                            value, timeout)) {
     case FutexThread::WaitResult::NotEqual:
       r.setString(cx->names().futexNotEqual);
       return true;
     case FutexThread::WaitResult::OK:
       r.setString(cx->names().futexOK);
       return true;
     case FutexThread::WaitResult::TimedOut:
       r.setString(cx->names().futexTimedOut);
@@ -629,41 +632,43 @@ static bool DoAtomicsWait(JSContext* cx,
 bool js::atomics_wait(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   HandleValue objv = args.get(0);
   HandleValue idxv = args.get(1);
   HandleValue valv = args.get(2);
   HandleValue timeoutv = args.get(3);
   MutableHandleValue r = args.rval();
 
-  Rooted<TypedArrayObject*> view(cx, nullptr);
-  if (!GetSharedTypedArray(cx, objv, true, &view)) {
+  Rooted<TypedArrayObject*> unwrappedView(cx);
+  if (!GetSharedTypedArray(cx, objv, true, &unwrappedView)) {
     return false;
   }
-  MOZ_ASSERT(view->type() == Scalar::Int32 || view->type() == Scalar::BigInt64);
+  MOZ_ASSERT(unwrappedView->type() == Scalar::Int32 ||
+             unwrappedView->type() == Scalar::BigInt64);
 
   uint32_t offset;
-  if (!GetTypedArrayIndex(cx, idxv, view, &offset)) {
+  if (!GetTypedArrayIndex(cx, idxv, unwrappedView, &offset)) {
     return false;
   }
 
-  if (view->type() == Scalar::Int32) {
+  if (unwrappedView->type() == Scalar::Int32) {
     int32_t value;
     if (!ToInt32(cx, valv, &value)) {
       return false;
     }
-    return DoAtomicsWait(cx, view, offset, value, timeoutv, r);
+    return DoAtomicsWait(cx, unwrappedView, offset, value, timeoutv, r);
   }
 
-  MOZ_ASSERT(view->type() == Scalar::BigInt64);
+  MOZ_ASSERT(unwrappedView->type() == Scalar::BigInt64);
   RootedBigInt valbi(cx, ToBigInt(cx, valv));
   if (!valbi) {
     return false;
   }
-  return DoAtomicsWait(cx, view, offset, BigInt::toInt64(valbi), timeoutv, r);
+  return DoAtomicsWait(cx, unwrappedView, offset, BigInt::toInt64(valbi),
+                       timeoutv, r);
 }
 
 int64_t js::atomics_notify_impl(SharedArrayRawBuffer* sarb, uint32_t byteOffset,
                                 int64_t count) {
   // Validation should ensure this does not happen.
   MOZ_ASSERT(sarb, "notify is only applicable to shared memory");
 
   AutoLockFutexAPI lock;
@@ -698,51 +703,55 @@ int64_t js::atomics_notify_impl(SharedAr
 
 bool js::atomics_notify(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   HandleValue objv = args.get(0);
   HandleValue idxv = args.get(1);
   HandleValue countv = args.get(2);
   MutableHandleValue r = args.rval();
 
-  Rooted<TypedArrayObject*> view(cx, nullptr);
-  if (!GetSharedTypedArray(cx, objv, true, &view)) {
+  Rooted<TypedArrayObject*> unwrappedView(cx);
+  if (!GetSharedTypedArray(cx, objv, true, &unwrappedView)) {
     return false;
   }
-  MOZ_ASSERT(view->type() == Scalar::Int32 || view->type() == Scalar::BigInt64);
-  uint32_t elementSize =
-      view->type() == Scalar::Int32 ? sizeof(int32_t) : sizeof(int64_t);
+  MOZ_ASSERT(unwrappedView->type() == Scalar::Int32 ||
+             unwrappedView->type() == Scalar::BigInt64);
+  uint32_t elementSize = unwrappedView->type() == Scalar::Int32
+                             ? sizeof(int32_t)
+                             : sizeof(int64_t);
   uint32_t offset;
-  if (!GetTypedArrayIndex(cx, idxv, view, &offset)) {
+  if (!GetTypedArrayIndex(cx, idxv, unwrappedView, &offset)) {
     return false;
   }
   int64_t count;
   if (countv.isUndefined()) {
     count = -1;
   } else {
     double dcount;
     if (!ToInteger(cx, countv, &dcount)) {
       return false;
     }
     if (dcount < 0.0) {
       dcount = 0.0;
     }
     count = dcount < double(1ULL << 63) ? int64_t(dcount) : -1;
   }
 
-  Rooted<SharedArrayBufferObject*> sab(cx, view->bufferShared());
+  Rooted<SharedArrayBufferObject*> unwrappedSab(cx,
+                                                unwrappedView->bufferShared());
   // The computation will not overflow because range checks have been
   // performed.
   uint32_t byteOffset =
       offset * elementSize +
-      (view->dataPointerShared().cast<uint8_t*>().unwrap(/* arithmetic */) -
-       sab->dataPointerShared().unwrap(/* arithmetic */));
+      (unwrappedView->dataPointerShared().cast<uint8_t*>().unwrap(
+           /* arithmetic */) -
+       unwrappedSab->dataPointerShared().unwrap(/* arithmetic */));
 
-  r.setNumber(
-      double(atomics_notify_impl(sab->rawBufferObject(), byteOffset, count)));
+  r.setNumber(double(
+      atomics_notify_impl(unwrappedSab->rawBufferObject(), byteOffset, count)));
 
   return true;
 }
 
 /* static */
 bool js::FutexThread::initialize() {
   MOZ_ASSERT(!lock_);
   lock_ = js_new<js::Mutex>(mutexid::FutexThread);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/atomics/cross-compartment-nukeccw.js
@@ -0,0 +1,219 @@
+load(libdir + "asserts.js");
+
+const otherGlobal = newGlobal({newCompartment: true});
+
+function nukeValue(wrapper, value) {
+  return {
+    valueOf() {
+      nukeCCW(wrapper);
+      return value;
+    }
+  };
+};
+
+function assertIsDeadWrapper(value) {
+  assertTypeErrorMessage(() => value.foo, "can't access dead object");
+}
+
+// Atomics.load
+{
+  let sab = new otherGlobal.SharedArrayBuffer(4);
+  let ta = new otherGlobal.Int32Array(sab);
+  ta[0] = 1;
+
+  let val = Atomics.load(ta, nukeValue(ta, 0));
+
+  assertEq(val, 1);
+  assertIsDeadWrapper(ta);
+}
+{
+  let sab = new otherGlobal.SharedArrayBuffer(4);
+  let ta = new otherGlobal.Int32Array(sab);
+  ta[0] = 1;
+
+  let val = Atomics.load(ta, nukeValue(sab, 0));
+
+  assertEq(val, 1);
+  assertEq(ta[0], 1);
+  assertIsDeadWrapper(sab);
+}
+
+// Atomics.store
+{
+  let sab = new otherGlobal.SharedArrayBuffer(4);
+  let ta = new otherGlobal.Int32Array(sab);
+
+  Atomics.store(ta, 0, nukeValue(ta, 1));
+
+  assertIsDeadWrapper(ta);
+}
+{
+  let sab = new otherGlobal.SharedArrayBuffer(4);
+  let ta = new otherGlobal.Int32Array(sab);
+
+  Atomics.store(ta, 0, nukeValue(sab, 1));
+
+  assertEq(ta[0], 1);
+  assertIsDeadWrapper(sab);
+}
+
+// Atomics.compareExchange
+{
+  let sab = new otherGlobal.SharedArrayBuffer(4);
+  let ta = new otherGlobal.Int32Array(sab);
+  ta[0] = 1;
+
+  let val = Atomics.compareExchange(ta, 0, 1, nukeValue(ta, 2));
+
+  assertEq(val, 1);
+  assertIsDeadWrapper(ta);
+}
+{
+  let sab = new otherGlobal.SharedArrayBuffer(4);
+  let ta = new otherGlobal.Int32Array(sab);
+  ta[0] = 1;
+
+  let val = Atomics.compareExchange(ta, 0, 1, nukeValue(sab, 2));
+
+  assertEq(val, 1);
+  assertEq(ta[0], 2);
+  assertIsDeadWrapper(sab);
+}
+
+// Atomics.exchange
+{
+  let sab = new otherGlobal.SharedArrayBuffer(4);
+  let ta = new otherGlobal.Int32Array(sab);
+  ta[0] = 1;
+
+  let val = Atomics.exchange(ta, 0, nukeValue(ta, 2));
+
+  assertEq(val, 1);
+  assertIsDeadWrapper(ta);
+}
+{
+  let sab = new otherGlobal.SharedArrayBuffer(4);
+  let ta = new otherGlobal.Int32Array(sab);
+  ta[0] = 1;
+
+  let val = Atomics.exchange(ta, 0, nukeValue(sab, 2));
+
+  assertEq(val, 1);
+  assertEq(ta[0], 2);
+  assertIsDeadWrapper(sab);
+}
+
+// Atomics.add
+{
+  let sab = new otherGlobal.SharedArrayBuffer(4);
+  let ta = new otherGlobal.Int32Array(sab);
+  ta[0] = 1;
+
+  let val = Atomics.add(ta, 0, nukeValue(ta, 2));
+
+  assertEq(val, 1);
+  assertIsDeadWrapper(ta);
+}
+{
+  let sab = new otherGlobal.SharedArrayBuffer(4);
+  let ta = new otherGlobal.Int32Array(sab);
+  ta[0] = 1;
+
+  let val = Atomics.add(ta, 0, nukeValue(sab, 2));
+
+  assertEq(val, 1);
+  assertEq(ta[0], 3);
+  assertIsDeadWrapper(sab);
+}
+
+// Atomics.sub
+{
+  let sab = new otherGlobal.SharedArrayBuffer(4);
+  let ta = new otherGlobal.Int32Array(sab);
+  ta[0] = 3;
+
+  let val = Atomics.sub(ta, 0, nukeValue(ta, 2));
+
+  assertEq(val, 3);
+  assertIsDeadWrapper(ta);
+}
+{
+  let sab = new otherGlobal.SharedArrayBuffer(4);
+  let ta = new otherGlobal.Int32Array(sab);
+  ta[0] = 3;
+
+  let val = Atomics.sub(ta, 0, nukeValue(sab, 2));
+
+  assertEq(val, 3);
+  assertEq(ta[0], 1);
+  assertIsDeadWrapper(sab);
+}
+
+// Atomics.and
+{
+  let sab = new otherGlobal.SharedArrayBuffer(4);
+  let ta = new otherGlobal.Int32Array(sab);
+  ta[0] = 3;
+
+  let val = Atomics.and(ta, 0, nukeValue(ta, 1));
+
+  assertEq(val, 3);
+  assertIsDeadWrapper(ta);
+}
+{
+  let sab = new otherGlobal.SharedArrayBuffer(4);
+  let ta = new otherGlobal.Int32Array(sab);
+  ta[0] = 3;
+
+  let val = Atomics.and(ta, 0, nukeValue(sab, 1));
+
+  assertEq(val, 3);
+  assertEq(ta[0], 1);
+  assertIsDeadWrapper(sab);
+}
+
+// Atomics.or
+{
+  let sab = new otherGlobal.SharedArrayBuffer(4);
+  let ta = new otherGlobal.Int32Array(sab);
+  ta[0] = 2;
+
+  let val = Atomics.or(ta, 0, nukeValue(ta, 1));
+
+  assertEq(val, 2);
+  assertIsDeadWrapper(ta);
+}
+{
+  let sab = new otherGlobal.SharedArrayBuffer(4);
+  let ta = new otherGlobal.Int32Array(sab);
+  ta[0] = 2;
+
+  let val = Atomics.or(ta, 0, nukeValue(sab, 1));
+
+  assertEq(val, 2);
+  assertEq(ta[0], 3);
+  assertIsDeadWrapper(sab);
+}
+
+// Atomics.xor
+{
+  let sab = new otherGlobal.SharedArrayBuffer(4);
+  let ta = new otherGlobal.Int32Array(sab);
+  ta[0] = 3;
+
+  let val = Atomics.xor(ta, 0, nukeValue(ta, 1));
+
+  assertEq(val, 3);
+  assertIsDeadWrapper(ta);
+}
+{
+  let sab = new otherGlobal.SharedArrayBuffer(4);
+  let ta = new otherGlobal.Int32Array(sab);
+  ta[0] = 3;
+
+  let val = Atomics.xor(ta, 0, nukeValue(sab, 1));
+
+  assertEq(val, 3);
+  assertEq(ta[0], 2);
+  assertIsDeadWrapper(sab);
+}
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Atomics/cross-compartment.js
@@ -0,0 +1,109 @@
+// |reftest| skip-if(!this.Atomics||!this.SharedArrayBuffer) fails-if(!xulRuntime.shell)
+
+const otherGlobal = newGlobal();
+
+const intArrayConstructors = [
+  otherGlobal.Int32Array,
+  otherGlobal.Int16Array,
+  otherGlobal.Int8Array,
+  otherGlobal.Uint32Array,
+  otherGlobal.Uint16Array,
+  otherGlobal.Uint8Array,
+];
+
+// Atomics.load
+for (let TA of intArrayConstructors) {
+  let ta = new TA(new otherGlobal.SharedArrayBuffer(4));
+  ta[0] = 1;
+
+  assertEq(Atomics.load(ta, 0), 1);
+}
+
+// Atomics.store
+for (let TA of intArrayConstructors) {
+  let ta = new TA(new otherGlobal.SharedArrayBuffer(4));
+
+  Atomics.store(ta, 0, 1);
+
+  assertEq(ta[0], 1);
+}
+
+// Atomics.compareExchange
+for (let TA of intArrayConstructors) {
+  let ta = new TA(new otherGlobal.SharedArrayBuffer(4));
+  ta[0] = 1;
+
+  let val = Atomics.compareExchange(ta, 0, 1, 2);
+
+  assertEq(val, 1);
+  assertEq(ta[0], 2);
+}
+
+// Atomics.exchange
+for (let TA of intArrayConstructors) {
+  let ta = new TA(new otherGlobal.SharedArrayBuffer(4));
+  ta[0] = 1;
+
+  let val = Atomics.exchange(ta, 0, 2);
+
+  assertEq(val, 1);
+  assertEq(ta[0], 2);
+}
+
+// Atomics.add
+for (let TA of intArrayConstructors) {
+  let ta = new TA(new otherGlobal.SharedArrayBuffer(4));
+  ta[0] = 1;
+
+  let val = Atomics.add(ta, 0, 2);
+
+  assertEq(val, 1);
+  assertEq(ta[0], 3);
+}
+
+// Atomics.sub
+for (let TA of intArrayConstructors) {
+  let ta = new TA(new otherGlobal.SharedArrayBuffer(4));
+  ta[0] = 3;
+
+  let val = Atomics.sub(ta, 0, 2);
+
+  assertEq(val, 3);
+  assertEq(ta[0], 1);
+}
+
+// Atomics.and
+for (let TA of intArrayConstructors) {
+  let ta = new TA(new otherGlobal.SharedArrayBuffer(4));
+  ta[0] = 3;
+
+  let val = Atomics.and(ta, 0, 1);
+
+  assertEq(val, 3);
+  assertEq(ta[0], 1);
+}
+
+// Atomics.or
+for (let TA of intArrayConstructors) {
+  let ta = new TA(new otherGlobal.SharedArrayBuffer(4));
+  ta[0] = 2;
+
+  let val = Atomics.or(ta, 0, 1);
+
+  assertEq(val, 2);
+  assertEq(ta[0], 3);
+}
+
+// Atomics.xor
+for (let TA of intArrayConstructors) {
+  let ta = new TA(new otherGlobal.SharedArrayBuffer(4));
+  ta[0] = 3;
+
+  let val = Atomics.xor(ta, 0, 1);
+
+  assertEq(val, 3);
+  assertEq(ta[0], 2);
+}
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
new file mode 100644