Bug 1885337 - Part 2: Implement to/from base64 methods. r=dminor
[gecko.git] / js / src / vm / TypedArrayObject.cpp
blobde9ec04fcc802dbf2cb48e24d638a75518d0b777
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/TypedArrayObject-inl.h"
8 #include "vm/TypedArrayObject.h"
10 #include "mozilla/CheckedInt.h"
11 #include "mozilla/FloatingPoint.h"
12 #include "mozilla/IntegerTypeTraits.h"
13 #include "mozilla/Likely.h"
14 #include "mozilla/PodOperations.h"
15 #include "mozilla/TextUtils.h"
17 #include <algorithm>
18 #include <iterator>
19 #include <limits>
20 #include <numeric>
21 #include <string.h>
22 #include <string_view>
23 #if !defined(XP_WIN) && !defined(__wasi__)
24 # include <sys/mman.h>
25 #endif
26 #include <type_traits>
28 #include "jsnum.h"
29 #include "jstypes.h"
31 #include "builtin/Array.h"
32 #include "builtin/DataViewObject.h"
33 #include "gc/Barrier.h"
34 #include "gc/MaybeRooted.h"
35 #include "jit/InlinableNatives.h"
36 #include "js/Conversions.h"
37 #include "js/experimental/TypedData.h" // JS_GetArrayBufferViewType, JS_GetTypedArray{Length,ByteOffset,ByteLength}, JS_IsTypedArrayObject
38 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
39 #include "js/PropertySpec.h"
40 #include "js/ScalarType.h" // JS::Scalar::Type
41 #include "js/UniquePtr.h"
42 #include "js/Wrapper.h"
43 #include "util/DifferentialTesting.h"
44 #include "util/StringBuffer.h"
45 #include "util/Text.h"
46 #include "util/WindowsWrapper.h"
47 #include "vm/ArrayBufferObject.h"
48 #include "vm/FunctionFlags.h" // js::FunctionFlags
49 #include "vm/GlobalObject.h"
50 #include "vm/JSContext.h"
51 #include "vm/JSObject.h"
52 #include "vm/PIC.h"
53 #include "vm/SelfHosting.h"
54 #include "vm/SharedMem.h"
55 #include "vm/Uint8Clamped.h"
56 #include "vm/WrapperObject.h"
58 #include "gc/Nursery-inl.h"
59 #include "vm/ArrayBufferObject-inl.h"
60 #include "vm/Compartment-inl.h"
61 #include "vm/GeckoProfiler-inl.h"
62 #include "vm/NativeObject-inl.h"
64 using namespace js;
66 using JS::CanonicalizeNaN;
67 using JS::ToInt32;
68 using JS::ToUint32;
69 using mozilla::IsAsciiDigit;
72 * TypedArrayObject
74 * The non-templated base class for the specific typed implementations.
75 * This class holds all the member variables that are used by
76 * the subclasses.
79 bool TypedArrayObject::convertValue(JSContext* cx, HandleValue v,
80 MutableHandleValue result) const {
81 switch (type()) {
82 case Scalar::BigInt64:
83 case Scalar::BigUint64: {
84 BigInt* bi = ToBigInt(cx, v);
85 if (!bi) {
86 return false;
88 result.setBigInt(bi);
89 return true;
91 case Scalar::Int8:
92 case Scalar::Uint8:
93 case Scalar::Int16:
94 case Scalar::Uint16:
95 case Scalar::Int32:
96 case Scalar::Uint32:
97 case Scalar::Float32:
98 case Scalar::Float64:
99 case Scalar::Uint8Clamped: {
100 double num;
101 if (!ToNumber(cx, v, &num)) {
102 return false;
104 result.setNumber(num);
105 return true;
107 case Scalar::MaxTypedArrayViewType:
108 case Scalar::Int64:
109 case Scalar::Simd128:
110 MOZ_CRASH("Unsupported TypedArray type");
112 MOZ_ASSERT_UNREACHABLE("Invalid scalar type");
113 return false;
116 static bool IsTypedArrayObject(HandleValue v) {
117 return v.isObject() && v.toObject().is<TypedArrayObject>();
120 static bool IsUint8ArrayObject(HandleValue v) {
121 return IsTypedArrayObject(v) &&
122 v.toObject().as<TypedArrayObject>().type() == Scalar::Uint8;
125 /* static */
126 bool TypedArrayObject::ensureHasBuffer(JSContext* cx,
127 Handle<TypedArrayObject*> typedArray) {
128 if (typedArray->hasBuffer()) {
129 return true;
132 MOZ_ASSERT(typedArray->is<FixedLengthTypedArrayObject>(),
133 "Resizable TypedArrays always use an ArrayBuffer");
135 Rooted<FixedLengthTypedArrayObject*> tarray(
136 cx, &typedArray->as<FixedLengthTypedArrayObject>());
138 size_t byteLength = tarray->byteLength();
140 AutoRealm ar(cx, tarray);
141 Rooted<ArrayBufferObject*> buffer(
142 cx, ArrayBufferObject::createZeroed(cx, tarray->byteLength()));
143 if (!buffer) {
144 return false;
147 buffer->pinLength(tarray->isLengthPinned());
149 // Attaching the first view to an array buffer is infallible.
150 MOZ_ALWAYS_TRUE(buffer->addView(cx, tarray));
152 // tarray is not shared, because if it were it would have a buffer.
153 memcpy(buffer->dataPointer(), tarray->dataPointerUnshared(), byteLength);
155 // If the object is in the nursery, the buffer will be freed by the next
156 // nursery GC. Free the data slot pointer if the object has no inline data.
157 size_t nbytes = RoundUp(byteLength, sizeof(Value));
158 Nursery& nursery = cx->nursery();
159 if (tarray->isTenured() && !tarray->hasInlineElements() &&
160 !nursery.isInside(tarray->elements())) {
161 js_free(tarray->elements());
162 RemoveCellMemory(tarray, nbytes, MemoryUse::TypedArrayElements);
165 tarray->setFixedSlot(TypedArrayObject::DATA_SLOT,
166 PrivateValue(buffer->dataPointer()));
167 tarray->setFixedSlot(TypedArrayObject::BUFFER_SLOT, ObjectValue(*buffer));
169 return true;
172 #ifdef DEBUG
173 void FixedLengthTypedArrayObject::assertZeroLengthArrayData() const {
174 if (length() == 0 && !hasBuffer()) {
175 uint8_t* end = fixedData(FixedLengthTypedArrayObject::FIXED_DATA_START);
176 MOZ_ASSERT(end[0] == ZeroLengthArrayData);
179 #endif
181 void FixedLengthTypedArrayObject::finalize(JS::GCContext* gcx, JSObject* obj) {
182 MOZ_ASSERT(!IsInsideNursery(obj));
183 auto* curObj = &obj->as<FixedLengthTypedArrayObject>();
185 // Template objects or discarded objects (which didn't have enough room
186 // for inner elements) don't have anything to free.
187 if (!curObj->elementsRaw()) {
188 return;
191 curObj->assertZeroLengthArrayData();
193 // Typed arrays with a buffer object do not need to be free'd
194 if (curObj->hasBuffer()) {
195 return;
198 // Free the data slot pointer if it does not point into the old JSObject.
199 if (!curObj->hasInlineElements()) {
200 size_t nbytes = RoundUp(curObj->byteLength(), sizeof(Value));
201 gcx->free_(obj, curObj->elements(), nbytes, MemoryUse::TypedArrayElements);
205 /* static */
206 size_t FixedLengthTypedArrayObject::objectMoved(JSObject* obj, JSObject* old) {
207 auto* newObj = &obj->as<FixedLengthTypedArrayObject>();
208 const auto* oldObj = &old->as<FixedLengthTypedArrayObject>();
209 MOZ_ASSERT(newObj->elementsRaw() == oldObj->elementsRaw());
211 // Typed arrays with a buffer object do not need an update.
212 if (oldObj->hasBuffer()) {
213 return 0;
216 if (!IsInsideNursery(old)) {
217 // Update the data slot pointer if it points to the old JSObject.
218 if (oldObj->hasInlineElements()) {
219 newObj->setInlineElements();
222 return 0;
225 void* buf = oldObj->elements();
227 // Discarded objects (which didn't have enough room for inner elements) don't
228 // have any data to move.
229 if (!buf) {
230 return 0;
233 Nursery& nursery = obj->runtimeFromMainThread()->gc.nursery();
235 // Determine if we can use inline data for the target array. If this is
236 // possible, the nursery will have picked an allocation size that is large
237 // enough.
238 size_t nbytes = oldObj->byteLength();
239 bool canUseDirectForward = nbytes >= sizeof(uintptr_t);
241 constexpr size_t headerSize = dataOffset() + sizeof(HeapSlot);
243 gc::AllocKind allocKind = oldObj->allocKindForTenure();
244 MOZ_ASSERT_IF(obj->isTenured(), obj->asTenured().getAllocKind() == allocKind);
245 MOZ_ASSERT_IF(nbytes == 0,
246 headerSize + sizeof(uint8_t) <= GetGCKindBytes(allocKind));
248 if (nursery.isInside(buf) &&
249 headerSize + nbytes <= GetGCKindBytes(allocKind)) {
250 MOZ_ASSERT(oldObj->hasInlineElements());
251 #ifdef DEBUG
252 if (nbytes == 0) {
253 uint8_t* output =
254 newObj->fixedData(FixedLengthTypedArrayObject::FIXED_DATA_START);
255 output[0] = ZeroLengthArrayData;
257 #endif
258 newObj->setInlineElements();
259 mozilla::PodCopy(newObj->elements(), oldObj->elements(), nbytes);
261 // Set a forwarding pointer for the element buffers in case they were
262 // preserved on the stack by Ion.
263 nursery.setForwardingPointerWhileTenuring(
264 oldObj->elements(), newObj->elements(), canUseDirectForward);
266 return 0;
269 // Non-inline allocations are rounded up.
270 nbytes = RoundUp(nbytes, sizeof(Value));
272 Nursery::WasBufferMoved result = nursery.maybeMoveBufferOnPromotion(
273 &buf, newObj, nbytes, MemoryUse::TypedArrayElements,
274 ArrayBufferContentsArena);
275 if (result == Nursery::BufferMoved) {
276 newObj->setReservedSlot(DATA_SLOT, PrivateValue(buf));
278 // Set a forwarding pointer for the element buffers in case they were
279 // preserved on the stack by Ion.
280 nursery.setForwardingPointerWhileTenuring(
281 oldObj->elements(), newObj->elements(), canUseDirectForward);
283 return nbytes;
286 return 0;
289 bool FixedLengthTypedArrayObject::hasInlineElements() const {
290 return elements() ==
291 this->fixedData(FixedLengthTypedArrayObject::FIXED_DATA_START) &&
292 byteLength() <= FixedLengthTypedArrayObject::INLINE_BUFFER_LIMIT;
295 void FixedLengthTypedArrayObject::setInlineElements() {
296 char* dataSlot = reinterpret_cast<char*>(this) + dataOffset();
297 *reinterpret_cast<void**>(dataSlot) =
298 this->fixedData(FixedLengthTypedArrayObject::FIXED_DATA_START);
301 /* Helper clamped uint8_t type */
303 uint32_t js::ClampDoubleToUint8(const double x) {
304 // Not < so that NaN coerces to 0
305 if (!(x >= 0)) {
306 return 0;
309 if (x > 255) {
310 return 255;
313 double toTruncate = x + 0.5;
314 uint8_t y = uint8_t(toTruncate);
317 * now val is rounded to nearest, ties rounded up. We want
318 * rounded to nearest ties to even, so check whether we had a
319 * tie.
321 if (y == toTruncate) {
323 * It was a tie (since adding 0.5 gave us the exact integer
324 * we want). Since we rounded up, we either already have an
325 * even number or we have an odd number but the number we
326 * want is one less. So just unconditionally masking out the
327 * ones bit should do the trick to get us the value we
328 * want.
330 return y & ~1;
333 return y;
336 static void ReportOutOfBounds(JSContext* cx, TypedArrayObject* typedArray) {
337 if (typedArray->hasDetachedBuffer()) {
338 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
339 JSMSG_TYPED_ARRAY_DETACHED);
340 } else {
341 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
342 JSMSG_TYPED_ARRAY_RESIZED_BOUNDS);
346 namespace {
348 template <class TypedArrayType>
349 static TypedArrayType* NewTypedArrayObject(JSContext* cx, const JSClass* clasp,
350 HandleObject proto,
351 gc::AllocKind allocKind,
352 gc::Heap heap) {
353 MOZ_ASSERT(proto);
355 MOZ_ASSERT(CanChangeToBackgroundAllocKind(allocKind, clasp));
356 allocKind = ForegroundToBackgroundAllocKind(allocKind);
358 static_assert(std::is_same_v<TypedArrayType, FixedLengthTypedArrayObject> ||
359 std::is_same_v<TypedArrayType, ResizableTypedArrayObject>);
361 // Fixed length typed arrays can store data inline so we only use fixed slots
362 // to cover the reserved slots, ignoring the AllocKind.
363 MOZ_ASSERT(ClassCanHaveFixedData(clasp));
364 constexpr size_t nfixed = TypedArrayType::RESERVED_SLOTS;
365 static_assert(nfixed <= NativeObject::MAX_FIXED_SLOTS);
366 static_assert(!std::is_same_v<TypedArrayType, FixedLengthTypedArrayObject> ||
367 nfixed == FixedLengthTypedArrayObject::FIXED_DATA_START);
369 Rooted<SharedShape*> shape(
371 SharedShape::getInitialShape(cx, clasp, cx->realm(), AsTaggedProto(proto),
372 nfixed, ObjectFlags()));
373 if (!shape) {
374 return nullptr;
377 return NativeObject::create<TypedArrayType>(cx, allocKind, heap, shape);
380 template <typename NativeType>
381 class FixedLengthTypedArrayObjectTemplate;
383 template <typename NativeType>
384 class ResizableTypedArrayObjectTemplate;
386 template <typename NativeType>
387 class TypedArrayObjectTemplate {
388 friend class js::TypedArrayObject;
390 using FixedLengthTypedArray = FixedLengthTypedArrayObjectTemplate<NativeType>;
391 using ResizableTypedArray = ResizableTypedArrayObjectTemplate<NativeType>;
392 using AutoLength = ArrayBufferViewObject::AutoLength;
394 static constexpr auto ByteLengthLimit = TypedArrayObject::ByteLengthLimit;
395 static constexpr auto INLINE_BUFFER_LIMIT =
396 FixedLengthTypedArrayObject::INLINE_BUFFER_LIMIT;
398 public:
399 static constexpr Scalar::Type ArrayTypeID() {
400 return TypeIDOfType<NativeType>::id;
402 static constexpr JSProtoKey protoKey() {
403 return TypeIDOfType<NativeType>::protoKey;
406 static constexpr bool ArrayTypeIsUnsigned() {
407 return TypeIsUnsigned<NativeType>();
409 static constexpr bool ArrayTypeIsFloatingPoint() {
410 return TypeIsFloatingPoint<NativeType>();
413 static constexpr size_t BYTES_PER_ELEMENT = sizeof(NativeType);
415 static JSObject* createPrototype(JSContext* cx, JSProtoKey key) {
416 Handle<GlobalObject*> global = cx->global();
417 RootedObject typedArrayProto(
418 cx, GlobalObject::getOrCreateTypedArrayPrototype(cx, global));
419 if (!typedArrayProto) {
420 return nullptr;
423 const JSClass* clasp = TypedArrayObject::protoClassForType(ArrayTypeID());
424 return GlobalObject::createBlankPrototypeInheriting(cx, clasp,
425 typedArrayProto);
428 static JSObject* createConstructor(JSContext* cx, JSProtoKey key) {
429 Handle<GlobalObject*> global = cx->global();
430 RootedFunction ctorProto(
431 cx, GlobalObject::getOrCreateTypedArrayConstructor(cx, global));
432 if (!ctorProto) {
433 return nullptr;
436 JSFunction* fun = NewFunctionWithProto(
437 cx, class_constructor, 3, FunctionFlags::NATIVE_CTOR, nullptr,
438 ClassName(key, cx), ctorProto, gc::AllocKind::FUNCTION, TenuredObject);
440 if (fun) {
441 fun->setJitInfo(&jit::JitInfo_TypedArrayConstructor);
444 return fun;
447 static bool convertValue(JSContext* cx, HandleValue v, NativeType* result);
449 static TypedArrayObject* makeTypedArrayWithTemplate(
450 JSContext* cx, TypedArrayObject* templateObj, HandleObject array) {
451 MOZ_ASSERT(!IsWrapper(array));
452 MOZ_ASSERT(!array->is<ArrayBufferObjectMaybeShared>());
454 return fromArray(cx, array);
457 static TypedArrayObject* makeTypedArrayWithTemplate(
458 JSContext* cx, TypedArrayObject* templateObj, HandleObject arrayBuffer,
459 HandleValue byteOffsetValue, HandleValue lengthValue) {
460 MOZ_ASSERT(!IsWrapper(arrayBuffer));
461 MOZ_ASSERT(arrayBuffer->is<ArrayBufferObjectMaybeShared>());
463 uint64_t byteOffset, length;
464 if (!byteOffsetAndLength(cx, byteOffsetValue, lengthValue, &byteOffset,
465 &length)) {
466 return nullptr;
469 return fromBufferSameCompartment(
470 cx, arrayBuffer.as<ArrayBufferObjectMaybeShared>(), byteOffset, length,
471 nullptr);
474 // ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78
475 // 23.2.5.1 TypedArray ( ...args )
476 static bool class_constructor(JSContext* cx, unsigned argc, Value* vp) {
477 AutoJSConstructorProfilerEntry pseudoFrame(cx, "[TypedArray]");
478 CallArgs args = CallArgsFromVp(argc, vp);
480 // Step 1.
481 if (!ThrowIfNotConstructing(cx, args, "typed array")) {
482 return false;
485 // Steps 2-6.
486 JSObject* obj = create(cx, args);
487 if (!obj) {
488 return false;
490 args.rval().setObject(*obj);
491 return true;
494 private:
495 static JSObject* create(JSContext* cx, const CallArgs& args) {
496 MOZ_ASSERT(args.isConstructing());
498 // Steps 5 and 6.c.
499 if (args.length() == 0 || !args[0].isObject()) {
500 // Step 6.c.ii.
501 uint64_t len;
502 if (!ToIndex(cx, args.get(0), JSMSG_BAD_ARRAY_LENGTH, &len)) {
503 return nullptr;
506 // Steps 5.a and 6.c.iii.
507 RootedObject proto(cx);
508 if (!GetPrototypeFromBuiltinConstructor(cx, args, protoKey(), &proto)) {
509 return nullptr;
512 return fromLength(cx, len, proto);
515 RootedObject dataObj(cx, &args[0].toObject());
517 // Step 6.b.i.
518 // 23.2.5.1.1 AllocateTypedArray, step 1.
519 RootedObject proto(cx);
520 if (!GetPrototypeFromBuiltinConstructor(cx, args, protoKey(), &proto)) {
521 return nullptr;
524 // Steps 6.b.ii and 6.b.iv.
525 if (!UncheckedUnwrap(dataObj)->is<ArrayBufferObjectMaybeShared>()) {
526 return fromArray(cx, dataObj, proto);
529 // Steps 6.b.iii.1-2.
530 // 23.2.5.1.3 InitializeTypedArrayFromArrayBuffer, steps 2 and 4.
531 uint64_t byteOffset, length;
532 if (!byteOffsetAndLength(cx, args.get(1), args.get(2), &byteOffset,
533 &length)) {
534 return nullptr;
537 // Step 6.b.iii.3.
538 if (dataObj->is<ArrayBufferObjectMaybeShared>()) {
539 auto buffer = dataObj.as<ArrayBufferObjectMaybeShared>();
540 return fromBufferSameCompartment(cx, buffer, byteOffset, length, proto);
542 return fromBufferWrapped(cx, dataObj, byteOffset, length, proto);
545 // ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78
546 // 23.2.5.1.3 InitializeTypedArrayFromArrayBuffer ( O, buffer, byteOffset,
547 // length ) Steps 2 and 4.
548 static bool byteOffsetAndLength(JSContext* cx, HandleValue byteOffsetValue,
549 HandleValue lengthValue, uint64_t* byteOffset,
550 uint64_t* length) {
551 // Step 2.
552 *byteOffset = 0;
553 if (!byteOffsetValue.isUndefined()) {
554 if (!ToIndex(cx, byteOffsetValue, byteOffset)) {
555 return false;
558 // Step 7.
559 if (*byteOffset % BYTES_PER_ELEMENT != 0) {
560 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
561 JSMSG_TYPED_ARRAY_CONSTRUCT_OFFSET_BOUNDS,
562 Scalar::name(ArrayTypeID()),
563 Scalar::byteSizeString(ArrayTypeID()));
564 return false;
568 // Step 4.
569 *length = UINT64_MAX;
570 if (!lengthValue.isUndefined()) {
571 if (!ToIndex(cx, lengthValue, length)) {
572 return false;
576 return true;
579 // ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78
580 // 23.2.5.1.3 InitializeTypedArrayFromArrayBuffer ( O, buffer, byteOffset,
581 // length ) Steps 5-8.
582 static bool computeAndCheckLength(
583 JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> bufferMaybeUnwrapped,
584 uint64_t byteOffset, uint64_t lengthIndex, size_t* length,
585 AutoLength* autoLength) {
586 MOZ_ASSERT(byteOffset % BYTES_PER_ELEMENT == 0);
587 MOZ_ASSERT(byteOffset < uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT));
588 MOZ_ASSERT_IF(lengthIndex != UINT64_MAX,
589 lengthIndex < uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT));
591 // Step 5.
592 if (bufferMaybeUnwrapped->isDetached()) {
593 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
594 JSMSG_TYPED_ARRAY_DETACHED);
595 return false;
598 // Step 6.
599 size_t bufferByteLength = bufferMaybeUnwrapped->byteLength();
600 MOZ_ASSERT(bufferByteLength <= ByteLengthLimit);
602 size_t len;
603 if (lengthIndex == UINT64_MAX) {
604 // Check if |byteOffset| valid.
605 if (byteOffset > bufferByteLength) {
606 JS_ReportErrorNumberASCII(
607 cx, GetErrorMessage, nullptr,
608 JSMSG_TYPED_ARRAY_CONSTRUCT_OFFSET_LENGTH_BOUNDS,
609 Scalar::name(ArrayTypeID()));
610 return false;
613 // Resizable buffers without an explicit length are auto-length.
614 if (bufferMaybeUnwrapped->isResizable()) {
615 *length = 0;
616 *autoLength = AutoLength::Yes;
617 return true;
620 // Steps 7.a and 7.c.
621 if (bufferByteLength % BYTES_PER_ELEMENT != 0) {
622 // The given byte array doesn't map exactly to
623 // |BYTES_PER_ELEMENT * N|
624 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
625 JSMSG_TYPED_ARRAY_CONSTRUCT_OFFSET_MISALIGNED,
626 Scalar::name(ArrayTypeID()),
627 Scalar::byteSizeString(ArrayTypeID()));
628 return false;
631 // Step 7.b.
632 size_t newByteLength = bufferByteLength - size_t(byteOffset);
633 len = newByteLength / BYTES_PER_ELEMENT;
634 } else {
635 // Step 8.a.
636 uint64_t newByteLength = lengthIndex * BYTES_PER_ELEMENT;
638 // Step 8.b.
639 if (byteOffset + newByteLength > bufferByteLength) {
640 // |byteOffset + newByteLength| is too big for the arraybuffer
641 JS_ReportErrorNumberASCII(
642 cx, GetErrorMessage, nullptr,
643 JSMSG_TYPED_ARRAY_CONSTRUCT_ARRAY_LENGTH_BOUNDS,
644 Scalar::name(ArrayTypeID()));
645 return false;
648 len = size_t(lengthIndex);
651 MOZ_ASSERT(len <= ByteLengthLimit / BYTES_PER_ELEMENT);
652 *length = len;
653 *autoLength = AutoLength::No;
654 return true;
657 // ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78
658 // 23.2.5.1.3 InitializeTypedArrayFromArrayBuffer ( O, buffer, byteOffset,
659 // length ) Steps 5-13.
660 static TypedArrayObject* fromBufferSameCompartment(
661 JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer,
662 uint64_t byteOffset, uint64_t lengthIndex, HandleObject proto) {
663 // Steps 5-8.
664 size_t length = 0;
665 auto autoLength = AutoLength::No;
666 if (!computeAndCheckLength(cx, buffer, byteOffset, lengthIndex, &length,
667 &autoLength)) {
668 return nullptr;
671 if (!buffer->isResizable()) {
672 // Steps 9-13.
673 return FixedLengthTypedArray::makeInstance(cx, buffer, byteOffset, length,
674 proto);
677 return ResizableTypedArray::makeInstance(cx, buffer, byteOffset, length,
678 autoLength, proto);
681 // Create a TypedArray object in another compartment.
683 // ES6 supports creating a TypedArray in global A (using global A's
684 // TypedArray constructor) backed by an ArrayBuffer created in global B.
686 // Our TypedArrayObject implementation doesn't support a TypedArray in
687 // compartment A backed by an ArrayBuffer in compartment B. So in this
688 // case, we create the TypedArray in B (!) and return a cross-compartment
689 // wrapper.
691 // Extra twist: the spec says the new TypedArray's [[Prototype]] must be
692 // A's TypedArray.prototype. So even though we're creating the TypedArray
693 // in B, its [[Prototype]] must be (a cross-compartment wrapper for) the
694 // TypedArray.prototype in A.
695 static JSObject* fromBufferWrapped(JSContext* cx, HandleObject bufobj,
696 uint64_t byteOffset, uint64_t lengthIndex,
697 HandleObject proto) {
698 JSObject* unwrapped = CheckedUnwrapStatic(bufobj);
699 if (!unwrapped) {
700 ReportAccessDenied(cx);
701 return nullptr;
704 if (!unwrapped->is<ArrayBufferObjectMaybeShared>()) {
705 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
706 JSMSG_TYPED_ARRAY_BAD_ARGS);
707 return nullptr;
710 Rooted<ArrayBufferObjectMaybeShared*> unwrappedBuffer(cx);
711 unwrappedBuffer = &unwrapped->as<ArrayBufferObjectMaybeShared>();
713 size_t length = 0;
714 auto autoLength = AutoLength::No;
715 if (!computeAndCheckLength(cx, unwrappedBuffer, byteOffset, lengthIndex,
716 &length, &autoLength)) {
717 return nullptr;
720 // Make sure to get the [[Prototype]] for the created typed array from
721 // this compartment.
722 RootedObject protoRoot(cx, proto);
723 if (!protoRoot) {
724 protoRoot = GlobalObject::getOrCreatePrototype(cx, protoKey());
725 if (!protoRoot) {
726 return nullptr;
730 RootedObject typedArray(cx);
732 JSAutoRealm ar(cx, unwrappedBuffer);
734 RootedObject wrappedProto(cx, protoRoot);
735 if (!cx->compartment()->wrap(cx, &wrappedProto)) {
736 return nullptr;
739 if (!unwrappedBuffer->isResizable()) {
740 typedArray = FixedLengthTypedArray::makeInstance(
741 cx, unwrappedBuffer, byteOffset, length, wrappedProto);
742 } else {
743 typedArray = ResizableTypedArray::makeInstance(
744 cx, unwrappedBuffer, byteOffset, length, autoLength, wrappedProto);
746 if (!typedArray) {
747 return nullptr;
751 if (!cx->compartment()->wrap(cx, &typedArray)) {
752 return nullptr;
755 return typedArray;
758 public:
759 static JSObject* fromBuffer(JSContext* cx, HandleObject bufobj,
760 size_t byteOffset, int64_t lengthInt) {
761 if (byteOffset % BYTES_PER_ELEMENT != 0) {
762 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
763 JSMSG_TYPED_ARRAY_CONSTRUCT_OFFSET_BOUNDS,
764 Scalar::name(ArrayTypeID()),
765 Scalar::byteSizeString(ArrayTypeID()));
766 return nullptr; // invalid byteOffset
769 uint64_t lengthIndex = lengthInt >= 0 ? uint64_t(lengthInt) : UINT64_MAX;
770 if (bufobj->is<ArrayBufferObjectMaybeShared>()) {
771 auto buffer = bufobj.as<ArrayBufferObjectMaybeShared>();
772 return fromBufferSameCompartment(cx, buffer, byteOffset, lengthIndex,
773 nullptr);
775 return fromBufferWrapped(cx, bufobj, byteOffset, lengthIndex, nullptr);
778 static bool maybeCreateArrayBuffer(JSContext* cx, uint64_t count,
779 MutableHandle<ArrayBufferObject*> buffer) {
780 if (count > ByteLengthLimit / BYTES_PER_ELEMENT) {
781 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
782 JSMSG_BAD_ARRAY_LENGTH);
783 return false;
785 size_t byteLength = count * BYTES_PER_ELEMENT;
787 MOZ_ASSERT(byteLength <= ByteLengthLimit);
788 static_assert(INLINE_BUFFER_LIMIT % BYTES_PER_ELEMENT == 0,
789 "ArrayBuffer inline storage shouldn't waste any space");
791 if (byteLength <= INLINE_BUFFER_LIMIT) {
792 // The array's data can be inline, and the buffer created lazily.
793 return true;
796 ArrayBufferObject* buf = ArrayBufferObject::createZeroed(cx, byteLength);
797 if (!buf) {
798 return false;
801 buffer.set(buf);
802 return true;
805 // ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78
806 // 23.2.5.1.1 AllocateTypedArray ( constructorName, newTarget, defaultProto [
807 // , length ] )
808 static TypedArrayObject* fromLength(JSContext* cx, uint64_t nelements,
809 HandleObject proto = nullptr,
810 gc::Heap heap = gc::Heap::Default) {
811 Rooted<ArrayBufferObject*> buffer(cx);
812 if (!maybeCreateArrayBuffer(cx, nelements, &buffer)) {
813 return nullptr;
816 return FixedLengthTypedArray::makeInstance(cx, buffer, 0, nelements, proto,
817 heap);
820 static TypedArrayObject* fromArray(JSContext* cx, HandleObject other,
821 HandleObject proto = nullptr);
823 static TypedArrayObject* fromTypedArray(JSContext* cx, HandleObject other,
824 bool isWrapped, HandleObject proto);
826 static TypedArrayObject* fromObject(JSContext* cx, HandleObject other,
827 HandleObject proto);
829 static const NativeType getIndex(TypedArrayObject* tarray, size_t index) {
830 MOZ_ASSERT(index < tarray->length().valueOr(0));
831 return jit::AtomicOperations::loadSafeWhenRacy(
832 tarray->dataPointerEither().cast<NativeType*>() + index);
835 static void setIndex(TypedArrayObject& tarray, size_t index, NativeType val) {
836 MOZ_ASSERT(index < tarray.length().valueOr(0));
837 jit::AtomicOperations::storeSafeWhenRacy(
838 tarray.dataPointerEither().cast<NativeType*>() + index, val);
841 static bool getElement(JSContext* cx, TypedArrayObject* tarray, size_t index,
842 MutableHandleValue val);
843 static bool getElementPure(TypedArrayObject* tarray, size_t index, Value* vp);
845 static bool setElement(JSContext* cx, Handle<TypedArrayObject*> obj,
846 uint64_t index, HandleValue v, ObjectOpResult& result);
849 template <typename NativeType>
850 class FixedLengthTypedArrayObjectTemplate
851 : public FixedLengthTypedArrayObject,
852 public TypedArrayObjectTemplate<NativeType> {
853 friend class js::TypedArrayObject;
855 using TypedArrayTemplate = TypedArrayObjectTemplate<NativeType>;
857 public:
858 using TypedArrayTemplate::ArrayTypeID;
859 using TypedArrayTemplate::BYTES_PER_ELEMENT;
860 using TypedArrayTemplate::protoKey;
862 static inline const JSClass* instanceClass() {
863 static_assert(ArrayTypeID() <
864 std::size(TypedArrayObject::fixedLengthClasses));
865 return &TypedArrayObject::fixedLengthClasses[ArrayTypeID()];
868 static FixedLengthTypedArrayObject* newBuiltinClassInstance(
869 JSContext* cx, gc::AllocKind allocKind, gc::Heap heap) {
870 RootedObject proto(cx, GlobalObject::getOrCreatePrototype(cx, protoKey()));
871 if (!proto) {
872 return nullptr;
874 return NewTypedArrayObject<FixedLengthTypedArrayObject>(
875 cx, instanceClass(), proto, allocKind, heap);
878 static FixedLengthTypedArrayObject* makeProtoInstance(
879 JSContext* cx, HandleObject proto, gc::AllocKind allocKind) {
880 MOZ_ASSERT(proto);
881 return NewTypedArrayObject<FixedLengthTypedArrayObject>(
882 cx, instanceClass(), proto, allocKind, gc::Heap::Default);
885 static FixedLengthTypedArrayObject* makeInstance(
886 JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer,
887 size_t byteOffset, size_t len, HandleObject proto,
888 gc::Heap heap = gc::Heap::Default) {
889 MOZ_ASSERT(len <= ByteLengthLimit / BYTES_PER_ELEMENT);
891 gc::AllocKind allocKind =
892 buffer ? gc::GetGCObjectKind(instanceClass())
893 : AllocKindForLazyBuffer(len * BYTES_PER_ELEMENT);
895 AutoSetNewObjectMetadata metadata(cx);
896 FixedLengthTypedArrayObject* obj;
897 if (proto) {
898 obj = makeProtoInstance(cx, proto, allocKind);
899 } else {
900 obj = newBuiltinClassInstance(cx, allocKind, heap);
902 if (!obj || !obj->init(cx, buffer, byteOffset, len, BYTES_PER_ELEMENT)) {
903 return nullptr;
906 return obj;
909 static FixedLengthTypedArrayObject* makeTemplateObject(JSContext* cx,
910 int32_t len) {
911 MOZ_ASSERT(len >= 0);
912 size_t nbytes;
913 MOZ_ALWAYS_TRUE(CalculateAllocSize<NativeType>(len, &nbytes));
914 bool fitsInline = nbytes <= INLINE_BUFFER_LIMIT;
915 gc::AllocKind allocKind = !fitsInline ? gc::GetGCObjectKind(instanceClass())
916 : AllocKindForLazyBuffer(nbytes);
917 MOZ_ASSERT(allocKind >= gc::GetGCObjectKind(instanceClass()));
919 AutoSetNewObjectMetadata metadata(cx);
921 auto* tarray = newBuiltinClassInstance(cx, allocKind, gc::Heap::Tenured);
922 if (!tarray) {
923 return nullptr;
926 initTypedArraySlots(tarray, len);
928 // Template objects don't need memory for their elements, since there
929 // won't be any elements to store.
930 MOZ_ASSERT(tarray->getReservedSlot(DATA_SLOT).isUndefined());
932 return tarray;
935 static void initTypedArraySlots(FixedLengthTypedArrayObject* tarray,
936 int32_t len) {
937 MOZ_ASSERT(len >= 0);
938 tarray->initFixedSlot(TypedArrayObject::BUFFER_SLOT, JS::FalseValue());
939 tarray->initFixedSlot(TypedArrayObject::LENGTH_SLOT, PrivateValue(len));
940 tarray->initFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT,
941 PrivateValue(size_t(0)));
943 #ifdef DEBUG
944 if (len == 0) {
945 uint8_t* output =
946 tarray->fixedData(FixedLengthTypedArrayObject::FIXED_DATA_START);
947 output[0] = TypedArrayObject::ZeroLengthArrayData;
949 #endif
952 static void initTypedArrayData(FixedLengthTypedArrayObject* tarray, void* buf,
953 size_t nbytes, gc::AllocKind allocKind) {
954 if (buf) {
955 InitReservedSlot(tarray, TypedArrayObject::DATA_SLOT, buf, nbytes,
956 MemoryUse::TypedArrayElements);
957 } else {
958 #ifdef DEBUG
959 constexpr size_t dataOffset = ArrayBufferViewObject::dataOffset();
960 constexpr size_t offset = dataOffset + sizeof(HeapSlot);
961 MOZ_ASSERT(offset + nbytes <= GetGCKindBytes(allocKind));
962 #endif
964 void* data = tarray->fixedData(FIXED_DATA_START);
965 tarray->initReservedSlot(DATA_SLOT, PrivateValue(data));
966 memset(data, 0, nbytes);
970 static FixedLengthTypedArrayObject* makeTypedArrayWithTemplate(
971 JSContext* cx, TypedArrayObject* templateObj, int32_t len) {
972 if (len < 0 || size_t(len) > ByteLengthLimit / BYTES_PER_ELEMENT) {
973 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
974 JSMSG_BAD_ARRAY_LENGTH);
975 return nullptr;
978 size_t nbytes = size_t(len) * BYTES_PER_ELEMENT;
979 MOZ_ASSERT(nbytes <= ByteLengthLimit);
981 bool fitsInline = nbytes <= INLINE_BUFFER_LIMIT;
983 AutoSetNewObjectMetadata metadata(cx);
985 gc::AllocKind allocKind = !fitsInline ? gc::GetGCObjectKind(instanceClass())
986 : AllocKindForLazyBuffer(nbytes);
987 MOZ_ASSERT(templateObj->getClass() == instanceClass());
989 RootedObject proto(cx, templateObj->staticPrototype());
990 auto* obj = makeProtoInstance(cx, proto, allocKind);
991 if (!obj) {
992 return nullptr;
995 initTypedArraySlots(obj, len);
997 void* buf = nullptr;
998 if (!fitsInline) {
999 MOZ_ASSERT(len > 0);
1001 nbytes = RoundUp(nbytes, sizeof(Value));
1002 buf = cx->nursery().allocateZeroedBuffer(obj, nbytes,
1003 js::ArrayBufferContentsArena);
1004 if (!buf) {
1005 ReportOutOfMemory(cx);
1006 return nullptr;
1010 initTypedArrayData(obj, buf, nbytes, allocKind);
1012 return obj;
1016 template <typename NativeType>
1017 class ResizableTypedArrayObjectTemplate
1018 : public ResizableTypedArrayObject,
1019 public TypedArrayObjectTemplate<NativeType> {
1020 friend class js::TypedArrayObject;
1022 using TypedArrayTemplate = TypedArrayObjectTemplate<NativeType>;
1024 public:
1025 using TypedArrayTemplate::ArrayTypeID;
1026 using TypedArrayTemplate::BYTES_PER_ELEMENT;
1027 using TypedArrayTemplate::protoKey;
1029 static inline const JSClass* instanceClass() {
1030 static_assert(ArrayTypeID() <
1031 std::size(TypedArrayObject::resizableClasses));
1032 return &TypedArrayObject::resizableClasses[ArrayTypeID()];
1035 static ResizableTypedArrayObject* newBuiltinClassInstance(
1036 JSContext* cx, gc::AllocKind allocKind, gc::Heap heap) {
1037 RootedObject proto(cx, GlobalObject::getOrCreatePrototype(cx, protoKey()));
1038 if (!proto) {
1039 return nullptr;
1041 return NewTypedArrayObject<ResizableTypedArrayObject>(
1042 cx, instanceClass(), proto, allocKind, heap);
1045 static ResizableTypedArrayObject* makeProtoInstance(JSContext* cx,
1046 HandleObject proto,
1047 gc::AllocKind allocKind) {
1048 MOZ_ASSERT(proto);
1049 return NewTypedArrayObject<ResizableTypedArrayObject>(
1050 cx, instanceClass(), proto, allocKind, gc::Heap::Default);
1053 static ResizableTypedArrayObject* makeInstance(
1054 JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer,
1055 size_t byteOffset, size_t len, AutoLength autoLength,
1056 HandleObject proto) {
1057 MOZ_ASSERT(buffer);
1058 MOZ_ASSERT(buffer->isResizable());
1059 MOZ_ASSERT(!buffer->isDetached());
1060 MOZ_ASSERT(autoLength == AutoLength::No || len == 0,
1061 "length is zero for 'auto' length views");
1062 MOZ_ASSERT(len <= ByteLengthLimit / BYTES_PER_ELEMENT);
1064 gc::AllocKind allocKind = gc::GetGCObjectKind(instanceClass());
1066 AutoSetNewObjectMetadata metadata(cx);
1067 ResizableTypedArrayObject* obj;
1068 if (proto) {
1069 obj = makeProtoInstance(cx, proto, allocKind);
1070 } else {
1071 obj = newBuiltinClassInstance(cx, allocKind, gc::Heap::Default);
1073 if (!obj || !obj->initResizable(cx, buffer, byteOffset, len,
1074 BYTES_PER_ELEMENT, autoLength)) {
1075 return nullptr;
1078 return obj;
1081 static ResizableTypedArrayObject* makeTemplateObject(JSContext* cx) {
1082 gc::AllocKind allocKind = gc::GetGCObjectKind(instanceClass());
1084 AutoSetNewObjectMetadata metadata(cx);
1086 auto* tarray = newBuiltinClassInstance(cx, allocKind, gc::Heap::Tenured);
1087 if (!tarray) {
1088 return nullptr;
1091 tarray->initFixedSlot(TypedArrayObject::BUFFER_SLOT, JS::FalseValue());
1092 tarray->initFixedSlot(TypedArrayObject::LENGTH_SLOT,
1093 PrivateValue(size_t(0)));
1094 tarray->initFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT,
1095 PrivateValue(size_t(0)));
1096 tarray->initFixedSlot(AUTO_LENGTH_SLOT, BooleanValue(false));
1097 tarray->initFixedSlot(ResizableTypedArrayObject::INITIAL_LENGTH_SLOT,
1098 PrivateValue(size_t(0)));
1099 tarray->initFixedSlot(ResizableTypedArrayObject::INITIAL_BYTE_OFFSET_SLOT,
1100 PrivateValue(size_t(0)));
1102 // Template objects don't need memory for their elements, since there
1103 // won't be any elements to store.
1104 MOZ_ASSERT(tarray->getReservedSlot(DATA_SLOT).isUndefined());
1106 return tarray;
1110 template <typename NativeType>
1111 bool TypedArrayObjectTemplate<NativeType>::convertValue(JSContext* cx,
1112 HandleValue v,
1113 NativeType* result) {
1114 double d;
1115 if (!ToNumber(cx, v, &d)) {
1116 return false;
1119 if (js::SupportDifferentialTesting()) {
1120 // See the comment in ElementSpecific::doubleToNative.
1121 d = JS::CanonicalizeNaN(d);
1124 // Assign based on characteristics of the destination type
1125 if constexpr (ArrayTypeIsFloatingPoint()) {
1126 *result = NativeType(d);
1127 } else if constexpr (ArrayTypeIsUnsigned()) {
1128 static_assert(sizeof(NativeType) <= 4);
1129 uint32_t n = ToUint32(d);
1130 *result = NativeType(n);
1131 } else if constexpr (ArrayTypeID() == Scalar::Uint8Clamped) {
1132 // The uint8_clamped type has a special rounding converter
1133 // for doubles.
1134 *result = NativeType(d);
1135 } else {
1136 static_assert(sizeof(NativeType) <= 4);
1137 int32_t n = ToInt32(d);
1138 *result = NativeType(n);
1140 return true;
1143 template <>
1144 bool TypedArrayObjectTemplate<int64_t>::convertValue(JSContext* cx,
1145 HandleValue v,
1146 int64_t* result) {
1147 JS_TRY_VAR_OR_RETURN_FALSE(cx, *result, ToBigInt64(cx, v));
1148 return true;
1151 template <>
1152 bool TypedArrayObjectTemplate<uint64_t>::convertValue(JSContext* cx,
1153 HandleValue v,
1154 uint64_t* result) {
1155 JS_TRY_VAR_OR_RETURN_FALSE(cx, *result, ToBigUint64(cx, v));
1156 return true;
1159 // https://tc39.github.io/proposal-bigint/#sec-integerindexedelementset
1160 // 9.4.5.11 IntegerIndexedElementSet ( O, index, value )
1161 template <typename NativeType>
1162 /* static */ bool TypedArrayObjectTemplate<NativeType>::setElement(
1163 JSContext* cx, Handle<TypedArrayObject*> obj, uint64_t index, HandleValue v,
1164 ObjectOpResult& result) {
1165 MOZ_ASSERT(!obj->hasDetachedBuffer());
1166 MOZ_ASSERT(index < obj->length().valueOr(0));
1168 // Step 1 is enforced by the caller.
1170 // Steps 2-3.
1171 NativeType nativeValue;
1172 if (!convertValue(cx, v, &nativeValue)) {
1173 return false;
1176 // Step 4.
1177 if (index < obj->length().valueOr(0)) {
1178 MOZ_ASSERT(!obj->hasDetachedBuffer(),
1179 "detaching an array buffer sets the length to zero");
1180 TypedArrayObjectTemplate<NativeType>::setIndex(*obj, index, nativeValue);
1183 // Step 5.
1184 return result.succeed();
1187 } /* anonymous namespace */
1189 TypedArrayObject* js::NewTypedArrayWithTemplateAndLength(
1190 JSContext* cx, HandleObject templateObj, int32_t len) {
1191 MOZ_ASSERT(templateObj->is<TypedArrayObject>());
1192 TypedArrayObject* tobj = &templateObj->as<TypedArrayObject>();
1194 switch (tobj->type()) {
1195 #define CREATE_TYPED_ARRAY(_, T, N) \
1196 case Scalar::N: \
1197 return FixedLengthTypedArrayObjectTemplate<T>::makeTypedArrayWithTemplate( \
1198 cx, tobj, len);
1199 JS_FOR_EACH_TYPED_ARRAY(CREATE_TYPED_ARRAY)
1200 #undef CREATE_TYPED_ARRAY
1201 default:
1202 MOZ_CRASH("Unsupported TypedArray type");
1206 TypedArrayObject* js::NewTypedArrayWithTemplateAndArray(
1207 JSContext* cx, HandleObject templateObj, HandleObject array) {
1208 MOZ_ASSERT(templateObj->is<TypedArrayObject>());
1209 TypedArrayObject* tobj = &templateObj->as<TypedArrayObject>();
1211 switch (tobj->type()) {
1212 #define CREATE_TYPED_ARRAY(_, T, N) \
1213 case Scalar::N: \
1214 return TypedArrayObjectTemplate<T>::makeTypedArrayWithTemplate(cx, tobj, \
1215 array);
1216 JS_FOR_EACH_TYPED_ARRAY(CREATE_TYPED_ARRAY)
1217 #undef CREATE_TYPED_ARRAY
1218 default:
1219 MOZ_CRASH("Unsupported TypedArray type");
1223 TypedArrayObject* js::NewTypedArrayWithTemplateAndBuffer(
1224 JSContext* cx, HandleObject templateObj, HandleObject arrayBuffer,
1225 HandleValue byteOffset, HandleValue length) {
1226 MOZ_ASSERT(templateObj->is<TypedArrayObject>());
1227 TypedArrayObject* tobj = &templateObj->as<TypedArrayObject>();
1229 switch (tobj->type()) {
1230 #define CREATE_TYPED_ARRAY(_, T, N) \
1231 case Scalar::N: \
1232 return TypedArrayObjectTemplate<T>::makeTypedArrayWithTemplate( \
1233 cx, tobj, arrayBuffer, byteOffset, length);
1234 JS_FOR_EACH_TYPED_ARRAY(CREATE_TYPED_ARRAY)
1235 #undef CREATE_TYPED_ARRAY
1236 default:
1237 MOZ_CRASH("Unsupported TypedArray type");
1241 TypedArrayObject* js::NewUint8ArrayWithLength(JSContext* cx, int32_t len,
1242 gc::Heap heap) {
1243 return TypedArrayObjectTemplate<uint8_t>::fromLength(cx, len, nullptr, heap);
1246 template <typename T>
1247 /* static */ TypedArrayObject* TypedArrayObjectTemplate<T>::fromArray(
1248 JSContext* cx, HandleObject other, HandleObject proto /* = nullptr */) {
1249 // Allow nullptr proto for FriendAPI methods, which don't care about
1250 // subclassing.
1251 if (other->is<TypedArrayObject>()) {
1252 return fromTypedArray(cx, other, /* wrapped= */ false, proto);
1255 if (other->is<WrapperObject>() &&
1256 UncheckedUnwrap(other)->is<TypedArrayObject>()) {
1257 return fromTypedArray(cx, other, /* wrapped= */ true, proto);
1260 return fromObject(cx, other, proto);
1263 // ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78
1264 // 23.2.5.1 TypedArray ( ...args )
1265 // 23.2.5.1.2 InitializeTypedArrayFromTypedArray ( O, srcArray )
1266 template <typename T>
1267 /* static */ TypedArrayObject* TypedArrayObjectTemplate<T>::fromTypedArray(
1268 JSContext* cx, HandleObject other, bool isWrapped, HandleObject proto) {
1269 MOZ_ASSERT_IF(!isWrapped, other->is<TypedArrayObject>());
1270 MOZ_ASSERT_IF(isWrapped, other->is<WrapperObject>() &&
1271 UncheckedUnwrap(other)->is<TypedArrayObject>());
1273 Rooted<TypedArrayObject*> srcArray(cx);
1274 if (!isWrapped) {
1275 srcArray = &other->as<TypedArrayObject>();
1276 } else {
1277 srcArray = other->maybeUnwrapAs<TypedArrayObject>();
1278 if (!srcArray) {
1279 ReportAccessDenied(cx);
1280 return nullptr;
1284 // InitializeTypedArrayFromTypedArray, step 1. (Skipped)
1286 // InitializeTypedArrayFromTypedArray, step 2.
1287 auto srcLength = srcArray->length();
1288 if (!srcLength) {
1289 ReportOutOfBounds(cx, srcArray);
1290 return nullptr;
1293 // InitializeTypedArrayFromTypedArray, steps 3-7. (Skipped)
1295 // InitializeTypedArrayFromTypedArray, step 8.
1296 size_t elementLength = *srcLength;
1298 // InitializeTypedArrayFromTypedArray, step 9. (Skipped)
1300 // InitializeTypedArrayFromTypedArray, step 10.a. (Partial)
1301 // InitializeTypedArrayFromTypedArray, step 11.a.
1302 Rooted<ArrayBufferObject*> buffer(cx);
1303 if (!maybeCreateArrayBuffer(cx, elementLength, &buffer)) {
1304 return nullptr;
1307 // InitializeTypedArrayFromTypedArray, step 11.b.
1308 if (Scalar::isBigIntType(ArrayTypeID()) !=
1309 Scalar::isBigIntType(srcArray->type())) {
1310 JS_ReportErrorNumberASCII(
1311 cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_NOT_COMPATIBLE,
1312 srcArray->getClass()->name,
1313 TypedArrayObject::fixedLengthClasses[ArrayTypeID()].name);
1314 return nullptr;
1317 // Step 6.b.i.
1318 // InitializeTypedArrayFromTypedArray, steps 12-15.
1319 Rooted<TypedArrayObject*> obj(cx, FixedLengthTypedArray::makeInstance(
1320 cx, buffer, 0, elementLength, proto));
1321 if (!obj) {
1322 return nullptr;
1325 MOZ_RELEASE_ASSERT(!srcArray->hasDetachedBuffer());
1327 // InitializeTypedArrayFromTypedArray, steps 10.a. (Remaining parts)
1328 // InitializeTypedArrayFromTypedArray, steps 11.c-f.
1329 MOZ_ASSERT(!obj->isSharedMemory());
1330 if (srcArray->isSharedMemory()) {
1331 if (!ElementSpecific<T, SharedOps>::setFromTypedArray(
1332 obj, elementLength, srcArray, elementLength, 0)) {
1333 MOZ_ASSERT_UNREACHABLE(
1334 "setFromTypedArray can only fail for overlapping buffers");
1335 return nullptr;
1337 } else {
1338 if (!ElementSpecific<T, UnsharedOps>::setFromTypedArray(
1339 obj, elementLength, srcArray, elementLength, 0)) {
1340 MOZ_ASSERT_UNREACHABLE(
1341 "setFromTypedArray can only fail for overlapping buffers");
1342 return nullptr;
1346 // Step 6.b.v.
1347 return obj;
1350 static MOZ_ALWAYS_INLINE bool IsOptimizableInit(JSContext* cx,
1351 HandleObject iterable,
1352 bool* optimized) {
1353 MOZ_ASSERT(!*optimized);
1355 if (!IsPackedArray(iterable)) {
1356 return true;
1359 ForOfPIC::Chain* stubChain = ForOfPIC::getOrCreate(cx);
1360 if (!stubChain) {
1361 return false;
1364 return stubChain->tryOptimizeArray(cx, iterable.as<ArrayObject>(), optimized);
1367 // ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78
1368 // 23.2.5.1 TypedArray ( ...args )
1369 // 23.2.5.1.4 InitializeTypedArrayFromList ( O, values )
1370 // 23.2.5.1.5 InitializeTypedArrayFromArrayLike ( O, arrayLike )
1371 template <typename T>
1372 /* static */ TypedArrayObject* TypedArrayObjectTemplate<T>::fromObject(
1373 JSContext* cx, HandleObject other, HandleObject proto) {
1374 // Steps 1-4 and 6.a (Already performed in caller).
1376 // Steps 6.b.i (Allocation deferred until later).
1378 // Steps 6.b.ii-iii. (Not applicable)
1380 // Step 6.b.iv.
1382 bool optimized = false;
1383 if (!IsOptimizableInit(cx, other, &optimized)) {
1384 return nullptr;
1387 // Fast path when iterable is a packed array using the default iterator.
1388 if (optimized) {
1389 // Steps 6.b.iv.2-3. (We don't need to call IterableToList for the fast
1390 // path).
1391 Handle<ArrayObject*> array = other.as<ArrayObject>();
1393 // InitializeTypedArrayFromList, step 1.
1394 size_t len = array->getDenseInitializedLength();
1396 // InitializeTypedArrayFromList, step 2.
1397 Rooted<ArrayBufferObject*> buffer(cx);
1398 if (!maybeCreateArrayBuffer(cx, len, &buffer)) {
1399 return nullptr;
1402 // Steps 6.b.i.
1403 Rooted<FixedLengthTypedArrayObject*> obj(
1404 cx, FixedLengthTypedArray::makeInstance(cx, buffer, 0, len, proto));
1405 if (!obj) {
1406 return nullptr;
1409 // InitializeTypedArrayFromList, steps 3-4.
1410 MOZ_ASSERT(!obj->isSharedMemory());
1411 if (!ElementSpecific<T, UnsharedOps>::initFromIterablePackedArray(cx, obj,
1412 array)) {
1413 return nullptr;
1416 // InitializeTypedArrayFromList, step 5. (The assertion isn't applicable for
1417 // the fast path).
1419 // Step 6.b.v.
1420 return obj;
1423 // Step 6.b.iv.1 (Assertion; implicit in our implementation).
1425 // Step 6.b.iv.2.
1426 RootedValue callee(cx);
1427 RootedId iteratorId(cx, PropertyKey::Symbol(cx->wellKnownSymbols().iterator));
1428 if (!GetProperty(cx, other, other, iteratorId, &callee)) {
1429 return nullptr;
1432 // Steps 6.b.iv.3-4.
1433 RootedObject arrayLike(cx);
1434 if (!callee.isNullOrUndefined()) {
1435 // Throw if other[Symbol.iterator] isn't callable.
1436 if (!callee.isObject() || !callee.toObject().isCallable()) {
1437 RootedValue otherVal(cx, ObjectValue(*other));
1438 UniqueChars bytes =
1439 DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, otherVal, nullptr);
1440 if (!bytes) {
1441 return nullptr;
1443 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE,
1444 bytes.get());
1445 return nullptr;
1448 FixedInvokeArgs<2> args2(cx);
1449 args2[0].setObject(*other);
1450 args2[1].set(callee);
1452 // Step 6.b.iv.3.a.
1453 RootedValue rval(cx);
1454 if (!CallSelfHostedFunction(cx, cx->names().IterableToList,
1455 UndefinedHandleValue, args2, &rval)) {
1456 return nullptr;
1459 // Step 6.b.iv.3.b (Implemented below).
1460 arrayLike = &rval.toObject();
1461 } else {
1462 // Step 4.a is an assertion: object is not an Iterator. Testing this is
1463 // literally the very last thing we did, so we don't assert here.
1465 // Step 4.b (Implemented below).
1466 arrayLike = other;
1469 // We implement InitializeTypedArrayFromList in terms of
1470 // InitializeTypedArrayFromArrayLike.
1472 // InitializeTypedArrayFromArrayLike, step 1.
1473 uint64_t len;
1474 if (!GetLengthProperty(cx, arrayLike, &len)) {
1475 return nullptr;
1478 // InitializeTypedArrayFromArrayLike, step 2.
1479 Rooted<ArrayBufferObject*> buffer(cx);
1480 if (!maybeCreateArrayBuffer(cx, len, &buffer)) {
1481 return nullptr;
1484 MOZ_ASSERT(len <= ByteLengthLimit / BYTES_PER_ELEMENT);
1486 // Steps 6.b.i.
1487 Rooted<TypedArrayObject*> obj(
1488 cx, FixedLengthTypedArray::makeInstance(cx, buffer, 0, len, proto));
1489 if (!obj) {
1490 return nullptr;
1493 // InitializeTypedArrayFromArrayLike, steps 3-4.
1494 MOZ_ASSERT(!obj->isSharedMemory());
1495 if (!ElementSpecific<T, UnsharedOps>::setFromNonTypedArray(cx, obj, arrayLike,
1496 len)) {
1497 return nullptr;
1500 // Step 6.b.v.
1501 return obj;
1504 static bool TypedArrayConstructor(JSContext* cx, unsigned argc, Value* vp) {
1505 CallArgs args = CallArgsFromVp(argc, vp);
1506 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1507 JSMSG_TYPED_ARRAY_CALL_OR_CONSTRUCT,
1508 args.isConstructing() ? "construct" : "call");
1509 return false;
1512 template <typename T>
1513 static bool GetTemplateObjectForNative(JSContext* cx,
1514 const JS::HandleValueArray args,
1515 MutableHandleObject res) {
1516 if (args.length() == 0) {
1517 return true;
1520 HandleValue arg = args[0];
1521 if (arg.isInt32()) {
1522 uint32_t len = 0;
1523 if (arg.toInt32() >= 0) {
1524 len = arg.toInt32();
1527 size_t nbytes;
1528 if (!js::CalculateAllocSize<T>(len, &nbytes) ||
1529 nbytes > TypedArrayObject::ByteLengthLimit) {
1530 return true;
1533 res.set(
1534 FixedLengthTypedArrayObjectTemplate<T>::makeTemplateObject(cx, len));
1535 return !!res;
1538 if (!arg.isObject()) {
1539 return true;
1541 auto* obj = &arg.toObject();
1543 // We don't support wrappers, because of the complicated interaction between
1544 // wrapped ArrayBuffers and TypedArrays, see |fromBufferWrapped()|.
1545 if (IsWrapper(obj)) {
1546 return true;
1549 // We don't use the template's length in the object case, so we can create
1550 // the template typed array with an initial length of zero.
1551 uint32_t len = 0;
1553 if (!obj->is<ArrayBufferObjectMaybeShared>() ||
1554 !obj->as<ArrayBufferObjectMaybeShared>().isResizable()) {
1555 res.set(
1556 FixedLengthTypedArrayObjectTemplate<T>::makeTemplateObject(cx, len));
1557 } else {
1558 res.set(ResizableTypedArrayObjectTemplate<T>::makeTemplateObject(cx));
1560 return !!res;
1563 /* static */ bool TypedArrayObject::GetTemplateObjectForNative(
1564 JSContext* cx, Native native, const JS::HandleValueArray args,
1565 MutableHandleObject res) {
1566 MOZ_ASSERT(!res);
1567 #define CHECK_TYPED_ARRAY_CONSTRUCTOR(_, T, N) \
1568 if (native == &TypedArrayObjectTemplate<T>::class_constructor) { \
1569 return ::GetTemplateObjectForNative<T>(cx, args, res); \
1571 JS_FOR_EACH_TYPED_ARRAY(CHECK_TYPED_ARRAY_CONSTRUCTOR)
1572 #undef CHECK_TYPED_ARRAY_CONSTRUCTOR
1573 return true;
1576 static bool LengthGetterImpl(JSContext* cx, const CallArgs& args) {
1577 auto* tarr = &args.thisv().toObject().as<TypedArrayObject>();
1578 args.rval().setNumber(tarr->length().valueOr(0));
1579 return true;
1582 static bool TypedArray_lengthGetter(JSContext* cx, unsigned argc, Value* vp) {
1583 CallArgs args = CallArgsFromVp(argc, vp);
1584 return CallNonGenericMethod<IsTypedArrayObject, LengthGetterImpl>(cx, args);
1587 static bool ByteOffsetGetterImpl(JSContext* cx, const CallArgs& args) {
1588 auto* tarr = &args.thisv().toObject().as<TypedArrayObject>();
1589 args.rval().setNumber(tarr->byteOffset().valueOr(0));
1590 return true;
1593 static bool TypedArray_byteOffsetGetter(JSContext* cx, unsigned argc,
1594 Value* vp) {
1595 CallArgs args = CallArgsFromVp(argc, vp);
1596 return CallNonGenericMethod<IsTypedArrayObject, ByteOffsetGetterImpl>(cx,
1597 args);
1600 static bool ByteLengthGetterImpl(JSContext* cx, const CallArgs& args) {
1601 auto* tarr = &args.thisv().toObject().as<TypedArrayObject>();
1602 args.rval().setNumber(tarr->byteLength().valueOr(0));
1603 return true;
1606 static bool TypedArray_byteLengthGetter(JSContext* cx, unsigned argc,
1607 Value* vp) {
1608 CallArgs args = CallArgsFromVp(argc, vp);
1609 return CallNonGenericMethod<IsTypedArrayObject, ByteLengthGetterImpl>(cx,
1610 args);
1613 static bool BufferGetterImpl(JSContext* cx, const CallArgs& args) {
1614 MOZ_ASSERT(IsTypedArrayObject(args.thisv()));
1615 Rooted<TypedArrayObject*> tarray(
1616 cx, &args.thisv().toObject().as<TypedArrayObject>());
1617 if (!TypedArrayObject::ensureHasBuffer(cx, tarray)) {
1618 return false;
1620 args.rval().set(tarray->bufferValue());
1621 return true;
1624 static bool TypedArray_bufferGetter(JSContext* cx, unsigned argc, Value* vp) {
1625 CallArgs args = CallArgsFromVp(argc, vp);
1626 return CallNonGenericMethod<IsTypedArrayObject, BufferGetterImpl>(cx, args);
1629 // ES2019 draft rev fc9ecdcd74294d0ca3146d4b274e2a8e79565dc3
1630 // 22.2.3.32 get %TypedArray%.prototype [ @@toStringTag ]
1631 static bool TypedArray_toStringTagGetter(JSContext* cx, unsigned argc,
1632 Value* vp) {
1633 CallArgs args = CallArgsFromVp(argc, vp);
1635 // Steps 1-2.
1636 if (!args.thisv().isObject()) {
1637 args.rval().setUndefined();
1638 return true;
1641 JSObject* obj = CheckedUnwrapStatic(&args.thisv().toObject());
1642 if (!obj) {
1643 ReportAccessDenied(cx);
1644 return false;
1647 // Step 3.
1648 if (!obj->is<TypedArrayObject>()) {
1649 args.rval().setUndefined();
1650 return true;
1653 // Steps 4-6.
1654 JSProtoKey protoKey = StandardProtoKeyOrNull(obj);
1655 MOZ_ASSERT(protoKey);
1657 args.rval().setString(ClassName(protoKey, cx));
1658 return true;
1661 /* static */ const JSPropertySpec TypedArrayObject::protoAccessors[] = {
1662 JS_PSG("length", TypedArray_lengthGetter, 0),
1663 JS_PSG("buffer", TypedArray_bufferGetter, 0),
1664 JS_PSG("byteLength", TypedArray_byteLengthGetter, 0),
1665 JS_PSG("byteOffset", TypedArray_byteOffsetGetter, 0),
1666 JS_SYM_GET(toStringTag, TypedArray_toStringTagGetter, 0),
1667 JS_PS_END};
1669 template <typename T>
1670 static inline bool SetFromTypedArray(Handle<TypedArrayObject*> target,
1671 size_t targetLength,
1672 Handle<TypedArrayObject*> source,
1673 size_t sourceLength, size_t offset) {
1674 // WARNING: |source| may be an unwrapped typed array from a different
1675 // compartment. Proceed with caution!
1677 if (target->isSharedMemory() || source->isSharedMemory()) {
1678 return ElementSpecific<T, SharedOps>::setFromTypedArray(
1679 target, targetLength, source, sourceLength, offset);
1681 return ElementSpecific<T, UnsharedOps>::setFromTypedArray(
1682 target, targetLength, source, sourceLength, offset);
1685 template <typename T>
1686 static inline bool SetFromNonTypedArray(JSContext* cx,
1687 Handle<TypedArrayObject*> target,
1688 HandleObject source, size_t len,
1689 size_t offset) {
1690 MOZ_ASSERT(!source->is<TypedArrayObject>(), "use SetFromTypedArray");
1692 if (target->isSharedMemory()) {
1693 return ElementSpecific<T, SharedOps>::setFromNonTypedArray(
1694 cx, target, source, len, offset);
1696 return ElementSpecific<T, UnsharedOps>::setFromNonTypedArray(
1697 cx, target, source, len, offset);
1700 // ES2023 draft rev 22cc56ab08fcab92a865978c0aa5c6f1d8ce250f
1701 // 23.2.3.24.1 SetTypedArrayFromTypedArray ( target, targetOffset, source )
1702 static bool SetTypedArrayFromTypedArray(JSContext* cx,
1703 Handle<TypedArrayObject*> target,
1704 double targetOffset,
1705 size_t targetLength,
1706 Handle<TypedArrayObject*> source) {
1707 // WARNING: |source| may be an unwrapped typed array from a different
1708 // compartment. Proceed with caution!
1710 MOZ_ASSERT(targetOffset >= 0);
1712 // Steps 1-3. (Performed in caller.)
1713 MOZ_ASSERT(!target->hasDetachedBuffer());
1715 // Steps 4-5.
1716 auto sourceLength = source->length();
1717 if (!sourceLength) {
1718 ReportOutOfBounds(cx, source);
1719 return false;
1722 // Steps 13-14 (Split into two checks to provide better error messages).
1723 if (targetOffset > targetLength) {
1724 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
1725 return false;
1728 // Step 14 (Cont'd).
1729 size_t offset = size_t(targetOffset);
1730 if (*sourceLength > targetLength - offset) {
1731 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1732 JSMSG_SOURCE_ARRAY_TOO_LONG);
1733 return false;
1736 // Step 15.
1737 if (Scalar::isBigIntType(target->type()) !=
1738 Scalar::isBigIntType(source->type())) {
1739 JS_ReportErrorNumberASCII(
1740 cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_NOT_COMPATIBLE,
1741 source->getClass()->name, target->getClass()->name);
1742 return false;
1745 // Steps 6-12, 16-24.
1746 switch (target->type()) {
1747 #define SET_FROM_TYPED_ARRAY(_, T, N) \
1748 case Scalar::N: \
1749 if (!SetFromTypedArray<T>(target, targetLength, source, *sourceLength, \
1750 offset)) { \
1751 ReportOutOfMemory(cx); \
1752 return false; \
1754 break;
1755 JS_FOR_EACH_TYPED_ARRAY(SET_FROM_TYPED_ARRAY)
1756 #undef SET_FROM_TYPED_ARRAY
1757 default:
1758 MOZ_CRASH("Unsupported TypedArray type");
1761 return true;
1764 // ES2023 draft rev 22cc56ab08fcab92a865978c0aa5c6f1d8ce250f
1765 // 23.2.3.24.1 SetTypedArrayFromArrayLike ( target, targetOffset, source )
1766 static bool SetTypedArrayFromArrayLike(JSContext* cx,
1767 Handle<TypedArrayObject*> target,
1768 double targetOffset, size_t targetLength,
1769 HandleObject src) {
1770 MOZ_ASSERT(targetOffset >= 0);
1772 // Steps 1-2. (Performed in caller.)
1773 MOZ_ASSERT(target->length().isSome());
1775 // Steps 3-4. (Performed in caller.)
1777 // Step 5.
1778 uint64_t srcLength;
1779 if (!GetLengthProperty(cx, src, &srcLength)) {
1780 return false;
1783 // Steps 6-7 (Split into two checks to provide better error messages).
1784 if (targetOffset > targetLength) {
1785 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
1786 return false;
1789 // Step 7 (Cont'd).
1790 size_t offset = size_t(targetOffset);
1791 if (srcLength > targetLength - offset) {
1792 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1793 JSMSG_SOURCE_ARRAY_TOO_LONG);
1794 return false;
1797 MOZ_ASSERT(srcLength <= targetLength);
1799 // Steps 8-9.
1800 if (srcLength > 0) {
1801 switch (target->type()) {
1802 #define SET_FROM_NON_TYPED_ARRAY(_, T, N) \
1803 case Scalar::N: \
1804 if (!SetFromNonTypedArray<T>(cx, target, src, srcLength, offset)) \
1805 return false; \
1806 break;
1807 JS_FOR_EACH_TYPED_ARRAY(SET_FROM_NON_TYPED_ARRAY)
1808 #undef SET_FROM_NON_TYPED_ARRAY
1809 default:
1810 MOZ_CRASH("Unsupported TypedArray type");
1814 // Step 10.
1815 return true;
1818 // ES2023 draft rev 22cc56ab08fcab92a865978c0aa5c6f1d8ce250f
1819 // 23.2.3.24 %TypedArray%.prototype.set ( source [ , offset ] )
1820 // 23.2.3.24.1 SetTypedArrayFromTypedArray ( target, targetOffset, source )
1821 // 23.2.3.24.2 SetTypedArrayFromArrayLike ( target, targetOffset, source )
1822 /* static */
1823 bool TypedArrayObject::set_impl(JSContext* cx, const CallArgs& args) {
1824 MOZ_ASSERT(IsTypedArrayObject(args.thisv()));
1826 // Steps 1-3 (Validation performed as part of CallNonGenericMethod).
1827 Rooted<TypedArrayObject*> target(
1828 cx, &args.thisv().toObject().as<TypedArrayObject>());
1830 // Steps 4-5.
1831 double targetOffset = 0;
1832 if (args.length() > 1) {
1833 // Step 4.
1834 if (!ToInteger(cx, args[1], &targetOffset)) {
1835 return false;
1838 // Step 5.
1839 if (targetOffset < 0) {
1840 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
1841 return false;
1845 // 23.2.3.24.1, steps 1-2.
1846 // 23.2.3.24.2, steps 1-2.
1847 auto targetLength = target->length();
1848 if (!targetLength) {
1849 ReportOutOfBounds(cx, target);
1850 return false;
1853 // 23.2.3.24.2, step 4. (23.2.3.24.1 only applies if args[0] is a typed
1854 // array, so it doesn't make a difference there to apply ToObject here.)
1855 RootedObject src(cx, ToObject(cx, args.get(0)));
1856 if (!src) {
1857 return false;
1860 Rooted<TypedArrayObject*> srcTypedArray(cx);
1862 JSObject* obj = CheckedUnwrapStatic(src);
1863 if (!obj) {
1864 ReportAccessDenied(cx);
1865 return false;
1868 if (obj->is<TypedArrayObject>()) {
1869 srcTypedArray = &obj->as<TypedArrayObject>();
1873 // Steps 6-7.
1874 if (srcTypedArray) {
1875 if (!SetTypedArrayFromTypedArray(cx, target, targetOffset, *targetLength,
1876 srcTypedArray)) {
1877 return false;
1879 } else {
1880 if (!SetTypedArrayFromArrayLike(cx, target, targetOffset, *targetLength,
1881 src)) {
1882 return false;
1886 // Step 8.
1887 args.rval().setUndefined();
1888 return true;
1891 /* static */
1892 bool TypedArrayObject::set(JSContext* cx, unsigned argc, Value* vp) {
1893 CallArgs args = CallArgsFromVp(argc, vp);
1894 return CallNonGenericMethod<IsTypedArrayObject, TypedArrayObject::set_impl>(
1895 cx, args);
1898 // ES2020 draft rev dc1e21c454bd316810be1c0e7af0131a2d7f38e9
1899 // 22.2.3.5 %TypedArray%.prototype.copyWithin ( target, start [ , end ] )
1900 /* static */
1901 bool TypedArrayObject::copyWithin_impl(JSContext* cx, const CallArgs& args) {
1902 MOZ_ASSERT(IsTypedArrayObject(args.thisv()));
1904 // Steps 1-2.
1905 Rooted<TypedArrayObject*> tarray(
1906 cx, &args.thisv().toObject().as<TypedArrayObject>());
1908 auto arrayLength = tarray->length();
1909 if (!arrayLength) {
1910 ReportOutOfBounds(cx, tarray);
1911 return false;
1914 // Step 3.
1915 size_t len = *arrayLength;
1917 // Step 4.
1918 double relativeTarget;
1919 if (!ToInteger(cx, args.get(0), &relativeTarget)) {
1920 return false;
1923 // Step 5.
1924 uint64_t to;
1925 if (relativeTarget < 0) {
1926 to = std::max(len + relativeTarget, 0.0);
1927 } else {
1928 to = std::min(relativeTarget, double(len));
1931 // Step 6.
1932 double relativeStart;
1933 if (!ToInteger(cx, args.get(1), &relativeStart)) {
1934 return false;
1937 // Step 7.
1938 uint64_t from;
1939 if (relativeStart < 0) {
1940 from = std::max(len + relativeStart, 0.0);
1941 } else {
1942 from = std::min(relativeStart, double(len));
1945 // Step 8.
1946 double relativeEnd;
1947 if (!args.hasDefined(2)) {
1948 relativeEnd = len;
1949 } else {
1950 if (!ToInteger(cx, args[2], &relativeEnd)) {
1951 return false;
1955 // Step 9.
1956 uint64_t final_;
1957 if (relativeEnd < 0) {
1958 final_ = std::max(len + relativeEnd, 0.0);
1959 } else {
1960 final_ = std::min(relativeEnd, double(len));
1963 // Step 10.
1964 MOZ_ASSERT(to <= len);
1965 uint64_t count;
1966 if (from <= final_) {
1967 count = std::min(final_ - from, len - to);
1968 } else {
1969 count = 0;
1972 // Step 11.
1974 // Note that this copies elements effectively by memmove, *not* in
1975 // step 11's specified order. This is unobservable, even when the underlying
1976 // buffer is a SharedArrayBuffer instance, because the access is unordered and
1977 // therefore is allowed to have data races.
1979 if (count == 0) {
1980 args.rval().setObject(*tarray);
1981 return true;
1984 // Reacquire the length because side-effects may have detached or resized the
1985 // array buffer.
1986 arrayLength = tarray->length();
1987 if (!arrayLength) {
1988 ReportOutOfBounds(cx, tarray);
1989 return false;
1992 // Recompute the bounds if the current length is smaller.
1993 if (*arrayLength < len) {
1994 MOZ_ASSERT(to + count <= len);
1995 MOZ_ASSERT(from + count <= len);
1997 len = *arrayLength;
1999 // Don't copy any bytes if either index is no longer in-bounds.
2000 if (to >= len || from >= len) {
2001 args.rval().setObject(*tarray);
2002 return true;
2005 // Restrict |count| to not copy any bytes after the end of the array.
2006 count = std::min(count, std::min(len - to, len - from));
2007 MOZ_ASSERT(count > 0);
2010 // Don't multiply by |tarray->bytesPerElement()| in case the compiler can't
2011 // strength-reduce multiplication by 1/2/4/8 into the equivalent shift.
2012 const size_t ElementShift = TypedArrayShift(tarray->type());
2014 MOZ_ASSERT((SIZE_MAX >> ElementShift) > to);
2015 size_t byteDest = to << ElementShift;
2017 MOZ_ASSERT((SIZE_MAX >> ElementShift) > from);
2018 size_t byteSrc = from << ElementShift;
2020 MOZ_ASSERT((SIZE_MAX >> ElementShift) >= count);
2021 size_t byteSize = count << ElementShift;
2023 #ifdef DEBUG
2025 size_t viewByteLength = len << ElementShift;
2026 MOZ_ASSERT(byteSize <= viewByteLength);
2027 MOZ_ASSERT(byteDest < viewByteLength);
2028 MOZ_ASSERT(byteSrc < viewByteLength);
2029 MOZ_ASSERT(byteDest <= viewByteLength - byteSize);
2030 MOZ_ASSERT(byteSrc <= viewByteLength - byteSize);
2032 #endif
2034 SharedMem<uint8_t*> data = tarray->dataPointerEither().cast<uint8_t*>();
2035 if (tarray->isSharedMemory()) {
2036 jit::AtomicOperations::memmoveSafeWhenRacy(data + byteDest, data + byteSrc,
2037 byteSize);
2038 } else {
2039 memmove(data.unwrapUnshared() + byteDest, data.unwrapUnshared() + byteSrc,
2040 byteSize);
2043 args.rval().setObject(*tarray);
2044 return true;
2047 /* static */
2048 bool TypedArrayObject::copyWithin(JSContext* cx, unsigned argc, Value* vp) {
2049 AutoJSMethodProfilerEntry pseudoFrame(cx, "[TypedArray].prototype",
2050 "copyWithin");
2051 CallArgs args = CallArgsFromVp(argc, vp);
2052 return CallNonGenericMethod<IsTypedArrayObject,
2053 TypedArrayObject::copyWithin_impl>(cx, args);
2056 // Byte vector with large enough inline storage to allow constructing small
2057 // typed arrays without extra heap allocations.
2058 using ByteVector =
2059 js::Vector<uint8_t, FixedLengthTypedArrayObject::INLINE_BUFFER_LIMIT>;
2061 static UniqueChars QuoteString(JSContext* cx, char16_t ch) {
2062 Sprinter sprinter(cx);
2063 if (!sprinter.init()) {
2064 return nullptr;
2067 StringEscape esc{};
2068 js::EscapePrinter ep(sprinter, esc);
2069 ep.putChar(ch);
2071 return sprinter.release();
2075 * FromHex ( string [ , maxLength ] )
2077 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-fromhex
2079 static bool FromHex(JSContext* cx, Handle<JSString*> string, size_t maxLength,
2080 ByteVector& bytes, size_t* readLength) {
2081 // Step 1. (Not applicable in our implementation.)
2083 // Step 2.
2084 size_t length = string->length();
2086 // Step 3.
2087 if (length % 2 != 0) {
2088 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2089 JSMSG_TYPED_ARRAY_BAD_HEX_STRING_LENGTH);
2090 return false;
2093 JSLinearString* linear = string->ensureLinear(cx);
2094 if (!linear) {
2095 return false;
2098 // Step 4. (Not applicable in our implementation.)
2099 MOZ_ASSERT(bytes.empty());
2101 // Step 5.
2102 size_t index = 0;
2104 // Step 6.
2105 while (index < length && bytes.length() < maxLength) {
2106 // Step 6.a.
2107 char16_t c0 = linear->latin1OrTwoByteChar(index);
2108 char16_t c1 = linear->latin1OrTwoByteChar(index + 1);
2110 // Step 6.b.
2111 if (MOZ_UNLIKELY(!mozilla::IsAsciiHexDigit(c0) ||
2112 !mozilla::IsAsciiHexDigit(c1))) {
2113 char16_t ch = !mozilla::IsAsciiHexDigit(c0) ? c0 : c1;
2114 if (auto str = QuoteString(cx, ch)) {
2115 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2116 JSMSG_TYPED_ARRAY_BAD_HEX_DIGIT, str.get());
2118 return false;
2121 // Step 6.c.
2122 index += 2;
2124 // Step 6.d.
2125 uint8_t byte = (mozilla::AsciiAlphanumericToNumber(c0) << 4) +
2126 mozilla::AsciiAlphanumericToNumber(c1);
2128 // Step 6.e.
2129 if (!bytes.append(byte)) {
2130 return false;
2134 // Step 7.
2135 *readLength = index;
2136 return true;
2139 namespace Base64 {
2140 static constexpr uint8_t InvalidChar = UINT8_MAX;
2142 static constexpr auto DecodeTable(const char (&alphabet)[65]) {
2143 std::array<uint8_t, 128> result = {};
2145 // Initialize all elements to InvalidChar.
2146 for (auto& e : result) {
2147 e = InvalidChar;
2150 // Map the base64 characters to their values.
2151 for (uint8_t i = 0; i < 64; ++i) {
2152 result[alphabet[i]] = i;
2155 return result;
2157 } // namespace Base64
2159 namespace Base64::Encode {
2160 static constexpr const char Base64[] =
2161 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2162 static_assert(std::char_traits<char>::length(Base64) == 64);
2164 static constexpr const char Base64Url[] =
2165 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
2166 static_assert(std::char_traits<char>::length(Base64Url) == 64);
2167 } // namespace Base64::Encode
2169 namespace Base64::Decode {
2170 static constexpr auto Base64 = DecodeTable(Base64::Encode::Base64);
2171 static_assert(Base64.size() == 128,
2172 "128 elements to allow access through ASCII characters");
2174 static constexpr auto Base64Url = DecodeTable(Base64::Encode::Base64Url);
2175 static_assert(Base64Url.size() == 128,
2176 "128 elements to allow access through ASCII characters");
2177 } // namespace Base64::Decode
2179 enum class Alphabet {
2181 * Standard base64 alphabet.
2183 Base64,
2186 * URL and filename safe base64 alphabet.
2188 Base64Url,
2191 enum class LastChunkHandling {
2193 * Allow partial chunks at the end of the input.
2195 Loose,
2198 * Disallow partial chunks at the end of the input.
2200 Strict,
2203 * Stop before partial chunks at the end of the input.
2205 StopBeforePartial,
2209 * FromBase64 ( string, alphabet, lastChunkHandling [ , maxLength ] )
2211 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-frombase64
2213 static bool FromBase64(JSContext* cx, Handle<JSString*> string,
2214 Alphabet alphabet, LastChunkHandling lastChunkHandling,
2215 size_t maxLength, ByteVector& bytes,
2216 size_t* readLength) {
2217 // Steps 1-2. (Not applicable in our implementation.)
2219 // Step 3.
2220 size_t remaining = maxLength;
2221 if (remaining == 0) {
2222 MOZ_ASSERT(bytes.empty());
2223 *readLength = 0;
2224 return true;
2227 JSLinearString* linear = string->ensureLinear(cx);
2228 if (!linear) {
2229 return false;
2232 // DecodeBase64Chunk ( chunk [ , throwOnExtraBits ] )
2234 // Encode a complete base64 chunk.
2235 auto decodeChunk = [&](uint32_t chunk) {
2236 MOZ_ASSERT(chunk <= 0xffffff);
2237 MOZ_ASSERT(remaining >= 3);
2239 if (!bytes.reserve(bytes.length() + 3)) {
2240 return false;
2242 bytes.infallibleAppend(chunk >> 16);
2243 bytes.infallibleAppend(chunk >> 8);
2244 bytes.infallibleAppend(chunk);
2245 return true;
2248 // DecodeBase64Chunk ( chunk [ , throwOnExtraBits ] )
2250 // Encode a three element partial base64 chunk.
2251 auto decodeChunk3 = [&](uint32_t chunk, bool throwOnExtraBits) {
2252 MOZ_ASSERT(chunk <= 0x3ffff);
2253 MOZ_ASSERT(remaining >= 2);
2255 if (throwOnExtraBits && (chunk & 0x3) != 0) {
2256 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2257 JSMSG_TYPED_ARRAY_EXTRA_BASE64_BITS);
2258 return false;
2261 if (!bytes.reserve(bytes.length() + 2)) {
2262 return false;
2264 bytes.infallibleAppend(chunk >> 10);
2265 bytes.infallibleAppend(chunk >> 2);
2266 return true;
2269 // DecodeBase64Chunk ( chunk [ , throwOnExtraBits ] )
2271 // Encode a two element partial base64 chunk.
2272 auto decodeChunk2 = [&](uint32_t chunk, bool throwOnExtraBits) {
2273 MOZ_ASSERT(chunk <= 0xfff);
2274 MOZ_ASSERT(remaining >= 1);
2276 if (throwOnExtraBits && (chunk & 0xf) != 0) {
2277 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2278 JSMSG_TYPED_ARRAY_EXTRA_BASE64_BITS);
2279 return false;
2282 if (!bytes.reserve(bytes.length() + 1)) {
2283 return false;
2285 bytes.infallibleAppend(chunk >> 4);
2286 return true;
2289 // DecodeBase64Chunk ( chunk [ , throwOnExtraBits ] )
2291 // Encode a partial base64 chunk.
2292 auto decodePartialChunk = [&](uint32_t chunk, uint32_t chunkLength,
2293 bool throwOnExtraBits = false) {
2294 MOZ_ASSERT(chunkLength == 2 || chunkLength == 3);
2295 return chunkLength == 2 ? decodeChunk2(chunk, throwOnExtraBits)
2296 : decodeChunk3(chunk, throwOnExtraBits);
2299 // Step 4.
2301 // String index after the last fully read base64 chunk.
2302 size_t read = 0;
2304 // Step 5.
2305 MOZ_ASSERT(bytes.empty());
2307 // Step 6.
2309 // Current base64 chunk, a uint24 number.
2310 uint32_t chunk = 0;
2312 // Step 7.
2314 // Current base64 chunk length, in the range [0..4].
2315 size_t chunkLength = 0;
2317 // Step 8.
2319 // Current string index.
2320 size_t index = 0;
2322 // Step 9.
2323 size_t length = linear->length();
2325 const auto& decode = alphabet == Alphabet::Base64 ? Base64::Decode::Base64
2326 : Base64::Decode::Base64Url;
2328 // Step 10.
2329 for (; index < length; index++) {
2330 // Step 10.c. (Reordered)
2331 char16_t ch = linear->latin1OrTwoByteChar(index);
2333 // Step 10.a.
2334 if (mozilla::IsAsciiWhitespace(ch)) {
2335 continue;
2338 // Step 10.b. (Moved out of loop.)
2340 // Step 10.d. (Performed in for-loop step.)
2342 // Step 10.e.
2343 if (ch == '=') {
2344 break;
2347 // Steps 10.f-g.
2348 uint8_t value = Base64::InvalidChar;
2349 if (mozilla::IsAscii(ch)) {
2350 value = decode[ch];
2352 if (MOZ_UNLIKELY(value == Base64::InvalidChar)) {
2353 if (auto str = QuoteString(cx, ch)) {
2354 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2355 JSMSG_TYPED_ARRAY_BAD_BASE64_CHAR, str.get());
2357 return false;
2360 // Step 10.h. (Not applicable in our implementation.)
2362 // Step 10.i.
2363 if ((remaining == 1 && chunkLength == 2) ||
2364 (remaining == 2 && chunkLength == 3)) {
2365 *readLength = read;
2366 return true;
2369 // Step 10.j.
2370 chunk = (chunk << 6) | value;
2372 // Step 10.k.
2373 chunkLength += 1;
2375 // Step 10.l.
2376 if (chunkLength == 4) {
2377 // Step 10.l.i.
2378 if (!decodeChunk(chunk)) {
2379 return false;
2382 // Step 10.l.ii.
2383 chunk = 0;
2385 // Step 10.l.iii.
2386 chunkLength = 0;
2388 // Step 10.l.iv.
2390 // NB: Add +1 to include the |index| update from step 10.d.
2391 read = index + 1;
2393 // Step 10.l.v.
2394 MOZ_ASSERT(remaining >= 3);
2395 remaining -= 3;
2396 if (remaining == 0) {
2397 *readLength = read;
2398 return true;
2403 // Step 10.b.
2404 if (index == length) {
2405 // Step 10.b.i.
2406 if (chunkLength > 0) {
2407 // Step 10.b.i.1.
2408 if (lastChunkHandling == LastChunkHandling::StopBeforePartial) {
2409 *readLength = read;
2410 return true;
2413 // Steps 10.b.i.2-3.
2414 if (lastChunkHandling == LastChunkHandling::Loose) {
2415 // Step 10.b.i.2.a.
2416 if (chunkLength == 1) {
2417 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2418 JSMSG_TYPED_ARRAY_BAD_INCOMPLETE_CHUNK);
2419 return false;
2421 MOZ_ASSERT(chunkLength == 2 || chunkLength == 3);
2423 // Step 10.b.i.2.b.
2424 if (!decodePartialChunk(chunk, chunkLength)) {
2425 return false;
2427 } else {
2428 // Step 10.b.i.3.a.
2429 MOZ_ASSERT(lastChunkHandling == LastChunkHandling::Strict);
2431 // Step 10.b.i.3.b.
2432 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2433 JSMSG_TYPED_ARRAY_BAD_INCOMPLETE_CHUNK);
2434 return false;
2438 // Step 10.b.ii.
2439 *readLength = length;
2440 return true;
2443 // Step 10.e.
2444 MOZ_ASSERT(index < length);
2445 MOZ_ASSERT(linear->latin1OrTwoByteChar(index) == '=');
2447 // Step 10.e.i.
2448 if (chunkLength < 2) {
2449 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2450 JSMSG_TYPED_ARRAY_BAD_INCOMPLETE_CHUNK);
2451 return false;
2453 MOZ_ASSERT(chunkLength == 2 || chunkLength == 3);
2455 // Step 10.e.ii. (Inlined SkipAsciiWhitespace)
2456 while (++index < length) {
2457 char16_t ch = linear->latin1OrTwoByteChar(index);
2458 if (!mozilla::IsAsciiWhitespace(ch)) {
2459 break;
2463 // Step 10.e.iii.
2464 if (chunkLength == 2) {
2465 // Step 10.e.iii.1.
2466 if (index == length) {
2467 // Step 10.e.iii.1.a.
2468 if (lastChunkHandling == LastChunkHandling::StopBeforePartial) {
2469 *readLength = read;
2470 return true;
2473 // Step 10.e.iii.1.b.
2474 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2475 JSMSG_TYPED_ARRAY_MISSING_BASE64_PADDING);
2476 return false;
2479 // Step 10.e.iii.2.
2480 char16_t ch = linear->latin1OrTwoByteChar(index);
2482 // Step 10.e.iii.3.
2483 if (ch == '=') {
2484 // Step 10.e.iii.3.a. (Inlined SkipAsciiWhitespace)
2485 while (++index < length) {
2486 char16_t ch = linear->latin1OrTwoByteChar(index);
2487 if (!mozilla::IsAsciiWhitespace(ch)) {
2488 break;
2494 // Step 10.e.iv.
2495 if (index < length) {
2496 char16_t ch = linear->latin1OrTwoByteChar(index);
2497 if (auto str = QuoteString(cx, ch)) {
2498 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2499 JSMSG_TYPED_ARRAY_BAD_BASE64_AFTER_PADDING,
2500 str.get());
2502 return false;
2505 // Steps 10.e.v-vi.
2506 bool throwOnExtraBits = lastChunkHandling == LastChunkHandling::Strict;
2508 // Step 10.e.vii.
2509 if (!decodePartialChunk(chunk, chunkLength, throwOnExtraBits)) {
2510 return false;
2513 // Step 10.e.viii.
2514 *readLength = length;
2515 return true;
2519 * Uint8Array.fromBase64 ( string [ , options ] )
2520 * Uint8Array.prototype.setFromBase64 ( string [ , options ] )
2521 * Uint8Array.prototype.toBase64 ( [ options ] )
2523 * Helper to retrieve the "alphabet" option.
2525 static bool GetAlphabetOption(JSContext* cx, Handle<JSObject*> options,
2526 Alphabet* result) {
2527 Rooted<Value> value(cx);
2528 if (!GetProperty(cx, options, options, cx->names().alphabet, &value)) {
2529 return false;
2532 if (value.isUndefined()) {
2533 *result = Alphabet::Base64;
2534 return true;
2537 if (!value.isString()) {
2538 return ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK,
2539 value, nullptr, "not a string");
2542 auto* linear = value.toString()->ensureLinear(cx);
2543 if (!linear) {
2544 return false;
2547 if (StringEqualsAscii(linear, "base64")) {
2548 *result = Alphabet::Base64;
2549 return true;
2552 if (StringEqualsAscii(linear, "base64url")) {
2553 *result = Alphabet::Base64Url;
2554 return true;
2557 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2558 JSMSG_TYPED_ARRAY_BAD_BASE64_ALPHABET);
2559 return false;
2563 * Uint8Array.fromBase64 ( string [ , options ] )
2564 * Uint8Array.prototype.setFromBase64 ( string [ , options ] )
2566 * Helper to retrieve the "lastChunkHandling" option.
2568 static bool GetLastChunkHandlingOption(JSContext* cx, Handle<JSObject*> options,
2569 LastChunkHandling* result) {
2570 Rooted<Value> value(cx);
2571 if (!GetProperty(cx, options, options, cx->names().lastChunkHandling,
2572 &value)) {
2573 return false;
2576 if (value.isUndefined()) {
2577 *result = LastChunkHandling::Loose;
2578 return true;
2581 if (!value.isString()) {
2582 return ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK,
2583 value, nullptr, "not a string");
2586 auto* linear = value.toString()->ensureLinear(cx);
2587 if (!linear) {
2588 return false;
2591 if (StringEqualsAscii(linear, "loose")) {
2592 *result = LastChunkHandling::Loose;
2593 return true;
2596 if (StringEqualsAscii(linear, "strict")) {
2597 *result = LastChunkHandling::Strict;
2598 return true;
2601 if (StringEqualsAscii(linear, "stop-before-partial")) {
2602 *result = LastChunkHandling::StopBeforePartial;
2603 return true;
2606 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2607 JSMSG_TYPED_ARRAY_BAD_BASE64_LAST_CHUNK_HANDLING);
2608 return false;
2612 * Uint8Array.fromBase64 ( string [ , options ] )
2614 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.frombase64
2616 static bool uint8array_fromBase64(JSContext* cx, unsigned argc, Value* vp) {
2617 CallArgs args = CallArgsFromVp(argc, vp);
2619 // Step 1.
2620 if (!args.get(0).isString()) {
2621 return ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK,
2622 args.get(0), nullptr, "not a string");
2624 Rooted<JSString*> string(cx, args[0].toString());
2626 // Steps 2-9.
2627 auto alphabet = Alphabet::Base64;
2628 auto lastChunkHandling = LastChunkHandling::Loose;
2629 if (args.hasDefined(1)) {
2630 // Step 2. (Inlined GetOptionsObject)
2631 Rooted<JSObject*> options(
2632 cx, RequireObjectArg(cx, "options", "fromBase64", args[1]));
2633 if (!options) {
2634 return false;
2637 // Steps 3-6.
2638 if (!GetAlphabetOption(cx, options, &alphabet)) {
2639 return false;
2642 // Steps 7-9.
2643 if (!GetLastChunkHandlingOption(cx, options, &lastChunkHandling)) {
2644 return false;
2648 // Step 10.
2649 constexpr size_t maxLength = std::numeric_limits<size_t>::max();
2650 ByteVector bytes(cx);
2651 size_t unusedReadLength;
2652 if (!FromBase64(cx, string, alphabet, lastChunkHandling, maxLength, bytes,
2653 &unusedReadLength)) {
2654 return false;
2657 // Step 11.
2658 size_t resultLength = bytes.length();
2660 // Step 12.
2661 auto* tarray =
2662 TypedArrayObjectTemplate<uint8_t>::fromLength(cx, resultLength);
2663 if (!tarray) {
2664 return false;
2667 // Step 13.
2668 auto target = SharedMem<uint8_t*>::unshared(tarray->dataPointerUnshared());
2669 auto source = SharedMem<uint8_t*>::unshared(bytes.begin());
2670 UnsharedOps::podCopy(target, source, resultLength);
2672 // Step 14.
2673 args.rval().setObject(*tarray);
2674 return true;
2678 * Uint8Array.fromHex ( string )
2680 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.fromhex
2682 static bool uint8array_fromHex(JSContext* cx, unsigned argc, Value* vp) {
2683 CallArgs args = CallArgsFromVp(argc, vp);
2685 // Step 1.
2686 if (!args.get(0).isString()) {
2687 return ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK,
2688 args.get(0), nullptr, "not a string");
2690 Rooted<JSString*> string(cx, args[0].toString());
2692 // Step 2.
2693 constexpr size_t maxLength = std::numeric_limits<size_t>::max();
2694 ByteVector bytes(cx);
2695 size_t unusedReadLength;
2696 if (!FromHex(cx, string, maxLength, bytes, &unusedReadLength)) {
2697 return false;
2700 // Step 3.
2701 size_t resultLength = bytes.length();
2703 // Step 4.
2704 auto* tarray =
2705 TypedArrayObjectTemplate<uint8_t>::fromLength(cx, resultLength);
2706 if (!tarray) {
2707 return false;
2710 // Step 5.
2711 auto target = SharedMem<uint8_t*>::unshared(tarray->dataPointerUnshared());
2712 auto source = SharedMem<uint8_t*>::unshared(bytes.begin());
2713 UnsharedOps::podCopy(target, source, resultLength);
2715 // Step 6.
2716 args.rval().setObject(*tarray);
2717 return true;
2721 * Uint8Array.prototype.setFromBase64 ( string [ , options ] )
2723 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.setfrombase64
2725 static bool uint8array_setFromBase64(JSContext* cx, const CallArgs& args) {
2726 Rooted<TypedArrayObject*> tarray(
2727 cx, &args.thisv().toObject().as<TypedArrayObject>());
2729 // Step 3.
2730 if (!args.get(0).isString()) {
2731 return ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK,
2732 args.get(0), nullptr, "not a string");
2734 Rooted<JSString*> string(cx, args[0].toString());
2736 // Steps 4-11.
2737 auto alphabet = Alphabet::Base64;
2738 auto lastChunkHandling = LastChunkHandling::Loose;
2739 if (args.hasDefined(1)) {
2740 // Step 2. (Inlined GetOptionsObject)
2741 Rooted<JSObject*> options(
2742 cx, RequireObjectArg(cx, "options", "setFromBase64", args[1]));
2743 if (!options) {
2744 return false;
2747 // Steps 3-6.
2748 if (!GetAlphabetOption(cx, options, &alphabet)) {
2749 return false;
2752 // Steps 7-9.
2753 if (!GetLastChunkHandlingOption(cx, options, &lastChunkHandling)) {
2754 return false;
2758 // Steps 12-14.
2759 auto length = tarray->length();
2760 if (!length) {
2761 ReportOutOfBounds(cx, tarray);
2762 return false;
2765 // Step 15.
2766 size_t maxLength = *length;
2768 // Steps 16-17.
2769 ByteVector bytes(cx);
2770 size_t readLength;
2771 if (!FromBase64(cx, string, alphabet, lastChunkHandling, maxLength, bytes,
2772 &readLength)) {
2773 return false;
2776 // Step 18.
2777 size_t written = bytes.length();
2779 // Step 19.
2781 // The underlying buffer has neither been detached nor shrunk. (It may have
2782 // been grown when it's a growable shared buffer and a concurrent thread
2783 // resized the buffer.)
2784 MOZ_ASSERT(!tarray->hasDetachedBuffer());
2785 MOZ_ASSERT(tarray->length().valueOr(0) >= *length);
2787 // Step 20.
2788 MOZ_ASSERT(written <= *length);
2790 // Step 21. (Inlined SetUint8ArrayBytes)
2791 auto target = tarray->dataPointerEither().cast<uint8_t*>();
2792 auto source = SharedMem<uint8_t*>::unshared(bytes.begin());
2793 if (tarray->isSharedMemory()) {
2794 SharedOps::podCopy(target, source, written);
2795 } else {
2796 UnsharedOps::podCopy(target, source, written);
2799 // Step 22.
2800 Rooted<PlainObject*> result(cx, NewPlainObject(cx));
2801 if (!result) {
2802 return false;
2805 // Step 23.
2806 Rooted<Value> readValue(cx, NumberValue(readLength));
2807 if (!DefineDataProperty(cx, result, cx->names().read, readValue)) {
2808 return false;
2811 // Step 24.
2812 Rooted<Value> writtenValue(cx, NumberValue(written));
2813 if (!DefineDataProperty(cx, result, cx->names().written, writtenValue)) {
2814 return false;
2817 // Step 25.
2818 args.rval().setObject(*result);
2819 return true;
2823 * Uint8Array.prototype.setFromBase64 ( string [ , options ] )
2825 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.setfrombase64
2827 static bool uint8array_setFromBase64(JSContext* cx, unsigned argc, Value* vp) {
2828 CallArgs args = CallArgsFromVp(argc, vp);
2830 // Steps 1-2.
2831 return CallNonGenericMethod<IsUint8ArrayObject, uint8array_setFromBase64>(
2832 cx, args);
2836 * Uint8Array.prototype.setFromHex ( string )
2838 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.setfromhex
2840 static bool uint8array_setFromHex(JSContext* cx, const CallArgs& args) {
2841 Rooted<TypedArrayObject*> tarray(
2842 cx, &args.thisv().toObject().as<TypedArrayObject>());
2844 // Step 3.
2845 if (!args.get(0).isString()) {
2846 return ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK,
2847 args.get(0), nullptr, "not a string");
2849 Rooted<JSString*> string(cx, args[0].toString());
2851 // Steps 4-6.
2852 auto length = tarray->length();
2853 if (!length) {
2854 ReportOutOfBounds(cx, tarray);
2855 return false;
2858 // Step 7.
2859 size_t maxLength = *length;
2861 // Steps 8-9.
2862 ByteVector bytes(cx);
2863 size_t readLength;
2864 if (!FromHex(cx, string, maxLength, bytes, &readLength)) {
2865 return false;
2868 // Step 10.
2869 size_t written = bytes.length();
2871 // Step 11.
2873 // The underlying buffer has neither been detached nor shrunk. (It may have
2874 // been grown when it's a growable shared buffer and a concurrent thread
2875 // resized the buffer.)
2876 MOZ_ASSERT(!tarray->hasDetachedBuffer());
2877 MOZ_ASSERT(tarray->length().valueOr(0) >= *length);
2879 // Step 12.
2880 MOZ_ASSERT(written <= *length);
2882 // Step 13. (Inlined SetUint8ArrayBytes)
2883 auto target = tarray->dataPointerEither().cast<uint8_t*>();
2884 auto source = SharedMem<uint8_t*>::unshared(bytes.begin());
2885 if (tarray->isSharedMemory()) {
2886 SharedOps::podCopy(target, source, written);
2887 } else {
2888 UnsharedOps::podCopy(target, source, written);
2891 // Step 14.
2892 Rooted<PlainObject*> result(cx, NewPlainObject(cx));
2893 if (!result) {
2894 return false;
2897 // Step 15.
2898 Rooted<Value> readValue(cx, NumberValue(readLength));
2899 if (!DefineDataProperty(cx, result, cx->names().read, readValue)) {
2900 return false;
2903 // Step 16.
2904 Rooted<Value> writtenValue(cx, NumberValue(written));
2905 if (!DefineDataProperty(cx, result, cx->names().written, writtenValue)) {
2906 return false;
2909 // Step 17.
2910 args.rval().setObject(*result);
2911 return true;
2915 * Uint8Array.prototype.setFromHex ( string )
2917 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.setfromhex
2919 static bool uint8array_setFromHex(JSContext* cx, unsigned argc, Value* vp) {
2920 CallArgs args = CallArgsFromVp(argc, vp);
2922 // Steps 1-2.
2923 return CallNonGenericMethod<IsUint8ArrayObject, uint8array_setFromHex>(cx,
2924 args);
2928 * Uint8Array.prototype.toBase64 ( [ options ] )
2930 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.tobase64
2932 static bool uint8array_toBase64(JSContext* cx, const CallArgs& args) {
2933 Rooted<TypedArrayObject*> tarray(
2934 cx, &args.thisv().toObject().as<TypedArrayObject>());
2936 // Steps 3-7.
2937 auto alphabet = Alphabet::Base64;
2938 if (args.hasDefined(0)) {
2939 // Step 3. (Inlined GetOptionsObject)
2940 Rooted<JSObject*> options(
2941 cx, RequireObjectArg(cx, "options", "toBase64", args[0]));
2942 if (!options) {
2943 return false;
2946 // Steps 4-7.
2947 if (!GetAlphabetOption(cx, options, &alphabet)) {
2948 return false;
2952 // Step 8. (Partial)
2953 auto length = tarray->length();
2954 if (!length) {
2955 ReportOutOfBounds(cx, tarray);
2956 return false;
2959 // Compute the output string length. Three input bytes are encoded as four
2960 // characters, so the output length is ⌈length × 4/3⌉.
2961 auto outLength = mozilla::CheckedInt<size_t>{*length};
2962 outLength += 2;
2963 outLength /= 3;
2964 outLength *= 4;
2965 if (!outLength.isValid() || outLength.value() > JSString::MAX_LENGTH) {
2966 ReportAllocationOverflow(cx);
2967 return false;
2970 JSStringBuilder sb(cx);
2971 if (!sb.reserve(outLength.value())) {
2972 return false;
2975 // Steps 9-10.
2976 const auto& base64Chars = alphabet == Alphabet::Base64
2977 ? Base64::Encode::Base64
2978 : Base64::Encode::Base64Url;
2980 auto encode = [&base64Chars](uint32_t value) {
2981 return base64Chars[value & 0x3f];
2984 // Our implementation directly converts the bytes to their string
2985 // representation instead of first collecting them into an intermediate list.
2986 auto data = tarray->dataPointerEither().cast<uint8_t*>();
2987 auto toRead = *length;
2988 for (; toRead >= 3; toRead -= 3) {
2989 // Combine three input bytes into a single uint24 value.
2990 auto byte0 = jit::AtomicOperations::loadSafeWhenRacy(data++);
2991 auto byte1 = jit::AtomicOperations::loadSafeWhenRacy(data++);
2992 auto byte2 = jit::AtomicOperations::loadSafeWhenRacy(data++);
2993 auto u24 = (uint32_t(byte0) << 16) | (uint32_t(byte1) << 8) | byte2;
2995 // Encode the uint24 value as base64.
2996 sb.infallibleAppend(encode(u24 >> 18));
2997 sb.infallibleAppend(encode(u24 >> 12));
2998 sb.infallibleAppend(encode(u24 >> 6));
2999 sb.infallibleAppend(encode(u24 >> 0));
3002 // Trailing two and one element bytes are padded with '='.
3003 if (toRead == 2) {
3004 // Combine two input bytes into a single uint24 value.
3005 auto byte0 = jit::AtomicOperations::loadSafeWhenRacy(data++);
3006 auto byte1 = jit::AtomicOperations::loadSafeWhenRacy(data++);
3007 auto u24 = (uint32_t(byte0) << 16) | (uint32_t(byte1) << 8);
3009 // Encode the uint24 value as base64, including padding.
3010 sb.infallibleAppend(encode(u24 >> 18));
3011 sb.infallibleAppend(encode(u24 >> 12));
3012 sb.infallibleAppend(encode(u24 >> 6));
3013 sb.infallibleAppend('=');
3014 } else if (toRead == 1) {
3015 // Combine one input byte into a single uint24 value.
3016 auto byte0 = jit::AtomicOperations::loadSafeWhenRacy(data++);
3017 auto u24 = uint32_t(byte0) << 16;
3019 // Encode the uint24 value as base64, including padding.
3020 sb.infallibleAppend(encode(u24 >> 18));
3021 sb.infallibleAppend(encode(u24 >> 12));
3022 sb.infallibleAppend('=');
3023 sb.infallibleAppend('=');
3024 } else {
3025 MOZ_ASSERT(toRead == 0);
3028 MOZ_ASSERT(sb.length() == outLength.value(), "all characters were written");
3030 // Step 11.
3031 auto* str = sb.finishString();
3032 if (!str) {
3033 return false;
3036 args.rval().setString(str);
3037 return true;
3041 * Uint8Array.prototype.toBase64 ( [ options ] )
3043 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.tobase64
3045 static bool uint8array_toBase64(JSContext* cx, unsigned argc, Value* vp) {
3046 CallArgs args = CallArgsFromVp(argc, vp);
3048 // Steps 1-2.
3049 return CallNonGenericMethod<IsUint8ArrayObject, uint8array_toBase64>(cx,
3050 args);
3054 * Uint8Array.prototype.toHex ( )
3056 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.tohex
3058 static bool uint8array_toHex(JSContext* cx, const CallArgs& args) {
3059 Rooted<TypedArrayObject*> tarray(
3060 cx, &args.thisv().toObject().as<TypedArrayObject>());
3062 // Step 3. (Partial)
3063 auto length = tarray->length();
3064 if (!length) {
3065 ReportOutOfBounds(cx, tarray);
3066 return false;
3069 // |length| is limited by |ByteLengthLimit|, which ensures that multiplying it
3070 // by two won't overflow.
3071 static_assert(TypedArrayObject::ByteLengthLimit <=
3072 std::numeric_limits<size_t>::max() / 2);
3073 MOZ_ASSERT(*length <= TypedArrayObject::ByteLengthLimit);
3075 // Compute the output string length. Each byte is encoded as two characters,
3076 // so the output length is exactly twice as large as |length|.
3077 size_t outLength = *length * 2;
3078 if (outLength > JSString::MAX_LENGTH) {
3079 ReportAllocationOverflow(cx);
3080 return false;
3083 // Step 4.
3084 JSStringBuilder sb(cx);
3085 if (!sb.reserve(outLength)) {
3086 return false;
3089 // NB: Lower case hex digits.
3090 static constexpr char HexDigits[] = "0123456789abcdef";
3091 static_assert(std::char_traits<char>::length(HexDigits) == 16);
3093 // Steps 3 and 5.
3095 // Our implementation directly converts the bytes to their string
3096 // representation instead of first collecting them into an intermediate list.
3097 auto data = tarray->dataPointerEither().cast<uint8_t*>();
3098 for (size_t index = 0; index < *length; index++) {
3099 auto byte = jit::AtomicOperations::loadSafeWhenRacy(data + index);
3101 sb.infallibleAppend(HexDigits[byte >> 4]);
3102 sb.infallibleAppend(HexDigits[byte & 0xf]);
3105 MOZ_ASSERT(sb.length() == outLength, "all characters were written");
3107 // Step 6.
3108 auto* str = sb.finishString();
3109 if (!str) {
3110 return false;
3113 args.rval().setString(str);
3114 return true;
3118 * Uint8Array.prototype.toHex ( )
3120 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.tohex
3122 static bool uint8array_toHex(JSContext* cx, unsigned argc, Value* vp) {
3123 CallArgs args = CallArgsFromVp(argc, vp);
3125 // Steps 1-2.
3126 return CallNonGenericMethod<IsUint8ArrayObject, uint8array_toHex>(cx, args);
3129 /* static */ const JSFunctionSpec TypedArrayObject::protoFunctions[] = {
3130 JS_SELF_HOSTED_FN("subarray", "TypedArraySubarray", 2, 0),
3131 JS_FN("set", TypedArrayObject::set, 1, 0),
3132 JS_FN("copyWithin", TypedArrayObject::copyWithin, 2, 0),
3133 JS_SELF_HOSTED_FN("every", "TypedArrayEvery", 1, 0),
3134 JS_SELF_HOSTED_FN("fill", "TypedArrayFill", 3, 0),
3135 JS_SELF_HOSTED_FN("filter", "TypedArrayFilter", 1, 0),
3136 JS_SELF_HOSTED_FN("find", "TypedArrayFind", 1, 0),
3137 JS_SELF_HOSTED_FN("findIndex", "TypedArrayFindIndex", 1, 0),
3138 JS_SELF_HOSTED_FN("findLast", "TypedArrayFindLast", 1, 0),
3139 JS_SELF_HOSTED_FN("findLastIndex", "TypedArrayFindLastIndex", 1, 0),
3140 JS_SELF_HOSTED_FN("forEach", "TypedArrayForEach", 1, 0),
3141 JS_SELF_HOSTED_FN("indexOf", "TypedArrayIndexOf", 2, 0),
3142 JS_SELF_HOSTED_FN("join", "TypedArrayJoin", 1, 0),
3143 JS_SELF_HOSTED_FN("lastIndexOf", "TypedArrayLastIndexOf", 1, 0),
3144 JS_SELF_HOSTED_FN("map", "TypedArrayMap", 1, 0),
3145 JS_SELF_HOSTED_FN("reduce", "TypedArrayReduce", 1, 0),
3146 JS_SELF_HOSTED_FN("reduceRight", "TypedArrayReduceRight", 1, 0),
3147 JS_SELF_HOSTED_FN("reverse", "TypedArrayReverse", 0, 0),
3148 JS_SELF_HOSTED_FN("slice", "TypedArraySlice", 2, 0),
3149 JS_SELF_HOSTED_FN("some", "TypedArraySome", 1, 0),
3150 JS_SELF_HOSTED_FN("sort", "TypedArraySort", 1, 0),
3151 JS_SELF_HOSTED_FN("entries", "TypedArrayEntries", 0, 0),
3152 JS_SELF_HOSTED_FN("keys", "TypedArrayKeys", 0, 0),
3153 JS_SELF_HOSTED_FN("values", "$TypedArrayValues", 0, 0),
3154 JS_SELF_HOSTED_SYM_FN(iterator, "$TypedArrayValues", 0, 0),
3155 JS_SELF_HOSTED_FN("includes", "TypedArrayIncludes", 2, 0),
3156 JS_SELF_HOSTED_FN("toString", "ArrayToString", 0, 0),
3157 JS_SELF_HOSTED_FN("toLocaleString", "TypedArrayToLocaleString", 2, 0),
3158 JS_SELF_HOSTED_FN("at", "TypedArrayAt", 1, 0),
3159 JS_SELF_HOSTED_FN("toReversed", "TypedArrayToReversed", 0, 0),
3160 JS_SELF_HOSTED_FN("toSorted", "TypedArrayToSorted", 1, 0),
3161 JS_SELF_HOSTED_FN("with", "TypedArrayWith", 2, 0),
3162 JS_FS_END,
3165 /* static */ const JSFunctionSpec TypedArrayObject::staticFunctions[] = {
3166 JS_SELF_HOSTED_FN("from", "TypedArrayStaticFrom", 3, 0),
3167 JS_SELF_HOSTED_FN("of", "TypedArrayStaticOf", 0, 0),
3168 JS_FS_END,
3171 /* static */ const JSPropertySpec TypedArrayObject::staticProperties[] = {
3172 JS_SELF_HOSTED_SYM_GET(species, "$TypedArraySpecies", 0),
3173 JS_PS_END,
3176 static JSObject* CreateSharedTypedArrayPrototype(JSContext* cx,
3177 JSProtoKey key) {
3178 return GlobalObject::createBlankPrototype(
3179 cx, cx->global(), &TypedArrayObject::sharedTypedArrayPrototypeClass);
3182 static const ClassSpec TypedArrayObjectSharedTypedArrayPrototypeClassSpec = {
3183 GenericCreateConstructor<TypedArrayConstructor, 0, gc::AllocKind::FUNCTION>,
3184 CreateSharedTypedArrayPrototype,
3185 TypedArrayObject::staticFunctions,
3186 TypedArrayObject::staticProperties,
3187 TypedArrayObject::protoFunctions,
3188 TypedArrayObject::protoAccessors,
3189 nullptr,
3190 ClassSpec::DontDefineConstructor,
3193 /* static */ const JSClass TypedArrayObject::sharedTypedArrayPrototypeClass = {
3194 "TypedArrayPrototype",
3195 JSCLASS_HAS_CACHED_PROTO(JSProto_TypedArray),
3196 JS_NULL_CLASS_OPS,
3197 &TypedArrayObjectSharedTypedArrayPrototypeClassSpec,
3200 namespace {
3202 // This default implementation is only valid for integer types less
3203 // than 32-bits in size.
3204 template <typename NativeType>
3205 bool TypedArrayObjectTemplate<NativeType>::getElementPure(
3206 TypedArrayObject* tarray, size_t index, Value* vp) {
3207 static_assert(sizeof(NativeType) < 4,
3208 "this method must only handle NativeType values that are "
3209 "always exact int32_t values");
3211 *vp = Int32Value(getIndex(tarray, index));
3212 return true;
3215 // We need to specialize for floats and other integer types.
3216 template <>
3217 bool TypedArrayObjectTemplate<int32_t>::getElementPure(TypedArrayObject* tarray,
3218 size_t index,
3219 Value* vp) {
3220 *vp = Int32Value(getIndex(tarray, index));
3221 return true;
3224 template <>
3225 bool TypedArrayObjectTemplate<uint32_t>::getElementPure(
3226 TypedArrayObject* tarray, size_t index, Value* vp) {
3227 uint32_t val = getIndex(tarray, index);
3228 *vp = NumberValue(val);
3229 return true;
3232 template <>
3233 bool TypedArrayObjectTemplate<float>::getElementPure(TypedArrayObject* tarray,
3234 size_t index, Value* vp) {
3235 float val = getIndex(tarray, index);
3236 double dval = val;
3239 * Doubles in typed arrays could be typed-punned arrays of integers. This
3240 * could allow user code to break the engine-wide invariant that only
3241 * canonical nans are stored into jsvals, which means user code could
3242 * confuse the engine into interpreting a double-typed jsval as an
3243 * object-typed jsval.
3245 * This could be removed for platforms/compilers known to convert a 32-bit
3246 * non-canonical nan to a 64-bit canonical nan.
3248 *vp = JS::CanonicalizedDoubleValue(dval);
3249 return true;
3252 template <>
3253 bool TypedArrayObjectTemplate<double>::getElementPure(TypedArrayObject* tarray,
3254 size_t index, Value* vp) {
3255 double val = getIndex(tarray, index);
3258 * Doubles in typed arrays could be typed-punned arrays of integers. This
3259 * could allow user code to break the engine-wide invariant that only
3260 * canonical nans are stored into jsvals, which means user code could
3261 * confuse the engine into interpreting a double-typed jsval as an
3262 * object-typed jsval.
3264 *vp = JS::CanonicalizedDoubleValue(val);
3265 return true;
3268 template <>
3269 bool TypedArrayObjectTemplate<int64_t>::getElementPure(TypedArrayObject* tarray,
3270 size_t index,
3271 Value* vp) {
3272 return false;
3275 template <>
3276 bool TypedArrayObjectTemplate<uint64_t>::getElementPure(
3277 TypedArrayObject* tarray, size_t index, Value* vp) {
3278 return false;
3280 } /* anonymous namespace */
3282 namespace {
3284 template <typename NativeType>
3285 bool TypedArrayObjectTemplate<NativeType>::getElement(JSContext* cx,
3286 TypedArrayObject* tarray,
3287 size_t index,
3288 MutableHandleValue val) {
3289 MOZ_ALWAYS_TRUE(getElementPure(tarray, index, val.address()));
3290 return true;
3293 template <>
3294 bool TypedArrayObjectTemplate<int64_t>::getElement(JSContext* cx,
3295 TypedArrayObject* tarray,
3296 size_t index,
3297 MutableHandleValue val) {
3298 int64_t n = getIndex(tarray, index);
3299 BigInt* res = BigInt::createFromInt64(cx, n);
3300 if (!res) {
3301 return false;
3303 val.setBigInt(res);
3304 return true;
3307 template <>
3308 bool TypedArrayObjectTemplate<uint64_t>::getElement(JSContext* cx,
3309 TypedArrayObject* tarray,
3310 size_t index,
3311 MutableHandleValue val) {
3312 uint64_t n = getIndex(tarray, index);
3313 BigInt* res = BigInt::createFromUint64(cx, n);
3314 if (!res) {
3315 return false;
3317 val.setBigInt(res);
3318 return true;
3320 } /* anonymous namespace */
3322 namespace js {
3324 template <>
3325 bool TypedArrayObject::getElement<CanGC>(JSContext* cx, size_t index,
3326 MutableHandleValue val) {
3327 switch (type()) {
3328 #define GET_ELEMENT(_, T, N) \
3329 case Scalar::N: \
3330 return TypedArrayObjectTemplate<T>::getElement(cx, this, index, val);
3331 JS_FOR_EACH_TYPED_ARRAY(GET_ELEMENT)
3332 #undef GET_ELEMENT
3333 case Scalar::MaxTypedArrayViewType:
3334 case Scalar::Int64:
3335 case Scalar::Simd128:
3336 break;
3339 MOZ_CRASH("Unknown TypedArray type");
3342 template <>
3343 bool TypedArrayObject::getElement<NoGC>(
3344 JSContext* cx, size_t index,
3345 typename MaybeRooted<Value, NoGC>::MutableHandleType vp) {
3346 return getElementPure(index, vp.address());
3349 } // namespace js
3351 bool TypedArrayObject::getElementPure(size_t index, Value* vp) {
3352 switch (type()) {
3353 #define GET_ELEMENT_PURE(_, T, N) \
3354 case Scalar::N: \
3355 return TypedArrayObjectTemplate<T>::getElementPure(this, index, vp);
3356 JS_FOR_EACH_TYPED_ARRAY(GET_ELEMENT_PURE)
3357 #undef GET_ELEMENT
3358 case Scalar::MaxTypedArrayViewType:
3359 case Scalar::Int64:
3360 case Scalar::Simd128:
3361 break;
3364 MOZ_CRASH("Unknown TypedArray type");
3367 /* static */
3368 bool TypedArrayObject::getElements(JSContext* cx,
3369 Handle<TypedArrayObject*> tarray,
3370 Value* vp) {
3371 size_t length = tarray->length().valueOr(0);
3372 MOZ_ASSERT_IF(length > 0, !tarray->hasDetachedBuffer());
3374 switch (tarray->type()) {
3375 #define GET_ELEMENTS(_, T, N) \
3376 case Scalar::N: \
3377 for (size_t i = 0; i < length; ++i, ++vp) { \
3378 if (!TypedArrayObjectTemplate<T>::getElement( \
3379 cx, tarray, i, MutableHandleValue::fromMarkedLocation(vp))) { \
3380 return false; \
3383 return true;
3384 JS_FOR_EACH_TYPED_ARRAY(GET_ELEMENTS)
3385 #undef GET_ELEMENTS
3386 case Scalar::MaxTypedArrayViewType:
3387 case Scalar::Int64:
3388 case Scalar::Simd128:
3389 break;
3392 MOZ_CRASH("Unknown TypedArray type");
3395 /***
3396 *** JS impl
3397 ***/
3400 * TypedArrayObject boilerplate
3403 static const JSClassOps TypedArrayClassOps = {
3404 nullptr, // addProperty
3405 nullptr, // delProperty
3406 nullptr, // enumerate
3407 nullptr, // newEnumerate
3408 nullptr, // resolve
3409 nullptr, // mayResolve
3410 FixedLengthTypedArrayObject::finalize, // finalize
3411 nullptr, // call
3412 nullptr, // construct
3413 ArrayBufferViewObject::trace, // trace
3416 static const JSClassOps ResizableTypedArrayClassOps = {
3417 nullptr, // addProperty
3418 nullptr, // delProperty
3419 nullptr, // enumerate
3420 nullptr, // newEnumerate
3421 nullptr, // resolve
3422 nullptr, // mayResolve
3423 nullptr, // finalize
3424 nullptr, // call
3425 nullptr, // construct
3426 ArrayBufferViewObject::trace, // trace
3429 static const ClassExtension TypedArrayClassExtension = {
3430 FixedLengthTypedArrayObject::objectMoved, // objectMovedOp
3433 static const JSPropertySpec
3434 static_prototype_properties[Scalar::MaxTypedArrayViewType][2] = {
3435 #define IMPL_TYPED_ARRAY_PROPERTIES(ExternalType, NativeType, Name) \
3437 JS_INT32_PS("BYTES_PER_ELEMENT", \
3438 TypedArrayObjectTemplate<NativeType>::BYTES_PER_ELEMENT, \
3439 JSPROP_READONLY | JSPROP_PERMANENT), \
3440 JS_PS_END, \
3443 JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_PROPERTIES)
3444 #undef IMPL_TYPED_ARRAY_PROPERTIES
3447 static const JSFunctionSpec uint8array_static_methods[] = {
3448 JS_FN("fromBase64", uint8array_fromBase64, 1, 0),
3449 JS_FN("fromHex", uint8array_fromHex, 1, 0),
3450 JS_FS_END,
3453 static const JSFunctionSpec uint8array_methods[] = {
3454 JS_FN("setFromBase64", uint8array_setFromBase64, 1, 0),
3455 JS_FN("setFromHex", uint8array_setFromHex, 1, 0),
3456 JS_FN("toBase64", uint8array_toBase64, 0, 0),
3457 JS_FN("toHex", uint8array_toHex, 0, 0),
3458 JS_FS_END,
3461 static constexpr const JSFunctionSpec* TypedArrayStaticMethods(
3462 Scalar::Type type) {
3463 if (type == Scalar::Uint8) {
3464 return uint8array_static_methods;
3466 return nullptr;
3469 static constexpr const JSFunctionSpec* TypedArrayMethods(Scalar::Type type) {
3470 if (type == Scalar::Uint8) {
3471 return uint8array_methods;
3473 return nullptr;
3476 static const ClassSpec
3477 TypedArrayObjectClassSpecs[Scalar::MaxTypedArrayViewType] = {
3478 #define IMPL_TYPED_ARRAY_CLASS_SPEC(ExternalType, NativeType, Name) \
3480 TypedArrayObjectTemplate<NativeType>::createConstructor, \
3481 TypedArrayObjectTemplate<NativeType>::createPrototype, \
3482 TypedArrayStaticMethods(Scalar::Type::Name), \
3483 static_prototype_properties[Scalar::Type::Name], \
3484 TypedArrayMethods(Scalar::Type::Name), \
3485 static_prototype_properties[Scalar::Type::Name], \
3486 nullptr, \
3487 JSProto_TypedArray, \
3490 JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_CLASS_SPEC)
3491 #undef IMPL_TYPED_ARRAY_CLASS_SPEC
3494 // Class definitions for fixed length and resizable typed arrays. Stored into a
3495 // 2-dimensional array to ensure the classes are in contiguous memory.
3496 const JSClass TypedArrayObject::anyClasses[2][Scalar::MaxTypedArrayViewType] = {
3497 // Class definitions for fixed length typed arrays.
3499 #define IMPL_TYPED_ARRAY_CLASS(ExternalType, NativeType, Name) \
3501 #Name "Array", \
3502 JSCLASS_HAS_RESERVED_SLOTS(TypedArrayObject::RESERVED_SLOTS) | \
3503 JSCLASS_HAS_CACHED_PROTO(JSProto_##Name##Array) | \
3504 JSCLASS_DELAY_METADATA_BUILDER | JSCLASS_SKIP_NURSERY_FINALIZE | \
3505 JSCLASS_BACKGROUND_FINALIZE, \
3506 &TypedArrayClassOps, \
3507 &TypedArrayObjectClassSpecs[Scalar::Type::Name], \
3508 &TypedArrayClassExtension, \
3511 JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_CLASS)
3512 #undef IMPL_TYPED_ARRAY_CLASS
3515 // Class definitions for resizable typed arrays.
3517 #define IMPL_TYPED_ARRAY_CLASS(ExternalType, NativeType, Name) \
3519 #Name "Array", \
3520 JSCLASS_HAS_RESERVED_SLOTS(ResizableTypedArrayObject::RESERVED_SLOTS) | \
3521 JSCLASS_HAS_CACHED_PROTO(JSProto_##Name##Array) | \
3522 JSCLASS_DELAY_METADATA_BUILDER, \
3523 &ResizableTypedArrayClassOps, \
3524 &TypedArrayObjectClassSpecs[Scalar::Type::Name], \
3525 JS_NULL_CLASS_EXT, \
3528 JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_CLASS)
3529 #undef IMPL_TYPED_ARRAY_CLASS
3533 const JSClass (
3534 &TypedArrayObject::fixedLengthClasses)[Scalar::MaxTypedArrayViewType] =
3535 TypedArrayObject::anyClasses[0];
3537 const JSClass (
3538 &TypedArrayObject::resizableClasses)[Scalar::MaxTypedArrayViewType] =
3539 TypedArrayObject::anyClasses[1];
3541 // The various typed array prototypes are supposed to 1) be normal objects,
3542 // 2) stringify to "[object <name of constructor>]", and 3) (Gecko-specific)
3543 // be xrayable. The first and second requirements mandate (in the absence of
3544 // @@toStringTag) a custom class. The third requirement mandates that each
3545 // prototype's class have the relevant typed array's cached JSProtoKey in them.
3546 // Thus we need one class with cached prototype per kind of typed array, with a
3547 // delegated ClassSpec.
3549 // Actually ({}).toString.call(Uint8Array.prototype) should throw, because
3550 // Uint8Array.prototype lacks the the typed array internal slots. (Same as
3551 // with %TypedArray%.prototype.) It's not clear this is desirable (see
3552 // above), but it's what we've always done, so keep doing it till we
3553 // implement @@toStringTag or ES6 changes.
3554 const JSClass TypedArrayObject::protoClasses[Scalar::MaxTypedArrayViewType] = {
3555 #define IMPL_TYPED_ARRAY_PROTO_CLASS(ExternalType, NativeType, Name) \
3557 #Name "Array.prototype", \
3558 JSCLASS_HAS_CACHED_PROTO(JSProto_##Name##Array), \
3559 JS_NULL_CLASS_OPS, \
3560 &TypedArrayObjectClassSpecs[Scalar::Type::Name], \
3563 JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_PROTO_CLASS)
3564 #undef IMPL_TYPED_ARRAY_PROTO_CLASS
3567 /* static */
3568 bool TypedArrayObject::isOriginalLengthGetter(Native native) {
3569 return native == TypedArray_lengthGetter;
3572 /* static */
3573 bool TypedArrayObject::isOriginalByteOffsetGetter(Native native) {
3574 return native == TypedArray_byteOffsetGetter;
3577 /* static */
3578 bool TypedArrayObject::isOriginalByteLengthGetter(Native native) {
3579 return native == TypedArray_byteLengthGetter;
3582 bool js::IsTypedArrayConstructor(const JSObject* obj) {
3583 #define CHECK_TYPED_ARRAY_CONSTRUCTOR(_, T, N) \
3584 if (IsNativeFunction(obj, TypedArrayObjectTemplate<T>::class_constructor)) { \
3585 return true; \
3587 JS_FOR_EACH_TYPED_ARRAY(CHECK_TYPED_ARRAY_CONSTRUCTOR)
3588 #undef CHECK_TYPED_ARRAY_CONSTRUCTOR
3589 return false;
3592 bool js::IsTypedArrayConstructor(HandleValue v, Scalar::Type type) {
3593 return IsNativeFunction(v, TypedArrayConstructorNative(type));
3596 JSNative js::TypedArrayConstructorNative(Scalar::Type type) {
3597 #define TYPED_ARRAY_CONSTRUCTOR_NATIVE(_, T, N) \
3598 if (type == Scalar::N) { \
3599 return TypedArrayObjectTemplate<T>::class_constructor; \
3601 JS_FOR_EACH_TYPED_ARRAY(TYPED_ARRAY_CONSTRUCTOR_NATIVE)
3602 #undef TYPED_ARRAY_CONSTRUCTOR_NATIVE
3604 MOZ_CRASH("unexpected typed array type");
3607 bool js::IsBufferSource(JSObject* object, SharedMem<uint8_t*>* dataPointer,
3608 size_t* byteLength) {
3609 if (object->is<TypedArrayObject>()) {
3610 TypedArrayObject& view = object->as<TypedArrayObject>();
3611 *dataPointer = view.dataPointerEither().cast<uint8_t*>();
3612 *byteLength = view.byteLength().valueOr(0);
3613 return true;
3616 if (object->is<DataViewObject>()) {
3617 DataViewObject& view = object->as<DataViewObject>();
3618 *dataPointer = view.dataPointerEither().cast<uint8_t*>();
3619 *byteLength = view.byteLength().valueOr(0);
3620 return true;
3623 if (object->is<ArrayBufferObject>()) {
3624 ArrayBufferObject& buffer = object->as<ArrayBufferObject>();
3625 *dataPointer = buffer.dataPointerShared();
3626 *byteLength = buffer.byteLength();
3627 return true;
3630 if (object->is<SharedArrayBufferObject>()) {
3631 SharedArrayBufferObject& buffer = object->as<SharedArrayBufferObject>();
3632 *dataPointer = buffer.dataPointerShared();
3633 *byteLength = buffer.byteLength();
3634 return true;
3637 return false;
3640 template <typename CharT>
3641 static inline bool StringIsInfinity(mozilla::Range<const CharT> s) {
3642 static constexpr std::string_view Infinity = "Infinity";
3644 // Compilers optimize this to one |cmp| instruction on x64 resp. two for x86,
3645 // when the input is a Latin-1 string, because the string "Infinity" is
3646 // exactly eight characters long, so it can be represented as a single uint64
3647 // value.
3648 return s.length() == Infinity.length() &&
3649 EqualChars(s.begin().get(), Infinity.data(), Infinity.length());
3652 template <typename CharT>
3653 static inline bool StringIsNaN(mozilla::Range<const CharT> s) {
3654 static constexpr std::string_view NaN = "NaN";
3656 // "NaN" is not as nicely optimizable as "Infinity", but oh well.
3657 return s.length() == NaN.length() &&
3658 EqualChars(s.begin().get(), NaN.data(), NaN.length());
3661 template <typename CharT>
3662 static mozilla::Maybe<uint64_t> StringToTypedArrayIndexSlow(
3663 mozilla::Range<const CharT> s) {
3664 const mozilla::RangedPtr<const CharT> start = s.begin();
3665 const mozilla::RangedPtr<const CharT> end = s.end();
3667 const CharT* actualEnd;
3668 double result = js_strtod(start.get(), end.get(), &actualEnd);
3670 // The complete string must have been parsed.
3671 if (actualEnd != end.get()) {
3672 return mozilla::Nothing();
3675 // Now convert it back to a string.
3676 ToCStringBuf cbuf;
3677 size_t cstrlen;
3678 const char* cstr = js::NumberToCString(&cbuf, result, &cstrlen);
3679 MOZ_ASSERT(cstr);
3681 // Both strings must be equal for a canonical numeric index string.
3682 if (s.length() != cstrlen || !EqualChars(start.get(), cstr, cstrlen)) {
3683 return mozilla::Nothing();
3686 // Directly perform IsInteger() check and encode negative and non-integer
3687 // indices as OOB.
3688 // See 9.4.5.2 [[HasProperty]], steps 3.b.iii and 3.b.v.
3689 // See 9.4.5.3 [[DefineOwnProperty]], steps 3.b.i and 3.b.iii.
3690 // See 9.4.5.8 IntegerIndexedElementGet, steps 5 and 8.
3691 // See 9.4.5.9 IntegerIndexedElementSet, steps 6 and 9.
3692 if (result < 0 || !IsInteger(result)) {
3693 return mozilla::Some(UINT64_MAX);
3696 // Anything equals-or-larger than 2^53 is definitely OOB, encode it
3697 // accordingly so that the cast to uint64_t is well defined.
3698 if (result >= DOUBLE_INTEGRAL_PRECISION_LIMIT) {
3699 return mozilla::Some(UINT64_MAX);
3702 // The string is an actual canonical numeric index.
3703 return mozilla::Some(result);
3706 template <typename CharT>
3707 mozilla::Maybe<uint64_t> js::StringToTypedArrayIndex(
3708 mozilla::Range<const CharT> s) {
3709 mozilla::RangedPtr<const CharT> cp = s.begin();
3710 const mozilla::RangedPtr<const CharT> end = s.end();
3712 MOZ_ASSERT(cp < end, "caller must check for empty strings");
3714 bool negative = false;
3715 if (*cp == '-') {
3716 negative = true;
3717 if (++cp == end) {
3718 return mozilla::Nothing();
3722 if (!IsAsciiDigit(*cp)) {
3723 // Check for "NaN", "Infinity", or "-Infinity".
3724 if ((!negative && StringIsNaN<CharT>({cp, end})) ||
3725 StringIsInfinity<CharT>({cp, end})) {
3726 return mozilla::Some(UINT64_MAX);
3728 return mozilla::Nothing();
3731 uint32_t digit = AsciiDigitToNumber(*cp++);
3733 // Don't allow leading zeros.
3734 if (digit == 0 && cp != end) {
3735 // The string may be of the form "0.xyz". The exponent form isn't possible
3736 // when the string starts with "0".
3737 if (*cp == '.') {
3738 return StringToTypedArrayIndexSlow(s);
3740 return mozilla::Nothing();
3743 uint64_t index = digit;
3745 for (; cp < end; cp++) {
3746 if (!IsAsciiDigit(*cp)) {
3747 // Take the slow path when the string has fractional parts or an exponent.
3748 if (*cp == '.' || *cp == 'e') {
3749 return StringToTypedArrayIndexSlow(s);
3751 return mozilla::Nothing();
3754 digit = AsciiDigitToNumber(*cp);
3756 static_assert(
3757 uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT) < (UINT64_MAX - 10) / 10,
3758 "2^53 is way below UINT64_MAX, so |10 * index + digit| can't overflow");
3760 index = 10 * index + digit;
3762 // Also take the slow path when the string is larger-or-equals 2^53.
3763 if (index >= uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT)) {
3764 return StringToTypedArrayIndexSlow(s);
3768 if (negative) {
3769 return mozilla::Some(UINT64_MAX);
3771 return mozilla::Some(index);
3774 template mozilla::Maybe<uint64_t> js::StringToTypedArrayIndex(
3775 mozilla::Range<const char16_t> s);
3777 template mozilla::Maybe<uint64_t> js::StringToTypedArrayIndex(
3778 mozilla::Range<const Latin1Char> s);
3780 bool js::SetTypedArrayElement(JSContext* cx, Handle<TypedArrayObject*> obj,
3781 uint64_t index, HandleValue v,
3782 ObjectOpResult& result) {
3783 switch (obj->type()) {
3784 #define SET_TYPED_ARRAY_ELEMENT(_, T, N) \
3785 case Scalar::N: \
3786 return TypedArrayObjectTemplate<T>::setElement(cx, obj, index, v, result);
3787 JS_FOR_EACH_TYPED_ARRAY(SET_TYPED_ARRAY_ELEMENT)
3788 #undef SET_TYPED_ARRAY_ELEMENT
3789 case Scalar::MaxTypedArrayViewType:
3790 case Scalar::Int64:
3791 case Scalar::Simd128:
3792 break;
3795 MOZ_CRASH("Unsupported TypedArray type");
3798 bool js::SetTypedArrayElementOutOfBounds(JSContext* cx,
3799 Handle<TypedArrayObject*> obj,
3800 uint64_t index, HandleValue v,
3801 ObjectOpResult& result) {
3802 // This method is only called for non-existent properties, which means any
3803 // absent indexed properties must be out of range. Unless the typed array is
3804 // backed by a growable SharedArrayBuffer, in which case another thread may
3805 // have grown the buffer.
3806 MOZ_ASSERT(index >= obj->length().valueOr(0) ||
3807 (obj->isSharedMemory() && obj->bufferShared()->isGrowable()));
3809 // The following steps refer to 10.4.5.16 TypedArraySetElement.
3811 // Steps 1-2.
3812 RootedValue converted(cx);
3813 if (!obj->convertValue(cx, v, &converted)) {
3814 return false;
3817 // Step 3.
3818 if (index < obj->length().valueOr(0)) {
3819 // Side-effects when converting the value may have put the index in-bounds
3820 // when the backing buffer is resizable.
3821 MOZ_ASSERT(obj->hasResizableBuffer());
3822 return SetTypedArrayElement(cx, obj, index, converted, result);
3825 // Step 4.
3826 return result.succeed();
3829 // ES2021 draft rev b3f9b5089bcc3ddd8486379015cd11eb1427a5eb
3830 // 9.4.5.3 [[DefineOwnProperty]], step 3.b.
3831 bool js::DefineTypedArrayElement(JSContext* cx, Handle<TypedArrayObject*> obj,
3832 uint64_t index,
3833 Handle<PropertyDescriptor> desc,
3834 ObjectOpResult& result) {
3835 // These are all substeps of 3.b.
3837 // Step i.
3838 if (index >= obj->length().valueOr(0)) {
3839 if (obj->hasDetachedBuffer()) {
3840 return result.fail(JSMSG_TYPED_ARRAY_DETACHED);
3842 return result.fail(JSMSG_DEFINE_BAD_INDEX);
3845 // Step ii.
3846 if (desc.isAccessorDescriptor()) {
3847 return result.fail(JSMSG_CANT_REDEFINE_PROP);
3850 // Step iii.
3851 if (desc.hasConfigurable() && !desc.configurable()) {
3852 return result.fail(JSMSG_CANT_REDEFINE_PROP);
3855 // Step iv.
3856 if (desc.hasEnumerable() && !desc.enumerable()) {
3857 return result.fail(JSMSG_CANT_REDEFINE_PROP);
3860 // Step v.
3861 if (desc.hasWritable() && !desc.writable()) {
3862 return result.fail(JSMSG_CANT_REDEFINE_PROP);
3865 // Step vi.
3866 if (desc.hasValue()) {
3867 switch (obj->type()) {
3868 #define DEFINE_TYPED_ARRAY_ELEMENT(_, T, N) \
3869 case Scalar::N: \
3870 return TypedArrayObjectTemplate<T>::setElement(cx, obj, index, \
3871 desc.value(), result);
3872 JS_FOR_EACH_TYPED_ARRAY(DEFINE_TYPED_ARRAY_ELEMENT)
3873 #undef DEFINE_TYPED_ARRAY_ELEMENT
3874 case Scalar::MaxTypedArrayViewType:
3875 case Scalar::Int64:
3876 case Scalar::Simd128:
3877 break;
3880 MOZ_CRASH("Unsupported TypedArray type");
3883 // Step vii.
3884 return result.succeed();
3887 template <typename T, typename U>
3888 static constexpr typename std::enable_if_t<std::is_unsigned_v<T>, U>
3889 UnsignedSortValue(U val) {
3890 return val;
3893 template <typename T, typename U>
3894 static constexpr
3895 typename std::enable_if_t<std::is_integral_v<T> && std::is_signed_v<T>, U>
3896 UnsignedSortValue(U val) {
3897 // Flip sign bit.
3898 return val ^ static_cast<U>(std::numeric_limits<T>::min());
3901 template <typename T, typename UnsignedT>
3902 static constexpr
3903 typename std::enable_if_t<std::is_floating_point_v<T>, UnsignedT>
3904 UnsignedSortValue(UnsignedT val) {
3905 // Flip sign bit for positive numbers; flip all bits for negative numbers,
3906 // except negative NaNs.
3907 using FloatingPoint = mozilla::FloatingPoint<T>;
3908 static_assert(std::is_same_v<typename FloatingPoint::Bits, UnsignedT>,
3909 "FloatingPoint::Bits matches the unsigned int representation");
3911 // FF80'0000 is negative infinity, (FF80'0000, FFFF'FFFF] are all NaNs with
3912 // the sign-bit set (and the equivalent holds for double values). So any value
3913 // larger than negative infinity is a negative NaN.
3914 constexpr UnsignedT NegativeInfinity =
3915 FloatingPoint::kSignBit | FloatingPoint::kExponentBits;
3916 if (val > NegativeInfinity) {
3917 return val;
3919 if (val & FloatingPoint::kSignBit) {
3920 return ~val;
3922 return val ^ FloatingPoint::kSignBit;
3925 template <typename T>
3926 static typename std::enable_if_t<std::is_integral_v<T> ||
3927 std::is_same_v<T, uint8_clamped>>
3928 TypedArrayStdSort(SharedMem<void*> data, size_t length) {
3929 T* unwrapped = data.cast<T*>().unwrapUnshared();
3930 std::sort(unwrapped, unwrapped + length);
3933 template <typename T>
3934 static typename std::enable_if_t<std::is_floating_point_v<T>> TypedArrayStdSort(
3935 SharedMem<void*> data, size_t length) {
3936 // Sort on the unsigned representation for performance reasons.
3937 using UnsignedT =
3938 typename mozilla::UnsignedStdintTypeForSize<sizeof(T)>::Type;
3939 UnsignedT* unwrapped = data.cast<UnsignedT*>().unwrapUnshared();
3940 std::sort(unwrapped, unwrapped + length, [](UnsignedT x, UnsignedT y) {
3941 constexpr auto SortValue = UnsignedSortValue<T, UnsignedT>;
3942 return SortValue(x) < SortValue(y);
3946 template <typename T, typename Ops>
3947 static typename std::enable_if_t<std::is_same_v<Ops, UnsharedOps>, bool>
3948 TypedArrayStdSort(JSContext* cx, TypedArrayObject* typedArray, size_t length) {
3949 TypedArrayStdSort<T>(typedArray->dataPointerEither(), length);
3950 return true;
3953 template <typename T, typename Ops>
3954 static typename std::enable_if_t<std::is_same_v<Ops, SharedOps>, bool>
3955 TypedArrayStdSort(JSContext* cx, TypedArrayObject* typedArray, size_t length) {
3956 // Always create a copy when sorting shared memory backed typed arrays to
3957 // ensure concurrent write accesses doesn't lead to UB when calling std::sort.
3958 auto ptr = cx->make_pod_array<T>(length);
3959 if (!ptr) {
3960 return false;
3962 SharedMem<T*> unshared = SharedMem<T*>::unshared(ptr.get());
3963 SharedMem<T*> data = typedArray->dataPointerShared().cast<T*>();
3965 Ops::podCopy(unshared, data, length);
3967 TypedArrayStdSort<T>(unshared.template cast<void*>(), length);
3969 Ops::podCopy(data, unshared, length);
3971 return true;
3974 template <typename T, typename Ops>
3975 static bool TypedArrayCountingSort(JSContext* cx, TypedArrayObject* typedArray,
3976 size_t length) {
3977 static_assert(std::is_integral_v<T> || std::is_same_v<T, uint8_clamped>,
3978 "Counting sort expects integral array elements");
3980 // Determined by performance testing.
3981 if (length <= 64) {
3982 return TypedArrayStdSort<T, Ops>(cx, typedArray, length);
3985 // Map signed values onto the unsigned range when storing in buffer.
3986 using UnsignedT =
3987 typename mozilla::UnsignedStdintTypeForSize<sizeof(T)>::Type;
3988 constexpr T min = std::numeric_limits<T>::min();
3990 constexpr size_t InlineStorage = sizeof(T) == 1 ? 256 : 0;
3991 Vector<size_t, InlineStorage> buffer(cx);
3992 if (!buffer.resize(size_t(std::numeric_limits<UnsignedT>::max()) + 1)) {
3993 return false;
3996 SharedMem<T*> data = typedArray->dataPointerEither().cast<T*>();
3998 // Populate the buffer.
3999 for (size_t i = 0; i < length; i++) {
4000 T val = Ops::load(data + i);
4001 buffer[UnsignedT(val - min)]++;
4004 // Traverse the buffer in order and write back elements to array.
4005 UnsignedT val = UnsignedT(-1); // intentional overflow on first increment
4006 for (size_t i = 0; i < length;) {
4007 // Invariant: sum(buffer[val:]) == length-i
4008 size_t j;
4009 do {
4010 j = buffer[++val];
4011 } while (j == 0);
4013 for (; j > 0; j--) {
4014 Ops::store(data + i++, T(val + min));
4018 return true;
4021 template <typename T, typename U, typename Ops>
4022 static void SortByColumn(SharedMem<U*> data, size_t length, SharedMem<U*> aux,
4023 uint8_t col) {
4024 static_assert(std::is_unsigned_v<U>, "SortByColumn sorts on unsigned values");
4025 static_assert(std::is_same_v<Ops, UnsharedOps>,
4026 "SortByColumn only works on unshared data");
4028 // |counts| is used to compute the starting index position for each key.
4029 // Letting counts[0] always be 0, simplifies the transform step below.
4030 // Example:
4032 // Computing frequency counts for the input [1 2 1] gives:
4033 // 0 1 2 3 ... (keys)
4034 // 0 0 2 1 (frequencies)
4036 // Transforming frequencies to indexes gives:
4037 // 0 1 2 3 ... (keys)
4038 // 0 0 2 3 (indexes)
4040 constexpr size_t R = 256;
4042 // Initialize all entries to zero.
4043 size_t counts[R + 1] = {};
4045 const auto ByteAtCol = [col](U x) {
4046 U y = UnsignedSortValue<T, U>(x);
4047 return static_cast<uint8_t>(y >> (col * 8));
4050 // Compute frequency counts.
4051 for (size_t i = 0; i < length; i++) {
4052 U val = Ops::load(data + i);
4053 uint8_t b = ByteAtCol(val);
4054 counts[b + 1]++;
4057 // Transform counts to indices.
4058 std::partial_sum(std::begin(counts), std::end(counts), std::begin(counts));
4060 // Distribute
4061 for (size_t i = 0; i < length; i++) {
4062 U val = Ops::load(data + i);
4063 uint8_t b = ByteAtCol(val);
4064 size_t j = counts[b]++;
4065 MOZ_ASSERT(j < length,
4066 "index is in bounds when |data| can't be modified concurrently");
4067 UnsharedOps::store(aux + j, val);
4070 // Copy back
4071 Ops::podCopy(data, aux, length);
4074 template <typename T, typename Ops>
4075 static bool TypedArrayRadixSort(JSContext* cx, TypedArrayObject* typedArray,
4076 size_t length) {
4077 // Determined by performance testing.
4078 constexpr size_t StdSortMinCutoff = sizeof(T) == 2 ? 64 : 256;
4080 // Radix sort uses O(n) additional space, limit this space to 64 MB.
4081 constexpr size_t StdSortMaxCutoff = (64 * 1024 * 1024) / sizeof(T);
4083 if (length <= StdSortMinCutoff || length >= StdSortMaxCutoff) {
4084 return TypedArrayStdSort<T, Ops>(cx, typedArray, length);
4087 if constexpr (sizeof(T) == 2) {
4088 // Radix sort uses O(n) additional space, so when |n| reaches 2^16, switch
4089 // over to counting sort to limit the additional space needed to 2^16.
4090 constexpr size_t CountingSortMaxCutoff = 65536;
4092 if (length >= CountingSortMaxCutoff) {
4093 return TypedArrayCountingSort<T, Ops>(cx, typedArray, length);
4097 using UnsignedT =
4098 typename mozilla::UnsignedStdintTypeForSize<sizeof(T)>::Type;
4100 auto ptr = cx->make_zeroed_pod_array<UnsignedT>(length);
4101 if (!ptr) {
4102 return false;
4104 SharedMem<UnsignedT*> aux = SharedMem<UnsignedT*>::unshared(ptr.get());
4106 SharedMem<UnsignedT*> data =
4107 typedArray->dataPointerEither().cast<UnsignedT*>();
4109 // Always create a copy when sorting shared memory backed typed arrays to
4110 // ensure concurrent write accesses don't lead to computing bad indices.
4111 SharedMem<UnsignedT*> unshared;
4112 SharedMem<UnsignedT*> shared;
4113 UniquePtr<UnsignedT[], JS::FreePolicy> ptrUnshared;
4114 if constexpr (std::is_same_v<Ops, SharedOps>) {
4115 ptrUnshared = cx->make_pod_array<UnsignedT>(length);
4116 if (!ptrUnshared) {
4117 return false;
4119 unshared = SharedMem<UnsignedT*>::unshared(ptrUnshared.get());
4120 shared = data;
4122 Ops::podCopy(unshared, shared, length);
4124 data = unshared;
4127 for (uint8_t col = 0; col < sizeof(UnsignedT); col++) {
4128 SortByColumn<T, UnsignedT, UnsharedOps>(data, length, aux, col);
4131 if constexpr (std::is_same_v<Ops, SharedOps>) {
4132 Ops::podCopy(shared, unshared, length);
4135 return true;
4138 using TypedArraySortFn = bool (*)(JSContext*, TypedArrayObject*, size_t length);
4140 template <typename T, typename Ops>
4141 static constexpr typename std::enable_if_t<sizeof(T) == 1, TypedArraySortFn>
4142 TypedArraySort() {
4143 return TypedArrayCountingSort<T, Ops>;
4146 template <typename T, typename Ops>
4147 static constexpr typename std::enable_if_t<sizeof(T) == 2 || sizeof(T) == 4,
4148 TypedArraySortFn>
4149 TypedArraySort() {
4150 return TypedArrayRadixSort<T, Ops>;
4153 template <typename T, typename Ops>
4154 static constexpr typename std::enable_if_t<sizeof(T) == 8, TypedArraySortFn>
4155 TypedArraySort() {
4156 return TypedArrayStdSort<T, Ops>;
4159 bool js::intrinsic_TypedArrayNativeSort(JSContext* cx, unsigned argc,
4160 Value* vp) {
4161 CallArgs args = CallArgsFromVp(argc, vp);
4162 MOZ_ASSERT(args.length() == 1);
4164 TypedArrayObject* typedArray =
4165 UnwrapAndDowncastValue<TypedArrayObject>(cx, args[0]);
4166 if (!typedArray) {
4167 return false;
4170 auto length = typedArray->length();
4171 MOZ_RELEASE_ASSERT(length,
4172 "TypedArray is neither detached nor out-of-bounds");
4174 bool isShared = typedArray->isSharedMemory();
4175 switch (typedArray->type()) {
4176 #define SORT(_, T, N) \
4177 case Scalar::N: \
4178 if (isShared) { \
4179 if (!TypedArraySort<T, SharedOps>()(cx, typedArray, *length)) { \
4180 return false; \
4182 } else { \
4183 if (!TypedArraySort<T, UnsharedOps>()(cx, typedArray, *length)) { \
4184 return false; \
4187 break;
4188 JS_FOR_EACH_TYPED_ARRAY(SORT)
4189 #undef SORT
4190 default:
4191 MOZ_CRASH("Unsupported TypedArray type");
4194 args.rval().set(args[0]);
4195 return true;
4198 /* JS Public API */
4200 #define IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(ExternalType, NativeType, Name) \
4201 JS_PUBLIC_API JSObject* JS_New##Name##Array(JSContext* cx, \
4202 size_t nelements) { \
4203 return TypedArrayObjectTemplate<NativeType>::fromLength(cx, nelements); \
4206 JS_PUBLIC_API JSObject* JS_New##Name##ArrayFromArray(JSContext* cx, \
4207 HandleObject other) { \
4208 return TypedArrayObjectTemplate<NativeType>::fromArray(cx, other); \
4211 JS_PUBLIC_API JSObject* JS_New##Name##ArrayWithBuffer( \
4212 JSContext* cx, HandleObject arrayBuffer, size_t byteOffset, \
4213 int64_t length) { \
4214 return TypedArrayObjectTemplate<NativeType>::fromBuffer( \
4215 cx, arrayBuffer, byteOffset, length); \
4218 JS_PUBLIC_API JSObject* js::Unwrap##Name##Array(JSObject* obj) { \
4219 obj = obj->maybeUnwrapIf<TypedArrayObject>(); \
4220 if (!obj) { \
4221 return nullptr; \
4223 const JSClass* clasp = obj->getClass(); \
4224 if (clasp != FixedLengthTypedArrayObjectTemplate< \
4225 NativeType>::instanceClass() && \
4226 clasp != \
4227 ResizableTypedArrayObjectTemplate<NativeType>::instanceClass()) { \
4228 return nullptr; \
4230 return obj; \
4233 JS_PUBLIC_API ExternalType* JS_Get##Name##ArrayLengthAndData( \
4234 JSObject* obj, size_t* length, bool* isSharedMemory, \
4235 const JS::AutoRequireNoGC& nogc) { \
4236 TypedArrayObject* tarr = obj->maybeUnwrapAs<TypedArrayObject>(); \
4237 if (!tarr) { \
4238 return nullptr; \
4240 mozilla::Span<ExternalType> span = \
4241 JS::TypedArray<JS::Scalar::Name>::fromObject(tarr).getData( \
4242 isSharedMemory, nogc); \
4243 *length = span.Length(); \
4244 return span.data(); \
4247 JS_PUBLIC_API ExternalType* JS_Get##Name##ArrayData( \
4248 JSObject* obj, bool* isSharedMemory, const JS::AutoRequireNoGC& nogc) { \
4249 size_t length; \
4250 return JS_Get##Name##ArrayLengthAndData(obj, &length, isSharedMemory, \
4251 nogc); \
4253 JS_PUBLIC_API JSObject* JS_GetObjectAs##Name##Array( \
4254 JSObject* obj, size_t* length, bool* isShared, ExternalType** data) { \
4255 obj = js::Unwrap##Name##Array(obj); \
4256 if (!obj) { \
4257 return nullptr; \
4259 TypedArrayObject* tarr = &obj->as<TypedArrayObject>(); \
4260 *length = tarr->length().valueOr(0); \
4261 *isShared = tarr->isSharedMemory(); \
4262 *data = static_cast<ExternalType*>(tarr->dataPointerEither().unwrap( \
4263 /*safe - caller sees isShared flag*/)); \
4264 return obj; \
4267 JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS)
4268 #undef IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS
4270 JS_PUBLIC_API bool JS_IsTypedArrayObject(JSObject* obj) {
4271 return obj->canUnwrapAs<TypedArrayObject>();
4274 JS_PUBLIC_API size_t JS_GetTypedArrayLength(JSObject* obj) {
4275 TypedArrayObject* tarr = obj->maybeUnwrapAs<TypedArrayObject>();
4276 if (!tarr) {
4277 return 0;
4279 return tarr->length().valueOr(0);
4282 JS_PUBLIC_API size_t JS_GetTypedArrayByteOffset(JSObject* obj) {
4283 TypedArrayObject* tarr = obj->maybeUnwrapAs<TypedArrayObject>();
4284 if (!tarr) {
4285 return 0;
4287 return tarr->byteOffset().valueOr(0);
4290 JS_PUBLIC_API size_t JS_GetTypedArrayByteLength(JSObject* obj) {
4291 TypedArrayObject* tarr = obj->maybeUnwrapAs<TypedArrayObject>();
4292 if (!tarr) {
4293 return 0;
4295 return tarr->byteLength().valueOr(0);
4298 JS_PUBLIC_API bool JS_GetTypedArraySharedness(JSObject* obj) {
4299 TypedArrayObject* tarr = obj->maybeUnwrapAs<TypedArrayObject>();
4300 if (!tarr) {
4301 return false;
4303 return tarr->isSharedMemory();
4306 JS_PUBLIC_API JS::Scalar::Type JS_GetArrayBufferViewType(JSObject* obj) {
4307 ArrayBufferViewObject* view = obj->maybeUnwrapAs<ArrayBufferViewObject>();
4308 if (!view) {
4309 return Scalar::MaxTypedArrayViewType;
4312 if (view->is<TypedArrayObject>()) {
4313 return view->as<TypedArrayObject>().type();
4315 if (view->is<DataViewObject>()) {
4316 return Scalar::MaxTypedArrayViewType;
4318 MOZ_CRASH("invalid ArrayBufferView type");
4321 JS_PUBLIC_API size_t JS_MaxMovableTypedArraySize() {
4322 return FixedLengthTypedArrayObject::INLINE_BUFFER_LIMIT;
4325 namespace JS {
4327 const JSClass* const TypedArray_base::fixedLengthClasses =
4328 TypedArrayObject::fixedLengthClasses;
4329 const JSClass* const TypedArray_base::resizableClasses =
4330 TypedArrayObject::resizableClasses;
4332 #define INSTANTIATE(ExternalType, NativeType, Name) \
4333 template class TypedArray<JS::Scalar::Name>;
4334 JS_FOR_EACH_TYPED_ARRAY(INSTANTIATE)
4335 #undef INSTANTIATE
4337 JS::ArrayBufferOrView JS::ArrayBufferOrView::unwrap(JSObject* maybeWrapped) {
4338 if (!maybeWrapped) {
4339 return JS::ArrayBufferOrView(nullptr);
4341 auto* ab = maybeWrapped->maybeUnwrapIf<ArrayBufferObjectMaybeShared>();
4342 if (ab) {
4343 return ArrayBufferOrView::fromObject(ab);
4346 return ArrayBufferView::unwrap(maybeWrapped);
4349 bool JS::ArrayBufferOrView::isDetached() const {
4350 MOZ_ASSERT(obj);
4351 if (obj->is<ArrayBufferObjectMaybeShared>()) {
4352 return obj->as<ArrayBufferObjectMaybeShared>().isDetached();
4353 } else {
4354 return obj->as<ArrayBufferViewObject>().hasDetachedBuffer();
4358 bool JS::ArrayBufferOrView::isResizable() const {
4359 MOZ_ASSERT(obj);
4360 if (obj->is<ArrayBufferObjectMaybeShared>()) {
4361 return obj->as<ArrayBufferObjectMaybeShared>().isResizable();
4362 } else {
4363 return obj->as<ArrayBufferViewObject>().hasResizableBuffer();
4367 JS::TypedArray_base JS::TypedArray_base::fromObject(JSObject* unwrapped) {
4368 if (unwrapped && unwrapped->is<TypedArrayObject>()) {
4369 return TypedArray_base(unwrapped);
4371 return TypedArray_base(nullptr);
4374 // Template getData function for TypedArrays, implemented here because
4375 // it requires internal APIs.
4376 template <JS::Scalar::Type EType>
4377 typename mozilla::Span<typename TypedArray<EType>::DataType>
4378 TypedArray<EType>::getData(bool* isSharedMemory, const AutoRequireNoGC&) {
4379 using ExternalType = TypedArray<EType>::DataType;
4380 if (!obj) {
4381 return nullptr;
4383 TypedArrayObject* tarr = &obj->as<TypedArrayObject>();
4384 MOZ_ASSERT(tarr);
4385 *isSharedMemory = tarr->isSharedMemory();
4386 return {static_cast<ExternalType*>(tarr->dataPointerEither().unwrap(
4387 /*safe - caller sees isShared*/)),
4388 tarr->length().valueOr(0)};
4391 // Force the method defined above to actually be instantianted in this
4392 // compilation unit and emitted into the object file, since otherwise a binary
4393 // could include the header file and emit an undefined symbol that would not be
4394 // satisfied by the linker. (This happens with opt gtest, at least. In a DEBUG
4395 // build, the header contains a call to this function so it will always be
4396 // emitted.)
4397 #define INSTANTIATE_GET_DATA(a, b, Name) \
4398 template mozilla::Span<typename TypedArray<JS::Scalar::Name>::DataType> \
4399 TypedArray<JS::Scalar::Name>::getData(bool* isSharedMemory, \
4400 const AutoRequireNoGC&);
4401 JS_FOR_EACH_TYPED_ARRAY(INSTANTIATE_GET_DATA)
4402 #undef INSTANTIATE_GET_DATA
4404 } /* namespace JS */