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