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/. */
7 #include "vm/ArrayBufferObject-inl.h"
8 #include "vm/ArrayBufferObject.h"
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"
18 #include <algorithm> // std::max, std::min
19 #include <memory> // std::uninitialized_copy_n
21 #if !defined(XP_WIN) && !defined(__wasi__)
22 # include <sys/mman.h>
24 #include <tuple> // std::tuple
25 #include <type_traits>
27 # include <valgrind/memcheck.h>
33 #include "gc/Barrier.h"
34 #include "gc/Memory.h"
35 #include "js/ArrayBuffer.h"
36 #include "js/Conversions.h"
37 #include "js/experimental/TypedData.h" // JS_IsArrayBufferViewObject
38 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
39 #include "js/MemoryMetrics.h"
40 #include "js/PropertySpec.h"
41 #include "js/SharedArrayBuffer.h"
42 #include "js/Wrapper.h"
43 #include "util/WindowsWrapper.h"
44 #include "vm/GlobalObject.h"
45 #include "vm/JSContext.h"
46 #include "vm/JSObject.h"
47 #include "vm/SharedArrayObject.h"
48 #include "vm/Warnings.h" // js::WarnNumberASCII
49 #include "wasm/WasmConstants.h"
50 #include "wasm/WasmLog.h"
51 #include "wasm/WasmMemory.h"
52 #include "wasm/WasmModuleTypes.h"
53 #include "wasm/WasmProcess.h"
55 #include "gc/GCContext-inl.h"
56 #include "gc/Marking-inl.h"
57 #include "vm/NativeObject-inl.h"
58 #include "vm/Realm-inl.h" // js::AutoRealm
62 using js::wasm::IndexType
;
63 using js::wasm::Pages
;
64 using mozilla::Atomic
;
65 using mozilla::CheckedInt
;
66 using mozilla::DebugOnly
;
68 using mozilla::Nothing
;
73 // Wasm allows large amounts of memory to be reserved at a time. On 64-bit
74 // platforms (with "huge memories") we reserve around 4GB of virtual address
75 // space for every wasm memory; on 32-bit platforms we usually do not, but users
76 // often initialize memories in the hundreds of megabytes.
78 // If too many wasm memories remain live, we run up against system resource
79 // exhaustion (address space or number of memory map descriptors) - see bug
80 // 1068684, bug 1073934, bug 1517412, bug 1502733 for details. The limiting case
81 // seems to be Android on ARM64, where the per-process address space is limited
82 // to 4TB (39 bits) by the organization of the page tables. An earlier problem
83 // was Windows Vista Home 64-bit, where the per-process address space is limited
84 // to 8TB (40 bits). And 32-bit platforms only have 4GB of address space anyway.
86 // Thus we track the amount of memory reserved for wasm, and set a limit per
87 // process. We trigger GC work when we approach the limit and we throw an OOM
88 // error if the per-process limit is exceeded. The limit (WasmReservedBytesMax)
89 // is specific to architecture, OS, and OS configuration.
91 // Since the WasmReservedBytesMax limit is not generally accounted for by
92 // any existing GC-trigger heuristics, we need an extra heuristic for triggering
93 // GCs when the caller is allocating memories rapidly without other garbage
94 // (e.g. bug 1773225). Thus, once the reserved memory crosses the threshold
95 // WasmReservedBytesStartTriggering, we start triggering GCs every
96 // WasmReservedBytesPerTrigger bytes. Once we reach
97 // WasmReservedBytesStartSyncFullGC bytes reserved, we perform expensive
98 // non-incremental full GCs as a last-ditch effort to avoid unnecessary failure.
99 // Once we reach WasmReservedBytesMax, we perform further full GCs before giving
102 // (History: The original implementation only tracked the number of "huge
103 // memories" allocated by WASM, but this was found to be insufficient because
104 // 32-bit platforms have similar resource exhaustion issues. We now track
105 // reserved bytes directly.)
107 // (We also used to reserve significantly more than 4GB for huge memories, but
108 // this was reduced in bug 1442544.)
110 // ASAN and TSAN use a ton of vmem for bookkeeping leaving a lot less for the
111 // program so use a lower limit.
112 #if defined(MOZ_TSAN) || defined(MOZ_ASAN)
113 static const uint64_t WasmMemAsanOverhead
= 2;
115 static const uint64_t WasmMemAsanOverhead
= 1;
118 // WasmReservedStartTriggering + WasmReservedPerTrigger must be well below
119 // WasmReservedStartSyncFullGC in order to provide enough time for incremental
122 #if defined(JS_CODEGEN_ARM64) && defined(ANDROID)
124 static const uint64_t WasmReservedBytesMax
=
125 75 * wasm::HugeMappedSize
/ WasmMemAsanOverhead
;
126 static const uint64_t WasmReservedBytesStartTriggering
=
127 15 * wasm::HugeMappedSize
;
128 static const uint64_t WasmReservedBytesStartSyncFullGC
=
129 WasmReservedBytesMax
- 15 * wasm::HugeMappedSize
;
130 static const uint64_t WasmReservedBytesPerTrigger
= 15 * wasm::HugeMappedSize
;
132 #elif defined(WASM_SUPPORTS_HUGE_MEMORY)
134 static const uint64_t WasmReservedBytesMax
=
135 1000 * wasm::HugeMappedSize
/ WasmMemAsanOverhead
;
136 static const uint64_t WasmReservedBytesStartTriggering
=
137 100 * wasm::HugeMappedSize
;
138 static const uint64_t WasmReservedBytesStartSyncFullGC
=
139 WasmReservedBytesMax
- 100 * wasm::HugeMappedSize
;
140 static const uint64_t WasmReservedBytesPerTrigger
= 100 * wasm::HugeMappedSize
;
142 #else // 32-bit (and weird 64-bit platforms without huge memory)
144 static const uint64_t GiB
= 1024 * 1024 * 1024;
146 static const uint64_t WasmReservedBytesMax
=
147 (4 * GiB
) / 2 / WasmMemAsanOverhead
;
148 static const uint64_t WasmReservedBytesStartTriggering
= (4 * GiB
) / 8;
149 static const uint64_t WasmReservedBytesStartSyncFullGC
=
150 WasmReservedBytesMax
- (4 * GiB
) / 8;
151 static const uint64_t WasmReservedBytesPerTrigger
= (4 * GiB
) / 8;
155 // The total number of bytes reserved for wasm memories.
156 static Atomic
<uint64_t, mozilla::ReleaseAcquire
> wasmReservedBytes(0);
157 // The number of bytes of wasm memory reserved since the last GC trigger.
158 static Atomic
<uint64_t, mozilla::ReleaseAcquire
> wasmReservedBytesSinceLast(0);
160 uint64_t js::WasmReservedBytes() { return wasmReservedBytes
; }
162 [[nodiscard
]] static bool CheckArrayBufferTooLarge(JSContext
* cx
,
164 // Refuse to allocate too large buffers.
165 if (MOZ_UNLIKELY(nbytes
> ArrayBufferObject::MaxByteLength
)) {
166 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
167 JSMSG_BAD_ARRAY_LENGTH
);
174 void* js::MapBufferMemory(wasm::IndexType t
, size_t mappedSize
,
175 size_t initialCommittedSize
) {
176 MOZ_ASSERT(mappedSize
% gc::SystemPageSize() == 0);
177 MOZ_ASSERT(initialCommittedSize
% gc::SystemPageSize() == 0);
178 MOZ_ASSERT(initialCommittedSize
<= mappedSize
);
180 auto failed
= mozilla::MakeScopeExit(
181 [&] { wasmReservedBytes
-= uint64_t(mappedSize
); });
182 wasmReservedBytes
+= uint64_t(mappedSize
);
184 // Test >= to guard against the case where multiple extant runtimes
186 if (wasmReservedBytes
>= WasmReservedBytesMax
) {
187 if (OnLargeAllocationFailure
) {
188 OnLargeAllocationFailure();
190 if (wasmReservedBytes
>= WasmReservedBytesMax
) {
196 void* data
= VirtualAlloc(nullptr, mappedSize
, MEM_RESERVE
, PAGE_NOACCESS
);
201 if (!VirtualAlloc(data
, initialCommittedSize
, MEM_COMMIT
, PAGE_READWRITE
)) {
202 VirtualFree(data
, 0, MEM_RELEASE
);
205 #elif defined(__wasi__)
206 void* data
= nullptr;
207 if (int err
= posix_memalign(&data
, gc::SystemPageSize(), mappedSize
)) {
208 MOZ_ASSERT(err
== ENOMEM
);
212 memset(data
, 0, mappedSize
);
213 #else // !XP_WIN && !__wasi__
215 MozTaggedAnonymousMmap(nullptr, mappedSize
, PROT_NONE
,
216 MAP_PRIVATE
| MAP_ANON
, -1, 0, "wasm-reserved");
217 if (data
== MAP_FAILED
) {
221 // Note we will waste a page on zero-sized memories here
222 if (mprotect(data
, initialCommittedSize
, PROT_READ
| PROT_WRITE
)) {
223 munmap(data
, mappedSize
);
226 #endif // !XP_WIN && !__wasi__
228 #if defined(MOZ_VALGRIND) && \
229 defined(VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE)
230 VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE(
231 (unsigned char*)data
+ initialCommittedSize
,
232 mappedSize
- initialCommittedSize
);
239 bool js::CommitBufferMemory(void* dataEnd
, size_t delta
) {
241 MOZ_ASSERT(delta
% gc::SystemPageSize() == 0);
244 if (!VirtualAlloc(dataEnd
, delta
, MEM_COMMIT
, PAGE_READWRITE
)) {
247 #elif defined(__wasi__)
248 // posix_memalign'd memory is already committed
251 if (mprotect(dataEnd
, delta
, PROT_READ
| PROT_WRITE
)) {
256 #if defined(MOZ_VALGRIND) && \
257 defined(VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE)
258 VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE((unsigned char*)dataEnd
, delta
);
264 bool js::ExtendBufferMapping(void* dataPointer
, size_t mappedSize
,
265 size_t newMappedSize
) {
266 MOZ_ASSERT(mappedSize
% gc::SystemPageSize() == 0);
267 MOZ_ASSERT(newMappedSize
% gc::SystemPageSize() == 0);
268 MOZ_ASSERT(newMappedSize
>= mappedSize
);
271 void* mappedEnd
= (char*)dataPointer
+ mappedSize
;
272 uint32_t delta
= newMappedSize
- mappedSize
;
273 if (!VirtualAlloc(mappedEnd
, delta
, MEM_RESERVE
, PAGE_NOACCESS
)) {
277 #elif defined(__wasi__)
279 #elif defined(XP_LINUX)
280 // Note this will not move memory (no MREMAP_MAYMOVE specified)
281 if (MAP_FAILED
== mremap(dataPointer
, mappedSize
, newMappedSize
, 0)) {
286 // No mechanism for remapping on MacOS and other Unices. Luckily
287 // shouldn't need it here as most of these are 64-bit.
292 void js::UnmapBufferMemory(wasm::IndexType t
, void* base
, size_t mappedSize
) {
293 MOZ_ASSERT(mappedSize
% gc::SystemPageSize() == 0);
296 VirtualFree(base
, 0, MEM_RELEASE
);
297 #elif defined(__wasi__)
300 munmap(base
, mappedSize
);
303 #if defined(MOZ_VALGRIND) && \
304 defined(VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE)
305 VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE((unsigned char*)base
,
309 // Untrack reserved memory *after* releasing memory -- otherwise, a race
310 // condition could enable the creation of unlimited buffers.
311 wasmReservedBytes
-= uint64_t(mappedSize
);
317 * This class holds the underlying raw buffer that the TypedArrayObject classes
318 * access. It can be created explicitly and passed to a TypedArrayObject, or
319 * can be created implicitly by constructing a TypedArrayObject with a size.
323 * ArrayBufferObject (base)
326 static const JSClassOps ArrayBufferObjectClassOps
= {
327 nullptr, // addProperty
328 nullptr, // delProperty
329 nullptr, // enumerate
330 nullptr, // newEnumerate
332 nullptr, // mayResolve
333 ArrayBufferObject::finalize
, // finalize
335 nullptr, // construct
339 static const JSFunctionSpec arraybuffer_functions
[] = {
340 JS_FN("isView", ArrayBufferObject::fun_isView
, 1, 0),
344 static const JSPropertySpec arraybuffer_properties
[] = {
345 JS_SELF_HOSTED_SYM_GET(species
, "$ArrayBufferSpecies", 0),
349 static const JSFunctionSpec arraybuffer_proto_functions
[] = {
350 JS_SELF_HOSTED_FN("slice", "ArrayBufferSlice", 2, 0),
351 JS_FN("transfer", ArrayBufferObject::transfer
, 0, 0),
352 JS_FN("transferToFixedLength", ArrayBufferObject::transferToFixedLength
, 0,
357 static const JSPropertySpec arraybuffer_proto_properties
[] = {
358 JS_PSG("byteLength", ArrayBufferObject::byteLengthGetter
, 0),
360 JS_PSG("maxByteLength", ArrayBufferObject::maxByteLengthGetter
, 0),
361 JS_PSG("resizable", ArrayBufferObject::resizableGetter
, 0),
363 JS_PSG("detached", ArrayBufferObject::detachedGetter
, 0),
364 JS_STRING_SYM_PS(toStringTag
, "ArrayBuffer", JSPROP_READONLY
),
368 static JSObject
* CreateArrayBufferPrototype(JSContext
* cx
, JSProtoKey key
) {
369 return GlobalObject::createBlankPrototype(cx
, cx
->global(),
370 &ArrayBufferObject::protoClass_
);
373 static const ClassSpec ArrayBufferObjectClassSpec
= {
374 GenericCreateConstructor
<ArrayBufferObject::class_constructor
, 1,
375 gc::AllocKind::FUNCTION
>,
376 CreateArrayBufferPrototype
,
377 arraybuffer_functions
,
378 arraybuffer_properties
,
379 arraybuffer_proto_functions
,
380 arraybuffer_proto_properties
,
383 static const ClassExtension FixedLengthArrayBufferObjectClassExtension
= {
384 ArrayBufferObject::objectMoved
<
385 FixedLengthArrayBufferObject
>, // objectMovedOp
388 static const ClassExtension ResizableArrayBufferObjectClassExtension
= {
389 ArrayBufferObject::objectMoved
<
390 ResizableArrayBufferObject
>, // objectMovedOp
393 const JSClass
ArrayBufferObject::protoClass_
= {
394 "ArrayBuffer.prototype",
395 JSCLASS_HAS_CACHED_PROTO(JSProto_ArrayBuffer
),
397 &ArrayBufferObjectClassSpec
,
400 const JSClass
FixedLengthArrayBufferObject::class_
= {
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
,
411 const JSClass
ResizableArrayBufferObject::class_
= {
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
,
422 static bool IsArrayBuffer(HandleValue v
) {
423 return v
.isObject() && v
.toObject().is
<ArrayBufferObject
>();
426 MOZ_ALWAYS_INLINE
bool ArrayBufferObject::byteLengthGetterImpl(
427 JSContext
* cx
, const CallArgs
& args
) {
428 MOZ_ASSERT(IsArrayBuffer(args
.thisv()));
429 auto* buffer
= &args
.thisv().toObject().as
<ArrayBufferObject
>();
430 args
.rval().setNumber(buffer
->byteLength());
434 bool ArrayBufferObject::byteLengthGetter(JSContext
* cx
, unsigned argc
,
436 CallArgs args
= CallArgsFromVp(argc
, vp
);
437 return CallNonGenericMethod
<IsArrayBuffer
, byteLengthGetterImpl
>(cx
, args
);
441 * ArrayBufferCopyAndDetach ( arrayBuffer, newLength, preserveResizability )
443 * https://tc39.es/proposal-arraybuffer-transfer/#sec-arraybuffercopyanddetach
445 static ArrayBufferObject
* ArrayBufferCopyAndDetach(
446 JSContext
* cx
, Handle
<ArrayBufferObject
*> arrayBuffer
,
447 Handle
<Value
> newLength
) {
448 // Steps 1-2. (Not applicable in our implementation.)
451 uint64_t newByteLength
;
452 if (newLength
.isUndefined()) {
454 newByteLength
= arrayBuffer
->byteLength();
457 if (!ToIndex(cx
, newLength
, &newByteLength
)) {
463 if (arrayBuffer
->isDetached()) {
464 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
465 JSMSG_TYPED_ARRAY_DETACHED
);
469 // Steps 6-7. (Not applicable in our implementation.)
470 // We don't yet support resizable ArrayBuffers (bug 1670026).
473 if (arrayBuffer
->hasDefinedDetachKey()) {
474 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
475 JSMSG_WASM_NO_TRANSFER
);
481 // 25.1.2.1 AllocateArrayBuffer, step 2.
482 // 6.2.9.1 CreateByteDataBlock, step 2.
483 if (!CheckArrayBufferTooLarge(cx
, newByteLength
)) {
486 return ArrayBufferObject::copyAndDetach(cx
, size_t(newByteLength
),
492 * get ArrayBuffer.prototype.maxByteLength
494 * https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.maxbytelength
496 bool ArrayBufferObject::maxByteLengthGetterImpl(JSContext
* cx
,
497 const CallArgs
& args
) {
498 MOZ_ASSERT(IsArrayBuffer(args
.thisv()));
500 auto* buffer
= &args
.thisv().toObject().as
<ArrayBufferObject
>();
503 size_t maxByteLength
;
504 if (buffer
->isResizable()) {
505 maxByteLength
= buffer
->as
<ResizableArrayBufferObject
>().maxByteLength();
507 maxByteLength
= buffer
->byteLength();
509 MOZ_ASSERT_IF(buffer
->isDetached(), maxByteLength
== 0);
512 args
.rval().setNumber(maxByteLength
);
517 * get ArrayBuffer.prototype.maxByteLength
519 * https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.maxbytelength
521 bool ArrayBufferObject::maxByteLengthGetter(JSContext
* cx
, unsigned argc
,
524 CallArgs args
= CallArgsFromVp(argc
, vp
);
525 return CallNonGenericMethod
<IsArrayBuffer
, maxByteLengthGetterImpl
>(cx
, args
);
529 * get ArrayBuffer.prototype.resizable
531 * https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.resizable
533 bool ArrayBufferObject::resizableGetterImpl(JSContext
* cx
,
534 const CallArgs
& args
) {
535 MOZ_ASSERT(IsArrayBuffer(args
.thisv()));
538 auto* buffer
= &args
.thisv().toObject().as
<ArrayBufferObject
>();
539 args
.rval().setBoolean(buffer
->isResizable());
544 * get ArrayBuffer.prototype.resizable
546 * https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.resizable
548 bool ArrayBufferObject::resizableGetter(JSContext
* cx
, unsigned argc
,
551 CallArgs args
= CallArgsFromVp(argc
, vp
);
552 return CallNonGenericMethod
<IsArrayBuffer
, resizableGetterImpl
>(cx
, args
);
557 * get ArrayBuffer.prototype.detached
559 * https://tc39.es/proposal-arraybuffer-transfer/#sec-get-arraybuffer.prototype.detached
561 bool ArrayBufferObject::detachedGetterImpl(JSContext
* cx
,
562 const CallArgs
& args
) {
563 MOZ_ASSERT(IsArrayBuffer(args
.thisv()));
566 auto* buffer
= &args
.thisv().toObject().as
<ArrayBufferObject
>();
567 args
.rval().setBoolean(buffer
->isDetached());
572 * get ArrayBuffer.prototype.detached
574 * https://tc39.es/proposal-arraybuffer-transfer/#sec-get-arraybuffer.prototype.detached
576 bool ArrayBufferObject::detachedGetter(JSContext
* cx
, unsigned argc
,
579 CallArgs args
= CallArgsFromVp(argc
, vp
);
580 return CallNonGenericMethod
<IsArrayBuffer
, detachedGetterImpl
>(cx
, args
);
584 * ArrayBuffer.prototype.transfer ( [ newLength ] )
586 * https://tc39.es/proposal-arraybuffer-transfer/#sec-arraybuffer.prototype.transfer
588 bool ArrayBufferObject::transferImpl(JSContext
* cx
, const CallArgs
& args
) {
589 MOZ_ASSERT(IsArrayBuffer(args
.thisv()));
592 Rooted
<ArrayBufferObject
*> buffer(
593 cx
, &args
.thisv().toObject().as
<ArrayBufferObject
>());
594 auto* newBuffer
= ArrayBufferCopyAndDetach(cx
, buffer
, args
.get(0));
599 args
.rval().setObject(*newBuffer
);
604 * ArrayBuffer.prototype.transfer ( [ newLength ] )
606 * https://tc39.es/proposal-arraybuffer-transfer/#sec-arraybuffer.prototype.transfer
608 bool ArrayBufferObject::transfer(JSContext
* cx
, unsigned argc
, Value
* vp
) {
609 CallArgs args
= CallArgsFromVp(argc
, vp
);
610 return CallNonGenericMethod
<IsArrayBuffer
, transferImpl
>(cx
, args
);
614 * ArrayBuffer.prototype.transferToFixedLength ( [ newLength ] )
616 * https://tc39.es/proposal-arraybuffer-transfer/#sec-arraybuffer.prototype.transfertofixedlength
618 bool ArrayBufferObject::transferToFixedLengthImpl(JSContext
* cx
,
619 const CallArgs
& args
) {
620 MOZ_ASSERT(IsArrayBuffer(args
.thisv()));
623 Rooted
<ArrayBufferObject
*> buffer(
624 cx
, &args
.thisv().toObject().as
<ArrayBufferObject
>());
625 auto* newBuffer
= ArrayBufferCopyAndDetach(cx
, buffer
, args
.get(0));
630 args
.rval().setObject(*newBuffer
);
635 * ArrayBuffer.prototype.transferToFixedLength ( [ newLength ] )
637 * https://tc39.es/proposal-arraybuffer-transfer/#sec-arraybuffer.prototype.transfertofixedlength
639 bool ArrayBufferObject::transferToFixedLength(JSContext
* cx
, unsigned argc
,
641 CallArgs args
= CallArgsFromVp(argc
, vp
);
642 return CallNonGenericMethod
<IsArrayBuffer
, transferToFixedLengthImpl
>(cx
,
647 * ArrayBuffer.isView(obj); ES6 (Dec 2013 draft) 24.1.3.1
649 bool ArrayBufferObject::fun_isView(JSContext
* cx
, unsigned argc
, Value
* vp
) {
650 CallArgs args
= CallArgsFromVp(argc
, vp
);
651 args
.rval().setBoolean(args
.get(0).isObject() &&
652 JS_IsArrayBufferViewObject(&args
.get(0).toObject()));
656 // ES2017 draft 24.1.2.1
657 bool ArrayBufferObject::class_constructor(JSContext
* cx
, unsigned argc
,
659 CallArgs args
= CallArgsFromVp(argc
, vp
);
662 if (!ThrowIfNotConstructing(cx
, args
, "ArrayBuffer")) {
668 if (!ToIndex(cx
, args
.get(0), &byteLength
)) {
672 mozilla::Maybe
<uint64_t> maxByteLength
;
675 if (cx
->realm()->creationOptions().getArrayBufferResizableEnabled()) {
676 // Inline call to GetArrayBufferMaxByteLengthOption.
677 if (args
.get(1).isObject()) {
678 Rooted
<JSObject
*> options(cx
, &args
[1].toObject());
680 Rooted
<Value
> val(cx
);
681 if (!GetProperty(cx
, options
, options
, cx
->names().maxByteLength
, &val
)) {
684 if (!val
.isUndefined()) {
685 uint64_t maxByteLengthInt
;
686 if (!ToIndex(cx
, val
, &maxByteLengthInt
)) {
690 // AllocateArrayBuffer, step 3.a.
691 if (byteLength
> maxByteLengthInt
) {
692 JS_ReportErrorNumberASCII(
693 cx
, GetErrorMessage
, nullptr,
694 JSMSG_ARRAYBUFFER_LENGTH_LARGER_THAN_MAXIMUM
);
697 maxByteLength
= mozilla::Some(maxByteLengthInt
);
703 // Step 4 (Inlined 25.1.2.1 AllocateArrayBuffer).
704 // 25.1.2.1, step 1 (Inlined 10.1.13 OrdinaryCreateFromConstructor).
705 RootedObject
proto(cx
);
706 if (!GetPrototypeFromBuiltinConstructor(cx
, args
, JSProto_ArrayBuffer
,
711 // 25.1.2.1, step 5 (Inlined 6.2.9.1 CreateByteDataBlock, step 2).
712 if (!CheckArrayBufferTooLarge(cx
, byteLength
)) {
717 // 25.1.2.1, step 8.a.
718 if (!CheckArrayBufferTooLarge(cx
, *maxByteLength
)) {
722 auto* bufobj
= ResizableArrayBufferObject::createZeroed(
723 cx
, byteLength
, *maxByteLength
, proto
);
727 args
.rval().setObject(*bufobj
);
731 // 25.1.2.1, steps 1 and 4-9.
732 JSObject
* bufobj
= createZeroed(cx
, byteLength
, proto
);
736 args
.rval().setObject(*bufobj
);
740 using ArrayBufferContents
= UniquePtr
<uint8_t[], JS::FreePolicy
>;
742 static ArrayBufferContents
AllocateUninitializedArrayBufferContents(
743 JSContext
* cx
, size_t nbytes
) {
744 // First attempt a normal allocation.
746 cx
->maybe_pod_arena_malloc
<uint8_t>(js::ArrayBufferContentsArena
, nbytes
);
747 if (MOZ_UNLIKELY(!p
)) {
748 // Otherwise attempt a large allocation, calling the
749 // large-allocation-failure callback if necessary.
750 p
= static_cast<uint8_t*>(cx
->runtime()->onOutOfMemoryCanGC(
751 js::AllocFunction::Malloc
, js::ArrayBufferContentsArena
, nbytes
));
753 ReportOutOfMemory(cx
);
757 return ArrayBufferContents(p
);
760 static ArrayBufferContents
AllocateArrayBufferContents(JSContext
* cx
,
762 // First attempt a normal allocation.
764 cx
->maybe_pod_arena_calloc
<uint8_t>(js::ArrayBufferContentsArena
, nbytes
);
765 if (MOZ_UNLIKELY(!p
)) {
766 // Otherwise attempt a large allocation, calling the
767 // large-allocation-failure callback if necessary.
768 p
= static_cast<uint8_t*>(cx
->runtime()->onOutOfMemoryCanGC(
769 js::AllocFunction::Calloc
, js::ArrayBufferContentsArena
, nbytes
));
771 ReportOutOfMemory(cx
);
775 return ArrayBufferContents(p
);
778 static ArrayBufferContents
ReallocateArrayBufferContents(JSContext
* cx
,
782 // First attempt a normal reallocation.
783 uint8_t* p
= cx
->maybe_pod_arena_realloc
<uint8_t>(
784 js::ArrayBufferContentsArena
, old
, oldSize
, newSize
);
785 if (MOZ_UNLIKELY(!p
)) {
786 // Otherwise attempt a large allocation, calling the
787 // large-allocation-failure callback if necessary.
788 p
= static_cast<uint8_t*>(cx
->runtime()->onOutOfMemoryCanGC(
789 js::AllocFunction::Realloc
, js::ArrayBufferContentsArena
, newSize
,
792 ReportOutOfMemory(cx
);
796 return ArrayBufferContents(p
);
799 static ArrayBufferContents
NewCopiedBufferContents(
800 JSContext
* cx
, Handle
<ArrayBufferObject
*> buffer
) {
801 ArrayBufferContents dataCopy
=
802 AllocateUninitializedArrayBufferContents(cx
, buffer
->byteLength());
804 if (auto count
= buffer
->byteLength()) {
805 memcpy(dataCopy
.get(), buffer
->dataPointer(), count
);
812 void ArrayBufferObject::detach(JSContext
* cx
,
813 Handle
<ArrayBufferObject
*> buffer
) {
815 MOZ_ASSERT(!buffer
->isPreparedForAsmJS());
816 MOZ_ASSERT(!buffer
->isLengthPinned());
818 // Update all views of the buffer to account for the buffer having been
819 // detached, and clear the buffer's data and list of views.
821 // Typed object buffers are not exposed and cannot be detached.
823 auto& innerViews
= ObjectRealm::get(buffer
).innerViews
.get();
824 if (InnerViewTable::ViewVector
* views
=
825 innerViews
.maybeViewsUnbarriered(buffer
)) {
826 for (size_t i
= 0; i
< views
->length(); i
++) {
827 JSObject
* view
= (*views
)[i
];
828 view
->as
<ArrayBufferViewObject
>().notifyBufferDetached();
830 innerViews
.removeViews(buffer
);
832 if (JSObject
* view
= buffer
->firstView()) {
833 view
->as
<ArrayBufferViewObject
>().notifyBufferDetached();
834 buffer
->setFirstView(nullptr);
837 if (buffer
->dataPointer()) {
838 buffer
->releaseData(cx
->gcContext());
839 buffer
->setDataPointer(BufferContents::createNoData());
842 buffer
->setByteLength(0);
843 buffer
->setIsDetached();
844 if (buffer
->isResizable()) {
845 buffer
->as
<ResizableArrayBufferObject
>().setMaxByteLength(0);
849 /* clang-format off */
851 * [SMDOC] WASM Linear Memory structure
853 * Wasm Raw Buf Linear Memory Structure
855 * The linear heap in Wasm is an mmaped array buffer. Several constants manage
858 * - byteLength - the wasm-visible current length of the buffer in
859 * bytes. Accesses in the range [0, byteLength] succeed. May only increase.
861 * - boundsCheckLimit - the size against which we perform bounds checks. The
862 * value of this depends on the bounds checking strategy chosen for the array
863 * buffer and the specific bounds checking semantics. For asm.js code and
864 * for wasm code running with explicit bounds checking, it is the always the
865 * same as the byteLength. For wasm code using the huge-memory trick, it is
866 * always wasm::GuardSize smaller than mappedSize.
868 * See also "Linear memory addresses and bounds checking" in
869 * wasm/WasmMemory.cpp.
871 * See also WasmMemoryObject::boundsCheckLimit().
873 * - sourceMaxSize - the optional declared limit on how far byteLength can grow
874 * in pages. This is the unmodified maximum size from the source module or
875 * JS-API invocation. This may not be representable in byte lengths, nor
876 * feasible for a module to actually grow to due to implementation limits.
877 * It is used for correct linking checks and js-types reflection.
879 * - clampedMaxSize - the maximum size on how far the byteLength can grow in
880 * pages. This value respects implementation limits and is always
881 * representable as a byte length. Every memory has a clampedMaxSize, even if
882 * no maximum was specified in source. When a memory has no sourceMaxSize,
883 * the clampedMaxSize will be the maximum amount of memory that can be grown
884 * to while still respecting implementation limits.
886 * - mappedSize - the actual mmapped size. Access in the range [0, mappedSize]
887 * will either succeed, or be handled by the wasm signal handlers. If
888 * sourceMaxSize is present at initialization, then we attempt to map the
889 * whole clampedMaxSize. Otherwise we only map the region needed for the
892 * The below diagram shows the layout of the wasm heap. The wasm-visible portion
893 * of the heap starts at 0. There is one extra page prior to the start of the
894 * wasm heap which contains the WasmArrayRawBuffer struct at its end (i.e. right
895 * before the start of the WASM heap).
898 * \ ArrayBufferObject::dataPointer()
901 * ______|_|______________________________________________________
902 * |______|_|______________|___________________|___________________|
903 * 0 byteLength clampedMaxSize mappedSize
905 * \_______________________/
907 * \_____________________________________/
909 * \______________________________________________________________/
912 * Invariants on byteLength, clampedMaxSize, and mappedSize:
913 * - byteLength only increases
914 * - 0 <= byteLength <= clampedMaxSize <= mappedSize
915 * - if sourceMaxSize is not specified, mappedSize may grow.
916 * It is otherwise constant.
917 * - initialLength <= clampedMaxSize <= sourceMaxSize (if present)
918 * - clampedMaxSize <= wasm::MaxMemoryPages()
920 * Invariants on boundsCheckLimit:
921 * - for wasm code with the huge-memory trick,
922 * clampedMaxSize <= boundsCheckLimit <= mappedSize
923 * - for asm.js code or wasm with explicit bounds checking,
924 * byteLength == boundsCheckLimit <= clampedMaxSize
925 * - on ARM, boundsCheckLimit must be a valid ARM immediate.
926 * - if sourceMaxSize is not specified, boundsCheckLimit may grow as
927 * mappedSize grows. They are otherwise constant.
929 * NOTE: For asm.js on 32-bit platforms and on all platforms when running with
930 * explicit bounds checking, we guarantee that
932 * byteLength == maxSize == boundsCheckLimit == mappedSize
934 * That is, signal handlers will not be invoked.
936 * The region between byteLength and mappedSize is the SLOP - an area where we use
937 * signal handlers to catch things that slip by bounds checks. Logically it has
940 * - from byteLength to boundsCheckLimit - this part of the SLOP serves to catch
941 * accesses to memory we have reserved but not yet grown into. This allows us
942 * to grow memory up to max (when present) without having to patch/update the
945 * - from boundsCheckLimit to mappedSize - this part of the SLOP allows us to
946 * bounds check against base pointers and fold some constant offsets inside
947 * loads. This enables better Bounds Check Elimination. See "Linear memory
948 * addresses and bounds checking" in wasm/WasmMemory.cpp.
951 /* clang-format on */
953 [[nodiscard
]] bool WasmArrayRawBuffer::growToPagesInPlace(Pages newPages
) {
954 size_t newSize
= newPages
.byteLength();
955 size_t oldSize
= byteLength();
957 MOZ_ASSERT(newSize
>= oldSize
);
958 MOZ_ASSERT(newPages
<= clampedMaxPages());
959 MOZ_ASSERT(newSize
<= mappedSize());
961 size_t delta
= newSize
- oldSize
;
962 MOZ_ASSERT(delta
% wasm::PageSize
== 0);
964 uint8_t* dataEnd
= dataPointer() + oldSize
;
965 MOZ_ASSERT(uintptr_t(dataEnd
) % gc::SystemPageSize() == 0);
967 if (delta
&& !CommitBufferMemory(dataEnd
, delta
)) {
976 bool WasmArrayRawBuffer::extendMappedSize(Pages maxPages
) {
977 size_t newMappedSize
= wasm::ComputeMappedSize(maxPages
);
978 MOZ_ASSERT(mappedSize_
<= newMappedSize
);
979 if (mappedSize_
== newMappedSize
) {
983 if (!ExtendBufferMapping(dataPointer(), mappedSize_
, newMappedSize
)) {
987 mappedSize_
= newMappedSize
;
991 void WasmArrayRawBuffer::tryGrowMaxPagesInPlace(Pages deltaMaxPages
) {
992 Pages newMaxPages
= clampedMaxPages_
;
994 DebugOnly
<bool> valid
= newMaxPages
.checkedIncrement(deltaMaxPages
);
995 // Caller must ensure increment does not overflow or increase over the
996 // specified maximum pages.
998 MOZ_ASSERT_IF(sourceMaxPages_
.isSome(), newMaxPages
<= *sourceMaxPages_
);
1000 if (!extendMappedSize(newMaxPages
)) {
1003 clampedMaxPages_
= newMaxPages
;
1006 void WasmArrayRawBuffer::discard(size_t byteOffset
, size_t byteLen
) {
1007 uint8_t* memBase
= dataPointer();
1009 // The caller is responsible for ensuring these conditions are met; see this
1010 // function's comment in ArrayBufferObject.h.
1011 MOZ_ASSERT(byteOffset
% wasm::PageSize
== 0);
1012 MOZ_ASSERT(byteLen
% wasm::PageSize
== 0);
1013 MOZ_ASSERT(wasm::MemoryBoundsCheck(uint64_t(byteOffset
), uint64_t(byteLen
),
1016 // Discarding zero bytes "succeeds" with no effect.
1021 void* addr
= memBase
+ uintptr_t(byteOffset
);
1023 // On POSIX-ish platforms, we discard memory by overwriting previously-mapped
1024 // pages with freshly-mapped pages (which are all zeroed). The operating
1025 // system recognizes this and decreases the process RSS, and eventually
1026 // collects the abandoned physical pages.
1028 // On Windows, committing over previously-committed pages has no effect, and
1029 // the memory must be explicitly decommitted first. This is not the same as an
1030 // munmap; the address space is still reserved.
1033 if (!VirtualFree(addr
, byteLen
, MEM_DECOMMIT
)) {
1034 MOZ_CRASH("wasm discard: failed to decommit memory");
1036 if (!VirtualAlloc(addr
, byteLen
, MEM_COMMIT
, PAGE_READWRITE
)) {
1037 MOZ_CRASH("wasm discard: decommitted memory but failed to recommit");
1039 #elif defined(__wasi__)
1040 memset(addr
, 0, byteLen
);
1042 void* data
= MozTaggedAnonymousMmap(addr
, byteLen
, PROT_READ
| PROT_WRITE
,
1043 MAP_PRIVATE
| MAP_ANON
| MAP_FIXED
, -1, 0,
1045 if (data
== MAP_FAILED
) {
1046 MOZ_CRASH("failed to discard wasm memory; memory mappings may be broken");
1052 WasmArrayRawBuffer
* WasmArrayRawBuffer::AllocateWasm(
1053 IndexType indexType
, Pages initialPages
, Pages clampedMaxPages
,
1054 const Maybe
<Pages
>& sourceMaxPages
, const Maybe
<size_t>& mapped
) {
1055 // Prior code has asserted that initial pages is within our implementation
1056 // limits (wasm::MaxMemoryPages) and we can assume it is a valid size_t.
1057 MOZ_ASSERT(initialPages
.hasByteLength());
1058 size_t numBytes
= initialPages
.byteLength();
1060 // If there is a specified maximum, attempt to map the whole range for
1061 // clampedMaxPages. Or else map only what's required for initialPages.
1062 Pages initialMappedPages
=
1063 sourceMaxPages
.isSome() ? clampedMaxPages
: initialPages
;
1065 // Use an override mapped size, or else compute the mapped size from
1066 // initialMappedPages.
1068 mapped
.isSome() ? *mapped
: wasm::ComputeMappedSize(initialMappedPages
);
1070 MOZ_RELEASE_ASSERT(mappedSize
<= SIZE_MAX
- gc::SystemPageSize());
1071 MOZ_RELEASE_ASSERT(numBytes
<= SIZE_MAX
- gc::SystemPageSize());
1072 MOZ_RELEASE_ASSERT(initialPages
<= clampedMaxPages
);
1073 MOZ_ASSERT(numBytes
% gc::SystemPageSize() == 0);
1074 MOZ_ASSERT(mappedSize
% gc::SystemPageSize() == 0);
1076 uint64_t mappedSizeWithHeader
= mappedSize
+ gc::SystemPageSize();
1077 uint64_t numBytesWithHeader
= numBytes
+ gc::SystemPageSize();
1079 void* data
= MapBufferMemory(indexType
, (size_t)mappedSizeWithHeader
,
1080 (size_t)numBytesWithHeader
);
1085 uint8_t* base
= reinterpret_cast<uint8_t*>(data
) + gc::SystemPageSize();
1086 uint8_t* header
= base
- sizeof(WasmArrayRawBuffer
);
1088 auto rawBuf
= new (header
) WasmArrayRawBuffer(
1089 indexType
, base
, clampedMaxPages
, sourceMaxPages
, mappedSize
, numBytes
);
1094 void WasmArrayRawBuffer::Release(void* mem
) {
1095 WasmArrayRawBuffer
* header
=
1096 (WasmArrayRawBuffer
*)((uint8_t*)mem
- sizeof(WasmArrayRawBuffer
));
1098 MOZ_RELEASE_ASSERT(header
->mappedSize() <= SIZE_MAX
- gc::SystemPageSize());
1099 size_t mappedSizeWithHeader
= header
->mappedSize() + gc::SystemPageSize();
1101 static_assert(std::is_trivially_destructible_v
<WasmArrayRawBuffer
>,
1102 "no need to call the destructor");
1104 UnmapBufferMemory(header
->indexType(), header
->basePointer(),
1105 mappedSizeWithHeader
);
1108 WasmArrayRawBuffer
* ArrayBufferObject::BufferContents::wasmBuffer() const {
1109 MOZ_RELEASE_ASSERT(kind_
== WASM
);
1110 return (WasmArrayRawBuffer
*)(data_
- sizeof(WasmArrayRawBuffer
));
1113 template <typename ObjT
, typename RawbufT
>
1114 static ArrayBufferObjectMaybeShared
* CreateSpecificWasmBuffer(
1115 JSContext
* cx
, const wasm::MemoryDesc
& memory
) {
1116 bool useHugeMemory
= wasm::IsHugeMemoryEnabled(memory
.indexType());
1117 Pages initialPages
= memory
.initialPages();
1118 Maybe
<Pages
> sourceMaxPages
= memory
.maximumPages();
1119 Pages clampedMaxPages
= wasm::ClampedMaxPages(
1120 memory
.indexType(), initialPages
, sourceMaxPages
, useHugeMemory
);
1122 Maybe
<size_t> mappedSize
;
1123 #ifdef WASM_SUPPORTS_HUGE_MEMORY
1124 // Override the mapped size if we are using huge memory. If we are not, then
1125 // it will be calculated by the raw buffer we are using.
1126 if (useHugeMemory
) {
1127 mappedSize
= Some(wasm::HugeMappedSize
);
1132 RawbufT::AllocateWasm(memory
.limits
.indexType
, initialPages
,
1133 clampedMaxPages
, sourceMaxPages
, mappedSize
);
1135 if (useHugeMemory
) {
1136 WarnNumberASCII(cx
, JSMSG_WASM_HUGE_MEMORY_FAILED
);
1137 if (cx
->isExceptionPending()) {
1138 cx
->clearPendingException();
1141 ReportOutOfMemory(cx
);
1145 // If we fail, and have a sourceMaxPages, try to reserve the biggest
1146 // chunk in the range [initialPages, clampedMaxPages) using log backoff.
1147 if (!sourceMaxPages
) {
1148 wasm::Log(cx
, "new Memory({initial=%" PRIu64
" pages}) failed",
1149 initialPages
.value());
1150 ReportOutOfMemory(cx
);
1154 uint64_t cur
= clampedMaxPages
.value() / 2;
1155 for (; Pages(cur
) > initialPages
; cur
/= 2) {
1156 buffer
= RawbufT::AllocateWasm(memory
.limits
.indexType
, initialPages
,
1157 Pages(cur
), sourceMaxPages
, mappedSize
);
1164 wasm::Log(cx
, "new Memory({initial=%" PRIu64
" pages}) failed",
1165 initialPages
.value());
1166 ReportOutOfMemory(cx
);
1170 // Try to grow our chunk as much as possible.
1171 for (size_t d
= cur
/ 2; d
>= 1; d
/= 2) {
1172 buffer
->tryGrowMaxPagesInPlace(Pages(d
));
1176 // ObjT::createFromNewRawBuffer assumes ownership of |buffer| even in case
1178 Rooted
<ArrayBufferObjectMaybeShared
*> object(
1179 cx
, ObjT::createFromNewRawBuffer(cx
, buffer
, initialPages
.byteLength()));
1184 // See MaximumLiveMappedBuffers comment above.
1185 if (wasmReservedBytes
> WasmReservedBytesStartSyncFullGC
) {
1186 JS::PrepareForFullGC(cx
);
1187 JS::NonIncrementalGC(cx
, JS::GCOptions::Normal
,
1188 JS::GCReason::TOO_MUCH_WASM_MEMORY
);
1189 wasmReservedBytesSinceLast
= 0;
1190 } else if (wasmReservedBytes
> WasmReservedBytesStartTriggering
) {
1191 wasmReservedBytesSinceLast
+= uint64_t(buffer
->mappedSize());
1192 if (wasmReservedBytesSinceLast
> WasmReservedBytesPerTrigger
) {
1193 (void)cx
->runtime()->gc
.triggerGC(JS::GCReason::TOO_MUCH_WASM_MEMORY
);
1194 wasmReservedBytesSinceLast
= 0;
1197 wasmReservedBytesSinceLast
= 0;
1200 // Log the result with details on the memory allocation
1201 if (sourceMaxPages
) {
1202 if (useHugeMemory
) {
1204 "new Memory({initial:%" PRIu64
" pages, maximum:%" PRIu64
1205 " pages}) succeeded",
1206 initialPages
.value(), sourceMaxPages
->value());
1209 "new Memory({initial:%" PRIu64
" pages, maximum:%" PRIu64
1210 " pages}) succeeded "
1211 "with internal maximum of %" PRIu64
" pages",
1212 initialPages
.value(), sourceMaxPages
->value(),
1213 object
->wasmClampedMaxPages().value());
1216 wasm::Log(cx
, "new Memory({initial:%" PRIu64
" pages}) succeeded",
1217 initialPages
.value());
1223 ArrayBufferObjectMaybeShared
* js::CreateWasmBuffer(
1224 JSContext
* cx
, const wasm::MemoryDesc
& memory
) {
1225 MOZ_RELEASE_ASSERT(memory
.initialPages() <=
1226 wasm::MaxMemoryPages(memory
.indexType()));
1227 MOZ_RELEASE_ASSERT(cx
->wasm().haveSignalHandlers
);
1229 if (memory
.isShared()) {
1230 if (!cx
->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled()) {
1231 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1232 JSMSG_WASM_NO_SHMEM_LINK
);
1235 return CreateSpecificWasmBuffer
<SharedArrayBufferObject
,
1236 WasmSharedArrayRawBuffer
>(cx
, memory
);
1238 return CreateSpecificWasmBuffer
<ArrayBufferObject
, WasmArrayRawBuffer
>(
1242 bool ArrayBufferObject::prepareForAsmJS() {
1243 MOZ_ASSERT(byteLength() % wasm::PageSize
== 0,
1244 "prior size checking should have guaranteed page-size multiple");
1245 MOZ_ASSERT(byteLength() > 0,
1246 "prior size checking should have excluded empty buffers");
1247 MOZ_ASSERT(!isResizable(),
1248 "prior checks should have excluded resizable buffers");
1250 switch (bufferKind()) {
1251 case MALLOCED_ARRAYBUFFER_CONTENTS_ARENA
:
1252 case MALLOCED_UNKNOWN_ARENA
:
1255 // It's okay if this uselessly sets the flag a second time.
1256 setIsPreparedForAsmJS();
1261 wasm::PageSize
> FixedLengthArrayBufferObject::MaxInlineBytes
,
1262 "inline data must be too small to be a page size multiple");
1263 MOZ_ASSERT_UNREACHABLE(
1264 "inline-data buffers should be implicitly excluded by size checks");
1268 MOZ_ASSERT_UNREACHABLE(
1269 "size checking should have excluded detached or empty buffers");
1272 // asm.js code and associated buffers are potentially long-lived. Yet a
1273 // buffer of user-owned data *must* be detached by the user before the
1274 // user-owned data is disposed. No caller wants to use a user-owned
1275 // ArrayBuffer with asm.js, so just don't support this and avoid a mess of
1278 // wasm buffers can be detached at any time.
1280 MOZ_ASSERT(!isPreparedForAsmJS());
1284 MOZ_ASSERT_UNREACHABLE("non-exhaustive kind-handling switch?");
1288 ArrayBufferObject::BufferContents
ArrayBufferObject::createMappedContents(
1289 int fd
, size_t offset
, size_t length
) {
1291 gc::AllocateMappedContent(fd
, offset
, length
, ARRAY_BUFFER_ALIGNMENT
);
1292 return BufferContents::createMapped(data
);
1295 uint8_t* FixedLengthArrayBufferObject::inlineDataPointer() const {
1296 return static_cast<uint8_t*>(fixedData(JSCLASS_RESERVED_SLOTS(&class_
)));
1299 uint8_t* ResizableArrayBufferObject::inlineDataPointer() const {
1300 return static_cast<uint8_t*>(fixedData(JSCLASS_RESERVED_SLOTS(&class_
)));
1303 uint8_t* ArrayBufferObject::dataPointer() const {
1304 return static_cast<uint8_t*>(getFixedSlot(DATA_SLOT
).toPrivate());
1307 SharedMem
<uint8_t*> ArrayBufferObject::dataPointerShared() const {
1308 return SharedMem
<uint8_t*>::unshared(getFixedSlot(DATA_SLOT
).toPrivate());
1311 ArrayBufferObject::FreeInfo
* ArrayBufferObject::freeInfo() const {
1312 MOZ_ASSERT(isExternal());
1313 MOZ_ASSERT(!isResizable());
1314 auto* data
= as
<FixedLengthArrayBufferObject
>().inlineDataPointer();
1315 return reinterpret_cast<FreeInfo
*>(data
);
1318 void ArrayBufferObject::releaseData(JS::GCContext
* gcx
) {
1319 switch (bufferKind()) {
1321 // Inline data doesn't require releasing.
1323 case MALLOCED_ARRAYBUFFER_CONTENTS_ARENA
:
1324 case MALLOCED_UNKNOWN_ARENA
:
1325 gcx
->free_(this, dataPointer(), associatedBytes(),
1326 MemoryUse::ArrayBufferContents
);
1329 // There's nothing to release if there's no data.
1330 MOZ_ASSERT(dataPointer() == nullptr);
1333 // User-owned data is released by, well, the user.
1336 gc::DeallocateMappedContent(dataPointer(), byteLength());
1337 gcx
->removeCellMemory(this, associatedBytes(),
1338 MemoryUse::ArrayBufferContents
);
1341 WasmArrayRawBuffer::Release(dataPointer());
1342 gcx
->removeCellMemory(this, byteLength(), MemoryUse::ArrayBufferContents
);
1345 MOZ_ASSERT(freeInfo()->freeFunc
);
1347 // The analyzer can't know for sure whether the embedder-supplied
1348 // free function will GC. We give the analyzer a hint here.
1349 // (Doing a GC in the free function is considered a programmer
1351 JS::AutoSuppressGCAnalysis nogc
;
1352 freeInfo()->freeFunc(dataPointer(), freeInfo()->freeUserData
);
1358 void ArrayBufferObject::setDataPointer(BufferContents contents
) {
1359 setFixedSlot(DATA_SLOT
, PrivateValue(contents
.data()));
1360 setFlags((flags() & ~KIND_MASK
) | contents
.kind());
1363 auto info
= freeInfo();
1364 info
->freeFunc
= contents
.freeFunc();
1365 info
->freeUserData
= contents
.freeUserData();
1369 size_t ArrayBufferObject::byteLength() const {
1370 return size_t(getFixedSlot(BYTE_LENGTH_SLOT
).toPrivate());
1373 inline size_t ArrayBufferObject::associatedBytes() const {
1375 if (isResizable()) {
1376 return as
<ResizableArrayBufferObject
>().maxByteLength();
1378 return byteLength();
1381 return RoundUp(byteLength(), js::gc::SystemPageSize());
1383 MOZ_CRASH("Unexpected buffer kind");
1386 void ArrayBufferObject::setByteLength(size_t length
) {
1387 MOZ_ASSERT(length
<= ArrayBufferObject::MaxByteLength
);
1388 setFixedSlot(BYTE_LENGTH_SLOT
, PrivateValue(length
));
1391 size_t ArrayBufferObject::wasmMappedSize() const {
1393 return contents().wasmBuffer()->mappedSize();
1395 return byteLength();
1398 IndexType
ArrayBufferObject::wasmIndexType() const {
1400 return contents().wasmBuffer()->indexType();
1402 MOZ_ASSERT(isPreparedForAsmJS());
1403 return wasm::IndexType::I32
;
1406 Pages
ArrayBufferObject::wasmPages() const {
1408 return contents().wasmBuffer()->pages();
1410 MOZ_ASSERT(isPreparedForAsmJS());
1411 return Pages::fromByteLengthExact(byteLength());
1414 Pages
ArrayBufferObject::wasmClampedMaxPages() const {
1416 return contents().wasmBuffer()->clampedMaxPages();
1418 MOZ_ASSERT(isPreparedForAsmJS());
1419 return Pages::fromByteLengthExact(byteLength());
1422 Maybe
<Pages
> ArrayBufferObject::wasmSourceMaxPages() const {
1424 return contents().wasmBuffer()->sourceMaxPages();
1426 MOZ_ASSERT(isPreparedForAsmJS());
1427 return Some
<Pages
>(Pages::fromByteLengthExact(byteLength()));
1430 size_t js::WasmArrayBufferMappedSize(const ArrayBufferObjectMaybeShared
* buf
) {
1431 if (buf
->is
<ArrayBufferObject
>()) {
1432 return buf
->as
<ArrayBufferObject
>().wasmMappedSize();
1434 return buf
->as
<SharedArrayBufferObject
>().wasmMappedSize();
1437 IndexType
js::WasmArrayBufferIndexType(
1438 const ArrayBufferObjectMaybeShared
* buf
) {
1439 if (buf
->is
<ArrayBufferObject
>()) {
1440 return buf
->as
<ArrayBufferObject
>().wasmIndexType();
1442 return buf
->as
<SharedArrayBufferObject
>().wasmIndexType();
1444 Pages
js::WasmArrayBufferPages(const ArrayBufferObjectMaybeShared
* buf
) {
1445 if (buf
->is
<ArrayBufferObject
>()) {
1446 return buf
->as
<ArrayBufferObject
>().wasmPages();
1448 return buf
->as
<SharedArrayBufferObject
>().volatileWasmPages();
1450 Pages
js::WasmArrayBufferClampedMaxPages(
1451 const ArrayBufferObjectMaybeShared
* buf
) {
1452 if (buf
->is
<ArrayBufferObject
>()) {
1453 return buf
->as
<ArrayBufferObject
>().wasmClampedMaxPages();
1455 return buf
->as
<SharedArrayBufferObject
>().wasmClampedMaxPages();
1457 Maybe
<Pages
> js::WasmArrayBufferSourceMaxPages(
1458 const ArrayBufferObjectMaybeShared
* buf
) {
1459 if (buf
->is
<ArrayBufferObject
>()) {
1460 return buf
->as
<ArrayBufferObject
>().wasmSourceMaxPages();
1462 return Some(buf
->as
<SharedArrayBufferObject
>().wasmSourceMaxPages());
1465 static void CheckStealPreconditions(Handle
<ArrayBufferObject
*> buffer
,
1469 MOZ_ASSERT(!buffer
->isDetached(), "can't steal from a detached buffer");
1470 MOZ_ASSERT(!buffer
->isPreparedForAsmJS(),
1471 "asm.js-prepared buffers don't have detachable/stealable data");
1475 ArrayBufferObject
* ArrayBufferObject::wasmGrowToPagesInPlace(
1476 wasm::IndexType t
, Pages newPages
, Handle
<ArrayBufferObject
*> oldBuf
,
1478 if (oldBuf
->isLengthPinned()) {
1482 CheckStealPreconditions(oldBuf
, cx
);
1484 MOZ_ASSERT(oldBuf
->isWasm());
1486 // Check that the new pages is within our allowable range. This will
1487 // simultaneously check against the maximum specified in source and our
1488 // implementation limits.
1489 if (newPages
> oldBuf
->wasmClampedMaxPages()) {
1492 MOZ_ASSERT(newPages
<= wasm::MaxMemoryPages(t
) &&
1493 newPages
.byteLength() <= ArrayBufferObject::MaxByteLength
);
1495 // We have checked against the clamped maximum and so we know we can convert
1496 // to byte lengths now.
1497 size_t newSize
= newPages
.byteLength();
1499 // On failure, do not throw and ensure that the original buffer is
1500 // unmodified and valid. After WasmArrayRawBuffer::growToPagesInPlace(), the
1501 // wasm-visible length of the buffer has been increased so it must be the
1502 // last fallible operation.
1504 auto* newBuf
= ArrayBufferObject::createEmpty(cx
);
1506 cx
->clearPendingException();
1510 MOZ_ASSERT(newBuf
->isNoData());
1512 if (!oldBuf
->contents().wasmBuffer()->growToPagesInPlace(newPages
)) {
1516 // Extract the grown contents from |oldBuf|.
1517 BufferContents oldContents
= oldBuf
->contents();
1519 // Overwrite |oldBuf|'s data pointer *without* releasing old data.
1520 oldBuf
->setDataPointer(BufferContents::createNoData());
1522 // Detach |oldBuf| now that doing so won't release |oldContents|.
1523 RemoveCellMemory(oldBuf
, oldBuf
->byteLength(),
1524 MemoryUse::ArrayBufferContents
);
1525 ArrayBufferObject::detach(cx
, oldBuf
);
1527 // Set |newBuf|'s contents to |oldBuf|'s original contents.
1528 newBuf
->initialize(newSize
, oldContents
);
1529 AddCellMemory(newBuf
, newSize
, MemoryUse::ArrayBufferContents
);
1535 ArrayBufferObject
* ArrayBufferObject::wasmMovingGrowToPages(
1536 IndexType t
, Pages newPages
, Handle
<ArrayBufferObject
*> oldBuf
,
1538 // On failure, do not throw and ensure that the original buffer is
1539 // unmodified and valid.
1540 if (oldBuf
->isLengthPinned()) {
1544 // Check that the new pages is within our allowable range. This will
1545 // simultaneously check against the maximum specified in source and our
1546 // implementation limits.
1547 if (newPages
> oldBuf
->wasmClampedMaxPages()) {
1550 MOZ_ASSERT(newPages
<= wasm::MaxMemoryPages(t
) &&
1551 newPages
.byteLength() < ArrayBufferObject::MaxByteLength
);
1553 // We have checked against the clamped maximum and so we know we can convert
1554 // to byte lengths now.
1555 size_t newSize
= newPages
.byteLength();
1557 if (wasm::ComputeMappedSize(newPages
) <= oldBuf
->wasmMappedSize() ||
1558 oldBuf
->contents().wasmBuffer()->extendMappedSize(newPages
)) {
1559 return wasmGrowToPagesInPlace(t
, newPages
, oldBuf
, cx
);
1562 Rooted
<ArrayBufferObject
*> newBuf(cx
, ArrayBufferObject::createEmpty(cx
));
1564 cx
->clearPendingException();
1568 Pages clampedMaxPages
=
1569 wasm::ClampedMaxPages(t
, newPages
, Nothing(), /* hugeMemory */ false);
1570 WasmArrayRawBuffer
* newRawBuf
= WasmArrayRawBuffer::AllocateWasm(
1571 oldBuf
->wasmIndexType(), newPages
, clampedMaxPages
, Nothing(), Nothing());
1576 AddCellMemory(newBuf
, newSize
, MemoryUse::ArrayBufferContents
);
1578 BufferContents contents
=
1579 BufferContents::createWasm(newRawBuf
->dataPointer());
1580 newBuf
->initialize(newSize
, contents
);
1582 memcpy(newBuf
->dataPointer(), oldBuf
->dataPointer(), oldBuf
->byteLength());
1583 ArrayBufferObject::detach(cx
, oldBuf
);
1589 void ArrayBufferObject::wasmDiscard(Handle
<ArrayBufferObject
*> buf
,
1590 uint64_t byteOffset
, uint64_t byteLen
) {
1591 MOZ_ASSERT(buf
->isWasm());
1592 buf
->contents().wasmBuffer()->discard(byteOffset
, byteLen
);
1595 uint32_t ArrayBufferObject::flags() const {
1596 return uint32_t(getFixedSlot(FLAGS_SLOT
).toInt32());
1599 void ArrayBufferObject::setFlags(uint32_t flags
) {
1600 setFixedSlot(FLAGS_SLOT
, Int32Value(flags
));
1603 static constexpr js::gc::AllocKind
GetArrayBufferGCObjectKind(size_t numSlots
) {
1604 if (numSlots
<= 4) {
1605 return js::gc::AllocKind::ARRAYBUFFER4
;
1607 if (numSlots
<= 8) {
1608 return js::gc::AllocKind::ARRAYBUFFER8
;
1610 if (numSlots
<= 12) {
1611 return js::gc::AllocKind::ARRAYBUFFER12
;
1613 return js::gc::AllocKind::ARRAYBUFFER16
;
1616 template <class ArrayBufferType
>
1617 static ArrayBufferType
* NewArrayBufferObject(JSContext
* cx
, HandleObject proto_
,
1618 gc::AllocKind allocKind
) {
1619 MOZ_ASSERT(allocKind
== gc::AllocKind::ARRAYBUFFER4
||
1620 allocKind
== gc::AllocKind::ARRAYBUFFER8
||
1621 allocKind
== gc::AllocKind::ARRAYBUFFER12
||
1622 allocKind
== gc::AllocKind::ARRAYBUFFER16
);
1624 static_assert(std::is_same_v
<ArrayBufferType
, FixedLengthArrayBufferObject
> ||
1625 std::is_same_v
<ArrayBufferType
, ResizableArrayBufferObject
>);
1627 RootedObject
proto(cx
, proto_
);
1629 proto
= GlobalObject::getOrCreatePrototype(cx
, JSProto_ArrayBuffer
);
1635 const JSClass
* clasp
= &ArrayBufferType::class_
;
1637 // Array buffers can store data inline so we only use fixed slots to cover the
1638 // reserved slots, ignoring the AllocKind.
1639 MOZ_ASSERT(ClassCanHaveFixedData(clasp
));
1640 constexpr size_t nfixed
= ArrayBufferType::RESERVED_SLOTS
;
1641 static_assert(nfixed
<= NativeObject::MAX_FIXED_SLOTS
);
1643 Rooted
<SharedShape
*> shape(
1645 SharedShape::getInitialShape(cx
, clasp
, cx
->realm(), AsTaggedProto(proto
),
1646 nfixed
, ObjectFlags()));
1651 // Array buffers can't be nursery allocated but can be background-finalized.
1652 MOZ_ASSERT(IsBackgroundFinalized(allocKind
));
1653 MOZ_ASSERT(!CanNurseryAllocateFinalizedClass(clasp
));
1654 constexpr gc::Heap heap
= gc::Heap::Tenured
;
1656 return NativeObject::create
<ArrayBufferType
>(cx
, allocKind
, heap
, shape
);
1659 // Creates a new ArrayBufferObject with %ArrayBuffer.prototype% as proto and no
1660 // space for inline data.
1661 static ArrayBufferObject
* NewArrayBufferObject(JSContext
* cx
) {
1662 constexpr auto allocKind
=
1663 GetArrayBufferGCObjectKind(FixedLengthArrayBufferObject::RESERVED_SLOTS
);
1664 return NewArrayBufferObject
<FixedLengthArrayBufferObject
>(cx
, nullptr,
1667 static ResizableArrayBufferObject
* NewResizableArrayBufferObject(
1669 constexpr auto allocKind
=
1670 GetArrayBufferGCObjectKind(ResizableArrayBufferObject::RESERVED_SLOTS
);
1671 return NewArrayBufferObject
<ResizableArrayBufferObject
>(cx
, nullptr,
1675 ArrayBufferObject
* ArrayBufferObject::createForContents(
1676 JSContext
* cx
, size_t nbytes
, BufferContents contents
) {
1677 MOZ_ASSERT(contents
);
1678 MOZ_ASSERT(contents
.kind() != INLINE_DATA
);
1679 MOZ_ASSERT(contents
.kind() != NO_DATA
);
1680 MOZ_ASSERT(contents
.kind() != WASM
);
1682 // 24.1.1.1, step 3 (Inlined 6.2.6.1 CreateByteDataBlock, step 2).
1683 if (!CheckArrayBufferTooLarge(cx
, nbytes
)) {
1687 // Some |contents| kinds need to store extra data in the ArrayBuffer beyond a
1688 // data pointer. If needed for the particular kind, add extra fixed slots to
1689 // the ArrayBuffer for use as raw storage to store such information.
1690 constexpr size_t reservedSlots
= FixedLengthArrayBufferObject::RESERVED_SLOTS
;
1692 size_t nAllocated
= 0;
1693 size_t nslots
= reservedSlots
;
1694 if (contents
.kind() == USER_OWNED
) {
1695 // No accounting to do in this case.
1696 } else if (contents
.kind() == EXTERNAL
) {
1697 // Store the FreeInfo in the inline data slots so that we
1698 // don't use up slots for it in non-refcounted array buffers.
1699 constexpr size_t freeInfoSlots
= HowMany(sizeof(FreeInfo
), sizeof(Value
));
1701 reservedSlots
+ freeInfoSlots
<= NativeObject::MAX_FIXED_SLOTS
,
1702 "FreeInfo must fit in inline slots");
1703 nslots
+= freeInfoSlots
;
1705 // The ABO is taking ownership, so account the bytes against the zone.
1706 nAllocated
= nbytes
;
1707 if (contents
.kind() == MAPPED
) {
1708 nAllocated
= RoundUp(nbytes
, js::gc::SystemPageSize());
1710 MOZ_ASSERT(contents
.kind() == MALLOCED_ARRAYBUFFER_CONTENTS_ARENA
||
1711 contents
.kind() == MALLOCED_UNKNOWN_ARENA
,
1712 "should have handled all possible callers' kinds");
1716 gc::AllocKind allocKind
= GetArrayBufferGCObjectKind(nslots
);
1718 AutoSetNewObjectMetadata
metadata(cx
);
1719 Rooted
<ArrayBufferObject
*> buffer(
1720 cx
, NewArrayBufferObject
<FixedLengthArrayBufferObject
>(cx
, nullptr,
1726 MOZ_ASSERT(!gc::IsInsideNursery(buffer
),
1727 "ArrayBufferObject has a finalizer that must be called to not "
1728 "leak in some cases, so it can't be nursery-allocated");
1730 buffer
->initialize(nbytes
, contents
);
1732 if (contents
.kind() == MAPPED
||
1733 contents
.kind() == MALLOCED_ARRAYBUFFER_CONTENTS_ARENA
||
1734 contents
.kind() == MALLOCED_UNKNOWN_ARENA
) {
1735 AddCellMemory(buffer
, nAllocated
, MemoryUse::ArrayBufferContents
);
1741 template <class ArrayBufferType
, ArrayBufferObject::FillContents FillType
>
1742 /* static */ std::tuple
<ArrayBufferType
*, uint8_t*>
1743 ArrayBufferObject::createUninitializedBufferAndData(
1744 JSContext
* cx
, size_t nbytes
, AutoSetNewObjectMetadata
&,
1745 JS::Handle
<JSObject
*> proto
) {
1746 MOZ_ASSERT(nbytes
<= ArrayBufferObject::MaxByteLength
,
1747 "caller must validate the byte count it passes");
1749 static_assert(std::is_same_v
<ArrayBufferType
, FixedLengthArrayBufferObject
> ||
1750 std::is_same_v
<ArrayBufferType
, ResizableArrayBufferObject
>);
1752 // Try fitting the data inline with the object by repurposing fixed-slot
1753 // storage. Add extra fixed slots if necessary to accomplish this, but don't
1754 // exceed the maximum number of fixed slots!
1755 size_t nslots
= ArrayBufferType::RESERVED_SLOTS
;
1756 ArrayBufferContents data
;
1757 if (nbytes
<= ArrayBufferType::MaxInlineBytes
) {
1758 int newSlots
= HowMany(nbytes
, sizeof(Value
));
1759 MOZ_ASSERT(int(nbytes
) <= newSlots
* int(sizeof(Value
)));
1763 data
= FillType
== FillContents::Uninitialized
1764 ? AllocateUninitializedArrayBufferContents(cx
, nbytes
)
1765 : AllocateArrayBufferContents(cx
, nbytes
);
1767 return {nullptr, nullptr};
1771 gc::AllocKind allocKind
= GetArrayBufferGCObjectKind(nslots
);
1773 auto* buffer
= NewArrayBufferObject
<ArrayBufferType
>(cx
, proto
, allocKind
);
1775 return {nullptr, nullptr};
1778 MOZ_ASSERT(!gc::IsInsideNursery(buffer
),
1779 "ArrayBufferObject has a finalizer that must be called to not "
1780 "leak in some cases, so it can't be nursery-allocated");
1783 return {buffer
, data
.release()};
1786 if constexpr (FillType
== FillContents::Zero
) {
1787 memset(buffer
->inlineDataPointer(), 0, nbytes
);
1789 return {buffer
, nullptr};
1792 template <ArrayBufferObject::FillContents FillType
>
1793 /* static */ std::tuple
<ArrayBufferObject
*, uint8_t*>
1794 ArrayBufferObject::createBufferAndData(
1795 JSContext
* cx
, size_t nbytes
, AutoSetNewObjectMetadata
& metadata
,
1796 JS::Handle
<JSObject
*> proto
/* = nullptr */) {
1797 MOZ_ASSERT(nbytes
<= ArrayBufferObject::MaxByteLength
,
1798 "caller must validate the byte count it passes");
1800 auto [buffer
, data
] =
1801 createUninitializedBufferAndData
<FixedLengthArrayBufferObject
, FillType
>(
1802 cx
, nbytes
, metadata
, proto
);
1804 return {nullptr, nullptr};
1808 buffer
->initialize(nbytes
, BufferContents::createMallocedArrayBufferContentsArena(data
));
1809 AddCellMemory(buffer
, nbytes
, MemoryUse::ArrayBufferContents
);
1811 data
= buffer
->inlineDataPointer();
1812 buffer
->initialize(nbytes
, BufferContents::createInlineData(data
));
1814 return {buffer
, data
};
1817 template <ArrayBufferObject::FillContents FillType
>
1818 /* static */ std::tuple
<ResizableArrayBufferObject
*, uint8_t*>
1819 ResizableArrayBufferObject::createBufferAndData(
1820 JSContext
* cx
, size_t byteLength
, size_t maxByteLength
,
1821 AutoSetNewObjectMetadata
& metadata
, Handle
<JSObject
*> proto
) {
1822 MOZ_ASSERT(byteLength
<= maxByteLength
);
1823 MOZ_ASSERT(maxByteLength
<= ArrayBufferObject::MaxByteLength
,
1824 "caller must validate the byte count it passes");
1826 // NOTE: The spec proposal for resizable ArrayBuffers suggests to use a
1827 // virtual memory based approach to avoid eagerly allocating the maximum byte
1828 // length. We don't yet support this and instead are allocating the maximum
1829 // byte length direct from the start.
1830 size_t nbytes
= maxByteLength
;
1832 auto [buffer
, data
] =
1833 createUninitializedBufferAndData
<ResizableArrayBufferObject
, FillType
>(
1834 cx
, nbytes
, metadata
, proto
);
1836 return {nullptr, nullptr};
1840 buffer
->initialize(byteLength
, maxByteLength
,
1841 BufferContents::createMallocedArrayBufferContentsArena(data
));
1842 AddCellMemory(buffer
, nbytes
, MemoryUse::ArrayBufferContents
);
1844 data
= buffer
->inlineDataPointer();
1845 buffer
->initialize(byteLength
, maxByteLength
,
1846 BufferContents::createInlineData(data
));
1848 return {buffer
, data
};
1851 /* static */ ArrayBufferObject
* ArrayBufferObject::copy(
1852 JSContext
* cx
, size_t newByteLength
,
1853 JS::Handle
<ArrayBufferObject
*> source
) {
1854 MOZ_ASSERT(!source
->isDetached());
1855 MOZ_ASSERT(newByteLength
<= ArrayBufferObject::MaxByteLength
,
1856 "caller must validate the byte count it passes");
1858 size_t sourceByteLength
= source
->byteLength();
1860 if (newByteLength
> sourceByteLength
) {
1861 // Copy into a larger buffer.
1862 AutoSetNewObjectMetadata
metadata(cx
);
1863 auto [buffer
, toFill
] = createBufferAndData
<FillContents::Zero
>(
1864 cx
, newByteLength
, metadata
, nullptr);
1869 std::copy_n(source
->dataPointer(), sourceByteLength
, toFill
);
1874 // Copy into a smaller or same size buffer.
1875 AutoSetNewObjectMetadata
metadata(cx
);
1876 auto [buffer
, toFill
] = createBufferAndData
<FillContents::Uninitialized
>(
1877 cx
, newByteLength
, metadata
, nullptr);
1882 std::uninitialized_copy_n(source
->dataPointer(), newByteLength
, toFill
);
1887 /* static */ ArrayBufferObject
* ArrayBufferObject::copyAndDetach(
1888 JSContext
* cx
, size_t newByteLength
,
1889 JS::Handle
<ArrayBufferObject
*> source
) {
1890 MOZ_ASSERT(!source
->isDetached());
1891 MOZ_ASSERT(newByteLength
<= ArrayBufferObject::MaxByteLength
,
1892 "caller must validate the byte count it passes");
1894 if (newByteLength
> FixedLengthArrayBufferObject::MaxInlineBytes
&&
1895 source
->isMalloced()) {
1896 if (newByteLength
== source
->associatedBytes()) {
1897 return copyAndDetachSteal(cx
, source
);
1899 if (source
->bufferKind() ==
1900 ArrayBufferObject::MALLOCED_ARRAYBUFFER_CONTENTS_ARENA
) {
1901 return copyAndDetachRealloc(cx
, newByteLength
, source
);
1905 auto* newBuffer
= ArrayBufferObject::copy(cx
, newByteLength
, source
);
1909 ArrayBufferObject::detach(cx
, source
);
1914 /* static */ ArrayBufferObject
* ArrayBufferObject::copyAndDetachSteal(
1915 JSContext
* cx
, JS::Handle
<ArrayBufferObject
*> source
) {
1916 MOZ_ASSERT(!source
->isDetached());
1917 MOZ_ASSERT(source
->isMalloced());
1919 size_t newByteLength
= source
->associatedBytes();
1920 MOZ_ASSERT(newByteLength
> FixedLengthArrayBufferObject::MaxInlineBytes
,
1921 "prefer copying small buffers");
1922 MOZ_ASSERT(source
->byteLength() <= newByteLength
,
1923 "source length is less-or-equal to |newByteLength|");
1925 auto* newBuffer
= ArrayBufferObject::createEmpty(cx
);
1930 // Extract the contents from |source|.
1931 BufferContents contents
= source
->contents();
1932 MOZ_ASSERT(contents
);
1933 MOZ_ASSERT(contents
.kind() == MALLOCED_ARRAYBUFFER_CONTENTS_ARENA
||
1934 contents
.kind() == MALLOCED_UNKNOWN_ARENA
);
1936 // Overwrite |source|'s data pointer *without* releasing the data.
1937 source
->setDataPointer(BufferContents::createNoData());
1939 // Detach |source| now that doing so won't release |contents|.
1940 RemoveCellMemory(source
, newByteLength
, MemoryUse::ArrayBufferContents
);
1941 ArrayBufferObject::detach(cx
, source
);
1943 // Set |newBuffer|'s contents to |source|'s original contents.
1944 newBuffer
->initialize(newByteLength
, contents
);
1945 AddCellMemory(newBuffer
, newByteLength
, MemoryUse::ArrayBufferContents
);
1950 /* static */ ArrayBufferObject
* ArrayBufferObject::copyAndDetachRealloc(
1951 JSContext
* cx
, size_t newByteLength
,
1952 JS::Handle
<ArrayBufferObject
*> source
) {
1953 MOZ_ASSERT(!source
->isDetached());
1954 MOZ_ASSERT(source
->bufferKind() == MALLOCED_ARRAYBUFFER_CONTENTS_ARENA
);
1955 MOZ_ASSERT(newByteLength
> FixedLengthArrayBufferObject::MaxInlineBytes
,
1956 "prefer copying small buffers");
1957 MOZ_ASSERT(newByteLength
<= ArrayBufferObject::MaxByteLength
,
1958 "caller must validate the byte count it passes");
1960 size_t oldByteLength
= source
->associatedBytes();
1961 MOZ_ASSERT(oldByteLength
!= newByteLength
,
1962 "steal instead of realloc same size buffers");
1963 MOZ_ASSERT(source
->byteLength() <= oldByteLength
,
1964 "source length is less-or-equal to |oldByteLength|");
1966 Rooted
<ArrayBufferObject
*> newBuffer(cx
, ArrayBufferObject::createEmpty(cx
));
1971 // Extract the contents from |source|.
1972 BufferContents contents
= source
->contents();
1973 MOZ_ASSERT(contents
);
1974 MOZ_ASSERT(contents
.kind() == MALLOCED_ARRAYBUFFER_CONTENTS_ARENA
);
1976 // Reallocate the data pointer.
1977 auto newData
= ReallocateArrayBufferContents(cx
, contents
.data(),
1978 oldByteLength
, newByteLength
);
1980 // If reallocation failed, the old pointer is still valid, so just return.
1984 BufferContents::createMallocedArrayBufferContentsArena(newData
.release());
1986 // Overwrite |source|'s data pointer *without* releasing the data.
1987 source
->setDataPointer(BufferContents::createNoData());
1989 // Detach |source| now that doing so won't release |contents|.
1990 RemoveCellMemory(source
, oldByteLength
, MemoryUse::ArrayBufferContents
);
1991 ArrayBufferObject::detach(cx
, source
);
1993 // Set |newBuffer|'s contents to |newContents|.
1994 newBuffer
->initialize(newByteLength
, newContents
);
1995 AddCellMemory(newBuffer
, newByteLength
, MemoryUse::ArrayBufferContents
);
1997 // Zero initialize the newly allocated memory, if necessary.
1998 if (newByteLength
> oldByteLength
) {
1999 size_t count
= newByteLength
- oldByteLength
;
2000 std::uninitialized_fill_n(newContents
.data() + oldByteLength
, count
, 0);
2006 ArrayBufferObject
* ArrayBufferObject::createZeroed(
2007 JSContext
* cx
, size_t nbytes
, HandleObject proto
/* = nullptr */) {
2008 // 24.1.1.1, step 3 (Inlined 6.2.6.1 CreateByteDataBlock, step 2).
2009 if (!CheckArrayBufferTooLarge(cx
, nbytes
)) {
2013 AutoSetNewObjectMetadata
metadata(cx
);
2014 auto [buffer
, toFill
] =
2015 createBufferAndData
<FillContents::Zero
>(cx
, nbytes
, metadata
, proto
);
2020 ResizableArrayBufferObject
* ResizableArrayBufferObject::createZeroed(
2021 JSContext
* cx
, size_t byteLength
, size_t maxByteLength
,
2022 HandleObject proto
/* = nullptr */) {
2023 // 24.1.1.1, step 3 (Inlined 6.2.6.1 CreateByteDataBlock, step 2).
2024 if (!CheckArrayBufferTooLarge(cx
, byteLength
) ||
2025 !CheckArrayBufferTooLarge(cx
, maxByteLength
)) {
2028 if (byteLength
> maxByteLength
) {
2029 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2030 JSMSG_ARRAYBUFFER_LENGTH_LARGER_THAN_MAXIMUM
);
2034 AutoSetNewObjectMetadata
metadata(cx
);
2035 auto [buffer
, toFill
] = createBufferAndData
<FillContents::Zero
>(
2036 cx
, byteLength
, maxByteLength
, metadata
, proto
);
2041 ArrayBufferObject
* ArrayBufferObject::createEmpty(JSContext
* cx
) {
2042 AutoSetNewObjectMetadata
metadata(cx
);
2043 ArrayBufferObject
* obj
= NewArrayBufferObject(cx
);
2048 obj
->initialize(0, BufferContents::createNoData());
2052 ResizableArrayBufferObject
* ResizableArrayBufferObject::createEmpty(
2054 AutoSetNewObjectMetadata
metadata(cx
);
2055 auto* obj
= NewResizableArrayBufferObject(cx
);
2060 obj
->initialize(0, 0, BufferContents::createNoData());
2064 ArrayBufferObject
* ArrayBufferObject::createFromNewRawBuffer(
2065 JSContext
* cx
, WasmArrayRawBuffer
* rawBuffer
, size_t initialSize
) {
2066 AutoSetNewObjectMetadata
metadata(cx
);
2067 ArrayBufferObject
* buffer
= NewArrayBufferObject(cx
);
2069 WasmArrayRawBuffer::Release(rawBuffer
->dataPointer());
2073 MOZ_ASSERT(initialSize
== rawBuffer
->byteLength());
2075 auto contents
= BufferContents::createWasm(rawBuffer
->dataPointer());
2076 buffer
->initialize(initialSize
, contents
);
2078 AddCellMemory(buffer
, initialSize
, MemoryUse::ArrayBufferContents
);
2083 /* static */ uint8_t* ArrayBufferObject::stealMallocedContents(
2084 JSContext
* cx
, Handle
<ArrayBufferObject
*> buffer
) {
2085 if (buffer
->isLengthPinned()) {
2088 CheckStealPreconditions(buffer
, cx
);
2090 switch (buffer
->bufferKind()) {
2091 case MALLOCED_ARRAYBUFFER_CONTENTS_ARENA
:
2092 case MALLOCED_UNKNOWN_ARENA
: {
2093 uint8_t* stolenData
= buffer
->dataPointer();
2094 MOZ_ASSERT(stolenData
);
2096 // Resizable buffers are initially allocated with their maximum
2097 // byte-length. When stealing the buffer contents shrink the allocated
2098 // memory to the actually used byte-length.
2099 if (buffer
->isResizable()) {
2100 auto* resizableBuffer
= &buffer
->as
<ResizableArrayBufferObject
>();
2101 size_t byteLength
= resizableBuffer
->byteLength();
2102 size_t maxByteLength
= resizableBuffer
->maxByteLength();
2103 MOZ_ASSERT(byteLength
<= maxByteLength
);
2105 if (byteLength
< maxByteLength
) {
2106 auto newData
= ReallocateArrayBufferContents(
2107 cx
, stolenData
, maxByteLength
, byteLength
);
2109 // If reallocation failed, the old pointer is still valid. The
2110 // ArrayBuffer isn't detached and still owns the malloc'ed memory.
2114 // The following code must be infallible, because the data pointer of
2115 // |buffer| is possibly no longer valid after the above realloc.
2117 stolenData
= newData
.release();
2121 RemoveCellMemory(buffer
, buffer
->associatedBytes(),
2122 MemoryUse::ArrayBufferContents
);
2124 // Overwrite the old data pointer *without* releasing the contents
2126 buffer
->setDataPointer(BufferContents::createNoData());
2128 // Detach |buffer| now that doing so won't free |stolenData|.
2129 ArrayBufferObject::detach(cx
, buffer
);
2138 // We can't use these data types directly. Make a copy to return.
2139 ArrayBufferContents copiedData
= NewCopiedBufferContents(cx
, buffer
);
2144 // Detach |buffer|. This immediately releases the currently owned
2145 // contents, freeing or unmapping data in the MAPPED and EXTERNAL cases.
2146 ArrayBufferObject::detach(cx
, buffer
);
2147 return copiedData
.release();
2151 MOZ_ASSERT_UNREACHABLE(
2152 "wasm buffers aren't stealable except by a "
2153 "memory.grow operation that shouldn't call this "
2158 MOZ_ASSERT_UNREACHABLE("garbage kind computed");
2162 /* static */ ArrayBufferObject::BufferContents
2163 ArrayBufferObject::extractStructuredCloneContents(
2164 JSContext
* cx
, Handle
<ArrayBufferObject
*> buffer
) {
2165 if (buffer
->isLengthPinned()) {
2166 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2167 JSMSG_ARRAYBUFFER_LENGTH_PINNED
);
2168 return BufferContents::createFailed();
2171 CheckStealPreconditions(buffer
, cx
);
2173 BufferContents contents
= buffer
->contents();
2175 switch (contents
.kind()) {
2179 ArrayBufferContents copiedData
= NewCopiedBufferContents(cx
, buffer
);
2181 return BufferContents::createFailed();
2184 ArrayBufferObject::detach(cx
, buffer
);
2185 return BufferContents::createMallocedArrayBufferContentsArena(
2186 copiedData
.release());
2189 case MALLOCED_ARRAYBUFFER_CONTENTS_ARENA
:
2190 case MALLOCED_UNKNOWN_ARENA
:
2192 MOZ_ASSERT(contents
);
2194 RemoveCellMemory(buffer
, buffer
->associatedBytes(),
2195 MemoryUse::ArrayBufferContents
);
2197 // Overwrite the old data pointer *without* releasing old data.
2198 buffer
->setDataPointer(BufferContents::createNoData());
2200 // Detach |buffer| now that doing so won't release |oldContents|.
2201 ArrayBufferObject::detach(cx
, buffer
);
2206 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2207 JSMSG_WASM_NO_TRANSFER
);
2208 return BufferContents::createFailed();
2211 MOZ_ASSERT_UNREACHABLE(
2212 "external ArrayBuffer shouldn't have passed the "
2213 "structured-clone preflighting");
2217 MOZ_ASSERT_UNREACHABLE("garbage kind computed");
2218 return BufferContents::createFailed();
2222 bool ArrayBufferObject::ensureNonInline(JSContext
* cx
,
2223 Handle
<ArrayBufferObject
*> buffer
) {
2224 if (buffer
->isDetached() || buffer
->isPreparedForAsmJS()) {
2228 if (buffer
->isLengthPinned()) {
2229 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2230 JSMSG_ARRAYBUFFER_LENGTH_PINNED
);
2234 BufferContents inlineContents
= buffer
->contents();
2235 if (inlineContents
.kind() != INLINE_DATA
) {
2239 size_t nbytes
= buffer
->byteLength();
2240 ArrayBufferContents copy
= NewCopiedBufferContents(cx
, buffer
);
2244 BufferContents outOfLineContents
=
2245 BufferContents::createMallocedArrayBufferContentsArena(copy
.release());
2246 buffer
->setDataPointer(outOfLineContents
);
2247 AddCellMemory(buffer
, nbytes
, MemoryUse::ArrayBufferContents
);
2249 if (!buffer
->firstView()) {
2250 return true; // No views! Easy!
2253 buffer
->firstView()->as
<ArrayBufferViewObject
>().notifyBufferMoved(
2254 inlineContents
.data(), outOfLineContents
.data());
2256 auto& innerViews
= ObjectRealm::get(buffer
).innerViews
.get();
2257 if (InnerViewTable::ViewVector
* views
=
2258 innerViews
.maybeViewsUnbarriered(buffer
)) {
2259 for (JSObject
* view
: *views
) {
2260 view
->as
<ArrayBufferViewObject
>().notifyBufferMoved(
2261 inlineContents
.data(), outOfLineContents
.data());
2269 void ArrayBufferObject::addSizeOfExcludingThis(
2270 JSObject
* obj
, mozilla::MallocSizeOf mallocSizeOf
, JS::ClassInfo
* info
,
2271 JS::RuntimeSizes
* runtimeSizes
) {
2272 auto& buffer
= obj
->as
<ArrayBufferObject
>();
2273 switch (buffer
.bufferKind()) {
2275 // Inline data's size should be reported by this object's size-class
2278 case MALLOCED_ARRAYBUFFER_CONTENTS_ARENA
:
2279 case MALLOCED_UNKNOWN_ARENA
:
2280 if (buffer
.isPreparedForAsmJS()) {
2281 info
->objectsMallocHeapElementsAsmJS
+=
2282 mallocSizeOf(buffer
.dataPointer());
2284 info
->objectsMallocHeapElementsNormal
+=
2285 mallocSizeOf(buffer
.dataPointer());
2289 // No data is no memory.
2290 MOZ_ASSERT(buffer
.dataPointer() == nullptr);
2293 // User-owned data should be accounted for by the user.
2296 // External data will be accounted for by the owner of the buffer,
2300 info
->objectsNonHeapElementsNormal
+= buffer
.byteLength();
2303 if (!buffer
.isDetached()) {
2304 info
->objectsNonHeapElementsWasm
+= buffer
.byteLength();
2306 MOZ_ASSERT(buffer
.wasmMappedSize() >= buffer
.byteLength());
2307 runtimeSizes
->wasmGuardPages
+=
2308 buffer
.wasmMappedSize() - buffer
.byteLength();
2316 void ArrayBufferObject::finalize(JS::GCContext
* gcx
, JSObject
* obj
) {
2317 obj
->as
<ArrayBufferObject
>().releaseData(gcx
);
2321 void ArrayBufferObject::copyData(ArrayBufferObject
* toBuffer
, size_t toIndex
,
2322 ArrayBufferObject
* fromBuffer
,
2323 size_t fromIndex
, size_t count
) {
2324 MOZ_ASSERT(!toBuffer
->isDetached());
2325 MOZ_ASSERT(toBuffer
->byteLength() >= count
);
2326 MOZ_ASSERT(toBuffer
->byteLength() >= toIndex
+ count
);
2327 MOZ_ASSERT(!fromBuffer
->isDetached());
2328 MOZ_ASSERT(fromBuffer
->byteLength() >= fromIndex
);
2329 MOZ_ASSERT(fromBuffer
->byteLength() >= fromIndex
+ count
);
2331 memcpy(toBuffer
->dataPointer() + toIndex
,
2332 fromBuffer
->dataPointer() + fromIndex
, count
);
2335 template <class ArrayBufferType
>
2337 size_t ArrayBufferObject::objectMoved(JSObject
* obj
, JSObject
* old
) {
2338 auto& dst
= obj
->as
<ArrayBufferType
>();
2339 const auto& src
= old
->as
<ArrayBufferType
>();
2342 !obj
->runtimeFromMainThread()->gc
.nursery().isInside(src
.dataPointer()));
2344 // Fix up possible inline data pointer.
2345 if (src
.hasInlineData()) {
2346 dst
.setFixedSlot(DATA_SLOT
, PrivateValue(dst
.inlineDataPointer()));
2352 JSObject
* ArrayBufferObject::firstView() {
2353 return getFixedSlot(FIRST_VIEW_SLOT
).isObject()
2354 ? &getFixedSlot(FIRST_VIEW_SLOT
).toObject()
2358 void ArrayBufferObject::setFirstView(ArrayBufferViewObject
* view
) {
2359 setFixedSlot(FIRST_VIEW_SLOT
, ObjectOrNullValue(view
));
2362 bool ArrayBufferObject::addView(JSContext
* cx
, ArrayBufferViewObject
* view
) {
2368 return ObjectRealm::get(this).innerViews
.get().addView(cx
, this, view
);
2375 inline bool InnerViewTable::Views::empty() { return views
.empty(); }
2377 inline bool InnerViewTable::Views::hasNurseryViews() {
2378 return firstNurseryView
< views
.length();
2381 bool InnerViewTable::Views::addView(ArrayBufferViewObject
* view
) {
2382 // Add the view to the list, ensuring that all nursery views are at end.
2384 if (!views
.append(view
)) {
2388 if (!gc::IsInsideNursery(view
)) {
2389 // Move tenured views before |firstNurseryView|.
2390 if (firstNurseryView
!= views
.length() - 1) {
2391 std::swap(views
[firstNurseryView
], views
.back());
2401 bool InnerViewTable::Views::sweepAfterMinorGC(JSTracer
* trc
) {
2402 return traceWeak(trc
, firstNurseryView
);
2405 bool InnerViewTable::Views::traceWeak(JSTracer
* trc
, size_t startIndex
) {
2406 // Use |trc| to trace the view vector from |startIndex| to the end, removing
2407 // dead views and updating |firstNurseryView|.
2409 size_t index
= startIndex
;
2410 bool sawNurseryView
= false;
2411 views
.mutableEraseIf(
2413 if (!JS::GCPolicy
<ViewVector::ElementType
>::traceWeak(trc
, &view
)) {
2417 if (!sawNurseryView
&& gc::IsInsideNursery(view
)) {
2418 sawNurseryView
= true;
2419 firstNurseryView
= index
;
2427 if (!sawNurseryView
) {
2428 firstNurseryView
= views
.length();
2433 return !views
.empty();
2436 inline void InnerViewTable::Views::check() {
2438 MOZ_ASSERT(firstNurseryView
<= views
.length());
2439 if (views
.length() < 100) {
2440 for (size_t i
= 0; i
< views
.length(); i
++) {
2441 MOZ_ASSERT(gc::IsInsideNursery(views
[i
]) == (i
>= firstNurseryView
));
2447 bool InnerViewTable::addView(JSContext
* cx
, ArrayBufferObject
* buffer
,
2448 ArrayBufferViewObject
* view
) {
2449 // ArrayBufferObject entries are only added when there are multiple views.
2450 MOZ_ASSERT(buffer
->firstView());
2451 MOZ_ASSERT(!gc::IsInsideNursery(buffer
));
2453 // Ensure the buffer is present in the map, getting the list of views.
2454 auto ptr
= map
.lookupForAdd(buffer
);
2455 if (!ptr
&& !map
.add(ptr
, buffer
, Views(cx
->zone()))) {
2456 ReportOutOfMemory(cx
);
2459 Views
& views
= ptr
->value();
2461 bool isNurseryView
= gc::IsInsideNursery(view
);
2462 bool hadNurseryViews
= views
.hasNurseryViews();
2463 if (!views
.addView(view
)) {
2464 ReportOutOfMemory(cx
);
2468 // If we added the first nursery view, add the buffer to the list of buffers
2469 // which have nursery views.
2470 if (isNurseryView
&& !hadNurseryViews
&& nurseryKeysValid
) {
2472 if (nurseryKeys
.length() < 100) {
2473 for (auto* key
: nurseryKeys
) {
2474 MOZ_ASSERT(key
!= buffer
);
2478 if (!nurseryKeys
.append(buffer
)) {
2479 nurseryKeysValid
= false;
2486 InnerViewTable::ViewVector
* InnerViewTable::maybeViewsUnbarriered(
2487 ArrayBufferObject
* buffer
) {
2488 auto ptr
= map
.lookup(buffer
);
2490 return &ptr
->value().views
;
2495 void InnerViewTable::removeViews(ArrayBufferObject
* buffer
) {
2496 auto ptr
= map
.lookup(buffer
);
2502 bool InnerViewTable::traceWeak(JSTracer
* trc
) { return map
.traceWeak(trc
); }
2504 void InnerViewTable::sweepAfterMinorGC(JSTracer
* trc
) {
2505 MOZ_ASSERT(needsSweepAfterMinorGC());
2507 if (nurseryKeysValid
) {
2508 for (size_t i
= 0; i
< nurseryKeys
.length(); i
++) {
2509 ArrayBufferObject
* buffer
= nurseryKeys
[i
];
2510 MOZ_ASSERT(!gc::IsInsideNursery(buffer
));
2511 auto ptr
= map
.lookup(buffer
);
2512 if (ptr
&& !ptr
->value().sweepAfterMinorGC(trc
)) {
2517 for (ArrayBufferViewMap::Enum
e(map
); !e
.empty(); e
.popFront()) {
2518 MOZ_ASSERT(!gc::IsInsideNursery(e
.front().key()));
2519 if (!e
.front().value().sweepAfterMinorGC(trc
)) {
2525 nurseryKeys
.clear();
2526 nurseryKeysValid
= true;
2529 size_t InnerViewTable::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf
) {
2530 size_t vectorSize
= 0;
2531 for (auto r
= map
.all(); !r
.empty(); r
.popFront()) {
2532 vectorSize
+= r
.front().value().views
.sizeOfExcludingThis(mallocSizeOf
);
2535 return vectorSize
+ map
.shallowSizeOfExcludingThis(mallocSizeOf
) +
2536 nurseryKeys
.sizeOfExcludingThis(mallocSizeOf
);
2540 bool JSObject::is
<js::ArrayBufferObjectMaybeShared
>() const {
2541 return is
<ArrayBufferObject
>() || is
<SharedArrayBufferObject
>();
2544 JS_PUBLIC_API
size_t JS::GetArrayBufferByteLength(JSObject
* obj
) {
2545 ArrayBufferObject
* aobj
= obj
->maybeUnwrapAs
<ArrayBufferObject
>();
2546 return aobj
? aobj
->byteLength() : 0;
2549 JS_PUBLIC_API
uint8_t* JS::GetArrayBufferData(JSObject
* obj
,
2550 bool* isSharedMemory
,
2551 const JS::AutoRequireNoGC
&) {
2552 ArrayBufferObject
* aobj
= obj
->maybeUnwrapIf
<ArrayBufferObject
>();
2556 *isSharedMemory
= false;
2557 return aobj
->dataPointer();
2560 static ArrayBufferObject
* UnwrapOrReportArrayBuffer(
2561 JSContext
* cx
, JS::Handle
<JSObject
*> maybeArrayBuffer
) {
2562 JSObject
* obj
= CheckedUnwrapStatic(maybeArrayBuffer
);
2564 ReportAccessDenied(cx
);
2568 if (!obj
->is
<ArrayBufferObject
>()) {
2569 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2570 JSMSG_ARRAYBUFFER_REQUIRED
);
2574 return &obj
->as
<ArrayBufferObject
>();
2577 JS_PUBLIC_API
bool JS::DetachArrayBuffer(JSContext
* cx
, HandleObject obj
) {
2582 Rooted
<ArrayBufferObject
*> unwrappedBuffer(
2583 cx
, UnwrapOrReportArrayBuffer(cx
, obj
));
2584 if (!unwrappedBuffer
) {
2588 if (unwrappedBuffer
->hasDefinedDetachKey()) {
2589 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2590 JSMSG_WASM_NO_TRANSFER
);
2593 if (unwrappedBuffer
->isLengthPinned()) {
2594 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2595 JSMSG_ARRAYBUFFER_LENGTH_PINNED
);
2599 AutoRealm
ar(cx
, unwrappedBuffer
);
2600 ArrayBufferObject::detach(cx
, unwrappedBuffer
);
2604 JS_PUBLIC_API
bool JS::HasDefinedArrayBufferDetachKey(JSContext
* cx
,
2607 Rooted
<ArrayBufferObject
*> unwrappedBuffer(
2608 cx
, UnwrapOrReportArrayBuffer(cx
, obj
));
2609 if (!unwrappedBuffer
) {
2613 *isDefined
= unwrappedBuffer
->hasDefinedDetachKey();
2617 JS_PUBLIC_API
bool JS::IsDetachedArrayBufferObject(JSObject
* obj
) {
2618 ArrayBufferObject
* aobj
= obj
->maybeUnwrapIf
<ArrayBufferObject
>();
2623 return aobj
->isDetached();
2626 JS_PUBLIC_API JSObject
* JS::NewArrayBuffer(JSContext
* cx
, size_t nbytes
) {
2630 return ArrayBufferObject::createZeroed(cx
, nbytes
);
2633 JS_PUBLIC_API JSObject
* JS::NewArrayBufferWithContents(
2634 JSContext
* cx
, size_t nbytes
,
2635 mozilla::UniquePtr
<void, JS::FreePolicy
> contents
) {
2636 auto* result
= NewArrayBufferWithContents(
2637 cx
, nbytes
, contents
.get(),
2638 JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory
);
2640 // If and only if an ArrayBuffer is successfully created, ownership of
2641 // |contents| is transferred to the new ArrayBuffer.
2642 (void)contents
.release();
2647 JS_PUBLIC_API JSObject
* JS::NewArrayBufferWithContents(
2648 JSContext
* cx
, size_t nbytes
, void* data
, NewArrayBufferOutOfMemory
) {
2651 MOZ_ASSERT_IF(!data
, nbytes
== 0);
2654 // Don't pass nulled contents to |createForContents|.
2655 return ArrayBufferObject::createZeroed(cx
, 0);
2658 using BufferContents
= ArrayBufferObject::BufferContents
;
2660 BufferContents contents
= BufferContents::createMallocedUnknownArena(data
);
2661 return ArrayBufferObject::createForContents(cx
, nbytes
, contents
);
2664 JS_PUBLIC_API JSObject
* JS::CopyArrayBuffer(JSContext
* cx
,
2665 Handle
<JSObject
*> arrayBuffer
) {
2669 MOZ_ASSERT(arrayBuffer
!= nullptr);
2671 Rooted
<ArrayBufferObject
*> unwrappedSource(
2672 cx
, UnwrapOrReportArrayBuffer(cx
, arrayBuffer
));
2673 if (!unwrappedSource
) {
2677 if (unwrappedSource
->isDetached()) {
2678 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2679 JSMSG_TYPED_ARRAY_DETACHED
);
2683 return ArrayBufferObject::copy(cx
, unwrappedSource
->byteLength(),
2687 JS_PUBLIC_API JSObject
* JS::NewExternalArrayBuffer(
2688 JSContext
* cx
, size_t nbytes
,
2689 mozilla::UniquePtr
<void, JS::BufferContentsDeleter
> contents
) {
2693 MOZ_ASSERT(contents
);
2695 using BufferContents
= ArrayBufferObject::BufferContents
;
2697 BufferContents bufferContents
= BufferContents::createExternal(
2698 contents
.get(), contents
.get_deleter().freeFunc(),
2699 contents
.get_deleter().userData());
2701 ArrayBufferObject::createForContents(cx
, nbytes
, bufferContents
);
2703 // If and only if an ArrayBuffer is successfully created, ownership of
2704 // |contents| is transferred to the new ArrayBuffer.
2705 (void)contents
.release();
2710 JS_PUBLIC_API JSObject
* JS::NewArrayBufferWithUserOwnedContents(JSContext
* cx
,
2718 using BufferContents
= ArrayBufferObject::BufferContents
;
2720 BufferContents contents
= BufferContents::createUserOwned(data
);
2721 return ArrayBufferObject::createForContents(cx
, nbytes
, contents
);
2724 JS_PUBLIC_API
bool JS::IsArrayBufferObject(JSObject
* obj
) {
2725 return obj
->canUnwrapAs
<ArrayBufferObject
>();
2728 JS_PUBLIC_API
bool JS::ArrayBufferHasData(JSObject
* obj
) {
2729 return !obj
->unwrapAs
<ArrayBufferObject
>().isDetached();
2732 JS_PUBLIC_API JSObject
* JS::UnwrapArrayBuffer(JSObject
* obj
) {
2733 return obj
->maybeUnwrapIf
<ArrayBufferObject
>();
2736 JS_PUBLIC_API JSObject
* JS::UnwrapSharedArrayBuffer(JSObject
* obj
) {
2737 return obj
->maybeUnwrapIf
<SharedArrayBufferObject
>();
2740 JS_PUBLIC_API
void* JS::StealArrayBufferContents(JSContext
* cx
,
2746 Rooted
<ArrayBufferObject
*> unwrappedBuffer(
2747 cx
, UnwrapOrReportArrayBuffer(cx
, obj
));
2748 if (!unwrappedBuffer
) {
2752 if (unwrappedBuffer
->isDetached()) {
2753 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2754 JSMSG_TYPED_ARRAY_DETACHED
);
2758 if (unwrappedBuffer
->hasDefinedDetachKey()) {
2759 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2760 JSMSG_WASM_NO_TRANSFER
);
2764 AutoRealm
ar(cx
, unwrappedBuffer
);
2765 return ArrayBufferObject::stealMallocedContents(cx
, unwrappedBuffer
);
2768 JS_PUBLIC_API JSObject
* JS::NewMappedArrayBufferWithContents(JSContext
* cx
,
2776 using BufferContents
= ArrayBufferObject::BufferContents
;
2778 BufferContents contents
= BufferContents::createMapped(data
);
2779 return ArrayBufferObject::createForContents(cx
, nbytes
, contents
);
2782 JS_PUBLIC_API
void* JS::CreateMappedArrayBufferContents(int fd
, size_t offset
,
2784 return ArrayBufferObject::createMappedContents(fd
, offset
, length
).data();
2787 JS_PUBLIC_API
void JS::ReleaseMappedArrayBufferContents(void* contents
,
2789 gc::DeallocateMappedContent(contents
, length
);
2792 JS_PUBLIC_API
bool JS::IsMappedArrayBufferObject(JSObject
* obj
) {
2793 ArrayBufferObject
* aobj
= obj
->maybeUnwrapIf
<ArrayBufferObject
>();
2798 return aobj
->isMapped();
2801 JS_PUBLIC_API JSObject
* JS::GetObjectAsArrayBuffer(JSObject
* obj
,
2804 ArrayBufferObject
* aobj
= obj
->maybeUnwrapIf
<ArrayBufferObject
>();
2809 *length
= aobj
->byteLength();
2810 *data
= aobj
->dataPointer();
2815 JS_PUBLIC_API
void JS::GetArrayBufferLengthAndData(JSObject
* obj
,
2817 bool* isSharedMemory
,
2819 auto& aobj
= obj
->as
<ArrayBufferObject
>();
2820 *length
= aobj
.byteLength();
2821 *data
= aobj
.dataPointer();
2822 *isSharedMemory
= false;
2825 const JSClass
* const JS::ArrayBuffer::FixedLengthUnsharedClass
=
2826 &FixedLengthArrayBufferObject::class_
;
2827 const JSClass
* const JS::ArrayBuffer::ResizableUnsharedClass
=
2828 &ResizableArrayBufferObject::class_
;
2829 const JSClass
* const JS::ArrayBuffer::SharedClass
=
2830 &SharedArrayBufferObject::class_
;
2832 /* static */ JS::ArrayBuffer
JS::ArrayBuffer::create(JSContext
* cx
,
2836 return JS::ArrayBuffer(ArrayBufferObject::createZeroed(cx
, nbytes
));
2839 mozilla::Span
<uint8_t> JS::ArrayBuffer::getData(
2840 bool* isSharedMemory
, const JS::AutoRequireNoGC
& nogc
) {
2841 auto* buffer
= obj
->maybeUnwrapAs
<ArrayBufferObjectMaybeShared
>();
2845 size_t length
= buffer
->byteLength();
2846 if (buffer
->is
<SharedArrayBufferObject
>()) {
2847 *isSharedMemory
= true;
2848 return {buffer
->dataPointerEither().unwrap(), length
};
2850 *isSharedMemory
= false;
2851 return {buffer
->as
<ArrayBufferObject
>().dataPointer(), length
};
2854 JS::ArrayBuffer
JS::ArrayBuffer::unwrap(JSObject
* maybeWrapped
) {
2855 if (!maybeWrapped
) {
2856 return JS::ArrayBuffer(nullptr);
2858 auto* ab
= maybeWrapped
->maybeUnwrapIf
<ArrayBufferObjectMaybeShared
>();
2859 return fromObject(ab
);
2862 bool JS::ArrayBufferCopyData(JSContext
* cx
, Handle
<JSObject
*> toBlock
,
2863 size_t toIndex
, Handle
<JSObject
*> fromBlock
,
2864 size_t fromIndex
, size_t count
) {
2865 Rooted
<ArrayBufferObjectMaybeShared
*> unwrappedToBlock(
2866 cx
, toBlock
->maybeUnwrapIf
<ArrayBufferObjectMaybeShared
>());
2867 if (!unwrappedToBlock
) {
2868 ReportAccessDenied(cx
);
2872 Rooted
<ArrayBufferObjectMaybeShared
*> unwrappedFromBlock(
2873 cx
, fromBlock
->maybeUnwrapIf
<ArrayBufferObjectMaybeShared
>());
2874 if (!unwrappedFromBlock
) {
2875 ReportAccessDenied(cx
);
2879 // Verify that lengths still make sense and throw otherwise.
2880 if (toIndex
+ count
< toIndex
|| // size_t overflow
2881 fromIndex
+ count
< fromIndex
|| // size_t overflow
2882 toIndex
+ count
> unwrappedToBlock
->byteLength() ||
2883 fromIndex
+ count
> unwrappedFromBlock
->byteLength()) {
2884 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2885 JSMSG_ARRAYBUFFER_COPY_RANGE
);
2889 // If both are array buffers, can use ArrayBufferCopyData
2890 if (unwrappedToBlock
->is
<ArrayBufferObject
>() &&
2891 unwrappedFromBlock
->is
<ArrayBufferObject
>()) {
2892 Rooted
<ArrayBufferObject
*> toArray(
2893 cx
, &unwrappedToBlock
->as
<ArrayBufferObject
>());
2894 Rooted
<ArrayBufferObject
*> fromArray(
2895 cx
, &unwrappedFromBlock
->as
<ArrayBufferObject
>());
2896 ArrayBufferObject::copyData(toArray
, toIndex
, fromArray
, fromIndex
, count
);
2900 Rooted
<ArrayBufferObjectMaybeShared
*> toArray(
2901 cx
, &unwrappedToBlock
->as
<ArrayBufferObjectMaybeShared
>());
2902 Rooted
<ArrayBufferObjectMaybeShared
*> fromArray(
2903 cx
, &unwrappedFromBlock
->as
<ArrayBufferObjectMaybeShared
>());
2904 SharedArrayBufferObject::copyData(toArray
, toIndex
, fromArray
, fromIndex
,
2910 // https://tc39.es/ecma262/#sec-clonearraybuffer
2911 // We only support the case where cloneConstructor is %ArrayBuffer%. Note,
2912 // this means that cloning a SharedArrayBuffer will produce an ArrayBuffer
2913 JSObject
* JS::ArrayBufferClone(JSContext
* cx
, Handle
<JSObject
*> srcBuffer
,
2914 size_t srcByteOffset
, size_t srcLength
) {
2915 MOZ_ASSERT(srcBuffer
->is
<ArrayBufferObjectMaybeShared
>());
2917 // 2. (reordered) If IsDetachedBuffer(srcBuffer) is true, throw a TypeError
2919 if (IsDetachedArrayBufferObject(srcBuffer
)) {
2920 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2921 JSMSG_TYPED_ARRAY_DETACHED
);
2925 // 1. Let targetBuffer be ? AllocateArrayBuffer(cloneConstructor, srcLength).
2926 JS::RootedObject
targetBuffer(cx
, JS::NewArrayBuffer(cx
, srcLength
));
2927 if (!targetBuffer
) {
2931 // 3. Let srcBlock be srcBuffer.[[ArrayBufferData]].
2932 // 4. Let targetBlock be targetBuffer.[[ArrayBufferData]].
2933 // 5. Perform CopyDataBlockBytes(targetBlock, 0, srcBlock, srcByteOffset,
2935 if (!ArrayBufferCopyData(cx
, targetBuffer
, 0, srcBuffer
, srcByteOffset
,
2940 // 6. Return targetBuffer.
2941 return targetBuffer
;