Bug 1842773 - Part 11: Make DataView byteOffset and byteLength accessors aware of...
[gecko.git] / js / src / vm / ArrayBufferViewObject.cpp
blob9ed5c88dadf7cbec0c817281a653a00aaa8f95a8
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"
22 using namespace js;
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
26 // that was moved.
27 /* static */
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)) {
36 buffer =
37 &gc::MaybeForwardedObjectAs<FixedLengthArrayBufferObject>(bufferObj);
38 } else if (gc::MaybeForwardedObjectIs<ResizableArrayBufferObject>(
39 bufferObj)) {
40 buffer =
41 &gc::MaybeForwardedObjectAs<ResizableArrayBufferObject>(bufferObj);
43 if (buffer) {
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());
57 template <>
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));
83 /* static */
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);
89 if (!buffer) {
90 return false;
92 Rooted<ArrayBufferObject*> unsharedBuffer(cx,
93 &buffer->as<ArrayBufferObject>());
94 return ArrayBufferObject::ensureNonInline(cx, unsharedBuffer);
97 /* static */
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)) {
104 return nullptr;
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>()) {
128 setIsSharedMemory();
131 initFixedSlot(BYTEOFFSET_SLOT, PrivateValue(byteOffset));
132 initFixedSlot(LENGTH_SLOT, PrivateValue(length));
133 if (buffer) {
134 initFixedSlot(BUFFER_SLOT, ObjectValue(*buffer));
135 } else {
136 MOZ_ASSERT(!isSharedMemory());
137 initFixedSlot(BUFFER_SLOT, JS::FalseValue());
140 if (buffer) {
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));
147 } else {
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);
154 #ifdef DEBUG
155 if (length == 0) {
156 uint8_t* elements = static_cast<uint8_t*>(data);
157 elements[0] = ZeroLengthArrayData;
159 #endif
162 #ifdef DEBUG
163 if (buffer) {
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);
174 #endif
176 // ArrayBufferObjects track their views to support detaching.
177 if (buffer && buffer->is<ArrayBufferObject>()) {
178 if (!buffer->as<ArrayBufferObject>().addView(cx, this)) {
179 return false;
183 return true;
186 bool ArrayBufferViewObject::hasResizableBuffer() const {
187 if (auto* buffer = bufferEither()) {
188 return buffer->isResizable();
190 return false;
193 /* JS Public API */
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>();
207 if (!view) {
208 return nullptr;
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,
217 uint8_t* buffer,
218 size_t bufSize) {
219 ArrayBufferViewObject* view = obj->maybeUnwrapAs<ArrayBufferViewObject>();
220 if (!view) {
221 return nullptr;
224 // Disallow shared memory until it is needed.
225 if (view->isSharedMemory()) {
226 return nullptr;
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);
239 return buffer;
243 return static_cast<uint8_t*>(view->dataPointerUnshared());
246 JS_PUBLIC_API JSObject* JS_GetArrayBufferViewBuffer(JSContext* cx,
247 HandleObject obj,
248 bool* isSharedMemory) {
249 AssertHeapIsIdle();
250 CHECK_THREAD(cx);
251 cx->check(obj);
253 Rooted<ArrayBufferViewObject*> unwrappedView(
254 cx, obj->maybeUnwrapAs<ArrayBufferViewObject>());
255 if (!unwrappedView) {
256 ReportAccessDenied(cx);
257 return nullptr;
260 ArrayBufferObjectMaybeShared* unwrappedBuffer;
262 AutoRealm ar(cx, unwrappedView);
263 unwrappedBuffer =
264 ArrayBufferViewObject::ensureBufferObject(cx, unwrappedView);
265 if (!unwrappedBuffer) {
266 return nullptr;
269 *isSharedMemory = unwrappedBuffer->is<SharedArrayBufferObject>();
271 RootedObject buffer(cx, unwrappedBuffer);
272 if (!cx->compartment()->wrap(cx, &buffer)) {
273 return nullptr;
276 return buffer;
279 JS_PUBLIC_API size_t JS_GetArrayBufferViewByteLength(JSObject* obj) {
280 obj = obj->maybeUnwrapAs<ArrayBufferViewObject>();
281 if (!obj) {
282 return 0;
284 size_t length = obj->is<DataViewObject>()
285 ? obj->as<DataViewObject>().byteLength().valueOr(0)
286 : obj->as<TypedArrayObject>().byteLength();
287 return length;
290 bool JS::ArrayBufferView::isDetached() const {
291 MOZ_ASSERT(obj);
292 return obj->as<ArrayBufferViewObject>().hasDetachedBuffer();
295 bool JS::ArrayBufferView::isResizable() const {
296 MOZ_ASSERT(obj);
297 return obj->as<ArrayBufferViewObject>().hasResizableBuffer();
300 JS_PUBLIC_API size_t JS_GetArrayBufferViewByteOffset(JSObject* obj) {
301 obj = obj->maybeUnwrapAs<ArrayBufferViewObject>();
302 if (!obj) {
303 return 0;
305 size_t offset = obj->is<DataViewObject>()
306 ? obj->as<DataViewObject>().byteOffset().valueOr(0)
307 : obj->as<TypedArrayObject>().byteOffset();
308 return offset;
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*/)),
321 byteLength};
324 JS_PUBLIC_API JSObject* JS_GetObjectAsArrayBufferView(JSObject* obj,
325 size_t* length,
326 bool* isSharedMemory,
327 uint8_t** data) {
328 obj = obj->maybeUnwrapIf<ArrayBufferViewObject>();
329 if (!obj) {
330 return nullptr;
333 js::GetArrayBufferViewLengthAndData(obj, length, isSharedMemory, data);
334 return obj;
337 JS_PUBLIC_API void js::GetArrayBufferViewLengthAndData(JSObject* obj,
338 size_t* length,
339 bool* isSharedMemory,
340 uint8_t** data) {
341 JS::AutoAssertNoGC nogc;
342 auto span =
343 JS::ArrayBufferView::fromObject(obj).getData(isSharedMemory, nogc);
344 *data = span.data();
345 *length = span.Length();
348 JS_PUBLIC_API bool JS::IsArrayBufferViewShared(JSObject* obj) {
349 ArrayBufferViewObject* view = obj->maybeUnwrapAs<ArrayBufferViewObject>();
350 if (!view) {
351 return false;
353 return view->isSharedMemory();
356 JS_PUBLIC_API bool JS::IsLargeArrayBufferView(JSObject* obj) {
357 #ifdef JS_64BIT
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;
363 #else
364 // Large ArrayBuffers are not supported on 32-bit.
365 static_assert(ArrayBufferObject::MaxByteLength ==
366 ArrayBufferObject::MaxByteLengthForSmallBuffer);
367 return false;
368 #endif
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();
376 return false;
379 JS_PUBLIC_API bool JS::PinArrayBufferOrViewLength(JSObject* obj, bool pin) {
380 ArrayBufferObjectMaybeShared* buffer =
381 obj->maybeUnwrapIf<ArrayBufferObjectMaybeShared>();
382 if (buffer) {
383 return buffer->pinLength(pin);
386 ArrayBufferViewObject* view = obj->maybeUnwrapAs<ArrayBufferViewObject>();
387 if (view) {
388 return view->pinLength(pin);
391 return false;
394 JS_PUBLIC_API bool JS::EnsureNonInlineArrayBufferOrView(JSContext* cx,
395 JSObject* obj) {
396 if (obj->is<SharedArrayBufferObject>()) {
397 // Always locked and out of line.
398 return true;
401 auto* buffer = obj->maybeUnwrapIf<ArrayBufferObject>();
402 if (buffer) {
403 Rooted<ArrayBufferObject*> rootedBuffer(cx, buffer);
404 return ArrayBufferObject::ensureNonInline(cx, rootedBuffer);
407 auto* view = obj->maybeUnwrapIf<ArrayBufferViewObject>();
408 if (view) {
409 if (view->isSharedMemory()) {
410 // Always locked and out of line.
411 return true;
413 Rooted<ArrayBufferViewObject*> rootedView(cx, view);
414 return ArrayBufferViewObject::ensureNonInline(cx, rootedView);
417 JS_ReportErrorASCII(cx, "unhandled type");
418 return false;