ArrayBufferObject.cpp (138969B)
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/ArrayBufferObject-inl.h" 8 #include "vm/ArrayBufferObject.h" 9 10 #include "mozilla/Assertions.h" 11 #include "mozilla/Attributes.h" 12 #include "mozilla/DebugOnly.h" 13 #include "mozilla/Likely.h" 14 #include "mozilla/Maybe.h" 15 #include "mozilla/ScopeExit.h" 16 #include "mozilla/TaggedAnonymousMemory.h" 17 18 #include <algorithm> // std::max, std::min 19 #include <memory> // std::uninitialized_copy_n 20 #include <string.h> 21 #if !defined(XP_WIN) && !defined(__wasi__) 22 # include <sys/mman.h> 23 #endif 24 #include <tuple> // std::tuple 25 #include <type_traits> 26 #ifdef MOZ_VALGRIND 27 # include <valgrind/memcheck.h> 28 #endif 29 30 #include "jsnum.h" 31 #include "jstypes.h" 32 33 #include "gc/Barrier.h" 34 #include "gc/Memory.h" 35 #include "jit/InlinableNatives.h" 36 #include "js/ArrayBuffer.h" 37 #include "js/Conversions.h" 38 #include "js/experimental/TypedData.h" // JS_IsArrayBufferViewObject 39 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* 40 #include "js/MemoryMetrics.h" 41 #include "js/Prefs.h" 42 #include "js/PropertySpec.h" 43 #include "js/SharedArrayBuffer.h" 44 #include "js/Wrapper.h" 45 #include "util/WindowsWrapper.h" 46 #include "vm/GlobalObject.h" 47 #include "vm/Interpreter.h" 48 #include "vm/JSContext.h" 49 #include "vm/JSObject.h" 50 #include "vm/SelfHosting.h" 51 #include "vm/SharedArrayObject.h" 52 #include "vm/Warnings.h" // js::WarnNumberASCII 53 #include "wasm/WasmConstants.h" 54 #include "wasm/WasmLog.h" 55 #include "wasm/WasmMemory.h" 56 #include "wasm/WasmModuleTypes.h" 57 #include "wasm/WasmProcess.h" 58 59 #include "gc/GCContext-inl.h" 60 #include "gc/Marking-inl.h" 61 #include "vm/NativeObject-inl.h" 62 #include "vm/Realm-inl.h" // js::AutoRealm 63 64 using js::wasm::AddressType; 65 using js::wasm::Pages; 66 using mozilla::Atomic; 67 using mozilla::DebugOnly; 68 using mozilla::Maybe; 69 using mozilla::Nothing; 70 using mozilla::Some; 71 72 using namespace js; 73 74 // Wasm allows large amounts of memory to be reserved at a time. On 64-bit 75 // platforms (with "huge memories") we reserve around 4GB of virtual address 76 // space for every wasm memory; on 32-bit platforms we usually do not, but users 77 // often initialize memories in the hundreds of megabytes. 78 // 79 // If too many wasm memories remain live, we run up against system resource 80 // exhaustion (address space or number of memory map descriptors) - see bug 81 // 1068684, bug 1073934, bug 1517412, bug 1502733 for details. The limiting case 82 // seems to be Android on ARM64, where the per-process address space is limited 83 // to 4TB (39 bits) by the organization of the page tables. An earlier problem 84 // was Windows Vista Home 64-bit, where the per-process address space is limited 85 // to 8TB (40 bits). And 32-bit platforms only have 4GB of address space anyway. 86 // 87 // Thus we track the amount of memory reserved for wasm, and set a limit per 88 // process. We trigger GC work when we approach the limit and we throw an OOM 89 // error if the per-process limit is exceeded. The limit (WasmReservedBytesMax) 90 // is specific to architecture, OS, and OS configuration. 91 // 92 // Since the WasmReservedBytesMax limit is not generally accounted for by 93 // any existing GC-trigger heuristics, we need an extra heuristic for triggering 94 // GCs when the caller is allocating memories rapidly without other garbage 95 // (e.g. bug 1773225). Thus, once the reserved memory crosses the threshold 96 // WasmReservedBytesStartTriggering, we start triggering GCs every 97 // WasmReservedBytesPerTrigger bytes. Once we reach 98 // WasmReservedBytesStartSyncFullGC bytes reserved, we perform expensive 99 // non-incremental full GCs as a last-ditch effort to avoid unnecessary failure. 100 // Once we reach WasmReservedBytesMax, we perform further full GCs before giving 101 // up. 102 // 103 // (History: The original implementation only tracked the number of "huge 104 // memories" allocated by WASM, but this was found to be insufficient because 105 // 32-bit platforms have similar resource exhaustion issues. We now track 106 // reserved bytes directly.) 107 // 108 // (We also used to reserve significantly more than 4GB for huge memories, but 109 // this was reduced in bug 1442544.) 110 111 // ASAN and TSAN use a ton of vmem for bookkeeping leaving a lot less for the 112 // program so use a lower limit. 113 #if defined(MOZ_TSAN) || defined(MOZ_ASAN) 114 static const uint64_t WasmMemAsanOverhead = 2; 115 #else 116 static const uint64_t WasmMemAsanOverhead = 1; 117 #endif 118 119 // WasmReservedStartTriggering + WasmReservedPerTrigger must be well below 120 // WasmReservedStartSyncFullGC in order to provide enough time for incremental 121 // GC to do its job. 122 123 #if defined(JS_CODEGEN_ARM64) && defined(ANDROID) 124 125 static const uint64_t WasmReservedBytesMax = 126 75 * wasm::HugeMappedSize / WasmMemAsanOverhead; 127 static const uint64_t WasmReservedBytesStartTriggering = 128 15 * wasm::HugeMappedSize; 129 static const uint64_t WasmReservedBytesStartSyncFullGC = 130 WasmReservedBytesMax - 15 * wasm::HugeMappedSize; 131 static const uint64_t WasmReservedBytesPerTrigger = 15 * wasm::HugeMappedSize; 132 133 #elif defined(WASM_SUPPORTS_HUGE_MEMORY) 134 135 static const uint64_t WasmReservedBytesMax = 136 1000 * wasm::HugeMappedSize / WasmMemAsanOverhead; 137 static const uint64_t WasmReservedBytesStartTriggering = 138 100 * wasm::HugeMappedSize; 139 static const uint64_t WasmReservedBytesStartSyncFullGC = 140 WasmReservedBytesMax - 100 * wasm::HugeMappedSize; 141 static const uint64_t WasmReservedBytesPerTrigger = 100 * wasm::HugeMappedSize; 142 143 #else // 32-bit (and weird 64-bit platforms without huge memory) 144 145 static const uint64_t GiB = 1024 * 1024 * 1024; 146 147 static const uint64_t WasmReservedBytesMax = 148 (4 * GiB) / 2 / WasmMemAsanOverhead; 149 static const uint64_t WasmReservedBytesStartTriggering = (4 * GiB) / 8; 150 static const uint64_t WasmReservedBytesStartSyncFullGC = 151 WasmReservedBytesMax - (4 * GiB) / 8; 152 static const uint64_t WasmReservedBytesPerTrigger = (4 * GiB) / 8; 153 154 #endif 155 156 // The total number of bytes reserved for wasm memories. 157 static Atomic<uint64_t, mozilla::ReleaseAcquire> wasmReservedBytes(0); 158 // The number of bytes of wasm memory reserved since the last GC trigger. 159 static Atomic<uint64_t, mozilla::ReleaseAcquire> wasmReservedBytesSinceLast(0); 160 161 uint64_t js::WasmReservedBytes() { return wasmReservedBytes; } 162 163 [[nodiscard]] static bool CheckArrayBufferTooLarge(JSContext* cx, 164 uint64_t nbytes) { 165 // Refuse to allocate too large buffers. 166 if (MOZ_UNLIKELY(nbytes > ArrayBufferObject::ByteLengthLimit)) { 167 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 168 JSMSG_BAD_ARRAY_LENGTH); 169 return false; 170 } 171 172 return true; 173 } 174 175 void* js::MapBufferMemory(wasm::AddressType t, wasm::PageSize pageSize, 176 size_t mappedSize, size_t initialCommittedSize) { 177 MOZ_ASSERT(mappedSize % gc::SystemPageSize() == 0); 178 MOZ_ASSERT(initialCommittedSize % gc::SystemPageSize() == 0); 179 MOZ_ASSERT(initialCommittedSize <= mappedSize); 180 181 auto failed = mozilla::MakeScopeExit( 182 [&] { wasmReservedBytes -= uint64_t(mappedSize); }); 183 wasmReservedBytes += uint64_t(mappedSize); 184 185 // Test >= to guard against the case where multiple extant runtimes 186 // race to allocate. 187 if (wasmReservedBytes >= WasmReservedBytesMax) { 188 if (OnLargeAllocationFailure) { 189 OnLargeAllocationFailure(); 190 } 191 if (wasmReservedBytes >= WasmReservedBytesMax) { 192 return nullptr; 193 } 194 } 195 196 #ifdef XP_WIN 197 void* data = VirtualAlloc(nullptr, mappedSize, MEM_RESERVE, PAGE_NOACCESS); 198 if (!data) { 199 return nullptr; 200 } 201 202 if (!VirtualAlloc(data, initialCommittedSize, MEM_COMMIT, PAGE_READWRITE)) { 203 VirtualFree(data, 0, MEM_RELEASE); 204 return nullptr; 205 } 206 207 gc::RecordMemoryAlloc(initialCommittedSize); 208 #elif defined(__wasi__) 209 void* data = nullptr; 210 if (int err = posix_memalign(&data, gc::SystemPageSize(), mappedSize)) { 211 MOZ_ASSERT(err == ENOMEM); 212 (void)err; 213 return nullptr; 214 } 215 MOZ_ASSERT(data); 216 memset(data, 0, mappedSize); 217 #else // !XP_WIN && !__wasi__ 218 void* data = 219 MozTaggedAnonymousMmap(nullptr, mappedSize, PROT_NONE, 220 MAP_PRIVATE | MAP_ANON, -1, 0, "wasm-reserved"); 221 if (data == MAP_FAILED) { 222 return nullptr; 223 } 224 225 // Note we will waste a page on zero-sized memories here 226 if (mprotect(data, initialCommittedSize, PROT_READ | PROT_WRITE)) { 227 munmap(data, mappedSize); 228 return nullptr; 229 } 230 231 gc::RecordMemoryAlloc(initialCommittedSize); 232 #endif // !XP_WIN && !__wasi__ 233 234 #if defined(MOZ_VALGRIND) && \ 235 defined(VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE) 236 VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE( 237 (unsigned char*)data + initialCommittedSize, 238 mappedSize - initialCommittedSize); 239 #endif 240 241 failed.release(); 242 return data; 243 } 244 245 bool js::CommitBufferMemory(void* dataEnd, size_t delta) { 246 MOZ_ASSERT(delta); 247 MOZ_ASSERT(delta % gc::SystemPageSize() == 0); 248 249 #ifdef XP_WIN 250 if (!VirtualAlloc(dataEnd, delta, MEM_COMMIT, PAGE_READWRITE)) { 251 return false; 252 } 253 #elif defined(__wasi__) 254 // posix_memalign'd memory is already committed 255 return true; 256 #else 257 if (mprotect(dataEnd, delta, PROT_READ | PROT_WRITE)) { 258 return false; 259 } 260 #endif // XP_WIN 261 262 gc::RecordMemoryAlloc(delta); 263 264 #if defined(MOZ_VALGRIND) && \ 265 defined(VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE) 266 VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE((unsigned char*)dataEnd, delta); 267 #endif 268 269 return true; 270 } 271 272 void js::UnmapBufferMemory(wasm::AddressType t, void* base, size_t mappedSize, 273 size_t committedSize) { 274 MOZ_ASSERT(mappedSize % gc::SystemPageSize() == 0); 275 MOZ_ASSERT(committedSize % gc::SystemPageSize() == 0); 276 277 #ifdef XP_WIN 278 VirtualFree(base, 0, MEM_RELEASE); 279 gc::RecordMemoryFree(committedSize); 280 #elif defined(__wasi__) 281 free(base); 282 (void)committedSize; 283 #else 284 munmap(base, mappedSize); 285 gc::RecordMemoryFree(committedSize); 286 #endif // XP_WIN 287 288 #if defined(MOZ_VALGRIND) && \ 289 defined(VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE) 290 VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE((unsigned char*)base, 291 mappedSize); 292 #endif 293 294 // Untrack reserved memory *after* releasing memory -- otherwise, a race 295 // condition could enable the creation of unlimited buffers. 296 wasmReservedBytes -= uint64_t(mappedSize); 297 } 298 299 /* 300 * ArrayBufferObject 301 * 302 * This class holds the underlying raw buffer that the TypedArrayObject classes 303 * access. It can be created explicitly and passed to a TypedArrayObject, or 304 * can be created implicitly by constructing a TypedArrayObject with a size. 305 */ 306 307 /* 308 * ArrayBufferObject (base) 309 */ 310 311 static const JSClassOps ArrayBufferObjectClassOps = { 312 nullptr, // addProperty 313 nullptr, // delProperty 314 nullptr, // enumerate 315 nullptr, // newEnumerate 316 nullptr, // resolve 317 nullptr, // mayResolve 318 ArrayBufferObject::finalize, // finalize 319 nullptr, // call 320 nullptr, // construct 321 nullptr, // trace 322 }; 323 324 static const JSFunctionSpec arraybuffer_functions[] = { 325 JS_FN("isView", ArrayBufferObject::fun_isView, 1, 0), 326 JS_FS_END, 327 }; 328 329 static const JSPropertySpec arraybuffer_properties[] = { 330 JS_SELF_HOSTED_SYM_GET(species, "$ArrayBufferSpecies", 0), 331 JS_PS_END, 332 }; 333 334 static const JSFunctionSpec arraybuffer_proto_functions[] = { 335 JS_FN("slice", ArrayBufferObject::slice, 2, 0), 336 #ifdef NIGHTLY_BUILD 337 JS_FN("sliceToImmutable", ArrayBufferObject::sliceToImmutable, 2, 0), 338 #endif 339 JS_FN("resize", ArrayBufferObject::resize, 1, 0), 340 JS_FN("transfer", ArrayBufferObject::transfer, 0, 0), 341 JS_FN("transferToFixedLength", ArrayBufferObject::transferToFixedLength, 0, 342 0), 343 #ifdef NIGHTLY_BUILD 344 JS_FN("transferToImmutable", ArrayBufferObject::transferToImmutable, 0, 0), 345 #endif 346 JS_FS_END, 347 }; 348 349 static const JSPropertySpec arraybuffer_proto_properties[] = { 350 JS_INLINABLE_PSG("byteLength", ArrayBufferObject::byteLengthGetter, 0, 351 ArrayBufferByteLength), 352 JS_PSG("maxByteLength", ArrayBufferObject::maxByteLengthGetter, 0), 353 JS_PSG("resizable", ArrayBufferObject::resizableGetter, 0), 354 JS_PSG("detached", ArrayBufferObject::detachedGetter, 0), 355 #ifdef NIGHTLY_BUILD 356 JS_PSG("immutable", ArrayBufferObject::immutableGetter, 0), 357 #endif 358 JS_STRING_SYM_PS(toStringTag, "ArrayBuffer", JSPROP_READONLY), 359 JS_PS_END, 360 }; 361 362 static JSObject* CreateArrayBufferPrototype(JSContext* cx, JSProtoKey key) { 363 return GlobalObject::createBlankPrototype(cx, cx->global(), 364 &ArrayBufferObject::protoClass_); 365 } 366 367 static const ClassSpec ArrayBufferObjectClassSpec = { 368 GenericCreateConstructor<ArrayBufferObject::class_constructor, 1, 369 gc::AllocKind::FUNCTION>, 370 CreateArrayBufferPrototype, 371 arraybuffer_functions, 372 arraybuffer_properties, 373 arraybuffer_proto_functions, 374 arraybuffer_proto_properties, 375 GenericFinishInit<WhichHasRealmFuseProperty::ProtoAndCtor>, 376 }; 377 378 static const ClassExtension FixedLengthArrayBufferObjectClassExtension = { 379 ArrayBufferObject::objectMoved< 380 FixedLengthArrayBufferObject>, // objectMovedOp 381 }; 382 383 static const ClassExtension ResizableArrayBufferObjectClassExtension = { 384 ArrayBufferObject::objectMoved< 385 ResizableArrayBufferObject>, // objectMovedOp 386 }; 387 388 static const ClassExtension ImmutableArrayBufferObjectClassExtension = { 389 ArrayBufferObject::objectMoved< 390 ImmutableArrayBufferObject>, // objectMovedOp 391 }; 392 393 const JSClass ArrayBufferObject::protoClass_ = { 394 "ArrayBuffer.prototype", 395 JSCLASS_HAS_CACHED_PROTO(JSProto_ArrayBuffer), 396 JS_NULL_CLASS_OPS, 397 &ArrayBufferObjectClassSpec, 398 }; 399 400 const JSClass FixedLengthArrayBufferObject::class_ = { 401 "ArrayBuffer", 402 JSCLASS_DELAY_METADATA_BUILDER | 403 JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) | 404 JSCLASS_HAS_CACHED_PROTO(JSProto_ArrayBuffer) | 405 JSCLASS_BACKGROUND_FINALIZE, 406 &ArrayBufferObjectClassOps, 407 &ArrayBufferObjectClassSpec, 408 &FixedLengthArrayBufferObjectClassExtension, 409 }; 410 411 const JSClass ResizableArrayBufferObject::class_ = { 412 "ArrayBuffer", 413 JSCLASS_DELAY_METADATA_BUILDER | 414 JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) | 415 JSCLASS_HAS_CACHED_PROTO(JSProto_ArrayBuffer) | 416 JSCLASS_BACKGROUND_FINALIZE, 417 &ArrayBufferObjectClassOps, 418 &ArrayBufferObjectClassSpec, 419 &ResizableArrayBufferObjectClassExtension, 420 }; 421 422 const JSClass ImmutableArrayBufferObject::class_ = { 423 "ArrayBuffer", 424 JSCLASS_DELAY_METADATA_BUILDER | 425 JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) | 426 JSCLASS_HAS_CACHED_PROTO(JSProto_ArrayBuffer) | 427 JSCLASS_BACKGROUND_FINALIZE, 428 &ArrayBufferObjectClassOps, 429 &ArrayBufferObjectClassSpec, 430 &ImmutableArrayBufferObjectClassExtension, 431 }; 432 433 static bool IsArrayBuffer(HandleValue v) { 434 return v.isObject() && v.toObject().is<ArrayBufferObject>(); 435 } 436 437 static bool IsResizableArrayBuffer(HandleValue v) { 438 return v.isObject() && v.toObject().is<ResizableArrayBufferObject>(); 439 } 440 441 MOZ_ALWAYS_INLINE bool ArrayBufferObject::byteLengthGetterImpl( 442 JSContext* cx, const CallArgs& args) { 443 MOZ_ASSERT(IsArrayBuffer(args.thisv())); 444 auto* buffer = &args.thisv().toObject().as<ArrayBufferObject>(); 445 args.rval().setNumber(buffer->byteLength()); 446 return true; 447 } 448 449 bool ArrayBufferObject::byteLengthGetter(JSContext* cx, unsigned argc, 450 Value* vp) { 451 CallArgs args = CallArgsFromVp(argc, vp); 452 return CallNonGenericMethod<IsArrayBuffer, byteLengthGetterImpl>(cx, args); 453 } 454 455 enum class PreserveResizability { No, Yes, Immutable }; 456 457 /** 458 * ArrayBufferCopyAndDetach ( arrayBuffer, newLength, preserveResizability ) 459 * 460 * https://tc39.es/proposal-arraybuffer-transfer/#sec-arraybuffercopyanddetach 461 */ 462 static ArrayBufferObject* ArrayBufferCopyAndDetach( 463 JSContext* cx, Handle<ArrayBufferObject*> arrayBuffer, 464 Handle<Value> newLength, PreserveResizability preserveResizability) { 465 // Steps 1-2. (Not applicable in our implementation.) 466 467 // Steps 3-4. 468 uint64_t newByteLength; 469 if (newLength.isUndefined()) { 470 // Step 3.a. 471 newByteLength = arrayBuffer->byteLength(); 472 } else { 473 // Step 4.a. 474 if (!ToIndex(cx, newLength, &newByteLength)) { 475 return nullptr; 476 } 477 } 478 479 // Step 5. 480 if (arrayBuffer->isDetached()) { 481 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 482 JSMSG_TYPED_ARRAY_DETACHED); 483 return nullptr; 484 } 485 if (arrayBuffer->isImmutable()) { 486 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 487 JSMSG_ARRAYBUFFER_IMMUTABLE); 488 return nullptr; 489 } 490 if (arrayBuffer->isLengthPinned()) { 491 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 492 JSMSG_ARRAYBUFFER_LENGTH_PINNED); 493 return nullptr; 494 } 495 496 // Step 8. 497 if (arrayBuffer->hasDefinedDetachKey()) { 498 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 499 JSMSG_WASM_NO_TRANSFER); 500 return nullptr; 501 } 502 503 // Steps 9-16. 504 // 505 // 25.1.2.1 AllocateArrayBuffer, step 2. 506 // 6.2.9.1 CreateByteDataBlock, step 2. 507 if (!CheckArrayBufferTooLarge(cx, newByteLength)) { 508 return nullptr; 509 } 510 511 #ifdef NIGHTLY_BUILD 512 // Additional step from Immutable ArrayBuffer proposal. 513 if (preserveResizability == PreserveResizability::Immutable) { 514 return ImmutableArrayBufferObject::copyAndDetach(cx, size_t(newByteLength), 515 arrayBuffer); 516 } 517 #endif 518 519 // Steps 6-7. 520 if (preserveResizability == PreserveResizability::Yes && 521 arrayBuffer->isResizable()) { 522 Rooted<ResizableArrayBufferObject*> resizableBuffer( 523 cx, &arrayBuffer->as<ResizableArrayBufferObject>()); 524 525 size_t maxByteLength = resizableBuffer->maxByteLength(); 526 if (size_t(newByteLength) > maxByteLength) { 527 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 528 JSMSG_ARRAYBUFFER_LENGTH_LARGER_THAN_MAXIMUM); 529 return nullptr; 530 } 531 532 return ResizableArrayBufferObject::copyAndDetach(cx, size_t(newByteLength), 533 resizableBuffer); 534 } 535 536 return FixedLengthArrayBufferObject::copyAndDetach(cx, size_t(newByteLength), 537 arrayBuffer); 538 } 539 540 /** 541 * get ArrayBuffer.prototype.maxByteLength 542 * 543 * https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.maxbytelength 544 */ 545 bool ArrayBufferObject::maxByteLengthGetterImpl(JSContext* cx, 546 const CallArgs& args) { 547 MOZ_ASSERT(IsArrayBuffer(args.thisv())); 548 549 auto* buffer = &args.thisv().toObject().as<ArrayBufferObject>(); 550 551 // Special case for wasm with potentially 64-bits memory. 552 // Manually compute the maxByteLength to avoid an overflow on 32-bit machines. 553 if (buffer->isWasm() && buffer->isResizable()) { 554 Pages sourceMaxPages = buffer->wasmSourceMaxPages().value(); 555 uint64_t sourceMaxBytes = sourceMaxPages.byteLength64(); 556 557 MOZ_ASSERT(sourceMaxBytes <= wasm::StandardPageSizeBytes * 558 wasm::MaxMemory64StandardPagesValidation); 559 args.rval().setNumber(double(sourceMaxBytes)); 560 561 return true; 562 } 563 564 // Steps 4-6. 565 size_t maxByteLength = buffer->maxByteLength(); 566 MOZ_ASSERT_IF(buffer->isDetached(), maxByteLength == 0); 567 568 // Step 7. 569 args.rval().setNumber(maxByteLength); 570 return true; 571 } 572 573 /** 574 * get ArrayBuffer.prototype.maxByteLength 575 * 576 * https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.maxbytelength 577 */ 578 bool ArrayBufferObject::maxByteLengthGetter(JSContext* cx, unsigned argc, 579 Value* vp) { 580 // Steps 1-3. 581 CallArgs args = CallArgsFromVp(argc, vp); 582 return CallNonGenericMethod<IsArrayBuffer, maxByteLengthGetterImpl>(cx, args); 583 } 584 585 /** 586 * get ArrayBuffer.prototype.resizable 587 * 588 * https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.resizable 589 */ 590 bool ArrayBufferObject::resizableGetterImpl(JSContext* cx, 591 const CallArgs& args) { 592 MOZ_ASSERT(IsArrayBuffer(args.thisv())); 593 594 // Step 4. 595 auto* buffer = &args.thisv().toObject().as<ArrayBufferObject>(); 596 args.rval().setBoolean(buffer->isResizable()); 597 return true; 598 } 599 600 /** 601 * get ArrayBuffer.prototype.resizable 602 * 603 * https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.resizable 604 */ 605 bool ArrayBufferObject::resizableGetter(JSContext* cx, unsigned argc, 606 Value* vp) { 607 // Steps 1-3. 608 CallArgs args = CallArgsFromVp(argc, vp); 609 return CallNonGenericMethod<IsArrayBuffer, resizableGetterImpl>(cx, args); 610 } 611 612 /** 613 * get ArrayBuffer.prototype.detached 614 * 615 * https://tc39.es/proposal-arraybuffer-transfer/#sec-get-arraybuffer.prototype.detached 616 */ 617 bool ArrayBufferObject::detachedGetterImpl(JSContext* cx, 618 const CallArgs& args) { 619 MOZ_ASSERT(IsArrayBuffer(args.thisv())); 620 621 // Step 4. 622 auto* buffer = &args.thisv().toObject().as<ArrayBufferObject>(); 623 args.rval().setBoolean(buffer->isDetached()); 624 return true; 625 } 626 627 /** 628 * get ArrayBuffer.prototype.detached 629 * 630 * https://tc39.es/proposal-arraybuffer-transfer/#sec-get-arraybuffer.prototype.detached 631 */ 632 bool ArrayBufferObject::detachedGetter(JSContext* cx, unsigned argc, 633 Value* vp) { 634 // Steps 1-3. 635 CallArgs args = CallArgsFromVp(argc, vp); 636 return CallNonGenericMethod<IsArrayBuffer, detachedGetterImpl>(cx, args); 637 } 638 639 #ifdef NIGHTLY_BUILD 640 /** 641 * get ArrayBuffer.prototype.immutable 642 * 643 * https://tc39.es/proposal-immutable-arraybuffer/#sec-get-arraybuffer.prototype.immutable 644 */ 645 bool ArrayBufferObject::immutableGetterImpl(JSContext* cx, 646 const CallArgs& args) { 647 MOZ_ASSERT(IsArrayBuffer(args.thisv())); 648 649 // Step 4. 650 auto* buffer = &args.thisv().toObject().as<ArrayBufferObject>(); 651 args.rval().setBoolean(buffer->isImmutable()); 652 return true; 653 } 654 655 /** 656 * get ArrayBuffer.prototype.immutable 657 * 658 * https://tc39.es/proposal-immutable-arraybuffer/#sec-get-arraybuffer.prototype.immutable 659 */ 660 bool ArrayBufferObject::immutableGetter(JSContext* cx, unsigned argc, 661 Value* vp) { 662 // Steps 1-3. 663 CallArgs args = CallArgsFromVp(argc, vp); 664 return CallNonGenericMethod<IsArrayBuffer, immutableGetterImpl>(cx, args); 665 } 666 #endif 667 668 /** 669 * ArrayBuffer.prototype.transfer ( [ newLength ] ) 670 * 671 * https://tc39.es/proposal-arraybuffer-transfer/#sec-arraybuffer.prototype.transfer 672 */ 673 bool ArrayBufferObject::transferImpl(JSContext* cx, const CallArgs& args) { 674 MOZ_ASSERT(IsArrayBuffer(args.thisv())); 675 676 // Steps 1-2. 677 Rooted<ArrayBufferObject*> buffer( 678 cx, &args.thisv().toObject().as<ArrayBufferObject>()); 679 auto* newBuffer = ArrayBufferCopyAndDetach(cx, buffer, args.get(0), 680 PreserveResizability::Yes); 681 if (!newBuffer) { 682 return false; 683 } 684 685 args.rval().setObject(*newBuffer); 686 return true; 687 } 688 689 /** 690 * ArrayBuffer.prototype.transfer ( [ newLength ] ) 691 * 692 * https://tc39.es/proposal-arraybuffer-transfer/#sec-arraybuffer.prototype.transfer 693 */ 694 bool ArrayBufferObject::transfer(JSContext* cx, unsigned argc, Value* vp) { 695 CallArgs args = CallArgsFromVp(argc, vp); 696 return CallNonGenericMethod<IsArrayBuffer, transferImpl>(cx, args); 697 } 698 699 /** 700 * ArrayBuffer.prototype.transferToFixedLength ( [ newLength ] ) 701 * 702 * https://tc39.es/proposal-arraybuffer-transfer/#sec-arraybuffer.prototype.transfertofixedlength 703 */ 704 bool ArrayBufferObject::transferToFixedLengthImpl(JSContext* cx, 705 const CallArgs& args) { 706 MOZ_ASSERT(IsArrayBuffer(args.thisv())); 707 708 // Steps 1-2. 709 Rooted<ArrayBufferObject*> buffer( 710 cx, &args.thisv().toObject().as<ArrayBufferObject>()); 711 auto* newBuffer = ArrayBufferCopyAndDetach(cx, buffer, args.get(0), 712 PreserveResizability::No); 713 if (!newBuffer) { 714 return false; 715 } 716 717 args.rval().setObject(*newBuffer); 718 return true; 719 } 720 721 /** 722 * ArrayBuffer.prototype.transferToFixedLength ( [ newLength ] ) 723 * 724 * https://tc39.es/proposal-arraybuffer-transfer/#sec-arraybuffer.prototype.transfertofixedlength 725 */ 726 bool ArrayBufferObject::transferToFixedLength(JSContext* cx, unsigned argc, 727 Value* vp) { 728 CallArgs args = CallArgsFromVp(argc, vp); 729 return CallNonGenericMethod<IsArrayBuffer, transferToFixedLengthImpl>(cx, 730 args); 731 } 732 733 #ifdef NIGHTLY_BUILD 734 /** 735 * ArrayBuffer.prototype.transferToImmutable ( [ newLength ] ) 736 * 737 * https://tc39.es/proposal-immutable-arraybuffer/#sec-arraybuffer.prototype.transfertoimmutable 738 */ 739 bool ArrayBufferObject::transferToImmutableImpl(JSContext* cx, 740 const CallArgs& args) { 741 MOZ_ASSERT(IsArrayBuffer(args.thisv())); 742 743 // Steps 1-2. 744 Rooted<ArrayBufferObject*> buffer( 745 cx, &args.thisv().toObject().as<ArrayBufferObject>()); 746 auto* newBuffer = ArrayBufferCopyAndDetach(cx, buffer, args.get(0), 747 PreserveResizability::Immutable); 748 if (!newBuffer) { 749 return false; 750 } 751 752 args.rval().setObject(*newBuffer); 753 return true; 754 } 755 756 /** 757 * ArrayBuffer.prototype.transferToImmutable ( [ newLength ] ) 758 * 759 * https://tc39.es/proposal-immutable-arraybuffer/#sec-arraybuffer.prototype.transfertoimmutable 760 */ 761 bool ArrayBufferObject::transferToImmutable(JSContext* cx, unsigned argc, 762 Value* vp) { 763 CallArgs args = CallArgsFromVp(argc, vp); 764 return CallNonGenericMethod<IsArrayBuffer, transferToImmutableImpl>(cx, args); 765 } 766 #endif 767 768 /** 769 * ArrayBuffer.prototype.resize ( newLength ) 770 * 771 * https://tc39.es/ecma262/#sec-arraybuffer.prototype.resize 772 */ 773 bool ArrayBufferObject::resizeImpl(JSContext* cx, const CallArgs& args) { 774 MOZ_ASSERT(IsResizableArrayBuffer(args.thisv())); 775 776 Rooted<ResizableArrayBufferObject*> obj( 777 cx, &args.thisv().toObject().as<ResizableArrayBufferObject>()); 778 779 // Step 4. 780 uint64_t newByteLength; 781 if (!ToIndex(cx, args.get(0), &newByteLength)) { 782 return false; 783 } 784 785 // Step 5. 786 if (obj->isDetached()) { 787 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 788 JSMSG_TYPED_ARRAY_DETACHED); 789 return false; 790 } 791 if (obj->isLengthPinned()) { 792 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 793 JSMSG_ARRAYBUFFER_LENGTH_PINNED); 794 return false; 795 } 796 797 // Additional step from Immutable ArrayBuffer proposal. 798 MOZ_ASSERT(!obj->isImmutable(), "resizable array buffers aren't immutable"); 799 800 // Step 6. 801 if (newByteLength > obj->maxByteLength()) { 802 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 803 JSMSG_ARRAYBUFFER_LENGTH_LARGER_THAN_MAXIMUM); 804 return false; 805 } 806 807 if (obj->isWasm()) { 808 // Special case for resizing of Wasm buffers. 809 if (newByteLength % wasm::StandardPageSizeBytes != 0) { 810 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 811 JSMSG_WASM_ARRAYBUFFER_PAGE_MULTIPLE); 812 return false; 813 } 814 if (newByteLength < obj->byteLength()) { 815 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 816 JSMSG_WASM_ARRAYBUFFER_CANNOT_SHRINK); 817 return false; 818 } 819 820 Pages newPages = 821 Pages::fromByteLengthExact(newByteLength, obj->wasmPageSize()); 822 MOZ_RELEASE_ASSERT(WasmArrayBufferSourceMaxPages(obj).isSome()); 823 Rooted<ArrayBufferObject*> res( 824 cx, 825 obj->wasmGrowToPagesInPlace(obj->wasmAddressType(), newPages, obj, cx)); 826 MOZ_ASSERT_IF(res, res == obj); 827 return !!res; 828 } 829 830 // Steps 7-15. 831 obj->resize(size_t(newByteLength)); 832 833 // Step 16. 834 args.rval().setUndefined(); 835 return true; 836 } 837 838 /** 839 * ArrayBuffer.prototype.resize ( newLength ) 840 * 841 * https://tc39.es/ecma262/#sec-arraybuffer.prototype.resize 842 */ 843 bool ArrayBufferObject::resize(JSContext* cx, unsigned argc, Value* vp) { 844 // Steps 1-3. 845 CallArgs args = CallArgsFromVp(argc, vp); 846 return CallNonGenericMethod<IsResizableArrayBuffer, resizeImpl>(cx, args); 847 } 848 849 static bool IsArrayBufferSpecies(JSContext* cx, JSFunction* species) { 850 return IsSelfHostedFunctionWithName(species, 851 cx->names().dollar_ArrayBufferSpecies_); 852 } 853 854 static bool HasBuiltinArrayBufferSpecies(ArrayBufferObject* obj, 855 JSContext* cx) { 856 // Ensure `ArrayBuffer.prototype.constructor` and `ArrayBuffer[@@species]` 857 // haven't been mutated. 858 if (!cx->realm()->realmFuses.optimizeArrayBufferSpeciesFuse.intact()) { 859 return false; 860 } 861 862 // Ensure |obj|'s prototype is the actual ArrayBuffer.prototype. 863 auto* proto = cx->global()->maybeGetPrototype(JSProto_ArrayBuffer); 864 if (!proto || obj->staticPrototype() != proto) { 865 return false; 866 } 867 868 // Fail if |obj| has an own `constructor` property. 869 if (obj->containsPure(NameToId(cx->names().constructor))) { 870 return false; 871 } 872 873 return true; 874 } 875 876 /** 877 * ArrayBuffer.prototype.slice ( start, end ) 878 * 879 * https://tc39.es/ecma262/#sec-arraybuffer.prototype.slice 880 */ 881 bool ArrayBufferObject::sliceImpl(JSContext* cx, const CallArgs& args) { 882 MOZ_ASSERT(IsArrayBuffer(args.thisv())); 883 884 Rooted<ArrayBufferObject*> obj( 885 cx, &args.thisv().toObject().as<ArrayBufferObject>()); 886 887 // Step 4. 888 if (obj->isDetached()) { 889 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 890 JSMSG_TYPED_ARRAY_DETACHED); 891 return false; 892 } 893 894 // Step 5. 895 size_t len = obj->byteLength(); 896 897 // Steps 6-9. 898 size_t first = 0; 899 if (args.hasDefined(0)) { 900 if (!ToIntegerIndex(cx, args[0], len, &first)) { 901 return false; 902 } 903 } 904 905 // Steps 10-13. 906 size_t final_ = len; 907 if (args.hasDefined(1)) { 908 if (!ToIntegerIndex(cx, args[1], len, &final_)) { 909 return false; 910 } 911 } 912 913 // Step 14. 914 size_t newLen = final_ >= first ? final_ - first : 0; 915 MOZ_ASSERT(newLen <= ArrayBufferObject::ByteLengthLimit); 916 917 // Steps 15-21. 918 Rooted<JSObject*> resultObj(cx); 919 ArrayBufferObject* unwrappedResult = nullptr; 920 if (HasBuiltinArrayBufferSpecies(obj, cx)) { 921 // Steps 15-16. 922 unwrappedResult = createZeroed(cx, newLen); 923 if (!unwrappedResult) { 924 return false; 925 } 926 resultObj.set(unwrappedResult); 927 928 // Steps 17-18. (Not applicable) 929 930 // Step 19. 931 MOZ_ASSERT(!unwrappedResult->isDetached()); 932 933 // Step 20. 934 MOZ_ASSERT(unwrappedResult != obj); 935 936 // Step 21. 937 MOZ_ASSERT(unwrappedResult->byteLength() == newLen); 938 } else { 939 // Step 15. 940 Rooted<JSObject*> ctor(cx, SpeciesConstructor(cx, obj, JSProto_ArrayBuffer, 941 IsArrayBufferSpecies)); 942 if (!ctor) { 943 return false; 944 } 945 946 // Step 16. 947 { 948 FixedConstructArgs<1> cargs(cx); 949 cargs[0].setNumber(newLen); 950 951 Rooted<Value> ctorVal(cx, ObjectValue(*ctor)); 952 if (!Construct(cx, ctorVal, cargs, ctorVal, &resultObj)) { 953 return false; 954 } 955 } 956 957 // Steps 17-18. 958 unwrappedResult = resultObj->maybeUnwrapIf<ArrayBufferObject>(); 959 if (!unwrappedResult) { 960 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 961 JSMSG_NON_ARRAY_BUFFER_RETURNED); 962 return false; 963 } 964 965 // Step 19. 966 if (unwrappedResult->isDetached()) { 967 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 968 JSMSG_TYPED_ARRAY_DETACHED); 969 return false; 970 } 971 972 // Additional step from Immutable ArrayBuffer proposal. 973 if (unwrappedResult->isImmutable()) { 974 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 975 JSMSG_ARRAYBUFFER_IMMUTABLE); 976 return false; 977 } 978 979 // Step 20. 980 if (unwrappedResult == obj) { 981 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 982 JSMSG_SAME_ARRAY_BUFFER_RETURNED); 983 return false; 984 } 985 986 // Step 21. 987 size_t resultByteLength = unwrappedResult->byteLength(); 988 if (resultByteLength < newLen) { 989 ToCStringBuf resultLenCbuf; 990 const char* resultLenStr = 991 NumberToCString(&resultLenCbuf, double(resultByteLength)); 992 993 ToCStringBuf newLenCbuf; 994 const char* newLenStr = NumberToCString(&newLenCbuf, double(newLen)); 995 996 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 997 JSMSG_SHORT_ARRAY_BUFFER_RETURNED, newLenStr, 998 resultLenStr); 999 return false; 1000 } 1001 } 1002 1003 // Steps 22-23. 1004 if (obj->isDetached()) { 1005 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1006 JSMSG_TYPED_ARRAY_DETACHED); 1007 return false; 1008 } 1009 1010 // Step 26. 1011 // 1012 // Reacquire the length in case the buffer has been resized. 1013 size_t currentLen = obj->byteLength(); 1014 1015 // Steps 24-25 and 27. 1016 if (first < currentLen) { 1017 // Step 27.a. 1018 size_t count = std::min(newLen, currentLen - first); 1019 1020 // Steps 24-25 and 27.b. 1021 ArrayBufferObject::copyData(unwrappedResult, 0, obj, first, count); 1022 } 1023 1024 // Step 28. 1025 args.rval().setObject(*resultObj); 1026 return true; 1027 } 1028 1029 /** 1030 * ArrayBuffer.prototype.slice ( start, end ) 1031 * 1032 * https://tc39.es/ecma262/#sec-arraybuffer.prototype.slice 1033 */ 1034 bool ArrayBufferObject::slice(JSContext* cx, unsigned argc, Value* vp) { 1035 // Steps 1-3. 1036 CallArgs args = CallArgsFromVp(argc, vp); 1037 return CallNonGenericMethod<IsArrayBuffer, sliceImpl>(cx, args); 1038 } 1039 1040 #ifdef NIGHTLY_BUILD 1041 /** 1042 * ArrayBuffer.prototype.sliceToImmutable ( start, end ) 1043 * 1044 * https://tc39.es/proposal-immutable-arraybuffer/#sec-arraybuffer.prototype.slicetoimmutable 1045 */ 1046 bool ArrayBufferObject::sliceToImmutableImpl(JSContext* cx, 1047 const CallArgs& args) { 1048 MOZ_ASSERT(IsArrayBuffer(args.thisv())); 1049 1050 Rooted<ArrayBufferObject*> obj( 1051 cx, &args.thisv().toObject().as<ArrayBufferObject>()); 1052 1053 // Step 4. 1054 if (obj->isDetached()) { 1055 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1056 JSMSG_TYPED_ARRAY_DETACHED); 1057 return false; 1058 } 1059 1060 // Step 5. 1061 size_t len = obj->byteLength(); 1062 1063 // Steps 6 and 7. 1064 size_t first = 0; 1065 if (args.hasDefined(0)) { 1066 if (!ToIntegerIndex(cx, args[0], len, &first)) { 1067 return false; 1068 } 1069 } 1070 1071 // Steps 6 and 8. 1072 size_t final_ = len; 1073 if (args.hasDefined(1)) { 1074 if (!ToIntegerIndex(cx, args[1], len, &final_)) { 1075 return false; 1076 } 1077 } 1078 1079 // Step 9. 1080 size_t newLen = final_ >= first ? final_ - first : 0; 1081 MOZ_ASSERT(newLen <= ArrayBufferObject::ByteLengthLimit); 1082 1083 // Steps 10-11. 1084 if (obj->isDetached()) { 1085 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1086 JSMSG_TYPED_ARRAY_DETACHED); 1087 return false; 1088 } 1089 1090 // Step 13. 1091 // 1092 // Reacquire the length in case the buffer has been resized. 1093 size_t currentLen = obj->byteLength(); 1094 1095 // Step 14. 1096 if (currentLen < final_) { 1097 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1098 JSMSG_ARRAYBUFFER_COPY_RANGE); 1099 return false; 1100 } 1101 1102 // Steps 12 and 15. 1103 auto* newBuffer = ImmutableArrayBufferObject::slice(cx, newLen, obj, first); 1104 if (!newBuffer) { 1105 return false; 1106 } 1107 1108 // Step 16. 1109 args.rval().setObject(*newBuffer); 1110 return true; 1111 } 1112 1113 /** 1114 * ArrayBuffer.prototype.sliceToImmutable ( start, end ) 1115 * 1116 * https://tc39.es/proposal-immutable-arraybuffer/#sec-arraybuffer.prototype.slicetoimmutable 1117 */ 1118 bool ArrayBufferObject::sliceToImmutable(JSContext* cx, unsigned argc, 1119 Value* vp) { 1120 // Steps 1-3. 1121 CallArgs args = CallArgsFromVp(argc, vp); 1122 return CallNonGenericMethod<IsArrayBuffer, sliceToImmutableImpl>(cx, args); 1123 } 1124 #endif 1125 1126 /* 1127 * ArrayBuffer.isView(obj); ES6 (Dec 2013 draft) 24.1.3.1 1128 */ 1129 bool ArrayBufferObject::fun_isView(JSContext* cx, unsigned argc, Value* vp) { 1130 CallArgs args = CallArgsFromVp(argc, vp); 1131 args.rval().setBoolean(args.get(0).isObject() && 1132 JS_IsArrayBufferViewObject(&args.get(0).toObject())); 1133 return true; 1134 } 1135 1136 // ES2024 draft rev 3a773fc9fae58be023228b13dbbd402ac18eeb6b 1137 // 25.1.4.1 ArrayBuffer ( length [ , options ] ) 1138 bool ArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, 1139 Value* vp) { 1140 CallArgs args = CallArgsFromVp(argc, vp); 1141 1142 // Step 1. 1143 if (!ThrowIfNotConstructing(cx, args, "ArrayBuffer")) { 1144 return false; 1145 } 1146 1147 // Step 2. 1148 uint64_t byteLength; 1149 if (!ToIndex(cx, args.get(0), &byteLength)) { 1150 return false; 1151 } 1152 1153 // Step 3. 1154 mozilla::Maybe<uint64_t> maxByteLength; 1155 // Inline call to GetArrayBufferMaxByteLengthOption. 1156 if (args.get(1).isObject()) { 1157 Rooted<JSObject*> options(cx, &args[1].toObject()); 1158 1159 Rooted<Value> val(cx); 1160 if (!GetProperty(cx, options, options, cx->names().maxByteLength, &val)) { 1161 return false; 1162 } 1163 if (!val.isUndefined()) { 1164 uint64_t maxByteLengthInt; 1165 if (!ToIndex(cx, val, &maxByteLengthInt)) { 1166 return false; 1167 } 1168 1169 // 25.1.3.1 AllocateArrayBuffer, step 3.a. 1170 if (byteLength > maxByteLengthInt) { 1171 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1172 JSMSG_ARRAYBUFFER_LENGTH_LARGER_THAN_MAXIMUM); 1173 return false; 1174 } 1175 maxByteLength = mozilla::Some(maxByteLengthInt); 1176 } 1177 } 1178 1179 // Step 4 (Inlined 25.1.3.1 AllocateArrayBuffer). 1180 // 25.1.3.1, step 4 (Inlined 10.1.13 OrdinaryCreateFromConstructor, step 2). 1181 RootedObject proto(cx); 1182 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_ArrayBuffer, 1183 &proto)) { 1184 return false; 1185 } 1186 1187 // 25.1.3.1, step 5 (Inlined 6.2.9.1 CreateByteDataBlock, step 2). 1188 if (!CheckArrayBufferTooLarge(cx, byteLength)) { 1189 return false; 1190 } 1191 1192 if (maxByteLength) { 1193 // 25.1.3.1, step 8.a. 1194 if (!CheckArrayBufferTooLarge(cx, *maxByteLength)) { 1195 return false; 1196 } 1197 1198 // 25.1.3.1, remaining steps. 1199 auto* bufobj = ResizableArrayBufferObject::createZeroed( 1200 cx, byteLength, *maxByteLength, proto); 1201 if (!bufobj) { 1202 return false; 1203 } 1204 args.rval().setObject(*bufobj); 1205 return true; 1206 } 1207 1208 // 25.1.3.1, remaining steps. 1209 JSObject* bufobj = createZeroed(cx, byteLength, proto); 1210 if (!bufobj) { 1211 return false; 1212 } 1213 args.rval().setObject(*bufobj); 1214 return true; 1215 } 1216 1217 using ArrayBufferContents = UniquePtr<uint8_t[], JS::FreePolicy>; 1218 1219 static ArrayBufferContents AllocateUninitializedArrayBufferContents( 1220 JSContext* cx, size_t nbytes) { 1221 // First attempt a normal allocation. 1222 uint8_t* p = 1223 cx->maybe_pod_arena_malloc<uint8_t>(js::ArrayBufferContentsArena, nbytes); 1224 if (MOZ_UNLIKELY(!p)) { 1225 // Otherwise attempt a large allocation, calling the 1226 // large-allocation-failure callback if necessary. 1227 p = static_cast<uint8_t*>(cx->runtime()->onOutOfMemoryCanGC( 1228 js::AllocFunction::Malloc, js::ArrayBufferContentsArena, nbytes)); 1229 if (!p) { 1230 MOZ_DIAGNOSTIC_ASSERT(!cx->brittleMode, 1231 "OOM in AllocateUninitializedArrayBufferContents"); 1232 ReportOutOfMemory(cx); 1233 } 1234 } 1235 1236 return ArrayBufferContents(p); 1237 } 1238 1239 static ArrayBufferContents AllocateArrayBufferContents(JSContext* cx, 1240 size_t nbytes) { 1241 // First attempt a normal allocation. 1242 uint8_t* p = 1243 cx->maybe_pod_arena_calloc<uint8_t>(js::ArrayBufferContentsArena, nbytes); 1244 if (MOZ_UNLIKELY(!p)) { 1245 // Otherwise attempt a large allocation, calling the 1246 // large-allocation-failure callback if necessary. 1247 p = static_cast<uint8_t*>(cx->runtime()->onOutOfMemoryCanGC( 1248 js::AllocFunction::Calloc, js::ArrayBufferContentsArena, nbytes)); 1249 if (!p) { 1250 ReportOutOfMemory(cx); 1251 } 1252 } 1253 1254 return ArrayBufferContents(p); 1255 } 1256 1257 static ArrayBufferContents ReallocateArrayBufferContents(JSContext* cx, 1258 uint8_t* old, 1259 size_t oldSize, 1260 size_t newSize) { 1261 // First attempt a normal reallocation. 1262 uint8_t* p = cx->maybe_pod_arena_realloc<uint8_t>( 1263 js::ArrayBufferContentsArena, old, oldSize, newSize); 1264 if (MOZ_UNLIKELY(!p)) { 1265 // Otherwise attempt a large allocation, calling the 1266 // large-allocation-failure callback if necessary. 1267 p = static_cast<uint8_t*>(cx->runtime()->onOutOfMemoryCanGC( 1268 js::AllocFunction::Realloc, js::ArrayBufferContentsArena, newSize, 1269 old)); 1270 if (!p) { 1271 ReportOutOfMemory(cx); 1272 } 1273 } 1274 1275 return ArrayBufferContents(p); 1276 } 1277 1278 static ArrayBufferContents NewCopiedBufferContents( 1279 JSContext* cx, Handle<ArrayBufferObject*> buffer) { 1280 ArrayBufferContents dataCopy = 1281 AllocateUninitializedArrayBufferContents(cx, buffer->byteLength()); 1282 if (dataCopy) { 1283 if (auto count = buffer->byteLength()) { 1284 memcpy(dataCopy.get(), buffer->dataPointer(), count); 1285 } 1286 } 1287 return dataCopy; 1288 } 1289 1290 /* static */ 1291 void ArrayBufferObject::detach(JSContext* cx, 1292 Handle<ArrayBufferObject*> buffer) { 1293 cx->check(buffer); 1294 MOZ_ASSERT(!buffer->isPreparedForAsmJS()); 1295 MOZ_ASSERT(!buffer->isLengthPinned()); 1296 MOZ_ASSERT(!buffer->isImmutable()); 1297 1298 // Update all views of the buffer to account for the buffer having been 1299 // detached, and clear the buffer's data and list of views. 1300 1301 auto& innerViews = ObjectRealm::get(buffer).innerViews.get(); 1302 if (InnerViewTable::ViewVector* views = 1303 innerViews.maybeViewsUnbarriered(buffer)) { 1304 AutoTouchingGrayThings tgt; 1305 for (size_t i = 0; i < views->length(); i++) { 1306 JSObject* view = (*views)[i]; 1307 view->as<ArrayBufferViewObject>().notifyBufferDetached(); 1308 } 1309 innerViews.removeViews(buffer); 1310 } 1311 if (JSObject* view = buffer->firstView()) { 1312 view->as<ArrayBufferViewObject>().notifyBufferDetached(); 1313 buffer->setFirstView(nullptr); 1314 } 1315 1316 if (buffer->dataPointer()) { 1317 buffer->releaseData(cx->gcContext()); 1318 buffer->setDataPointer(BufferContents::createNoData()); 1319 } 1320 1321 buffer->setByteLength(0); 1322 buffer->setIsDetached(); 1323 if (buffer->isResizable()) { 1324 buffer->as<ResizableArrayBufferObject>().setMaxByteLength(0); 1325 } 1326 } 1327 1328 void ResizableArrayBufferObject::resize(size_t newByteLength) { 1329 MOZ_ASSERT(!isPreparedForAsmJS()); 1330 MOZ_ASSERT(!isWasm()); 1331 MOZ_ASSERT(!isDetached()); 1332 MOZ_ASSERT(!isImmutable()); 1333 MOZ_ASSERT(!isLengthPinned()); 1334 MOZ_ASSERT(isResizable()); 1335 MOZ_ASSERT(newByteLength <= maxByteLength()); 1336 1337 // Clear the bytes between `data[newByteLength..oldByteLength]` when 1338 // shrinking the buffer. We don't need to clear any bytes when growing the 1339 // buffer, because the new space was either initialized to zero when creating 1340 // the buffer, or a prior shrink zeroed it out here. 1341 size_t oldByteLength = byteLength(); 1342 if (newByteLength < oldByteLength) { 1343 size_t nbytes = oldByteLength - newByteLength; 1344 memset(dataPointer() + newByteLength, 0, nbytes); 1345 } 1346 1347 setByteLength(newByteLength); 1348 1349 // Update all views of the buffer to account for the buffer having been 1350 // resized. 1351 1352 auto& innerViews = ObjectRealm::get(this).innerViews.get(); 1353 if (InnerViewTable::ViewVector* views = 1354 innerViews.maybeViewsUnbarriered(this)) { 1355 AutoTouchingGrayThings tgt; 1356 for (auto& view : *views) { 1357 view->notifyBufferResized(); 1358 } 1359 } 1360 if (auto* view = firstView()) { 1361 view->as<ArrayBufferViewObject>().notifyBufferResized(); 1362 } 1363 } 1364 1365 /* 1366 * [SMDOC] WASM Linear Memory structure 1367 * 1368 * The wasm linear memory is, on its face, a simple buffer of memory. However, 1369 * we perform several optimizations with the memory allocation to avoid bounds 1370 * checks and to avoid moving grows. Many different types/objects are involved 1371 * in our memory allocations: 1372 * 1373 * - wasm::Instance - stores information about each memory in a 1374 * MemoryInstanceData, which itself holds a reference to a WasmMemoryObject. 1375 * Memory 0 gets special treatment to avoid loading the MemoryInstanceData at 1376 * runtime. 1377 * 1378 * - WasmMemoryObject - stores a reference to a (Shared)ArrayBufferObject for 1379 * the backing storage. 1380 * 1381 * - ArrayBufferObject - owns the actual buffer of memory for asm.js memories 1382 * and non-shared wasm memories. For wasm memories (but NOT asm.js memories), 1383 * additional wasm metadata is stored in a WasmArrayRawBuffer next to the 1384 * data itself. 1385 * 1386 * - SharedArrayBufferObject - owns the actual buffer of memory for shared wasm 1387 * memories, in the form of a WasmSharedArrayRawBuffer. 1388 * 1389 * - WasmArrayRawBuffer - metadata for a non-shared wasm memory allocation. See 1390 * below for details. 1391 * 1392 * - WasmSharedArrayRawBuffer - a nearly equivalent class to WasmArrayRawBuffer 1393 * used for shared wasm memories only. The same terminology from 1394 * WasmArrayRawBuffer applies, but the memory allocation strategy is 1395 * different. 1396 * 1397 * 1398 * ## Wasm memory terminology 1399 * 1400 * A wasm/asm.js linear memory is an mmap'd array buffer. In the general case, 1401 * accesses to memory must be bounds checked, but bounds checks can be 1402 * simplified, omitted, or deferred to signal handling based on the properties 1403 * of the memory (such as a known maximum size). Some common terminology applies 1404 * to all asm.js and wasm memories, and is generally handled by 1405 * WasmMemoryObject. The following terms are all expressed in bytes for clarity, 1406 * but in practice they may be stored as a page count instead: 1407 * 1408 * - byteLength - the actual current length of the buffer. Accesses in 1409 * the range [0, byteLength) must succeed; accesses >= byteLength must fail. 1410 * May only increase, if the memory grows. 1411 * 1412 * - boundsCheckLimit - the size against which we perform bounds checks. This 1413 * is sometimes equal to byteLength, but may be greater. Regardless, it is 1414 * always safe to use in bounds checks for reasons described in "Linear 1415 * memory addresses and bounds checking" in wasm/WasmMemory.cpp. 1416 * 1417 * - sourceMaxSize - the optional declared limit on how far byteLength can 1418 * grow. This is the unmodified maximum size from the source module or JS-API 1419 * invocation. This may not be representable in pointer size, nor feasible 1420 * for a module to actually grow to due to implementation limits. It is used 1421 * for correct linking checks and js-types reflection. 1422 * 1423 * - clampedMaxSize - the maximum size on how far the byteLength can grow. This 1424 * value respects implementation limits. Every memory has a clampedMaxSize, 1425 * even if no maximum was specified in source. This value may be greater 1426 * than or less than mappedSize. 1427 * 1428 * - mappedSize - the actual mmap'd size. Access in the range [0, mappedSize) 1429 * will either succeed, or be handled by the wasm signal handlers. The amount 1430 * that we map can vary according to multiple factors - see "Allocation 1431 * strategies" below. (This property does not apply to asm.js.) 1432 * 1433 * The below diagram shows the layout of the wasm heap. The wasm-visible portion 1434 * of the heap starts at 0. There is one extra page prior to the start of the 1435 * wasm heap which contains the WasmArrayRawBuffer struct at its end (i.e. right 1436 * before the start of the WASM memory). The mappedSize does NOT include this 1437 * page. 1438 * 1439 * 1440 * Wasm(Shared)ArrayRawBuffer 1441 * │ dataPointer() (from (Shared)ArrayBufferObject) 1442 * │ │ 1443 * ┌──────│─│──────────────────────────────────────────────────────┐ 1444 * └──────│─│──────────────│─────────────────────────────────│─────│ 1445 * 0 byteLength boundsCheckLimit mappedSize 1446 * 1447 * └───────────────────────┘ 1448 * COMMITTED 1449 * └─────────────────────────────────┴─────┘ 1450 * SLOP 1451 * └───────────────────────────────────────────────────────────────┘ 1452 * MAPPED 1453 * 1454 * 1455 * Invariants on byteLength and mappedSize: 1456 * - byteLength only increases. 1457 * - 0 <= byteLength <= mappedSize. 1458 * - If sourceMaxSize is not specified, mappedSize may grow. 1459 * Otherwise, mappedSize is constant. 1460 * 1461 * Invariants on sourceMaxSize and clampedMaxSize: 1462 * - initialLength <= clampedMaxSize <= sourceMaxSize (if present). 1463 * - clampedMaxSize <= wasm::MaxMemoryPages(). 1464 * 1465 * Invariants on boundsCheckLimit: 1466 * - For asm.js code: boundsCheckLimit == byteLength. 1467 * Signal handlers will not be invoked. 1468 * - For wasm code without the huge memory trick: 1469 * byteLength <= boundsCheckLimit < mappedSize 1470 * - For wasm code with the huge memory trick: 1471 * boundsCheckLimit is irrelevant as bounds checks are unnecessary. 1472 * - If sourceMaxSize is present, boundsCheckLimit is constant. 1473 * Otherwise, it may increase with mappedSize. 1474 * - On ARM, boundsCheckLimit must be a valid ARM immediate. 1475 * 1476 * The region between byteLength and mappedSize is the SLOP - an area where we 1477 * use signal handlers to catch invalid accesses, including those that slip by 1478 * bounds checks. Logically it has two parts: 1479 * 1480 * - From byteLength to boundsCheckLimit - as described above, we sometimes set 1481 * boundsCheckLimit higher than byteLength, and this part of the SLOP catches 1482 * accesses to memory that therefore pass a bounds check but should 1483 * nonetheless trap. 1484 * 1485 * - From boundsCheckLimit to mappedSize - this part of the SLOP is a guard 1486 * region that allows us to ignore most offsets when performing bounds 1487 * checks. 1488 * 1489 * For more information about both of these cases, see "Linear memory addresses 1490 * and bounds checking" in wasm/WasmMemory.cpp. 1491 * 1492 * 1493 * ## Allocation strategies 1494 * 1495 * Our memory allocation strategy varies according to several factors. 1496 * 1497 * - Huge memories - if allocating a 32-bit memory on a platform with 1498 * sufficient address space, we simply map 4GiB of memory plus ample guard 1499 * space. This eliminates the need for bounds checks entirely except in cases 1500 * of very large offsets. This applies whether or not the memory has a 1501 * declared maximum. See "Linear memory addresses and bounds checking" in 1502 * wasm/WasmMemory.cpp. 1503 * 1504 * - Memories with no maximum - rather than over-allocate, we conservatively 1505 * map only the requested initial pages plus the required guard region. 1506 * 1507 * - Memories with a maximum - we do our best to reserve the entire maximum 1508 * space up front so that grows will not fail later. If we fail to map the 1509 * entire maximum, we iteratively map less and less memory until allocation 1510 * succeeds. 1511 * 1512 * 1513 * ## Grow strategies 1514 * 1515 * We have two strategies for growing a wasm memory. 1516 * 1517 * - Growing in place - if mappedSize is large enough to contain the new size, 1518 * we can simply commit the previously-mapped pages. This is always the case 1519 * for huge memories, and for memories with a declared max (and by extension 1520 * shared memories). 1521 * 1522 * - Moving grow - if we have not previously mapped enough space, we must 1523 * allocate an entirely new buffer and copy the contents of the old buffer. 1524 * This causes the memory to move and therefore requires us to update 1525 * information on the Instance. It also increases memory pressure because 1526 * both the old and new memories must be resident at the same time, and 1527 * afterwards the entirety of the old buffer's contents will be resident in 1528 * memory (even if some pages had never been touched before the move). 1529 * 1530 * A moving grow can fall back to a grow in place if the mappedSize allows 1531 * for it. However, at the time of writing, our memory allocation/grow 1532 * strategies do not allow this to happen. 1533 * 1534 */ 1535 1536 [[nodiscard]] bool WasmArrayRawBuffer::growToPagesInPlace(Pages newPages) { 1537 size_t newSize = newPages.byteLength(); 1538 size_t oldSize = byteLength(); 1539 1540 MOZ_ASSERT(newSize >= oldSize); 1541 MOZ_ASSERT(newPages <= clampedMaxPages()); 1542 MOZ_ASSERT(newSize <= mappedSize()); 1543 1544 size_t delta = newSize - oldSize; 1545 MOZ_ASSERT(delta % wasm::StandardPageSizeBytes == 0); 1546 1547 uint8_t* dataEnd = dataPointer() + oldSize; 1548 MOZ_ASSERT(uintptr_t(dataEnd) % gc::SystemPageSize() == 0); 1549 1550 if (delta && !CommitBufferMemory(dataEnd, delta)) { 1551 return false; 1552 } 1553 1554 length_ = newSize; 1555 1556 return true; 1557 } 1558 1559 void WasmArrayRawBuffer::discard(size_t byteOffset, size_t byteLen) { 1560 uint8_t* memBase = dataPointer(); 1561 1562 // The caller is responsible for ensuring these conditions are met; see this 1563 // function's comment in ArrayBufferObject.h. 1564 MOZ_ASSERT(byteOffset % wasm::StandardPageSizeBytes == 0); 1565 MOZ_ASSERT(byteLen % wasm::StandardPageSizeBytes == 0); 1566 MOZ_ASSERT(wasm::MemoryBoundsCheck(uint64_t(byteOffset), uint64_t(byteLen), 1567 byteLength())); 1568 1569 // Discarding zero bytes "succeeds" with no effect. 1570 if (byteLen == 0) { 1571 return; 1572 } 1573 1574 void* addr = memBase + uintptr_t(byteOffset); 1575 1576 // On POSIX-ish platforms, we discard memory by overwriting previously-mapped 1577 // pages with freshly-mapped pages (which are all zeroed). The operating 1578 // system recognizes this and decreases the process RSS, and eventually 1579 // collects the abandoned physical pages. 1580 // 1581 // On Windows, committing over previously-committed pages has no effect, and 1582 // the memory must be explicitly decommitted first. This is not the same as an 1583 // munmap; the address space is still reserved. 1584 1585 #ifdef XP_WIN 1586 if (!VirtualFree(addr, byteLen, MEM_DECOMMIT)) { 1587 MOZ_CRASH("wasm discard: failed to decommit memory"); 1588 } 1589 if (!VirtualAlloc(addr, byteLen, MEM_COMMIT, PAGE_READWRITE)) { 1590 MOZ_CRASH("wasm discard: decommitted memory but failed to recommit"); 1591 }; 1592 #elif defined(__wasi__) 1593 memset(addr, 0, byteLen); 1594 #else // !XP_WIN 1595 void* data = MozTaggedAnonymousMmap(addr, byteLen, PROT_READ | PROT_WRITE, 1596 MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0, 1597 "wasm-reserved"); 1598 if (data == MAP_FAILED) { 1599 MOZ_CRASH("failed to discard wasm memory; memory mappings may be broken"); 1600 } 1601 #endif 1602 } 1603 1604 /* static */ 1605 WasmArrayRawBuffer* WasmArrayRawBuffer::AllocateWasm( 1606 AddressType addressType, wasm::PageSize pageSize, Pages initialPages, 1607 Pages clampedMaxPages, const Maybe<Pages>& sourceMaxPages, 1608 const Maybe<size_t>& mapped) { 1609 // Prior code has asserted that initial pages is within our implementation 1610 // limits (wasm::MaxMemoryPages) and we can assume it is a valid size_t. 1611 MOZ_RELEASE_ASSERT(initialPages.pageSize() == pageSize); 1612 MOZ_RELEASE_ASSERT(clampedMaxPages.pageSize() == pageSize); 1613 MOZ_RELEASE_ASSERT(!sourceMaxPages.isSome() || 1614 (pageSize == sourceMaxPages->pageSize())); 1615 MOZ_ASSERT(initialPages.hasByteLength()); 1616 size_t numBytes = initialPages.byteLength(); 1617 1618 // If there is a specified maximum, attempt to map the whole range for 1619 // clampedMaxPages. Or else map only what's required for initialPages. 1620 Pages initialMappedPages = 1621 sourceMaxPages.isSome() ? clampedMaxPages : initialPages; 1622 1623 // Use an override mapped size, or else compute the mapped size from 1624 // initialMappedPages. 1625 #ifdef ENABLE_WASM_CUSTOM_PAGE_SIZES 1626 MOZ_ASSERT_IF(pageSize == wasm::PageSize::Tiny, !mapped.isSome()); 1627 #endif 1628 size_t mappedSize = 1629 mapped.isSome() ? *mapped : wasm::ComputeMappedSize(initialMappedPages); 1630 1631 MOZ_RELEASE_ASSERT(mappedSize <= SIZE_MAX - gc::SystemPageSize()); 1632 MOZ_RELEASE_ASSERT(numBytes <= SIZE_MAX - gc::SystemPageSize()); 1633 MOZ_RELEASE_ASSERT(initialPages <= clampedMaxPages); 1634 // With custom page sizes, the wasm-visible byte length may not fall along 1635 // system page boundaries. 1636 MOZ_ASSERT_IF(pageSize == wasm::PageSize::Standard, 1637 numBytes % gc::SystemPageSize() == 0); 1638 MOZ_ASSERT(mappedSize % gc::SystemPageSize() == 0); 1639 1640 uint64_t mappedSizeWithHeader = mappedSize + gc::SystemPageSize(); 1641 #ifndef ENABLE_WASM_CUSTOM_PAGE_SIZES 1642 uint64_t numBytesWithHeader = numBytes + gc::SystemPageSize(); 1643 #else 1644 // For tiny page size, the mapped size and the committed size are the 1645 // same since we do not have a slop area or guard page. 1646 uint64_t numBytesWithHeader = pageSize == wasm::PageSize::Tiny 1647 ? mappedSizeWithHeader 1648 : (numBytes + gc::SystemPageSize()); 1649 #endif 1650 1651 MOZ_ASSERT(numBytesWithHeader % gc::SystemPageSize() == 0); 1652 1653 void* data = 1654 MapBufferMemory(addressType, pageSize, (size_t)mappedSizeWithHeader, 1655 (size_t)numBytesWithHeader); 1656 if (!data) { 1657 return nullptr; 1658 } 1659 1660 uint8_t* base = reinterpret_cast<uint8_t*>(data) + gc::SystemPageSize(); 1661 uint8_t* header = base - sizeof(WasmArrayRawBuffer); 1662 1663 auto rawBuf = new (header) 1664 WasmArrayRawBuffer(addressType, pageSize, base, clampedMaxPages, 1665 sourceMaxPages, mappedSize, numBytes); 1666 return rawBuf; 1667 } 1668 1669 /* static */ 1670 void WasmArrayRawBuffer::Release(void* mem) { 1671 WasmArrayRawBuffer* header = 1672 (WasmArrayRawBuffer*)((uint8_t*)mem - sizeof(WasmArrayRawBuffer)); 1673 1674 MOZ_RELEASE_ASSERT(header->mappedSize() <= SIZE_MAX - gc::SystemPageSize()); 1675 size_t mappedSizeWithHeader = header->mappedSize() + gc::SystemPageSize(); 1676 #ifndef ENABLE_WASM_CUSTOM_PAGE_SIZES 1677 size_t committedSize = header->byteLength() + gc::SystemPageSize(); 1678 #else 1679 // See above for numBytesWithHeader in AllocateWasm. 1680 size_t committedSize = header->pageSize() == wasm::PageSize::Tiny 1681 ? mappedSizeWithHeader 1682 : (header->byteLength() + gc::SystemPageSize()); 1683 #endif 1684 MOZ_ASSERT(committedSize % gc::SystemPageSize() == 0); 1685 1686 static_assert(std::is_trivially_destructible_v<WasmArrayRawBuffer>, 1687 "no need to call the destructor"); 1688 1689 UnmapBufferMemory(header->addressType(), header->basePointer(), 1690 mappedSizeWithHeader, committedSize); 1691 } 1692 1693 WasmArrayRawBuffer* ArrayBufferObject::BufferContents::wasmBuffer() const { 1694 MOZ_RELEASE_ASSERT(kind_ == WASM); 1695 return (WasmArrayRawBuffer*)(data_ - sizeof(WasmArrayRawBuffer)); 1696 } 1697 1698 template <typename ObjT, typename RawbufT> 1699 static ArrayBufferObjectMaybeShared* CreateSpecificWasmBuffer( 1700 JSContext* cx, const wasm::MemoryDesc& memory) { 1701 bool useHugeMemory = 1702 wasm::IsHugeMemoryEnabled(memory.addressType(), memory.pageSize()); 1703 wasm::PageSize pageSize = memory.pageSize(); 1704 Pages initialPages = memory.initialPages(); 1705 Maybe<Pages> sourceMaxPages = memory.maximumPages(); 1706 Pages clampedMaxPages = wasm::ClampedMaxPages( 1707 memory.addressType(), initialPages, sourceMaxPages, useHugeMemory); 1708 1709 Maybe<size_t> mappedSize; 1710 #ifdef WASM_SUPPORTS_HUGE_MEMORY 1711 // Override the mapped size if we are using huge memory. If we are not, then 1712 // it will be calculated by the raw buffer we are using. 1713 if (useHugeMemory) { 1714 mappedSize = Some(wasm::HugeMappedSize); 1715 } 1716 #endif 1717 1718 RawbufT* buffer = RawbufT::AllocateWasm( 1719 memory.limits.addressType, memory.pageSize(), initialPages, 1720 clampedMaxPages, sourceMaxPages, mappedSize); 1721 if (!buffer) { 1722 if (useHugeMemory) { 1723 WarnNumberASCII(cx, JSMSG_WASM_HUGE_MEMORY_FAILED); 1724 if (cx->isExceptionPending()) { 1725 cx->clearPendingException(); 1726 } 1727 1728 ReportOutOfMemory(cx); 1729 return nullptr; 1730 } 1731 1732 // If we fail, and have a sourceMaxPages, try to reserve the biggest 1733 // chunk in the range [initialPages, clampedMaxPages) using log backoff. 1734 if (!sourceMaxPages) { 1735 wasm::Log(cx, 1736 "new Memory({initial=%" PRIu64 1737 " pages, " 1738 "pageSize=%" PRIu32 " bytes}) failed", 1739 initialPages.pageCount(), 1740 wasm::PageSizeInBytes(initialPages.pageSize())); 1741 ReportOutOfMemory(cx); 1742 return nullptr; 1743 } 1744 1745 uint64_t cur = clampedMaxPages.pageCount() / 2; 1746 for (; cur > initialPages.pageCount(); cur /= 2) { 1747 buffer = RawbufT::AllocateWasm( 1748 memory.limits.addressType, pageSize, initialPages, 1749 Pages::fromPageCount(cur, pageSize), sourceMaxPages, mappedSize); 1750 if (buffer) { 1751 break; 1752 } 1753 } 1754 1755 if (!buffer) { 1756 wasm::Log(cx, 1757 "new Memory({initial=%" PRIu64 1758 " pages, " 1759 "pageSize=%" PRIu32 " bytes}) failed", 1760 initialPages.pageCount(), 1761 wasm::PageSizeInBytes(initialPages.pageSize())); 1762 ReportOutOfMemory(cx); 1763 return nullptr; 1764 } 1765 } 1766 1767 // ObjT::createFromNewRawBuffer assumes ownership of |buffer| even in case 1768 // of failure. 1769 Rooted<ArrayBufferObjectMaybeShared*> object( 1770 cx, ObjT::createFromNewRawBuffer(cx, buffer, initialPages.byteLength())); 1771 if (!object) { 1772 return nullptr; 1773 } 1774 1775 // See MaximumLiveMappedBuffers comment above. 1776 if (wasmReservedBytes > WasmReservedBytesStartSyncFullGC) { 1777 JS::PrepareForFullGC(cx); 1778 JS::NonIncrementalGC(cx, JS::GCOptions::Normal, 1779 JS::GCReason::TOO_MUCH_WASM_MEMORY); 1780 wasmReservedBytesSinceLast = 0; 1781 } else if (wasmReservedBytes > WasmReservedBytesStartTriggering) { 1782 wasmReservedBytesSinceLast += uint64_t(buffer->mappedSize()); 1783 if (wasmReservedBytesSinceLast > WasmReservedBytesPerTrigger) { 1784 (void)cx->runtime()->gc.triggerGC(JS::GCReason::TOO_MUCH_WASM_MEMORY); 1785 wasmReservedBytesSinceLast = 0; 1786 } 1787 } else { 1788 wasmReservedBytesSinceLast = 0; 1789 } 1790 1791 // Log the result with details on the memory allocation 1792 if (sourceMaxPages) { 1793 if (useHugeMemory) { 1794 wasm::Log(cx, 1795 "new Memory({initial:%" PRIu64 " pages, maximum:%" PRIu64 1796 " pages, pageSize:%" PRIu32 " bytes}) succeeded", 1797 initialPages.pageCount(), sourceMaxPages->pageCount(), 1798 wasm::PageSizeInBytes(initialPages.pageSize())); 1799 } else { 1800 wasm::Log(cx, 1801 "new Memory({initial:%" PRIu64 " pages, maximum:%" PRIu64 1802 " pages, pageSize:%" PRIu32 1803 " bytes}) succeeded " 1804 "with internal maximum of %" PRIu64 " pages", 1805 initialPages.pageCount(), sourceMaxPages->pageCount(), 1806 wasm::PageSizeInBytes(initialPages.pageSize()), 1807 object->wasmClampedMaxPages().pageCount()); 1808 } 1809 } else { 1810 wasm::Log(cx, 1811 "new Memory({initial:%" PRIu64 " pages, pageSize:%" PRIu32 1812 " bytes}) succeeded", 1813 initialPages.pageCount(), 1814 wasm::PageSizeInBytes(initialPages.pageSize())); 1815 } 1816 1817 return object; 1818 } 1819 1820 ArrayBufferObjectMaybeShared* js::CreateWasmBuffer( 1821 JSContext* cx, const wasm::MemoryDesc& memory) { 1822 MOZ_RELEASE_ASSERT( 1823 memory.initialPages() <= 1824 wasm::MaxMemoryPages(memory.addressType(), memory.pageSize())); 1825 MOZ_RELEASE_ASSERT(cx->wasm().haveSignalHandlers); 1826 #ifndef ENABLE_WASM_CUSTOM_PAGE_SIZES 1827 MOZ_ASSERT(memory.pageSize() == wasm::PageSize::Standard); 1828 #endif 1829 1830 if (memory.isShared()) { 1831 if (!cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled()) { 1832 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1833 JSMSG_WASM_NO_SHMEM_LINK); 1834 return nullptr; 1835 } 1836 return CreateSpecificWasmBuffer<SharedArrayBufferObject, 1837 WasmSharedArrayRawBuffer>(cx, memory); 1838 } 1839 return CreateSpecificWasmBuffer<ArrayBufferObject, WasmArrayRawBuffer>( 1840 cx, memory); 1841 } 1842 1843 bool ArrayBufferObject::prepareForAsmJS() { 1844 MOZ_ASSERT(byteLength() % wasm::StandardPageSizeBytes == 0, 1845 "prior size checking should have guaranteed page-size multiple"); 1846 MOZ_ASSERT(byteLength() > 0, 1847 "prior size checking should have excluded empty buffers"); 1848 MOZ_ASSERT(!isResizable(), 1849 "prior checks should have excluded resizable buffers"); 1850 MOZ_ASSERT(!isImmutable(), 1851 "prior checks should have excluded immutable buffers"); 1852 1853 switch (bufferKind()) { 1854 case MALLOCED_ARRAYBUFFER_CONTENTS_ARENA: 1855 case MALLOCED_UNKNOWN_ARENA: 1856 case MAPPED: 1857 case EXTERNAL: 1858 // It's okay if this uselessly sets the flag a second time. 1859 setIsPreparedForAsmJS(); 1860 return true; 1861 1862 case INLINE_DATA: 1863 static_assert(wasm::StandardPageSizeBytes > 1864 FixedLengthArrayBufferObject::MaxInlineBytes, 1865 "inline data must be too small to be a page size multiple"); 1866 MOZ_ASSERT_UNREACHABLE( 1867 "inline-data buffers should be implicitly excluded by size checks"); 1868 return false; 1869 1870 case NO_DATA: 1871 MOZ_ASSERT_UNREACHABLE( 1872 "size checking should have excluded detached or empty buffers"); 1873 return false; 1874 1875 // asm.js code and associated buffers are potentially long-lived. Yet a 1876 // buffer of user-owned data *must* be detached by the user before the 1877 // user-owned data is disposed. No caller wants to use a user-owned 1878 // ArrayBuffer with asm.js, so just don't support this and avoid a mess of 1879 // complexity. 1880 case USER_OWNED: 1881 // wasm buffers can be detached at any time. 1882 case WASM: 1883 MOZ_ASSERT(!isPreparedForAsmJS()); 1884 return false; 1885 } 1886 1887 MOZ_ASSERT_UNREACHABLE("non-exhaustive kind-handling switch?"); 1888 return false; 1889 } 1890 1891 ArrayBufferObject::BufferContents ArrayBufferObject::createMappedContents( 1892 int fd, size_t offset, size_t length) { 1893 void* data = 1894 gc::AllocateMappedContent(fd, offset, length, ARRAY_BUFFER_ALIGNMENT); 1895 return BufferContents::createMapped(data); 1896 } 1897 1898 uint8_t* FixedLengthArrayBufferObject::inlineDataPointer() const { 1899 return static_cast<uint8_t*>(fixedData(JSCLASS_RESERVED_SLOTS(&class_))); 1900 } 1901 1902 uint8_t* ResizableArrayBufferObject::inlineDataPointer() const { 1903 return static_cast<uint8_t*>(fixedData(JSCLASS_RESERVED_SLOTS(&class_))); 1904 } 1905 1906 uint8_t* ImmutableArrayBufferObject::inlineDataPointer() const { 1907 return static_cast<uint8_t*>(fixedData(JSCLASS_RESERVED_SLOTS(&class_))); 1908 } 1909 1910 uint8_t* ArrayBufferObject::dataPointer() const { 1911 return static_cast<uint8_t*>(getFixedSlot(DATA_SLOT).toPrivate()); 1912 } 1913 1914 SharedMem<uint8_t*> ArrayBufferObject::dataPointerShared() const { 1915 return SharedMem<uint8_t*>::unshared(getFixedSlot(DATA_SLOT).toPrivate()); 1916 } 1917 1918 ArrayBufferObject::FreeInfo* ArrayBufferObject::freeInfo() const { 1919 MOZ_ASSERT(isExternal()); 1920 MOZ_ASSERT(!isResizable()); 1921 MOZ_ASSERT(!isImmutable()); 1922 auto* data = as<FixedLengthArrayBufferObject>().inlineDataPointer(); 1923 return reinterpret_cast<FreeInfo*>(data); 1924 } 1925 1926 void ArrayBufferObject::releaseData(JS::GCContext* gcx) { 1927 switch (bufferKind()) { 1928 case INLINE_DATA: 1929 // Inline data doesn't require releasing. 1930 break; 1931 case MALLOCED_ARRAYBUFFER_CONTENTS_ARENA: 1932 case MALLOCED_UNKNOWN_ARENA: 1933 gcx->free_(this, dataPointer(), associatedBytes(), 1934 MemoryUse::ArrayBufferContents); 1935 break; 1936 case NO_DATA: 1937 // There's nothing to release if there's no data. 1938 MOZ_ASSERT(dataPointer() == nullptr); 1939 break; 1940 case USER_OWNED: 1941 // User-owned data is released by, well, the user. 1942 break; 1943 case MAPPED: 1944 gc::DeallocateMappedContent(dataPointer(), byteLength()); 1945 gcx->removeCellMemory(this, associatedBytes(), 1946 MemoryUse::ArrayBufferContents); 1947 break; 1948 case WASM: 1949 WasmArrayRawBuffer::Release(dataPointer()); 1950 gcx->removeCellMemory(this, byteLength(), MemoryUse::ArrayBufferContents); 1951 break; 1952 case EXTERNAL: 1953 MOZ_ASSERT(freeInfo()->freeFunc); 1954 { 1955 // The analyzer can't know for sure whether the embedder-supplied 1956 // free function will GC. We give the analyzer a hint here. 1957 // (Doing a GC in the free function is considered a programmer 1958 // error.) 1959 JS::AutoSuppressGCAnalysis nogc; 1960 freeInfo()->freeFunc(dataPointer(), freeInfo()->freeUserData); 1961 } 1962 break; 1963 } 1964 } 1965 1966 void ArrayBufferObject::setDataPointer(BufferContents contents) { 1967 setFixedSlot(DATA_SLOT, PrivateValue(contents.data())); 1968 setFlags((flags() & ~KIND_MASK) | contents.kind()); 1969 1970 if (isExternal()) { 1971 auto info = freeInfo(); 1972 info->freeFunc = contents.freeFunc(); 1973 info->freeUserData = contents.freeUserData(); 1974 } 1975 } 1976 1977 size_t ArrayBufferObject::byteLength() const { 1978 return size_t(getFixedSlot(BYTE_LENGTH_SLOT).toPrivate()); 1979 } 1980 1981 inline size_t ArrayBufferObject::associatedBytes() const { 1982 if (isMalloced()) { 1983 return maxByteLength(); 1984 } 1985 if (isMapped()) { 1986 return RoundUp(byteLength(), js::gc::SystemPageSize()); 1987 } 1988 MOZ_CRASH("Unexpected buffer kind"); 1989 } 1990 1991 void ArrayBufferObject::setByteLength(size_t length) { 1992 MOZ_ASSERT(length <= ArrayBufferObject::ByteLengthLimit); 1993 setFixedSlot(BYTE_LENGTH_SLOT, PrivateValue(length)); 1994 } 1995 1996 size_t ArrayBufferObject::wasmMappedSize() const { 1997 if (isWasm()) { 1998 return contents().wasmBuffer()->mappedSize(); 1999 } 2000 return byteLength(); 2001 } 2002 2003 AddressType ArrayBufferObject::wasmAddressType() const { 2004 if (isWasm()) { 2005 return contents().wasmBuffer()->addressType(); 2006 } 2007 MOZ_ASSERT(isPreparedForAsmJS()); 2008 return wasm::AddressType::I32; 2009 } 2010 2011 wasm::PageSize ArrayBufferObject::wasmPageSize() const { 2012 if (isWasm()) { 2013 return contents().wasmBuffer()->pageSize(); 2014 } 2015 MOZ_ASSERT(isPreparedForAsmJS()); 2016 return wasm::PageSize::Standard; 2017 } 2018 2019 Pages ArrayBufferObject::wasmPages() const { 2020 if (isWasm()) { 2021 return contents().wasmBuffer()->pages(); 2022 } 2023 MOZ_ASSERT(isPreparedForAsmJS()); 2024 return Pages::fromByteLengthExact(byteLength(), wasmPageSize()); 2025 } 2026 2027 Pages ArrayBufferObject::wasmClampedMaxPages() const { 2028 if (isWasm()) { 2029 return contents().wasmBuffer()->clampedMaxPages(); 2030 } 2031 MOZ_ASSERT(isPreparedForAsmJS()); 2032 return Pages::fromByteLengthExact(byteLength(), wasmPageSize()); 2033 } 2034 2035 Maybe<Pages> ArrayBufferObject::wasmSourceMaxPages() const { 2036 if (isWasm()) { 2037 return contents().wasmBuffer()->sourceMaxPages(); 2038 } 2039 MOZ_ASSERT(isPreparedForAsmJS()); 2040 return Some<Pages>(Pages::fromByteLengthExact(byteLength(), wasmPageSize())); 2041 } 2042 2043 size_t js::WasmArrayBufferMappedSize(const ArrayBufferObjectMaybeShared* buf) { 2044 if (buf->is<ArrayBufferObject>()) { 2045 return buf->as<ArrayBufferObject>().wasmMappedSize(); 2046 } 2047 return buf->as<SharedArrayBufferObject>().wasmMappedSize(); 2048 } 2049 2050 AddressType js::WasmArrayBufferAddressType( 2051 const ArrayBufferObjectMaybeShared* buf) { 2052 if (buf->is<ArrayBufferObject>()) { 2053 return buf->as<ArrayBufferObject>().wasmAddressType(); 2054 } 2055 return buf->as<SharedArrayBufferObject>().wasmAddressType(); 2056 } 2057 wasm::PageSize js::WasmArrayBufferPageSize( 2058 const ArrayBufferObjectMaybeShared* buf) { 2059 if (buf->is<ArrayBufferObject>()) { 2060 return buf->as<ArrayBufferObject>().wasmPageSize(); 2061 } 2062 return buf->as<SharedArrayBufferObject>().wasmPageSize(); 2063 } 2064 Pages js::WasmArrayBufferPages(const ArrayBufferObjectMaybeShared* buf) { 2065 if (buf->is<ArrayBufferObject>()) { 2066 return buf->as<ArrayBufferObject>().wasmPages(); 2067 } 2068 return buf->as<SharedArrayBufferObject>().volatileWasmPages(); 2069 } 2070 Pages js::WasmArrayBufferClampedMaxPages( 2071 const ArrayBufferObjectMaybeShared* buf) { 2072 if (buf->is<ArrayBufferObject>()) { 2073 return buf->as<ArrayBufferObject>().wasmClampedMaxPages(); 2074 } 2075 return buf->as<SharedArrayBufferObject>().wasmClampedMaxPages(); 2076 } 2077 Maybe<Pages> js::WasmArrayBufferSourceMaxPages( 2078 const ArrayBufferObjectMaybeShared* buf) { 2079 if (buf->is<ArrayBufferObject>()) { 2080 return buf->as<ArrayBufferObject>().wasmSourceMaxPages(); 2081 } 2082 return Some(buf->as<SharedArrayBufferObject>().wasmSourceMaxPages()); 2083 } 2084 2085 static void CheckStealPreconditions(Handle<ArrayBufferObject*> buffer, 2086 JSContext* cx) { 2087 cx->check(buffer); 2088 2089 MOZ_ASSERT(!buffer->isDetached(), "can't steal from a detached buffer"); 2090 MOZ_ASSERT(!buffer->isImmutable(), "can't steal from an immutable buffer"); 2091 MOZ_ASSERT(!buffer->isLengthPinned(), 2092 "can't steal from a buffer with a pinned length"); 2093 MOZ_ASSERT(!buffer->isPreparedForAsmJS(), 2094 "asm.js-prepared buffers don't have detachable/stealable data"); 2095 } 2096 2097 /* static */ 2098 ArrayBufferObject* ArrayBufferObject::wasmGrowToPagesInPlace( 2099 wasm::AddressType t, Pages newPages, Handle<ArrayBufferObject*> oldBuf, 2100 JSContext* cx) { 2101 if (oldBuf->isLengthPinned()) { 2102 return nullptr; 2103 } 2104 2105 MOZ_ASSERT(oldBuf->isWasm()); 2106 2107 // Check that the new pages is within our allowable range. This will 2108 // simultaneously check against the maximum specified in source and our 2109 // implementation limits. 2110 if (newPages > oldBuf->wasmClampedMaxPages()) { 2111 return nullptr; 2112 } 2113 MOZ_ASSERT(newPages <= wasm::MaxMemoryPages(t, newPages.pageSize()) && 2114 newPages.byteLength() <= ArrayBufferObject::ByteLengthLimit); 2115 2116 if (oldBuf->is<ResizableArrayBufferObject>()) { 2117 RemoveCellMemory(oldBuf, oldBuf->byteLength(), 2118 MemoryUse::ArrayBufferContents); 2119 2120 if (!oldBuf->contents().wasmBuffer()->growToPagesInPlace(newPages)) { 2121 // If fails, the buffer still exists on oldBuf, keep tracking 2122 // cell memory there. 2123 AddCellMemory(oldBuf, oldBuf->byteLength(), 2124 MemoryUse::ArrayBufferContents); 2125 return nullptr; 2126 } 2127 oldBuf->setByteLength(newPages.byteLength()); 2128 AddCellMemory(oldBuf, newPages.byteLength(), 2129 MemoryUse::ArrayBufferContents); 2130 return oldBuf; 2131 } 2132 2133 CheckStealPreconditions(oldBuf, cx); 2134 2135 // We have checked against the clamped maximum and so we know we can convert 2136 // to byte lengths now. 2137 size_t newSize = newPages.byteLength(); 2138 2139 // On failure, do not throw and ensure that the original buffer is 2140 // unmodified and valid. After WasmArrayRawBuffer::growToPagesInPlace(), the 2141 // wasm-visible length of the buffer has been increased so it must be the 2142 // last fallible operation. 2143 2144 auto* newBuf = ArrayBufferObject::createEmpty(cx); 2145 if (!newBuf) { 2146 cx->clearPendingException(); 2147 return nullptr; 2148 } 2149 2150 MOZ_ASSERT(newBuf->isNoData()); 2151 2152 if (!oldBuf->contents().wasmBuffer()->growToPagesInPlace(newPages)) { 2153 return nullptr; 2154 } 2155 2156 // Extract the grown contents from |oldBuf|. 2157 BufferContents oldContents = oldBuf->contents(); 2158 2159 // Overwrite |oldBuf|'s data pointer *without* releasing old data. 2160 oldBuf->setDataPointer(BufferContents::createNoData()); 2161 2162 // Detach |oldBuf| now that doing so won't release |oldContents|. 2163 RemoveCellMemory(oldBuf, oldBuf->byteLength(), 2164 MemoryUse::ArrayBufferContents); 2165 ArrayBufferObject::detach(cx, oldBuf); 2166 2167 // Set |newBuf|'s contents to |oldBuf|'s original contents. 2168 newBuf->initialize(newSize, oldContents); 2169 AddCellMemory(newBuf, newSize, MemoryUse::ArrayBufferContents); 2170 2171 return newBuf; 2172 } 2173 2174 /* static */ 2175 ArrayBufferObject* ArrayBufferObject::wasmMovingGrowToPages( 2176 AddressType t, Pages newPages, Handle<ArrayBufferObject*> oldBuf, 2177 JSContext* cx) { 2178 MOZ_ASSERT(oldBuf->is<FixedLengthArrayBufferObject>()); 2179 2180 // On failure, do not throw and ensure that the original buffer is 2181 // unmodified and valid. 2182 if (oldBuf->isLengthPinned()) { 2183 return nullptr; 2184 } 2185 2186 // Check that the new pages is within our allowable range. This will 2187 // simultaneously check against the maximum specified in source and our 2188 // implementation limits. 2189 if (newPages > oldBuf->wasmClampedMaxPages()) { 2190 return nullptr; 2191 } 2192 MOZ_ASSERT(newPages <= wasm::MaxMemoryPages(t, newPages.pageSize()) && 2193 newPages.byteLength() <= ArrayBufferObject::ByteLengthLimit); 2194 2195 // We have checked against the clamped maximum and so we know we can convert 2196 // to byte lengths now. 2197 size_t newSize = newPages.byteLength(); 2198 2199 // TODO: If the memory reservation allows, or if we can extend the mapping in 2200 // place, fall back to wasmGrowToPagesInPlace to avoid a new allocation + 2201 // copy. 2202 2203 Rooted<ArrayBufferObject*> newBuf(cx, ArrayBufferObject::createEmpty(cx)); 2204 if (!newBuf) { 2205 cx->clearPendingException(); 2206 return nullptr; 2207 } 2208 2209 Pages clampedMaxPages = 2210 wasm::ClampedMaxPages(t, newPages, Nothing(), /* hugeMemory */ false); 2211 MOZ_ASSERT(newPages.pageSize() == oldBuf->wasmPageSize()); 2212 WasmArrayRawBuffer* newRawBuf = WasmArrayRawBuffer::AllocateWasm( 2213 oldBuf->wasmAddressType(), oldBuf->wasmPageSize(), newPages, 2214 clampedMaxPages, Nothing(), Nothing()); 2215 if (!newRawBuf) { 2216 return nullptr; 2217 } 2218 2219 AddCellMemory(newBuf, newSize, MemoryUse::ArrayBufferContents); 2220 2221 BufferContents contents = 2222 BufferContents::createWasm(newRawBuf->dataPointer()); 2223 newBuf->initialize(newSize, contents); 2224 2225 memcpy(newBuf->dataPointer(), oldBuf->dataPointer(), oldBuf->byteLength()); 2226 ArrayBufferObject::detach(cx, oldBuf); 2227 2228 return newBuf; 2229 } 2230 2231 /* static */ 2232 void ArrayBufferObject::wasmDiscard(Handle<ArrayBufferObject*> buf, 2233 uint64_t byteOffset, uint64_t byteLen) { 2234 MOZ_ASSERT(buf->isWasm()); 2235 buf->contents().wasmBuffer()->discard(byteOffset, byteLen); 2236 } 2237 2238 uint32_t ArrayBufferObject::flags() const { 2239 return uint32_t(getFixedSlot(FLAGS_SLOT).toInt32()); 2240 } 2241 2242 void ArrayBufferObject::setFlags(uint32_t flags) { 2243 setFixedSlot(FLAGS_SLOT, Int32Value(flags)); 2244 } 2245 2246 static constexpr js::gc::AllocKind GetArrayBufferGCObjectKind(size_t numSlots) { 2247 if (numSlots <= 4) { 2248 return js::gc::AllocKind::ARRAYBUFFER4; 2249 } 2250 if (numSlots <= 6) { 2251 return js::gc::AllocKind::ARRAYBUFFER6; 2252 } 2253 if (numSlots <= 8) { 2254 return js::gc::AllocKind::ARRAYBUFFER8; 2255 } 2256 if (numSlots <= 12) { 2257 return js::gc::AllocKind::ARRAYBUFFER12; 2258 } 2259 return js::gc::AllocKind::ARRAYBUFFER16; 2260 } 2261 2262 template <class ArrayBufferType> 2263 static ArrayBufferType* NewArrayBufferObject(JSContext* cx, HandleObject proto_, 2264 gc::AllocKind allocKind) { 2265 MOZ_ASSERT(allocKind == gc::AllocKind::ARRAYBUFFER4 || 2266 allocKind == gc::AllocKind::ARRAYBUFFER6 || 2267 allocKind == gc::AllocKind::ARRAYBUFFER8 || 2268 allocKind == gc::AllocKind::ARRAYBUFFER12 || 2269 allocKind == gc::AllocKind::ARRAYBUFFER16); 2270 2271 static_assert(std::is_same_v<ArrayBufferType, FixedLengthArrayBufferObject> || 2272 std::is_same_v<ArrayBufferType, ResizableArrayBufferObject> || 2273 std::is_same_v<ArrayBufferType, ImmutableArrayBufferObject>); 2274 2275 RootedObject proto(cx, proto_); 2276 if (!proto) { 2277 proto = GlobalObject::getOrCreatePrototype(cx, JSProto_ArrayBuffer); 2278 if (!proto) { 2279 MOZ_DIAGNOSTIC_ASSERT(!cx->brittleMode, "creating ArrayBuffer proto"); 2280 return nullptr; 2281 } 2282 } 2283 2284 const JSClass* clasp = &ArrayBufferType::class_; 2285 2286 // Array buffers can store data inline so we only use fixed slots to cover the 2287 // reserved slots, ignoring the AllocKind. 2288 MOZ_ASSERT(ClassCanHaveFixedData(clasp)); 2289 constexpr size_t nfixed = ArrayBufferType::RESERVED_SLOTS; 2290 static_assert(nfixed <= NativeObject::MAX_FIXED_SLOTS); 2291 2292 Rooted<SharedShape*> shape( 2293 cx, 2294 SharedShape::getInitialShape(cx, clasp, cx->realm(), AsTaggedProto(proto), 2295 nfixed, ObjectFlags())); 2296 if (!shape) { 2297 MOZ_DIAGNOSTIC_ASSERT(!cx->brittleMode, "get ArrayBuffer initial shape"); 2298 return nullptr; 2299 } 2300 2301 // Array buffers can't be nursery allocated but can be background-finalized. 2302 MOZ_ASSERT(IsBackgroundFinalized(allocKind)); 2303 MOZ_ASSERT(!CanNurseryAllocateFinalizedClass(clasp)); 2304 constexpr gc::Heap heap = gc::Heap::Tenured; 2305 2306 auto* buffer = 2307 NativeObject::create<ArrayBufferType>(cx, allocKind, heap, shape); 2308 if (!buffer) { 2309 MOZ_DIAGNOSTIC_ASSERT(!cx->brittleMode, "create NativeObject failed"); 2310 } 2311 return buffer; 2312 } 2313 2314 // Creates a new ArrayBufferObject with %ArrayBuffer.prototype% as proto and no 2315 // space for inline data. 2316 static auto* NewArrayBufferObject(JSContext* cx) { 2317 constexpr auto allocKind = 2318 GetArrayBufferGCObjectKind(FixedLengthArrayBufferObject::RESERVED_SLOTS); 2319 return NewArrayBufferObject<FixedLengthArrayBufferObject>(cx, nullptr, 2320 allocKind); 2321 } 2322 static auto* NewResizableArrayBufferObject(JSContext* cx) { 2323 constexpr auto allocKind = 2324 GetArrayBufferGCObjectKind(ResizableArrayBufferObject::RESERVED_SLOTS); 2325 return NewArrayBufferObject<ResizableArrayBufferObject>(cx, nullptr, 2326 allocKind); 2327 } 2328 static auto* NewImmutableArrayBufferObject(JSContext* cx) { 2329 constexpr auto allocKind = 2330 GetArrayBufferGCObjectKind(ImmutableArrayBufferObject::RESERVED_SLOTS); 2331 return NewArrayBufferObject<ImmutableArrayBufferObject>(cx, nullptr, 2332 allocKind); 2333 } 2334 2335 ArrayBufferObject* ArrayBufferObject::createForContents( 2336 JSContext* cx, size_t nbytes, BufferContents contents) { 2337 MOZ_ASSERT(contents); 2338 MOZ_ASSERT(contents.kind() != INLINE_DATA); 2339 MOZ_ASSERT(contents.kind() != NO_DATA); 2340 MOZ_ASSERT(contents.kind() != WASM); 2341 2342 // 24.1.1.1, step 3 (Inlined 6.2.6.1 CreateByteDataBlock, step 2). 2343 if (!CheckArrayBufferTooLarge(cx, nbytes)) { 2344 return nullptr; 2345 } 2346 2347 // Some |contents| kinds need to store extra data in the ArrayBuffer beyond a 2348 // data pointer. If needed for the particular kind, add extra fixed slots to 2349 // the ArrayBuffer for use as raw storage to store such information. 2350 constexpr size_t reservedSlots = FixedLengthArrayBufferObject::RESERVED_SLOTS; 2351 2352 size_t nAllocated = 0; 2353 size_t nslots = reservedSlots; 2354 if (contents.kind() == USER_OWNED) { 2355 // No accounting to do in this case. 2356 } else if (contents.kind() == EXTERNAL) { 2357 // Store the FreeInfo in the inline data slots so that we 2358 // don't use up slots for it in non-refcounted array buffers. 2359 constexpr size_t freeInfoSlots = HowMany(sizeof(FreeInfo), sizeof(Value)); 2360 static_assert( 2361 reservedSlots + freeInfoSlots <= NativeObject::MAX_FIXED_SLOTS, 2362 "FreeInfo must fit in inline slots"); 2363 nslots += freeInfoSlots; 2364 } else { 2365 // The ABO is taking ownership, so account the bytes against the zone. 2366 nAllocated = nbytes; 2367 if (contents.kind() == MAPPED) { 2368 nAllocated = RoundUp(nbytes, js::gc::SystemPageSize()); 2369 } else { 2370 MOZ_ASSERT(contents.kind() == MALLOCED_ARRAYBUFFER_CONTENTS_ARENA || 2371 contents.kind() == MALLOCED_UNKNOWN_ARENA, 2372 "should have handled all possible callers' kinds"); 2373 } 2374 } 2375 2376 gc::AllocKind allocKind = GetArrayBufferGCObjectKind(nslots); 2377 2378 AutoSetNewObjectMetadata metadata(cx); 2379 Rooted<ArrayBufferObject*> buffer( 2380 cx, NewArrayBufferObject<FixedLengthArrayBufferObject>(cx, nullptr, 2381 allocKind)); 2382 if (!buffer) { 2383 return nullptr; 2384 } 2385 2386 MOZ_ASSERT(!gc::IsInsideNursery(buffer), 2387 "ArrayBufferObject has a finalizer that must be called to not " 2388 "leak in some cases, so it can't be nursery-allocated"); 2389 2390 buffer->initialize(nbytes, contents); 2391 2392 if (contents.kind() == MAPPED || 2393 contents.kind() == MALLOCED_ARRAYBUFFER_CONTENTS_ARENA || 2394 contents.kind() == MALLOCED_UNKNOWN_ARENA) { 2395 AddCellMemory(buffer, nAllocated, MemoryUse::ArrayBufferContents); 2396 } 2397 2398 return buffer; 2399 } 2400 2401 ArrayBufferObject* ArrayBufferObject::createFromTypedArrayMallocedElements( 2402 JSContext* cx, Handle<FixedLengthTypedArrayObject*> tarray) { 2403 MOZ_ASSERT(cx->realm() == tarray->realm()); 2404 MOZ_ASSERT(tarray->hasMallocedElements(cx)); 2405 2406 size_t byteLength = tarray->byteLength(); 2407 2408 // The typed array's byteLength must be a valid array buffer length. 2409 static_assert(TypedArrayObject::ByteLengthLimit == 2410 ArrayBufferObject::ByteLengthLimit); 2411 MOZ_RELEASE_ASSERT(byteLength <= ArrayBufferObject::ByteLengthLimit); 2412 2413 constexpr size_t reservedSlots = FixedLengthArrayBufferObject::RESERVED_SLOTS; 2414 constexpr gc::AllocKind allocKind = GetArrayBufferGCObjectKind(reservedSlots); 2415 2416 AutoSetNewObjectMetadata metadata(cx); 2417 Rooted<ArrayBufferObject*> buffer( 2418 cx, NewArrayBufferObject<FixedLengthArrayBufferObject>(cx, nullptr, 2419 allocKind)); 2420 if (!buffer) { 2421 return nullptr; 2422 } 2423 2424 MOZ_ASSERT(!gc::IsInsideNursery(buffer), 2425 "ArrayBufferObject has a finalizer that must be called to not " 2426 "leak in some cases, so it can't be nursery-allocated"); 2427 2428 // Transfer ownership of the malloced buffer from the typed array to the 2429 // new array buffer. 2430 2431 size_t nbytes = RoundUp(byteLength, sizeof(Value)); 2432 if (!tarray->isTenured()) { 2433 cx->nursery().removeMallocedBuffer(tarray->elements(), nbytes); 2434 } 2435 RemoveCellMemory(tarray, nbytes, MemoryUse::TypedArrayElements); 2436 2437 auto contents = BufferContents::createMallocedArrayBufferContentsArena( 2438 tarray->elements()); 2439 buffer->initialize(byteLength, contents); 2440 AddCellMemory(buffer, byteLength, MemoryUse::ArrayBufferContents); 2441 2442 return buffer; 2443 } 2444 2445 template <class ArrayBufferType, ArrayBufferObject::FillContents FillType> 2446 /* static */ std::tuple<ArrayBufferType*, uint8_t*> 2447 ArrayBufferObject::createUninitializedBufferAndData( 2448 JSContext* cx, size_t nbytes, AutoSetNewObjectMetadata&, 2449 JS::Handle<JSObject*> proto) { 2450 MOZ_ASSERT(nbytes <= ArrayBufferObject::ByteLengthLimit, 2451 "caller must validate the byte count it passes"); 2452 2453 static_assert(std::is_same_v<ArrayBufferType, FixedLengthArrayBufferObject> || 2454 std::is_same_v<ArrayBufferType, ResizableArrayBufferObject> || 2455 std::is_same_v<ArrayBufferType, ImmutableArrayBufferObject>); 2456 2457 // Try fitting the data inline with the object by repurposing fixed-slot 2458 // storage. Add extra fixed slots if necessary to accomplish this, but don't 2459 // exceed the maximum number of fixed slots! 2460 size_t nslots = ArrayBufferType::RESERVED_SLOTS; 2461 ArrayBufferContents data; 2462 if (nbytes <= ArrayBufferType::MaxInlineBytes) { 2463 int newSlots = HowMany(nbytes, sizeof(Value)); 2464 MOZ_ASSERT(int(nbytes) <= newSlots * int(sizeof(Value))); 2465 2466 nslots += newSlots; 2467 } else { 2468 data = FillType == FillContents::Uninitialized 2469 ? AllocateUninitializedArrayBufferContents(cx, nbytes) 2470 : AllocateArrayBufferContents(cx, nbytes); 2471 if (!data) { 2472 if (cx->brittleMode) { 2473 if (nbytes < INT32_MAX) { 2474 MOZ_DIAGNOSTIC_CRASH("ArrayBuffer allocation OOM < 2GB - 1"); 2475 } else { 2476 MOZ_DIAGNOSTIC_ASSERT( 2477 false, 2478 "ArrayBuffer allocation OOM between 2GB and ByteLengthLimit"); 2479 } 2480 } 2481 return {nullptr, nullptr}; 2482 } 2483 } 2484 2485 gc::AllocKind allocKind = GetArrayBufferGCObjectKind(nslots); 2486 2487 auto* buffer = NewArrayBufferObject<ArrayBufferType>(cx, proto, allocKind); 2488 if (!buffer) { 2489 return {nullptr, nullptr}; 2490 } 2491 2492 MOZ_ASSERT(!gc::IsInsideNursery(buffer), 2493 "ArrayBufferObject has a finalizer that must be called to not " 2494 "leak in some cases, so it can't be nursery-allocated"); 2495 2496 if (data) { 2497 return {buffer, data.release()}; 2498 } 2499 2500 if constexpr (FillType == FillContents::Zero) { 2501 memset(buffer->inlineDataPointer(), 0, nbytes); 2502 } 2503 return {buffer, nullptr}; 2504 } 2505 2506 template <class ArrayBufferType, ArrayBufferObject::FillContents FillType> 2507 /* static */ std::tuple<ArrayBufferType*, uint8_t*> 2508 ArrayBufferObject::createBufferAndData( 2509 JSContext* cx, size_t nbytes, AutoSetNewObjectMetadata& metadata, 2510 JS::Handle<JSObject*> proto /* = nullptr */) { 2511 MOZ_ASSERT(nbytes <= ArrayBufferObject::ByteLengthLimit, 2512 "caller must validate the byte count it passes"); 2513 2514 static_assert(!std::is_same_v<ArrayBufferType, ResizableArrayBufferObject>, 2515 "Use ResizableArrayBufferObject::createBufferAndData"); 2516 2517 auto [buffer, data] = 2518 createUninitializedBufferAndData<ArrayBufferType, FillType>( 2519 cx, nbytes, metadata, proto); 2520 if (!buffer) { 2521 return {nullptr, nullptr}; 2522 } 2523 2524 if (data) { 2525 buffer->initialize( 2526 nbytes, BufferContents::createMallocedArrayBufferContentsArena(data)); 2527 AddCellMemory(buffer, nbytes, MemoryUse::ArrayBufferContents); 2528 } else { 2529 data = buffer->inlineDataPointer(); 2530 buffer->initialize(nbytes, BufferContents::createInlineData(data)); 2531 } 2532 return {buffer, data}; 2533 } 2534 2535 template <ArrayBufferObject::FillContents FillType> 2536 /* static */ std::tuple<ResizableArrayBufferObject*, uint8_t*> 2537 ResizableArrayBufferObject::createBufferAndData( 2538 JSContext* cx, size_t byteLength, size_t maxByteLength, 2539 AutoSetNewObjectMetadata& metadata, Handle<JSObject*> proto) { 2540 MOZ_ASSERT(byteLength <= maxByteLength); 2541 MOZ_ASSERT(maxByteLength <= ArrayBufferObject::ByteLengthLimit, 2542 "caller must validate the byte count it passes"); 2543 2544 // NOTE: The spec proposal for resizable ArrayBuffers suggests to use a 2545 // virtual memory based approach to avoid eagerly allocating the maximum byte 2546 // length. We don't yet support this and instead are allocating the maximum 2547 // byte length direct from the start. 2548 size_t nbytes = maxByteLength; 2549 2550 auto [buffer, data] = 2551 createUninitializedBufferAndData<ResizableArrayBufferObject, FillType>( 2552 cx, nbytes, metadata, proto); 2553 if (!buffer) { 2554 return {nullptr, nullptr}; 2555 } 2556 2557 if (data) { 2558 buffer->initialize( 2559 byteLength, maxByteLength, 2560 BufferContents::createMallocedArrayBufferContentsArena(data)); 2561 AddCellMemory(buffer, nbytes, MemoryUse::ArrayBufferContents); 2562 } else { 2563 data = buffer->inlineDataPointer(); 2564 buffer->initialize(byteLength, maxByteLength, 2565 BufferContents::createInlineData(data)); 2566 } 2567 return {buffer, data}; 2568 } 2569 2570 template <class ArrayBufferType> 2571 /* static */ ArrayBufferType* ArrayBufferObject::copy( 2572 JSContext* cx, size_t newByteLength, 2573 JS::Handle<ArrayBufferObject*> source) { 2574 MOZ_ASSERT(!source->isDetached()); 2575 MOZ_ASSERT(newByteLength <= ArrayBufferObject::ByteLengthLimit, 2576 "caller must validate the byte count it passes"); 2577 2578 size_t sourceByteLength = source->byteLength(); 2579 2580 if (newByteLength > sourceByteLength) { 2581 // Copy into a larger buffer. 2582 AutoSetNewObjectMetadata metadata(cx); 2583 auto [buffer, toFill] = 2584 createBufferAndData<ArrayBufferType, FillContents::Zero>( 2585 cx, newByteLength, metadata, nullptr); 2586 if (!buffer) { 2587 return nullptr; 2588 } 2589 2590 std::copy_n(source->dataPointer(), sourceByteLength, toFill); 2591 2592 return buffer; 2593 } 2594 2595 // Copy into a smaller or same size buffer. 2596 AutoSetNewObjectMetadata metadata(cx); 2597 auto [buffer, toFill] = 2598 createBufferAndData<ArrayBufferType, FillContents::Uninitialized>( 2599 cx, newByteLength, metadata, nullptr); 2600 if (!buffer) { 2601 return nullptr; 2602 } 2603 2604 std::uninitialized_copy_n(source->dataPointer(), newByteLength, toFill); 2605 2606 return buffer; 2607 } 2608 2609 /* static */ ResizableArrayBufferObject* ResizableArrayBufferObject::copy( 2610 JSContext* cx, size_t newByteLength, 2611 JS::Handle<ResizableArrayBufferObject*> source) { 2612 MOZ_ASSERT(!source->isDetached()); 2613 MOZ_ASSERT(newByteLength <= source->maxByteLength()); 2614 2615 size_t sourceByteLength = source->byteLength(); 2616 size_t newMaxByteLength = source->maxByteLength(); 2617 2618 if (newByteLength > sourceByteLength) { 2619 // Copy into a larger buffer. 2620 AutoSetNewObjectMetadata metadata(cx); 2621 auto [buffer, toFill] = createBufferAndData<FillContents::Zero>( 2622 cx, newByteLength, newMaxByteLength, metadata, nullptr); 2623 if (!buffer) { 2624 return nullptr; 2625 } 2626 2627 // The `createBufferAndData()` call first zero-initializes the complete 2628 // buffer and then we copy over |sourceByteLength| bytes from |source|. It 2629 // seems prudent to only zero-initialize the trailing bytes of |toFill| 2630 // to avoid writing twice to `toFill[0..newByteLength]`. We don't yet 2631 // implement this optimization, because this method is only called for 2632 // small, inline buffers, so any write optimizations probably won't make 2633 // much of a difference. 2634 std::copy_n(source->dataPointer(), sourceByteLength, toFill); 2635 2636 return buffer; 2637 } 2638 2639 // Copy into a smaller or same size buffer. 2640 AutoSetNewObjectMetadata metadata(cx); 2641 auto [buffer, toFill] = createBufferAndData<FillContents::Uninitialized>( 2642 cx, newByteLength, newMaxByteLength, metadata, nullptr); 2643 if (!buffer) { 2644 return nullptr; 2645 } 2646 2647 std::uninitialized_copy_n(source->dataPointer(), newByteLength, toFill); 2648 2649 return buffer; 2650 } 2651 2652 template <class ArrayBufferType> 2653 /* static */ ArrayBufferType* ArrayBufferObject::copyAndDetach( 2654 JSContext* cx, size_t newByteLength, 2655 JS::Handle<ArrayBufferObject*> source) { 2656 MOZ_ASSERT(!source->isDetached()); 2657 MOZ_ASSERT(!source->isImmutable()); 2658 MOZ_ASSERT(!source->isLengthPinned()); 2659 MOZ_ASSERT(newByteLength <= ArrayBufferObject::ByteLengthLimit, 2660 "caller must validate the byte count it passes"); 2661 2662 if (newByteLength > ArrayBufferType::MaxInlineBytes && source->isMalloced()) { 2663 if (newByteLength == source->associatedBytes()) { 2664 return copyAndDetachSteal<ArrayBufferType>(cx, source); 2665 } 2666 if (source->bufferKind() == 2667 ArrayBufferObject::MALLOCED_ARRAYBUFFER_CONTENTS_ARENA) { 2668 return copyAndDetachRealloc<ArrayBufferType>(cx, newByteLength, source); 2669 } 2670 } 2671 2672 auto* newBuffer = 2673 ArrayBufferObject::copy<ArrayBufferType>(cx, newByteLength, source); 2674 if (!newBuffer) { 2675 return nullptr; 2676 } 2677 ArrayBufferObject::detach(cx, source); 2678 2679 return newBuffer; 2680 } 2681 2682 template <class ArrayBufferType> 2683 /* static */ ArrayBufferType* ArrayBufferObject::copyAndDetachSteal( 2684 JSContext* cx, JS::Handle<ArrayBufferObject*> source) { 2685 MOZ_ASSERT(!source->isDetached()); 2686 MOZ_ASSERT(!source->isImmutable()); 2687 MOZ_ASSERT(!source->isLengthPinned()); 2688 MOZ_ASSERT(source->isMalloced()); 2689 2690 size_t newByteLength = source->associatedBytes(); 2691 MOZ_ASSERT(newByteLength > ArrayBufferType::MaxInlineBytes, 2692 "prefer copying small buffers"); 2693 MOZ_ASSERT(source->byteLength() <= newByteLength, 2694 "source length is less-or-equal to |newByteLength|"); 2695 2696 auto* newBuffer = ArrayBufferType::createEmpty(cx); 2697 if (!newBuffer) { 2698 return nullptr; 2699 } 2700 2701 // Extract the contents from |source|. 2702 BufferContents contents = source->contents(); 2703 MOZ_ASSERT(contents); 2704 MOZ_ASSERT(contents.kind() == MALLOCED_ARRAYBUFFER_CONTENTS_ARENA || 2705 contents.kind() == MALLOCED_UNKNOWN_ARENA); 2706 2707 // Overwrite |source|'s data pointer *without* releasing the data. 2708 source->setDataPointer(BufferContents::createNoData()); 2709 2710 // Detach |source| now that doing so won't release |contents|. 2711 RemoveCellMemory(source, newByteLength, MemoryUse::ArrayBufferContents); 2712 ArrayBufferObject::detach(cx, source); 2713 2714 // Set |newBuffer|'s contents to |source|'s original contents. 2715 newBuffer->initialize(newByteLength, contents); 2716 AddCellMemory(newBuffer, newByteLength, MemoryUse::ArrayBufferContents); 2717 2718 return newBuffer; 2719 } 2720 2721 template <class ArrayBufferType> 2722 /* static */ ArrayBufferType* ArrayBufferObject::copyAndDetachRealloc( 2723 JSContext* cx, size_t newByteLength, 2724 JS::Handle<ArrayBufferObject*> source) { 2725 MOZ_ASSERT(!source->isDetached()); 2726 MOZ_ASSERT(!source->isImmutable()); 2727 MOZ_ASSERT(!source->isLengthPinned()); 2728 MOZ_ASSERT(source->bufferKind() == MALLOCED_ARRAYBUFFER_CONTENTS_ARENA); 2729 MOZ_ASSERT(newByteLength > ArrayBufferType::MaxInlineBytes, 2730 "prefer copying small buffers"); 2731 MOZ_ASSERT(newByteLength <= ArrayBufferObject::ByteLengthLimit, 2732 "caller must validate the byte count it passes"); 2733 2734 size_t oldByteLength = source->associatedBytes(); 2735 MOZ_ASSERT(oldByteLength != newByteLength, 2736 "steal instead of realloc same size buffers"); 2737 MOZ_ASSERT(source->byteLength() <= oldByteLength, 2738 "source length is less-or-equal to |oldByteLength|"); 2739 2740 Rooted<ArrayBufferType*> newBuffer(cx, ArrayBufferType::createEmpty(cx)); 2741 if (!newBuffer) { 2742 return nullptr; 2743 } 2744 2745 // Extract the contents from |source|. 2746 BufferContents contents = source->contents(); 2747 MOZ_ASSERT(contents); 2748 MOZ_ASSERT(contents.kind() == MALLOCED_ARRAYBUFFER_CONTENTS_ARENA); 2749 2750 // Reallocate the data pointer. 2751 auto newData = ReallocateArrayBufferContents(cx, contents.data(), 2752 oldByteLength, newByteLength); 2753 if (!newData) { 2754 // If reallocation failed, the old pointer is still valid, so just return. 2755 return nullptr; 2756 } 2757 auto newContents = 2758 BufferContents::createMallocedArrayBufferContentsArena(newData.release()); 2759 2760 // Overwrite |source|'s data pointer *without* releasing the data. 2761 source->setDataPointer(BufferContents::createNoData()); 2762 2763 // Detach |source| now that doing so won't release |contents|. 2764 RemoveCellMemory(source, oldByteLength, MemoryUse::ArrayBufferContents); 2765 ArrayBufferObject::detach(cx, source); 2766 2767 // Set |newBuffer|'s contents to |newContents|. 2768 newBuffer->initialize(newByteLength, newContents); 2769 AddCellMemory(newBuffer, newByteLength, MemoryUse::ArrayBufferContents); 2770 2771 // Zero initialize the newly allocated memory, if necessary. 2772 if (newByteLength > oldByteLength) { 2773 size_t count = newByteLength - oldByteLength; 2774 std::uninitialized_fill_n(newContents.data() + oldByteLength, count, 0); 2775 } 2776 2777 return newBuffer; 2778 } 2779 2780 /* static */ ResizableArrayBufferObject* 2781 ResizableArrayBufferObject::copyAndDetach( 2782 JSContext* cx, size_t newByteLength, 2783 JS::Handle<ResizableArrayBufferObject*> source) { 2784 MOZ_ASSERT(!source->isDetached()); 2785 MOZ_ASSERT(!source->isImmutable()); 2786 MOZ_ASSERT(!source->isLengthPinned()); 2787 MOZ_ASSERT(newByteLength <= source->maxByteLength()); 2788 2789 if (source->maxByteLength() > ResizableArrayBufferObject::MaxInlineBytes && 2790 source->isMalloced()) { 2791 return copyAndDetachSteal(cx, newByteLength, source); 2792 } 2793 2794 auto* newBuffer = ResizableArrayBufferObject::copy(cx, newByteLength, source); 2795 if (!newBuffer) { 2796 return nullptr; 2797 } 2798 ArrayBufferObject::detach(cx, source); 2799 2800 return newBuffer; 2801 } 2802 2803 /* static */ ResizableArrayBufferObject* 2804 ResizableArrayBufferObject::copyAndDetachSteal( 2805 JSContext* cx, size_t newByteLength, 2806 JS::Handle<ResizableArrayBufferObject*> source) { 2807 MOZ_ASSERT(!source->isDetached()); 2808 MOZ_ASSERT(!source->isImmutable()); 2809 MOZ_ASSERT(!source->isLengthPinned()); 2810 MOZ_ASSERT(newByteLength <= source->maxByteLength()); 2811 MOZ_ASSERT(source->isMalloced()); 2812 2813 size_t sourceByteLength = source->byteLength(); 2814 size_t maxByteLength = source->maxByteLength(); 2815 MOZ_ASSERT(maxByteLength > ResizableArrayBufferObject::MaxInlineBytes, 2816 "prefer copying small buffers"); 2817 2818 auto* newBuffer = ResizableArrayBufferObject::createEmpty(cx); 2819 if (!newBuffer) { 2820 return nullptr; 2821 } 2822 2823 // Extract the contents from |source|. 2824 BufferContents contents = source->contents(); 2825 MOZ_ASSERT(contents); 2826 MOZ_ASSERT(contents.kind() == MALLOCED_ARRAYBUFFER_CONTENTS_ARENA || 2827 contents.kind() == MALLOCED_UNKNOWN_ARENA); 2828 2829 // Overwrite |source|'s data pointer *without* releasing the data. 2830 source->setDataPointer(BufferContents::createNoData()); 2831 2832 // Detach |source| now that doing so won't release |contents|. 2833 RemoveCellMemory(source, maxByteLength, MemoryUse::ArrayBufferContents); 2834 ArrayBufferObject::detach(cx, source); 2835 2836 // Set |newBuffer|'s contents to |source|'s original contents. 2837 newBuffer->initialize(newByteLength, maxByteLength, contents); 2838 AddCellMemory(newBuffer, maxByteLength, MemoryUse::ArrayBufferContents); 2839 2840 // Clear the bytes between `data[newByteLength..sourceByteLength]`. 2841 if (newByteLength < sourceByteLength) { 2842 size_t nbytes = sourceByteLength - newByteLength; 2843 memset(newBuffer->dataPointer() + newByteLength, 0, nbytes); 2844 } 2845 2846 return newBuffer; 2847 } 2848 2849 /* static */ ImmutableArrayBufferObject* ImmutableArrayBufferObject::slice( 2850 JSContext* cx, size_t newByteLength, JS::Handle<ArrayBufferObject*> source, 2851 size_t sourceByteOffset) { 2852 MOZ_ASSERT(newByteLength <= ArrayBufferObject::ByteLengthLimit, 2853 "caller must validate the byte count it passes"); 2854 MOZ_ASSERT(!source->isDetached()); 2855 MOZ_ASSERT(source->byteLength() >= sourceByteOffset); 2856 MOZ_ASSERT(source->byteLength() >= sourceByteOffset + newByteLength); 2857 2858 AutoSetNewObjectMetadata metadata(cx); 2859 auto [newBuffer, toFill] = createBufferAndData<ImmutableArrayBufferObject, 2860 FillContents::Uninitialized>( 2861 cx, newByteLength, metadata); 2862 if (!newBuffer) { 2863 return nullptr; 2864 } 2865 2866 std::uninitialized_copy_n(source->dataPointer() + sourceByteOffset, 2867 newByteLength, toFill); 2868 2869 return newBuffer; 2870 } 2871 2872 FixedLengthArrayBufferObject* ArrayBufferObject::createZeroed( 2873 JSContext* cx, size_t nbytes, HandleObject proto /* = nullptr */) { 2874 // 24.1.1.1, step 3 (Inlined 6.2.6.1 CreateByteDataBlock, step 2). 2875 if (!CheckArrayBufferTooLarge(cx, nbytes)) { 2876 MOZ_DIAGNOSTIC_ASSERT(!cx->brittleMode, "buffer too large"); 2877 return nullptr; 2878 } 2879 2880 AutoSetNewObjectMetadata metadata(cx); 2881 auto [buffer, toFill] = 2882 createBufferAndData<FixedLengthArrayBufferObject, FillContents::Zero>( 2883 cx, nbytes, metadata, proto); 2884 (void)toFill; 2885 return buffer; 2886 } 2887 2888 ResizableArrayBufferObject* ResizableArrayBufferObject::createZeroed( 2889 JSContext* cx, size_t byteLength, size_t maxByteLength, 2890 HandleObject proto /* = nullptr */) { 2891 // 24.1.1.1, step 3 (Inlined 6.2.6.1 CreateByteDataBlock, step 2). 2892 if (!CheckArrayBufferTooLarge(cx, byteLength) || 2893 !CheckArrayBufferTooLarge(cx, maxByteLength)) { 2894 return nullptr; 2895 } 2896 if (byteLength > maxByteLength) { 2897 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 2898 JSMSG_ARRAYBUFFER_LENGTH_LARGER_THAN_MAXIMUM); 2899 return nullptr; 2900 } 2901 2902 AutoSetNewObjectMetadata metadata(cx); 2903 auto [buffer, toFill] = createBufferAndData<FillContents::Zero>( 2904 cx, byteLength, maxByteLength, metadata, proto); 2905 (void)toFill; 2906 return buffer; 2907 } 2908 2909 ImmutableArrayBufferObject* ImmutableArrayBufferObject::createZeroed( 2910 JSContext* cx, size_t byteLength, HandleObject proto /* = nullptr */) { 2911 // 24.1.1.1, step 3 (Inlined 6.2.6.1 CreateByteDataBlock, step 2). 2912 if (!CheckArrayBufferTooLarge(cx, byteLength)) { 2913 MOZ_DIAGNOSTIC_ASSERT(!cx->brittleMode, "buffer too large"); 2914 return nullptr; 2915 } 2916 2917 AutoSetNewObjectMetadata metadata(cx); 2918 auto [buffer, toFill] = 2919 createBufferAndData<ImmutableArrayBufferObject, FillContents::Zero>( 2920 cx, byteLength, metadata, proto); 2921 (void)toFill; 2922 return buffer; 2923 } 2924 2925 FixedLengthArrayBufferObject* ArrayBufferObject::createEmpty(JSContext* cx) { 2926 AutoSetNewObjectMetadata metadata(cx); 2927 auto* obj = NewArrayBufferObject(cx); 2928 if (!obj) { 2929 return nullptr; 2930 } 2931 2932 obj->initialize(0, BufferContents::createNoData()); 2933 return obj; 2934 } 2935 2936 ResizableArrayBufferObject* ResizableArrayBufferObject::createEmpty( 2937 JSContext* cx) { 2938 AutoSetNewObjectMetadata metadata(cx); 2939 auto* obj = NewResizableArrayBufferObject(cx); 2940 if (!obj) { 2941 return nullptr; 2942 } 2943 2944 obj->initialize(0, 0, BufferContents::createNoData()); 2945 return obj; 2946 } 2947 2948 ImmutableArrayBufferObject* ImmutableArrayBufferObject::createEmpty( 2949 JSContext* cx) { 2950 AutoSetNewObjectMetadata metadata(cx); 2951 auto* obj = NewImmutableArrayBufferObject(cx); 2952 if (!obj) { 2953 return nullptr; 2954 } 2955 2956 obj->initialize(0, BufferContents::createNoData()); 2957 return obj; 2958 } 2959 2960 ArrayBufferObject* ArrayBufferObject::createFromNewRawBuffer( 2961 JSContext* cx, WasmArrayRawBuffer* rawBuffer, size_t initialSize) { 2962 AutoSetNewObjectMetadata metadata(cx); 2963 ArrayBufferObject* buffer = NewArrayBufferObject(cx); 2964 if (!buffer) { 2965 WasmArrayRawBuffer::Release(rawBuffer->dataPointer()); 2966 return nullptr; 2967 } 2968 2969 MOZ_ASSERT(initialSize == rawBuffer->byteLength()); 2970 2971 auto contents = BufferContents::createWasm(rawBuffer->dataPointer()); 2972 buffer->initialize(initialSize, contents); 2973 2974 AddCellMemory(buffer, initialSize, MemoryUse::ArrayBufferContents); 2975 2976 return buffer; 2977 } 2978 2979 template <typename ArrayBufferType> 2980 ArrayBufferType* ArrayBufferObject::createFromWasmObject( 2981 JSContext* cx, Handle<ArrayBufferObject*> donor) { 2982 AutoSetNewObjectMetadata metadata(cx); 2983 // Similar to NewResizableArrayBufferObject/NewArrayBufferObject 2984 constexpr auto allocKind = 2985 GetArrayBufferGCObjectKind(ArrayBufferType::RESERVED_SLOTS); 2986 ArrayBufferType* buffer = 2987 NewArrayBufferObject<ArrayBufferType>(cx, nullptr, allocKind); 2988 if (!buffer) { 2989 return nullptr; 2990 } 2991 2992 RemoveCellMemory(donor, donor->byteLength(), MemoryUse::ArrayBufferContents); 2993 2994 MOZ_RELEASE_ASSERT(donor->isWasm()); 2995 // The only flag allowed to be set on the `donor` is the resizable flag. 2996 MOZ_RELEASE_ASSERT((donor->flags() & ~KIND_MASK & ~RESIZABLE) == 0); 2997 BufferContents contents(donor->dataPointer(), WASM); 2998 size_t byteLength = donor->byteLength(); 2999 [[maybe_unused]] size_t maxByteLength = donor->wasmClampedMaxByteLength(); 3000 3001 // Overwrite |oldBuf|'s data pointer *without* releasing old data. 3002 donor->setDataPointer(BufferContents::createNoData()); 3003 ArrayBufferObject::detach(cx, donor); 3004 3005 if constexpr (std::is_same_v<ArrayBufferType, ResizableArrayBufferObject>) { 3006 buffer->initialize(byteLength, maxByteLength, contents); 3007 } else { 3008 static_assert( 3009 std::is_same_v<ArrayBufferType, FixedLengthArrayBufferObject>); 3010 buffer->initialize(byteLength, contents); 3011 } 3012 3013 AddCellMemory(buffer, buffer->byteLength(), MemoryUse::ArrayBufferContents); 3014 return buffer; 3015 } 3016 3017 template FixedLengthArrayBufferObject* 3018 ArrayBufferObject::createFromWasmObject<FixedLengthArrayBufferObject>( 3019 JSContext* cx, Handle<ArrayBufferObject*> donor); 3020 3021 template ResizableArrayBufferObject* 3022 ArrayBufferObject::createFromWasmObject<ResizableArrayBufferObject>( 3023 JSContext* cx, Handle<ArrayBufferObject*> donor); 3024 3025 /* static */ uint8_t* ArrayBufferObject::stealMallocedContents( 3026 JSContext* cx, Handle<ArrayBufferObject*> buffer) { 3027 CheckStealPreconditions(buffer, cx); 3028 3029 switch (buffer->bufferKind()) { 3030 case MALLOCED_ARRAYBUFFER_CONTENTS_ARENA: 3031 case MALLOCED_UNKNOWN_ARENA: { 3032 uint8_t* stolenData = buffer->dataPointer(); 3033 MOZ_ASSERT(stolenData); 3034 3035 // Resizable buffers are initially allocated with their maximum 3036 // byte-length. When stealing the buffer contents shrink the allocated 3037 // memory to the actually used byte-length. 3038 if (buffer->isResizable()) { 3039 auto* resizableBuffer = &buffer->as<ResizableArrayBufferObject>(); 3040 size_t byteLength = resizableBuffer->byteLength(); 3041 size_t maxByteLength = resizableBuffer->maxByteLength(); 3042 MOZ_ASSERT(byteLength <= maxByteLength); 3043 3044 if (byteLength < maxByteLength) { 3045 auto newData = ReallocateArrayBufferContents( 3046 cx, stolenData, maxByteLength, byteLength); 3047 if (!newData) { 3048 // If reallocation failed, the old pointer is still valid. The 3049 // ArrayBuffer isn't detached and still owns the malloc'ed memory. 3050 return nullptr; 3051 } 3052 3053 // The following code must be infallible, because the data pointer of 3054 // |buffer| is possibly no longer valid after the above realloc. 3055 3056 stolenData = newData.release(); 3057 } 3058 } 3059 3060 RemoveCellMemory(buffer, buffer->associatedBytes(), 3061 MemoryUse::ArrayBufferContents); 3062 3063 // Overwrite the old data pointer *without* releasing the contents 3064 // being stolen. 3065 buffer->setDataPointer(BufferContents::createNoData()); 3066 3067 // Detach |buffer| now that doing so won't free |stolenData|. 3068 ArrayBufferObject::detach(cx, buffer); 3069 return stolenData; 3070 } 3071 3072 case INLINE_DATA: 3073 case NO_DATA: 3074 case USER_OWNED: 3075 case MAPPED: 3076 case EXTERNAL: { 3077 // We can't use these data types directly. Make a copy to return. 3078 ArrayBufferContents copiedData = NewCopiedBufferContents(cx, buffer); 3079 if (!copiedData) { 3080 return nullptr; 3081 } 3082 3083 // Detach |buffer|. This immediately releases the currently owned 3084 // contents, freeing or unmapping data in the MAPPED and EXTERNAL cases. 3085 ArrayBufferObject::detach(cx, buffer); 3086 return copiedData.release(); 3087 } 3088 3089 case WASM: 3090 MOZ_ASSERT_UNREACHABLE( 3091 "wasm buffers aren't stealable except by a " 3092 "memory.grow operation that shouldn't call this " 3093 "function"); 3094 return nullptr; 3095 } 3096 3097 MOZ_ASSERT_UNREACHABLE("garbage kind computed"); 3098 return nullptr; 3099 } 3100 3101 /* static */ ArrayBufferObject::BufferContents 3102 ArrayBufferObject::extractStructuredCloneContents( 3103 JSContext* cx, Handle<ArrayBufferObject*> buffer) { 3104 if (buffer->isLengthPinned()) { 3105 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3106 JSMSG_ARRAYBUFFER_LENGTH_PINNED); 3107 return BufferContents::createFailed(); 3108 } 3109 3110 CheckStealPreconditions(buffer, cx); 3111 3112 // We don't yet support extracting the contents of resizable buffers. 3113 MOZ_ASSERT(!buffer->isResizable(), 3114 "extracting the contents of resizable buffers not supported"); 3115 3116 BufferContents contents = buffer->contents(); 3117 3118 switch (contents.kind()) { 3119 case INLINE_DATA: 3120 case NO_DATA: 3121 case USER_OWNED: { 3122 ArrayBufferContents copiedData = NewCopiedBufferContents(cx, buffer); 3123 if (!copiedData) { 3124 return BufferContents::createFailed(); 3125 } 3126 3127 ArrayBufferObject::detach(cx, buffer); 3128 return BufferContents::createMallocedArrayBufferContentsArena( 3129 copiedData.release()); 3130 } 3131 3132 case MALLOCED_ARRAYBUFFER_CONTENTS_ARENA: 3133 case MALLOCED_UNKNOWN_ARENA: 3134 case MAPPED: { 3135 MOZ_ASSERT(contents); 3136 3137 RemoveCellMemory(buffer, buffer->associatedBytes(), 3138 MemoryUse::ArrayBufferContents); 3139 3140 // Overwrite the old data pointer *without* releasing old data. 3141 buffer->setDataPointer(BufferContents::createNoData()); 3142 3143 // Detach |buffer| now that doing so won't release |oldContents|. 3144 ArrayBufferObject::detach(cx, buffer); 3145 return contents; 3146 } 3147 3148 case WASM: 3149 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3150 JSMSG_WASM_NO_TRANSFER); 3151 return BufferContents::createFailed(); 3152 3153 case EXTERNAL: 3154 MOZ_ASSERT_UNREACHABLE( 3155 "external ArrayBuffer shouldn't have passed the " 3156 "structured-clone preflighting"); 3157 break; 3158 } 3159 3160 MOZ_ASSERT_UNREACHABLE("garbage kind computed"); 3161 return BufferContents::createFailed(); 3162 } 3163 3164 /* static */ 3165 bool ArrayBufferObject::ensureNonInline(JSContext* cx, 3166 Handle<ArrayBufferObject*> buffer) { 3167 if (buffer->isDetached() || buffer->isPreparedForAsmJS()) { 3168 return true; 3169 } 3170 3171 if (buffer->isLengthPinned()) { 3172 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3173 JSMSG_ARRAYBUFFER_LENGTH_PINNED); 3174 MOZ_DIAGNOSTIC_ASSERT(!cx->brittleMode, "ArrayBuffer length pinned"); 3175 return false; 3176 } 3177 3178 BufferContents inlineContents = buffer->contents(); 3179 if (inlineContents.kind() != INLINE_DATA) { 3180 return true; 3181 } 3182 3183 size_t nbytes = buffer->maxByteLength(); 3184 ArrayBufferContents copy = NewCopiedBufferContents(cx, buffer); 3185 if (!copy) { 3186 return false; 3187 } 3188 BufferContents outOfLineContents = 3189 BufferContents::createMallocedArrayBufferContentsArena(copy.release()); 3190 buffer->setDataPointer(outOfLineContents); 3191 AddCellMemory(buffer, nbytes, MemoryUse::ArrayBufferContents); 3192 3193 if (!buffer->firstView()) { 3194 return true; // No views! Easy! 3195 } 3196 3197 buffer->firstView()->as<ArrayBufferViewObject>().notifyBufferMoved( 3198 inlineContents.data(), outOfLineContents.data()); 3199 3200 auto& innerViews = ObjectRealm::get(buffer).innerViews.get(); 3201 if (InnerViewTable::ViewVector* views = 3202 innerViews.maybeViewsUnbarriered(buffer)) { 3203 for (JSObject* view : *views) { 3204 view->as<ArrayBufferViewObject>().notifyBufferMoved( 3205 inlineContents.data(), outOfLineContents.data()); 3206 } 3207 } 3208 3209 return true; 3210 } 3211 3212 /* static */ 3213 void ArrayBufferObject::addSizeOfExcludingThis( 3214 JSObject* obj, mozilla::MallocSizeOf mallocSizeOf, JS::ClassInfo* info, 3215 JS::RuntimeSizes* runtimeSizes) { 3216 auto& buffer = obj->as<ArrayBufferObject>(); 3217 switch (buffer.bufferKind()) { 3218 case INLINE_DATA: 3219 // Inline data's size should be reported by this object's size-class 3220 // reporting. 3221 break; 3222 case MALLOCED_ARRAYBUFFER_CONTENTS_ARENA: 3223 case MALLOCED_UNKNOWN_ARENA: 3224 if (buffer.isPreparedForAsmJS()) { 3225 info->objectsMallocHeapElementsAsmJS += 3226 mallocSizeOf(buffer.dataPointer()); 3227 } else { 3228 info->objectsMallocHeapElementsNormal += 3229 mallocSizeOf(buffer.dataPointer()); 3230 } 3231 break; 3232 case NO_DATA: 3233 // No data is no memory. 3234 MOZ_ASSERT(buffer.dataPointer() == nullptr); 3235 break; 3236 case USER_OWNED: 3237 // User-owned data should be accounted for by the user. 3238 break; 3239 case EXTERNAL: 3240 // External data will be accounted for by the owner of the buffer, 3241 // not this view. 3242 break; 3243 case MAPPED: 3244 info->objectsNonHeapElementsNormal += buffer.byteLength(); 3245 break; 3246 case WASM: 3247 if (!buffer.isDetached()) { 3248 info->objectsNonHeapElementsWasm += buffer.byteLength(); 3249 if (runtimeSizes) { 3250 MOZ_ASSERT(buffer.wasmMappedSize() >= buffer.byteLength()); 3251 runtimeSizes->wasmGuardPages += 3252 buffer.wasmMappedSize() - buffer.byteLength(); 3253 } 3254 } 3255 break; 3256 } 3257 } 3258 3259 /* static */ 3260 void ArrayBufferObject::finalize(JS::GCContext* gcx, JSObject* obj) { 3261 obj->as<ArrayBufferObject>().releaseData(gcx); 3262 } 3263 3264 /* static */ 3265 void ArrayBufferObject::copyData(ArrayBufferObject* toBuffer, size_t toIndex, 3266 ArrayBufferObject* fromBuffer, 3267 size_t fromIndex, size_t count) { 3268 MOZ_ASSERT(!toBuffer->isDetached()); 3269 MOZ_ASSERT(toBuffer->byteLength() >= count); 3270 MOZ_ASSERT(toBuffer->byteLength() >= toIndex + count); 3271 MOZ_ASSERT(!fromBuffer->isDetached()); 3272 MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex); 3273 MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex + count); 3274 3275 memcpy(toBuffer->dataPointer() + toIndex, 3276 fromBuffer->dataPointer() + fromIndex, count); 3277 } 3278 3279 template <class ArrayBufferType> 3280 /* static */ 3281 size_t ArrayBufferObject::objectMoved(JSObject* obj, JSObject* old) { 3282 auto& dst = obj->as<ArrayBufferType>(); 3283 const auto& src = old->as<ArrayBufferType>(); 3284 3285 #ifdef DEBUG 3286 // Check the data pointer is not inside the nursery, but take account of the 3287 // fact that inline data pointers for zero length buffers can point to the end 3288 // of a chunk which can abut the start of the nursery. 3289 if (src.byteLength() != 0 || (uintptr_t(src.dataPointer()) & gc::ChunkMask)) { 3290 Nursery& nursery = obj->runtimeFromMainThread()->gc.nursery(); 3291 MOZ_ASSERT(!nursery.isInside(src.dataPointer())); 3292 } 3293 #endif 3294 3295 // Fix up possible inline data pointer. 3296 if (src.hasInlineData()) { 3297 dst.setFixedSlot(DATA_SLOT, PrivateValue(dst.inlineDataPointer())); 3298 } 3299 3300 return 0; 3301 } 3302 3303 JSObject* ArrayBufferObject::firstView() { 3304 return getFixedSlot(FIRST_VIEW_SLOT).isObject() 3305 ? &getFixedSlot(FIRST_VIEW_SLOT).toObject() 3306 : nullptr; 3307 } 3308 3309 void ArrayBufferObject::setFirstView(ArrayBufferViewObject* view) { 3310 setFixedSlot(FIRST_VIEW_SLOT, ObjectOrNullValue(view)); 3311 } 3312 3313 bool ArrayBufferObject::addView(JSContext* cx, ArrayBufferViewObject* view) { 3314 if (!firstView()) { 3315 setFirstView(view); 3316 return true; 3317 } 3318 3319 return ObjectRealm::get(this).innerViews.get().addView(cx, this, view); 3320 } 3321 3322 #if defined(DEBUG) || defined(JS_JITSPEW) 3323 3324 template <typename KnownF, typename UnknownF> 3325 void BufferKindToString(ArrayBufferObject::BufferKind kind, KnownF known, 3326 UnknownF unknown) { 3327 switch (kind) { 3328 case ArrayBufferObject::BufferKind::INLINE_DATA: 3329 known("INLINE_DATA"); 3330 break; 3331 case ArrayBufferObject::BufferKind::MALLOCED_ARRAYBUFFER_CONTENTS_ARENA: 3332 known("MALLOCED_ARRAYBUFFER_CONTENTS_ARENA"); 3333 break; 3334 case ArrayBufferObject::BufferKind::NO_DATA: 3335 known("NO_DATA"); 3336 break; 3337 case ArrayBufferObject::BufferKind::USER_OWNED: 3338 known("USER_OWNED"); 3339 break; 3340 case ArrayBufferObject::BufferKind::WASM: 3341 known("WASM"); 3342 break; 3343 case ArrayBufferObject::BufferKind::MAPPED: 3344 known("MAPPED"); 3345 break; 3346 case ArrayBufferObject::BufferKind::EXTERNAL: 3347 known("EXTERNAL"); 3348 break; 3349 case ArrayBufferObject::BufferKind::MALLOCED_UNKNOWN_ARENA: 3350 known("MALLOCED_UNKNOWN_ARENA"); 3351 break; 3352 default: 3353 unknown(uint8_t(kind)); 3354 break; 3355 } 3356 } 3357 3358 template <typename KnownF, typename UnknownF> 3359 void ForEachArrayBufferFlag(uint32_t flags, KnownF known, UnknownF unknown) { 3360 for (uint32_t i = ArrayBufferObject::ArrayBufferFlags::BUFFER_KIND_MASK + 1; 3361 i; i = i << 1) { 3362 if (!(flags & i)) { 3363 continue; 3364 } 3365 switch (ArrayBufferObject::ArrayBufferFlags(flags & i)) { 3366 case ArrayBufferObject::ArrayBufferFlags::DETACHED: 3367 known("DETACHED"); 3368 break; 3369 case ArrayBufferObject::ArrayBufferFlags::FOR_ASMJS: 3370 known("FOR_ASMJS"); 3371 break; 3372 default: 3373 unknown(i); 3374 break; 3375 } 3376 } 3377 } 3378 3379 void ArrayBufferObject::dumpOwnFields(js::JSONPrinter& json) const { 3380 json.formatProperty("byteLength", "%zu", 3381 size_t(getFixedSlot(BYTE_LENGTH_SLOT).toPrivate())); 3382 3383 BufferKindToString( 3384 bufferKind(), 3385 [&](const char* name) { json.property("bufferKind", name); }, 3386 [&](uint8_t value) { 3387 json.formatProperty("bufferKind", "Unknown(%02x)", value); 3388 }); 3389 3390 json.beginInlineListProperty("flags"); 3391 ForEachArrayBufferFlag( 3392 flags(), [&](const char* name) { json.value("%s", name); }, 3393 [&](uint32_t value) { json.value("Unknown(%08x)", value); }); 3394 json.endInlineList(); 3395 3396 void* data = dataPointer(); 3397 if (data) { 3398 json.formatProperty("data", "0x%p", data); 3399 } else { 3400 json.nullProperty("data"); 3401 } 3402 } 3403 3404 void ArrayBufferObject::dumpOwnStringContent(js::GenericPrinter& out) const { 3405 out.printf("byteLength=%zu, ", 3406 size_t(getFixedSlot(BYTE_LENGTH_SLOT).toPrivate())); 3407 3408 BufferKindToString( 3409 bufferKind(), 3410 [&](const char* name) { out.printf("bufferKind=%s, ", name); }, 3411 [&](uint8_t value) { out.printf("bufferKind=Unknown(%02x), ", value); }); 3412 3413 out.printf("flags=["); 3414 bool first = true; 3415 ForEachArrayBufferFlag( 3416 flags(), 3417 [&](const char* name) { 3418 if (!first) { 3419 out.put(","); 3420 } 3421 first = false; 3422 out.put(name); 3423 }, 3424 [&](uint32_t value) { 3425 if (!first) { 3426 out.put(","); 3427 } 3428 first = false; 3429 out.printf("Unknown(%08x)", value); 3430 }); 3431 out.put("], "); 3432 3433 void* data = dataPointer(); 3434 if (data) { 3435 out.printf("data=0x%p", data); 3436 } else { 3437 out.put("data=null"); 3438 } 3439 } 3440 #endif 3441 3442 /* 3443 * InnerViewTable 3444 */ 3445 3446 inline bool InnerViewTable::Views::empty() { return views.empty(); } 3447 3448 inline bool InnerViewTable::Views::hasNurseryViews() { 3449 return firstNurseryView < views.length(); 3450 } 3451 3452 bool InnerViewTable::Views::addView(ArrayBufferViewObject* view) { 3453 // Add the view to the list, ensuring that all nursery views are at end. 3454 3455 if (!views.append(view)) { 3456 return false; 3457 } 3458 3459 if (!gc::IsInsideNursery(view)) { 3460 // Move tenured views before |firstNurseryView|. 3461 if (firstNurseryView != views.length() - 1) { 3462 std::swap(views[firstNurseryView], views.back()); 3463 } 3464 firstNurseryView++; 3465 } 3466 3467 check(); 3468 3469 return true; 3470 } 3471 3472 bool InnerViewTable::Views::sweepAfterMinorGC(JSTracer* trc) { 3473 return traceWeak(trc, firstNurseryView); 3474 } 3475 3476 bool InnerViewTable::Views::traceWeak(JSTracer* trc, size_t startIndex) { 3477 // Use |trc| to trace the view vector from |startIndex| to the end, removing 3478 // dead views and updating |firstNurseryView|. 3479 3480 size_t index = startIndex; 3481 bool sawNurseryView = false; 3482 views.mutableEraseIf( 3483 [&](auto& view) { 3484 if (!JS::GCPolicy<ViewVector::ElementType>::traceWeak(trc, &view)) { 3485 return true; 3486 } 3487 3488 if (!sawNurseryView && gc::IsInsideNursery(view)) { 3489 sawNurseryView = true; 3490 firstNurseryView = index; 3491 } 3492 3493 index++; 3494 return false; 3495 }, 3496 startIndex); 3497 3498 if (!sawNurseryView) { 3499 firstNurseryView = views.length(); 3500 } 3501 3502 check(); 3503 3504 return !views.empty(); 3505 } 3506 3507 inline void InnerViewTable::Views::check() { 3508 #ifdef DEBUG 3509 MOZ_ASSERT(firstNurseryView <= views.length()); 3510 if (views.length() < 100) { 3511 for (size_t i = 0; i < views.length(); i++) { 3512 MOZ_ASSERT(gc::IsInsideNursery(views[i]) == (i >= firstNurseryView)); 3513 } 3514 } 3515 #endif 3516 } 3517 3518 bool InnerViewTable::addView(JSContext* cx, ArrayBufferObject* buffer, 3519 ArrayBufferViewObject* view) { 3520 // ArrayBufferObject entries are only added when there are multiple views. 3521 MOZ_ASSERT(buffer->firstView()); 3522 MOZ_ASSERT(!gc::IsInsideNursery(buffer)); 3523 3524 // Ensure the buffer is present in the map, getting the list of views. 3525 auto ptr = map.lookupForAdd(buffer); 3526 if (!ptr && !map.add(ptr, buffer, Views(cx->zone()))) { 3527 ReportOutOfMemory(cx); 3528 return false; 3529 } 3530 Views& views = ptr->value(); 3531 3532 bool isNurseryView = gc::IsInsideNursery(view); 3533 bool hadNurseryViews = views.hasNurseryViews(); 3534 if (!views.addView(view)) { 3535 ReportOutOfMemory(cx); 3536 return false; 3537 } 3538 3539 // If we added the first nursery view, add the buffer to the list of buffers 3540 // which have nursery views. 3541 if (isNurseryView && !hadNurseryViews && nurseryKeysValid) { 3542 #ifdef DEBUG 3543 if (nurseryKeys.length() < 100) { 3544 for (const auto& key : nurseryKeys) { 3545 MOZ_ASSERT(key != buffer); 3546 } 3547 } 3548 #endif 3549 if (!nurseryKeys.append(buffer)) { 3550 nurseryKeysValid = false; 3551 } 3552 } 3553 3554 return true; 3555 } 3556 3557 InnerViewTable::ViewVector* InnerViewTable::maybeViewsUnbarriered( 3558 ArrayBufferObject* buffer) { 3559 auto ptr = map.lookup(buffer); 3560 if (ptr) { 3561 return &ptr->value().views; 3562 } 3563 return nullptr; 3564 } 3565 3566 void InnerViewTable::removeViews(ArrayBufferObject* buffer) { 3567 auto ptr = map.lookup(buffer); 3568 MOZ_ASSERT(ptr); 3569 3570 map.remove(ptr); 3571 } 3572 3573 bool InnerViewTable::traceWeak(JSTracer* trc) { 3574 nurseryKeys.traceWeak(trc); 3575 map.traceWeak(trc); 3576 return true; 3577 } 3578 3579 void InnerViewTable::sweepAfterMinorGC(JSTracer* trc) { 3580 MOZ_ASSERT(needsSweepAfterMinorGC()); 3581 3582 NurseryKeysVector keys; 3583 bool valid = true; 3584 std::swap(nurseryKeys, keys); 3585 std::swap(nurseryKeysValid, valid); 3586 3587 // Use nursery keys vector if possible. 3588 if (valid) { 3589 for (ArrayBufferObject* buffer : keys) { 3590 MOZ_ASSERT(!gc::IsInsideNursery(buffer)); 3591 auto ptr = map.lookup(buffer); 3592 if (ptr && !sweepViewsAfterMinorGC(trc, buffer, ptr->value())) { 3593 map.remove(ptr); 3594 } 3595 } 3596 return; 3597 } 3598 3599 // Otherwise look at every map entry. 3600 for (ArrayBufferViewMap::Enum e(map); !e.empty(); e.popFront()) { 3601 MOZ_ASSERT(!gc::IsInsideNursery(e.front().key())); 3602 if (!sweepViewsAfterMinorGC(trc, e.front().key(), e.front().value())) { 3603 e.removeFront(); 3604 } 3605 } 3606 } 3607 3608 bool InnerViewTable::sweepViewsAfterMinorGC(JSTracer* trc, 3609 ArrayBufferObject* buffer, 3610 Views& views) { 3611 if (!views.sweepAfterMinorGC(trc)) { 3612 return false; // No more views. 3613 } 3614 3615 if (views.hasNurseryViews() && !nurseryKeys.append(buffer)) { 3616 nurseryKeysValid = false; 3617 } 3618 3619 return true; 3620 } 3621 3622 size_t InnerViewTable::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) { 3623 size_t vectorSize = 0; 3624 for (auto r = map.all(); !r.empty(); r.popFront()) { 3625 vectorSize += r.front().value().views.sizeOfExcludingThis(mallocSizeOf); 3626 } 3627 3628 return vectorSize + map.shallowSizeOfExcludingThis(mallocSizeOf) + 3629 nurseryKeys.sizeOfExcludingThis(mallocSizeOf); 3630 } 3631 3632 template <> 3633 bool JSObject::is<js::ArrayBufferObjectMaybeShared>() const { 3634 return is<ArrayBufferObject>() || is<SharedArrayBufferObject>(); 3635 } 3636 3637 JS_PUBLIC_API size_t JS::GetArrayBufferByteLength(JSObject* obj) { 3638 ArrayBufferObject* aobj = obj->maybeUnwrapAs<ArrayBufferObject>(); 3639 return aobj ? aobj->byteLength() : 0; 3640 } 3641 3642 JS_PUBLIC_API uint8_t* JS::GetArrayBufferData(JSObject* obj, 3643 bool* isSharedMemory, 3644 const JS::AutoRequireNoGC&) { 3645 ArrayBufferObject* aobj = obj->maybeUnwrapIf<ArrayBufferObject>(); 3646 if (!aobj) { 3647 return nullptr; 3648 } 3649 *isSharedMemory = false; 3650 return aobj->dataPointer(); 3651 } 3652 3653 static ArrayBufferObject* UnwrapOrReportArrayBuffer( 3654 JSContext* cx, JS::Handle<JSObject*> maybeArrayBuffer) { 3655 JSObject* obj = CheckedUnwrapStatic(maybeArrayBuffer); 3656 if (!obj) { 3657 ReportAccessDenied(cx); 3658 return nullptr; 3659 } 3660 3661 if (!obj->is<ArrayBufferObject>()) { 3662 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3663 JSMSG_ARRAYBUFFER_REQUIRED); 3664 return nullptr; 3665 } 3666 3667 return &obj->as<ArrayBufferObject>(); 3668 } 3669 3670 JS_PUBLIC_API bool JS::DetachArrayBuffer(JSContext* cx, HandleObject obj) { 3671 AssertHeapIsIdle(); 3672 CHECK_THREAD(cx); 3673 cx->check(obj); 3674 3675 Rooted<ArrayBufferObject*> unwrappedBuffer( 3676 cx, UnwrapOrReportArrayBuffer(cx, obj)); 3677 if (!unwrappedBuffer) { 3678 return false; 3679 } 3680 3681 if (unwrappedBuffer->hasDefinedDetachKey()) { 3682 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3683 JSMSG_WASM_NO_TRANSFER); 3684 return false; 3685 } 3686 if (unwrappedBuffer->isImmutable()) { 3687 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3688 JSMSG_ARRAYBUFFER_IMMUTABLE); 3689 return false; 3690 } 3691 if (unwrappedBuffer->isLengthPinned()) { 3692 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3693 JSMSG_ARRAYBUFFER_LENGTH_PINNED); 3694 return false; 3695 } 3696 3697 AutoRealm ar(cx, unwrappedBuffer); 3698 ArrayBufferObject::detach(cx, unwrappedBuffer); 3699 return true; 3700 } 3701 3702 JS_PUBLIC_API bool JS::HasDefinedArrayBufferDetachKey(JSContext* cx, 3703 HandleObject obj, 3704 bool* isDefined) { 3705 Rooted<ArrayBufferObject*> unwrappedBuffer( 3706 cx, UnwrapOrReportArrayBuffer(cx, obj)); 3707 if (!unwrappedBuffer) { 3708 return false; 3709 } 3710 3711 *isDefined = unwrappedBuffer->hasDefinedDetachKey(); 3712 return true; 3713 } 3714 3715 JS_PUBLIC_API bool JS::IsDetachedArrayBufferObject(JSObject* obj) { 3716 ArrayBufferObject* aobj = obj->maybeUnwrapIf<ArrayBufferObject>(); 3717 if (!aobj) { 3718 return false; 3719 } 3720 3721 return aobj->isDetached(); 3722 } 3723 3724 JS_PUBLIC_API JSObject* JS::NewArrayBuffer(JSContext* cx, size_t nbytes) { 3725 AssertHeapIsIdle(); 3726 CHECK_THREAD(cx); 3727 3728 return ArrayBufferObject::createZeroed(cx, nbytes); 3729 } 3730 3731 JS_PUBLIC_API JSObject* JS::NewArrayBufferWithContents( 3732 JSContext* cx, size_t nbytes, 3733 mozilla::UniquePtr<void, JS::FreePolicy> contents) { 3734 auto* result = NewArrayBufferWithContents( 3735 cx, nbytes, contents.get(), 3736 JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory); 3737 if (result) { 3738 // If and only if an ArrayBuffer is successfully created, ownership of 3739 // |contents| is transferred to the new ArrayBuffer. 3740 (void)contents.release(); 3741 } 3742 return result; 3743 } 3744 3745 JS_PUBLIC_API JSObject* JS::NewArrayBufferWithContents( 3746 JSContext* cx, size_t nbytes, void* data, NewArrayBufferOutOfMemory) { 3747 AssertHeapIsIdle(); 3748 CHECK_THREAD(cx); 3749 MOZ_ASSERT_IF(!data, nbytes == 0); 3750 3751 if (!data) { 3752 // Don't pass nulled contents to |createForContents|. 3753 return ArrayBufferObject::createZeroed(cx, 0); 3754 } 3755 3756 using BufferContents = ArrayBufferObject::BufferContents; 3757 3758 BufferContents contents = BufferContents::createMallocedUnknownArena(data); 3759 return ArrayBufferObject::createForContents(cx, nbytes, contents); 3760 } 3761 3762 JS_PUBLIC_API JSObject* JS::CopyArrayBuffer(JSContext* cx, 3763 Handle<JSObject*> arrayBuffer) { 3764 AssertHeapIsIdle(); 3765 CHECK_THREAD(cx); 3766 3767 MOZ_ASSERT(arrayBuffer != nullptr); 3768 3769 Rooted<ArrayBufferObject*> unwrappedSource( 3770 cx, UnwrapOrReportArrayBuffer(cx, arrayBuffer)); 3771 if (!unwrappedSource) { 3772 return nullptr; 3773 } 3774 3775 if (unwrappedSource->isDetached()) { 3776 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3777 JSMSG_TYPED_ARRAY_DETACHED); 3778 return nullptr; 3779 } 3780 3781 return FixedLengthArrayBufferObject::copy(cx, unwrappedSource->byteLength(), 3782 unwrappedSource); 3783 } 3784 3785 JS_PUBLIC_API JSObject* JS::NewExternalArrayBuffer( 3786 JSContext* cx, size_t nbytes, 3787 mozilla::UniquePtr<void, JS::BufferContentsDeleter> contents) { 3788 AssertHeapIsIdle(); 3789 CHECK_THREAD(cx); 3790 3791 MOZ_ASSERT(contents); 3792 3793 using BufferContents = ArrayBufferObject::BufferContents; 3794 3795 BufferContents bufferContents = BufferContents::createExternal( 3796 contents.get(), contents.get_deleter().freeFunc(), 3797 contents.get_deleter().userData()); 3798 auto* result = 3799 ArrayBufferObject::createForContents(cx, nbytes, bufferContents); 3800 if (result) { 3801 // If and only if an ArrayBuffer is successfully created, ownership of 3802 // |contents| is transferred to the new ArrayBuffer. 3803 (void)contents.release(); 3804 } 3805 return result; 3806 } 3807 3808 JS_PUBLIC_API JSObject* JS::NewArrayBufferWithUserOwnedContents(JSContext* cx, 3809 size_t nbytes, 3810 void* data) { 3811 AssertHeapIsIdle(); 3812 CHECK_THREAD(cx); 3813 3814 MOZ_ASSERT(data); 3815 3816 using BufferContents = ArrayBufferObject::BufferContents; 3817 3818 BufferContents contents = BufferContents::createUserOwned(data); 3819 return ArrayBufferObject::createForContents(cx, nbytes, contents); 3820 } 3821 3822 JS_PUBLIC_API bool JS::IsArrayBufferObject(JSObject* obj) { 3823 return obj->canUnwrapAs<ArrayBufferObject>(); 3824 } 3825 3826 JS_PUBLIC_API bool JS::ArrayBufferHasData(JSObject* obj) { 3827 return !obj->unwrapAs<ArrayBufferObject>().isDetached(); 3828 } 3829 3830 JS_PUBLIC_API JSObject* JS::UnwrapArrayBuffer(JSObject* obj) { 3831 return obj->maybeUnwrapIf<ArrayBufferObject>(); 3832 } 3833 3834 JS_PUBLIC_API JSObject* JS::UnwrapSharedArrayBuffer(JSObject* obj) { 3835 return obj->maybeUnwrapIf<SharedArrayBufferObject>(); 3836 } 3837 3838 JS_PUBLIC_API void* JS::StealArrayBufferContents(JSContext* cx, 3839 HandleObject obj) { 3840 AssertHeapIsIdle(); 3841 CHECK_THREAD(cx); 3842 cx->check(obj); 3843 3844 Rooted<ArrayBufferObject*> unwrappedBuffer( 3845 cx, UnwrapOrReportArrayBuffer(cx, obj)); 3846 if (!unwrappedBuffer) { 3847 return nullptr; 3848 } 3849 3850 if (unwrappedBuffer->isDetached()) { 3851 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3852 JSMSG_TYPED_ARRAY_DETACHED); 3853 return nullptr; 3854 } 3855 if (unwrappedBuffer->isImmutable()) { 3856 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3857 JSMSG_ARRAYBUFFER_IMMUTABLE); 3858 return nullptr; 3859 } 3860 if (unwrappedBuffer->isLengthPinned()) { 3861 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3862 JSMSG_ARRAYBUFFER_LENGTH_PINNED); 3863 return nullptr; 3864 } 3865 3866 if (unwrappedBuffer->hasDefinedDetachKey()) { 3867 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3868 JSMSG_WASM_NO_TRANSFER); 3869 return nullptr; 3870 } 3871 3872 AutoRealm ar(cx, unwrappedBuffer); 3873 return ArrayBufferObject::stealMallocedContents(cx, unwrappedBuffer); 3874 } 3875 3876 JS_PUBLIC_API JSObject* JS::NewMappedArrayBufferWithContents(JSContext* cx, 3877 size_t nbytes, 3878 void* data) { 3879 AssertHeapIsIdle(); 3880 CHECK_THREAD(cx); 3881 3882 MOZ_ASSERT(data); 3883 3884 using BufferContents = ArrayBufferObject::BufferContents; 3885 3886 BufferContents contents = BufferContents::createMapped(data); 3887 return ArrayBufferObject::createForContents(cx, nbytes, contents); 3888 } 3889 3890 JS_PUBLIC_API void* JS::CreateMappedArrayBufferContents(int fd, size_t offset, 3891 size_t length) { 3892 return ArrayBufferObject::createMappedContents(fd, offset, length).data(); 3893 } 3894 3895 JS_PUBLIC_API void JS::ReleaseMappedArrayBufferContents(void* contents, 3896 size_t length) { 3897 gc::DeallocateMappedContent(contents, length); 3898 } 3899 3900 JS_PUBLIC_API bool JS::IsMappedArrayBufferObject(JSObject* obj) { 3901 ArrayBufferObject* aobj = obj->maybeUnwrapIf<ArrayBufferObject>(); 3902 if (!aobj) { 3903 return false; 3904 } 3905 3906 return aobj->isMapped(); 3907 } 3908 3909 JS_PUBLIC_API JSObject* JS::GetObjectAsArrayBuffer(JSObject* obj, 3910 size_t* length, 3911 uint8_t** data) { 3912 ArrayBufferObject* aobj = obj->maybeUnwrapIf<ArrayBufferObject>(); 3913 if (!aobj) { 3914 return nullptr; 3915 } 3916 3917 *length = aobj->byteLength(); 3918 *data = aobj->dataPointer(); 3919 3920 return aobj; 3921 } 3922 3923 JS_PUBLIC_API void JS::GetArrayBufferLengthAndData(JSObject* obj, 3924 size_t* length, 3925 bool* isSharedMemory, 3926 uint8_t** data) { 3927 auto& aobj = obj->as<ArrayBufferObject>(); 3928 *length = aobj.byteLength(); 3929 *data = aobj.dataPointer(); 3930 *isSharedMemory = false; 3931 } 3932 3933 const JSClass* const JS::ArrayBuffer::FixedLengthUnsharedClass = 3934 &FixedLengthArrayBufferObject::class_; 3935 const JSClass* const JS::ArrayBuffer::ResizableUnsharedClass = 3936 &ResizableArrayBufferObject::class_; 3937 const JSClass* const JS::ArrayBuffer::ImmutableUnsharedClass = 3938 &ImmutableArrayBufferObject::class_; 3939 const JSClass* const JS::ArrayBuffer::FixedLengthSharedClass = 3940 &FixedLengthSharedArrayBufferObject::class_; 3941 const JSClass* const JS::ArrayBuffer::GrowableSharedClass = 3942 &GrowableSharedArrayBufferObject::class_; 3943 3944 /* static */ JS::ArrayBuffer JS::ArrayBuffer::create(JSContext* cx, 3945 size_t nbytes) { 3946 AssertHeapIsIdle(); 3947 CHECK_THREAD(cx); 3948 return JS::ArrayBuffer(ArrayBufferObject::createZeroed(cx, nbytes)); 3949 } 3950 3951 mozilla::Span<uint8_t> JS::ArrayBuffer::getData( 3952 bool* isSharedMemory, const JS::AutoRequireNoGC& nogc) { 3953 auto* buffer = obj->maybeUnwrapAs<ArrayBufferObjectMaybeShared>(); 3954 if (!buffer) { 3955 return nullptr; 3956 } 3957 size_t length = buffer->byteLength(); 3958 if (buffer->is<SharedArrayBufferObject>()) { 3959 *isSharedMemory = true; 3960 return {buffer->dataPointerEither().unwrap(), length}; 3961 } 3962 *isSharedMemory = false; 3963 return {buffer->as<ArrayBufferObject>().dataPointer(), length}; 3964 }; 3965 3966 JS::ArrayBuffer JS::ArrayBuffer::unwrap(JSObject* maybeWrapped) { 3967 if (!maybeWrapped) { 3968 return JS::ArrayBuffer(nullptr); 3969 } 3970 auto* ab = maybeWrapped->maybeUnwrapIf<ArrayBufferObjectMaybeShared>(); 3971 return fromObject(ab); 3972 } 3973 3974 bool JS::ArrayBufferCopyData(JSContext* cx, Handle<JSObject*> toBlock, 3975 size_t toIndex, Handle<JSObject*> fromBlock, 3976 size_t fromIndex, size_t count) { 3977 Rooted<ArrayBufferObjectMaybeShared*> unwrappedToBlock( 3978 cx, toBlock->maybeUnwrapIf<ArrayBufferObjectMaybeShared>()); 3979 if (!unwrappedToBlock) { 3980 ReportAccessDenied(cx); 3981 return false; 3982 } 3983 3984 Rooted<ArrayBufferObjectMaybeShared*> unwrappedFromBlock( 3985 cx, fromBlock->maybeUnwrapIf<ArrayBufferObjectMaybeShared>()); 3986 if (!unwrappedFromBlock) { 3987 ReportAccessDenied(cx); 3988 return false; 3989 } 3990 3991 // Verify that lengths still make sense and throw otherwise. 3992 if (toIndex + count < toIndex || // size_t overflow 3993 fromIndex + count < fromIndex || // size_t overflow 3994 toIndex + count > unwrappedToBlock->byteLength() || 3995 fromIndex + count > unwrappedFromBlock->byteLength()) { 3996 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3997 JSMSG_ARRAYBUFFER_COPY_RANGE); 3998 return false; 3999 } 4000 4001 MOZ_ASSERT(!unwrappedToBlock->isDetached()); 4002 MOZ_ASSERT(!unwrappedToBlock->isImmutable()); 4003 MOZ_ASSERT(!unwrappedFromBlock->isDetached()); 4004 4005 // If both are array buffers, can use ArrayBufferCopyData 4006 if (unwrappedToBlock->is<ArrayBufferObject>() && 4007 unwrappedFromBlock->is<ArrayBufferObject>()) { 4008 Rooted<ArrayBufferObject*> toArray( 4009 cx, &unwrappedToBlock->as<ArrayBufferObject>()); 4010 Rooted<ArrayBufferObject*> fromArray( 4011 cx, &unwrappedFromBlock->as<ArrayBufferObject>()); 4012 ArrayBufferObject::copyData(toArray, toIndex, fromArray, fromIndex, count); 4013 return true; 4014 } 4015 4016 Rooted<ArrayBufferObjectMaybeShared*> toArray( 4017 cx, &unwrappedToBlock->as<ArrayBufferObjectMaybeShared>()); 4018 Rooted<ArrayBufferObjectMaybeShared*> fromArray( 4019 cx, &unwrappedFromBlock->as<ArrayBufferObjectMaybeShared>()); 4020 SharedArrayBufferObject::copyData(toArray, toIndex, fromArray, fromIndex, 4021 count); 4022 4023 return true; 4024 } 4025 4026 // https://tc39.es/ecma262/#sec-clonearraybuffer 4027 // We only support the case where cloneConstructor is %ArrayBuffer%. Note, 4028 // this means that cloning a SharedArrayBuffer will produce an ArrayBuffer 4029 JSObject* JS::ArrayBufferClone(JSContext* cx, Handle<JSObject*> srcBuffer, 4030 size_t srcByteOffset, size_t srcLength) { 4031 MOZ_ASSERT(srcBuffer->is<ArrayBufferObjectMaybeShared>()); 4032 4033 // 2. (reordered) If IsDetachedBuffer(srcBuffer) is true, throw a TypeError 4034 // exception. 4035 if (IsDetachedArrayBufferObject(srcBuffer)) { 4036 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 4037 JSMSG_TYPED_ARRAY_DETACHED); 4038 return nullptr; 4039 } 4040 4041 // 1. Let targetBuffer be ? AllocateArrayBuffer(cloneConstructor, srcLength). 4042 JS::RootedObject targetBuffer(cx, JS::NewArrayBuffer(cx, srcLength)); 4043 if (!targetBuffer) { 4044 return nullptr; 4045 } 4046 4047 // 3. Let srcBlock be srcBuffer.[[ArrayBufferData]]. 4048 // 4. Let targetBlock be targetBuffer.[[ArrayBufferData]]. 4049 // 5. Perform CopyDataBlockBytes(targetBlock, 0, srcBlock, srcByteOffset, 4050 // srcLength). 4051 if (!ArrayBufferCopyData(cx, targetBuffer, 0, srcBuffer, srcByteOffset, 4052 srcLength)) { 4053 return nullptr; 4054 } 4055 4056 // 6. Return targetBuffer. 4057 return targetBuffer; 4058 }