ArrayBufferViewObject.h (9976B)
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 #ifndef vm_ArrayBufferViewObject_h 8 #define vm_ArrayBufferViewObject_h 9 10 #include "mozilla/Maybe.h" 11 12 #include "builtin/TypedArrayConstants.h" 13 #include "vm/ArrayBufferObject.h" 14 #include "vm/NativeObject.h" 15 #include "vm/SharedArrayObject.h" 16 #include "vm/SharedMem.h" 17 18 namespace js { 19 20 class JS_PUBLIC_API GenericPrinter; 21 class JSONPrinter; 22 23 /* 24 * ArrayBufferViewObject 25 * 26 * Common base class for all array buffer views (DataViewObject and 27 * TypedArrayObject). 28 */ 29 30 class ArrayBufferViewObject : public NativeObject { 31 public: 32 // Underlying (Shared)ArrayBufferObject. ObjectValue if there is 33 // a buffer. Otherwise, the buffer is implicit because the data 34 // is held inline, and the buffer slot will store the pinned status 35 // (FalseValue or TrueValue). 36 static constexpr size_t BUFFER_SLOT = 0; 37 static_assert(BUFFER_SLOT == JS_TYPEDARRAYLAYOUT_BUFFER_SLOT, 38 "self-hosted code with burned-in constants must get the " 39 "right buffer slot"); 40 41 // Slot containing length of the view in number of typed elements. 42 static constexpr size_t LENGTH_SLOT = 1; 43 44 // Offset of view within underlying (Shared)ArrayBufferObject. 45 static constexpr size_t BYTEOFFSET_SLOT = 2; 46 47 // Pointer to raw buffer memory. 48 static constexpr size_t DATA_SLOT = 3; 49 50 static constexpr size_t RESERVED_SLOTS = 4; 51 52 // Additional slots for views on resizable/growable (Shared)ArrayBufferObject. 53 54 static const uint8_t AUTO_LENGTH_SLOT = 4; 55 static const uint8_t INITIAL_LENGTH_SLOT = 5; 56 static const uint8_t INITIAL_BYTE_OFFSET_SLOT = 6; 57 58 static constexpr size_t RESIZABLE_RESERVED_SLOTS = 7; 59 60 #ifdef DEBUG 61 static const uint8_t ZeroLengthArrayData = 0x4A; 62 #endif 63 64 static constexpr int bufferOffset() { 65 return NativeObject::getFixedSlotOffset(BUFFER_SLOT); 66 } 67 static constexpr int lengthOffset() { 68 return NativeObject::getFixedSlotOffset(LENGTH_SLOT); 69 } 70 static constexpr int byteOffsetOffset() { 71 return NativeObject::getFixedSlotOffset(BYTEOFFSET_SLOT); 72 } 73 static constexpr int dataOffset() { 74 return NativeObject::getFixedSlotOffset(DATA_SLOT); 75 } 76 static constexpr int autoLengthOffset() { 77 return NativeObject::getFixedSlotOffset(AUTO_LENGTH_SLOT); 78 } 79 static constexpr int initialLengthOffset() { 80 return NativeObject::getFixedSlotOffset(INITIAL_LENGTH_SLOT); 81 } 82 static constexpr int initialByteOffsetOffset() { 83 return NativeObject::getFixedSlotOffset(INITIAL_BYTE_OFFSET_SLOT); 84 } 85 86 private: 87 void* dataPointerEither_() const { 88 // Note, do not check whether shared or not 89 // Keep synced with js::Get<Type>ArrayLengthAndData in jsfriendapi.h! 90 return maybePtrFromReservedSlot<void>(DATA_SLOT); 91 } 92 93 public: 94 [[nodiscard]] bool init(JSContext* cx, ArrayBufferObjectMaybeShared* buffer, 95 size_t byteOffset, size_t length, 96 uint32_t bytesPerElement); 97 98 enum class AutoLength : bool { No, Yes }; 99 100 [[nodiscard]] bool initResizable(JSContext* cx, 101 ArrayBufferObjectMaybeShared* buffer, 102 size_t byteOffset, size_t length, 103 uint32_t bytesPerElement, 104 AutoLength autoLength); 105 106 static ArrayBufferObjectMaybeShared* ensureBufferObject( 107 JSContext* cx, Handle<ArrayBufferViewObject*> obj); 108 109 void notifyBufferDetached(); 110 void notifyBufferResized(); 111 void notifyBufferMoved(uint8_t* srcBufStart, uint8_t* dstBufStart); 112 113 void initDataPointer(SharedMem<uint8_t*> viewData) { 114 // Install a pointer to the buffer location that corresponds 115 // to offset zero within the typed array. 116 // 117 // The following unwrap is safe because the DATA_SLOT is 118 // accessed only from jitted code and from the 119 // dataPointerEither_() accessor above; in neither case does the 120 // raw pointer escape untagged into C++ code. 121 void* data = viewData.unwrap(/*safe - see above*/); 122 initReservedSlot(DATA_SLOT, PrivateValue(data)); 123 } 124 125 SharedMem<void*> dataPointerShared() const { 126 return SharedMem<void*>::shared(dataPointerEither_()); 127 } 128 SharedMem<void*> dataPointerEither() const { 129 if (isSharedMemory()) { 130 return SharedMem<void*>::shared(dataPointerEither_()); 131 } 132 return SharedMem<void*>::unshared(dataPointerEither_()); 133 } 134 void* dataPointerUnshared() const { 135 MOZ_ASSERT(!isSharedMemory()); 136 return dataPointerEither_(); 137 } 138 139 Value bufferValue() const { return getFixedSlot(BUFFER_SLOT); } 140 bool hasBuffer() const { return bufferValue().isObject(); } 141 142 ArrayBufferObject* bufferUnshared() const { 143 MOZ_ASSERT(!isSharedMemory()); 144 ArrayBufferObjectMaybeShared* obj = bufferEither(); 145 if (!obj) { 146 return nullptr; 147 } 148 return &obj->as<ArrayBufferObject>(); 149 } 150 SharedArrayBufferObject* bufferShared() const { 151 MOZ_ASSERT(isSharedMemory()); 152 ArrayBufferObjectMaybeShared* obj = bufferEither(); 153 if (!obj) { 154 return nullptr; 155 } 156 return &obj->as<SharedArrayBufferObject>(); 157 } 158 ArrayBufferObjectMaybeShared* bufferEither() const { 159 JSObject* obj = 160 bufferValue().isBoolean() ? nullptr : bufferValue().toObjectOrNull(); 161 if (!obj) { 162 return nullptr; 163 } 164 MOZ_ASSERT(isSharedMemory() ? obj->is<SharedArrayBufferObject>() 165 : obj->is<ArrayBufferObject>()); 166 return &obj->as<ArrayBufferObjectMaybeShared>(); 167 } 168 169 bool hasDetachedBuffer() const { 170 // Shared buffers can't be detached. 171 if (isSharedMemory()) { 172 return false; 173 } 174 175 // A typed array with a null buffer has never had its buffer exposed to 176 // become detached. 177 ArrayBufferObject* buffer = bufferUnshared(); 178 if (!buffer) { 179 return false; 180 } 181 182 return buffer->isDetached(); 183 } 184 185 bool hasResizableBuffer() const; 186 187 bool hasImmutableBuffer() const; 188 189 private: 190 bool hasDetachedBufferOrIsOutOfBounds() const { 191 // Shared buffers can't be detached or get out-of-bounds. 192 if (isSharedMemory()) { 193 return false; 194 } 195 196 // A view with a null buffer has never had its buffer exposed to become 197 // detached or get out-of-bounds. 198 auto* buffer = bufferUnshared(); 199 if (!buffer) { 200 return false; 201 } 202 203 return buffer->isDetached() || (buffer->isResizable() && isOutOfBounds()); 204 } 205 206 public: 207 bool isLengthPinned() const { 208 Value buffer = bufferValue(); 209 if (buffer.isBoolean()) { 210 return buffer.toBoolean(); 211 } 212 if (isSharedMemory()) { 213 return true; 214 } 215 return bufferUnshared()->isLengthPinned(); 216 } 217 218 bool pinLength(bool pin) { 219 if (isSharedMemory()) { 220 // Always pinned, cannot change. 221 return false; 222 } 223 224 if (hasBuffer()) { 225 return bufferUnshared()->pinLength(pin); 226 } 227 228 // No ArrayBuffer (data is inline in the view). bufferValue() is a 229 // BooleanValue saying whether the length is currently pinned. 230 MOZ_ASSERT(bufferValue().isBoolean()); 231 232 bool wasPinned = bufferValue().toBoolean(); 233 if (wasPinned == pin) { 234 return false; 235 } 236 237 setFixedSlot(BUFFER_SLOT, JS::BooleanValue(pin)); 238 return true; 239 } 240 241 static bool ensureNonInline(JSContext* cx, 242 JS::Handle<ArrayBufferViewObject*> view); 243 244 private: 245 void computeResizableLengthAndByteOffset(size_t bytesPerElement); 246 247 size_t bytesPerElement() const; 248 249 protected: 250 size_t lengthSlotValue() const { 251 return size_t(getFixedSlot(LENGTH_SLOT).toPrivate()); 252 } 253 254 size_t byteOffsetSlotValue() const { 255 return size_t(getFixedSlot(BYTEOFFSET_SLOT).toPrivate()); 256 } 257 258 /** 259 * Offset into the buffer's data-pointer. Different from |byteOffset| for 260 * views on non-detached resizable buffers which are currently out-of-bounds. 261 */ 262 size_t dataPointerOffset() const; 263 264 /** 265 * Return the current length, or |Nothing| if the view is detached or 266 * out-of-bounds. 267 */ 268 mozilla::Maybe<size_t> length() const; 269 270 public: 271 /** 272 * Return the current byteOffset, or |Nothing| if the view is detached or 273 * out-of-bounds. 274 */ 275 mozilla::Maybe<size_t> byteOffset() const; 276 277 private: 278 size_t initialByteOffsetValue() const { 279 // No assertion for resizable buffers here, because this method is called 280 // from dataPointerOffset(), which can be called during tracing. 281 return size_t(getFixedSlot(INITIAL_BYTE_OFFSET_SLOT).toPrivate()); 282 } 283 284 public: 285 // The following methods can only be called on views for resizable buffers. 286 287 bool isAutoLength() const { 288 MOZ_ASSERT(hasResizableBuffer()); 289 return getFixedSlot(AUTO_LENGTH_SLOT).toBoolean(); 290 } 291 292 size_t initialLength() const { 293 MOZ_ASSERT(hasResizableBuffer()); 294 return size_t(getFixedSlot(INITIAL_LENGTH_SLOT).toPrivate()); 295 } 296 297 size_t initialByteOffset() const { 298 MOZ_ASSERT(hasResizableBuffer()); 299 return initialByteOffsetValue(); 300 } 301 302 bool isOutOfBounds() const { 303 MOZ_ASSERT(hasResizableBuffer()); 304 305 // The view is out-of-bounds if the length and byteOffset slots are both set 306 // to zero and the initial length or initial byteOffset are non-zero. If the 307 // initial length and initial byteOffset are both zero, the view can never 308 // get out-of-bounds. 309 return lengthSlotValue() == 0 && byteOffsetSlotValue() == 0 && 310 (initialLength() > 0 || initialByteOffset() > 0); 311 } 312 313 public: 314 static void trace(JSTracer* trc, JSObject* obj); 315 316 #if defined(DEBUG) || defined(JS_JITSPEW) 317 void dumpOwnFields(js::JSONPrinter& json) const; 318 void dumpOwnStringContent(js::GenericPrinter& out) const; 319 #endif 320 }; 321 322 } // namespace js 323 324 template <> 325 bool JSObject::is<js::ArrayBufferViewObject>() const; 326 327 #endif // vm_ArrayBufferViewObject_h