SharedArrayObject.cpp (37377B)
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/DebugOnly.h" 11 #include "mozilla/TaggedAnonymousMemory.h" 12 13 #include "jsnum.h" 14 15 #include "gc/GCContext.h" 16 #include "gc/Memory.h" 17 #include "jit/AtomicOperations.h" 18 #include "jit/InlinableNatives.h" 19 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* 20 #include "js/Prefs.h" 21 #include "js/PropertySpec.h" 22 #include "js/SharedArrayBuffer.h" 23 #include "util/Memory.h" 24 #include "util/WindowsWrapper.h" 25 #include "vm/Interpreter.h" 26 #include "vm/SelfHosting.h" 27 #include "vm/SharedMem.h" 28 #include "wasm/WasmConstants.h" 29 #include "wasm/WasmMemory.h" 30 31 #include "vm/ArrayBufferObject-inl.h" 32 #include "vm/JSObject-inl.h" 33 #include "vm/NativeObject-inl.h" 34 35 using js::wasm::Pages; 36 using mozilla::DebugOnly; 37 38 using namespace js; 39 using namespace js::jit; 40 41 static size_t WasmSharedArrayAccessibleSize(size_t length) { 42 return AlignBytes(length, gc::SystemPageSize()); 43 } 44 45 static size_t NonWasmSharedArrayAllocSize(size_t length) { 46 MOZ_ASSERT(length <= ArrayBufferObject::ByteLengthLimit); 47 return sizeof(SharedArrayRawBuffer) + length; 48 } 49 50 // The mapped size for a plain shared array buffer, used only for tracking 51 // memory usage. This is incorrect for some WASM cases, and for hypothetical 52 // callers of js::SharedArrayBufferObject::createFromNewRawBuffer that do not 53 // currently exist, but it's fine as a signal of GC pressure. 54 static size_t SharedArrayMappedSize(bool isWasm, size_t length) { 55 // Wasm buffers use MapBufferMemory and allocate a full page for the header. 56 // Non-Wasm buffers use malloc. 57 if (isWasm) { 58 return WasmSharedArrayAccessibleSize(length) + gc::SystemPageSize(); 59 } 60 return NonWasmSharedArrayAllocSize(length); 61 } 62 63 SharedArrayRawBuffer* SharedArrayRawBuffer::Allocate(bool isGrowable, 64 size_t length, 65 size_t maxLength) { 66 MOZ_RELEASE_ASSERT(length <= ArrayBufferObject::ByteLengthLimit); 67 MOZ_RELEASE_ASSERT(maxLength <= ArrayBufferObject::ByteLengthLimit); 68 MOZ_ASSERT_IF(!isGrowable, length == maxLength); 69 MOZ_ASSERT_IF(isGrowable, length <= maxLength); 70 71 size_t allocSize = NonWasmSharedArrayAllocSize(maxLength); 72 uint8_t* p = js_pod_calloc<uint8_t>(allocSize); 73 if (!p) { 74 return nullptr; 75 } 76 MOZ_ASSERT(reinterpret_cast<uintptr_t>(p) % 77 ArrayBufferObject::ARRAY_BUFFER_ALIGNMENT == 78 0, 79 "shared array buffer memory is aligned"); 80 81 // jemalloc tiny allocations can produce allocations not aligned to the 82 // smallest std::malloc allocation. Ensure shared array buffer allocations 83 // don't have to worry about this special case. 84 static_assert(sizeof(SharedArrayRawBuffer) > sizeof(void*), 85 "SharedArrayRawBuffer doesn't fit in jemalloc tiny allocation"); 86 87 static_assert(sizeof(SharedArrayRawBuffer) % 88 ArrayBufferObject::ARRAY_BUFFER_ALIGNMENT == 89 0, 90 "sizeof(SharedArrayRawBuffer) is a multiple of the array " 91 "buffer alignment, so |p + sizeof(SharedArrayRawBuffer)| is " 92 "also array buffer aligned"); 93 94 uint8_t* buffer = p + sizeof(SharedArrayRawBuffer); 95 return new (p) SharedArrayRawBuffer(isGrowable, buffer, length); 96 } 97 98 WasmSharedArrayRawBuffer* WasmSharedArrayRawBuffer::AllocateWasm( 99 wasm::AddressType addressType, wasm::PageSize pageSize, Pages initialPages, 100 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_RELEASE_ASSERT(initialPages.pageSize() == pageSize); 106 MOZ_RELEASE_ASSERT(clampedMaxPages.pageSize() == pageSize); 107 MOZ_RELEASE_ASSERT(!sourceMaxPages.isSome() || 108 (pageSize == sourceMaxPages->pageSize())); 109 MOZ_ASSERT(initialPages.hasByteLength()); 110 size_t length = initialPages.byteLength(); 111 112 MOZ_RELEASE_ASSERT(length <= ArrayBufferObject::ByteLengthLimit); 113 114 size_t accessibleSize = WasmSharedArrayAccessibleSize(length); 115 if (accessibleSize < length) { 116 return nullptr; 117 } 118 119 size_t computedMappedSize = mappedSize.isSome() 120 ? *mappedSize 121 : wasm::ComputeMappedSize(clampedMaxPages); 122 MOZ_ASSERT(accessibleSize <= computedMappedSize); 123 124 uint64_t mappedSizeWithHeader = computedMappedSize + gc::SystemPageSize(); 125 uint64_t accessibleSizeWithHeader = accessibleSize + gc::SystemPageSize(); 126 127 void* p = MapBufferMemory(addressType, pageSize, mappedSizeWithHeader, 128 accessibleSizeWithHeader); 129 if (!p) { 130 return nullptr; 131 } 132 133 uint8_t* buffer = reinterpret_cast<uint8_t*>(p) + gc::SystemPageSize(); 134 uint8_t* base = buffer - sizeof(WasmSharedArrayRawBuffer); 135 return new (base) WasmSharedArrayRawBuffer( 136 buffer, length, addressType, pageSize, clampedMaxPages, 137 sourceMaxPages.valueOr(Pages::fromPageCount(0, pageSize)), 138 computedMappedSize); 139 } 140 141 bool WasmSharedArrayRawBuffer::wasmGrowToPagesInPlace(const Lock&, 142 wasm::AddressType t, 143 wasm::Pages newPages) { 144 // Check that the new pages is within our allowable range. This will 145 // simultaneously check against the maximum specified in source and our 146 // implementation limits. 147 if (newPages > clampedMaxPages_) { 148 return false; 149 } 150 MOZ_ASSERT(newPages <= wasm::MaxMemoryPages(t, newPages.pageSize()) && 151 newPages.byteLength() <= ArrayBufferObject::ByteLengthLimit); 152 153 // We have checked against the clamped maximum and so we know we can convert 154 // to byte lengths now. 155 size_t newLength = newPages.byteLength(); 156 157 MOZ_ASSERT(newLength >= length_); 158 159 if (newLength == length_) { 160 return true; 161 } 162 163 size_t delta = newLength - length_; 164 MOZ_ASSERT(delta % wasm::StandardPageSizeBytes == 0); 165 166 uint8_t* dataEnd = dataPointerShared().unwrap(/* for resize */) + length_; 167 MOZ_ASSERT(uintptr_t(dataEnd) % gc::SystemPageSize() == 0); 168 169 if (!CommitBufferMemory(dataEnd, delta)) { 170 return false; 171 } 172 173 // We rely on CommitBufferMemory (and therefore memmap/VirtualAlloc) to only 174 // return once it has committed memory for all threads. We only update with a 175 // new length once this has occurred. 176 length_ = newLength; 177 178 return true; 179 } 180 181 void WasmSharedArrayRawBuffer::discard(size_t byteOffset, size_t byteLen) { 182 SharedMem<uint8_t*> memBase = dataPointerShared(); 183 184 // The caller is responsible for ensuring these conditions are met; see this 185 // function's comment in SharedArrayObject.h. 186 MOZ_ASSERT(byteOffset % wasm::StandardPageSizeBytes == 0); 187 MOZ_ASSERT(byteLen % wasm::StandardPageSizeBytes == 0); 188 MOZ_ASSERT(wasm::MemoryBoundsCheck(uint64_t(byteOffset), uint64_t(byteLen), 189 volatileByteLength())); 190 191 // Discarding zero bytes "succeeds" with no effect. 192 if (byteLen == 0) { 193 return; 194 } 195 196 SharedMem<uint8_t*> addr = memBase + uintptr_t(byteOffset); 197 198 // On POSIX-ish platforms, we discard memory by overwriting previously-mapped 199 // pages with freshly-mapped pages (which are all zeroed). The operating 200 // system recognizes this and decreases the process RSS, and eventually 201 // collects the abandoned physical pages. 202 // 203 // On Windows, committing over previously-committed pages has no effect. We 204 // could decommit and recommit, but this doesn't work for shared memories 205 // since other threads could access decommitted memory - causing a trap. 206 // Instead, we simply zero memory (memset 0), and then VirtualUnlock(), which 207 // for Historical Reasons immediately removes the pages from the working set. 208 // And then, because the pages were zeroed, Windows will actually reclaim the 209 // memory entirely instead of paging it out to disk. Naturally this behavior 210 // is not officially documented, but a Raymond Chen blog post is basically as 211 // good as MSDN, right? 212 // 213 // https://devblogs.microsoft.com/oldnewthing/20170113-00/?p=95185 214 215 #ifdef XP_WIN 216 // Discarding the entire region at once causes us to page the entire region 217 // into the working set, only to throw it out again. This can be actually 218 // disastrous when discarding already-discarded memory. To mitigate this, we 219 // discard a chunk of memory at a time - this comes at a small performance 220 // cost from syscalls and potentially less-optimal memsets. 221 size_t numPages = byteLen / wasm::StandardPageSizeBytes; 222 for (size_t i = 0; i < numPages; i++) { 223 AtomicOperations::memsetSafeWhenRacy( 224 addr + (i * wasm::StandardPageSizeBytes), 0, 225 wasm::StandardPageSizeBytes); 226 DebugOnly<bool> result = 227 VirtualUnlock(addr.unwrap() + (i * wasm::StandardPageSizeBytes), 228 wasm::StandardPageSizeBytes); 229 MOZ_ASSERT(!result); // this always "fails" when unlocking unlocked 230 // memory...which is the only case we care about 231 } 232 #elif defined(__wasi__) 233 AtomicOperations::memsetSafeWhenRacy(addr, 0, byteLen); 234 #else // !XP_WIN 235 void* data = MozTaggedAnonymousMmap( 236 addr.unwrap(), byteLen, PROT_READ | PROT_WRITE, 237 MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0, "wasm-reserved"); 238 if (data == MAP_FAILED) { 239 MOZ_CRASH("failed to discard wasm memory; memory mappings may be broken"); 240 } 241 #endif 242 } 243 244 bool SharedArrayRawBuffer::addReference() { 245 MOZ_RELEASE_ASSERT(refcount_ > 0); 246 247 // Be careful never to overflow the refcount field. 248 for (;;) { 249 uint32_t old_refcount = refcount_; 250 uint32_t new_refcount = old_refcount + 1; 251 if (new_refcount == 0) { 252 return false; 253 } 254 if (refcount_.compareExchange(old_refcount, new_refcount)) { 255 return true; 256 } 257 } 258 } 259 260 void SharedArrayRawBuffer::dropReference() { 261 // Normally if the refcount is zero then the memory will have been unmapped 262 // and this test may just crash, but if the memory has been retained for any 263 // reason we will catch the underflow here. 264 MOZ_RELEASE_ASSERT(refcount_ > 0); 265 266 // Drop the reference to the buffer. 267 uint32_t new_refcount = --refcount_; // Atomic. 268 if (new_refcount) { 269 return; 270 } 271 272 // This was the final reference, so release the buffer. 273 if (isWasm()) { 274 WasmSharedArrayRawBuffer* wasmBuf = toWasmBuffer(); 275 wasm::AddressType addressType = wasmBuf->wasmAddressType(); 276 uint8_t* basePointer = wasmBuf->basePointer(); 277 size_t mappedSizeWithHeader = wasmBuf->mappedSize() + gc::SystemPageSize(); 278 size_t committedSize = wasmBuf->volatileByteLength() + gc::SystemPageSize(); 279 // Call the destructor to destroy the growLock_ Mutex. 280 wasmBuf->~WasmSharedArrayRawBuffer(); 281 UnmapBufferMemory(addressType, basePointer, mappedSizeWithHeader, 282 committedSize); 283 } else { 284 js_delete(this); 285 } 286 } 287 288 bool SharedArrayRawBuffer::growJS(size_t newByteLength) { 289 MOZ_ASSERT(!isWasm()); 290 MOZ_RELEASE_ASSERT(isGrowableJS()); 291 292 // The caller is responsible to ensure |newByteLength| doesn't exceed the 293 // maximum allowed byte length. 294 295 while (true) { 296 // `mozilla::Atomic::compareExchange` doesn't return the current value, so 297 // we need to perform a normal load here. (bug 1005335) 298 size_t oldByteLength = length_; 299 if (newByteLength == oldByteLength) { 300 return true; 301 } 302 if (newByteLength < oldByteLength) { 303 return false; 304 } 305 if (length_.compareExchange(oldByteLength, newByteLength)) { 306 return true; 307 } 308 } 309 } 310 311 static bool IsSharedArrayBuffer(HandleValue v) { 312 return v.isObject() && v.toObject().is<SharedArrayBufferObject>(); 313 } 314 315 static bool IsGrowableSharedArrayBuffer(HandleValue v) { 316 return v.isObject() && v.toObject().is<GrowableSharedArrayBufferObject>(); 317 } 318 319 MOZ_ALWAYS_INLINE bool SharedArrayBufferObject::byteLengthGetterImpl( 320 JSContext* cx, const CallArgs& args) { 321 MOZ_ASSERT(IsSharedArrayBuffer(args.thisv())); 322 auto* buffer = &args.thisv().toObject().as<SharedArrayBufferObject>(); 323 args.rval().setNumber(buffer->byteLength()); 324 return true; 325 } 326 327 bool SharedArrayBufferObject::byteLengthGetter(JSContext* cx, unsigned argc, 328 Value* vp) { 329 CallArgs args = CallArgsFromVp(argc, vp); 330 return CallNonGenericMethod<IsSharedArrayBuffer, byteLengthGetterImpl>(cx, 331 args); 332 } 333 334 /** 335 * get SharedArrayBuffer.prototype.maxByteLength 336 */ 337 bool SharedArrayBufferObject::maxByteLengthGetterImpl(JSContext* cx, 338 const CallArgs& args) { 339 MOZ_ASSERT(IsSharedArrayBuffer(args.thisv())); 340 auto* buffer = &args.thisv().toObject().as<SharedArrayBufferObject>(); 341 342 // Special case for wasm with potentially 64-bits memory. 343 // Manually compute the maxByteLength to avoid an overflow on 32-bit machines. 344 if (buffer->isWasm() && buffer->isResizable()) { 345 Pages sourceMaxPages = buffer->rawWasmBufferObject()->wasmSourceMaxPages(); 346 uint64_t sourceMaxBytes = sourceMaxPages.byteLength64(); 347 348 MOZ_ASSERT(sourceMaxBytes <= wasm::StandardPageSizeBytes * 349 wasm::MaxMemory64StandardPagesValidation); 350 args.rval().setNumber(double(sourceMaxBytes)); 351 352 return true; 353 } 354 355 // Steps 4-6. 356 args.rval().setNumber(buffer->byteLengthOrMaxByteLength()); 357 return true; 358 } 359 360 /** 361 * get SharedArrayBuffer.prototype.maxByteLength 362 */ 363 bool SharedArrayBufferObject::maxByteLengthGetter(JSContext* cx, unsigned argc, 364 Value* vp) { 365 // Steps 1-3. 366 CallArgs args = CallArgsFromVp(argc, vp); 367 return CallNonGenericMethod<IsSharedArrayBuffer, maxByteLengthGetterImpl>( 368 cx, args); 369 } 370 371 /** 372 * get SharedArrayBuffer.prototype.growable 373 */ 374 bool SharedArrayBufferObject::growableGetterImpl(JSContext* cx, 375 const CallArgs& args) { 376 MOZ_ASSERT(IsSharedArrayBuffer(args.thisv())); 377 auto* buffer = &args.thisv().toObject().as<SharedArrayBufferObject>(); 378 379 // Step 4. 380 args.rval().setBoolean(buffer->isGrowable()); 381 return true; 382 } 383 384 /** 385 * get SharedArrayBuffer.prototype.growable 386 */ 387 bool SharedArrayBufferObject::growableGetter(JSContext* cx, unsigned argc, 388 Value* vp) { 389 // Steps 1-3. 390 CallArgs args = CallArgsFromVp(argc, vp); 391 return CallNonGenericMethod<IsSharedArrayBuffer, growableGetterImpl>(cx, 392 args); 393 } 394 395 /** 396 * SharedArrayBuffer.prototype.grow ( newLength ) 397 */ 398 bool SharedArrayBufferObject::growImpl(JSContext* cx, const CallArgs& args) { 399 MOZ_ASSERT(IsGrowableSharedArrayBuffer(args.thisv())); 400 Rooted<GrowableSharedArrayBufferObject*> buffer( 401 cx, &args.thisv().toObject().as<GrowableSharedArrayBufferObject>()); 402 403 // Step 4. 404 uint64_t newByteLength; 405 if (!ToIndex(cx, args.get(0), &newByteLength)) { 406 return false; 407 } 408 409 // Steps 5-11. 410 if (newByteLength > buffer->maxByteLength()) { 411 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 412 JSMSG_ARRAYBUFFER_LENGTH_LARGER_THAN_MAXIMUM); 413 return false; 414 } 415 416 if (buffer->isWasm()) { 417 // Special case for resizing of Wasm buffers. 418 if (newByteLength % wasm::StandardPageSizeBytes != 0) { 419 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 420 JSMSG_WASM_ARRAYBUFFER_PAGE_MULTIPLE); 421 return false; 422 } 423 424 mozilla::Maybe<WasmSharedArrayRawBuffer::Lock> lock( 425 mozilla::Some(buffer->rawWasmBufferObject())); 426 427 if (newByteLength < buffer->rawWasmBufferObject()->volatileByteLength()) { 428 lock.reset(); 429 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 430 JSMSG_WASM_ARRAYBUFFER_CANNOT_SHRINK); 431 return false; 432 } 433 434 Pages newPages = 435 Pages::fromByteLengthExact(newByteLength, buffer->wasmPageSize()); 436 return buffer->rawWasmBufferObject()->wasmGrowToPagesInPlace( 437 *lock, buffer->wasmAddressType(), newPages); 438 } 439 440 if (!buffer->rawBufferObject()->growJS(newByteLength)) { 441 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 442 JSMSG_SHARED_ARRAY_LENGTH_SMALLER_THAN_CURRENT); 443 return false; 444 } 445 446 args.rval().setUndefined(); 447 return true; 448 } 449 450 /** 451 * SharedArrayBuffer.prototype.grow ( newLength ) 452 */ 453 bool SharedArrayBufferObject::grow(JSContext* cx, unsigned argc, Value* vp) { 454 // Steps 1-3. 455 CallArgs args = CallArgsFromVp(argc, vp); 456 return CallNonGenericMethod<IsGrowableSharedArrayBuffer, growImpl>(cx, args); 457 } 458 459 static bool IsSharedArrayBufferSpecies(JSContext* cx, JSFunction* species) { 460 return IsSelfHostedFunctionWithName( 461 species, cx->names().dollar_SharedArrayBufferSpecies_); 462 } 463 464 static bool HasBuiltinSharedArrayBufferSpecies(SharedArrayBufferObject* obj, 465 JSContext* cx) { 466 // Ensure `SharedArrayBuffer.prototype.constructor` and 467 // `SharedArrayBuffer[@@species]` haven't been mutated. 468 if (!cx->realm()->realmFuses.optimizeSharedArrayBufferSpeciesFuse.intact()) { 469 return false; 470 } 471 472 // Ensure |obj|'s prototype is the actual SharedArrayBuffer.prototype. 473 auto* proto = cx->global()->maybeGetPrototype(JSProto_SharedArrayBuffer); 474 if (!proto || obj->staticPrototype() != proto) { 475 return false; 476 } 477 478 // Fail if |obj| has an own `constructor` property. 479 if (obj->containsPure(NameToId(cx->names().constructor))) { 480 return false; 481 } 482 483 return true; 484 } 485 486 /** 487 * SharedArrayBuffer.prototype.slice ( start, end ) 488 * 489 * https://tc39.es/ecma262/#sec-sharedarraybuffer.prototype.slice 490 */ 491 bool SharedArrayBufferObject::sliceImpl(JSContext* cx, const CallArgs& args) { 492 MOZ_ASSERT(IsSharedArrayBuffer(args.thisv())); 493 494 Rooted<SharedArrayBufferObject*> obj( 495 cx, &args.thisv().toObject().as<SharedArrayBufferObject>()); 496 497 // Step 4. 498 size_t len = obj->byteLength(); 499 500 // Steps 5-8. 501 size_t first = 0; 502 if (args.hasDefined(0)) { 503 if (!ToIntegerIndex(cx, args[0], len, &first)) { 504 return false; 505 } 506 } 507 508 // Steps 9-12. 509 size_t final_ = len; 510 if (args.hasDefined(1)) { 511 if (!ToIntegerIndex(cx, args[1], len, &final_)) { 512 return false; 513 } 514 } 515 516 // Step 13. 517 size_t newLen = final_ >= first ? final_ - first : 0; 518 MOZ_ASSERT(newLen <= ArrayBufferObject::ByteLengthLimit); 519 520 // Steps 14-19. 521 Rooted<JSObject*> resultObj(cx); 522 SharedArrayBufferObject* unwrappedResult = nullptr; 523 if (HasBuiltinSharedArrayBufferSpecies(obj, cx)) { 524 // Steps 14-15. 525 unwrappedResult = New(cx, newLen); 526 if (!unwrappedResult) { 527 return false; 528 } 529 resultObj.set(unwrappedResult); 530 531 // Steps 16-17. (Not applicable) 532 533 // Step 18. 534 MOZ_ASSERT(obj->rawBufferObject() != unwrappedResult->rawBufferObject()); 535 536 // Step 19. 537 MOZ_ASSERT(unwrappedResult->byteLength() == newLen); 538 } else { 539 // Step 14. 540 Rooted<JSObject*> ctor( 541 cx, SpeciesConstructor(cx, obj, JSProto_SharedArrayBuffer, 542 IsSharedArrayBufferSpecies)); 543 if (!ctor) { 544 return false; 545 } 546 547 // Step 15. 548 { 549 FixedConstructArgs<1> cargs(cx); 550 cargs[0].setNumber(newLen); 551 552 Rooted<Value> ctorVal(cx, ObjectValue(*ctor)); 553 if (!Construct(cx, ctorVal, cargs, ctorVal, &resultObj)) { 554 return false; 555 } 556 } 557 558 // Steps 16-17. 559 unwrappedResult = resultObj->maybeUnwrapIf<SharedArrayBufferObject>(); 560 if (!unwrappedResult) { 561 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 562 JSMSG_NON_SHARED_ARRAY_BUFFER_RETURNED); 563 return false; 564 } 565 566 // Step 18. 567 if (obj->rawBufferObject() == unwrappedResult->rawBufferObject()) { 568 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 569 JSMSG_SAME_SHARED_ARRAY_BUFFER_RETURNED); 570 return false; 571 } 572 573 // Step 19. 574 size_t resultByteLength = unwrappedResult->byteLength(); 575 if (resultByteLength < newLen) { 576 ToCStringBuf resultLenCbuf; 577 const char* resultLenStr = 578 NumberToCString(&resultLenCbuf, double(resultByteLength)); 579 580 ToCStringBuf newLenCbuf; 581 const char* newLenStr = NumberToCString(&newLenCbuf, double(newLen)); 582 583 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 584 JSMSG_SHORT_SHARED_ARRAY_BUFFER_RETURNED, 585 newLenStr, resultLenStr); 586 return false; 587 } 588 } 589 590 // Steps 20-22. 591 SharedArrayBufferObject::copyData(unwrappedResult, 0, obj, first, newLen); 592 593 // Step 23. 594 args.rval().setObject(*resultObj); 595 return true; 596 } 597 598 /** 599 * SharedArrayBuffer.prototype.slice ( start, end ) 600 * 601 * https://tc39.es/ecma262/#sec-sharedarraybuffer.prototype.slice 602 */ 603 bool SharedArrayBufferObject::slice(JSContext* cx, unsigned argc, Value* vp) { 604 // Steps 1-3. 605 CallArgs args = CallArgsFromVp(argc, vp); 606 return CallNonGenericMethod<IsSharedArrayBuffer, sliceImpl>(cx, args); 607 } 608 609 // ES2024 draft rev 3a773fc9fae58be023228b13dbbd402ac18eeb6b 610 // 25.2.3.1 SharedArrayBuffer ( length [ , options ] ) 611 bool SharedArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, 612 Value* vp) { 613 CallArgs args = CallArgsFromVp(argc, vp); 614 615 // Step 1. 616 if (!ThrowIfNotConstructing(cx, args, "SharedArrayBuffer")) { 617 return false; 618 } 619 620 // Step 2. 621 uint64_t byteLength; 622 if (!ToIndex(cx, args.get(0), &byteLength)) { 623 return false; 624 } 625 626 // Step 3. 627 mozilla::Maybe<uint64_t> maxByteLength; 628 // Inline call to GetArrayBufferMaxByteLengthOption. 629 if (args.get(1).isObject()) { 630 Rooted<JSObject*> options(cx, &args[1].toObject()); 631 632 Rooted<Value> val(cx); 633 if (!GetProperty(cx, options, options, cx->names().maxByteLength, &val)) { 634 return false; 635 } 636 if (!val.isUndefined()) { 637 uint64_t maxByteLengthInt; 638 if (!ToIndex(cx, val, &maxByteLengthInt)) { 639 return false; 640 } 641 642 // 25.2.2.1 AllocateSharedArrayBuffer, step 3.a. 643 if (byteLength > maxByteLengthInt) { 644 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 645 JSMSG_ARRAYBUFFER_LENGTH_LARGER_THAN_MAXIMUM); 646 return false; 647 } 648 maxByteLength = mozilla::Some(maxByteLengthInt); 649 } 650 } 651 652 // Step 4 (Inlined 25.2.2.1 AllocateSharedArrayBuffer). 653 // 25.2.2.1, step 5 (Inlined 10.1.13 OrdinaryCreateFromConstructor, step 2). 654 RootedObject proto(cx); 655 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_SharedArrayBuffer, 656 &proto)) { 657 return false; 658 } 659 660 // 25.2.2.1, step 6. 661 uint64_t allocLength = maxByteLength.valueOr(byteLength); 662 663 // 25.2.2.1, step 7 (Inlined 6.2.9.2 CreateSharedByteDataBlock, step 1). 664 // Refuse to allocate too large buffers. 665 if (allocLength > ArrayBufferObject::ByteLengthLimit) { 666 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 667 JSMSG_SHARED_ARRAY_BAD_LENGTH); 668 return false; 669 } 670 671 if (maxByteLength) { 672 // 25.2.2.1, remaining steps. 673 auto* bufobj = NewGrowable(cx, byteLength, *maxByteLength, proto); 674 if (!bufobj) { 675 return false; 676 } 677 args.rval().setObject(*bufobj); 678 return true; 679 } 680 681 // 25.2.2.1, remaining steps. 682 JSObject* bufobj = New(cx, byteLength, proto); 683 if (!bufobj) { 684 return false; 685 } 686 args.rval().setObject(*bufobj); 687 return true; 688 } 689 690 FixedLengthSharedArrayBufferObject* SharedArrayBufferObject::New( 691 JSContext* cx, size_t length, HandleObject proto) { 692 bool isGrowable = false; 693 size_t maxLength = length; 694 auto* buffer = SharedArrayRawBuffer::Allocate(isGrowable, length, maxLength); 695 if (!buffer) { 696 js::ReportOutOfMemory(cx); 697 return nullptr; 698 } 699 700 auto* obj = New(cx, buffer, length, proto); 701 if (!obj) { 702 buffer->dropReference(); 703 return nullptr; 704 } 705 706 return obj; 707 } 708 709 FixedLengthSharedArrayBufferObject* SharedArrayBufferObject::New( 710 JSContext* cx, SharedArrayRawBuffer* buffer, size_t length, 711 HandleObject proto) { 712 return NewWith<FixedLengthSharedArrayBufferObject>(cx, buffer, length, proto); 713 } 714 715 GrowableSharedArrayBufferObject* SharedArrayBufferObject::NewGrowable( 716 JSContext* cx, size_t length, size_t maxLength, HandleObject proto) { 717 bool isGrowable = true; 718 auto* buffer = SharedArrayRawBuffer::Allocate(isGrowable, length, maxLength); 719 if (!buffer) { 720 js::ReportOutOfMemory(cx); 721 return nullptr; 722 } 723 724 auto* obj = NewGrowable(cx, buffer, maxLength, proto); 725 if (!obj) { 726 buffer->dropReference(); 727 return nullptr; 728 } 729 730 return obj; 731 } 732 733 GrowableSharedArrayBufferObject* SharedArrayBufferObject::NewGrowable( 734 JSContext* cx, SharedArrayRawBuffer* buffer, size_t maxLength, 735 HandleObject proto) { 736 return NewWith<GrowableSharedArrayBufferObject>(cx, buffer, maxLength, proto); 737 } 738 739 template <class SharedArrayBufferType> 740 SharedArrayBufferType* SharedArrayBufferObject::NewWith( 741 JSContext* cx, SharedArrayRawBuffer* buffer, size_t length, 742 HandleObject proto) { 743 MOZ_ASSERT(cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled()); 744 745 static_assert( 746 std::is_same_v<SharedArrayBufferType, 747 FixedLengthSharedArrayBufferObject> || 748 std::is_same_v<SharedArrayBufferType, GrowableSharedArrayBufferObject>); 749 750 if (!buffer->isWasm()) { 751 if constexpr (std::is_same_v<SharedArrayBufferType, 752 FixedLengthSharedArrayBufferObject>) { 753 MOZ_ASSERT(!buffer->isGrowableJS()); 754 } else { 755 MOZ_ASSERT(buffer->isGrowableJS()); 756 } 757 } 758 759 AutoSetNewObjectMetadata metadata(cx); 760 auto* obj = NewObjectWithClassProto<SharedArrayBufferType>(cx, proto); 761 if (!obj) { 762 return nullptr; 763 } 764 765 MOZ_ASSERT(obj->getClass() == &SharedArrayBufferType::class_); 766 767 cx->runtime()->incSABCount(); 768 769 if (!obj->acceptRawBuffer(buffer, length)) { 770 js::ReportOutOfMemory(cx); 771 return nullptr; 772 } 773 774 return obj; 775 } 776 777 bool SharedArrayBufferObject::acceptRawBuffer(SharedArrayRawBuffer* buffer, 778 size_t length) { 779 MOZ_ASSERT(!isInitialized()); 780 if (!zone()->addSharedMemory(buffer, 781 SharedArrayMappedSize(buffer->isWasm(), length), 782 MemoryUse::SharedArrayRawBuffer)) { 783 return false; 784 } 785 786 setFixedSlot(RAWBUF_SLOT, PrivateValue(buffer)); 787 setFixedSlot(LENGTH_SLOT, PrivateValue(length)); 788 MOZ_ASSERT(isInitialized()); 789 return true; 790 } 791 792 void SharedArrayBufferObject::dropRawBuffer() { 793 size_t length = byteLengthOrMaxByteLength(); 794 size_t size = SharedArrayMappedSize(isWasm(), length); 795 zoneFromAnyThread()->removeSharedMemory(rawBufferObject(), size, 796 MemoryUse::SharedArrayRawBuffer); 797 rawBufferObject()->dropReference(); 798 setFixedSlot(RAWBUF_SLOT, UndefinedValue()); 799 MOZ_ASSERT(!isInitialized()); 800 } 801 802 SharedArrayRawBuffer* SharedArrayBufferObject::rawBufferObject() const { 803 Value v = getFixedSlot(RAWBUF_SLOT); 804 MOZ_ASSERT(!v.isUndefined()); 805 return reinterpret_cast<SharedArrayRawBuffer*>(v.toPrivate()); 806 } 807 808 void SharedArrayBufferObject::Finalize(JS::GCContext* gcx, JSObject* obj) { 809 // Must be foreground finalizable so that we can account for the object. 810 MOZ_ASSERT(gcx->onMainThread()); 811 gcx->runtime()->decSABCount(); 812 813 SharedArrayBufferObject& buf = obj->as<SharedArrayBufferObject>(); 814 815 // Detect the case of failure during SharedArrayBufferObject creation, 816 // which causes a SharedArrayRawBuffer to never be attached. 817 Value v = buf.getFixedSlot(RAWBUF_SLOT); 818 if (!v.isUndefined()) { 819 buf.dropRawBuffer(); 820 } 821 } 822 823 /* static */ 824 void SharedArrayBufferObject::addSizeOfExcludingThis( 825 JSObject* obj, mozilla::MallocSizeOf mallocSizeOf, JS::ClassInfo* info, 826 JS::RuntimeSizes* runtimeSizes) { 827 // Divide the buffer size by the refcount to get the fraction of the buffer 828 // owned by this thread. It's conceivable that the refcount might change in 829 // the middle of memory reporting, in which case the amount reported for 830 // some threads might be to high (if the refcount goes up) or too low (if 831 // the refcount goes down). But that's unlikely and hard to avoid, so we 832 // just live with the risk. 833 const SharedArrayBufferObject& buf = obj->as<SharedArrayBufferObject>(); 834 835 if (MOZ_UNLIKELY(!buf.isInitialized())) { 836 return; 837 } 838 839 size_t nbytes = buf.byteLengthOrMaxByteLength(); 840 size_t owned = nbytes / buf.rawBufferObject()->refcount(); 841 if (buf.isWasm()) { 842 info->objectsNonHeapElementsWasmShared += owned; 843 if (runtimeSizes) { 844 size_t ownedGuardPages = 845 (buf.wasmMappedSize() - nbytes) / buf.rawBufferObject()->refcount(); 846 runtimeSizes->wasmGuardPages += ownedGuardPages; 847 } 848 } else { 849 info->objectsNonHeapElementsShared += owned; 850 } 851 } 852 853 /* static */ 854 void SharedArrayBufferObject::copyData(ArrayBufferObjectMaybeShared* toBuffer, 855 size_t toIndex, 856 ArrayBufferObjectMaybeShared* fromBuffer, 857 size_t fromIndex, size_t count) { 858 MOZ_ASSERT(!toBuffer->isDetached()); 859 MOZ_ASSERT(toBuffer->byteLength() >= count); 860 MOZ_ASSERT(toBuffer->byteLength() >= toIndex + count); 861 MOZ_ASSERT(!fromBuffer->isDetached()); 862 MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex); 863 MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex + count); 864 865 jit::AtomicOperations::memcpySafeWhenRacy( 866 toBuffer->dataPointerEither() + toIndex, 867 fromBuffer->dataPointerEither() + fromIndex, count); 868 } 869 870 SharedArrayBufferObject* SharedArrayBufferObject::createFromNewRawBuffer( 871 JSContext* cx, WasmSharedArrayRawBuffer* buffer, size_t initialSize) { 872 MOZ_ASSERT(cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled()); 873 874 AutoSetNewObjectMetadata metadata(cx); 875 auto* obj = NewBuiltinClassInstance<FixedLengthSharedArrayBufferObject>(cx); 876 if (!obj) { 877 buffer->dropReference(); 878 return nullptr; 879 } 880 881 cx->runtime()->incSABCount(); 882 883 if (!obj->acceptRawBuffer(buffer, initialSize)) { 884 buffer->dropReference(); 885 js::ReportOutOfMemory(cx); 886 return nullptr; 887 } 888 889 return obj; 890 } 891 892 template <typename SharedArrayBufferType> 893 SharedArrayBufferType* SharedArrayBufferObject::createFromWasmObject( 894 JSContext* cx, Handle<SharedArrayBufferObject*> wasmBuffer) { 895 MOZ_ASSERT(cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled()); 896 MOZ_ASSERT(wasmBuffer->isWasm()); 897 898 SharedArrayRawBuffer* rawBuffer = wasmBuffer->rawBufferObject(); 899 size_t byteLengthOrMaximum; 900 if constexpr (std::is_same_v<SharedArrayBufferType, 901 GrowableSharedArrayBufferObject>) { 902 byteLengthOrMaximum = rawBuffer->toWasmBuffer()->wasmClampedMaxByteLength(); 903 } else { 904 static_assert(std::is_same_v<SharedArrayBufferType, 905 FixedLengthSharedArrayBufferObject>); 906 byteLengthOrMaximum = rawBuffer->volatileByteLength(); 907 } 908 909 if (!rawBuffer->addReference()) { 910 JS_ReportErrorASCII(cx, "Reference count overflow on SharedArrayBuffer"); 911 return nullptr; 912 } 913 914 SharedArrayBufferType* obj = NewWith<SharedArrayBufferType>( 915 cx, rawBuffer, byteLengthOrMaximum, nullptr); 916 if (!obj) { 917 rawBuffer->dropReference(); 918 return nullptr; 919 } 920 921 return obj; 922 } 923 924 template FixedLengthSharedArrayBufferObject* SharedArrayBufferObject:: 925 createFromWasmObject<FixedLengthSharedArrayBufferObject>( 926 JSContext* cx, Handle<SharedArrayBufferObject*> wasmBuffer); 927 928 template GrowableSharedArrayBufferObject* 929 SharedArrayBufferObject::createFromWasmObject<GrowableSharedArrayBufferObject>( 930 JSContext* cx, Handle<SharedArrayBufferObject*> wasmBuffer); 931 932 /* static */ 933 void SharedArrayBufferObject::wasmDiscard(Handle<SharedArrayBufferObject*> buf, 934 uint64_t byteOffset, 935 uint64_t byteLen) { 936 MOZ_ASSERT(buf->isWasm()); 937 buf->rawWasmBufferObject()->discard(byteOffset, byteLen); 938 } 939 940 static const JSClassOps SharedArrayBufferObjectClassOps = { 941 nullptr, // addProperty 942 nullptr, // delProperty 943 nullptr, // enumerate 944 nullptr, // newEnumerate 945 nullptr, // resolve 946 nullptr, // mayResolve 947 SharedArrayBufferObject::Finalize, // finalize 948 nullptr, // call 949 nullptr, // construct 950 nullptr, // trace 951 }; 952 953 static const JSFunctionSpec sharedarray_functions[] = { 954 JS_FS_END, 955 }; 956 957 static const JSPropertySpec sharedarray_properties[] = { 958 JS_SELF_HOSTED_SYM_GET(species, "$SharedArrayBufferSpecies", 0), 959 JS_PS_END, 960 }; 961 962 static const JSFunctionSpec sharedarray_proto_functions[] = { 963 JS_FN("slice", SharedArrayBufferObject::slice, 2, 0), 964 JS_FN("grow", SharedArrayBufferObject::grow, 1, 0), 965 JS_FS_END, 966 }; 967 968 static const JSPropertySpec sharedarray_proto_properties[] = { 969 JS_INLINABLE_PSG("byteLength", SharedArrayBufferObject::byteLengthGetter, 0, 970 SharedArrayBufferByteLength), 971 JS_PSG("maxByteLength", SharedArrayBufferObject::maxByteLengthGetter, 0), 972 JS_PSG("growable", SharedArrayBufferObject::growableGetter, 0), 973 JS_STRING_SYM_PS(toStringTag, "SharedArrayBuffer", JSPROP_READONLY), 974 JS_PS_END, 975 }; 976 977 static JSObject* CreateSharedArrayBufferPrototype(JSContext* cx, 978 JSProtoKey key) { 979 return GlobalObject::createBlankPrototype( 980 cx, cx->global(), &SharedArrayBufferObject::protoClass_); 981 } 982 983 static const ClassSpec SharedArrayBufferObjectClassSpec = { 984 GenericCreateConstructor<SharedArrayBufferObject::class_constructor, 1, 985 gc::AllocKind::FUNCTION>, 986 CreateSharedArrayBufferPrototype, 987 sharedarray_functions, 988 sharedarray_properties, 989 sharedarray_proto_functions, 990 sharedarray_proto_properties, 991 GenericFinishInit<WhichHasRealmFuseProperty::ProtoAndCtor>, 992 }; 993 994 const JSClass SharedArrayBufferObject::protoClass_ = { 995 "SharedArrayBuffer.prototype", 996 JSCLASS_HAS_CACHED_PROTO(JSProto_SharedArrayBuffer), 997 JS_NULL_CLASS_OPS, 998 &SharedArrayBufferObjectClassSpec, 999 }; 1000 1001 const JSClass FixedLengthSharedArrayBufferObject::class_ = { 1002 "SharedArrayBuffer", 1003 JSCLASS_DELAY_METADATA_BUILDER | 1004 JSCLASS_HAS_RESERVED_SLOTS(SharedArrayBufferObject::RESERVED_SLOTS) | 1005 JSCLASS_HAS_CACHED_PROTO(JSProto_SharedArrayBuffer) | 1006 JSCLASS_FOREGROUND_FINALIZE, 1007 &SharedArrayBufferObjectClassOps, 1008 &SharedArrayBufferObjectClassSpec, 1009 JS_NULL_CLASS_EXT, 1010 }; 1011 1012 const JSClass GrowableSharedArrayBufferObject::class_ = { 1013 "SharedArrayBuffer", 1014 JSCLASS_DELAY_METADATA_BUILDER | 1015 JSCLASS_HAS_RESERVED_SLOTS(SharedArrayBufferObject::RESERVED_SLOTS) | 1016 JSCLASS_HAS_CACHED_PROTO(JSProto_SharedArrayBuffer) | 1017 JSCLASS_FOREGROUND_FINALIZE, 1018 &SharedArrayBufferObjectClassOps, 1019 &SharedArrayBufferObjectClassSpec, 1020 JS_NULL_CLASS_EXT, 1021 }; 1022 1023 JS_PUBLIC_API size_t JS::GetSharedArrayBufferByteLength(JSObject* obj) { 1024 auto* aobj = obj->maybeUnwrapAs<SharedArrayBufferObject>(); 1025 return aobj ? aobj->byteLength() : 0; 1026 } 1027 1028 JS_PUBLIC_API void JS::GetSharedArrayBufferLengthAndData(JSObject* obj, 1029 size_t* length, 1030 bool* isSharedMemory, 1031 uint8_t** data) { 1032 MOZ_ASSERT(obj->is<SharedArrayBufferObject>()); 1033 *length = obj->as<SharedArrayBufferObject>().byteLength(); 1034 *data = obj->as<SharedArrayBufferObject>().dataPointerShared().unwrap( 1035 /*safe - caller knows*/); 1036 *isSharedMemory = true; 1037 } 1038 1039 JS_PUBLIC_API JSObject* JS::NewSharedArrayBuffer(JSContext* cx, size_t nbytes) { 1040 MOZ_ASSERT(cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled()); 1041 1042 if (nbytes > ArrayBufferObject::ByteLengthLimit) { 1043 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1044 JSMSG_SHARED_ARRAY_BAD_LENGTH); 1045 return nullptr; 1046 } 1047 1048 return SharedArrayBufferObject::New(cx, nbytes, 1049 /* proto = */ nullptr); 1050 } 1051 1052 JS_PUBLIC_API bool JS::IsSharedArrayBufferObject(JSObject* obj) { 1053 return obj->canUnwrapAs<SharedArrayBufferObject>(); 1054 } 1055 1056 JS_PUBLIC_API uint8_t* JS::GetSharedArrayBufferData( 1057 JSObject* obj, bool* isSharedMemory, const JS::AutoRequireNoGC&) { 1058 auto* aobj = obj->maybeUnwrapAs<SharedArrayBufferObject>(); 1059 if (!aobj) { 1060 return nullptr; 1061 } 1062 *isSharedMemory = true; 1063 return aobj->dataPointerShared().unwrap(/*safe - caller knows*/); 1064 } 1065 1066 JS_PUBLIC_API bool JS::ContainsSharedArrayBuffer(JSContext* cx) { 1067 return cx->runtime()->hasLiveSABs(); 1068 }