1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  * vim: set ts=8 sts=2 et sw=2 tw=80:
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "vm/ArrayBufferViewObject.h"
8 
9 #include "builtin/DataViewObject.h"
10 #include "gc/Nursery.h"
11 #include "js/experimental/TypedData.h"  // JS_GetArrayBufferView{Data,Buffer,Length,ByteOffset}, JS_GetObjectAsArrayBufferView, JS_IsArrayBufferViewObject
12 #include "js/SharedArrayBuffer.h"
13 #include "vm/JSContext.h"
14 #include "vm/TypedArrayObject.h"
15 
16 #include "gc/Nursery-inl.h"
17 #include "vm/ArrayBufferObject-inl.h"
18 #include "vm/NativeObject-inl.h"
19 
20 using namespace js;
21 
22 /*
23  * This method is used to trace TypedArrayObjects and DataViewObjects. We need
24  * a custom tracer to move the object's data pointer if its owner was moved and
25  * stores its data inline.
26  */
27 /* static */
trace(JSTracer * trc,JSObject * objArg)28 void ArrayBufferViewObject::trace(JSTracer* trc, JSObject* objArg) {
29   ArrayBufferViewObject* obj = &objArg->as<ArrayBufferViewObject>();
30   HeapSlot& bufSlot = obj->getFixedSlotRef(BUFFER_SLOT);
31   TraceEdge(trc, &bufSlot, "ArrayBufferViewObject.buffer");
32 
33   // Update obj's data pointer if it moved.
34   if (bufSlot.isObject()) {
35     if (gc::MaybeForwardedObjectIs<ArrayBufferObject>(&bufSlot.toObject())) {
36       ArrayBufferObject& buf =
37           gc::MaybeForwardedObjectAs<ArrayBufferObject>(&bufSlot.toObject());
38       size_t offset = obj->byteOffset();
39 
40       MOZ_ASSERT_IF(buf.dataPointer() == nullptr, offset == 0);
41 
42       // The data may or may not be inline with the buffer. The buffer
43       // can only move during a compacting GC, in which case its
44       // objectMoved hook has already updated the buffer's data pointer.
45       size_t nfixed = obj->numFixedSlotsMaybeForwarded();
46       obj->setPrivateUnbarriered(nfixed, buf.dataPointer() + offset);
47     }
48   }
49 }
50 
51 template <>
is() const52 bool JSObject::is<js::ArrayBufferViewObject>() const {
53   return is<DataViewObject>() || is<TypedArrayObject>();
54 }
55 
notifyBufferDetached()56 void ArrayBufferViewObject::notifyBufferDetached() {
57   MOZ_ASSERT(!isSharedMemory());
58   MOZ_ASSERT(hasBuffer());
59 
60   setFixedSlot(LENGTH_SLOT, PrivateValue(size_t(0)));
61   setFixedSlot(BYTEOFFSET_SLOT, PrivateValue(size_t(0)));
62 
63   setPrivate(nullptr);
64 }
65 
66 /* static */
bufferObject(JSContext * cx,Handle<ArrayBufferViewObject * > thisObject)67 ArrayBufferObjectMaybeShared* ArrayBufferViewObject::bufferObject(
68     JSContext* cx, Handle<ArrayBufferViewObject*> thisObject) {
69   if (thisObject->is<TypedArrayObject>()) {
70     Rooted<TypedArrayObject*> typedArray(cx,
71                                          &thisObject->as<TypedArrayObject>());
72     if (!TypedArrayObject::ensureHasBuffer(cx, typedArray)) {
73       return nullptr;
74     }
75   }
76   return thisObject->bufferEither();
77 }
78 
init(JSContext * cx,ArrayBufferObjectMaybeShared * buffer,size_t byteOffset,size_t length,uint32_t bytesPerElement)79 bool ArrayBufferViewObject::init(JSContext* cx,
80                                  ArrayBufferObjectMaybeShared* buffer,
81                                  size_t byteOffset, size_t length,
82                                  uint32_t bytesPerElement) {
83   MOZ_ASSERT_IF(!buffer, byteOffset == 0);
84   MOZ_ASSERT_IF(buffer, !buffer->isDetached());
85 
86   MOZ_ASSERT(byteOffset <= ArrayBufferObject::maxBufferByteLength());
87   MOZ_ASSERT(length <= ArrayBufferObject::maxBufferByteLength());
88   MOZ_ASSERT(byteOffset + length <= ArrayBufferObject::maxBufferByteLength());
89 
90   MOZ_ASSERT_IF(is<TypedArrayObject>(),
91                 length <= TypedArrayObject::maxByteLength() / bytesPerElement);
92 
93   // The isSharedMemory property is invariant.  Self-hosting code that
94   // sets BUFFER_SLOT or the private slot (if it does) must maintain it by
95   // always setting those to reference shared memory.
96   if (buffer && buffer->is<SharedArrayBufferObject>()) {
97     setIsSharedMemory();
98   }
99 
100   initFixedSlot(BYTEOFFSET_SLOT, PrivateValue(byteOffset));
101   initFixedSlot(LENGTH_SLOT, PrivateValue(length));
102   initFixedSlot(BUFFER_SLOT, ObjectOrNullValue(buffer));
103 
104   if (buffer) {
105     SharedMem<uint8_t*> ptr = buffer->dataPointerEither();
106     initDataPointer(ptr + byteOffset);
107 
108     // Only ArrayBuffers used for inline typed objects can have
109     // nursery-allocated data and we shouldn't see such buffers here.
110     MOZ_ASSERT_IF(buffer->byteLength() > 0, !cx->nursery().isInside(ptr));
111   } else {
112     MOZ_ASSERT(is<TypedArrayObject>());
113     MOZ_ASSERT(length * bytesPerElement <=
114                TypedArrayObject::INLINE_BUFFER_LIMIT);
115     void* data = fixedData(TypedArrayObject::FIXED_DATA_START);
116     initPrivate(data);
117     memset(data, 0, length * bytesPerElement);
118 #ifdef DEBUG
119     if (length == 0) {
120       uint8_t* elements = static_cast<uint8_t*>(data);
121       elements[0] = ZeroLengthArrayData;
122     }
123 #endif
124   }
125 
126 #ifdef DEBUG
127   if (buffer) {
128     size_t viewByteLength = length * bytesPerElement;
129     size_t viewByteOffset = byteOffset;
130     size_t bufferByteLength = buffer->byteLength();
131     // Unwraps are safe: both are for the pointer value.
132     MOZ_ASSERT_IF(buffer->is<ArrayBufferObject>(),
133                   buffer->dataPointerEither().unwrap(/*safe*/) <=
134                       dataPointerEither().unwrap(/*safe*/));
135     MOZ_ASSERT(bufferByteLength - viewByteOffset >= viewByteLength);
136     MOZ_ASSERT(viewByteOffset <= bufferByteLength);
137   }
138 
139   // Verify that the private slot is at the expected place.
140   MOZ_ASSERT(numFixedSlots() == DATA_SLOT);
141 #endif
142 
143   // ArrayBufferObjects track their views to support detaching.
144   if (buffer && buffer->is<ArrayBufferObject>()) {
145     if (!buffer->as<ArrayBufferObject>().addView(cx, this)) {
146       return false;
147     }
148   }
149 
150   return true;
151 }
152 
153 /* JS Public API */
154 
JS_IsArrayBufferViewObject(JSObject * obj)155 JS_PUBLIC_API bool JS_IsArrayBufferViewObject(JSObject* obj) {
156   return obj->canUnwrapAs<ArrayBufferViewObject>();
157 }
158 
UnwrapArrayBufferView(JSObject * obj)159 JS_PUBLIC_API JSObject* js::UnwrapArrayBufferView(JSObject* obj) {
160   return obj->maybeUnwrapIf<ArrayBufferViewObject>();
161 }
162 
JS_GetArrayBufferViewData(JSObject * obj,bool * isSharedMemory,const JS::AutoRequireNoGC &)163 JS_PUBLIC_API void* JS_GetArrayBufferViewData(JSObject* obj,
164                                               bool* isSharedMemory,
165                                               const JS::AutoRequireNoGC&) {
166   ArrayBufferViewObject* view = obj->maybeUnwrapAs<ArrayBufferViewObject>();
167   if (!view) {
168     return nullptr;
169   }
170 
171   *isSharedMemory = view->isSharedMemory();
172   return view->dataPointerEither().unwrap(
173       /*safe - caller sees isSharedMemory flag*/);
174 }
175 
JS_GetArrayBufferViewFixedData(JSObject * obj,uint8_t * buffer,size_t bufSize)176 JS_PUBLIC_API uint8_t* JS_GetArrayBufferViewFixedData(JSObject* obj,
177                                                       uint8_t* buffer,
178                                                       size_t bufSize) {
179   ArrayBufferViewObject* view = obj->maybeUnwrapAs<ArrayBufferViewObject>();
180   if (!view) {
181     return nullptr;
182   }
183 
184   // Disallow shared memory until it is needed.
185   if (view->isSharedMemory()) {
186     return nullptr;
187   }
188 
189   // TypedArrays (but not DataViews) can have inline data, in which case we
190   // need to copy into the given buffer.
191   if (view->is<TypedArrayObject>()) {
192     TypedArrayObject* ta = &view->as<TypedArrayObject>();
193     if (ta->hasInlineElements()) {
194       size_t bytes = ta->byteLength();
195       if (bytes > bufSize) {
196         return nullptr;  // Does not fit.
197       }
198       memcpy(buffer, view->dataPointerUnshared(), bytes);
199       return buffer;
200     }
201   }
202 
203   return static_cast<uint8_t*>(view->dataPointerUnshared());
204 }
205 
JS_GetArrayBufferViewBuffer(JSContext * cx,HandleObject obj,bool * isSharedMemory)206 JS_PUBLIC_API JSObject* JS_GetArrayBufferViewBuffer(JSContext* cx,
207                                                     HandleObject obj,
208                                                     bool* isSharedMemory) {
209   AssertHeapIsIdle();
210   CHECK_THREAD(cx);
211   cx->check(obj);
212 
213   Rooted<ArrayBufferViewObject*> unwrappedView(
214       cx, obj->maybeUnwrapAs<ArrayBufferViewObject>());
215   if (!unwrappedView) {
216     ReportAccessDenied(cx);
217     return nullptr;
218   }
219 
220   ArrayBufferObjectMaybeShared* unwrappedBuffer;
221   {
222     AutoRealm ar(cx, unwrappedView);
223     unwrappedBuffer = ArrayBufferViewObject::bufferObject(cx, unwrappedView);
224     if (!unwrappedBuffer) {
225       return nullptr;
226     }
227   }
228   *isSharedMemory = unwrappedBuffer->is<SharedArrayBufferObject>();
229 
230   RootedObject buffer(cx, unwrappedBuffer);
231   if (!cx->compartment()->wrap(cx, &buffer)) {
232     return nullptr;
233   }
234 
235   return buffer;
236 }
237 
JS_GetArrayBufferViewByteLength(JSObject * obj)238 JS_PUBLIC_API size_t JS_GetArrayBufferViewByteLength(JSObject* obj) {
239   obj = obj->maybeUnwrapAs<ArrayBufferViewObject>();
240   if (!obj) {
241     return 0;
242   }
243   size_t length = obj->is<DataViewObject>()
244                       ? obj->as<DataViewObject>().byteLength()
245                       : obj->as<TypedArrayObject>().byteLength();
246   return length;
247 }
248 
JS_GetArrayBufferViewByteOffset(JSObject * obj)249 JS_PUBLIC_API size_t JS_GetArrayBufferViewByteOffset(JSObject* obj) {
250   obj = obj->maybeUnwrapAs<ArrayBufferViewObject>();
251   if (!obj) {
252     return 0;
253   }
254   size_t offset = obj->is<DataViewObject>()
255                       ? obj->as<DataViewObject>().byteOffset()
256                       : obj->as<TypedArrayObject>().byteOffset();
257   return offset;
258 }
259 
JS_GetObjectAsArrayBufferView(JSObject * obj,size_t * length,bool * isSharedMemory,uint8_t ** data)260 JS_PUBLIC_API JSObject* JS_GetObjectAsArrayBufferView(JSObject* obj,
261                                                       size_t* length,
262                                                       bool* isSharedMemory,
263                                                       uint8_t** data) {
264   obj = obj->maybeUnwrapIf<ArrayBufferViewObject>();
265   if (!obj) {
266     return nullptr;
267   }
268 
269   js::GetArrayBufferViewLengthAndData(obj, length, isSharedMemory, data);
270   return obj;
271 }
272 
GetArrayBufferViewLengthAndData(JSObject * obj,size_t * length,bool * isSharedMemory,uint8_t ** data)273 JS_PUBLIC_API void js::GetArrayBufferViewLengthAndData(JSObject* obj,
274                                                        size_t* length,
275                                                        bool* isSharedMemory,
276                                                        uint8_t** data) {
277   MOZ_ASSERT(obj->is<ArrayBufferViewObject>());
278 
279   size_t byteLength = obj->is<DataViewObject>()
280                           ? obj->as<DataViewObject>().byteLength()
281                           : obj->as<TypedArrayObject>().byteLength();
282   *length = byteLength;
283 
284   ArrayBufferViewObject& view = obj->as<ArrayBufferViewObject>();
285   *isSharedMemory = view.isSharedMemory();
286   *data = static_cast<uint8_t*>(
287       view.dataPointerEither().unwrap(/*safe - caller sees isShared flag*/));
288 }
289 
IsArrayBufferViewShared(JSObject * obj)290 JS_PUBLIC_API bool JS::IsArrayBufferViewShared(JSObject* obj) {
291   ArrayBufferViewObject* view = obj->maybeUnwrapAs<ArrayBufferViewObject>();
292   if (!view) {
293     return false;
294   }
295   return view->isSharedMemory();
296 }
297 
IsLargeArrayBufferView(JSObject * obj)298 JS_PUBLIC_API bool JS::IsLargeArrayBufferView(JSObject* obj) {
299 #ifdef JS_64BIT
300   obj = &obj->unwrapAs<ArrayBufferViewObject>();
301   size_t len = obj->is<DataViewObject>()
302                    ? obj->as<DataViewObject>().byteLength()
303                    : obj->as<TypedArrayObject>().byteLength();
304   return len > ArrayBufferObject::MaxByteLengthForSmallBuffer;
305 #else
306   // Large ArrayBuffers are not supported on 32-bit.
307   MOZ_ASSERT(ArrayBufferObject::maxBufferByteLength() ==
308              ArrayBufferObject::MaxByteLengthForSmallBuffer);
309   return false;
310 #endif
311 }
312