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"
22 #include <string_view>
23 #if !defined(XP_WIN) && !defined(__wasi__)
24 # include <sys/mman.h>
26 #include <type_traits>
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"
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"
66 using JS::CanonicalizeNaN
;
69 using mozilla::IsAsciiDigit
;
74 * The non-templated base class for the specific typed implementations.
75 * This class holds all the member variables that are used by
79 bool TypedArrayObject::convertValue(JSContext
* cx
, HandleValue v
,
80 MutableHandleValue result
) const {
82 case Scalar::BigInt64
:
83 case Scalar::BigUint64
: {
84 BigInt
* bi
= ToBigInt(cx
, v
);
99 case Scalar::Uint8Clamped
: {
101 if (!ToNumber(cx
, v
, &num
)) {
104 result
.setNumber(num
);
107 case Scalar::MaxTypedArrayViewType
:
109 case Scalar::Simd128
:
110 MOZ_CRASH("Unsupported TypedArray type");
112 MOZ_ASSERT_UNREACHABLE("Invalid scalar type");
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
;
126 bool TypedArrayObject::ensureHasBuffer(JSContext
* cx
,
127 Handle
<TypedArrayObject
*> typedArray
) {
128 if (typedArray
->hasBuffer()) {
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()));
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
));
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
);
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()) {
191 curObj
->assertZeroLengthArrayData();
193 // Typed arrays with a buffer object do not need to be free'd
194 if (curObj
->hasBuffer()) {
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
);
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()) {
216 if (!IsInsideNursery(old
)) {
217 // Update the data slot pointer if it points to the old JSObject.
218 if (oldObj
->hasInlineElements()) {
219 newObj
->setInlineElements();
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.
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
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());
254 newObj
->fixedData(FixedLengthTypedArrayObject::FIXED_DATA_START
);
255 output
[0] = ZeroLengthArrayData
;
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
);
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
);
289 bool FixedLengthTypedArrayObject::hasInlineElements() const {
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
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
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
336 static void ReportOutOfBounds(JSContext
* cx
, TypedArrayObject
* typedArray
) {
337 if (typedArray
->hasDetachedBuffer()) {
338 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
339 JSMSG_TYPED_ARRAY_DETACHED
);
341 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
342 JSMSG_TYPED_ARRAY_RESIZED_BOUNDS
);
348 template <class TypedArrayType
>
349 static TypedArrayType
* NewTypedArrayObject(JSContext
* cx
, const JSClass
* clasp
,
351 gc::AllocKind allocKind
,
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()));
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
;
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
) {
423 const JSClass
* clasp
= TypedArrayObject::protoClassForType(ArrayTypeID());
424 return GlobalObject::createBlankPrototypeInheriting(cx
, clasp
,
428 static JSObject
* createConstructor(JSContext
* cx
, JSProtoKey key
) {
429 Handle
<GlobalObject
*> global
= cx
->global();
430 RootedFunction
ctorProto(
431 cx
, GlobalObject::getOrCreateTypedArrayConstructor(cx
, global
));
436 JSFunction
* fun
= NewFunctionWithProto(
437 cx
, class_constructor
, 3, FunctionFlags::NATIVE_CTOR
, nullptr,
438 ClassName(key
, cx
), ctorProto
, gc::AllocKind::FUNCTION
, TenuredObject
);
441 fun
->setJitInfo(&jit::JitInfo_TypedArrayConstructor
);
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
,
469 return fromBufferSameCompartment(
470 cx
, arrayBuffer
.as
<ArrayBufferObjectMaybeShared
>(), byteOffset
, length
,
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
);
481 if (!ThrowIfNotConstructing(cx
, args
, "typed array")) {
486 JSObject
* obj
= create(cx
, args
);
490 args
.rval().setObject(*obj
);
495 static JSObject
* create(JSContext
* cx
, const CallArgs
& args
) {
496 MOZ_ASSERT(args
.isConstructing());
499 if (args
.length() == 0 || !args
[0].isObject()) {
502 if (!ToIndex(cx
, args
.get(0), JSMSG_BAD_ARRAY_LENGTH
, &len
)) {
506 // Steps 5.a and 6.c.iii.
507 RootedObject
proto(cx
);
508 if (!GetPrototypeFromBuiltinConstructor(cx
, args
, protoKey(), &proto
)) {
512 return fromLength(cx
, len
, proto
);
515 RootedObject
dataObj(cx
, &args
[0].toObject());
518 // 23.2.5.1.1 AllocateTypedArray, step 1.
519 RootedObject
proto(cx
);
520 if (!GetPrototypeFromBuiltinConstructor(cx
, args
, protoKey(), &proto
)) {
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
,
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
,
553 if (!byteOffsetValue
.isUndefined()) {
554 if (!ToIndex(cx
, byteOffsetValue
, byteOffset
)) {
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()));
569 *length
= UINT64_MAX
;
570 if (!lengthValue
.isUndefined()) {
571 if (!ToIndex(cx
, lengthValue
, length
)) {
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
));
592 if (bufferMaybeUnwrapped
->isDetached()) {
593 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
594 JSMSG_TYPED_ARRAY_DETACHED
);
599 size_t bufferByteLength
= bufferMaybeUnwrapped
->byteLength();
600 MOZ_ASSERT(bufferByteLength
<= ByteLengthLimit
);
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()));
613 // Resizable buffers without an explicit length are auto-length.
614 if (bufferMaybeUnwrapped
->isResizable()) {
616 *autoLength
= AutoLength::Yes
;
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()));
632 size_t newByteLength
= bufferByteLength
- size_t(byteOffset
);
633 len
= newByteLength
/ BYTES_PER_ELEMENT
;
636 uint64_t newByteLength
= lengthIndex
* BYTES_PER_ELEMENT
;
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()));
648 len
= size_t(lengthIndex
);
651 MOZ_ASSERT(len
<= ByteLengthLimit
/ BYTES_PER_ELEMENT
);
653 *autoLength
= AutoLength::No
;
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
) {
665 auto autoLength
= AutoLength::No
;
666 if (!computeAndCheckLength(cx
, buffer
, byteOffset
, lengthIndex
, &length
,
671 if (!buffer
->isResizable()) {
673 return FixedLengthTypedArray::makeInstance(cx
, buffer
, byteOffset
, length
,
677 return ResizableTypedArray::makeInstance(cx
, buffer
, byteOffset
, length
,
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
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
);
700 ReportAccessDenied(cx
);
704 if (!unwrapped
->is
<ArrayBufferObjectMaybeShared
>()) {
705 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
706 JSMSG_TYPED_ARRAY_BAD_ARGS
);
710 Rooted
<ArrayBufferObjectMaybeShared
*> unwrappedBuffer(cx
);
711 unwrappedBuffer
= &unwrapped
->as
<ArrayBufferObjectMaybeShared
>();
714 auto autoLength
= AutoLength::No
;
715 if (!computeAndCheckLength(cx
, unwrappedBuffer
, byteOffset
, lengthIndex
,
716 &length
, &autoLength
)) {
720 // Make sure to get the [[Prototype]] for the created typed array from
722 RootedObject
protoRoot(cx
, proto
);
724 protoRoot
= GlobalObject::getOrCreatePrototype(cx
, protoKey());
730 RootedObject
typedArray(cx
);
732 JSAutoRealm
ar(cx
, unwrappedBuffer
);
734 RootedObject
wrappedProto(cx
, protoRoot
);
735 if (!cx
->compartment()->wrap(cx
, &wrappedProto
)) {
739 if (!unwrappedBuffer
->isResizable()) {
740 typedArray
= FixedLengthTypedArray::makeInstance(
741 cx
, unwrappedBuffer
, byteOffset
, length
, wrappedProto
);
743 typedArray
= ResizableTypedArray::makeInstance(
744 cx
, unwrappedBuffer
, byteOffset
, length
, autoLength
, wrappedProto
);
751 if (!cx
->compartment()->wrap(cx
, &typedArray
)) {
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
,
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
);
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.
796 ArrayBufferObject
* buf
= ArrayBufferObject::createZeroed(cx
, byteLength
);
805 // ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78
806 // 23.2.5.1.1 AllocateTypedArray ( constructorName, newTarget, defaultProto [
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
)) {
816 return FixedLengthTypedArray::makeInstance(cx
, buffer
, 0, nelements
, proto
,
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
,
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
>;
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()));
874 return NewTypedArrayObject
<FixedLengthTypedArrayObject
>(
875 cx
, instanceClass(), proto
, allocKind
, heap
);
878 static FixedLengthTypedArrayObject
* makeProtoInstance(
879 JSContext
* cx
, HandleObject proto
, gc::AllocKind allocKind
) {
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
;
898 obj
= makeProtoInstance(cx
, proto
, allocKind
);
900 obj
= newBuiltinClassInstance(cx
, allocKind
, heap
);
902 if (!obj
|| !obj
->init(cx
, buffer
, byteOffset
, len
, BYTES_PER_ELEMENT
)) {
909 static FixedLengthTypedArrayObject
* makeTemplateObject(JSContext
* cx
,
911 MOZ_ASSERT(len
>= 0);
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
);
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());
935 static void initTypedArraySlots(FixedLengthTypedArrayObject
* tarray
,
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)));
946 tarray
->fixedData(FixedLengthTypedArrayObject::FIXED_DATA_START
);
947 output
[0] = TypedArrayObject::ZeroLengthArrayData
;
952 static void initTypedArrayData(FixedLengthTypedArrayObject
* tarray
, void* buf
,
953 size_t nbytes
, gc::AllocKind allocKind
) {
955 InitReservedSlot(tarray
, TypedArrayObject::DATA_SLOT
, buf
, nbytes
,
956 MemoryUse::TypedArrayElements
);
959 constexpr size_t dataOffset
= ArrayBufferViewObject::dataOffset();
960 constexpr size_t offset
= dataOffset
+ sizeof(HeapSlot
);
961 MOZ_ASSERT(offset
+ nbytes
<= GetGCKindBytes(allocKind
));
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
);
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
);
995 initTypedArraySlots(obj
, len
);
1001 nbytes
= RoundUp(nbytes
, sizeof(Value
));
1002 buf
= cx
->nursery().allocateZeroedBuffer(obj
, nbytes
,
1003 js::ArrayBufferContentsArena
);
1005 ReportOutOfMemory(cx
);
1010 initTypedArrayData(obj
, buf
, nbytes
, allocKind
);
1016 template <typename NativeType
>
1017 class ResizableTypedArrayObjectTemplate
1018 : public ResizableTypedArrayObject
,
1019 public TypedArrayObjectTemplate
<NativeType
> {
1020 friend class js::TypedArrayObject
;
1022 using TypedArrayTemplate
= TypedArrayObjectTemplate
<NativeType
>;
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()));
1041 return NewTypedArrayObject
<ResizableTypedArrayObject
>(
1042 cx
, instanceClass(), proto
, allocKind
, heap
);
1045 static ResizableTypedArrayObject
* makeProtoInstance(JSContext
* cx
,
1047 gc::AllocKind allocKind
) {
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
) {
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
;
1069 obj
= makeProtoInstance(cx
, proto
, allocKind
);
1071 obj
= newBuiltinClassInstance(cx
, allocKind
, gc::Heap::Default
);
1073 if (!obj
|| !obj
->initResizable(cx
, buffer
, byteOffset
, len
,
1074 BYTES_PER_ELEMENT
, autoLength
)) {
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
);
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());
1110 template <typename NativeType
>
1111 bool TypedArrayObjectTemplate
<NativeType
>::convertValue(JSContext
* cx
,
1113 NativeType
* result
) {
1115 if (!ToNumber(cx
, v
, &d
)) {
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
1134 *result
= NativeType(d
);
1136 static_assert(sizeof(NativeType
) <= 4);
1137 int32_t n
= ToInt32(d
);
1138 *result
= NativeType(n
);
1144 bool TypedArrayObjectTemplate
<int64_t>::convertValue(JSContext
* cx
,
1147 JS_TRY_VAR_OR_RETURN_FALSE(cx
, *result
, ToBigInt64(cx
, v
));
1152 bool TypedArrayObjectTemplate
<uint64_t>::convertValue(JSContext
* cx
,
1155 JS_TRY_VAR_OR_RETURN_FALSE(cx
, *result
, ToBigUint64(cx
, v
));
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.
1171 NativeType nativeValue
;
1172 if (!convertValue(cx
, v
, &nativeValue
)) {
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
);
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) \
1197 return FixedLengthTypedArrayObjectTemplate<T>::makeTypedArrayWithTemplate( \
1199 JS_FOR_EACH_TYPED_ARRAY(CREATE_TYPED_ARRAY
)
1200 #undef CREATE_TYPED_ARRAY
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) \
1214 return TypedArrayObjectTemplate<T>::makeTypedArrayWithTemplate(cx, tobj, \
1216 JS_FOR_EACH_TYPED_ARRAY(CREATE_TYPED_ARRAY
)
1217 #undef CREATE_TYPED_ARRAY
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) \
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
1237 MOZ_CRASH("Unsupported TypedArray type");
1241 TypedArrayObject
* js::NewUint8ArrayWithLength(JSContext
* cx
, int32_t len
,
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
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
);
1275 srcArray
= &other
->as
<TypedArrayObject
>();
1277 srcArray
= other
->maybeUnwrapAs
<TypedArrayObject
>();
1279 ReportAccessDenied(cx
);
1284 // InitializeTypedArrayFromTypedArray, step 1. (Skipped)
1286 // InitializeTypedArrayFromTypedArray, step 2.
1287 auto srcLength
= srcArray
->length();
1289 ReportOutOfBounds(cx
, srcArray
);
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
)) {
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
);
1318 // InitializeTypedArrayFromTypedArray, steps 12-15.
1319 Rooted
<TypedArrayObject
*> obj(cx
, FixedLengthTypedArray::makeInstance(
1320 cx
, buffer
, 0, elementLength
, proto
));
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");
1338 if (!ElementSpecific
<T
, UnsharedOps
>::setFromTypedArray(
1339 obj
, elementLength
, srcArray
, elementLength
, 0)) {
1340 MOZ_ASSERT_UNREACHABLE(
1341 "setFromTypedArray can only fail for overlapping buffers");
1350 static MOZ_ALWAYS_INLINE
bool IsOptimizableInit(JSContext
* cx
,
1351 HandleObject iterable
,
1353 MOZ_ASSERT(!*optimized
);
1355 if (!IsPackedArray(iterable
)) {
1359 ForOfPIC::Chain
* stubChain
= ForOfPIC::getOrCreate(cx
);
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)
1382 bool optimized
= false;
1383 if (!IsOptimizableInit(cx
, other
, &optimized
)) {
1387 // Fast path when iterable is a packed array using the default iterator.
1389 // Steps 6.b.iv.2-3. (We don't need to call IterableToList for the fast
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
)) {
1403 Rooted
<FixedLengthTypedArrayObject
*> obj(
1404 cx
, FixedLengthTypedArray::makeInstance(cx
, buffer
, 0, len
, proto
));
1409 // InitializeTypedArrayFromList, steps 3-4.
1410 MOZ_ASSERT(!obj
->isSharedMemory());
1411 if (!ElementSpecific
<T
, UnsharedOps
>::initFromIterablePackedArray(cx
, obj
,
1416 // InitializeTypedArrayFromList, step 5. (The assertion isn't applicable for
1423 // Step 6.b.iv.1 (Assertion; implicit in our implementation).
1426 RootedValue
callee(cx
);
1427 RootedId
iteratorId(cx
, PropertyKey::Symbol(cx
->wellKnownSymbols().iterator
));
1428 if (!GetProperty(cx
, other
, other
, iteratorId
, &callee
)) {
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
));
1439 DecompileValueGenerator(cx
, JSDVG_SEARCH_STACK
, otherVal
, nullptr);
1443 JS_ReportErrorNumberUTF8(cx
, GetErrorMessage
, nullptr, JSMSG_NOT_ITERABLE
,
1448 FixedInvokeArgs
<2> args2(cx
);
1449 args2
[0].setObject(*other
);
1450 args2
[1].set(callee
);
1453 RootedValue
rval(cx
);
1454 if (!CallSelfHostedFunction(cx
, cx
->names().IterableToList
,
1455 UndefinedHandleValue
, args2
, &rval
)) {
1459 // Step 6.b.iv.3.b (Implemented below).
1460 arrayLike
= &rval
.toObject();
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).
1469 // We implement InitializeTypedArrayFromList in terms of
1470 // InitializeTypedArrayFromArrayLike.
1472 // InitializeTypedArrayFromArrayLike, step 1.
1474 if (!GetLengthProperty(cx
, arrayLike
, &len
)) {
1478 // InitializeTypedArrayFromArrayLike, step 2.
1479 Rooted
<ArrayBufferObject
*> buffer(cx
);
1480 if (!maybeCreateArrayBuffer(cx
, len
, &buffer
)) {
1484 MOZ_ASSERT(len
<= ByteLengthLimit
/ BYTES_PER_ELEMENT
);
1487 Rooted
<TypedArrayObject
*> obj(
1488 cx
, FixedLengthTypedArray::makeInstance(cx
, buffer
, 0, len
, proto
));
1493 // InitializeTypedArrayFromArrayLike, steps 3-4.
1494 MOZ_ASSERT(!obj
->isSharedMemory());
1495 if (!ElementSpecific
<T
, UnsharedOps
>::setFromNonTypedArray(cx
, obj
, arrayLike
,
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");
1512 template <typename T
>
1513 static bool GetTemplateObjectForNative(JSContext
* cx
,
1514 const JS::HandleValueArray args
,
1515 MutableHandleObject res
) {
1516 if (args
.length() == 0) {
1520 HandleValue arg
= args
[0];
1521 if (arg
.isInt32()) {
1523 if (arg
.toInt32() >= 0) {
1524 len
= arg
.toInt32();
1528 if (!js::CalculateAllocSize
<T
>(len
, &nbytes
) ||
1529 nbytes
> TypedArrayObject::ByteLengthLimit
) {
1534 FixedLengthTypedArrayObjectTemplate
<T
>::makeTemplateObject(cx
, len
));
1538 if (!arg
.isObject()) {
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
)) {
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.
1553 if (!obj
->is
<ArrayBufferObjectMaybeShared
>() ||
1554 !obj
->as
<ArrayBufferObjectMaybeShared
>().isResizable()) {
1556 FixedLengthTypedArrayObjectTemplate
<T
>::makeTemplateObject(cx
, len
));
1558 res
.set(ResizableTypedArrayObjectTemplate
<T
>::makeTemplateObject(cx
));
1563 /* static */ bool TypedArrayObject::GetTemplateObjectForNative(
1564 JSContext
* cx
, Native native
, const JS::HandleValueArray args
,
1565 MutableHandleObject 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
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));
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));
1593 static bool TypedArray_byteOffsetGetter(JSContext
* cx
, unsigned argc
,
1595 CallArgs args
= CallArgsFromVp(argc
, vp
);
1596 return CallNonGenericMethod
<IsTypedArrayObject
, ByteOffsetGetterImpl
>(cx
,
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));
1606 static bool TypedArray_byteLengthGetter(JSContext
* cx
, unsigned argc
,
1608 CallArgs args
= CallArgsFromVp(argc
, vp
);
1609 return CallNonGenericMethod
<IsTypedArrayObject
, ByteLengthGetterImpl
>(cx
,
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
)) {
1620 args
.rval().set(tarray
->bufferValue());
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
,
1633 CallArgs args
= CallArgsFromVp(argc
, vp
);
1636 if (!args
.thisv().isObject()) {
1637 args
.rval().setUndefined();
1641 JSObject
* obj
= CheckedUnwrapStatic(&args
.thisv().toObject());
1643 ReportAccessDenied(cx
);
1648 if (!obj
->is
<TypedArrayObject
>()) {
1649 args
.rval().setUndefined();
1654 JSProtoKey protoKey
= StandardProtoKeyOrNull(obj
);
1655 MOZ_ASSERT(protoKey
);
1657 args
.rval().setString(ClassName(protoKey
, cx
));
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),
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
,
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());
1716 auto sourceLength
= source
->length();
1717 if (!sourceLength
) {
1718 ReportOutOfBounds(cx
, source
);
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
);
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
);
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
);
1745 // Steps 6-12, 16-24.
1746 switch (target
->type()) {
1747 #define SET_FROM_TYPED_ARRAY(_, T, N) \
1749 if (!SetFromTypedArray<T>(target, targetLength, source, *sourceLength, \
1751 ReportOutOfMemory(cx); \
1755 JS_FOR_EACH_TYPED_ARRAY(SET_FROM_TYPED_ARRAY
)
1756 #undef SET_FROM_TYPED_ARRAY
1758 MOZ_CRASH("Unsupported TypedArray type");
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
,
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.)
1779 if (!GetLengthProperty(cx
, src
, &srcLength
)) {
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
);
1790 size_t offset
= size_t(targetOffset
);
1791 if (srcLength
> targetLength
- offset
) {
1792 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1793 JSMSG_SOURCE_ARRAY_TOO_LONG
);
1797 MOZ_ASSERT(srcLength
<= targetLength
);
1800 if (srcLength
> 0) {
1801 switch (target
->type()) {
1802 #define SET_FROM_NON_TYPED_ARRAY(_, T, N) \
1804 if (!SetFromNonTypedArray<T>(cx, target, src, srcLength, offset)) \
1807 JS_FOR_EACH_TYPED_ARRAY(SET_FROM_NON_TYPED_ARRAY
)
1808 #undef SET_FROM_NON_TYPED_ARRAY
1810 MOZ_CRASH("Unsupported TypedArray type");
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 )
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
>());
1831 double targetOffset
= 0;
1832 if (args
.length() > 1) {
1834 if (!ToInteger(cx
, args
[1], &targetOffset
)) {
1839 if (targetOffset
< 0) {
1840 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr, JSMSG_BAD_INDEX
);
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
);
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)));
1860 Rooted
<TypedArrayObject
*> srcTypedArray(cx
);
1862 JSObject
* obj
= CheckedUnwrapStatic(src
);
1864 ReportAccessDenied(cx
);
1868 if (obj
->is
<TypedArrayObject
>()) {
1869 srcTypedArray
= &obj
->as
<TypedArrayObject
>();
1874 if (srcTypedArray
) {
1875 if (!SetTypedArrayFromTypedArray(cx
, target
, targetOffset
, *targetLength
,
1880 if (!SetTypedArrayFromArrayLike(cx
, target
, targetOffset
, *targetLength
,
1887 args
.rval().setUndefined();
1892 bool TypedArrayObject::set(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1893 CallArgs args
= CallArgsFromVp(argc
, vp
);
1894 return CallNonGenericMethod
<IsTypedArrayObject
, TypedArrayObject::set_impl
>(
1898 // ES2020 draft rev dc1e21c454bd316810be1c0e7af0131a2d7f38e9
1899 // 22.2.3.5 %TypedArray%.prototype.copyWithin ( target, start [ , end ] )
1901 bool TypedArrayObject::copyWithin_impl(JSContext
* cx
, const CallArgs
& args
) {
1902 MOZ_ASSERT(IsTypedArrayObject(args
.thisv()));
1905 Rooted
<TypedArrayObject
*> tarray(
1906 cx
, &args
.thisv().toObject().as
<TypedArrayObject
>());
1908 auto arrayLength
= tarray
->length();
1910 ReportOutOfBounds(cx
, tarray
);
1915 size_t len
= *arrayLength
;
1918 double relativeTarget
;
1919 if (!ToInteger(cx
, args
.get(0), &relativeTarget
)) {
1925 if (relativeTarget
< 0) {
1926 to
= std::max(len
+ relativeTarget
, 0.0);
1928 to
= std::min(relativeTarget
, double(len
));
1932 double relativeStart
;
1933 if (!ToInteger(cx
, args
.get(1), &relativeStart
)) {
1939 if (relativeStart
< 0) {
1940 from
= std::max(len
+ relativeStart
, 0.0);
1942 from
= std::min(relativeStart
, double(len
));
1947 if (!args
.hasDefined(2)) {
1950 if (!ToInteger(cx
, args
[2], &relativeEnd
)) {
1957 if (relativeEnd
< 0) {
1958 final_
= std::max(len
+ relativeEnd
, 0.0);
1960 final_
= std::min(relativeEnd
, double(len
));
1964 MOZ_ASSERT(to
<= len
);
1966 if (from
<= final_
) {
1967 count
= std::min(final_
- from
, len
- to
);
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.
1980 args
.rval().setObject(*tarray
);
1984 // Reacquire the length because side-effects may have detached or resized the
1986 arrayLength
= tarray
->length();
1988 ReportOutOfBounds(cx
, tarray
);
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
);
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
);
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
;
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
);
2034 SharedMem
<uint8_t*> data
= tarray
->dataPointerEither().cast
<uint8_t*>();
2035 if (tarray
->isSharedMemory()) {
2036 jit::AtomicOperations::memmoveSafeWhenRacy(data
+ byteDest
, data
+ byteSrc
,
2039 memmove(data
.unwrapUnshared() + byteDest
, data
.unwrapUnshared() + byteSrc
,
2043 args
.rval().setObject(*tarray
);
2048 bool TypedArrayObject::copyWithin(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2049 AutoJSMethodProfilerEntry
pseudoFrame(cx
, "[TypedArray].prototype",
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.
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()) {
2068 js::EscapePrinter
ep(sprinter
, esc
);
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.)
2084 size_t length
= string
->length();
2087 if (length
% 2 != 0) {
2088 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2089 JSMSG_TYPED_ARRAY_BAD_HEX_STRING_LENGTH
);
2093 JSLinearString
* linear
= string
->ensureLinear(cx
);
2098 // Step 4. (Not applicable in our implementation.)
2099 MOZ_ASSERT(bytes
.empty());
2105 while (index
< length
&& bytes
.length() < maxLength
) {
2107 char16_t c0
= linear
->latin1OrTwoByteChar(index
);
2108 char16_t c1
= linear
->latin1OrTwoByteChar(index
+ 1);
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());
2125 uint8_t byte
= (mozilla::AsciiAlphanumericToNumber(c0
) << 4) +
2126 mozilla::AsciiAlphanumericToNumber(c1
);
2129 if (!bytes
.append(byte
)) {
2135 *readLength
= index
;
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
) {
2150 // Map the base64 characters to their values.
2151 for (uint8_t i
= 0; i
< 64; ++i
) {
2152 result
[alphabet
[i
]] = i
;
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.
2186 * URL and filename safe base64 alphabet.
2191 enum class LastChunkHandling
{
2193 * Allow partial chunks at the end of the input.
2198 * Disallow partial chunks at the end of the input.
2203 * Stop before partial chunks at the end of the input.
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.)
2220 size_t remaining
= maxLength
;
2221 if (remaining
== 0) {
2222 MOZ_ASSERT(bytes
.empty());
2227 JSLinearString
* linear
= string
->ensureLinear(cx
);
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)) {
2242 bytes
.infallibleAppend(chunk
>> 16);
2243 bytes
.infallibleAppend(chunk
>> 8);
2244 bytes
.infallibleAppend(chunk
);
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
);
2261 if (!bytes
.reserve(bytes
.length() + 2)) {
2264 bytes
.infallibleAppend(chunk
>> 10);
2265 bytes
.infallibleAppend(chunk
>> 2);
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
);
2282 if (!bytes
.reserve(bytes
.length() + 1)) {
2285 bytes
.infallibleAppend(chunk
>> 4);
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
);
2301 // String index after the last fully read base64 chunk.
2305 MOZ_ASSERT(bytes
.empty());
2309 // Current base64 chunk, a uint24 number.
2314 // Current base64 chunk length, in the range [0..4].
2315 size_t chunkLength
= 0;
2319 // Current string index.
2323 size_t length
= linear
->length();
2325 const auto& decode
= alphabet
== Alphabet::Base64
? Base64::Decode::Base64
2326 : Base64::Decode::Base64Url
;
2329 for (; index
< length
; index
++) {
2330 // Step 10.c. (Reordered)
2331 char16_t ch
= linear
->latin1OrTwoByteChar(index
);
2334 if (mozilla::IsAsciiWhitespace(ch
)) {
2338 // Step 10.b. (Moved out of loop.)
2340 // Step 10.d. (Performed in for-loop step.)
2348 uint8_t value
= Base64::InvalidChar
;
2349 if (mozilla::IsAscii(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());
2360 // Step 10.h. (Not applicable in our implementation.)
2363 if ((remaining
== 1 && chunkLength
== 2) ||
2364 (remaining
== 2 && chunkLength
== 3)) {
2370 chunk
= (chunk
<< 6) | value
;
2376 if (chunkLength
== 4) {
2378 if (!decodeChunk(chunk
)) {
2390 // NB: Add +1 to include the |index| update from step 10.d.
2394 MOZ_ASSERT(remaining
>= 3);
2396 if (remaining
== 0) {
2404 if (index
== length
) {
2406 if (chunkLength
> 0) {
2408 if (lastChunkHandling
== LastChunkHandling::StopBeforePartial
) {
2413 // Steps 10.b.i.2-3.
2414 if (lastChunkHandling
== LastChunkHandling::Loose
) {
2416 if (chunkLength
== 1) {
2417 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2418 JSMSG_TYPED_ARRAY_BAD_INCOMPLETE_CHUNK
);
2421 MOZ_ASSERT(chunkLength
== 2 || chunkLength
== 3);
2424 if (!decodePartialChunk(chunk
, chunkLength
)) {
2429 MOZ_ASSERT(lastChunkHandling
== LastChunkHandling::Strict
);
2432 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2433 JSMSG_TYPED_ARRAY_BAD_INCOMPLETE_CHUNK
);
2439 *readLength
= length
;
2444 MOZ_ASSERT(index
< length
);
2445 MOZ_ASSERT(linear
->latin1OrTwoByteChar(index
) == '=');
2448 if (chunkLength
< 2) {
2449 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2450 JSMSG_TYPED_ARRAY_BAD_INCOMPLETE_CHUNK
);
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
)) {
2464 if (chunkLength
== 2) {
2466 if (index
== length
) {
2467 // Step 10.e.iii.1.a.
2468 if (lastChunkHandling
== LastChunkHandling::StopBeforePartial
) {
2473 // Step 10.e.iii.1.b.
2474 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2475 JSMSG_TYPED_ARRAY_MISSING_BASE64_PADDING
);
2480 char16_t ch
= linear
->latin1OrTwoByteChar(index
);
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
)) {
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
,
2506 bool throwOnExtraBits
= lastChunkHandling
== LastChunkHandling::Strict
;
2509 if (!decodePartialChunk(chunk
, chunkLength
, throwOnExtraBits
)) {
2514 *readLength
= length
;
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
,
2527 Rooted
<Value
> value(cx
);
2528 if (!GetProperty(cx
, options
, options
, cx
->names().alphabet
, &value
)) {
2532 if (value
.isUndefined()) {
2533 *result
= Alphabet::Base64
;
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
);
2547 if (StringEqualsAscii(linear
, "base64")) {
2548 *result
= Alphabet::Base64
;
2552 if (StringEqualsAscii(linear
, "base64url")) {
2553 *result
= Alphabet::Base64Url
;
2557 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2558 JSMSG_TYPED_ARRAY_BAD_BASE64_ALPHABET
);
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
,
2576 if (value
.isUndefined()) {
2577 *result
= LastChunkHandling::Loose
;
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
);
2591 if (StringEqualsAscii(linear
, "loose")) {
2592 *result
= LastChunkHandling::Loose
;
2596 if (StringEqualsAscii(linear
, "strict")) {
2597 *result
= LastChunkHandling::Strict
;
2601 if (StringEqualsAscii(linear
, "stop-before-partial")) {
2602 *result
= LastChunkHandling::StopBeforePartial
;
2606 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2607 JSMSG_TYPED_ARRAY_BAD_BASE64_LAST_CHUNK_HANDLING
);
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
);
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());
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]));
2638 if (!GetAlphabetOption(cx
, options
, &alphabet
)) {
2643 if (!GetLastChunkHandlingOption(cx
, options
, &lastChunkHandling
)) {
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
)) {
2658 size_t resultLength
= bytes
.length();
2662 TypedArrayObjectTemplate
<uint8_t>::fromLength(cx
, resultLength
);
2668 auto target
= SharedMem
<uint8_t*>::unshared(tarray
->dataPointerUnshared());
2669 auto source
= SharedMem
<uint8_t*>::unshared(bytes
.begin());
2670 UnsharedOps::podCopy(target
, source
, resultLength
);
2673 args
.rval().setObject(*tarray
);
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
);
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());
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
)) {
2701 size_t resultLength
= bytes
.length();
2705 TypedArrayObjectTemplate
<uint8_t>::fromLength(cx
, resultLength
);
2711 auto target
= SharedMem
<uint8_t*>::unshared(tarray
->dataPointerUnshared());
2712 auto source
= SharedMem
<uint8_t*>::unshared(bytes
.begin());
2713 UnsharedOps::podCopy(target
, source
, resultLength
);
2716 args
.rval().setObject(*tarray
);
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
>());
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());
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]));
2748 if (!GetAlphabetOption(cx
, options
, &alphabet
)) {
2753 if (!GetLastChunkHandlingOption(cx
, options
, &lastChunkHandling
)) {
2759 auto length
= tarray
->length();
2761 ReportOutOfBounds(cx
, tarray
);
2766 size_t maxLength
= *length
;
2769 ByteVector
bytes(cx
);
2771 if (!FromBase64(cx
, string
, alphabet
, lastChunkHandling
, maxLength
, bytes
,
2777 size_t written
= bytes
.length();
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
);
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
);
2796 UnsharedOps::podCopy(target
, source
, written
);
2800 Rooted
<PlainObject
*> result(cx
, NewPlainObject(cx
));
2806 Rooted
<Value
> readValue(cx
, NumberValue(readLength
));
2807 if (!DefineDataProperty(cx
, result
, cx
->names().read
, readValue
)) {
2812 Rooted
<Value
> writtenValue(cx
, NumberValue(written
));
2813 if (!DefineDataProperty(cx
, result
, cx
->names().written
, writtenValue
)) {
2818 args
.rval().setObject(*result
);
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
);
2831 return CallNonGenericMethod
<IsUint8ArrayObject
, uint8array_setFromBase64
>(
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
>());
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());
2852 auto length
= tarray
->length();
2854 ReportOutOfBounds(cx
, tarray
);
2859 size_t maxLength
= *length
;
2862 ByteVector
bytes(cx
);
2864 if (!FromHex(cx
, string
, maxLength
, bytes
, &readLength
)) {
2869 size_t written
= bytes
.length();
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
);
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
);
2888 UnsharedOps::podCopy(target
, source
, written
);
2892 Rooted
<PlainObject
*> result(cx
, NewPlainObject(cx
));
2898 Rooted
<Value
> readValue(cx
, NumberValue(readLength
));
2899 if (!DefineDataProperty(cx
, result
, cx
->names().read
, readValue
)) {
2904 Rooted
<Value
> writtenValue(cx
, NumberValue(written
));
2905 if (!DefineDataProperty(cx
, result
, cx
->names().written
, writtenValue
)) {
2910 args
.rval().setObject(*result
);
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
);
2923 return CallNonGenericMethod
<IsUint8ArrayObject
, uint8array_setFromHex
>(cx
,
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
>());
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]));
2947 if (!GetAlphabetOption(cx
, options
, &alphabet
)) {
2952 // Step 8. (Partial)
2953 auto length
= tarray
->length();
2955 ReportOutOfBounds(cx
, tarray
);
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
};
2965 if (!outLength
.isValid() || outLength
.value() > JSString::MAX_LENGTH
) {
2966 ReportAllocationOverflow(cx
);
2970 JSStringBuilder
sb(cx
);
2971 if (!sb
.reserve(outLength
.value())) {
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 '='.
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('=');
3025 MOZ_ASSERT(toRead
== 0);
3028 MOZ_ASSERT(sb
.length() == outLength
.value(), "all characters were written");
3031 auto* str
= sb
.finishString();
3036 args
.rval().setString(str
);
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
);
3049 return CallNonGenericMethod
<IsUint8ArrayObject
, uint8array_toBase64
>(cx
,
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();
3065 ReportOutOfBounds(cx
, tarray
);
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
);
3084 JSStringBuilder
sb(cx
);
3085 if (!sb
.reserve(outLength
)) {
3089 // NB: Lower case hex digits.
3090 static constexpr char HexDigits
[] = "0123456789abcdef";
3091 static_assert(std::char_traits
<char>::length(HexDigits
) == 16);
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");
3108 auto* str
= sb
.finishString();
3113 args
.rval().setString(str
);
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
);
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),
3165 /* static */ const JSFunctionSpec
TypedArrayObject::staticFunctions
[] = {
3166 JS_SELF_HOSTED_FN("from", "TypedArrayStaticFrom", 3, 0),
3167 JS_SELF_HOSTED_FN("of", "TypedArrayStaticOf", 0, 0),
3171 /* static */ const JSPropertySpec
TypedArrayObject::staticProperties
[] = {
3172 JS_SELF_HOSTED_SYM_GET(species
, "$TypedArraySpecies", 0),
3176 static JSObject
* CreateSharedTypedArrayPrototype(JSContext
* cx
,
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
,
3190 ClassSpec::DontDefineConstructor
,
3193 /* static */ const JSClass
TypedArrayObject::sharedTypedArrayPrototypeClass
= {
3194 "TypedArrayPrototype",
3195 JSCLASS_HAS_CACHED_PROTO(JSProto_TypedArray
),
3197 &TypedArrayObjectSharedTypedArrayPrototypeClassSpec
,
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
));
3215 // We need to specialize for floats and other integer types.
3217 bool TypedArrayObjectTemplate
<int32_t>::getElementPure(TypedArrayObject
* tarray
,
3220 *vp
= Int32Value(getIndex(tarray
, index
));
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
);
3233 bool TypedArrayObjectTemplate
<float>::getElementPure(TypedArrayObject
* tarray
,
3234 size_t index
, Value
* vp
) {
3235 float val
= getIndex(tarray
, index
);
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
);
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
);
3269 bool TypedArrayObjectTemplate
<int64_t>::getElementPure(TypedArrayObject
* tarray
,
3276 bool TypedArrayObjectTemplate
<uint64_t>::getElementPure(
3277 TypedArrayObject
* tarray
, size_t index
, Value
* vp
) {
3280 } /* anonymous namespace */
3284 template <typename NativeType
>
3285 bool TypedArrayObjectTemplate
<NativeType
>::getElement(JSContext
* cx
,
3286 TypedArrayObject
* tarray
,
3288 MutableHandleValue val
) {
3289 MOZ_ALWAYS_TRUE(getElementPure(tarray
, index
, val
.address()));
3294 bool TypedArrayObjectTemplate
<int64_t>::getElement(JSContext
* cx
,
3295 TypedArrayObject
* tarray
,
3297 MutableHandleValue val
) {
3298 int64_t n
= getIndex(tarray
, index
);
3299 BigInt
* res
= BigInt::createFromInt64(cx
, n
);
3308 bool TypedArrayObjectTemplate
<uint64_t>::getElement(JSContext
* cx
,
3309 TypedArrayObject
* tarray
,
3311 MutableHandleValue val
) {
3312 uint64_t n
= getIndex(tarray
, index
);
3313 BigInt
* res
= BigInt::createFromUint64(cx
, n
);
3320 } /* anonymous namespace */
3325 bool TypedArrayObject::getElement
<CanGC
>(JSContext
* cx
, size_t index
,
3326 MutableHandleValue val
) {
3328 #define GET_ELEMENT(_, T, N) \
3330 return TypedArrayObjectTemplate<T>::getElement(cx, this, index, val);
3331 JS_FOR_EACH_TYPED_ARRAY(GET_ELEMENT
)
3333 case Scalar::MaxTypedArrayViewType
:
3335 case Scalar::Simd128
:
3339 MOZ_CRASH("Unknown TypedArray type");
3343 bool TypedArrayObject::getElement
<NoGC
>(
3344 JSContext
* cx
, size_t index
,
3345 typename MaybeRooted
<Value
, NoGC
>::MutableHandleType vp
) {
3346 return getElementPure(index
, vp
.address());
3351 bool TypedArrayObject::getElementPure(size_t index
, Value
* vp
) {
3353 #define GET_ELEMENT_PURE(_, T, N) \
3355 return TypedArrayObjectTemplate<T>::getElementPure(this, index, vp);
3356 JS_FOR_EACH_TYPED_ARRAY(GET_ELEMENT_PURE
)
3358 case Scalar::MaxTypedArrayViewType
:
3360 case Scalar::Simd128
:
3364 MOZ_CRASH("Unknown TypedArray type");
3368 bool TypedArrayObject::getElements(JSContext
* cx
,
3369 Handle
<TypedArrayObject
*> tarray
,
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) \
3377 for (size_t i = 0; i < length; ++i, ++vp) { \
3378 if (!TypedArrayObjectTemplate<T>::getElement( \
3379 cx, tarray, i, MutableHandleValue::fromMarkedLocation(vp))) { \
3384 JS_FOR_EACH_TYPED_ARRAY(GET_ELEMENTS
)
3386 case Scalar::MaxTypedArrayViewType
:
3388 case Scalar::Simd128
:
3392 MOZ_CRASH("Unknown TypedArray type");
3400 * TypedArrayObject boilerplate
3403 static const JSClassOps TypedArrayClassOps
= {
3404 nullptr, // addProperty
3405 nullptr, // delProperty
3406 nullptr, // enumerate
3407 nullptr, // newEnumerate
3409 nullptr, // mayResolve
3410 FixedLengthTypedArrayObject::finalize
, // finalize
3412 nullptr, // construct
3413 ArrayBufferViewObject::trace
, // trace
3416 static const JSClassOps ResizableTypedArrayClassOps
= {
3417 nullptr, // addProperty
3418 nullptr, // delProperty
3419 nullptr, // enumerate
3420 nullptr, // newEnumerate
3422 nullptr, // mayResolve
3423 nullptr, // finalize
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), \
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),
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),
3461 static constexpr const JSFunctionSpec
* TypedArrayStaticMethods(
3462 Scalar::Type type
) {
3463 if (type
== Scalar::Uint8
) {
3464 return uint8array_static_methods
;
3469 static constexpr const JSFunctionSpec
* TypedArrayMethods(Scalar::Type type
) {
3470 if (type
== Scalar::Uint8
) {
3471 return uint8array_methods
;
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], \
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) \
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) \
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
3534 &TypedArrayObject::fixedLengthClasses
)[Scalar::MaxTypedArrayViewType
] =
3535 TypedArrayObject::anyClasses
[0];
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
3568 bool TypedArrayObject::isOriginalLengthGetter(Native native
) {
3569 return native
== TypedArray_lengthGetter
;
3573 bool TypedArrayObject::isOriginalByteOffsetGetter(Native native
) {
3574 return native
== TypedArray_byteOffsetGetter
;
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)) { \
3587 JS_FOR_EACH_TYPED_ARRAY(CHECK_TYPED_ARRAY_CONSTRUCTOR
)
3588 #undef CHECK_TYPED_ARRAY_CONSTRUCTOR
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);
3616 if (object
->is
<DataViewObject
>()) {
3617 DataViewObject
& view
= object
->as
<DataViewObject
>();
3618 *dataPointer
= view
.dataPointerEither().cast
<uint8_t*>();
3619 *byteLength
= view
.byteLength().valueOr(0);
3623 if (object
->is
<ArrayBufferObject
>()) {
3624 ArrayBufferObject
& buffer
= object
->as
<ArrayBufferObject
>();
3625 *dataPointer
= buffer
.dataPointerShared();
3626 *byteLength
= buffer
.byteLength();
3630 if (object
->is
<SharedArrayBufferObject
>()) {
3631 SharedArrayBufferObject
& buffer
= object
->as
<SharedArrayBufferObject
>();
3632 *dataPointer
= buffer
.dataPointerShared();
3633 *byteLength
= buffer
.byteLength();
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
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.
3678 const char* cstr
= js::NumberToCString(&cbuf
, result
, &cstrlen
);
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
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;
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".
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
);
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
);
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) \
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
:
3791 case Scalar::Simd128
:
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.
3812 RootedValue
converted(cx
);
3813 if (!obj
->convertValue(cx
, v
, &converted
)) {
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
);
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
,
3833 Handle
<PropertyDescriptor
> desc
,
3834 ObjectOpResult
& result
) {
3835 // These are all substeps of 3.b.
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
);
3846 if (desc
.isAccessorDescriptor()) {
3847 return result
.fail(JSMSG_CANT_REDEFINE_PROP
);
3851 if (desc
.hasConfigurable() && !desc
.configurable()) {
3852 return result
.fail(JSMSG_CANT_REDEFINE_PROP
);
3856 if (desc
.hasEnumerable() && !desc
.enumerable()) {
3857 return result
.fail(JSMSG_CANT_REDEFINE_PROP
);
3861 if (desc
.hasWritable() && !desc
.writable()) {
3862 return result
.fail(JSMSG_CANT_REDEFINE_PROP
);
3866 if (desc
.hasValue()) {
3867 switch (obj
->type()) {
3868 #define DEFINE_TYPED_ARRAY_ELEMENT(_, T, 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
:
3876 case Scalar::Simd128
:
3880 MOZ_CRASH("Unsupported TypedArray type");
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
) {
3893 template <typename T
, typename U
>
3895 typename
std::enable_if_t
<std::is_integral_v
<T
> && std::is_signed_v
<T
>, U
>
3896 UnsignedSortValue(U val
) {
3898 return val
^ static_cast<U
>(std::numeric_limits
<T
>::min());
3901 template <typename T
, typename UnsignedT
>
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
) {
3919 if (val
& FloatingPoint::kSignBit
) {
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.
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
);
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
);
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
);
3974 template <typename T
, typename Ops
>
3975 static bool TypedArrayCountingSort(JSContext
* cx
, TypedArrayObject
* typedArray
,
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.
3982 return TypedArrayStdSort
<T
, Ops
>(cx
, typedArray
, length
);
3985 // Map signed values onto the unsigned range when storing in buffer.
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)) {
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
4013 for (; j
> 0; j
--) {
4014 Ops::store(data
+ i
++, T(val
+ min
));
4021 template <typename T
, typename U
, typename Ops
>
4022 static void SortByColumn(SharedMem
<U
*> data
, size_t length
, SharedMem
<U
*> aux
,
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.
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
);
4057 // Transform counts to indices.
4058 std::partial_sum(std::begin(counts
), std::end(counts
), std::begin(counts
));
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
);
4071 Ops::podCopy(data
, aux
, length
);
4074 template <typename T
, typename Ops
>
4075 static bool TypedArrayRadixSort(JSContext
* cx
, TypedArrayObject
* typedArray
,
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
);
4098 typename
mozilla::UnsignedStdintTypeForSize
<sizeof(T
)>::Type
;
4100 auto ptr
= cx
->make_zeroed_pod_array
<UnsignedT
>(length
);
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
);
4119 unshared
= SharedMem
<UnsignedT
*>::unshared(ptrUnshared
.get());
4122 Ops::podCopy(unshared
, shared
, length
);
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
);
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
>
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,
4150 return TypedArrayRadixSort
<T
, Ops
>;
4153 template <typename T
, typename Ops
>
4154 static constexpr typename
std::enable_if_t
<sizeof(T
) == 8, TypedArraySortFn
>
4156 return TypedArrayStdSort
<T
, Ops
>;
4159 bool js::intrinsic_TypedArrayNativeSort(JSContext
* cx
, unsigned argc
,
4161 CallArgs args
= CallArgsFromVp(argc
, vp
);
4162 MOZ_ASSERT(args
.length() == 1);
4164 TypedArrayObject
* typedArray
=
4165 UnwrapAndDowncastValue
<TypedArrayObject
>(cx
, args
[0]);
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) \
4179 if (!TypedArraySort<T, SharedOps>()(cx, typedArray, *length)) { \
4183 if (!TypedArraySort<T, UnsharedOps>()(cx, typedArray, *length)) { \
4188 JS_FOR_EACH_TYPED_ARRAY(SORT
)
4191 MOZ_CRASH("Unsupported TypedArray type");
4194 args
.rval().set(args
[0]);
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, \
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>(); \
4223 const JSClass* clasp = obj->getClass(); \
4224 if (clasp != FixedLengthTypedArrayObjectTemplate< \
4225 NativeType>::instanceClass() && \
4227 ResizableTypedArrayObjectTemplate<NativeType>::instanceClass()) { \
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>(); \
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) { \
4250 return JS_Get##Name##ArrayLengthAndData(obj, &length, isSharedMemory, \
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); \
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*/)); \
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
>();
4279 return tarr
->length().valueOr(0);
4282 JS_PUBLIC_API
size_t JS_GetTypedArrayByteOffset(JSObject
* obj
) {
4283 TypedArrayObject
* tarr
= obj
->maybeUnwrapAs
<TypedArrayObject
>();
4287 return tarr
->byteOffset().valueOr(0);
4290 JS_PUBLIC_API
size_t JS_GetTypedArrayByteLength(JSObject
* obj
) {
4291 TypedArrayObject
* tarr
= obj
->maybeUnwrapAs
<TypedArrayObject
>();
4295 return tarr
->byteLength().valueOr(0);
4298 JS_PUBLIC_API
bool JS_GetTypedArraySharedness(JSObject
* obj
) {
4299 TypedArrayObject
* tarr
= obj
->maybeUnwrapAs
<TypedArrayObject
>();
4303 return tarr
->isSharedMemory();
4306 JS_PUBLIC_API
JS::Scalar::Type
JS_GetArrayBufferViewType(JSObject
* obj
) {
4307 ArrayBufferViewObject
* view
= obj
->maybeUnwrapAs
<ArrayBufferViewObject
>();
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
;
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
)
4337 JS::ArrayBufferOrView
JS::ArrayBufferOrView::unwrap(JSObject
* maybeWrapped
) {
4338 if (!maybeWrapped
) {
4339 return JS::ArrayBufferOrView(nullptr);
4341 auto* ab
= maybeWrapped
->maybeUnwrapIf
<ArrayBufferObjectMaybeShared
>();
4343 return ArrayBufferOrView::fromObject(ab
);
4346 return ArrayBufferView::unwrap(maybeWrapped
);
4349 bool JS::ArrayBufferOrView::isDetached() const {
4351 if (obj
->is
<ArrayBufferObjectMaybeShared
>()) {
4352 return obj
->as
<ArrayBufferObjectMaybeShared
>().isDetached();
4354 return obj
->as
<ArrayBufferViewObject
>().hasDetachedBuffer();
4358 bool JS::ArrayBufferOrView::isResizable() const {
4360 if (obj
->is
<ArrayBufferObjectMaybeShared
>()) {
4361 return obj
->as
<ArrayBufferObjectMaybeShared
>().isResizable();
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
;
4383 TypedArrayObject
* tarr
= &obj
->as
<TypedArrayObject
>();
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
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 */