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/ArrayBufferViewObject.h"
9 #include "builtin/DataViewObject.h"
10 #include "gc/Nursery.h"
11 #include "js/ErrorReport.h"
12 #include "js/experimental/TypedData.h" // JS_GetArrayBufferView{Data,Buffer,Length,ByteOffset}, JS_GetObjectAsArrayBufferView, JS_IsArrayBufferViewObject
13 #include "js/SharedArrayBuffer.h"
14 #include "vm/Compartment.h"
15 #include "vm/JSContext.h"
16 #include "vm/TypedArrayObject.h"
18 #include "gc/Nursery-inl.h"
19 #include "vm/ArrayBufferObject-inl.h"
20 #include "vm/NativeObject-inl.h"
24 // This method is used to trace TypedArrayObjects and DataViewObjects. It
25 // updates the object's data pointer if it points to inline data in an object
28 void ArrayBufferViewObject::trace(JSTracer
* trc
, JSObject
* obj
) {
29 ArrayBufferViewObject
* view
= &obj
->as
<ArrayBufferViewObject
>();
31 // Update view's data pointer if it moved.
32 if (view
->hasBuffer()) {
33 JSObject
* bufferObj
= &view
->bufferValue().toObject();
34 ArrayBufferObject
* buffer
= nullptr;
35 if (gc::MaybeForwardedObjectIs
<FixedLengthArrayBufferObject
>(bufferObj
)) {
37 &gc::MaybeForwardedObjectAs
<FixedLengthArrayBufferObject
>(bufferObj
);
38 } else if (gc::MaybeForwardedObjectIs
<ResizableArrayBufferObject
>(
41 &gc::MaybeForwardedObjectAs
<ResizableArrayBufferObject
>(bufferObj
);
44 size_t offset
= view
->byteOffset();
45 MOZ_ASSERT_IF(!buffer
->dataPointer(), offset
== 0);
47 // The data may or may not be inline with the buffer. The buffer can only
48 // move during a compacting GC, in which case its objectMoved hook has
49 // already updated the buffer's data pointer.
50 view
->notifyBufferMoved(
51 static_cast<uint8_t*>(view
->dataPointerEither_()) - offset
,
52 buffer
->dataPointer());
58 bool JSObject::is
<js::ArrayBufferViewObject
>() const {
59 return is
<DataViewObject
>() || is
<TypedArrayObject
>();
62 void ArrayBufferViewObject::notifyBufferDetached() {
63 MOZ_ASSERT(!isSharedMemory());
64 MOZ_ASSERT(hasBuffer());
65 MOZ_ASSERT(!bufferUnshared()->isLengthPinned());
67 setFixedSlot(LENGTH_SLOT
, PrivateValue(size_t(0)));
68 setFixedSlot(BYTEOFFSET_SLOT
, PrivateValue(size_t(0)));
69 setFixedSlot(DATA_SLOT
, UndefinedValue());
72 void ArrayBufferViewObject::notifyBufferMoved(uint8_t* srcBufStart
,
73 uint8_t* dstBufStart
) {
74 MOZ_ASSERT(!isSharedMemory());
75 MOZ_ASSERT(hasBuffer());
77 if (srcBufStart
!= dstBufStart
) {
78 void* data
= dstBufStart
+ byteOffset();
79 getFixedSlotRef(DATA_SLOT
).unbarrieredSet(PrivateValue(data
));
84 bool ArrayBufferViewObject::ensureNonInline(
85 JSContext
* cx
, Handle
<ArrayBufferViewObject
*> view
) {
86 MOZ_ASSERT(!view
->isSharedMemory());
87 // Create an ArrayBuffer for the data if it was in the view.
88 ArrayBufferObjectMaybeShared
* buffer
= ensureBufferObject(cx
, view
);
92 Rooted
<ArrayBufferObject
*> unsharedBuffer(cx
,
93 &buffer
->as
<ArrayBufferObject
>());
94 return ArrayBufferObject::ensureNonInline(cx
, unsharedBuffer
);
98 ArrayBufferObjectMaybeShared
* ArrayBufferViewObject::ensureBufferObject(
99 JSContext
* cx
, Handle
<ArrayBufferViewObject
*> thisObject
) {
100 if (thisObject
->is
<TypedArrayObject
>()) {
101 Rooted
<TypedArrayObject
*> typedArray(cx
,
102 &thisObject
->as
<TypedArrayObject
>());
103 if (!TypedArrayObject::ensureHasBuffer(cx
, typedArray
)) {
107 return thisObject
->bufferEither();
110 bool ArrayBufferViewObject::init(JSContext
* cx
,
111 ArrayBufferObjectMaybeShared
* buffer
,
112 size_t byteOffset
, size_t length
,
113 uint32_t bytesPerElement
) {
114 MOZ_ASSERT_IF(!buffer
, byteOffset
== 0);
115 MOZ_ASSERT_IF(buffer
, !buffer
->isDetached());
117 MOZ_ASSERT(byteOffset
<= ArrayBufferObject::MaxByteLength
);
118 MOZ_ASSERT(length
<= ArrayBufferObject::MaxByteLength
);
119 MOZ_ASSERT(byteOffset
+ length
<= ArrayBufferObject::MaxByteLength
);
121 MOZ_ASSERT_IF(is
<TypedArrayObject
>(),
122 length
<= TypedArrayObject::MaxByteLength
/ bytesPerElement
);
124 // The isSharedMemory property is invariant. Self-hosting code that
125 // sets BUFFER_SLOT or the private slot (if it does) must maintain it by
126 // always setting those to reference shared memory.
127 if (buffer
&& buffer
->is
<SharedArrayBufferObject
>()) {
131 initFixedSlot(BYTEOFFSET_SLOT
, PrivateValue(byteOffset
));
132 initFixedSlot(LENGTH_SLOT
, PrivateValue(length
));
134 initFixedSlot(BUFFER_SLOT
, ObjectValue(*buffer
));
136 MOZ_ASSERT(!isSharedMemory());
137 initFixedSlot(BUFFER_SLOT
, JS::FalseValue());
141 SharedMem
<uint8_t*> ptr
= buffer
->dataPointerEither();
142 initDataPointer(ptr
+ byteOffset
);
144 // Only ArrayBuffers used for inline typed objects can have
145 // nursery-allocated data and we shouldn't see such buffers here.
146 MOZ_ASSERT_IF(buffer
->byteLength() > 0, !cx
->nursery().isInside(ptr
));
148 MOZ_ASSERT(is
<TypedArrayObject
>());
149 MOZ_ASSERT(length
* bytesPerElement
<=
150 TypedArrayObject::INLINE_BUFFER_LIMIT
);
151 void* data
= fixedData(TypedArrayObject::FIXED_DATA_START
);
152 initReservedSlot(DATA_SLOT
, PrivateValue(data
));
153 memset(data
, 0, length
* bytesPerElement
);
156 uint8_t* elements
= static_cast<uint8_t*>(data
);
157 elements
[0] = ZeroLengthArrayData
;
164 size_t viewByteLength
= length
* bytesPerElement
;
165 size_t viewByteOffset
= byteOffset
;
166 size_t bufferByteLength
= buffer
->byteLength();
167 // Unwraps are safe: both are for the pointer value.
168 MOZ_ASSERT_IF(buffer
->is
<ArrayBufferObject
>(),
169 buffer
->dataPointerEither().unwrap(/*safe*/) <=
170 dataPointerEither().unwrap(/*safe*/));
171 MOZ_ASSERT(bufferByteLength
- viewByteOffset
>= viewByteLength
);
172 MOZ_ASSERT(viewByteOffset
<= bufferByteLength
);
176 // ArrayBufferObjects track their views to support detaching.
177 if (buffer
&& buffer
->is
<ArrayBufferObject
>()) {
178 if (!buffer
->as
<ArrayBufferObject
>().addView(cx
, this)) {
186 bool ArrayBufferViewObject::hasResizableBuffer() const {
187 if (auto* buffer
= bufferEither()) {
188 return buffer
->isResizable();
195 JS_PUBLIC_API
bool JS_IsArrayBufferViewObject(JSObject
* obj
) {
196 return obj
->canUnwrapAs
<ArrayBufferViewObject
>();
199 JS_PUBLIC_API JSObject
* js::UnwrapArrayBufferView(JSObject
* obj
) {
200 return obj
->maybeUnwrapIf
<ArrayBufferViewObject
>();
203 JS_PUBLIC_API
void* JS_GetArrayBufferViewData(JSObject
* obj
,
204 bool* isSharedMemory
,
205 const JS::AutoRequireNoGC
&) {
206 ArrayBufferViewObject
* view
= obj
->maybeUnwrapAs
<ArrayBufferViewObject
>();
211 *isSharedMemory
= view
->isSharedMemory();
212 return view
->dataPointerEither().unwrap(
213 /*safe - caller sees isSharedMemory flag*/);
216 JS_PUBLIC_API
uint8_t* JS_GetArrayBufferViewFixedData(JSObject
* obj
,
219 ArrayBufferViewObject
* view
= obj
->maybeUnwrapAs
<ArrayBufferViewObject
>();
224 // Disallow shared memory until it is needed.
225 if (view
->isSharedMemory()) {
229 // TypedArrays (but not DataViews) can have inline data, in which case we
230 // need to copy into the given buffer.
231 if (view
->is
<TypedArrayObject
>()) {
232 TypedArrayObject
* ta
= &view
->as
<TypedArrayObject
>();
233 if (ta
->hasInlineElements()) {
234 size_t bytes
= ta
->byteLength();
235 if (bytes
> bufSize
) {
236 return nullptr; // Does not fit.
238 memcpy(buffer
, view
->dataPointerUnshared(), bytes
);
243 return static_cast<uint8_t*>(view
->dataPointerUnshared());
246 JS_PUBLIC_API JSObject
* JS_GetArrayBufferViewBuffer(JSContext
* cx
,
248 bool* isSharedMemory
) {
253 Rooted
<ArrayBufferViewObject
*> unwrappedView(
254 cx
, obj
->maybeUnwrapAs
<ArrayBufferViewObject
>());
255 if (!unwrappedView
) {
256 ReportAccessDenied(cx
);
260 ArrayBufferObjectMaybeShared
* unwrappedBuffer
;
262 AutoRealm
ar(cx
, unwrappedView
);
264 ArrayBufferViewObject::ensureBufferObject(cx
, unwrappedView
);
265 if (!unwrappedBuffer
) {
269 *isSharedMemory
= unwrappedBuffer
->is
<SharedArrayBufferObject
>();
271 RootedObject
buffer(cx
, unwrappedBuffer
);
272 if (!cx
->compartment()->wrap(cx
, &buffer
)) {
279 JS_PUBLIC_API
size_t JS_GetArrayBufferViewByteLength(JSObject
* obj
) {
280 obj
= obj
->maybeUnwrapAs
<ArrayBufferViewObject
>();
284 size_t length
= obj
->is
<DataViewObject
>()
285 ? obj
->as
<DataViewObject
>().byteLength().valueOr(0)
286 : obj
->as
<TypedArrayObject
>().byteLength();
290 bool JS::ArrayBufferView::isDetached() const {
292 return obj
->as
<ArrayBufferViewObject
>().hasDetachedBuffer();
295 bool JS::ArrayBufferView::isResizable() const {
297 return obj
->as
<ArrayBufferViewObject
>().hasResizableBuffer();
300 JS_PUBLIC_API
size_t JS_GetArrayBufferViewByteOffset(JSObject
* obj
) {
301 obj
= obj
->maybeUnwrapAs
<ArrayBufferViewObject
>();
305 size_t offset
= obj
->is
<DataViewObject
>()
306 ? obj
->as
<DataViewObject
>().byteOffset().valueOr(0)
307 : obj
->as
<TypedArrayObject
>().byteOffset();
311 JS_PUBLIC_API
mozilla::Span
<uint8_t> JS::ArrayBufferView::getData(
312 bool* isSharedMemory
, const AutoRequireNoGC
&) {
313 MOZ_ASSERT(obj
->is
<ArrayBufferViewObject
>());
314 size_t byteLength
= obj
->is
<DataViewObject
>()
315 ? obj
->as
<DataViewObject
>().byteLength().valueOr(0)
316 : obj
->as
<TypedArrayObject
>().byteLength();
317 ArrayBufferViewObject
& view
= obj
->as
<ArrayBufferViewObject
>();
318 *isSharedMemory
= view
.isSharedMemory();
319 return {static_cast<uint8_t*>(view
.dataPointerEither().unwrap(
320 /*safe - caller sees isShared flag*/)),
324 JS_PUBLIC_API JSObject
* JS_GetObjectAsArrayBufferView(JSObject
* obj
,
326 bool* isSharedMemory
,
328 obj
= obj
->maybeUnwrapIf
<ArrayBufferViewObject
>();
333 js::GetArrayBufferViewLengthAndData(obj
, length
, isSharedMemory
, data
);
337 JS_PUBLIC_API
void js::GetArrayBufferViewLengthAndData(JSObject
* obj
,
339 bool* isSharedMemory
,
341 JS::AutoAssertNoGC nogc
;
343 JS::ArrayBufferView::fromObject(obj
).getData(isSharedMemory
, nogc
);
345 *length
= span
.Length();
348 JS_PUBLIC_API
bool JS::IsArrayBufferViewShared(JSObject
* obj
) {
349 ArrayBufferViewObject
* view
= obj
->maybeUnwrapAs
<ArrayBufferViewObject
>();
353 return view
->isSharedMemory();
356 JS_PUBLIC_API
bool JS::IsLargeArrayBufferView(JSObject
* obj
) {
358 obj
= &obj
->unwrapAs
<ArrayBufferViewObject
>();
359 size_t len
= obj
->is
<DataViewObject
>()
360 ? obj
->as
<DataViewObject
>().byteLength().valueOr(0)
361 : obj
->as
<TypedArrayObject
>().byteLength();
362 return len
> ArrayBufferObject::MaxByteLengthForSmallBuffer
;
364 // Large ArrayBuffers are not supported on 32-bit.
365 static_assert(ArrayBufferObject::MaxByteLength
==
366 ArrayBufferObject::MaxByteLengthForSmallBuffer
);
371 JS_PUBLIC_API
bool JS::IsResizableArrayBufferView(JSObject
* obj
) {
372 auto* view
= &obj
->unwrapAs
<ArrayBufferViewObject
>();
373 if (auto* buffer
= view
->bufferEither()) {
374 return buffer
->isResizable();
379 JS_PUBLIC_API
bool JS::PinArrayBufferOrViewLength(JSObject
* obj
, bool pin
) {
380 ArrayBufferObjectMaybeShared
* buffer
=
381 obj
->maybeUnwrapIf
<ArrayBufferObjectMaybeShared
>();
383 return buffer
->pinLength(pin
);
386 ArrayBufferViewObject
* view
= obj
->maybeUnwrapAs
<ArrayBufferViewObject
>();
388 return view
->pinLength(pin
);
394 JS_PUBLIC_API
bool JS::EnsureNonInlineArrayBufferOrView(JSContext
* cx
,
396 if (obj
->is
<SharedArrayBufferObject
>()) {
397 // Always locked and out of line.
401 auto* buffer
= obj
->maybeUnwrapIf
<ArrayBufferObject
>();
403 Rooted
<ArrayBufferObject
*> rootedBuffer(cx
, buffer
);
404 return ArrayBufferObject::ensureNonInline(cx
, rootedBuffer
);
407 auto* view
= obj
->maybeUnwrapIf
<ArrayBufferViewObject
>();
409 if (view
->isSharedMemory()) {
410 // Always locked and out of line.
413 Rooted
<ArrayBufferViewObject
*> rootedView(cx
, view
);
414 return ArrayBufferViewObject::ensureNonInline(cx
, rootedView
);
417 JS_ReportErrorASCII(cx
, "unhandled type");