1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  * vim: set ts=8 sts=2 et sw=2 tw=80:
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "vm/SharedArrayObject.h"
8 
9 #include "mozilla/Atomics.h"
10 #include "mozilla/CheckedInt.h"
11 #include "mozilla/DebugOnly.h"
12 
13 #include "gc/FreeOp.h"
14 #include "jit/AtomicOperations.h"
15 #include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
16 #include "js/PropertySpec.h"
17 #include "js/SharedArrayBuffer.h"
18 #include "js/Wrapper.h"
19 #include "util/Memory.h"
20 #include "vm/SharedMem.h"
21 #include "wasm/WasmMemory.h"
22 #include "wasm/WasmSignalHandlers.h"
23 
24 #include "vm/ArrayBufferObject-inl.h"
25 #include "vm/JSObject-inl.h"
26 #include "vm/NativeObject-inl.h"
27 
28 using js::wasm::Pages;
29 using mozilla::CheckedInt;
30 using mozilla::DebugOnly;
31 using mozilla::Maybe;
32 using mozilla::Nothing;
33 using mozilla::Some;
34 
35 using namespace js;
36 
SharedArrayAccessibleSize(size_t length)37 static size_t SharedArrayAccessibleSize(size_t length) {
38   return AlignBytes(length, gc::SystemPageSize());
39 }
40 
41 // The mapped size for a plain shared array buffer, used only for tracking
42 // memory usage. This is incorrect for some WASM cases, and for hypothetical
43 // callers of js::SharedArrayBufferObject::createFromNewRawBuffer that do not
44 // currently exist, but it's fine as a signal of GC pressure.
SharedArrayMappedSize(size_t length)45 static size_t SharedArrayMappedSize(size_t length) {
46   return SharedArrayAccessibleSize(length) + gc::SystemPageSize();
47 }
48 
49 // `wasmSourceMaxPages` must always be something for wasm and nothing for
50 // other users.
AllocateInternal(wasm::IndexType wasmIndexType,size_t length,const Maybe<wasm::Pages> & wasmClampedMaxPages,const Maybe<wasm::Pages> & wasmSourceMaxPages,const Maybe<size_t> & wasmMappedSize)51 SharedArrayRawBuffer* SharedArrayRawBuffer::AllocateInternal(
52     wasm::IndexType wasmIndexType, size_t length,
53     const Maybe<wasm::Pages>& wasmClampedMaxPages,
54     const Maybe<wasm::Pages>& wasmSourceMaxPages,
55     const Maybe<size_t>& wasmMappedSize) {
56   MOZ_RELEASE_ASSERT(length <= ArrayBufferObject::maxBufferByteLength());
57 
58   size_t accessibleSize = SharedArrayAccessibleSize(length);
59   if (accessibleSize < length) {
60     return nullptr;
61   }
62 
63   bool preparedForWasm = wasmClampedMaxPages.isSome();
64   size_t computedMappedSize;
65   if (preparedForWasm) {
66     computedMappedSize = wasmMappedSize.isSome()
67                              ? *wasmMappedSize
68                              : wasm::ComputeMappedSize(*wasmClampedMaxPages);
69   } else {
70     MOZ_ASSERT(wasmMappedSize.isNothing());
71     computedMappedSize = accessibleSize;
72   }
73   MOZ_ASSERT(accessibleSize <= computedMappedSize);
74 
75   uint64_t mappedSizeWithHeader = computedMappedSize + gc::SystemPageSize();
76   uint64_t accessibleSizeWithHeader = accessibleSize + gc::SystemPageSize();
77 
78   void* p = MapBufferMemory(wasmIndexType, mappedSizeWithHeader,
79                             accessibleSizeWithHeader);
80   if (!p) {
81     return nullptr;
82   }
83 
84   uint8_t* buffer = reinterpret_cast<uint8_t*>(p) + gc::SystemPageSize();
85   uint8_t* base = buffer - sizeof(SharedArrayRawBuffer);
86   SharedArrayRawBuffer* rawbuf = new (base) SharedArrayRawBuffer(
87       wasmIndexType, buffer, length, wasmClampedMaxPages.valueOr(Pages(0)),
88       wasmSourceMaxPages.valueOr(Pages(0)), computedMappedSize,
89       preparedForWasm);
90   MOZ_ASSERT(rawbuf->length_ == length);  // Deallocation needs this
91   return rawbuf;
92 }
93 
Allocate(size_t length)94 SharedArrayRawBuffer* SharedArrayRawBuffer::Allocate(size_t length) {
95   return SharedArrayRawBuffer::AllocateInternal(
96       wasm::IndexType::I32, length, Nothing(), Nothing(), Nothing());
97 }
98 
AllocateWasm(wasm::IndexType indexType,Pages initialPages,wasm::Pages clampedMaxPages,const mozilla::Maybe<wasm::Pages> & sourceMaxPages,const mozilla::Maybe<size_t> & mappedSize)99 SharedArrayRawBuffer* SharedArrayRawBuffer::AllocateWasm(
100     wasm::IndexType indexType, Pages initialPages, wasm::Pages clampedMaxPages,
101     const mozilla::Maybe<wasm::Pages>& sourceMaxPages,
102     const mozilla::Maybe<size_t>& mappedSize) {
103   // Prior code has asserted that initial pages is within our implementation
104   // limits (wasm::MaxMemoryPages()) and we can assume it is a valid size_t.
105   MOZ_ASSERT(initialPages.hasByteLength());
106   size_t length = initialPages.byteLength();
107   return SharedArrayRawBuffer::AllocateInternal(
108       indexType, length, Some(clampedMaxPages), sourceMaxPages, mappedSize);
109 }
110 
tryGrowMaxPagesInPlace(Pages deltaMaxPages)111 void SharedArrayRawBuffer::tryGrowMaxPagesInPlace(Pages deltaMaxPages) {
112   Pages newMaxPages = wasmClampedMaxPages_;
113   DebugOnly<bool> valid = newMaxPages.checkedIncrement(deltaMaxPages);
114   // Caller must ensure increment does not overflow or increase over the
115   // specified maximum pages.
116   MOZ_ASSERT(valid);
117   MOZ_ASSERT(newMaxPages <= wasmSourceMaxPages_);
118 
119   size_t newMappedSize = wasm::ComputeMappedSize(newMaxPages);
120   MOZ_ASSERT(mappedSize_ <= newMappedSize);
121   if (mappedSize_ == newMappedSize) {
122     return;
123   }
124 
125   if (!ExtendBufferMapping(basePointer(), mappedSize_, newMappedSize)) {
126     return;
127   }
128 
129   mappedSize_ = newMappedSize;
130   wasmClampedMaxPages_ = newMaxPages;
131 }
132 
wasmGrowToPagesInPlace(const Lock &,wasm::IndexType t,wasm::Pages newPages)133 bool SharedArrayRawBuffer::wasmGrowToPagesInPlace(const Lock&,
134                                                   wasm::IndexType t,
135                                                   wasm::Pages newPages) {
136   // Check that the new pages is within our allowable range. This will
137   // simultaneously check against the maximum specified in source and our
138   // implementation limits.
139   if (newPages > wasmClampedMaxPages_) {
140     return false;
141   }
142   MOZ_ASSERT(newPages <= wasm::MaxMemoryPages(t) &&
143              newPages.byteLength() <= ArrayBufferObject::maxBufferByteLength());
144 
145   // We have checked against the clamped maximum and so we know we can convert
146   // to byte lengths now.
147   size_t newLength = newPages.byteLength();
148 
149   MOZ_ASSERT(newLength >= length_);
150 
151   if (newLength == length_) {
152     return true;
153   }
154 
155   size_t delta = newLength - length_;
156   MOZ_ASSERT(delta % wasm::PageSize == 0);
157 
158   uint8_t* dataEnd = dataPointerShared().unwrap(/* for resize */) + length_;
159   MOZ_ASSERT(uintptr_t(dataEnd) % gc::SystemPageSize() == 0);
160 
161   if (!CommitBufferMemory(dataEnd, delta)) {
162     return false;
163   }
164 
165   // We rely on CommitBufferMemory (and therefore memmap/VirtualAlloc) to only
166   // return once it has committed memory for all threads. We only update with a
167   // new length once this has occurred.
168   length_ = newLength;
169 
170   return true;
171 }
172 
addReference()173 bool SharedArrayRawBuffer::addReference() {
174   MOZ_RELEASE_ASSERT(refcount_ > 0);
175 
176   // Be careful never to overflow the refcount field.
177   for (;;) {
178     uint32_t old_refcount = refcount_;
179     uint32_t new_refcount = old_refcount + 1;
180     if (new_refcount == 0) {
181       return false;
182     }
183     if (refcount_.compareExchange(old_refcount, new_refcount)) {
184       return true;
185     }
186   }
187 }
188 
dropReference()189 void SharedArrayRawBuffer::dropReference() {
190   // Normally if the refcount is zero then the memory will have been unmapped
191   // and this test may just crash, but if the memory has been retained for any
192   // reason we will catch the underflow here.
193   MOZ_RELEASE_ASSERT(refcount_ > 0);
194 
195   // Drop the reference to the buffer.
196   uint32_t new_refcount = --refcount_;  // Atomic.
197   if (new_refcount) {
198     return;
199   }
200 
201   size_t mappedSizeWithHeader = mappedSize_ + gc::SystemPageSize();
202 
203   // This was the final reference, so release the buffer.
204   UnmapBufferMemory(wasmIndexType(), basePointer(), mappedSizeWithHeader);
205 }
206 
IsSharedArrayBuffer(HandleValue v)207 static bool IsSharedArrayBuffer(HandleValue v) {
208   return v.isObject() && v.toObject().is<SharedArrayBufferObject>();
209 }
210 
byteLengthGetterImpl(JSContext * cx,const CallArgs & args)211 MOZ_ALWAYS_INLINE bool SharedArrayBufferObject::byteLengthGetterImpl(
212     JSContext* cx, const CallArgs& args) {
213   MOZ_ASSERT(IsSharedArrayBuffer(args.thisv()));
214   auto* buffer = &args.thisv().toObject().as<SharedArrayBufferObject>();
215   args.rval().setNumber(buffer->byteLength());
216   return true;
217 }
218 
byteLengthGetter(JSContext * cx,unsigned argc,Value * vp)219 bool SharedArrayBufferObject::byteLengthGetter(JSContext* cx, unsigned argc,
220                                                Value* vp) {
221   CallArgs args = CallArgsFromVp(argc, vp);
222   return CallNonGenericMethod<IsSharedArrayBuffer, byteLengthGetterImpl>(cx,
223                                                                          args);
224 }
225 
226 // ES2017 draft rev 6390c2f1b34b309895d31d8c0512eac8660a0210
227 // 24.2.2.1 SharedArrayBuffer( length )
class_constructor(JSContext * cx,unsigned argc,Value * vp)228 bool SharedArrayBufferObject::class_constructor(JSContext* cx, unsigned argc,
229                                                 Value* vp) {
230   CallArgs args = CallArgsFromVp(argc, vp);
231 
232   // Step 1.
233   if (!ThrowIfNotConstructing(cx, args, "SharedArrayBuffer")) {
234     return false;
235   }
236 
237   // Step 2.
238   uint64_t byteLength;
239   if (!ToIndex(cx, args.get(0), &byteLength)) {
240     return false;
241   }
242 
243   // Step 3 (Inlined 24.2.1.1 AllocateSharedArrayBuffer).
244   // 24.2.1.1, step 1 (Inlined 9.1.14 OrdinaryCreateFromConstructor).
245   RootedObject proto(cx);
246   if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_SharedArrayBuffer,
247                                           &proto)) {
248     return false;
249   }
250 
251   // 24.2.1.1, step 3 (Inlined 6.2.7.2 CreateSharedByteDataBlock, step 2).
252   // Refuse to allocate too large buffers.
253   if (byteLength > ArrayBufferObject::maxBufferByteLength()) {
254     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
255                               JSMSG_SHARED_ARRAY_BAD_LENGTH);
256     return false;
257   }
258 
259   // 24.2.1.1, steps 1 and 4-6.
260   JSObject* bufobj = New(cx, byteLength, proto);
261   if (!bufobj) {
262     return false;
263   }
264   args.rval().setObject(*bufobj);
265   return true;
266 }
267 
New(JSContext * cx,size_t length,HandleObject proto)268 SharedArrayBufferObject* SharedArrayBufferObject::New(JSContext* cx,
269                                                       size_t length,
270                                                       HandleObject proto) {
271   SharedArrayRawBuffer* buffer = SharedArrayRawBuffer::Allocate(length);
272   if (!buffer) {
273     js::ReportOutOfMemory(cx);
274     return nullptr;
275   }
276 
277   SharedArrayBufferObject* obj = New(cx, buffer, length, proto);
278   if (!obj) {
279     buffer->dropReference();
280     return nullptr;
281   }
282 
283   return obj;
284 }
285 
New(JSContext * cx,SharedArrayRawBuffer * buffer,size_t length,HandleObject proto)286 SharedArrayBufferObject* SharedArrayBufferObject::New(
287     JSContext* cx, SharedArrayRawBuffer* buffer, size_t length,
288     HandleObject proto) {
289   MOZ_ASSERT(cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled());
290 
291   AutoSetNewObjectMetadata metadata(cx);
292   Rooted<SharedArrayBufferObject*> obj(
293       cx, NewObjectWithClassProto<SharedArrayBufferObject>(cx, proto));
294   if (!obj) {
295     return nullptr;
296   }
297 
298   MOZ_ASSERT(obj->getClass() == &class_);
299 
300   cx->runtime()->incSABCount();
301 
302   if (!obj->acceptRawBuffer(buffer, length)) {
303     js::ReportOutOfMemory(cx);
304     return nullptr;
305   }
306 
307   return obj;
308 }
309 
acceptRawBuffer(SharedArrayRawBuffer * buffer,size_t length)310 bool SharedArrayBufferObject::acceptRawBuffer(SharedArrayRawBuffer* buffer,
311                                               size_t length) {
312   if (!zone()->addSharedMemory(buffer, SharedArrayMappedSize(length),
313                                MemoryUse::SharedArrayRawBuffer)) {
314     return false;
315   }
316 
317   setFixedSlot(RAWBUF_SLOT, PrivateValue(buffer));
318   setFixedSlot(LENGTH_SLOT, PrivateValue(length));
319   return true;
320 }
321 
dropRawBuffer()322 void SharedArrayBufferObject::dropRawBuffer() {
323   size_t size = SharedArrayMappedSize(byteLength());
324   zoneFromAnyThread()->removeSharedMemory(rawBufferObject(), size,
325                                           MemoryUse::SharedArrayRawBuffer);
326   setFixedSlot(RAWBUF_SLOT, UndefinedValue());
327 }
328 
rawBufferObject() const329 SharedArrayRawBuffer* SharedArrayBufferObject::rawBufferObject() const {
330   Value v = getFixedSlot(RAWBUF_SLOT);
331   MOZ_ASSERT(!v.isUndefined());
332   return reinterpret_cast<SharedArrayRawBuffer*>(v.toPrivate());
333 }
334 
Finalize(JSFreeOp * fop,JSObject * obj)335 void SharedArrayBufferObject::Finalize(JSFreeOp* fop, JSObject* obj) {
336   // Must be foreground finalizable so that we can account for the object.
337   MOZ_ASSERT(fop->onMainThread());
338   fop->runtime()->decSABCount();
339 
340   SharedArrayBufferObject& buf = obj->as<SharedArrayBufferObject>();
341 
342   // Detect the case of failure during SharedArrayBufferObject creation,
343   // which causes a SharedArrayRawBuffer to never be attached.
344   Value v = buf.getFixedSlot(RAWBUF_SLOT);
345   if (!v.isUndefined()) {
346     buf.rawBufferObject()->dropReference();
347     buf.dropRawBuffer();
348   }
349 }
350 
351 /* static */
addSizeOfExcludingThis(JSObject * obj,mozilla::MallocSizeOf mallocSizeOf,JS::ClassInfo * info,JS::RuntimeSizes * runtimeSizes)352 void SharedArrayBufferObject::addSizeOfExcludingThis(
353     JSObject* obj, mozilla::MallocSizeOf mallocSizeOf, JS::ClassInfo* info,
354     JS::RuntimeSizes* runtimeSizes) {
355   // Divide the buffer size by the refcount to get the fraction of the buffer
356   // owned by this thread. It's conceivable that the refcount might change in
357   // the middle of memory reporting, in which case the amount reported for
358   // some threads might be to high (if the refcount goes up) or too low (if
359   // the refcount goes down). But that's unlikely and hard to avoid, so we
360   // just live with the risk.
361   const SharedArrayBufferObject& buf = obj->as<SharedArrayBufferObject>();
362   size_t owned = buf.byteLength() / buf.rawBufferObject()->refcount();
363   if (buf.isWasm()) {
364     info->objectsNonHeapElementsWasmShared += owned;
365     if (runtimeSizes) {
366       size_t ownedGuardPages = (buf.wasmMappedSize() - buf.byteLength()) /
367                                buf.rawBufferObject()->refcount();
368       runtimeSizes->wasmGuardPages += ownedGuardPages;
369     }
370   } else {
371     info->objectsNonHeapElementsShared += owned;
372   }
373 }
374 
375 /* static */
copyData(Handle<ArrayBufferObjectMaybeShared * > toBuffer,size_t toIndex,Handle<ArrayBufferObjectMaybeShared * > fromBuffer,size_t fromIndex,size_t count)376 void SharedArrayBufferObject::copyData(
377     Handle<ArrayBufferObjectMaybeShared*> toBuffer, size_t toIndex,
378     Handle<ArrayBufferObjectMaybeShared*> fromBuffer, size_t fromIndex,
379     size_t count) {
380   MOZ_ASSERT(toBuffer->byteLength() >= count);
381   MOZ_ASSERT(toBuffer->byteLength() >= toIndex + count);
382   MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex);
383   MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex + count);
384 
385   jit::AtomicOperations::memcpySafeWhenRacy(
386       toBuffer->dataPointerEither() + toIndex,
387       fromBuffer->dataPointerEither() + fromIndex, count);
388 }
389 
createFromNewRawBuffer(JSContext * cx,SharedArrayRawBuffer * buffer,size_t initialSize)390 SharedArrayBufferObject* SharedArrayBufferObject::createFromNewRawBuffer(
391     JSContext* cx, SharedArrayRawBuffer* buffer, size_t initialSize) {
392   MOZ_ASSERT(cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled());
393 
394   AutoSetNewObjectMetadata metadata(cx);
395   SharedArrayBufferObject* obj =
396       NewBuiltinClassInstance<SharedArrayBufferObject>(cx);
397   if (!obj) {
398     buffer->dropReference();
399     return nullptr;
400   }
401 
402   cx->runtime()->incSABCount();
403 
404   if (!obj->acceptRawBuffer(buffer, initialSize)) {
405     buffer->dropReference();
406     return nullptr;
407   }
408 
409   return obj;
410 }
411 
412 static const JSClassOps SharedArrayBufferObjectClassOps = {
413     nullptr,                            // addProperty
414     nullptr,                            // delProperty
415     nullptr,                            // enumerate
416     nullptr,                            // newEnumerate
417     nullptr,                            // resolve
418     nullptr,                            // mayResolve
419     SharedArrayBufferObject::Finalize,  // finalize
420     nullptr,                            // call
421     nullptr,                            // hasInstance
422     nullptr,                            // construct
423     nullptr,                            // trace
424 };
425 
426 static const JSFunctionSpec sharedarrray_functions[] = {JS_FS_END};
427 
428 static const JSPropertySpec sharedarrray_properties[] = {
429     JS_SELF_HOSTED_SYM_GET(species, "$SharedArrayBufferSpecies", 0), JS_PS_END};
430 
431 static const JSFunctionSpec sharedarray_proto_functions[] = {
432     JS_SELF_HOSTED_FN("slice", "SharedArrayBufferSlice", 2, 0), JS_FS_END};
433 
434 static const JSPropertySpec sharedarray_proto_properties[] = {
435     JS_PSG("byteLength", SharedArrayBufferObject::byteLengthGetter, 0),
436     JS_STRING_SYM_PS(toStringTag, "SharedArrayBuffer", JSPROP_READONLY),
437     JS_PS_END};
438 
439 static const ClassSpec SharedArrayBufferObjectClassSpec = {
440     GenericCreateConstructor<SharedArrayBufferObject::class_constructor, 1,
441                              gc::AllocKind::FUNCTION>,
442     GenericCreatePrototype<SharedArrayBufferObject>,
443     sharedarrray_functions,
444     sharedarrray_properties,
445     sharedarray_proto_functions,
446     sharedarray_proto_properties};
447 
448 const JSClass SharedArrayBufferObject::class_ = {
449     "SharedArrayBuffer",
450     JSCLASS_DELAY_METADATA_BUILDER |
451         JSCLASS_HAS_RESERVED_SLOTS(SharedArrayBufferObject::RESERVED_SLOTS) |
452         JSCLASS_HAS_CACHED_PROTO(JSProto_SharedArrayBuffer) |
453         JSCLASS_FOREGROUND_FINALIZE,
454     &SharedArrayBufferObjectClassOps, &SharedArrayBufferObjectClassSpec,
455     JS_NULL_CLASS_EXT};
456 
457 const JSClass SharedArrayBufferObject::protoClass_ = {
458     "SharedArrayBuffer.prototype",
459     JSCLASS_HAS_CACHED_PROTO(JSProto_SharedArrayBuffer), JS_NULL_CLASS_OPS,
460     &SharedArrayBufferObjectClassSpec};
461 
GetSharedArrayBufferByteLength(JSObject * obj)462 JS_PUBLIC_API size_t JS::GetSharedArrayBufferByteLength(JSObject* obj) {
463   auto* aobj = obj->maybeUnwrapAs<SharedArrayBufferObject>();
464   return aobj ? aobj->byteLength() : 0;
465 }
466 
GetSharedArrayBufferLengthAndData(JSObject * obj,size_t * length,bool * isSharedMemory,uint8_t ** data)467 JS_PUBLIC_API void JS::GetSharedArrayBufferLengthAndData(JSObject* obj,
468                                                          size_t* length,
469                                                          bool* isSharedMemory,
470                                                          uint8_t** data) {
471   MOZ_ASSERT(obj->is<SharedArrayBufferObject>());
472   *length = obj->as<SharedArrayBufferObject>().byteLength();
473   *data = obj->as<SharedArrayBufferObject>().dataPointerShared().unwrap(
474       /*safe - caller knows*/);
475   *isSharedMemory = true;
476 }
477 
NewSharedArrayBuffer(JSContext * cx,size_t nbytes)478 JS_PUBLIC_API JSObject* JS::NewSharedArrayBuffer(JSContext* cx, size_t nbytes) {
479   MOZ_ASSERT(cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled());
480 
481   if (nbytes > ArrayBufferObject::maxBufferByteLength()) {
482     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
483                               JSMSG_SHARED_ARRAY_BAD_LENGTH);
484     return nullptr;
485   }
486 
487   return SharedArrayBufferObject::New(cx, nbytes,
488                                       /* proto = */ nullptr);
489 }
490 
IsSharedArrayBufferObject(JSObject * obj)491 JS_PUBLIC_API bool JS::IsSharedArrayBufferObject(JSObject* obj) {
492   return obj->canUnwrapAs<SharedArrayBufferObject>();
493 }
494 
GetSharedArrayBufferData(JSObject * obj,bool * isSharedMemory,const JS::AutoRequireNoGC &)495 JS_PUBLIC_API uint8_t* JS::GetSharedArrayBufferData(
496     JSObject* obj, bool* isSharedMemory, const JS::AutoRequireNoGC&) {
497   auto* aobj = obj->maybeUnwrapAs<SharedArrayBufferObject>();
498   if (!aobj) {
499     return nullptr;
500   }
501   *isSharedMemory = true;
502   return aobj->dataPointerShared().unwrap(/*safe - caller knows*/);
503 }
504 
ContainsSharedArrayBuffer(JSContext * cx)505 JS_PUBLIC_API bool JS::ContainsSharedArrayBuffer(JSContext* cx) {
506   return cx->runtime()->hasLiveSABs();
507 }
508