TypedArrayObject.cpp (246839B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "vm/TypedArrayObject-inl.h" 8 #include "vm/TypedArrayObject.h" 9 10 #include "mozilla/Casting.h" 11 #include "mozilla/CheckedInt.h" 12 #include "mozilla/EndianUtils.h" 13 #include "mozilla/FloatingPoint.h" 14 #include "mozilla/IntegerTypeTraits.h" 15 #include "mozilla/Likely.h" 16 #include "mozilla/PodOperations.h" 17 #include "mozilla/ScopeExit.h" 18 #include "mozilla/SIMD.h" 19 #include "mozilla/TextUtils.h" 20 21 #include <algorithm> 22 #include <charconv> 23 #include <iterator> 24 #include <limits> 25 #include <numeric> 26 #include <string.h> 27 #include <string_view> 28 #if !defined(XP_WIN) && !defined(__wasi__) 29 # include <sys/mman.h> 30 #endif 31 #include <type_traits> 32 33 #include "jsnum.h" 34 #include "jstypes.h" 35 36 #include "builtin/Array.h" 37 #include "builtin/DataViewObject.h" 38 #include "gc/Barrier.h" 39 #include "gc/MaybeRooted.h" 40 #include "jit/InlinableNatives.h" 41 #include "jit/TrampolineNatives.h" 42 #include "js/Conversions.h" 43 #include "js/experimental/TypedData.h" // JS_GetArrayBufferViewType, JS_GetTypedArray{Length,ByteOffset,ByteLength}, JS_IsTypedArrayObject 44 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* 45 #include "js/PropertySpec.h" 46 #include "js/ScalarType.h" // JS::Scalar::Type 47 #include "js/UniquePtr.h" 48 #include "js/Wrapper.h" 49 #include "util/DifferentialTesting.h" 50 #include "util/StringBuilder.h" 51 #include "util/Text.h" 52 #include "util/WindowsWrapper.h" 53 #include "vm/ArrayBufferObject.h" 54 #include "vm/EqualityOperations.h" 55 #include "vm/Float16.h" 56 #include "vm/FunctionFlags.h" // js::FunctionFlags 57 #include "vm/GlobalObject.h" 58 #include "vm/Interpreter.h" 59 #include "vm/JSContext.h" 60 #include "vm/JSObject.h" 61 #include "vm/SelfHosting.h" 62 #include "vm/SharedMem.h" 63 #include "vm/Uint8Clamped.h" 64 #include "vm/WrapperObject.h" 65 66 #include "builtin/Sorting-inl.h" 67 #include "gc/Nursery-inl.h" 68 #include "vm/ArrayBufferObject-inl.h" 69 #include "vm/Compartment-inl.h" 70 #include "vm/GeckoProfiler-inl.h" 71 #include "vm/NativeObject-inl.h" 72 73 using namespace js; 74 75 using mozilla::IsAsciiDigit; 76 77 /* 78 * TypedArrayObject 79 * 80 * The non-templated base class for the specific typed implementations. 81 * This class holds all the member variables that are used by 82 * the subclasses. 83 */ 84 85 bool TypedArrayObject::convertValue(JSContext* cx, HandleValue v, 86 MutableHandleValue result) const { 87 switch (type()) { 88 case Scalar::BigInt64: 89 case Scalar::BigUint64: { 90 BigInt* bi = ToBigInt(cx, v); 91 if (!bi) { 92 return false; 93 } 94 result.setBigInt(bi); 95 return true; 96 } 97 case Scalar::Int8: 98 case Scalar::Uint8: 99 case Scalar::Int16: 100 case Scalar::Uint16: 101 case Scalar::Int32: 102 case Scalar::Uint32: 103 case Scalar::Float16: 104 case Scalar::Float32: 105 case Scalar::Float64: 106 case Scalar::Uint8Clamped: { 107 double num; 108 if (!ToNumber(cx, v, &num)) { 109 return false; 110 } 111 result.setNumber(num); 112 return true; 113 } 114 case Scalar::MaxTypedArrayViewType: 115 case Scalar::Int64: 116 case Scalar::Simd128: 117 MOZ_CRASH("Unsupported TypedArray type"); 118 } 119 MOZ_ASSERT_UNREACHABLE("Invalid scalar type"); 120 return false; 121 } 122 123 static bool IsTypedArrayObject(HandleValue v) { 124 return v.isObject() && v.toObject().is<TypedArrayObject>(); 125 } 126 127 static bool IsUint8ArrayObject(HandleValue v) { 128 return IsTypedArrayObject(v) && 129 v.toObject().as<TypedArrayObject>().type() == Scalar::Uint8; 130 } 131 132 /* static */ 133 bool TypedArrayObject::ensureHasBuffer(JSContext* cx, 134 Handle<TypedArrayObject*> typedArray) { 135 if (typedArray->hasBuffer()) { 136 return true; 137 } 138 139 MOZ_ASSERT(typedArray->is<FixedLengthTypedArrayObject>(), 140 "Resizable and immutable TypedArrays always use an ArrayBuffer"); 141 142 auto tarray = HandleObject(typedArray).as<FixedLengthTypedArrayObject>(); 143 144 size_t byteLength = tarray->byteLength(); 145 146 AutoRealm ar(cx, tarray); 147 148 ArrayBufferObject* buffer; 149 if (tarray->hasMallocedElements(cx)) { 150 // Allocate a new array buffer and transfer our malloced buffer to it 151 // without copying. 152 buffer = 153 ArrayBufferObject::createFromTypedArrayMallocedElements(cx, tarray); 154 if (!buffer) { 155 return false; 156 } 157 } else { 158 buffer = ArrayBufferObject::createZeroed(cx, byteLength); 159 if (!buffer) { 160 return false; 161 } 162 163 // tarray is not shared, because if it were it would have a buffer. 164 memcpy(buffer->dataPointer(), tarray->dataPointerUnshared(), byteLength); 165 166 // If the object is in the nursery, the buffer will be freed by the next 167 // nursery GC. Free the data slot pointer if the object has no inline data. 168 // 169 // Note: we checked hasMallocedElements above, but allocating the array 170 // buffer object might have triggered a GC and this can malloc typed array 171 // elements if the typed array was in the nursery. 172 if (tarray->isTenured() && tarray->hasMallocedElements(cx)) { 173 size_t nbytes = RoundUp(byteLength, sizeof(Value)); 174 js_free(tarray->elements()); 175 RemoveCellMemory(tarray, nbytes, MemoryUse::TypedArrayElements); 176 } 177 178 tarray->setFixedSlot(TypedArrayObject::DATA_SLOT, 179 PrivateValue(buffer->dataPointer())); 180 } 181 182 MOZ_ASSERT(tarray->elements() == buffer->dataPointer()); 183 184 buffer->pinLength(tarray->isLengthPinned()); 185 186 // Attaching the first view to an array buffer is infallible. 187 MOZ_ALWAYS_TRUE(buffer->addView(cx, tarray)); 188 189 tarray->setFixedSlot(TypedArrayObject::BUFFER_SLOT, ObjectValue(*buffer)); 190 191 return true; 192 } 193 194 #ifdef DEBUG 195 void FixedLengthTypedArrayObject::assertZeroLengthArrayData() const { 196 if (length() == 0 && !hasBuffer()) { 197 uint8_t* end = fixedData(FixedLengthTypedArrayObject::FIXED_DATA_START); 198 MOZ_ASSERT(end[0] == ZeroLengthArrayData); 199 } 200 } 201 #endif 202 203 void FixedLengthTypedArrayObject::finalize(JS::GCContext* gcx, JSObject* obj) { 204 MOZ_ASSERT(!IsInsideNursery(obj)); 205 auto* curObj = &obj->as<FixedLengthTypedArrayObject>(); 206 207 // Template objects or discarded objects (which didn't have enough room 208 // for inner elements) don't have anything to free. 209 if (!curObj->elementsRaw()) { 210 return; 211 } 212 213 curObj->assertZeroLengthArrayData(); 214 215 // Typed arrays with a buffer object do not need to be free'd 216 if (curObj->hasBuffer()) { 217 return; 218 } 219 220 // Free the data slot pointer if it does not point into the old JSObject. 221 if (!curObj->hasInlineElements()) { 222 size_t nbytes = RoundUp(curObj->byteLength(), sizeof(Value)); 223 gcx->free_(obj, curObj->elements(), nbytes, MemoryUse::TypedArrayElements); 224 } 225 } 226 227 /* static */ 228 size_t FixedLengthTypedArrayObject::objectMoved(JSObject* obj, JSObject* old) { 229 auto* newObj = &obj->as<FixedLengthTypedArrayObject>(); 230 const auto* oldObj = &old->as<FixedLengthTypedArrayObject>(); 231 MOZ_ASSERT(newObj->elementsRaw() == oldObj->elementsRaw()); 232 233 // Typed arrays with a buffer object do not need an update. 234 if (oldObj->hasBuffer()) { 235 return 0; 236 } 237 238 if (!IsInsideNursery(old)) { 239 // Update the data slot pointer if it points to the old JSObject. 240 if (oldObj->hasInlineElements()) { 241 newObj->setInlineElements(); 242 } 243 244 return 0; 245 } 246 247 void* buf = oldObj->elements(); 248 249 // Discarded objects (which didn't have enough room for inner elements) don't 250 // have any data to move. 251 if (!buf) { 252 return 0; 253 } 254 255 Nursery& nursery = obj->runtimeFromMainThread()->gc.nursery(); 256 257 // Determine if we can use inline data for the target array. If this is 258 // possible, the nursery will have picked an allocation size that is large 259 // enough. 260 size_t nbytes = oldObj->byteLength(); 261 bool canUseDirectForward = nbytes >= sizeof(uintptr_t); 262 263 constexpr size_t headerSize = dataOffset() + sizeof(HeapSlot); 264 265 gc::AllocKind allocKind = oldObj->allocKindForTenure(); 266 MOZ_ASSERT_IF(obj->isTenured(), obj->asTenured().getAllocKind() == allocKind); 267 MOZ_ASSERT_IF(nbytes == 0, 268 headerSize + sizeof(uint8_t) <= GetGCKindBytes(allocKind)); 269 270 if (nursery.isInside(buf) && 271 headerSize + nbytes <= GetGCKindBytes(allocKind)) { 272 MOZ_ASSERT(oldObj->hasInlineElements()); 273 #ifdef DEBUG 274 if (nbytes == 0) { 275 uint8_t* output = 276 newObj->fixedData(FixedLengthTypedArrayObject::FIXED_DATA_START); 277 output[0] = ZeroLengthArrayData; 278 } 279 #endif 280 newObj->setInlineElements(); 281 mozilla::PodCopy(newObj->elements(), oldObj->elements(), nbytes); 282 283 // Set a forwarding pointer for the element buffers in case they were 284 // preserved on the stack by Ion. 285 nursery.setForwardingPointerWhileTenuring( 286 oldObj->elements(), newObj->elements(), canUseDirectForward); 287 288 return 0; 289 } 290 291 // Non-inline allocations are rounded up. 292 nbytes = RoundUp(nbytes, sizeof(Value)); 293 294 Nursery::WasBufferMoved result = 295 nursery.maybeMoveNurseryOrMallocBufferOnPromotion( 296 &buf, newObj, nbytes, nbytes, MemoryUse::TypedArrayElements, 297 ArrayBufferContentsArena); 298 if (result == Nursery::BufferMoved) { 299 newObj->setReservedSlot(DATA_SLOT, PrivateValue(buf)); 300 301 // Set a forwarding pointer for the element buffers in case they were 302 // preserved on the stack by Ion. 303 nursery.setForwardingPointerWhileTenuring( 304 oldObj->elements(), newObj->elements(), canUseDirectForward); 305 306 return nbytes; 307 } 308 309 return 0; 310 } 311 312 bool FixedLengthTypedArrayObject::hasInlineElements() const { 313 return elements() == 314 this->fixedData(FixedLengthTypedArrayObject::FIXED_DATA_START) && 315 byteLength() <= FixedLengthTypedArrayObject::INLINE_BUFFER_LIMIT; 316 } 317 318 void FixedLengthTypedArrayObject::setInlineElements() { 319 char* dataSlot = reinterpret_cast<char*>(this) + dataOffset(); 320 *reinterpret_cast<void**>(dataSlot) = 321 this->fixedData(FixedLengthTypedArrayObject::FIXED_DATA_START); 322 } 323 324 bool FixedLengthTypedArrayObject::hasMallocedElements(JSContext* cx) const { 325 return !hasInlineElements() && !cx->nursery().isInside(elements()); 326 } 327 328 /* Helper clamped uint8_t type */ 329 330 uint32_t js::ClampDoubleToUint8(const double x) { 331 // Not < so that NaN coerces to 0 332 if (!(x > 0)) { 333 return 0; 334 } 335 336 if (x >= 255) { 337 return 255; 338 } 339 340 // Convert with truncation. 341 uint8_t y = uint8_t(x); 342 343 // Now |y| is rounded toward zero. We want rounded to nearest, ties to even. 344 double r = x - double(y); 345 346 // It was a tie (since the difference is exactly 0.5). Round up if the number 347 // is odd. 348 if (r == 0.5) { 349 return y + (y & 1); 350 } 351 352 // Round up if truncation incorrectly rounded down. 353 return y + (r > 0.5); 354 } 355 356 static void ReportOutOfBounds(JSContext* cx, TypedArrayObject* typedArray) { 357 if (typedArray->hasDetachedBuffer()) { 358 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 359 JSMSG_TYPED_ARRAY_DETACHED); 360 } else { 361 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 362 JSMSG_TYPED_ARRAY_RESIZED_BOUNDS); 363 } 364 } 365 366 namespace { 367 368 template <class TypedArrayType> 369 static TypedArrayType* NewTypedArrayObject(JSContext* cx, const JSClass* clasp, 370 HandleObject proto, 371 gc::AllocKind allocKind, 372 gc::Heap heap) { 373 MOZ_ASSERT(proto); 374 allocKind = gc::GetFinalizedAllocKindForClass(allocKind, clasp); 375 376 static_assert(std::is_same_v<TypedArrayType, FixedLengthTypedArrayObject> || 377 std::is_same_v<TypedArrayType, ResizableTypedArrayObject> || 378 std::is_same_v<TypedArrayType, ImmutableTypedArrayObject>); 379 380 // Fixed length typed arrays can store data inline so we only use fixed slots 381 // to cover the reserved slots, ignoring the AllocKind. 382 MOZ_ASSERT(ClassCanHaveFixedData(clasp)); 383 constexpr size_t nfixed = TypedArrayType::RESERVED_SLOTS; 384 static_assert(nfixed <= NativeObject::MAX_FIXED_SLOTS); 385 static_assert(!std::is_same_v<TypedArrayType, FixedLengthTypedArrayObject> || 386 nfixed == FixedLengthTypedArrayObject::FIXED_DATA_START); 387 388 Rooted<SharedShape*> shape( 389 cx, 390 SharedShape::getInitialShape(cx, clasp, cx->realm(), AsTaggedProto(proto), 391 nfixed, ObjectFlags())); 392 if (!shape) { 393 return nullptr; 394 } 395 396 return NativeObject::create<TypedArrayType>(cx, allocKind, heap, shape); 397 } 398 399 template <typename NativeType> 400 class FixedLengthTypedArrayObjectTemplate; 401 402 template <typename NativeType> 403 class ResizableTypedArrayObjectTemplate; 404 405 template <typename NativeType> 406 class ImmutableTypedArrayObjectTemplate; 407 408 template <typename NativeType> 409 class TypedArrayObjectTemplate { 410 friend class js::TypedArrayObject; 411 412 using FixedLengthTypedArray = FixedLengthTypedArrayObjectTemplate<NativeType>; 413 using ResizableTypedArray = ResizableTypedArrayObjectTemplate<NativeType>; 414 using ImmutableTypedArray = ImmutableTypedArrayObjectTemplate<NativeType>; 415 using AutoLength = ArrayBufferViewObject::AutoLength; 416 417 static constexpr auto ByteLengthLimit = TypedArrayObject::ByteLengthLimit; 418 static constexpr auto INLINE_BUFFER_LIMIT = 419 FixedLengthTypedArrayObject::INLINE_BUFFER_LIMIT; 420 421 public: 422 static constexpr Scalar::Type ArrayTypeID() { 423 return TypeIDOfType<NativeType>::id; 424 } 425 static constexpr JSProtoKey protoKey() { 426 return TypeIDOfType<NativeType>::protoKey; 427 } 428 429 static constexpr size_t BYTES_PER_ELEMENT = sizeof(NativeType); 430 431 static JSObject* createPrototype(JSContext* cx, JSProtoKey key) { 432 Handle<GlobalObject*> global = cx->global(); 433 RootedObject typedArrayProto( 434 cx, GlobalObject::getOrCreateTypedArrayPrototype(cx, global)); 435 if (!typedArrayProto) { 436 return nullptr; 437 } 438 439 const JSClass* clasp = TypedArrayObject::protoClassForType(ArrayTypeID()); 440 return GlobalObject::createBlankPrototypeInheriting(cx, clasp, 441 typedArrayProto); 442 } 443 444 static JSObject* createConstructor(JSContext* cx, JSProtoKey key) { 445 Handle<GlobalObject*> global = cx->global(); 446 RootedFunction ctorProto( 447 cx, GlobalObject::getOrCreateTypedArrayConstructor(cx, global)); 448 if (!ctorProto) { 449 return nullptr; 450 } 451 452 JSFunction* fun = NewFunctionWithProto( 453 cx, class_constructor, 3, FunctionFlags::NATIVE_CTOR, nullptr, 454 ClassName(key, cx), ctorProto, gc::AllocKind::FUNCTION, TenuredObject); 455 456 if (fun) { 457 fun->setJitInfo(&jit::JitInfo_TypedArrayConstructor); 458 } 459 460 return fun; 461 } 462 463 static bool convertValue(JSContext* cx, HandleValue v, NativeType* result); 464 465 static TypedArrayObject* makeTypedArrayWithTemplate( 466 JSContext* cx, TypedArrayObject* templateObj, HandleObject array) { 467 MOZ_ASSERT(!IsWrapper(array)); 468 MOZ_ASSERT(!array->is<ArrayBufferObjectMaybeShared>()); 469 470 return fromArray(cx, array); 471 } 472 473 static TypedArrayObject* makeTypedArrayWithTemplate( 474 JSContext* cx, TypedArrayObject* templateObj, HandleObject arrayBuffer, 475 HandleValue byteOffsetValue, HandleValue lengthValue) { 476 MOZ_ASSERT(!IsWrapper(arrayBuffer)); 477 MOZ_ASSERT(arrayBuffer->is<ArrayBufferObjectMaybeShared>()); 478 479 uint64_t byteOffset, length; 480 if (!byteOffsetAndLength(cx, byteOffsetValue, lengthValue, &byteOffset, 481 &length)) { 482 return nullptr; 483 } 484 485 return fromBufferSameCompartment( 486 cx, arrayBuffer.as<ArrayBufferObjectMaybeShared>(), byteOffset, length, 487 nullptr); 488 } 489 490 // ES2026 draft rev 6d71ca0e2dbf1c0cfb87b5eb7a83cf7c76591561 491 // 23.2.5.1 TypedArray ( ...args ) 492 static bool class_constructor(JSContext* cx, unsigned argc, Value* vp) { 493 AutoJSConstructorProfilerEntry pseudoFrame(cx, "[TypedArray]"); 494 CallArgs args = CallArgsFromVp(argc, vp); 495 496 // Step 1. 497 if (!ThrowIfNotConstructing(cx, args, "typed array")) { 498 return false; 499 } 500 501 // Steps 2-6. 502 JSObject* obj = create(cx, args); 503 if (!obj) { 504 return false; 505 } 506 args.rval().setObject(*obj); 507 return true; 508 } 509 510 private: 511 static JSObject* create(JSContext* cx, const CallArgs& args) { 512 MOZ_ASSERT(args.isConstructing()); 513 514 // Steps 5 and 6.c. 515 if (args.length() == 0 || !args[0].isObject()) { 516 // Step 6.c.ii. 517 uint64_t len; 518 if (!ToIndex(cx, args.get(0), JSMSG_BAD_ARRAY_LENGTH, &len)) { 519 return nullptr; 520 } 521 522 // Steps 5.a and 6.c.iii. 523 RootedObject proto(cx); 524 if (!GetPrototypeFromBuiltinConstructor(cx, args, protoKey(), &proto)) { 525 return nullptr; 526 } 527 528 return fromLength(cx, len, proto); 529 } 530 531 RootedObject dataObj(cx, &args[0].toObject()); 532 533 // Step 6.b.i. 534 // 23.2.5.1.1 AllocateTypedArray, step 1. 535 RootedObject proto(cx); 536 if (!GetPrototypeFromBuiltinConstructor(cx, args, protoKey(), &proto)) { 537 return nullptr; 538 } 539 540 // Steps 6.b.ii and 6.b.iv. 541 if (!UncheckedUnwrap(dataObj)->is<ArrayBufferObjectMaybeShared>()) { 542 return fromArray(cx, dataObj, proto); 543 } 544 545 // Steps 6.b.iii.1-2. 546 // 23.2.5.1.3 InitializeTypedArrayFromArrayBuffer, steps 2 and 4. 547 uint64_t byteOffset, length; 548 if (!byteOffsetAndLength(cx, args.get(1), args.get(2), &byteOffset, 549 &length)) { 550 return nullptr; 551 } 552 553 // Step 6.b.iii.3. 554 if (dataObj->is<ArrayBufferObjectMaybeShared>()) { 555 auto buffer = dataObj.as<ArrayBufferObjectMaybeShared>(); 556 return fromBufferSameCompartment(cx, buffer, byteOffset, length, proto); 557 } 558 return fromBufferWrapped(cx, dataObj, byteOffset, length, proto); 559 } 560 561 // ES2026 draft rev 6d71ca0e2dbf1c0cfb87b5eb7a83cf7c76591561 562 // 23.2.5.1.3 InitializeTypedArrayFromArrayBuffer ( O, buffer, byteOffset, 563 // length ) Steps 2-3 and 5. 564 static bool byteOffsetAndLength(JSContext* cx, HandleValue byteOffsetValue, 565 HandleValue lengthValue, uint64_t* byteOffset, 566 uint64_t* length) { 567 // Steps 2-3. 568 *byteOffset = 0; 569 if (!byteOffsetValue.isUndefined()) { 570 // Step 2. 571 if (!ToIndex(cx, byteOffsetValue, byteOffset)) { 572 return false; 573 } 574 575 // Step 3. 576 if (*byteOffset % BYTES_PER_ELEMENT != 0) { 577 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 578 JSMSG_TYPED_ARRAY_CONSTRUCT_OFFSET_BOUNDS, 579 Scalar::name(ArrayTypeID()), 580 Scalar::byteSizeString(ArrayTypeID())); 581 return false; 582 } 583 } 584 585 // Step 5. 586 *length = UINT64_MAX; 587 if (!lengthValue.isUndefined()) { 588 if (!ToIndex(cx, lengthValue, length)) { 589 return false; 590 } 591 } 592 593 return true; 594 } 595 596 // ES2026 draft rev 6d71ca0e2dbf1c0cfb87b5eb7a83cf7c76591561 597 // 23.2.5.1.3 InitializeTypedArrayFromArrayBuffer ( O, buffer, byteOffset, 598 // length ) Steps 6-9. 599 static bool computeAndCheckLength( 600 JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> bufferMaybeUnwrapped, 601 uint64_t byteOffset, uint64_t lengthIndex, size_t* length, 602 AutoLength* autoLength) { 603 MOZ_ASSERT(byteOffset % BYTES_PER_ELEMENT == 0); 604 MOZ_ASSERT(byteOffset < uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT)); 605 MOZ_ASSERT_IF(lengthIndex != UINT64_MAX, 606 lengthIndex < uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT)); 607 608 // Step 6. 609 if (bufferMaybeUnwrapped->isDetached()) { 610 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 611 JSMSG_TYPED_ARRAY_DETACHED); 612 return false; 613 } 614 615 // Step 7. 616 size_t bufferByteLength = bufferMaybeUnwrapped->byteLength(); 617 MOZ_ASSERT(bufferByteLength <= ByteLengthLimit); 618 619 // Steps 8-9. 620 size_t len; 621 if (lengthIndex == UINT64_MAX) { 622 // Steps 8.a and 9.a.iii. 623 // 624 // Check if |byteOffset| valid. 625 if (byteOffset > bufferByteLength) { 626 JS_ReportErrorNumberASCII( 627 cx, GetErrorMessage, nullptr, 628 JSMSG_TYPED_ARRAY_CONSTRUCT_OFFSET_LENGTH_BOUNDS, 629 Scalar::name(ArrayTypeID())); 630 return false; 631 } 632 633 // Steps 8.b-c. 634 // 635 // Resizable buffers without an explicit length are auto-length. 636 if (bufferMaybeUnwrapped->isResizable()) { 637 *length = 0; 638 *autoLength = AutoLength::Yes; 639 return true; 640 } 641 642 // Step 9.a.i. 643 if (bufferByteLength % BYTES_PER_ELEMENT != 0) { 644 // The given byte array doesn't map exactly to 645 // |BYTES_PER_ELEMENT * N| 646 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 647 JSMSG_TYPED_ARRAY_CONSTRUCT_OFFSET_MISALIGNED, 648 Scalar::name(ArrayTypeID()), 649 Scalar::byteSizeString(ArrayTypeID())); 650 return false; 651 } 652 653 // Step 9.a.ii. 654 size_t newByteLength = bufferByteLength - size_t(byteOffset); 655 len = newByteLength / BYTES_PER_ELEMENT; 656 } else { 657 // Step 9.b.i. 658 uint64_t newByteLength = lengthIndex * BYTES_PER_ELEMENT; 659 660 // Step 9.b.ii. 661 if (byteOffset + newByteLength > bufferByteLength) { 662 // |byteOffset + newByteLength| is too big for the arraybuffer 663 JS_ReportErrorNumberASCII( 664 cx, GetErrorMessage, nullptr, 665 JSMSG_TYPED_ARRAY_CONSTRUCT_ARRAY_LENGTH_BOUNDS, 666 Scalar::name(ArrayTypeID())); 667 return false; 668 } 669 670 len = size_t(lengthIndex); 671 } 672 673 // Steps 9.c-d. 674 MOZ_ASSERT(len <= ByteLengthLimit / BYTES_PER_ELEMENT); 675 *length = len; 676 *autoLength = AutoLength::No; 677 return true; 678 } 679 680 // ES2026 draft rev 6d71ca0e2dbf1c0cfb87b5eb7a83cf7c76591561 681 // 23.2.5.1.3 InitializeTypedArrayFromArrayBuffer ( O, buffer, byteOffset, 682 // length ) Steps 6-12. 683 static TypedArrayObject* fromBufferSameCompartment( 684 JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer, 685 uint64_t byteOffset, uint64_t lengthIndex, HandleObject proto) { 686 // Steps 6-9. 687 size_t length = 0; 688 auto autoLength = AutoLength::No; 689 if (!computeAndCheckLength(cx, buffer, byteOffset, lengthIndex, &length, 690 &autoLength)) { 691 return nullptr; 692 } 693 694 // Steps 10-12. 695 if (buffer->isResizable()) { 696 return ResizableTypedArray::makeInstance(cx, buffer, byteOffset, length, 697 autoLength, proto); 698 } 699 if (buffer->isImmutable()) { 700 return ImmutableTypedArray::makeInstance(cx, buffer, byteOffset, length, 701 proto); 702 } 703 return FixedLengthTypedArray::makeInstance(cx, buffer, byteOffset, length, 704 proto); 705 } 706 707 // Create a TypedArray object in another compartment. 708 // 709 // ES6 supports creating a TypedArray in global A (using global A's 710 // TypedArray constructor) backed by an ArrayBuffer created in global B. 711 // 712 // Our TypedArrayObject implementation doesn't support a TypedArray in 713 // compartment A backed by an ArrayBuffer in compartment B. So in this 714 // case, we create the TypedArray in B (!) and return a cross-compartment 715 // wrapper. 716 // 717 // Extra twist: the spec says the new TypedArray's [[Prototype]] must be 718 // A's TypedArray.prototype. So even though we're creating the TypedArray 719 // in B, its [[Prototype]] must be (a cross-compartment wrapper for) the 720 // TypedArray.prototype in A. 721 static JSObject* fromBufferWrapped(JSContext* cx, HandleObject bufobj, 722 uint64_t byteOffset, uint64_t lengthIndex, 723 HandleObject proto) { 724 JSObject* unwrapped = CheckedUnwrapStatic(bufobj); 725 if (!unwrapped) { 726 ReportAccessDenied(cx); 727 return nullptr; 728 } 729 730 if (!unwrapped->is<ArrayBufferObjectMaybeShared>()) { 731 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 732 JSMSG_TYPED_ARRAY_BAD_ARGS); 733 return nullptr; 734 } 735 736 Rooted<ArrayBufferObjectMaybeShared*> unwrappedBuffer(cx); 737 unwrappedBuffer = &unwrapped->as<ArrayBufferObjectMaybeShared>(); 738 739 size_t length = 0; 740 auto autoLength = AutoLength::No; 741 if (!computeAndCheckLength(cx, unwrappedBuffer, byteOffset, lengthIndex, 742 &length, &autoLength)) { 743 return nullptr; 744 } 745 746 // Make sure to get the [[Prototype]] for the created typed array from 747 // this compartment. 748 RootedObject protoRoot(cx, proto); 749 if (!protoRoot) { 750 protoRoot = GlobalObject::getOrCreatePrototype(cx, protoKey()); 751 if (!protoRoot) { 752 return nullptr; 753 } 754 } 755 756 RootedObject typedArray(cx); 757 { 758 JSAutoRealm ar(cx, unwrappedBuffer); 759 760 RootedObject wrappedProto(cx, protoRoot); 761 if (!cx->compartment()->wrap(cx, &wrappedProto)) { 762 return nullptr; 763 } 764 765 if (unwrappedBuffer->isResizable()) { 766 typedArray = ResizableTypedArray::makeInstance( 767 cx, unwrappedBuffer, byteOffset, length, autoLength, wrappedProto); 768 } else if (unwrappedBuffer->isImmutable()) { 769 typedArray = ImmutableTypedArray::makeInstance( 770 cx, unwrappedBuffer, byteOffset, length, wrappedProto); 771 } else { 772 typedArray = FixedLengthTypedArray::makeInstance( 773 cx, unwrappedBuffer, byteOffset, length, wrappedProto); 774 } 775 if (!typedArray) { 776 return nullptr; 777 } 778 } 779 780 if (!cx->compartment()->wrap(cx, &typedArray)) { 781 return nullptr; 782 } 783 784 return typedArray; 785 } 786 787 public: 788 static JSObject* fromBuffer(JSContext* cx, HandleObject bufobj, 789 size_t byteOffset, int64_t lengthInt) { 790 if (byteOffset % BYTES_PER_ELEMENT != 0) { 791 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 792 JSMSG_TYPED_ARRAY_CONSTRUCT_OFFSET_BOUNDS, 793 Scalar::name(ArrayTypeID()), 794 Scalar::byteSizeString(ArrayTypeID())); 795 return nullptr; // invalid byteOffset 796 } 797 798 uint64_t lengthIndex = lengthInt >= 0 ? uint64_t(lengthInt) : UINT64_MAX; 799 if (bufobj->is<ArrayBufferObjectMaybeShared>()) { 800 auto buffer = bufobj.as<ArrayBufferObjectMaybeShared>(); 801 return fromBufferSameCompartment(cx, buffer, byteOffset, lengthIndex, 802 nullptr); 803 } 804 return fromBufferWrapped(cx, bufobj, byteOffset, lengthIndex, nullptr); 805 } 806 807 static TypedArrayObject* fromBuffer( 808 JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer, 809 size_t byteOffset) { 810 MOZ_ASSERT(byteOffset % BYTES_PER_ELEMENT == 0); 811 return fromBufferSameCompartment(cx, buffer, byteOffset, UINT64_MAX, 812 nullptr); 813 } 814 815 static TypedArrayObject* fromBuffer( 816 JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer, 817 size_t byteOffset, size_t length) { 818 MOZ_ASSERT(byteOffset % BYTES_PER_ELEMENT == 0); 819 return fromBufferSameCompartment(cx, buffer, byteOffset, length, nullptr); 820 } 821 822 static bool maybeCreateArrayBuffer(JSContext* cx, uint64_t count, 823 MutableHandle<ArrayBufferObject*> buffer) { 824 if (count > ByteLengthLimit / BYTES_PER_ELEMENT) { 825 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 826 JSMSG_BAD_ARRAY_LENGTH); 827 return false; 828 } 829 size_t byteLength = count * BYTES_PER_ELEMENT; 830 831 MOZ_ASSERT(byteLength <= ByteLengthLimit); 832 static_assert(INLINE_BUFFER_LIMIT % BYTES_PER_ELEMENT == 0, 833 "ArrayBuffer inline storage shouldn't waste any space"); 834 835 if (byteLength <= INLINE_BUFFER_LIMIT) { 836 // The array's data can be inline, and the buffer created lazily. 837 return true; 838 } 839 840 ArrayBufferObject* buf = ArrayBufferObject::createZeroed(cx, byteLength); 841 if (!buf) { 842 return false; 843 } 844 845 buffer.set(buf); 846 return true; 847 } 848 849 // ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78 850 // 23.2.5.1.1 AllocateTypedArray ( constructorName, newTarget, defaultProto [ 851 // , length ] ) 852 static TypedArrayObject* fromLength(JSContext* cx, uint64_t nelements, 853 HandleObject proto = nullptr, 854 gc::Heap heap = gc::Heap::Default) { 855 Rooted<ArrayBufferObject*> buffer(cx); 856 if (!maybeCreateArrayBuffer(cx, nelements, &buffer)) { 857 return nullptr; 858 } 859 860 return FixedLengthTypedArray::makeInstance(cx, buffer, 0, nelements, proto, 861 heap); 862 } 863 864 static TypedArrayObject* fromArray(JSContext* cx, HandleObject other, 865 HandleObject proto = nullptr); 866 867 static TypedArrayObject* fromTypedArray(JSContext* cx, HandleObject other, 868 bool isWrapped, HandleObject proto); 869 870 static TypedArrayObject* fromObject(JSContext* cx, HandleObject other, 871 HandleObject proto); 872 873 static const NativeType getIndex(TypedArrayObject* tarray, size_t index) { 874 MOZ_ASSERT(index < tarray->length().valueOr(0)); 875 return jit::AtomicOperations::loadSafeWhenRacy( 876 tarray->dataPointerEither().cast<NativeType*>() + index); 877 } 878 879 static void setIndex(TypedArrayObject& tarray, size_t index, NativeType val) { 880 MOZ_ASSERT(index < tarray.length().valueOr(0)); 881 jit::AtomicOperations::storeSafeWhenRacy( 882 tarray.dataPointerEither().cast<NativeType*>() + index, val); 883 } 884 885 static bool getElement(JSContext* cx, TypedArrayObject* tarray, size_t index, 886 MutableHandleValue val); 887 static bool getElementPure(TypedArrayObject* tarray, size_t index, Value* vp); 888 889 static bool setElement(JSContext* cx, Handle<TypedArrayObject*> obj, 890 uint64_t index, HandleValue v, ObjectOpResult& result); 891 }; 892 893 template <typename NativeType> 894 class FixedLengthTypedArrayObjectTemplate 895 : public FixedLengthTypedArrayObject, 896 public TypedArrayObjectTemplate<NativeType> { 897 friend class js::TypedArrayObject; 898 899 using TypedArrayTemplate = TypedArrayObjectTemplate<NativeType>; 900 901 public: 902 using TypedArrayTemplate::ArrayTypeID; 903 using TypedArrayTemplate::BYTES_PER_ELEMENT; 904 using TypedArrayTemplate::protoKey; 905 906 static inline const JSClass* instanceClass() { 907 static_assert(ArrayTypeID() < 908 std::size(TypedArrayObject::fixedLengthClasses)); 909 return &TypedArrayObject::fixedLengthClasses[ArrayTypeID()]; 910 } 911 912 static FixedLengthTypedArrayObject* newBuiltinClassInstance( 913 JSContext* cx, gc::AllocKind allocKind, gc::Heap heap) { 914 RootedObject proto(cx, GlobalObject::getOrCreatePrototype(cx, protoKey())); 915 if (!proto) { 916 return nullptr; 917 } 918 return NewTypedArrayObject<FixedLengthTypedArrayObject>( 919 cx, instanceClass(), proto, allocKind, heap); 920 } 921 922 static FixedLengthTypedArrayObject* makeProtoInstance( 923 JSContext* cx, HandleObject proto, gc::AllocKind allocKind) { 924 MOZ_ASSERT(proto); 925 return NewTypedArrayObject<FixedLengthTypedArrayObject>( 926 cx, instanceClass(), proto, allocKind, gc::Heap::Default); 927 } 928 929 static FixedLengthTypedArrayObject* makeInstance( 930 JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer, 931 size_t byteOffset, size_t len, HandleObject proto, 932 gc::Heap heap = gc::Heap::Default) { 933 MOZ_ASSERT(len <= ByteLengthLimit / BYTES_PER_ELEMENT); 934 935 gc::AllocKind allocKind = 936 buffer ? gc::GetGCObjectKind(instanceClass()) 937 : AllocKindForLazyBuffer(len * BYTES_PER_ELEMENT); 938 939 AutoSetNewObjectMetadata metadata(cx); 940 FixedLengthTypedArrayObject* obj; 941 if (proto) { 942 obj = makeProtoInstance(cx, proto, allocKind); 943 } else { 944 obj = newBuiltinClassInstance(cx, allocKind, heap); 945 } 946 if (!obj || !obj->init(cx, buffer, byteOffset, len, BYTES_PER_ELEMENT)) { 947 return nullptr; 948 } 949 950 return obj; 951 } 952 953 static FixedLengthTypedArrayObject* makeTemplateObject(JSContext* cx, 954 int32_t len) { 955 MOZ_ASSERT(len >= 0); 956 size_t nbytes; 957 MOZ_ALWAYS_TRUE(CalculateAllocSize<NativeType>(len, &nbytes)); 958 bool fitsInline = nbytes <= INLINE_BUFFER_LIMIT; 959 gc::AllocKind allocKind = !fitsInline ? gc::GetGCObjectKind(instanceClass()) 960 : AllocKindForLazyBuffer(nbytes); 961 MOZ_ASSERT(allocKind >= gc::GetGCObjectKind(instanceClass())); 962 963 AutoSetNewObjectMetadata metadata(cx); 964 965 auto* tarray = newBuiltinClassInstance(cx, allocKind, gc::Heap::Tenured); 966 if (!tarray) { 967 return nullptr; 968 } 969 970 initTypedArraySlots(tarray, len); 971 972 // Template objects don't need memory for their elements, since there 973 // won't be any elements to store. 974 MOZ_ASSERT(tarray->getReservedSlot(DATA_SLOT).isUndefined()); 975 976 return tarray; 977 } 978 979 static FixedLengthTypedArrayObject* fromDetachedBuffer( 980 JSContext* cx, Handle<ArrayBufferObject*> buffer, 981 gc::Heap heap = gc::Heap::Default) { 982 MOZ_ASSERT(buffer->isDetached()); 983 984 gc::AllocKind allocKind = gc::GetGCObjectKind(instanceClass()); 985 986 AutoSetNewObjectMetadata metadata(cx); 987 auto* obj = newBuiltinClassInstance(cx, allocKind, heap); 988 if (!obj) { 989 return nullptr; 990 } 991 992 // Normal construction doesn't allow creating a new TypedArray with an 993 // already detached ArrayBuffer. Initialize all slots as if a TypedArrray 994 // had been created with a non-detached buffer and the buffer was detached 995 // later. 996 obj->initFixedSlot(BUFFER_SLOT, ObjectValue(*buffer)); 997 obj->initFixedSlot(LENGTH_SLOT, PrivateValue(size_t(0))); 998 obj->initFixedSlot(BYTEOFFSET_SLOT, PrivateValue(size_t(0))); 999 obj->initFixedSlot(DATA_SLOT, UndefinedValue()); 1000 1001 return obj; 1002 } 1003 1004 static void initTypedArraySlots(FixedLengthTypedArrayObject* tarray, 1005 int32_t len) { 1006 MOZ_ASSERT(len >= 0); 1007 tarray->initFixedSlot(TypedArrayObject::BUFFER_SLOT, JS::FalseValue()); 1008 tarray->initFixedSlot(TypedArrayObject::LENGTH_SLOT, PrivateValue(len)); 1009 tarray->initFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT, 1010 PrivateValue(size_t(0))); 1011 1012 #ifdef DEBUG 1013 if (len == 0) { 1014 uint8_t* output = 1015 tarray->fixedData(FixedLengthTypedArrayObject::FIXED_DATA_START); 1016 output[0] = TypedArrayObject::ZeroLengthArrayData; 1017 } 1018 #endif 1019 } 1020 1021 static void initTypedArrayData(FixedLengthTypedArrayObject* tarray, void* buf, 1022 size_t nbytes, gc::AllocKind allocKind) { 1023 if (buf) { 1024 InitReservedSlot(tarray, TypedArrayObject::DATA_SLOT, buf, nbytes, 1025 MemoryUse::TypedArrayElements); 1026 } else { 1027 #ifdef DEBUG 1028 constexpr size_t dataOffset = ArrayBufferViewObject::dataOffset(); 1029 constexpr size_t offset = dataOffset + sizeof(HeapSlot); 1030 MOZ_ASSERT(offset + nbytes <= GetGCKindBytes(allocKind)); 1031 #endif 1032 1033 void* data = tarray->fixedData(FIXED_DATA_START); 1034 tarray->initReservedSlot(DATA_SLOT, PrivateValue(data)); 1035 memset(data, 0, nbytes); 1036 } 1037 } 1038 1039 static FixedLengthTypedArrayObject* makeTypedArrayWithTemplate( 1040 JSContext* cx, TypedArrayObject* templateObj, int32_t len) { 1041 if (len < 0 || size_t(len) > ByteLengthLimit / BYTES_PER_ELEMENT) { 1042 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1043 JSMSG_BAD_ARRAY_LENGTH); 1044 return nullptr; 1045 } 1046 1047 size_t nbytes = size_t(len) * BYTES_PER_ELEMENT; 1048 MOZ_ASSERT(nbytes <= ByteLengthLimit); 1049 1050 bool fitsInline = nbytes <= INLINE_BUFFER_LIMIT; 1051 1052 AutoSetNewObjectMetadata metadata(cx); 1053 1054 gc::AllocKind allocKind = !fitsInline ? gc::GetGCObjectKind(instanceClass()) 1055 : AllocKindForLazyBuffer(nbytes); 1056 MOZ_ASSERT(templateObj->getClass() == instanceClass()); 1057 1058 RootedObject proto(cx, templateObj->staticPrototype()); 1059 auto* obj = makeProtoInstance(cx, proto, allocKind); 1060 if (!obj) { 1061 return nullptr; 1062 } 1063 1064 initTypedArraySlots(obj, len); 1065 1066 void* buf = nullptr; 1067 if (!fitsInline) { 1068 MOZ_ASSERT(len > 0); 1069 1070 nbytes = RoundUp(nbytes, sizeof(Value)); 1071 buf = cx->nursery().allocateZeroedBuffer(obj, nbytes, 1072 js::ArrayBufferContentsArena); 1073 if (!buf) { 1074 ReportOutOfMemory(cx); 1075 return nullptr; 1076 } 1077 } 1078 1079 initTypedArrayData(obj, buf, nbytes, allocKind); 1080 1081 return obj; 1082 } 1083 }; 1084 1085 template <typename NativeType> 1086 class ResizableTypedArrayObjectTemplate 1087 : public ResizableTypedArrayObject, 1088 public TypedArrayObjectTemplate<NativeType> { 1089 friend class js::TypedArrayObject; 1090 1091 using TypedArrayTemplate = TypedArrayObjectTemplate<NativeType>; 1092 1093 public: 1094 using TypedArrayTemplate::ArrayTypeID; 1095 using TypedArrayTemplate::BYTES_PER_ELEMENT; 1096 using TypedArrayTemplate::protoKey; 1097 1098 static inline const JSClass* instanceClass() { 1099 static_assert(ArrayTypeID() < 1100 std::size(TypedArrayObject::resizableClasses)); 1101 return &TypedArrayObject::resizableClasses[ArrayTypeID()]; 1102 } 1103 1104 static ResizableTypedArrayObject* newBuiltinClassInstance( 1105 JSContext* cx, gc::AllocKind allocKind, gc::Heap heap) { 1106 RootedObject proto(cx, GlobalObject::getOrCreatePrototype(cx, protoKey())); 1107 if (!proto) { 1108 return nullptr; 1109 } 1110 return NewTypedArrayObject<ResizableTypedArrayObject>( 1111 cx, instanceClass(), proto, allocKind, heap); 1112 } 1113 1114 static ResizableTypedArrayObject* makeProtoInstance(JSContext* cx, 1115 HandleObject proto, 1116 gc::AllocKind allocKind) { 1117 MOZ_ASSERT(proto); 1118 return NewTypedArrayObject<ResizableTypedArrayObject>( 1119 cx, instanceClass(), proto, allocKind, gc::Heap::Default); 1120 } 1121 1122 static ResizableTypedArrayObject* makeInstance( 1123 JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer, 1124 size_t byteOffset, size_t len, AutoLength autoLength, 1125 HandleObject proto) { 1126 MOZ_ASSERT(buffer); 1127 MOZ_ASSERT(buffer->isResizable()); 1128 MOZ_ASSERT(!buffer->isDetached()); 1129 MOZ_ASSERT(autoLength == AutoLength::No || len == 0, 1130 "length is zero for 'auto' length views"); 1131 MOZ_ASSERT(len <= ByteLengthLimit / BYTES_PER_ELEMENT); 1132 1133 gc::AllocKind allocKind = gc::GetGCObjectKind(instanceClass()); 1134 1135 AutoSetNewObjectMetadata metadata(cx); 1136 ResizableTypedArrayObject* obj; 1137 if (proto) { 1138 obj = makeProtoInstance(cx, proto, allocKind); 1139 } else { 1140 obj = newBuiltinClassInstance(cx, allocKind, gc::Heap::Default); 1141 } 1142 if (!obj || !obj->initResizable(cx, buffer, byteOffset, len, 1143 BYTES_PER_ELEMENT, autoLength)) { 1144 return nullptr; 1145 } 1146 1147 return obj; 1148 } 1149 1150 static ResizableTypedArrayObject* makeTemplateObject(JSContext* cx) { 1151 gc::AllocKind allocKind = gc::GetGCObjectKind(instanceClass()); 1152 1153 AutoSetNewObjectMetadata metadata(cx); 1154 1155 auto* tarray = newBuiltinClassInstance(cx, allocKind, gc::Heap::Tenured); 1156 if (!tarray) { 1157 return nullptr; 1158 } 1159 1160 tarray->initFixedSlot(TypedArrayObject::BUFFER_SLOT, JS::FalseValue()); 1161 tarray->initFixedSlot(TypedArrayObject::LENGTH_SLOT, 1162 PrivateValue(size_t(0))); 1163 tarray->initFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT, 1164 PrivateValue(size_t(0))); 1165 tarray->initFixedSlot(AUTO_LENGTH_SLOT, BooleanValue(false)); 1166 tarray->initFixedSlot(ResizableTypedArrayObject::INITIAL_LENGTH_SLOT, 1167 PrivateValue(size_t(0))); 1168 tarray->initFixedSlot(ResizableTypedArrayObject::INITIAL_BYTE_OFFSET_SLOT, 1169 PrivateValue(size_t(0))); 1170 1171 // Template objects don't need memory for their elements, since there 1172 // won't be any elements to store. 1173 MOZ_ASSERT(tarray->getReservedSlot(DATA_SLOT).isUndefined()); 1174 1175 return tarray; 1176 } 1177 }; 1178 1179 template <typename NativeType> 1180 class ImmutableTypedArrayObjectTemplate 1181 : public ImmutableTypedArrayObject, 1182 public TypedArrayObjectTemplate<NativeType> { 1183 friend class js::TypedArrayObject; 1184 1185 using TypedArrayTemplate = TypedArrayObjectTemplate<NativeType>; 1186 1187 public: 1188 using TypedArrayTemplate::ArrayTypeID; 1189 using TypedArrayTemplate::BYTES_PER_ELEMENT; 1190 using TypedArrayTemplate::protoKey; 1191 1192 static inline const JSClass* instanceClass() { 1193 static_assert(ArrayTypeID() < 1194 std::size(TypedArrayObject::immutableClasses)); 1195 return &TypedArrayObject::immutableClasses[ArrayTypeID()]; 1196 } 1197 1198 static ImmutableTypedArrayObject* newBuiltinClassInstance( 1199 JSContext* cx, gc::AllocKind allocKind, gc::Heap heap) { 1200 RootedObject proto(cx, GlobalObject::getOrCreatePrototype(cx, protoKey())); 1201 if (!proto) { 1202 return nullptr; 1203 } 1204 return NewTypedArrayObject<ImmutableTypedArrayObject>( 1205 cx, instanceClass(), proto, allocKind, heap); 1206 } 1207 1208 static ImmutableTypedArrayObject* makeProtoInstance(JSContext* cx, 1209 HandleObject proto, 1210 gc::AllocKind allocKind) { 1211 MOZ_ASSERT(proto); 1212 return NewTypedArrayObject<ImmutableTypedArrayObject>( 1213 cx, instanceClass(), proto, allocKind, gc::Heap::Default); 1214 } 1215 1216 static ImmutableTypedArrayObject* makeInstance( 1217 JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer, 1218 size_t byteOffset, size_t len, HandleObject proto) { 1219 MOZ_ASSERT(buffer); 1220 MOZ_ASSERT(buffer->isImmutable()); 1221 MOZ_ASSERT(!buffer->isDetached()); 1222 MOZ_ASSERT(len <= ByteLengthLimit / BYTES_PER_ELEMENT); 1223 1224 gc::AllocKind allocKind = gc::GetGCObjectKind(instanceClass()); 1225 1226 AutoSetNewObjectMetadata metadata(cx); 1227 ImmutableTypedArrayObject* obj; 1228 if (proto) { 1229 obj = makeProtoInstance(cx, proto, allocKind); 1230 } else { 1231 obj = newBuiltinClassInstance(cx, allocKind, gc::Heap::Default); 1232 } 1233 if (!obj || !obj->init(cx, buffer, byteOffset, len, BYTES_PER_ELEMENT)) { 1234 return nullptr; 1235 } 1236 1237 return obj; 1238 } 1239 1240 static ImmutableTypedArrayObject* makeTemplateObject(JSContext* cx) { 1241 gc::AllocKind allocKind = gc::GetGCObjectKind(instanceClass()); 1242 1243 AutoSetNewObjectMetadata metadata(cx); 1244 1245 auto* tarray = newBuiltinClassInstance(cx, allocKind, gc::Heap::Tenured); 1246 if (!tarray) { 1247 return nullptr; 1248 } 1249 1250 tarray->initFixedSlot(TypedArrayObject::BUFFER_SLOT, JS::FalseValue()); 1251 tarray->initFixedSlot(TypedArrayObject::LENGTH_SLOT, 1252 PrivateValue(size_t(0))); 1253 tarray->initFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT, 1254 PrivateValue(size_t(0))); 1255 1256 // Template objects don't need memory for their elements, since there 1257 // won't be any elements to store. 1258 MOZ_ASSERT(tarray->getReservedSlot(DATA_SLOT).isUndefined()); 1259 1260 return tarray; 1261 } 1262 }; 1263 1264 template <typename NativeType> 1265 bool TypedArrayObjectTemplate<NativeType>::convertValue(JSContext* cx, 1266 HandleValue v, 1267 NativeType* result) { 1268 double d; 1269 if (!ToNumber(cx, v, &d)) { 1270 return false; 1271 } 1272 1273 if constexpr (!std::numeric_limits<NativeType>::is_integer) { 1274 if (js::SupportDifferentialTesting()) { 1275 // See the comment in ElementSpecific::doubleToNative. 1276 d = JS::CanonicalizeNaN(d); 1277 } 1278 } 1279 1280 // Assign based on characteristics of the destination type 1281 *result = ConvertNumber<NativeType>(d); 1282 return true; 1283 } 1284 1285 template <> 1286 bool TypedArrayObjectTemplate<int64_t>::convertValue(JSContext* cx, 1287 HandleValue v, 1288 int64_t* result) { 1289 JS_TRY_VAR_OR_RETURN_FALSE(cx, *result, ToBigInt64(cx, v)); 1290 return true; 1291 } 1292 1293 template <> 1294 bool TypedArrayObjectTemplate<uint64_t>::convertValue(JSContext* cx, 1295 HandleValue v, 1296 uint64_t* result) { 1297 JS_TRY_VAR_OR_RETURN_FALSE(cx, *result, ToBigUint64(cx, v)); 1298 return true; 1299 } 1300 1301 /** 1302 * 10.4.5.16 TypedArraySetElement ( O, index, value ) 1303 * 1304 * ES2025 draft rev ac21460fedf4b926520b06c9820bdbebad596a8b 1305 */ 1306 template <typename NativeType> 1307 /* static */ bool TypedArrayObjectTemplate<NativeType>::setElement( 1308 JSContext* cx, Handle<TypedArrayObject*> obj, uint64_t index, HandleValue v, 1309 ObjectOpResult& result) { 1310 MOZ_ASSERT(!obj->is<ImmutableTypedArrayObject>()); 1311 1312 // Steps 1-2. 1313 NativeType nativeValue; 1314 if (!convertValue(cx, v, &nativeValue)) { 1315 return false; 1316 } 1317 1318 // Step 3. 1319 if (index < obj->length().valueOr(0)) { 1320 MOZ_ASSERT(!obj->hasDetachedBuffer(), 1321 "detaching an array buffer sets the length to zero"); 1322 TypedArrayObjectTemplate<NativeType>::setIndex(*obj, index, nativeValue); 1323 } 1324 1325 // Step 4. 1326 return result.succeed(); 1327 } 1328 1329 } /* anonymous namespace */ 1330 1331 TypedArrayObject* js::NewTypedArrayWithTemplateAndLength( 1332 JSContext* cx, HandleObject templateObj, int32_t len) { 1333 MOZ_ASSERT(templateObj->is<TypedArrayObject>()); 1334 TypedArrayObject* tobj = &templateObj->as<TypedArrayObject>(); 1335 1336 switch (tobj->type()) { 1337 #define CREATE_TYPED_ARRAY(_, T, N) \ 1338 case Scalar::N: \ 1339 return FixedLengthTypedArrayObjectTemplate<T>::makeTypedArrayWithTemplate( \ 1340 cx, tobj, len); 1341 JS_FOR_EACH_TYPED_ARRAY(CREATE_TYPED_ARRAY) 1342 #undef CREATE_TYPED_ARRAY 1343 default: 1344 MOZ_CRASH("Unsupported TypedArray type"); 1345 } 1346 } 1347 1348 TypedArrayObject* js::NewTypedArrayWithTemplateAndArray( 1349 JSContext* cx, HandleObject templateObj, HandleObject array) { 1350 MOZ_ASSERT(templateObj->is<TypedArrayObject>()); 1351 TypedArrayObject* tobj = &templateObj->as<TypedArrayObject>(); 1352 1353 switch (tobj->type()) { 1354 #define CREATE_TYPED_ARRAY(_, T, N) \ 1355 case Scalar::N: \ 1356 return TypedArrayObjectTemplate<T>::makeTypedArrayWithTemplate(cx, tobj, \ 1357 array); 1358 JS_FOR_EACH_TYPED_ARRAY(CREATE_TYPED_ARRAY) 1359 #undef CREATE_TYPED_ARRAY 1360 default: 1361 MOZ_CRASH("Unsupported TypedArray type"); 1362 } 1363 } 1364 1365 TypedArrayObject* js::NewTypedArrayWithTemplateAndBuffer( 1366 JSContext* cx, HandleObject templateObj, HandleObject arrayBuffer, 1367 HandleValue byteOffset, HandleValue length) { 1368 MOZ_ASSERT(templateObj->is<TypedArrayObject>()); 1369 TypedArrayObject* tobj = &templateObj->as<TypedArrayObject>(); 1370 1371 switch (tobj->type()) { 1372 #define CREATE_TYPED_ARRAY(_, T, N) \ 1373 case Scalar::N: \ 1374 return TypedArrayObjectTemplate<T>::makeTypedArrayWithTemplate( \ 1375 cx, tobj, arrayBuffer, byteOffset, length); 1376 JS_FOR_EACH_TYPED_ARRAY(CREATE_TYPED_ARRAY) 1377 #undef CREATE_TYPED_ARRAY 1378 default: 1379 MOZ_CRASH("Unsupported TypedArray type"); 1380 } 1381 } 1382 1383 TypedArrayObject* js::NewUint8ArrayWithLength(JSContext* cx, int32_t len, 1384 gc::Heap heap) { 1385 return TypedArrayObjectTemplate<uint8_t>::fromLength(cx, len, nullptr, heap); 1386 } 1387 1388 template <typename T> 1389 /* static */ TypedArrayObject* TypedArrayObjectTemplate<T>::fromArray( 1390 JSContext* cx, HandleObject other, HandleObject proto /* = nullptr */) { 1391 // Allow nullptr proto for FriendAPI methods, which don't care about 1392 // subclassing. 1393 if (other->is<TypedArrayObject>()) { 1394 return fromTypedArray(cx, other, /* wrapped= */ false, proto); 1395 } 1396 1397 if (other->is<WrapperObject>() && 1398 UncheckedUnwrap(other)->is<TypedArrayObject>()) { 1399 return fromTypedArray(cx, other, /* wrapped= */ true, proto); 1400 } 1401 1402 return fromObject(cx, other, proto); 1403 } 1404 1405 // ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78 1406 // 23.2.5.1 TypedArray ( ...args ) 1407 // 23.2.5.1.2 InitializeTypedArrayFromTypedArray ( O, srcArray ) 1408 template <typename T> 1409 /* static */ TypedArrayObject* TypedArrayObjectTemplate<T>::fromTypedArray( 1410 JSContext* cx, HandleObject other, bool isWrapped, HandleObject proto) { 1411 MOZ_ASSERT_IF(!isWrapped, other->is<TypedArrayObject>()); 1412 MOZ_ASSERT_IF(isWrapped, other->is<WrapperObject>() && 1413 UncheckedUnwrap(other)->is<TypedArrayObject>()); 1414 1415 Rooted<TypedArrayObject*> srcArray(cx); 1416 if (!isWrapped) { 1417 srcArray = &other->as<TypedArrayObject>(); 1418 } else { 1419 srcArray = other->maybeUnwrapAs<TypedArrayObject>(); 1420 if (!srcArray) { 1421 ReportAccessDenied(cx); 1422 return nullptr; 1423 } 1424 } 1425 1426 // InitializeTypedArrayFromTypedArray, step 1. (Skipped) 1427 1428 // InitializeTypedArrayFromTypedArray, step 2. 1429 auto srcLength = srcArray->length(); 1430 if (!srcLength) { 1431 ReportOutOfBounds(cx, srcArray); 1432 return nullptr; 1433 } 1434 1435 // InitializeTypedArrayFromTypedArray, steps 3-7. (Skipped) 1436 1437 // InitializeTypedArrayFromTypedArray, step 8. 1438 size_t elementLength = *srcLength; 1439 1440 // InitializeTypedArrayFromTypedArray, step 9. (Skipped) 1441 1442 // InitializeTypedArrayFromTypedArray, step 10.a. (Partial) 1443 // InitializeTypedArrayFromTypedArray, step 11.a. 1444 Rooted<ArrayBufferObject*> buffer(cx); 1445 if (!maybeCreateArrayBuffer(cx, elementLength, &buffer)) { 1446 return nullptr; 1447 } 1448 1449 // InitializeTypedArrayFromTypedArray, step 11.b. 1450 if (Scalar::isBigIntType(ArrayTypeID()) != 1451 Scalar::isBigIntType(srcArray->type())) { 1452 JS_ReportErrorNumberASCII( 1453 cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_NOT_COMPATIBLE, 1454 srcArray->getClass()->name, 1455 TypedArrayObject::fixedLengthClasses[ArrayTypeID()].name); 1456 return nullptr; 1457 } 1458 1459 // Step 6.b.i. 1460 // InitializeTypedArrayFromTypedArray, steps 12-15. 1461 Rooted<TypedArrayObject*> obj(cx, FixedLengthTypedArray::makeInstance( 1462 cx, buffer, 0, elementLength, proto)); 1463 if (!obj) { 1464 return nullptr; 1465 } 1466 1467 MOZ_RELEASE_ASSERT(!srcArray->hasDetachedBuffer()); 1468 1469 // InitializeTypedArrayFromTypedArray, steps 10.a. (Remaining parts) 1470 // InitializeTypedArrayFromTypedArray, steps 11.c-f. 1471 MOZ_ASSERT(!obj->isSharedMemory()); 1472 if (srcArray->isSharedMemory()) { 1473 if (!ElementSpecific<T, SharedOps>::setFromTypedArray( 1474 obj, elementLength, srcArray, elementLength, 0)) { 1475 MOZ_ASSERT_UNREACHABLE( 1476 "setFromTypedArray can only fail for overlapping buffers"); 1477 return nullptr; 1478 } 1479 } else { 1480 if (!ElementSpecific<T, UnsharedOps>::setFromTypedArray( 1481 obj, elementLength, srcArray, elementLength, 0)) { 1482 MOZ_ASSERT_UNREACHABLE( 1483 "setFromTypedArray can only fail for overlapping buffers"); 1484 return nullptr; 1485 } 1486 } 1487 1488 // Step 6.b.v. 1489 return obj; 1490 } 1491 1492 // ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78 1493 // 23.2.5.1 TypedArray ( ...args ) 1494 // 23.2.5.1.4 InitializeTypedArrayFromList ( O, values ) 1495 // 23.2.5.1.5 InitializeTypedArrayFromArrayLike ( O, arrayLike ) 1496 template <typename T> 1497 /* static */ TypedArrayObject* TypedArrayObjectTemplate<T>::fromObject( 1498 JSContext* cx, HandleObject other, HandleObject proto) { 1499 // Steps 1-4 and 6.a (Already performed in caller). 1500 1501 // Steps 6.b.i (Allocation deferred until later). 1502 1503 // Steps 6.b.ii-iii. (Not applicable) 1504 1505 // Step 6.b.iv. 1506 1507 // Fast path when iterable is a packed array using the default iterator. 1508 if (IsArrayWithDefaultIterator<MustBePacked::Yes>(other, cx)) { 1509 // Steps 6.b.iv.2-3. (We don't need to call IterableToList for the fast 1510 // path). 1511 Handle<ArrayObject*> array = other.as<ArrayObject>(); 1512 1513 // InitializeTypedArrayFromList, step 1. 1514 size_t len = array->getDenseInitializedLength(); 1515 1516 // InitializeTypedArrayFromList, step 2. 1517 Rooted<ArrayBufferObject*> buffer(cx); 1518 if (!maybeCreateArrayBuffer(cx, len, &buffer)) { 1519 return nullptr; 1520 } 1521 1522 // Steps 6.b.i. 1523 Rooted<FixedLengthTypedArrayObject*> obj( 1524 cx, FixedLengthTypedArray::makeInstance(cx, buffer, 0, len, proto)); 1525 if (!obj) { 1526 return nullptr; 1527 } 1528 1529 // InitializeTypedArrayFromList, steps 3-4. 1530 MOZ_ASSERT(!obj->isSharedMemory()); 1531 if (!ElementSpecific<T, UnsharedOps>::initFromIterablePackedArray(cx, obj, 1532 array)) { 1533 return nullptr; 1534 } 1535 1536 // InitializeTypedArrayFromList, step 5. (The assertion isn't applicable for 1537 // the fast path). 1538 1539 // Step 6.b.v. 1540 return obj; 1541 } 1542 1543 // Step 6.b.iv.1 (Assertion; implicit in our implementation). 1544 1545 // Step 6.b.iv.2. 1546 RootedValue callee(cx); 1547 RootedId iteratorId(cx, PropertyKey::Symbol(cx->wellKnownSymbols().iterator)); 1548 if (!GetProperty(cx, other, other, iteratorId, &callee)) { 1549 return nullptr; 1550 } 1551 1552 // Steps 6.b.iv.3-4. 1553 RootedObject arrayLike(cx); 1554 if (!callee.isNullOrUndefined()) { 1555 // Throw if other[Symbol.iterator] isn't callable. 1556 if (!callee.isObject() || !callee.toObject().isCallable()) { 1557 RootedValue otherVal(cx, ObjectValue(*other)); 1558 UniqueChars bytes = 1559 DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, otherVal, nullptr); 1560 if (!bytes) { 1561 return nullptr; 1562 } 1563 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE, 1564 bytes.get()); 1565 return nullptr; 1566 } 1567 1568 FixedInvokeArgs<2> args2(cx); 1569 args2[0].setObject(*other); 1570 args2[1].set(callee); 1571 1572 // Step 6.b.iv.3.a. 1573 RootedValue rval(cx); 1574 if (!CallSelfHostedFunction(cx, cx->names().IterableToList, 1575 UndefinedHandleValue, args2, &rval)) { 1576 return nullptr; 1577 } 1578 1579 // Step 6.b.iv.3.b (Implemented below). 1580 arrayLike = &rval.toObject(); 1581 } else { 1582 // Step 4.a is an assertion: object is not an Iterator. Testing this is 1583 // literally the very last thing we did, so we don't assert here. 1584 1585 // Step 4.b (Implemented below). 1586 arrayLike = other; 1587 } 1588 1589 // We implement InitializeTypedArrayFromList in terms of 1590 // InitializeTypedArrayFromArrayLike. 1591 1592 // InitializeTypedArrayFromArrayLike, step 1. 1593 uint64_t len; 1594 if (!GetLengthProperty(cx, arrayLike, &len)) { 1595 return nullptr; 1596 } 1597 1598 // InitializeTypedArrayFromArrayLike, step 2. 1599 Rooted<ArrayBufferObject*> buffer(cx); 1600 if (!maybeCreateArrayBuffer(cx, len, &buffer)) { 1601 return nullptr; 1602 } 1603 1604 MOZ_ASSERT(len <= ByteLengthLimit / BYTES_PER_ELEMENT); 1605 1606 // Steps 6.b.i. 1607 Rooted<TypedArrayObject*> obj( 1608 cx, FixedLengthTypedArray::makeInstance(cx, buffer, 0, len, proto)); 1609 if (!obj) { 1610 return nullptr; 1611 } 1612 1613 // InitializeTypedArrayFromArrayLike, steps 3-4. 1614 MOZ_ASSERT(!obj->isSharedMemory()); 1615 if (!ElementSpecific<T, UnsharedOps>::setFromNonTypedArray(cx, obj, arrayLike, 1616 len)) { 1617 return nullptr; 1618 } 1619 1620 // Step 6.b.v. 1621 return obj; 1622 } 1623 1624 static bool TypedArrayConstructor(JSContext* cx, unsigned argc, Value* vp) { 1625 CallArgs args = CallArgsFromVp(argc, vp); 1626 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1627 JSMSG_TYPED_ARRAY_CALL_OR_CONSTRUCT, 1628 args.isConstructing() ? "construct" : "call"); 1629 return false; 1630 } 1631 1632 template <typename T> 1633 static bool GetTemplateObjectForLength(JSContext* cx, int32_t length, 1634 MutableHandle<TypedArrayObject*> res) { 1635 size_t len = size_t(std::max(length, 0)); 1636 1637 size_t nbytes; 1638 if (!js::CalculateAllocSize<T>(len, &nbytes) || 1639 nbytes > TypedArrayObject::ByteLengthLimit) { 1640 return true; 1641 } 1642 1643 res.set(FixedLengthTypedArrayObjectTemplate<T>::makeTemplateObject(cx, len)); 1644 return !!res; 1645 } 1646 1647 template <typename T> 1648 static TypedArrayObject* GetTemplateObjectForBuffer( 1649 JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer) { 1650 if (buffer->isResizable()) { 1651 return ResizableTypedArrayObjectTemplate<T>::makeTemplateObject(cx); 1652 } 1653 1654 if (buffer->isImmutable()) { 1655 return ImmutableTypedArrayObjectTemplate<T>::makeTemplateObject(cx); 1656 } 1657 1658 // We don't use the template's length in the object case, so we can create 1659 // the template typed array with an initial length of zero. 1660 uint32_t len = 0; 1661 1662 return FixedLengthTypedArrayObjectTemplate<T>::makeTemplateObject(cx, len); 1663 } 1664 1665 template <typename T> 1666 static TypedArrayObject* GetTemplateObjectForBufferView( 1667 JSContext* cx, Handle<TypedArrayObject*> bufferView) { 1668 if (bufferView->is<ResizableTypedArrayObject>()) { 1669 return ResizableTypedArrayObjectTemplate<T>::makeTemplateObject(cx); 1670 } 1671 1672 if (bufferView->is<ImmutableTypedArrayObject>()) { 1673 return ImmutableTypedArrayObjectTemplate<T>::makeTemplateObject(cx); 1674 } 1675 1676 // We don't use the template's length in the object case, so we can create 1677 // the template typed array with an initial length of zero. 1678 uint32_t len = 0; 1679 1680 return FixedLengthTypedArrayObjectTemplate<T>::makeTemplateObject(cx, len); 1681 } 1682 1683 template <typename T> 1684 static TypedArrayObject* GetTemplateObjectForArrayLike( 1685 JSContext* cx, Handle<JSObject*> arrayLike) { 1686 MOZ_ASSERT(!arrayLike->is<ArrayBufferObjectMaybeShared>(), 1687 "Use GetTemplateObjectForBuffer for array buffer objects"); 1688 MOZ_ASSERT(!IsWrapper(arrayLike), "Wrappers not supported"); 1689 1690 // We don't use the template's length in the object case, so we can create 1691 // the template typed array with an initial length of zero. 1692 uint32_t len = 0; 1693 1694 return FixedLengthTypedArrayObjectTemplate<T>::makeTemplateObject(cx, len); 1695 } 1696 1697 /* static */ bool TypedArrayObject::GetTemplateObjectForLength( 1698 JSContext* cx, Scalar::Type type, int32_t length, 1699 MutableHandle<TypedArrayObject*> res) { 1700 MOZ_ASSERT(!res); 1701 1702 switch (type) { 1703 #define CREATE_TYPED_ARRAY_TEMPLATE(_, T, N) \ 1704 case Scalar::N: \ 1705 return ::GetTemplateObjectForLength<T>(cx, length, res); 1706 JS_FOR_EACH_TYPED_ARRAY(CREATE_TYPED_ARRAY_TEMPLATE) 1707 #undef CREATE_TYPED_ARRAY_TEMPLATE 1708 default: 1709 MOZ_CRASH("Unsupported TypedArray type"); 1710 } 1711 } 1712 1713 /* static */ TypedArrayObject* TypedArrayObject::GetTemplateObjectForBuffer( 1714 JSContext* cx, Scalar::Type type, 1715 Handle<ArrayBufferObjectMaybeShared*> buffer) { 1716 switch (type) { 1717 #define CREATE_TYPED_ARRAY_TEMPLATE(_, T, N) \ 1718 case Scalar::N: \ 1719 return ::GetTemplateObjectForBuffer<T>(cx, buffer); 1720 JS_FOR_EACH_TYPED_ARRAY(CREATE_TYPED_ARRAY_TEMPLATE) 1721 #undef CREATE_TYPED_ARRAY_TEMPLATE 1722 default: 1723 MOZ_CRASH("Unsupported TypedArray type"); 1724 } 1725 } 1726 1727 /* static */ TypedArrayObject* TypedArrayObject::GetTemplateObjectForBufferView( 1728 JSContext* cx, Handle<TypedArrayObject*> bufferView) { 1729 switch (bufferView->type()) { 1730 #define CREATE_TYPED_ARRAY_TEMPLATE(_, T, N) \ 1731 case Scalar::N: \ 1732 return ::GetTemplateObjectForBufferView<T>(cx, bufferView); 1733 JS_FOR_EACH_TYPED_ARRAY(CREATE_TYPED_ARRAY_TEMPLATE) 1734 #undef CREATE_TYPED_ARRAY_TEMPLATE 1735 default: 1736 MOZ_CRASH("Unsupported TypedArray type"); 1737 } 1738 } 1739 1740 /* static */ TypedArrayObject* TypedArrayObject::GetTemplateObjectForArrayLike( 1741 JSContext* cx, Scalar::Type type, Handle<JSObject*> arrayLike) { 1742 // We don't support wrappers, because of the complicated interaction between 1743 // wrapped ArrayBuffers and TypedArrays, see |fromBufferWrapped()|. 1744 MOZ_ASSERT(!IsWrapper(arrayLike)); 1745 1746 switch (type) { 1747 #define CREATE_TYPED_ARRAY_TEMPLATE(_, T, N) \ 1748 case Scalar::N: \ 1749 return ::GetTemplateObjectForArrayLike<T>(cx, arrayLike); 1750 JS_FOR_EACH_TYPED_ARRAY(CREATE_TYPED_ARRAY_TEMPLATE) 1751 #undef CREATE_TYPED_ARRAY_TEMPLATE 1752 default: 1753 MOZ_CRASH("Unsupported TypedArray type"); 1754 } 1755 } 1756 1757 static bool LengthGetterImpl(JSContext* cx, const CallArgs& args) { 1758 auto* tarr = &args.thisv().toObject().as<TypedArrayObject>(); 1759 args.rval().setNumber(tarr->length().valueOr(0)); 1760 return true; 1761 } 1762 1763 static bool TypedArray_lengthGetter(JSContext* cx, unsigned argc, Value* vp) { 1764 CallArgs args = CallArgsFromVp(argc, vp); 1765 return CallNonGenericMethod<IsTypedArrayObject, LengthGetterImpl>(cx, args); 1766 } 1767 1768 static bool ByteOffsetGetterImpl(JSContext* cx, const CallArgs& args) { 1769 auto* tarr = &args.thisv().toObject().as<TypedArrayObject>(); 1770 args.rval().setNumber(tarr->byteOffset().valueOr(0)); 1771 return true; 1772 } 1773 1774 static bool TypedArray_byteOffsetGetter(JSContext* cx, unsigned argc, 1775 Value* vp) { 1776 CallArgs args = CallArgsFromVp(argc, vp); 1777 return CallNonGenericMethod<IsTypedArrayObject, ByteOffsetGetterImpl>(cx, 1778 args); 1779 } 1780 1781 static bool ByteLengthGetterImpl(JSContext* cx, const CallArgs& args) { 1782 auto* tarr = &args.thisv().toObject().as<TypedArrayObject>(); 1783 args.rval().setNumber(tarr->byteLength().valueOr(0)); 1784 return true; 1785 } 1786 1787 static bool TypedArray_byteLengthGetter(JSContext* cx, unsigned argc, 1788 Value* vp) { 1789 CallArgs args = CallArgsFromVp(argc, vp); 1790 return CallNonGenericMethod<IsTypedArrayObject, ByteLengthGetterImpl>(cx, 1791 args); 1792 } 1793 1794 static bool BufferGetterImpl(JSContext* cx, const CallArgs& args) { 1795 MOZ_ASSERT(IsTypedArrayObject(args.thisv())); 1796 Rooted<TypedArrayObject*> tarray( 1797 cx, &args.thisv().toObject().as<TypedArrayObject>()); 1798 if (!TypedArrayObject::ensureHasBuffer(cx, tarray)) { 1799 return false; 1800 } 1801 args.rval().set(tarray->bufferValue()); 1802 return true; 1803 } 1804 1805 static bool TypedArray_bufferGetter(JSContext* cx, unsigned argc, Value* vp) { 1806 CallArgs args = CallArgsFromVp(argc, vp); 1807 return CallNonGenericMethod<IsTypedArrayObject, BufferGetterImpl>(cx, args); 1808 } 1809 1810 // ES2019 draft rev fc9ecdcd74294d0ca3146d4b274e2a8e79565dc3 1811 // 22.2.3.32 get %TypedArray%.prototype [ @@toStringTag ] 1812 static bool TypedArray_toStringTagGetter(JSContext* cx, unsigned argc, 1813 Value* vp) { 1814 CallArgs args = CallArgsFromVp(argc, vp); 1815 1816 // Steps 1-2. 1817 if (!args.thisv().isObject()) { 1818 args.rval().setUndefined(); 1819 return true; 1820 } 1821 1822 JSObject* obj = CheckedUnwrapStatic(&args.thisv().toObject()); 1823 if (!obj) { 1824 ReportAccessDenied(cx); 1825 return false; 1826 } 1827 1828 // Step 3. 1829 if (!obj->is<TypedArrayObject>()) { 1830 args.rval().setUndefined(); 1831 return true; 1832 } 1833 1834 // Steps 4-6. 1835 JSProtoKey protoKey = StandardProtoKeyOrNull(obj); 1836 MOZ_ASSERT(protoKey); 1837 1838 args.rval().setString(ClassName(protoKey, cx)); 1839 return true; 1840 } 1841 1842 /* static */ const JSPropertySpec TypedArrayObject::protoAccessors[] = { 1843 JS_INLINABLE_PSG("length", TypedArray_lengthGetter, 0, TypedArrayLength), 1844 JS_PSG("buffer", TypedArray_bufferGetter, 0), 1845 JS_INLINABLE_PSG("byteLength", TypedArray_byteLengthGetter, 0, 1846 TypedArrayByteLength), 1847 JS_INLINABLE_PSG("byteOffset", TypedArray_byteOffsetGetter, 0, 1848 TypedArrayByteOffset), 1849 JS_SYM_GET(toStringTag, TypedArray_toStringTagGetter, 0), 1850 JS_PS_END, 1851 }; 1852 1853 template <typename T> 1854 static inline bool SetFromTypedArray(TypedArrayObject* target, 1855 size_t targetLength, 1856 TypedArrayObject* source, 1857 size_t sourceLength, size_t offset, 1858 size_t sourceOffset = 0) { 1859 // WARNING: |source| may be an unwrapped typed array from a different 1860 // compartment. Proceed with caution! 1861 1862 if (target->isSharedMemory() || source->isSharedMemory()) { 1863 return ElementSpecific<T, SharedOps>::setFromTypedArray( 1864 target, targetLength, source, sourceLength, offset, sourceOffset); 1865 } 1866 return ElementSpecific<T, UnsharedOps>::setFromTypedArray( 1867 target, targetLength, source, sourceLength, offset, sourceOffset); 1868 } 1869 1870 template <typename T> 1871 static inline bool SetFromNonTypedArray(JSContext* cx, 1872 Handle<TypedArrayObject*> target, 1873 HandleObject source, size_t len, 1874 size_t offset) { 1875 MOZ_ASSERT(!source->is<TypedArrayObject>(), "use SetFromTypedArray"); 1876 1877 if (target->isSharedMemory()) { 1878 return ElementSpecific<T, SharedOps>::setFromNonTypedArray( 1879 cx, target, source, len, offset); 1880 } 1881 return ElementSpecific<T, UnsharedOps>::setFromNonTypedArray( 1882 cx, target, source, len, offset); 1883 } 1884 1885 // ES2023 draft rev 22cc56ab08fcab92a865978c0aa5c6f1d8ce250f 1886 // 23.2.3.24.1 SetTypedArrayFromTypedArray ( target, targetOffset, source ) 1887 static bool SetTypedArrayFromTypedArray(JSContext* cx, 1888 Handle<TypedArrayObject*> target, 1889 double targetOffset, 1890 size_t targetLength, 1891 Handle<TypedArrayObject*> source) { 1892 // WARNING: |source| may be an unwrapped typed array from a different 1893 // compartment. Proceed with caution! 1894 1895 MOZ_ASSERT(targetOffset >= 0); 1896 1897 // Steps 1-3. (Performed in caller.) 1898 MOZ_ASSERT(!target->hasDetachedBuffer()); 1899 1900 // Steps 4-5. 1901 auto sourceLength = source->length(); 1902 if (!sourceLength) { 1903 ReportOutOfBounds(cx, source); 1904 return false; 1905 } 1906 1907 // Steps 13-14 (Split into two checks to provide better error messages). 1908 if (targetOffset > targetLength) { 1909 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX); 1910 return false; 1911 } 1912 1913 // Step 14 (Cont'd). 1914 size_t offset = size_t(targetOffset); 1915 if (*sourceLength > targetLength - offset) { 1916 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1917 JSMSG_SOURCE_ARRAY_TOO_LONG); 1918 return false; 1919 } 1920 1921 // Step 15. 1922 if (Scalar::isBigIntType(target->type()) != 1923 Scalar::isBigIntType(source->type())) { 1924 JS_ReportErrorNumberASCII( 1925 cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_NOT_COMPATIBLE, 1926 source->getClass()->name, target->getClass()->name); 1927 return false; 1928 } 1929 1930 // Steps 6-12, 16-24. 1931 switch (target->type()) { 1932 #define SET_FROM_TYPED_ARRAY(_, T, N) \ 1933 case Scalar::N: \ 1934 if (!SetFromTypedArray<T>(target, targetLength, source, *sourceLength, \ 1935 offset)) { \ 1936 ReportOutOfMemory(cx); \ 1937 return false; \ 1938 } \ 1939 break; 1940 JS_FOR_EACH_TYPED_ARRAY(SET_FROM_TYPED_ARRAY) 1941 #undef SET_FROM_TYPED_ARRAY 1942 default: 1943 MOZ_CRASH("Unsupported TypedArray type"); 1944 } 1945 1946 return true; 1947 } 1948 1949 // ES2023 draft rev 22cc56ab08fcab92a865978c0aa5c6f1d8ce250f 1950 // 23.2.3.24.1 SetTypedArrayFromArrayLike ( target, targetOffset, source ) 1951 static bool SetTypedArrayFromArrayLike(JSContext* cx, 1952 Handle<TypedArrayObject*> target, 1953 double targetOffset, size_t targetLength, 1954 HandleObject src) { 1955 MOZ_ASSERT(targetOffset >= 0); 1956 1957 // Steps 1-2. (Performed in caller.) 1958 MOZ_ASSERT(target->length().isSome()); 1959 1960 // Steps 3-4. (Performed in caller.) 1961 1962 // Step 5. 1963 uint64_t srcLength; 1964 if (!GetLengthProperty(cx, src, &srcLength)) { 1965 return false; 1966 } 1967 1968 // Steps 6-7 (Split into two checks to provide better error messages). 1969 if (targetOffset > targetLength) { 1970 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX); 1971 return false; 1972 } 1973 1974 // Step 7 (Cont'd). 1975 size_t offset = size_t(targetOffset); 1976 if (srcLength > targetLength - offset) { 1977 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1978 JSMSG_SOURCE_ARRAY_TOO_LONG); 1979 return false; 1980 } 1981 1982 MOZ_ASSERT(srcLength <= targetLength); 1983 1984 // Steps 8-9. 1985 if (srcLength > 0) { 1986 switch (target->type()) { 1987 #define SET_FROM_NON_TYPED_ARRAY(_, T, N) \ 1988 case Scalar::N: \ 1989 if (!SetFromNonTypedArray<T>(cx, target, src, srcLength, offset)) \ 1990 return false; \ 1991 break; 1992 JS_FOR_EACH_TYPED_ARRAY(SET_FROM_NON_TYPED_ARRAY) 1993 #undef SET_FROM_NON_TYPED_ARRAY 1994 default: 1995 MOZ_CRASH("Unsupported TypedArray type"); 1996 } 1997 } 1998 1999 // Step 10. 2000 return true; 2001 } 2002 2003 // ES2023 draft rev 22cc56ab08fcab92a865978c0aa5c6f1d8ce250f 2004 // 23.2.3.24 %TypedArray%.prototype.set ( source [ , offset ] ) 2005 // 23.2.3.24.1 SetTypedArrayFromTypedArray ( target, targetOffset, source ) 2006 // 23.2.3.24.2 SetTypedArrayFromArrayLike ( target, targetOffset, source ) 2007 static bool TypedArray_set(JSContext* cx, const CallArgs& args) { 2008 MOZ_ASSERT(IsTypedArrayObject(args.thisv())); 2009 2010 // Steps 1-3 (Validation performed as part of CallNonGenericMethod). 2011 Rooted<TypedArrayObject*> target( 2012 cx, &args.thisv().toObject().as<TypedArrayObject>()); 2013 2014 // Additional step from Immutable ArrayBuffer proposal. 2015 if (target->is<ImmutableTypedArrayObject>()) { 2016 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 2017 JSMSG_ARRAYBUFFER_IMMUTABLE); 2018 return false; 2019 } 2020 2021 // Steps 4-5. 2022 double targetOffset = 0; 2023 if (args.length() > 1) { 2024 // Step 4. 2025 if (!ToInteger(cx, args[1], &targetOffset)) { 2026 return false; 2027 } 2028 2029 // Step 5. 2030 if (targetOffset < 0) { 2031 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX); 2032 return false; 2033 } 2034 } 2035 2036 // 23.2.3.24.1, steps 1-2. 2037 // 23.2.3.24.2, steps 1-2. 2038 auto targetLength = target->length(); 2039 if (!targetLength) { 2040 ReportOutOfBounds(cx, target); 2041 return false; 2042 } 2043 2044 // 23.2.3.24.2, step 4. (23.2.3.24.1 only applies if args[0] is a typed 2045 // array, so it doesn't make a difference there to apply ToObject here.) 2046 RootedObject src(cx, ToObject(cx, args.get(0))); 2047 if (!src) { 2048 return false; 2049 } 2050 2051 Rooted<TypedArrayObject*> srcTypedArray(cx); 2052 { 2053 JSObject* obj = CheckedUnwrapStatic(src); 2054 if (!obj) { 2055 ReportAccessDenied(cx); 2056 return false; 2057 } 2058 2059 if (obj->is<TypedArrayObject>()) { 2060 srcTypedArray = &obj->as<TypedArrayObject>(); 2061 } 2062 } 2063 2064 // Steps 6-7. 2065 if (srcTypedArray) { 2066 if (!SetTypedArrayFromTypedArray(cx, target, targetOffset, *targetLength, 2067 srcTypedArray)) { 2068 return false; 2069 } 2070 } else { 2071 if (!SetTypedArrayFromArrayLike(cx, target, targetOffset, *targetLength, 2072 src)) { 2073 return false; 2074 } 2075 } 2076 2077 // Step 8. 2078 args.rval().setUndefined(); 2079 return true; 2080 } 2081 2082 static bool TypedArray_set(JSContext* cx, unsigned argc, Value* vp) { 2083 CallArgs args = CallArgsFromVp(argc, vp); 2084 return CallNonGenericMethod<IsTypedArrayObject, TypedArray_set>(cx, args); 2085 } 2086 2087 static bool TypedArraySet(TypedArrayObject* target, TypedArrayObject* source, 2088 intptr_t offset) { 2089 MOZ_ASSERT(offset >= 0); 2090 2091 size_t targetLength = target->length().valueOr(0); 2092 size_t sourceLength = source->length().valueOr(0); 2093 2094 switch (target->type()) { 2095 #define SET_FROM_TYPED_ARRAY(_, T, N) \ 2096 case Scalar::N: \ 2097 return SetFromTypedArray<T>(target, targetLength, source, sourceLength, \ 2098 size_t(offset)); 2099 JS_FOR_EACH_TYPED_ARRAY(SET_FROM_TYPED_ARRAY) 2100 #undef SET_FROM_TYPED_ARRAY 2101 default: 2102 break; 2103 } 2104 MOZ_CRASH("Unsupported TypedArray type"); 2105 } 2106 2107 bool js::TypedArraySet(JSContext* cx, TypedArrayObject* target, 2108 TypedArrayObject* source, intptr_t offset) { 2109 if (!::TypedArraySet(target, source, offset)) { 2110 ReportOutOfMemory(cx); 2111 return false; 2112 } 2113 return true; 2114 } 2115 2116 void js::TypedArraySetInfallible(TypedArrayObject* target, 2117 TypedArrayObject* source, intptr_t offset) { 2118 AutoUnsafeCallWithABI unsafe; 2119 2120 MOZ_ALWAYS_TRUE(::TypedArraySet(target, source, offset)); 2121 } 2122 2123 static bool TypedArraySetFromSubarray(TypedArrayObject* target, 2124 TypedArrayObject* source, intptr_t offset, 2125 intptr_t sourceOffset, 2126 intptr_t sourceLength) { 2127 MOZ_ASSERT(offset >= 0); 2128 MOZ_ASSERT(sourceOffset >= 0); 2129 MOZ_ASSERT(sourceLength >= 0); 2130 2131 size_t targetLength = target->length().valueOr(0); 2132 2133 switch (target->type()) { 2134 #define SET_FROM_TYPED_ARRAY(_, T, N) \ 2135 case Scalar::N: \ 2136 return SetFromTypedArray<T>(target, targetLength, source, \ 2137 size_t(sourceLength), size_t(offset), \ 2138 size_t(sourceOffset)); 2139 JS_FOR_EACH_TYPED_ARRAY(SET_FROM_TYPED_ARRAY) 2140 #undef SET_FROM_TYPED_ARRAY 2141 default: 2142 break; 2143 } 2144 MOZ_CRASH("Unsupported TypedArray type"); 2145 } 2146 2147 bool js::TypedArraySetFromSubarray(JSContext* cx, TypedArrayObject* target, 2148 TypedArrayObject* source, intptr_t offset, 2149 intptr_t sourceOffset, 2150 intptr_t sourceLength) { 2151 if (!::TypedArraySetFromSubarray(target, source, offset, sourceOffset, 2152 sourceLength)) { 2153 ReportOutOfMemory(cx); 2154 return false; 2155 } 2156 return true; 2157 } 2158 2159 void js::TypedArraySetFromSubarrayInfallible(TypedArrayObject* target, 2160 TypedArrayObject* source, 2161 intptr_t offset, 2162 intptr_t sourceOffset, 2163 intptr_t sourceLength) { 2164 AutoUnsafeCallWithABI unsafe; 2165 2166 MOZ_ALWAYS_TRUE(::TypedArraySetFromSubarray(target, source, offset, 2167 sourceOffset, sourceLength)); 2168 } 2169 2170 // ES2020 draft rev dc1e21c454bd316810be1c0e7af0131a2d7f38e9 2171 // 22.2.3.5 %TypedArray%.prototype.copyWithin ( target, start [ , end ] ) 2172 static bool TypedArray_copyWithin(JSContext* cx, const CallArgs& args) { 2173 MOZ_ASSERT(IsTypedArrayObject(args.thisv())); 2174 2175 // Steps 1-2. 2176 Rooted<TypedArrayObject*> tarray( 2177 cx, &args.thisv().toObject().as<TypedArrayObject>()); 2178 2179 auto arrayLength = tarray->length(); 2180 if (!arrayLength) { 2181 ReportOutOfBounds(cx, tarray); 2182 return false; 2183 } 2184 2185 // Additional step from Immutable ArrayBuffer proposal. 2186 if (tarray->is<ImmutableTypedArrayObject>()) { 2187 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 2188 JSMSG_ARRAYBUFFER_IMMUTABLE); 2189 return false; 2190 } 2191 2192 // Step 3. 2193 size_t len = *arrayLength; 2194 2195 // Steps 4-5. 2196 size_t to = 0; 2197 if (args.hasDefined(0)) { 2198 if (!ToIntegerIndex(cx, args[0], len, &to)) { 2199 return false; 2200 } 2201 } 2202 2203 // Steps 6-7. 2204 size_t from = 0; 2205 if (args.hasDefined(1)) { 2206 if (!ToIntegerIndex(cx, args[1], len, &from)) { 2207 return false; 2208 } 2209 } 2210 2211 // Steps 8-9. 2212 size_t final_ = len; 2213 if (args.hasDefined(2)) { 2214 if (!ToIntegerIndex(cx, args[2], len, &final_)) { 2215 return false; 2216 } 2217 } 2218 2219 // Step 10. 2220 MOZ_ASSERT(to <= len); 2221 size_t count; 2222 if (from <= final_) { 2223 count = std::min(final_ - from, len - to); 2224 } else { 2225 count = 0; 2226 } 2227 2228 // Step 11. 2229 // 2230 // Note that this copies elements effectively by memmove, *not* in 2231 // step 11's specified order. This is unobservable, even when the underlying 2232 // buffer is a SharedArrayBuffer instance, because the access is unordered and 2233 // therefore is allowed to have data races. 2234 2235 if (count == 0) { 2236 args.rval().setObject(*tarray); 2237 return true; 2238 } 2239 2240 // Reacquire the length because side-effects may have detached or resized the 2241 // array buffer. 2242 arrayLength = tarray->length(); 2243 if (!arrayLength) { 2244 ReportOutOfBounds(cx, tarray); 2245 return false; 2246 } 2247 2248 // Recompute the bounds if the current length is smaller. 2249 if (*arrayLength < len) { 2250 MOZ_ASSERT(to + count <= len); 2251 MOZ_ASSERT(from + count <= len); 2252 2253 len = *arrayLength; 2254 2255 // Don't copy any bytes if either index is no longer in-bounds. 2256 if (to >= len || from >= len) { 2257 args.rval().setObject(*tarray); 2258 return true; 2259 } 2260 2261 // Restrict |count| to not copy any bytes after the end of the array. 2262 count = std::min(count, std::min(len - to, len - from)); 2263 MOZ_ASSERT(count > 0); 2264 } 2265 2266 // Don't multiply by |tarray->bytesPerElement()| in case the compiler can't 2267 // strength-reduce multiplication by 1/2/4/8 into the equivalent shift. 2268 const size_t ElementShift = TypedArrayShift(tarray->type()); 2269 2270 MOZ_ASSERT((SIZE_MAX >> ElementShift) > to); 2271 size_t byteDest = to << ElementShift; 2272 2273 MOZ_ASSERT((SIZE_MAX >> ElementShift) > from); 2274 size_t byteSrc = from << ElementShift; 2275 2276 MOZ_ASSERT((SIZE_MAX >> ElementShift) >= count); 2277 size_t byteSize = count << ElementShift; 2278 2279 #ifdef DEBUG 2280 { 2281 size_t viewByteLength = len << ElementShift; 2282 MOZ_ASSERT(byteSize <= viewByteLength); 2283 MOZ_ASSERT(byteDest < viewByteLength); 2284 MOZ_ASSERT(byteSrc < viewByteLength); 2285 MOZ_ASSERT(byteDest <= viewByteLength - byteSize); 2286 MOZ_ASSERT(byteSrc <= viewByteLength - byteSize); 2287 } 2288 #endif 2289 2290 if (tarray->isSharedMemory()) { 2291 auto data = SharedOps::extract(tarray).cast<uint8_t*>(); 2292 SharedOps::memmove(data + byteDest, data + byteSrc, byteSize); 2293 } else { 2294 auto data = UnsharedOps::extract(tarray).cast<uint8_t*>(); 2295 UnsharedOps::memmove(data + byteDest, data + byteSrc, byteSize); 2296 } 2297 2298 args.rval().setObject(*tarray); 2299 return true; 2300 } 2301 2302 static bool TypedArray_copyWithin(JSContext* cx, unsigned argc, Value* vp) { 2303 AutoJSMethodProfilerEntry pseudoFrame(cx, "[TypedArray].prototype", 2304 "copyWithin"); 2305 CallArgs args = CallArgsFromVp(argc, vp); 2306 return CallNonGenericMethod<IsTypedArrayObject, TypedArray_copyWithin>(cx, 2307 args); 2308 } 2309 2310 template <typename ExternalType, typename NativeType> 2311 static bool TypedArrayJoinKernel(JSContext* cx, 2312 Handle<TypedArrayObject*> tarray, size_t len, 2313 Handle<JSLinearString*> sep, 2314 JSStringBuilder& sb) { 2315 // Steps 7-8. 2316 for (size_t k = 0; k < len; k++) { 2317 if (!CheckForInterrupt(cx)) { 2318 return false; 2319 } 2320 2321 // Step 8.a. 2322 if (k > 0 && sep->length() > 0 && !sb.append(sep)) { 2323 return false; 2324 } 2325 2326 // Step 8.b-c. 2327 auto element = TypedArrayObjectTemplate<NativeType>::getIndex(tarray, k); 2328 if constexpr (std::numeric_limits<NativeType>::is_integer) { 2329 // Plus one to include the largest number and plus one for the sign. 2330 constexpr size_t MaximumLength = 2331 std::numeric_limits<NativeType>::digits10 + 1 + 2332 std::numeric_limits<NativeType>::is_signed; 2333 2334 char str[MaximumLength] = {}; 2335 auto result = std::to_chars(str, std::end(str), 2336 static_cast<ExternalType>(element), 10); 2337 MOZ_ASSERT(result.ec == std::errc()); 2338 2339 size_t strlen = result.ptr - str; 2340 if (!sb.append(str, strlen)) { 2341 return false; 2342 } 2343 } else { 2344 ToCStringBuf cbuf; 2345 size_t strlen; 2346 char* str = NumberToCString(&cbuf, static_cast<double>(element), &strlen); 2347 if (!sb.append(str, strlen)) { 2348 return false; 2349 } 2350 } 2351 } 2352 return true; 2353 } 2354 2355 /** 2356 * %TypedArray%.prototype.join ( separator ) 2357 * 2358 * ES2025 draft rev c4042979a7cdd96b663ffcc43aeee90af8d7a576 2359 */ 2360 static bool TypedArray_join(JSContext* cx, const CallArgs& args) { 2361 MOZ_ASSERT(IsTypedArrayObject(args.thisv())); 2362 2363 // Steps 1-3. 2364 Rooted<TypedArrayObject*> tarray( 2365 cx, &args.thisv().toObject().as<TypedArrayObject>()); 2366 2367 auto arrayLength = tarray->length(); 2368 if (!arrayLength) { 2369 ReportOutOfBounds(cx, tarray); 2370 return false; 2371 } 2372 size_t len = *arrayLength; 2373 2374 // Steps 4-5. 2375 Rooted<JSLinearString*> sep(cx); 2376 if (args.hasDefined(0)) { 2377 JSString* s = ToString<CanGC>(cx, args[0]); 2378 if (!s) { 2379 return false; 2380 } 2381 2382 sep = s->ensureLinear(cx); 2383 if (!sep) { 2384 return false; 2385 } 2386 } else { 2387 sep = cx->names().comma_; 2388 } 2389 2390 // Steps 6-9 (When the length is zero, directly return the empty string). 2391 if (len == 0) { 2392 args.rval().setString(cx->emptyString()); 2393 return true; 2394 } 2395 2396 // Step 6. 2397 JSStringBuilder sb(cx); 2398 if (sep->hasTwoByteChars() && !sb.ensureTwoByteChars()) { 2399 return false; 2400 } 2401 2402 // Reacquire the length because side-effects may have detached or resized the 2403 // array buffer. 2404 size_t actualLength = std::min(len, tarray->length().valueOr(0)); 2405 2406 // The string representation of each element has at least one character. 2407 auto res = mozilla::CheckedInt<uint32_t>(actualLength); 2408 2409 // The separator will be added |length - 1| times, reserve space for that so 2410 // that we don't have to unnecessarily grow the buffer. 2411 size_t seplen = sep->length(); 2412 if (seplen > 0) { 2413 if (len > UINT32_MAX) { 2414 ReportAllocationOverflow(cx); 2415 return false; 2416 } 2417 res += mozilla::CheckedInt<uint32_t>(seplen) * (uint32_t(len) - 1); 2418 } 2419 if (!res.isValid()) { 2420 ReportAllocationOverflow(cx); 2421 return false; 2422 } 2423 if (!sb.reserve(res.value())) { 2424 return false; 2425 } 2426 2427 switch (tarray->type()) { 2428 #define TYPED_ARRAY_JOIN(ExternalType, NativeType, Name) \ 2429 case Scalar::Name: \ 2430 if (!TypedArrayJoinKernel<ExternalType, NativeType>( \ 2431 cx, tarray, actualLength, sep, sb)) { \ 2432 return false; \ 2433 } \ 2434 break; 2435 JS_FOR_EACH_TYPED_ARRAY(TYPED_ARRAY_JOIN) 2436 #undef TYPED_ARRAY_JOIN 2437 default: 2438 MOZ_CRASH("Unsupported TypedArray type"); 2439 } 2440 2441 for (size_t k = actualLength; k < len; k++) { 2442 if (!CheckForInterrupt(cx)) { 2443 return false; 2444 } 2445 2446 // Step 8.a. 2447 if (k > 0 && !sb.append(sep)) { 2448 return false; 2449 } 2450 2451 // Steps 8.b-c. (Not applicable) 2452 } 2453 2454 // Step 9. 2455 JSString* str = sb.finishString(); 2456 if (!str) { 2457 return false; 2458 } 2459 2460 args.rval().setString(str); 2461 return true; 2462 } 2463 2464 /** 2465 * %TypedArray%.prototype.join ( separator ) 2466 * 2467 * ES2025 draft rev c4042979a7cdd96b663ffcc43aeee90af8d7a576 2468 */ 2469 static bool TypedArray_join(JSContext* cx, unsigned argc, Value* vp) { 2470 AutoJSMethodProfilerEntry pseudoFrame(cx, "[TypedArray].prototype", "join"); 2471 CallArgs args = CallArgsFromVp(argc, vp); 2472 return CallNonGenericMethod<IsTypedArrayObject, TypedArray_join>(cx, args); 2473 } 2474 2475 template <typename Ops, typename NativeType> 2476 static int64_t TypedArrayIndexOfNaive(TypedArrayObject* tarray, size_t k, 2477 size_t len, NativeType searchElement) { 2478 MOZ_RELEASE_ASSERT(k < len); 2479 MOZ_RELEASE_ASSERT(len <= tarray->length().valueOr(0)); 2480 2481 SharedMem<NativeType*> data = 2482 Ops::extract(tarray).template cast<NativeType*>(); 2483 for (size_t i = k; i < len; i++) { 2484 NativeType element = Ops::load(data + i); 2485 if (element == searchElement) { 2486 return int64_t(i); 2487 } 2488 } 2489 return -1; 2490 } 2491 2492 template <typename NativeType> 2493 static int64_t TypedArrayIndexOfSIMD(TypedArrayObject* tarray, size_t k, 2494 size_t len, NativeType searchElement) { 2495 MOZ_RELEASE_ASSERT(k < len); 2496 MOZ_RELEASE_ASSERT(len <= tarray->length().valueOr(0)); 2497 2498 if constexpr (sizeof(NativeType) == 1) { 2499 auto* data = UnsharedOps::extract(tarray).cast<char*>().unwrapUnshared(); 2500 auto* ptr = mozilla::SIMD::memchr8( 2501 data + k, mozilla::BitwiseCast<char>(searchElement), len - k); 2502 if (!ptr) { 2503 return -1; 2504 } 2505 return int64_t(ptr - data); 2506 } else if constexpr (sizeof(NativeType) == 2) { 2507 auto* data = 2508 UnsharedOps::extract(tarray).cast<char16_t*>().unwrapUnshared(); 2509 auto* ptr = mozilla::SIMD::memchr16( 2510 data + k, mozilla::BitwiseCast<char16_t>(searchElement), len - k); 2511 if (!ptr) { 2512 return -1; 2513 } 2514 return int64_t(ptr - data); 2515 } else if constexpr (sizeof(NativeType) == 4) { 2516 auto* data = 2517 UnsharedOps::extract(tarray).cast<uint32_t*>().unwrapUnshared(); 2518 auto* ptr = mozilla::SIMD::memchr32( 2519 data + k, mozilla::BitwiseCast<uint32_t>(searchElement), len - k); 2520 if (!ptr) { 2521 return -1; 2522 } 2523 return int64_t(ptr - data); 2524 } else { 2525 static_assert(sizeof(NativeType) == 8); 2526 2527 auto* data = 2528 UnsharedOps::extract(tarray).cast<uint64_t*>().unwrapUnshared(); 2529 auto* ptr = mozilla::SIMD::memchr64( 2530 data + k, mozilla::BitwiseCast<uint64_t>(searchElement), len - k); 2531 if (!ptr) { 2532 return -1; 2533 } 2534 return int64_t(ptr - data); 2535 } 2536 } 2537 2538 template <typename ExternalType, typename NativeType> 2539 static typename std::enable_if_t<!std::numeric_limits<NativeType>::is_integer, 2540 int64_t> 2541 TypedArrayIndexOf(TypedArrayObject* tarray, size_t k, size_t len, 2542 const Value& searchElement) { 2543 if (!searchElement.isNumber()) { 2544 return -1; 2545 } 2546 2547 double d = searchElement.toNumber(); 2548 NativeType e = NativeType(d); 2549 2550 // Return early if the search element is not representable using |NativeType| 2551 // or if it's NaN. 2552 if (double(e) != d) { 2553 return -1; 2554 } 2555 MOZ_ASSERT(!std::isnan(d)); 2556 2557 if (tarray->isSharedMemory()) { 2558 return TypedArrayIndexOfNaive<SharedOps>(tarray, k, len, e); 2559 } 2560 if (e == NativeType(0)) { 2561 // Can't use bitwise comparison when searching for ±0. 2562 return TypedArrayIndexOfNaive<UnsharedOps>(tarray, k, len, e); 2563 } 2564 return TypedArrayIndexOfSIMD(tarray, k, len, e); 2565 } 2566 2567 template <typename ExternalType, typename NativeType> 2568 static typename std::enable_if_t<std::numeric_limits<NativeType>::is_integer && 2569 sizeof(NativeType) < 8, 2570 int64_t> 2571 TypedArrayIndexOf(TypedArrayObject* tarray, size_t k, size_t len, 2572 const Value& searchElement) { 2573 if (!searchElement.isNumber()) { 2574 return -1; 2575 } 2576 2577 int64_t d; 2578 if (searchElement.isInt32()) { 2579 d = searchElement.toInt32(); 2580 } else { 2581 if (!mozilla::NumberEqualsInt64(searchElement.toDouble(), &d)) { 2582 return -1; 2583 } 2584 } 2585 2586 // Ensure search element is representable using |ExternalType|, which implies 2587 // it can be represented using |NativeType|. 2588 mozilla::CheckedInt<ExternalType> checked{d}; 2589 if (!checked.isValid()) { 2590 return -1; 2591 } 2592 NativeType e = static_cast<NativeType>(checked.value()); 2593 2594 if (tarray->isSharedMemory()) { 2595 return TypedArrayIndexOfNaive<SharedOps>(tarray, k, len, e); 2596 } 2597 return TypedArrayIndexOfSIMD(tarray, k, len, e); 2598 } 2599 2600 template <typename ExternalType, typename NativeType> 2601 static typename std::enable_if_t<std::is_same_v<NativeType, int64_t>, int64_t> 2602 TypedArrayIndexOf(TypedArrayObject* tarray, size_t k, size_t len, 2603 const Value& searchElement) { 2604 if (!searchElement.isBigInt()) { 2605 return -1; 2606 } 2607 2608 int64_t e; 2609 if (!BigInt::isInt64(searchElement.toBigInt(), &e)) { 2610 return -1; 2611 } 2612 2613 if (tarray->isSharedMemory()) { 2614 return TypedArrayIndexOfNaive<SharedOps>(tarray, k, len, e); 2615 } 2616 return TypedArrayIndexOfSIMD(tarray, k, len, e); 2617 } 2618 2619 template <typename ExternalType, typename NativeType> 2620 static typename std::enable_if_t<std::is_same_v<NativeType, uint64_t>, int64_t> 2621 TypedArrayIndexOf(TypedArrayObject* tarray, size_t k, size_t len, 2622 const Value& searchElement) { 2623 if (!searchElement.isBigInt()) { 2624 return -1; 2625 } 2626 2627 uint64_t e; 2628 if (!BigInt::isUint64(searchElement.toBigInt(), &e)) { 2629 return -1; 2630 } 2631 2632 if (tarray->isSharedMemory()) { 2633 return TypedArrayIndexOfNaive<SharedOps>(tarray, k, len, e); 2634 } 2635 return TypedArrayIndexOfSIMD(tarray, k, len, e); 2636 } 2637 2638 /** 2639 * %TypedArray%.prototype.indexOf ( searchElement [ , fromIndex ] ) 2640 * 2641 * ES2025 draft rev c4042979a7cdd96b663ffcc43aeee90af8d7a576 2642 */ 2643 static bool TypedArray_indexOf(JSContext* cx, const CallArgs& args) { 2644 MOZ_ASSERT(IsTypedArrayObject(args.thisv())); 2645 2646 // Steps 1-3. 2647 Rooted<TypedArrayObject*> tarray( 2648 cx, &args.thisv().toObject().as<TypedArrayObject>()); 2649 2650 auto arrayLength = tarray->length(); 2651 if (!arrayLength) { 2652 ReportOutOfBounds(cx, tarray); 2653 return false; 2654 } 2655 size_t len = *arrayLength; 2656 2657 // Step 4. 2658 if (len == 0) { 2659 args.rval().setInt32(-1); 2660 return true; 2661 } 2662 2663 // Steps 5-10. 2664 size_t k = 0; 2665 if (args.hasDefined(1)) { 2666 // Steps 5-6. 2667 if (!ToIntegerIndex(cx, args[1], len, &k)) { 2668 return false; 2669 } 2670 2671 // Reacquire the length because side-effects may have detached or resized 2672 // the array buffer. 2673 len = std::min(len, tarray->length().valueOr(0)); 2674 2675 // Return early if |k| exceeds the current length. 2676 if (k >= len) { 2677 args.rval().setInt32(-1); 2678 return true; 2679 } 2680 } 2681 MOZ_ASSERT(k < len); 2682 2683 // Steps 11-12. 2684 int64_t result; 2685 switch (tarray->type()) { 2686 #define TYPED_ARRAY_INDEXOF(ExternalType, NativeType, Name) \ 2687 case Scalar::Name: \ 2688 result = TypedArrayIndexOf<ExternalType, NativeType>(tarray, k, len, \ 2689 args.get(0)); \ 2690 break; 2691 JS_FOR_EACH_TYPED_ARRAY(TYPED_ARRAY_INDEXOF) 2692 #undef TYPED_ARRAY_INDEXOF 2693 default: 2694 MOZ_CRASH("Unsupported TypedArray type"); 2695 } 2696 MOZ_ASSERT_IF(result >= 0, uint64_t(result) < len); 2697 MOZ_ASSERT_IF(result < 0, result == -1); 2698 2699 args.rval().setNumber(result); 2700 return true; 2701 } 2702 2703 /** 2704 * %TypedArray%.prototype.indexOf ( searchElement [ , fromIndex ] ) 2705 * 2706 * ES2025 draft rev c4042979a7cdd96b663ffcc43aeee90af8d7a576 2707 */ 2708 static bool TypedArray_indexOf(JSContext* cx, unsigned argc, Value* vp) { 2709 AutoJSMethodProfilerEntry pseudoFrame(cx, "[TypedArray].prototype", 2710 "indexOf"); 2711 CallArgs args = CallArgsFromVp(argc, vp); 2712 return CallNonGenericMethod<IsTypedArrayObject, TypedArray_indexOf>(cx, args); 2713 } 2714 2715 template <typename Ops, typename NativeType> 2716 static int64_t TypedArrayLastIndexOf(TypedArrayObject* tarray, size_t k, 2717 size_t len, NativeType searchElement) { 2718 MOZ_RELEASE_ASSERT(k < len); 2719 MOZ_RELEASE_ASSERT(len <= tarray->length().valueOr(0)); 2720 2721 SharedMem<NativeType*> data = 2722 Ops::extract(tarray).template cast<NativeType*>(); 2723 for (size_t i = k + 1; i > 0;) { 2724 NativeType element = Ops::load(data + --i); 2725 if (element == searchElement) { 2726 return int64_t(i); 2727 } 2728 } 2729 return -1; 2730 } 2731 2732 template <typename ExternalType, typename NativeType> 2733 static typename std::enable_if_t<!std::numeric_limits<NativeType>::is_integer, 2734 int64_t> 2735 TypedArrayLastIndexOf(TypedArrayObject* tarray, size_t k, size_t len, 2736 const Value& searchElement) { 2737 if (!searchElement.isNumber()) { 2738 return -1; 2739 } 2740 2741 double d = searchElement.toNumber(); 2742 NativeType e = NativeType(d); 2743 2744 // Return early if the search element is not representable using |NativeType| 2745 // or if it's NaN. 2746 if (double(e) != d) { 2747 return -1; 2748 } 2749 MOZ_ASSERT(!std::isnan(d)); 2750 2751 if (tarray->isSharedMemory()) { 2752 return TypedArrayLastIndexOf<SharedOps>(tarray, k, len, e); 2753 } 2754 return TypedArrayLastIndexOf<UnsharedOps>(tarray, k, len, e); 2755 } 2756 2757 template <typename ExternalType, typename NativeType> 2758 static typename std::enable_if_t<std::numeric_limits<NativeType>::is_integer && 2759 sizeof(NativeType) < 8, 2760 int64_t> 2761 TypedArrayLastIndexOf(TypedArrayObject* tarray, size_t k, size_t len, 2762 const Value& searchElement) { 2763 if (!searchElement.isNumber()) { 2764 return -1; 2765 } 2766 2767 int64_t d; 2768 if (searchElement.isInt32()) { 2769 d = searchElement.toInt32(); 2770 } else { 2771 if (!mozilla::NumberEqualsInt64(searchElement.toDouble(), &d)) { 2772 return -1; 2773 } 2774 } 2775 2776 // Ensure search element is representable using |ExternalType|, which implies 2777 // it can be represented using |NativeType|. 2778 mozilla::CheckedInt<ExternalType> checked{d}; 2779 if (!checked.isValid()) { 2780 return -1; 2781 } 2782 NativeType e = static_cast<NativeType>(checked.value()); 2783 2784 if (tarray->isSharedMemory()) { 2785 return TypedArrayLastIndexOf<SharedOps>(tarray, k, len, e); 2786 } 2787 return TypedArrayLastIndexOf<UnsharedOps>(tarray, k, len, e); 2788 } 2789 2790 template <typename ExternalType, typename NativeType> 2791 static typename std::enable_if_t<std::is_same_v<NativeType, int64_t>, int64_t> 2792 TypedArrayLastIndexOf(TypedArrayObject* tarray, size_t k, size_t len, 2793 const Value& searchElement) { 2794 if (!searchElement.isBigInt()) { 2795 return -1; 2796 } 2797 2798 int64_t e; 2799 if (!BigInt::isInt64(searchElement.toBigInt(), &e)) { 2800 return -1; 2801 } 2802 2803 if (tarray->isSharedMemory()) { 2804 return TypedArrayLastIndexOf<SharedOps>(tarray, k, len, e); 2805 } 2806 return TypedArrayLastIndexOf<UnsharedOps>(tarray, k, len, e); 2807 } 2808 2809 template <typename ExternalType, typename NativeType> 2810 static typename std::enable_if_t<std::is_same_v<NativeType, uint64_t>, int64_t> 2811 TypedArrayLastIndexOf(TypedArrayObject* tarray, size_t k, size_t len, 2812 const Value& searchElement) { 2813 if (!searchElement.isBigInt()) { 2814 return -1; 2815 } 2816 2817 uint64_t e; 2818 if (!BigInt::isUint64(searchElement.toBigInt(), &e)) { 2819 return -1; 2820 } 2821 2822 if (tarray->isSharedMemory()) { 2823 return TypedArrayLastIndexOf<SharedOps>(tarray, k, len, e); 2824 } 2825 return TypedArrayLastIndexOf<UnsharedOps>(tarray, k, len, e); 2826 } 2827 2828 /** 2829 * %TypedArray%.prototype.lastIndexOf ( searchElement [ , fromIndex ] ) 2830 * 2831 * ES2025 draft rev c4042979a7cdd96b663ffcc43aeee90af8d7a576 2832 */ 2833 static bool TypedArray_lastIndexOf(JSContext* cx, const CallArgs& args) { 2834 MOZ_ASSERT(IsTypedArrayObject(args.thisv())); 2835 2836 // Steps 1-3. 2837 Rooted<TypedArrayObject*> tarray( 2838 cx, &args.thisv().toObject().as<TypedArrayObject>()); 2839 2840 auto arrayLength = tarray->length(); 2841 if (!arrayLength) { 2842 ReportOutOfBounds(cx, tarray); 2843 return false; 2844 } 2845 size_t len = *arrayLength; 2846 2847 // Step 4. 2848 if (len == 0) { 2849 args.rval().setInt32(-1); 2850 return true; 2851 } 2852 2853 // Steps 5-8. 2854 size_t k = len - 1; 2855 if (args.length() > 1) { 2856 // Step 5. 2857 double fromIndex; 2858 if (!ToInteger(cx, args[1], &fromIndex)) { 2859 return false; 2860 } 2861 2862 // Steps 6-8. 2863 if (fromIndex >= 0) { 2864 k = size_t(std::min(fromIndex, double(len - 1))); 2865 } else { 2866 double d = double(len) + fromIndex; 2867 if (d < 0) { 2868 args.rval().setInt32(-1); 2869 return true; 2870 } 2871 k = size_t(d); 2872 } 2873 MOZ_ASSERT(k < len); 2874 2875 // Reacquire the length because side-effects may have detached or resized 2876 // the array buffer. 2877 size_t currentLength = tarray->length().valueOr(0); 2878 2879 // Restrict the search index and length if the new length is smaller. 2880 if (currentLength < len) { 2881 // Return early if the new length is zero. 2882 if (currentLength == 0) { 2883 args.rval().setInt32(-1); 2884 return true; 2885 } 2886 2887 // Otherwise just restrict |k| and |len| to the current length. 2888 k = std::min(k, currentLength - 1); 2889 len = currentLength; 2890 } 2891 } 2892 MOZ_ASSERT(k < len); 2893 2894 // Steps 9-10. 2895 int64_t result; 2896 switch (tarray->type()) { 2897 #define TYPED_ARRAY_LASTINDEXOF(ExternalType, NativeType, Name) \ 2898 case Scalar::Name: \ 2899 result = TypedArrayLastIndexOf<ExternalType, NativeType>(tarray, k, len, \ 2900 args.get(0)); \ 2901 break; 2902 JS_FOR_EACH_TYPED_ARRAY(TYPED_ARRAY_LASTINDEXOF) 2903 #undef TYPED_ARRAY_LASTINDEXOF 2904 default: 2905 MOZ_CRASH("Unsupported TypedArray type"); 2906 } 2907 MOZ_ASSERT_IF(result >= 0, uint64_t(result) < len); 2908 MOZ_ASSERT_IF(result < 0, result == -1); 2909 2910 args.rval().setNumber(result); 2911 return true; 2912 } 2913 2914 /** 2915 * %TypedArray%.prototype.lastIndexOf ( searchElement [ , fromIndex ] ) 2916 * 2917 * ES2025 draft rev c4042979a7cdd96b663ffcc43aeee90af8d7a576 2918 */ 2919 static bool TypedArray_lastIndexOf(JSContext* cx, unsigned argc, Value* vp) { 2920 AutoJSMethodProfilerEntry pseudoFrame(cx, "[TypedArray].prototype", 2921 "lastIndexOf"); 2922 CallArgs args = CallArgsFromVp(argc, vp); 2923 return CallNonGenericMethod<IsTypedArrayObject, TypedArray_lastIndexOf>(cx, 2924 args); 2925 } 2926 2927 template <typename T> 2928 static inline bool IsNaN(T num) { 2929 if constexpr (std::is_same_v<T, float16>) { 2930 return num != num; 2931 } else { 2932 // Static analysis complains when using self-comparison for built-in types, 2933 // so we have to use `std::isnan`. 2934 return std::isnan(num); 2935 } 2936 } 2937 2938 template <typename Ops, typename NativeType> 2939 static int64_t TypedArrayIncludesNaN(TypedArrayObject* tarray, size_t k, 2940 size_t len) { 2941 MOZ_RELEASE_ASSERT(k < len); 2942 MOZ_RELEASE_ASSERT(len <= tarray->length().valueOr(0)); 2943 2944 SharedMem<NativeType*> data = 2945 Ops::extract(tarray).template cast<NativeType*>(); 2946 for (size_t i = k; i < len; i++) { 2947 NativeType element = Ops::load(data + i); 2948 if (IsNaN(element)) { 2949 return int64_t(i); 2950 } 2951 } 2952 return -1; 2953 } 2954 2955 template <typename ExternalType, typename NativeType> 2956 static typename std::enable_if_t<!std::numeric_limits<NativeType>::is_integer, 2957 int64_t> 2958 TypedArrayIncludes(TypedArrayObject* tarray, size_t k, size_t len, 2959 const Value& searchElement) { 2960 if (searchElement.isDouble() && std::isnan(searchElement.toDouble())) { 2961 if (tarray->isSharedMemory()) { 2962 return TypedArrayIncludesNaN<SharedOps, NativeType>(tarray, k, len); 2963 } 2964 return TypedArrayIncludesNaN<UnsharedOps, NativeType>(tarray, k, len); 2965 } 2966 2967 // Delegate to TypedArrayIndexOf if not NaN. 2968 return TypedArrayIndexOf<ExternalType, NativeType>(tarray, k, len, 2969 searchElement); 2970 } 2971 2972 template <typename ExternalType, typename NativeType> 2973 static typename std::enable_if_t<std::numeric_limits<NativeType>::is_integer, 2974 int64_t> 2975 TypedArrayIncludes(TypedArrayObject* tarray, size_t k, size_t len, 2976 const Value& searchElement) { 2977 // Delegate to TypedArrayIndexOf for integer types. 2978 return TypedArrayIndexOf<ExternalType, NativeType>(tarray, k, len, 2979 searchElement); 2980 } 2981 2982 /** 2983 * %TypedArray%.prototype.includes ( searchElement [ , fromIndex ] ) 2984 * 2985 * ES2025 draft rev c4042979a7cdd96b663ffcc43aeee90af8d7a576 2986 */ 2987 static bool TypedArray_includes(JSContext* cx, const CallArgs& args) { 2988 MOZ_ASSERT(IsTypedArrayObject(args.thisv())); 2989 2990 // Steps 1-3. 2991 Rooted<TypedArrayObject*> tarray( 2992 cx, &args.thisv().toObject().as<TypedArrayObject>()); 2993 2994 auto arrayLength = tarray->length(); 2995 if (!arrayLength) { 2996 ReportOutOfBounds(cx, tarray); 2997 return false; 2998 } 2999 size_t len = *arrayLength; 3000 3001 // Step 4. 3002 if (len == 0) { 3003 args.rval().setBoolean(false); 3004 return true; 3005 } 3006 3007 // Steps 5-10. 3008 size_t k = 0; 3009 if (args.hasDefined(1)) { 3010 if (!ToIntegerIndex(cx, args[1], len, &k)) { 3011 return false; 3012 } 3013 3014 // Reacquire the length because side-effects may have detached or resized 3015 // the array buffer. 3016 size_t currentLength = tarray->length().valueOr(0); 3017 3018 // Contrary to `indexOf`, `includes` doesn't perform `HasProperty`, so we 3019 // have to handle the case when the current length is smaller than the 3020 // original length. 3021 if (currentLength < len) { 3022 // Accessing an element beyond the typed array length returns `undefined`, 3023 // so return `true` iff the search element is `undefined`. 3024 if (k < len && args[0].isUndefined()) { 3025 args.rval().setBoolean(true); 3026 return true; 3027 } 3028 3029 // Otherwise just restrict |len| to the current length. 3030 len = currentLength; 3031 } 3032 3033 // Return early if |k| exceeds the current length. 3034 if (k >= len) { 3035 args.rval().setBoolean(false); 3036 return true; 3037 } 3038 } 3039 MOZ_ASSERT(k < len); 3040 3041 // Steps 11-12. 3042 int64_t result; 3043 switch (tarray->type()) { 3044 #define TYPED_ARRAY_INCLUDES(ExternalType, NativeType, Name) \ 3045 case Scalar::Name: \ 3046 result = TypedArrayIncludes<ExternalType, NativeType>(tarray, k, len, \ 3047 args.get(0)); \ 3048 break; 3049 JS_FOR_EACH_TYPED_ARRAY(TYPED_ARRAY_INCLUDES) 3050 #undef TYPED_ARRAY_INCLUDES 3051 default: 3052 MOZ_CRASH("Unsupported TypedArray type"); 3053 } 3054 MOZ_ASSERT_IF(result >= 0, uint64_t(result) < len); 3055 MOZ_ASSERT_IF(result < 0, result == -1); 3056 3057 args.rval().setBoolean(result >= 0); 3058 return true; 3059 } 3060 3061 /** 3062 * %TypedArray%.prototype.includes ( searchElement [ , fromIndex ] ) 3063 * 3064 * ES2025 draft rev c4042979a7cdd96b663ffcc43aeee90af8d7a576 3065 */ 3066 static bool TypedArray_includes(JSContext* cx, unsigned argc, Value* vp) { 3067 AutoJSMethodProfilerEntry pseudoFrame(cx, "[TypedArray].prototype", 3068 "includes"); 3069 CallArgs args = CallArgsFromVp(argc, vp); 3070 return CallNonGenericMethod<IsTypedArrayObject, TypedArray_includes>(cx, 3071 args); 3072 } 3073 3074 template <typename Ops, typename NativeType> 3075 static void TypedArrayFillLoop(TypedArrayObject* tarray, NativeType value, 3076 size_t startIndex, size_t endIndex) { 3077 MOZ_RELEASE_ASSERT(startIndex <= endIndex); 3078 MOZ_RELEASE_ASSERT(endIndex <= tarray->length().valueOr(0)); 3079 3080 SharedMem<NativeType*> data = 3081 Ops::extract(tarray).template cast<NativeType*>(); 3082 for (size_t i = startIndex; i < endIndex; i++) { 3083 Ops::store(data + i, value); 3084 } 3085 } 3086 3087 template <typename NativeType> 3088 static void TypedArrayFillStdMemset(TypedArrayObject* tarray, uint8_t value, 3089 size_t startIndex, size_t endIndex) { 3090 MOZ_RELEASE_ASSERT(startIndex <= endIndex); 3091 MOZ_RELEASE_ASSERT(endIndex <= tarray->length().valueOr(0)); 3092 3093 SharedMem<uint8_t*> data = UnsharedOps::extract(tarray).cast<uint8_t*>(); 3094 std::memset(data.unwrapUnshared() + startIndex * sizeof(NativeType), value, 3095 (endIndex - startIndex) * sizeof(NativeType)); 3096 } 3097 3098 template <typename NativeType> 3099 static void TypedArrayFillAtomicMemset(TypedArrayObject* tarray, uint8_t value, 3100 size_t startIndex, size_t endIndex) { 3101 MOZ_RELEASE_ASSERT(startIndex <= endIndex); 3102 MOZ_RELEASE_ASSERT(endIndex <= tarray->length().valueOr(0)); 3103 3104 SharedMem<uint8_t*> data = SharedOps::extract(tarray).cast<uint8_t*>(); 3105 jit::AtomicOperations::memsetSafeWhenRacy( 3106 data + startIndex * sizeof(NativeType), value, 3107 (endIndex - startIndex) * sizeof(NativeType)); 3108 } 3109 3110 template <typename NativeType> 3111 static NativeType ConvertToNativeType(const Value& value) { 3112 if constexpr (!std::numeric_limits<NativeType>::is_integer) { 3113 double d = value.toNumber(); 3114 3115 if (js::SupportDifferentialTesting()) { 3116 // See the comment in ElementSpecific::doubleToNative. 3117 d = JS::CanonicalizeNaN(d); 3118 } 3119 3120 return ConvertNumber<NativeType>(d); 3121 } else if constexpr (std::is_same_v<NativeType, int64_t>) { 3122 return BigInt::toInt64(value.toBigInt()); 3123 } else if constexpr (std::is_same_v<NativeType, uint64_t>) { 3124 return BigInt::toUint64(value.toBigInt()); 3125 } else { 3126 return ConvertNumber<NativeType>(value.toNumber()); 3127 } 3128 } 3129 3130 template <typename NativeType> 3131 static void TypedArrayFill(TypedArrayObject* tarray, NativeType val, 3132 size_t startIndex, size_t endIndex) { 3133 using UnsignedT = 3134 typename mozilla::UnsignedStdintTypeForSize<sizeof(NativeType)>::Type; 3135 UnsignedT bits = mozilla::BitwiseCast<UnsignedT>(val); 3136 3137 // Duplicate the LSB to check if we can call memset. 3138 UnsignedT pattern; 3139 std::memset(&pattern, uint8_t(bits), sizeof(UnsignedT)); 3140 3141 if (tarray->isSharedMemory()) { 3142 // To prevent teared writes, only use memset on shared memory when copying 3143 // single bytes. 3144 if (bits == pattern && sizeof(NativeType) == 1) { 3145 TypedArrayFillAtomicMemset<NativeType>(tarray, uint8_t(bits), startIndex, 3146 endIndex); 3147 } else { 3148 TypedArrayFillLoop<SharedOps>(tarray, val, startIndex, endIndex); 3149 } 3150 } else { 3151 if (bits == pattern) { 3152 TypedArrayFillStdMemset<NativeType>(tarray, uint8_t(bits), startIndex, 3153 endIndex); 3154 } else { 3155 TypedArrayFillLoop<UnsharedOps>(tarray, val, startIndex, endIndex); 3156 } 3157 } 3158 } 3159 3160 template <typename NativeType> 3161 static void TypedArrayFill(TypedArrayObject* tarray, const Value& value, 3162 size_t startIndex, size_t endIndex) { 3163 NativeType val = ConvertToNativeType<NativeType>(value); 3164 TypedArrayFill(tarray, val, startIndex, endIndex); 3165 } 3166 3167 /** 3168 * %TypedArray%.prototype.fill ( value [ , start [ , end ] ] ) 3169 * 3170 * ES2025 draft rev c4042979a7cdd96b663ffcc43aeee90af8d7a576 3171 */ 3172 static bool TypedArray_fill(JSContext* cx, const CallArgs& args) { 3173 MOZ_ASSERT(IsTypedArrayObject(args.thisv())); 3174 3175 // Steps 1-2. 3176 Rooted<TypedArrayObject*> tarray( 3177 cx, &args.thisv().toObject().as<TypedArrayObject>()); 3178 3179 auto arrayLength = tarray->length(); 3180 if (!arrayLength) { 3181 ReportOutOfBounds(cx, tarray); 3182 return false; 3183 } 3184 3185 // Additional step from Immutable ArrayBuffer proposal. 3186 if (tarray->is<ImmutableTypedArrayObject>()) { 3187 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3188 JSMSG_ARRAYBUFFER_IMMUTABLE); 3189 return false; 3190 } 3191 3192 // Step 3 3193 size_t len = *arrayLength; 3194 3195 // Steps 4-5. 3196 Rooted<Value> value(cx); 3197 if (!tarray->convertValue(cx, args.get(0), &value)) { 3198 return false; 3199 } 3200 3201 // Steps 6-9 3202 size_t startIndex = 0; 3203 if (args.hasDefined(1)) { 3204 if (!ToIntegerIndex(cx, args[1], len, &startIndex)) { 3205 return false; 3206 } 3207 } 3208 3209 // Steps 10-13. 3210 size_t endIndex = len; 3211 if (args.hasDefined(2)) { 3212 if (!ToIntegerIndex(cx, args[2], len, &endIndex)) { 3213 return false; 3214 } 3215 } 3216 3217 // Steps 14-16. 3218 // 3219 // Reacquire the length because side-effects may have detached or resized 3220 // the array buffer. 3221 arrayLength = tarray->length(); 3222 if (!arrayLength) { 3223 ReportOutOfBounds(cx, tarray); 3224 return false; 3225 } 3226 len = *arrayLength; 3227 3228 // Step 17. 3229 endIndex = std::min(endIndex, len); 3230 3231 // Steps 18-19. 3232 if (startIndex < endIndex) { 3233 switch (tarray->type()) { 3234 #define TYPED_ARRAY_FILL(_, NativeType, Name) \ 3235 case Scalar::Name: \ 3236 TypedArrayFill<NativeType>(tarray, value.get(), startIndex, endIndex); \ 3237 break; 3238 JS_FOR_EACH_TYPED_ARRAY(TYPED_ARRAY_FILL) 3239 #undef TYPED_ARRAY_FILL 3240 default: 3241 MOZ_CRASH("Unsupported TypedArray type"); 3242 } 3243 } 3244 3245 // Step 20. 3246 args.rval().setObject(*tarray); 3247 return true; 3248 } 3249 3250 /** 3251 * %TypedArray%.prototype.fill ( value [ , start [ , end ] ] ) 3252 * 3253 * ES2025 draft rev c4042979a7cdd96b663ffcc43aeee90af8d7a576 3254 */ 3255 static bool TypedArray_fill(JSContext* cx, unsigned argc, Value* vp) { 3256 AutoJSMethodProfilerEntry pseudoFrame(cx, "[TypedArray].prototype", "fill"); 3257 CallArgs args = CallArgsFromVp(argc, vp); 3258 return CallNonGenericMethod<IsTypedArrayObject, TypedArray_fill>(cx, args); 3259 } 3260 3261 // Test if `ConvertNumber<To, From>` can be instantiated. 3262 // 3263 // For example `ConvertNumber<int64_t, double>` can't be instantiated. This is 3264 // checked through static assertions in `ConvertNumber`. 3265 // 3266 // As a further optimization also avoid generating unreachable code, like for 3267 // example `ConvertNumber<double, float>`. 3268 template <typename To, typename From> 3269 static constexpr bool IsValidForConvertNumber() { 3270 if constexpr (!std::numeric_limits<From>::is_integer) { 3271 return !std::numeric_limits<To>::is_integer && sizeof(From) >= sizeof(To); 3272 } else if constexpr (sizeof(From) == sizeof(int64_t)) { 3273 return std::numeric_limits<To>::is_integer && sizeof(From) == sizeof(To); 3274 } else { 3275 return std::numeric_limits<To>::is_integer && sizeof(From) >= sizeof(To); 3276 } 3277 } 3278 3279 template <typename T> 3280 static void TypedArrayFillFromJit(TypedArrayObject* obj, T fillValue, 3281 intptr_t start, intptr_t end) { 3282 if constexpr (!std::numeric_limits<T>::is_integer) { 3283 MOZ_ASSERT(Scalar::isFloatingType(obj->type())); 3284 } else if constexpr (std::is_same_v<T, int64_t>) { 3285 MOZ_ASSERT(Scalar::isBigIntType(obj->type())); 3286 } else { 3287 static_assert(std::is_same_v<T, int32_t>); 3288 MOZ_ASSERT(!Scalar::isFloatingType(obj->type())); 3289 MOZ_ASSERT(!Scalar::isBigIntType(obj->type())); 3290 } 3291 MOZ_ASSERT(!obj->hasDetachedBuffer()); 3292 MOZ_ASSERT(!obj->is<ImmutableTypedArrayObject>()); 3293 MOZ_ASSERT(!obj->is<ResizableTypedArrayObject>()); 3294 3295 size_t length = obj->length().valueOr(0); 3296 size_t startIndex = ToIntegerIndex(start, length); 3297 size_t endIndex = ToIntegerIndex(end, length); 3298 3299 // Return early if the fill range is empty. 3300 if (startIndex >= endIndex) { 3301 return; 3302 } 3303 3304 switch (obj->type()) { 3305 #define TYPED_ARRAY_FILL(_, NativeType, Name) \ 3306 case Scalar::Name: \ 3307 if constexpr (IsValidForConvertNumber<NativeType, T>()) { \ 3308 TypedArrayFill<NativeType>(obj, ConvertNumber<NativeType>(fillValue), \ 3309 startIndex, endIndex); \ 3310 return; \ 3311 } \ 3312 break; 3313 JS_FOR_EACH_TYPED_ARRAY(TYPED_ARRAY_FILL) 3314 #undef TYPED_ARRAY_FILL 3315 default: 3316 MOZ_CRASH("Unsupported TypedArray type"); 3317 } 3318 MOZ_CRASH("Unexpected invalid number conversion"); 3319 } 3320 3321 void js::TypedArrayFillInt32(TypedArrayObject* obj, int32_t fillValue, 3322 intptr_t start, intptr_t end) { 3323 AutoUnsafeCallWithABI unsafe; 3324 TypedArrayFillFromJit(obj, fillValue, start, end); 3325 } 3326 3327 void js::TypedArrayFillDouble(TypedArrayObject* obj, double fillValue, 3328 intptr_t start, intptr_t end) { 3329 AutoUnsafeCallWithABI unsafe; 3330 TypedArrayFillFromJit(obj, fillValue, start, end); 3331 } 3332 3333 void js::TypedArrayFillFloat32(TypedArrayObject* obj, float fillValue, 3334 intptr_t start, intptr_t end) { 3335 AutoUnsafeCallWithABI unsafe; 3336 TypedArrayFillFromJit(obj, fillValue, start, end); 3337 } 3338 3339 void js::TypedArrayFillInt64(TypedArrayObject* obj, int64_t fillValue, 3340 intptr_t start, intptr_t end) { 3341 AutoUnsafeCallWithABI unsafe; 3342 TypedArrayFillFromJit(obj, fillValue, start, end); 3343 } 3344 3345 void js::TypedArrayFillBigInt(TypedArrayObject* obj, BigInt* fillValue, 3346 intptr_t start, intptr_t end) { 3347 AutoUnsafeCallWithABI unsafe; 3348 TypedArrayFillFromJit(obj, BigInt::toInt64(fillValue), start, end); 3349 } 3350 3351 template <typename Ops, typename NativeType> 3352 static void TypedArrayReverse(TypedArrayObject* tarray, size_t len) { 3353 MOZ_RELEASE_ASSERT(len > 0); 3354 MOZ_RELEASE_ASSERT(len <= tarray->length().valueOr(0)); 3355 3356 SharedMem<NativeType*> lower = 3357 Ops::extract(tarray).template cast<NativeType*>(); 3358 SharedMem<NativeType*> upper = lower + (len - 1); 3359 for (; lower < upper; lower++, upper--) { 3360 NativeType lowerValue = Ops::load(lower); 3361 NativeType upperValue = Ops::load(upper); 3362 3363 Ops::store(lower, upperValue); 3364 Ops::store(upper, lowerValue); 3365 } 3366 } 3367 3368 template <typename NativeType> 3369 static void TypedArrayReverse(TypedArrayObject* tarray, size_t len) { 3370 if (tarray->isSharedMemory()) { 3371 TypedArrayReverse<SharedOps, NativeType>(tarray, len); 3372 } else { 3373 TypedArrayReverse<UnsharedOps, NativeType>(tarray, len); 3374 } 3375 } 3376 3377 /** 3378 * %TypedArray%.prototype.reverse ( ) 3379 * 3380 * ES2025 draft rev c4042979a7cdd96b663ffcc43aeee90af8d7a576 3381 */ 3382 static bool TypedArray_reverse(JSContext* cx, const CallArgs& args) { 3383 MOZ_ASSERT(IsTypedArrayObject(args.thisv())); 3384 3385 // Steps 1-2. 3386 Rooted<TypedArrayObject*> tarray( 3387 cx, &args.thisv().toObject().as<TypedArrayObject>()); 3388 3389 auto arrayLength = tarray->length(); 3390 if (!arrayLength) { 3391 ReportOutOfBounds(cx, tarray); 3392 return false; 3393 } 3394 3395 // Additional step from Immutable ArrayBuffer proposal. 3396 if (tarray->is<ImmutableTypedArrayObject>()) { 3397 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3398 JSMSG_ARRAYBUFFER_IMMUTABLE); 3399 return false; 3400 } 3401 3402 // Step 3. 3403 size_t len = *arrayLength; 3404 3405 // Steps 4-6. 3406 if (len > 1) { 3407 switch (tarray->type()) { 3408 #define TYPED_ARRAY_REVERSE(_, NativeType, Name) \ 3409 case Scalar::Name: \ 3410 TypedArrayReverse<NativeType>(tarray, len); \ 3411 break; 3412 JS_FOR_EACH_TYPED_ARRAY(TYPED_ARRAY_REVERSE) 3413 #undef TYPED_ARRAY_REVERSE 3414 default: 3415 MOZ_CRASH("Unsupported TypedArray type"); 3416 } 3417 } 3418 3419 // Step 7. 3420 args.rval().setObject(*tarray); 3421 return true; 3422 } 3423 3424 /** 3425 * %TypedArray%.prototype.reverse ( ) 3426 * 3427 * ES2025 draft rev c4042979a7cdd96b663ffcc43aeee90af8d7a576 3428 */ 3429 static bool TypedArray_reverse(JSContext* cx, unsigned argc, Value* vp) { 3430 AutoJSMethodProfilerEntry pseudoFrame(cx, "[TypedArray].prototype", 3431 "reverse"); 3432 CallArgs args = CallArgsFromVp(argc, vp); 3433 return CallNonGenericMethod<IsTypedArrayObject, TypedArray_reverse>(cx, args); 3434 } 3435 3436 /** 3437 * TypedArrayCreateSameType ( exemplar, argumentList ) 3438 * 3439 * ES2025 draft rev c4042979a7cdd96b663ffcc43aeee90af8d7a576 3440 */ 3441 static TypedArrayObject* TypedArrayCreateSameType( 3442 JSContext* cx, Handle<TypedArrayObject*> exemplar, size_t length) { 3443 switch (exemplar->type()) { 3444 #define TYPED_ARRAY_CREATE(_, NativeType, Name) \ 3445 case Scalar::Name: \ 3446 return TypedArrayObjectTemplate<NativeType>::fromLength(cx, length); 3447 JS_FOR_EACH_TYPED_ARRAY(TYPED_ARRAY_CREATE) 3448 #undef TYPED_ARRAY_CREATE 3449 default: 3450 MOZ_CRASH("Unsupported TypedArray type"); 3451 } 3452 } 3453 3454 /** 3455 * TypedArrayCreateSameType ( exemplar, argumentList ) 3456 * 3457 * ES2025 draft rev c4042979a7cdd96b663ffcc43aeee90af8d7a576 3458 */ 3459 static TypedArrayObject* TypedArrayCreateSameType( 3460 JSContext* cx, Handle<TypedArrayObject*> exemplar, 3461 Handle<ArrayBufferObjectMaybeShared*> buffer, size_t byteOffset) { 3462 switch (exemplar->type()) { 3463 #define TYPED_ARRAY_CREATE(_, NativeType, Name) \ 3464 case Scalar::Name: \ 3465 return TypedArrayObjectTemplate<NativeType>::fromBuffer(cx, buffer, \ 3466 byteOffset); 3467 JS_FOR_EACH_TYPED_ARRAY(TYPED_ARRAY_CREATE) 3468 #undef TYPED_ARRAY_CREATE 3469 default: 3470 MOZ_CRASH("Unsupported TypedArray type"); 3471 } 3472 } 3473 3474 /** 3475 * TypedArrayCreateSameType ( exemplar, argumentList ) 3476 * 3477 * ES2025 draft rev c4042979a7cdd96b663ffcc43aeee90af8d7a576 3478 */ 3479 static TypedArrayObject* TypedArrayCreateSameType( 3480 JSContext* cx, Handle<TypedArrayObject*> exemplar, 3481 Handle<ArrayBufferObjectMaybeShared*> buffer, size_t byteOffset, 3482 size_t length) { 3483 switch (exemplar->type()) { 3484 #define TYPED_ARRAY_CREATE(_, NativeType, Name) \ 3485 case Scalar::Name: \ 3486 return TypedArrayObjectTemplate<NativeType>::fromBuffer( \ 3487 cx, buffer, byteOffset, length); 3488 JS_FOR_EACH_TYPED_ARRAY(TYPED_ARRAY_CREATE) 3489 #undef TYPED_ARRAY_CREATE 3490 default: 3491 MOZ_CRASH("Unsupported TypedArray type"); 3492 } 3493 } 3494 3495 template <typename NativeType> 3496 static void TypedArrayCopyElements(TypedArrayObject* source, 3497 TypedArrayObject* target, size_t length) { 3498 MOZ_ASSERT(source->type() == target->type()); 3499 MOZ_ASSERT(!target->isSharedMemory()); 3500 MOZ_ASSERT(length > 0); 3501 MOZ_RELEASE_ASSERT(length <= source->length().valueOr(0)); 3502 MOZ_RELEASE_ASSERT(length <= target->length().valueOr(0)); 3503 3504 auto dest = UnsharedOps::extract(target).cast<NativeType*>(); 3505 if (source->isSharedMemory()) { 3506 auto src = SharedOps::extract(source).cast<NativeType*>(); 3507 SharedOps::podCopy(dest, src, length); 3508 } else { 3509 auto src = UnsharedOps::extract(source).cast<NativeType*>(); 3510 UnsharedOps::podCopy(dest, src, length); 3511 } 3512 } 3513 3514 static void TypedArrayCopyElements(TypedArrayObject* source, 3515 TypedArrayObject* target, size_t length) { 3516 switch (source->type()) { 3517 #define TYPED_ARRAY_COPY_ELEMENTS(_, NativeType, Name) \ 3518 case Scalar::Name: \ 3519 return TypedArrayCopyElements<NativeType>(source, target, length); 3520 JS_FOR_EACH_TYPED_ARRAY(TYPED_ARRAY_COPY_ELEMENTS) 3521 #undef TYPED_ARRAY_COPY_ELEMENTS 3522 default: 3523 MOZ_CRASH("Unsupported TypedArray type"); 3524 } 3525 } 3526 3527 /** 3528 * %TypedArray%.prototype.toReversed ( ) 3529 * 3530 * ES2025 draft rev c4042979a7cdd96b663ffcc43aeee90af8d7a576 3531 */ 3532 static bool TypedArray_toReversed(JSContext* cx, const CallArgs& args) { 3533 MOZ_ASSERT(IsTypedArrayObject(args.thisv())); 3534 3535 // Steps 1-3. 3536 Rooted<TypedArrayObject*> tarray( 3537 cx, &args.thisv().toObject().as<TypedArrayObject>()); 3538 3539 auto arrayLength = tarray->length(); 3540 if (!arrayLength) { 3541 ReportOutOfBounds(cx, tarray); 3542 return false; 3543 } 3544 size_t length = *arrayLength; 3545 3546 // Step 4. 3547 TypedArrayObject* result = TypedArrayCreateSameType(cx, tarray, length); 3548 if (!result) { 3549 return false; 3550 } 3551 3552 // Steps 5-6. 3553 if (length > 0) { 3554 TypedArrayCopyElements(tarray, result, length); 3555 3556 switch (result->type()) { 3557 #define TYPED_ARRAY_TOREVERSED(_, NativeType, Name) \ 3558 case Scalar::Name: \ 3559 TypedArrayReverse<UnsharedOps, NativeType>(result, length); \ 3560 break; 3561 JS_FOR_EACH_TYPED_ARRAY(TYPED_ARRAY_TOREVERSED) 3562 #undef TYPED_ARRAY_TOREVERSED 3563 default: 3564 MOZ_CRASH("Unsupported TypedArray type"); 3565 } 3566 } 3567 3568 // Step 7. 3569 args.rval().setObject(*result); 3570 return true; 3571 } 3572 3573 /** 3574 * %TypedArray%.prototype.toReversed ( ) 3575 * 3576 * ES2025 draft rev c4042979a7cdd96b663ffcc43aeee90af8d7a576 3577 */ 3578 static bool TypedArray_toReversed(JSContext* cx, unsigned argc, Value* vp) { 3579 AutoJSMethodProfilerEntry pseudoFrame(cx, "[TypedArray].prototype", 3580 "toReversed"); 3581 CallArgs args = CallArgsFromVp(argc, vp); 3582 return CallNonGenericMethod<IsTypedArrayObject, TypedArray_toReversed>(cx, 3583 args); 3584 } 3585 3586 template <typename Ops, typename NativeType> 3587 static void TypedArraySetElement(TypedArrayObject* tarray, size_t index, 3588 const Value& value) { 3589 MOZ_RELEASE_ASSERT(index < tarray->length().valueOr(0)); 3590 3591 NativeType val = ConvertToNativeType<NativeType>(value); 3592 3593 SharedMem<NativeType*> data = 3594 Ops::extract(tarray).template cast<NativeType*>(); 3595 Ops::store(data + index, val); 3596 } 3597 3598 /** 3599 * %TypedArray%.prototype.with ( index, value ) 3600 * 3601 * ES2025 draft rev c4042979a7cdd96b663ffcc43aeee90af8d7a576 3602 */ 3603 static bool TypedArray_with(JSContext* cx, const CallArgs& args) { 3604 MOZ_ASSERT(IsTypedArrayObject(args.thisv())); 3605 3606 // Steps 1-3. 3607 Rooted<TypedArrayObject*> tarray( 3608 cx, &args.thisv().toObject().as<TypedArrayObject>()); 3609 3610 auto arrayLength = tarray->length(); 3611 if (!arrayLength) { 3612 ReportOutOfBounds(cx, tarray); 3613 return false; 3614 } 3615 size_t len = *arrayLength; 3616 3617 // Step 4. 3618 double relativeIndex; 3619 if (!ToInteger(cx, args.get(0), &relativeIndex)) { 3620 return false; 3621 } 3622 3623 // Steps 5-6. 3624 double actualIndex; 3625 if (relativeIndex >= 0) { 3626 actualIndex = relativeIndex; 3627 } else { 3628 actualIndex = double(len) + relativeIndex; 3629 } 3630 3631 // Steps 7-8. 3632 Rooted<Value> value(cx); 3633 if (!tarray->convertValue(cx, args.get(1), &value)) { 3634 return false; 3635 } 3636 3637 // Reacquire the length because side-effects may have detached or resized 3638 // the array buffer. 3639 size_t currentLength = tarray->length().valueOr(0); 3640 3641 // Step 9. (Inlined IsValidIntegerIndex) 3642 if (actualIndex < 0 || actualIndex >= double(currentLength)) { 3643 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX); 3644 return false; 3645 } 3646 MOZ_ASSERT(currentLength > 0); 3647 3648 // Step 10. 3649 Rooted<TypedArrayObject*> result(cx, 3650 TypedArrayCreateSameType(cx, tarray, len)); 3651 if (!result) { 3652 return false; 3653 } 3654 3655 // Steps 11-12. 3656 if (len > 0) { 3657 // Start with copying all elements from |tarray|. 3658 TypedArrayCopyElements(tarray, result, std::min(len, currentLength)); 3659 3660 // Set the replacement value. 3661 if (actualIndex < double(len)) { 3662 switch (result->type()) { 3663 #define TYPED_ARRAY_SET_ELEMENT(_, NativeType, Name) \ 3664 case Scalar::Name: \ 3665 TypedArraySetElement<UnsharedOps, NativeType>(result, size_t(actualIndex), \ 3666 value); \ 3667 break; 3668 JS_FOR_EACH_TYPED_ARRAY(TYPED_ARRAY_SET_ELEMENT) 3669 #undef TYPED_ARRAY_SET_ELEMENT 3670 default: 3671 MOZ_CRASH("Unsupported TypedArray type"); 3672 } 3673 } 3674 3675 // Fill the remaining elements with `undefined`. 3676 if (currentLength < len) { 3677 if (!result->convertValue(cx, UndefinedHandleValue, &value)) { 3678 return false; 3679 } 3680 3681 switch (result->type()) { 3682 #define TYPED_ARRAY_FILL(_, NativeType, Name) \ 3683 case Scalar::Name: \ 3684 TypedArrayFill<NativeType>(result, value.get(), currentLength, len); \ 3685 break; 3686 JS_FOR_EACH_TYPED_ARRAY(TYPED_ARRAY_FILL) 3687 #undef TYPED_ARRAY_FILL 3688 default: 3689 MOZ_CRASH("Unsupported TypedArray type"); 3690 } 3691 } 3692 } 3693 3694 // Step 13. 3695 args.rval().setObject(*result); 3696 return true; 3697 } 3698 3699 /** 3700 * %TypedArray%.prototype.with ( index, value ) 3701 * 3702 * ES2025 draft rev c4042979a7cdd96b663ffcc43aeee90af8d7a576 3703 */ 3704 static bool TypedArray_with(JSContext* cx, unsigned argc, Value* vp) { 3705 AutoJSMethodProfilerEntry pseudoFrame(cx, "[TypedArray].prototype", "with"); 3706 CallArgs args = CallArgsFromVp(argc, vp); 3707 return CallNonGenericMethod<IsTypedArrayObject, TypedArray_with>(cx, args); 3708 } 3709 3710 /** 3711 * TypedArrayCreateFromConstructor ( constructor, argumentList ) 3712 */ 3713 template <typename... Args> 3714 static TypedArrayObject* TypedArrayCreateFromConstructor( 3715 JSContext* cx, Handle<JSObject*> constructor, Args... args) { 3716 // Step 1. 3717 Rooted<JSObject*> resultObj(cx); 3718 { 3719 auto toNumberOrObjectValue = [](auto v) { 3720 if constexpr (std::is_arithmetic_v<decltype(v)>) { 3721 return NumberValue(v); 3722 } else { 3723 return ObjectValue(*v); 3724 } 3725 }; 3726 3727 FixedConstructArgs<sizeof...(args)> cargs(cx); 3728 3729 size_t i = 0; 3730 ((cargs[i].set(toNumberOrObjectValue(args)), i++), ...); 3731 3732 Rooted<Value> ctorVal(cx, ObjectValue(*constructor)); 3733 if (!Construct(cx, ctorVal, cargs, ctorVal, &resultObj)) { 3734 return nullptr; 3735 } 3736 } 3737 3738 // Step 2. 3739 auto* unwrapped = resultObj->maybeUnwrapIf<TypedArrayObject>(); 3740 if (!unwrapped) { 3741 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3742 JSMSG_NON_TYPED_ARRAY_RETURNED); 3743 return nullptr; 3744 } 3745 3746 auto resultLength = unwrapped->length(); 3747 if (!resultLength) { 3748 ReportOutOfBounds(cx, unwrapped); 3749 return nullptr; 3750 } 3751 3752 // Step 3. (Assertion not applicable in our implementation.) 3753 3754 // Step 4. 3755 if constexpr (sizeof...(args) == 1) { 3756 // Use nested if-statements because GCC miscompiles `decltype((args, ...))`. 3757 auto length = (args, ...); 3758 if constexpr (std::is_arithmetic_v<decltype(length)>) { 3759 // Additional step from <https://tc39.es/proposal-immutable-arraybuffer>. 3760 if (unwrapped->is<ImmutableTypedArrayObject>()) { 3761 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3762 JSMSG_ARRAYBUFFER_IMMUTABLE); 3763 return nullptr; 3764 } 3765 3766 // Steps 4.a-b. (Performed above) 3767 3768 // Step 4.c. 3769 if (*resultLength < length) { 3770 ToCStringBuf lengthBuf; 3771 ToCStringBuf resultLengthBuf; 3772 JS_ReportErrorNumberASCII( 3773 cx, GetErrorMessage, nullptr, JSMSG_SHORT_TYPED_ARRAY_RETURNED, 3774 NumberToCString(&lengthBuf, length), 3775 NumberToCString(&resultLengthBuf, *resultLength)); 3776 return nullptr; 3777 } 3778 } 3779 } 3780 3781 // Step 5. 3782 return unwrapped; 3783 } 3784 3785 static bool HasBuiltinTypedArraySpecies(TypedArrayObject* obj, JSContext* cx) { 3786 // Ensure `%TypedArray%.prototype.constructor` and `%TypedArray%[@@species]` 3787 // haven't been mutated. Ensure concrete `TypedArray.prototype.constructor` 3788 // and the prototype of `TypedArray.prototype` haven't been mutated. 3789 if (!cx->realm()->realmFuses.optimizeTypedArraySpeciesFuse.intact()) { 3790 return false; 3791 } 3792 3793 auto protoKey = StandardProtoKeyOrNull(obj); 3794 3795 // Ensure |obj|'s prototype is the actual concrete TypedArray.prototype. 3796 auto* proto = cx->global()->maybeGetPrototype(protoKey); 3797 if (!proto || obj->staticPrototype() != proto) { 3798 return false; 3799 } 3800 3801 // Fail if |obj| has an own `constructor` property. 3802 if (obj->containsPure(cx->names().constructor)) { 3803 return false; 3804 } 3805 3806 return true; 3807 } 3808 3809 static bool IsTypedArraySpecies(JSContext* cx, JSFunction* species) { 3810 return IsSelfHostedFunctionWithName(species, 3811 cx->names().dollar_TypedArraySpecies_); 3812 } 3813 3814 /** 3815 * TypedArraySpeciesCreate ( exemplar, argumentList ) 3816 * 3817 * ES2026 draft rev 60c4df0b65e1c2e6b6581a5bc08a9311223be1da 3818 */ 3819 template <typename... Args> 3820 static TypedArrayObject* TypedArraySpeciesCreateImpl( 3821 JSContext* cx, Handle<TypedArrayObject*> exemplar, Args... args) { 3822 if (HasBuiltinTypedArraySpecies(exemplar, cx)) { 3823 return TypedArrayCreateSameType(cx, exemplar, args...); 3824 } 3825 3826 // Step 1. 3827 auto ctorKey = StandardProtoKeyOrNull(exemplar); 3828 Rooted<JSObject*> defaultCtor( 3829 cx, GlobalObject::getOrCreateConstructor(cx, ctorKey)); 3830 if (!defaultCtor) { 3831 return nullptr; 3832 } 3833 3834 // Steps 1-2. 3835 Rooted<JSObject*> constructor( 3836 cx, SpeciesConstructor(cx, exemplar, defaultCtor, IsTypedArraySpecies)); 3837 if (!constructor) { 3838 return nullptr; 3839 } 3840 3841 if (constructor == defaultCtor) { 3842 return TypedArrayCreateSameType(cx, exemplar, args...); 3843 } 3844 3845 // Step 3. 3846 auto* unwrappedResult = 3847 TypedArrayCreateFromConstructor(cx, constructor, args...); 3848 if (!unwrappedResult) { 3849 return nullptr; 3850 } 3851 3852 // Step 4. 3853 if (Scalar::isBigIntType(exemplar->type()) != 3854 Scalar::isBigIntType(unwrappedResult->type())) { 3855 JS_ReportErrorNumberASCII( 3856 cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_NOT_COMPATIBLE, 3857 exemplar->getClass()->name, unwrappedResult->getClass()->name); 3858 return nullptr; 3859 } 3860 3861 // Step 5. 3862 return unwrappedResult; 3863 } 3864 3865 /** 3866 * TypedArraySpeciesCreate ( exemplar, argumentList ) 3867 * 3868 * ES2026 draft rev 60c4df0b65e1c2e6b6581a5bc08a9311223be1da 3869 */ 3870 static TypedArrayObject* TypedArraySpeciesCreate( 3871 JSContext* cx, Handle<TypedArrayObject*> exemplar, size_t length) { 3872 return TypedArraySpeciesCreateImpl(cx, exemplar, length); 3873 } 3874 3875 /** 3876 * TypedArraySpeciesCreate ( exemplar, argumentList ) 3877 * 3878 * ES2026 draft rev 60c4df0b65e1c2e6b6581a5bc08a9311223be1da 3879 */ 3880 static TypedArrayObject* TypedArraySpeciesCreate( 3881 JSContext* cx, Handle<TypedArrayObject*> exemplar, 3882 Handle<ArrayBufferObjectMaybeShared*> buffer, size_t byteOffset) { 3883 return TypedArraySpeciesCreateImpl(cx, exemplar, buffer, byteOffset); 3884 } 3885 3886 /** 3887 * TypedArraySpeciesCreate ( exemplar, argumentList ) 3888 * 3889 * ES2026 draft rev 60c4df0b65e1c2e6b6581a5bc08a9311223be1da 3890 */ 3891 static TypedArrayObject* TypedArraySpeciesCreate( 3892 JSContext* cx, Handle<TypedArrayObject*> exemplar, 3893 Handle<ArrayBufferObjectMaybeShared*> buffer, size_t byteOffset, 3894 size_t length) { 3895 return TypedArraySpeciesCreateImpl(cx, exemplar, buffer, byteOffset, length); 3896 } 3897 3898 static void TypedArrayBitwiseSlice(TypedArrayObject* source, size_t startIndex, 3899 size_t count, TypedArrayObject* target) { 3900 MOZ_ASSERT(CanUseBitwiseCopy(target->type(), source->type())); 3901 MOZ_ASSERT(!source->hasDetachedBuffer()); 3902 MOZ_ASSERT(!target->hasDetachedBuffer()); 3903 MOZ_ASSERT(!target->is<ImmutableTypedArrayObject>()); 3904 MOZ_ASSERT(count > 0); 3905 MOZ_ASSERT(startIndex + count <= source->length().valueOr(0)); 3906 MOZ_ASSERT(count <= target->length().valueOr(0)); 3907 3908 size_t elementSize = TypedArrayElemSize(source->type()); 3909 MOZ_ASSERT(elementSize == TypedArrayElemSize(target->type())); 3910 3911 SharedMem<uint8_t*> sourceData = 3912 source->dataPointerEither().cast<uint8_t*>() + startIndex * elementSize; 3913 3914 SharedMem<uint8_t*> targetData = target->dataPointerEither().cast<uint8_t*>(); 3915 3916 size_t byteLength = count * elementSize; 3917 3918 // The same-type case requires exact copying preserving the bit-level encoding 3919 // of the source data, so use memcpy if possible. If source and target are the 3920 // same buffer, we can't use memcpy (or memmove), because the specification 3921 // requires sequential copying of the values. This case is only possible if a 3922 // @@species constructor created a specifically crafted typed array. It won't 3923 // happen in normal code and hence doesn't need to be optimized. 3924 if (!TypedArrayObject::sameBuffer(source, target)) { 3925 if (source->isSharedMemory() || target->isSharedMemory()) { 3926 jit::AtomicOperations::memcpySafeWhenRacy(targetData, sourceData, 3927 byteLength); 3928 } else { 3929 std::memcpy(targetData.unwrapUnshared(), sourceData.unwrapUnshared(), 3930 byteLength); 3931 } 3932 } else { 3933 for (; byteLength > 0; byteLength--) { 3934 jit::AtomicOperations::storeSafeWhenRacy( 3935 targetData++, jit::AtomicOperations::loadSafeWhenRacy(sourceData++)); 3936 } 3937 } 3938 } 3939 3940 template <typename NativeType> 3941 static double TypedArraySliceCopySlowGet(TypedArrayObject* tarray, 3942 size_t index) { 3943 return static_cast<double>( 3944 TypedArrayObjectTemplate<NativeType>::getIndex(tarray, index)); 3945 } 3946 3947 template <typename NativeType> 3948 static void TypedArraySliceCopySlowSet(TypedArrayObject* tarray, size_t index, 3949 double value) { 3950 if constexpr (!std::numeric_limits<NativeType>::is_integer) { 3951 if (js::SupportDifferentialTesting()) { 3952 // See the comment in ElementSpecific::doubleToNative. 3953 value = JS::CanonicalizeNaN(value); 3954 } 3955 } 3956 TypedArrayObjectTemplate<NativeType>::setIndex( 3957 *tarray, index, ConvertNumber<NativeType>(value)); 3958 } 3959 3960 template <> 3961 void TypedArraySliceCopySlowSet<int64_t>(TypedArrayObject* tarray, size_t index, 3962 double value) { 3963 // Specialization because ConvertNumber doesn't allow double to int64_t. 3964 MOZ_CRASH("unexpected set with int64_t"); 3965 } 3966 3967 template <> 3968 void TypedArraySliceCopySlowSet<uint64_t>(TypedArrayObject* tarray, 3969 size_t index, double value) { 3970 // Specialization because ConvertNumber doesn't allow double to uint64_t. 3971 MOZ_CRASH("unexpected set with uint64_t"); 3972 } 3973 3974 static void TypedArraySliceCopySlow(TypedArrayObject* source, size_t startIndex, 3975 size_t count, TypedArrayObject* target) { 3976 MOZ_ASSERT(!CanUseBitwiseCopy(target->type(), source->type())); 3977 MOZ_ASSERT(!source->hasDetachedBuffer()); 3978 MOZ_ASSERT(!target->hasDetachedBuffer()); 3979 MOZ_ASSERT(!target->is<ImmutableTypedArrayObject>()); 3980 MOZ_ASSERT(count > 0); 3981 MOZ_ASSERT(startIndex + count <= source->length().valueOr(0)); 3982 MOZ_ASSERT(count <= target->length().valueOr(0)); 3983 3984 static_assert( 3985 CanUseBitwiseCopy(Scalar::BigInt64, Scalar::BigUint64) && 3986 CanUseBitwiseCopy(Scalar::BigUint64, Scalar::BigInt64), 3987 "BigInt contents, even if sign is different, can be copied bitwise"); 3988 3989 MOZ_ASSERT(!Scalar::isBigIntType(target->type()) && 3990 !Scalar::isBigIntType(source->type())); 3991 3992 // Step 14.h.i. 3993 size_t n = 0; 3994 3995 // Step 14.h.ii. 3996 size_t k = startIndex; 3997 3998 // Step 14.h.iii. 3999 while (n < count) { 4000 // Step 14.h.iii.1. (Not applicable) 4001 4002 // Step 14.h.iii.2. 4003 double value; 4004 switch (source->type()) { 4005 #define GET_ELEMENT(_, T, N) \ 4006 case Scalar::N: \ 4007 value = TypedArraySliceCopySlowGet<T>(source, k); \ 4008 break; 4009 JS_FOR_EACH_TYPED_ARRAY(GET_ELEMENT) 4010 #undef GET_ELEMENT 4011 case Scalar::MaxTypedArrayViewType: 4012 case Scalar::Int64: 4013 case Scalar::Simd128: 4014 break; 4015 } 4016 4017 // Step 14.h.iii.3. 4018 switch (target->type()) { 4019 #define SET_ELEMENT(_, T, N) \ 4020 case Scalar::N: \ 4021 TypedArraySliceCopySlowSet<T>(target, n, value); \ 4022 break; 4023 JS_FOR_EACH_TYPED_ARRAY(SET_ELEMENT) 4024 #undef SET_ELEMENT 4025 case Scalar::MaxTypedArrayViewType: 4026 case Scalar::Int64: 4027 case Scalar::Simd128: 4028 break; 4029 } 4030 4031 // Step 14.h.iii.4. 4032 k += 1; 4033 4034 // Step 14.h.iii.5. 4035 n += 1; 4036 } 4037 } 4038 4039 /** 4040 * %TypedArray%.prototype.slice ( start, end ) 4041 * 4042 * ES2026 draft rev 60c4df0b65e1c2e6b6581a5bc08a9311223be1da 4043 */ 4044 static bool TypedArray_slice(JSContext* cx, const CallArgs& args) { 4045 MOZ_ASSERT(IsTypedArrayObject(args.thisv())); 4046 4047 // Steps 1-3. 4048 Rooted<TypedArrayObject*> tarray( 4049 cx, &args.thisv().toObject().as<TypedArrayObject>()); 4050 4051 auto arrayLength = tarray->length(); 4052 if (!arrayLength) { 4053 ReportOutOfBounds(cx, tarray); 4054 return false; 4055 } 4056 size_t len = *arrayLength; 4057 4058 // Steps 4-7. 4059 size_t startIndex = 0; 4060 if (args.hasDefined(0)) { 4061 if (!ToIntegerIndex(cx, args[0], len, &startIndex)) { 4062 return false; 4063 } 4064 } 4065 4066 // Steps 8-11. 4067 size_t endIndex = len; 4068 if (args.hasDefined(1)) { 4069 if (!ToIntegerIndex(cx, args[1], len, &endIndex)) { 4070 return false; 4071 } 4072 } 4073 4074 // Step 12. 4075 size_t count = endIndex >= startIndex ? endIndex - startIndex : 0; 4076 4077 // Step 13. 4078 Rooted<TypedArrayObject*> unwrappedResult( 4079 cx, TypedArraySpeciesCreate(cx, tarray, count)); 4080 if (!unwrappedResult) { 4081 return false; 4082 } 4083 4084 // Additional step from <https://tc39.es/proposal-immutable-arraybuffer>. 4085 MOZ_ASSERT(!unwrappedResult->is<ImmutableTypedArrayObject>()); 4086 4087 // Step 14. 4088 if (count > 0) { 4089 // Steps 14.a-b. 4090 auto arrayLength = tarray->length(); 4091 if (!arrayLength) { 4092 ReportOutOfBounds(cx, tarray); 4093 return false; 4094 } 4095 4096 // Step 14.c. 4097 endIndex = std::min(endIndex, *arrayLength); 4098 4099 // Step 14.d. 4100 count = endIndex >= startIndex ? endIndex - startIndex : 0; 4101 4102 // Copy if updated |count| is still non-zero. 4103 if (count > 0) { 4104 // Step 14.e. 4105 auto srcType = tarray->type(); 4106 4107 // Step 14.f. 4108 auto targetType = unwrappedResult->type(); 4109 4110 // Steps 14.g-h. 4111 // 4112 // The specification requires us to perform bitwise copying when |result| 4113 // and |tarray| have the same type. Additionally, as an optimization, we 4114 // can also perform bitwise copying when both types have compatible 4115 // bit-level representations. 4116 if (MOZ_LIKELY(CanUseBitwiseCopy(targetType, srcType))) { 4117 TypedArrayBitwiseSlice(tarray, startIndex, count, unwrappedResult); 4118 } else { 4119 TypedArraySliceCopySlow(tarray, startIndex, count, unwrappedResult); 4120 } 4121 } 4122 } 4123 4124 // Step 15. 4125 if (MOZ_LIKELY(cx->compartment() == unwrappedResult->compartment())) { 4126 args.rval().setObject(*unwrappedResult); 4127 } else { 4128 Rooted<JSObject*> wrappedResult(cx, unwrappedResult); 4129 if (!cx->compartment()->wrap(cx, &wrappedResult)) { 4130 return false; 4131 } 4132 args.rval().setObject(*wrappedResult); 4133 } 4134 return true; 4135 } 4136 4137 /** 4138 * %TypedArray%.prototype.slice ( start, end ) 4139 * 4140 * ES2026 draft rev 60c4df0b65e1c2e6b6581a5bc08a9311223be1da 4141 */ 4142 static bool TypedArray_slice(JSContext* cx, unsigned argc, Value* vp) { 4143 AutoJSMethodProfilerEntry pseudoFrame(cx, "[TypedArray].prototype", "slice"); 4144 CallArgs args = CallArgsFromVp(argc, vp); 4145 return CallNonGenericMethod<IsTypedArrayObject, TypedArray_slice>(cx, args); 4146 } 4147 4148 /** 4149 * %TypedArray%.prototype.subarray ( start, end ) 4150 * 4151 * ES2026 draft rev 60c4df0b65e1c2e6b6581a5bc08a9311223be1da 4152 */ 4153 static bool TypedArray_subarray(JSContext* cx, const CallArgs& args) { 4154 MOZ_ASSERT(IsTypedArrayObject(args.thisv())); 4155 4156 // Steps 1-3. 4157 Rooted<TypedArrayObject*> tarray( 4158 cx, &args.thisv().toObject().as<TypedArrayObject>()); 4159 4160 // Step 4. 4161 if (!TypedArrayObject::ensureHasBuffer(cx, tarray)) { 4162 return false; 4163 } 4164 Rooted<ArrayBufferObjectMaybeShared*> buffer(cx, tarray->bufferEither()); 4165 4166 // Steps 5-7. 4167 size_t srcLength = tarray->length().valueOr(0); 4168 4169 // Step 13. 4170 // 4171 // Reordered because otherwise it'd be observable that we reset 4172 // the byteOffset to zero when the underlying array buffer gets detached. 4173 size_t srcByteOffset = tarray->byteOffsetMaybeOutOfBounds(); 4174 4175 // Steps 8-11. 4176 size_t startIndex = 0; 4177 if (args.hasDefined(0)) { 4178 if (!ToIntegerIndex(cx, args[0], srcLength, &startIndex)) { 4179 return false; 4180 } 4181 } 4182 4183 // Step 12. 4184 size_t elementSize = TypedArrayElemSize(tarray->type()); 4185 4186 // Step 14. 4187 size_t beginByteOffset = srcByteOffset + (startIndex * elementSize); 4188 4189 // Steps 15-16. 4190 TypedArrayObject* unwrappedResult; 4191 if (!args.hasDefined(1) && tarray->is<ResizableTypedArrayObject>() && 4192 tarray->as<ResizableTypedArrayObject>().isAutoLength()) { 4193 // Step 15.a. 4194 unwrappedResult = 4195 TypedArraySpeciesCreate(cx, tarray, buffer, beginByteOffset); 4196 } else { 4197 // Steps 16.a-d. 4198 size_t endIndex = srcLength; 4199 if (args.hasDefined(1)) { 4200 if (!ToIntegerIndex(cx, args[1], srcLength, &endIndex)) { 4201 return false; 4202 } 4203 } 4204 4205 // Step 16.e. 4206 size_t newLength = endIndex >= startIndex ? endIndex - startIndex : 0; 4207 4208 // Step 16.f. 4209 unwrappedResult = 4210 TypedArraySpeciesCreate(cx, tarray, buffer, beginByteOffset, newLength); 4211 } 4212 if (!unwrappedResult) { 4213 return false; 4214 } 4215 4216 // Step 17. 4217 if (MOZ_LIKELY(cx->compartment() == unwrappedResult->compartment())) { 4218 args.rval().setObject(*unwrappedResult); 4219 } else { 4220 Rooted<JSObject*> wrappedResult(cx, unwrappedResult); 4221 if (!cx->compartment()->wrap(cx, &wrappedResult)) { 4222 return false; 4223 } 4224 args.rval().setObject(*wrappedResult); 4225 } 4226 return true; 4227 } 4228 4229 /** 4230 * %TypedArray%.prototype.subarray ( start, end ) 4231 * 4232 * ES2026 draft rev 60c4df0b65e1c2e6b6581a5bc08a9311223be1da 4233 */ 4234 static bool TypedArray_subarray(JSContext* cx, unsigned argc, Value* vp) { 4235 AutoJSMethodProfilerEntry pseudoFrame(cx, "[TypedArray].prototype", 4236 "subarray"); 4237 CallArgs args = CallArgsFromVp(argc, vp); 4238 return CallNonGenericMethod<IsTypedArrayObject, TypedArray_subarray>(cx, 4239 args); 4240 } 4241 4242 TypedArrayObject* js::TypedArraySubarray(JSContext* cx, 4243 Handle<TypedArrayObject*> obj, 4244 intptr_t start, intptr_t end) { 4245 MOZ_ASSERT(!obj->hasDetachedBuffer()); 4246 MOZ_ASSERT(!obj->is<ResizableTypedArrayObject>()); 4247 4248 size_t srcLength = obj->length().valueOr(0); 4249 4250 size_t startIndex = ToIntegerIndex(start, srcLength); 4251 size_t endIndex = ToIntegerIndex(end, srcLength); 4252 4253 size_t newLength = endIndex >= startIndex ? endIndex - startIndex : 0; 4254 4255 return TypedArraySubarrayWithLength(cx, obj, startIndex, newLength); 4256 } 4257 4258 TypedArrayObject* js::TypedArraySubarrayWithLength( 4259 JSContext* cx, Handle<TypedArrayObject*> obj, intptr_t start, 4260 intptr_t length) { 4261 MOZ_ASSERT(!obj->hasDetachedBuffer()); 4262 MOZ_ASSERT(!obj->is<ResizableTypedArrayObject>()); 4263 MOZ_ASSERT(start >= 0); 4264 MOZ_ASSERT(length >= 0); 4265 MOZ_ASSERT(size_t(start + length) <= obj->length().valueOr(0)); 4266 4267 if (!TypedArrayObject::ensureHasBuffer(cx, obj)) { 4268 return nullptr; 4269 } 4270 Rooted<ArrayBufferObjectMaybeShared*> buffer(cx, obj->bufferEither()); 4271 4272 size_t srcByteOffset = obj->byteOffset().valueOr(0); 4273 size_t elementSize = TypedArrayElemSize(obj->type()); 4274 size_t beginByteOffset = srcByteOffset + (start * elementSize); 4275 4276 auto* result = 4277 TypedArrayCreateSameType(cx, obj, buffer, beginByteOffset, length); 4278 4279 // Other exceptions aren't allowed, because TypedArraySubarray is a 4280 // recoverable operation. 4281 MOZ_ASSERT_IF(!result, cx->isThrowingOutOfMemory()); 4282 4283 return result; 4284 } 4285 4286 static auto* TypedArrayFromDetachedBuffer(JSContext* cx, 4287 Handle<TypedArrayObject*> obj) { 4288 MOZ_ASSERT(obj->hasDetachedBuffer()); 4289 4290 Rooted<ArrayBufferObject*> buffer(cx, obj->bufferUnshared()); 4291 4292 switch (obj->type()) { 4293 #define TYPED_ARRAY_CREATE(_, NativeType, Name) \ 4294 case Scalar::Name: \ 4295 return FixedLengthTypedArrayObjectTemplate< \ 4296 NativeType>::fromDetachedBuffer(cx, buffer); 4297 JS_FOR_EACH_TYPED_ARRAY(TYPED_ARRAY_CREATE) 4298 #undef TYPED_ARRAY_CREATE 4299 default: 4300 MOZ_CRASH("Unsupported TypedArray type"); 4301 } 4302 } 4303 4304 TypedArrayObject* js::TypedArraySubarrayRecover(JSContext* cx, 4305 Handle<TypedArrayObject*> obj, 4306 intptr_t start, 4307 intptr_t length) { 4308 MOZ_ASSERT(!obj->is<ResizableTypedArrayObject>()); 4309 MOZ_ASSERT(start >= 0); 4310 MOZ_ASSERT(length >= 0); 4311 4312 // Special case: The buffer was detached after calling `subarray`. This case 4313 // can only happen when recovering a TypedArraySubarray allocation. 4314 if (obj->hasDetachedBuffer()) { 4315 return TypedArrayFromDetachedBuffer(cx, obj); 4316 } 4317 return TypedArraySubarrayWithLength(cx, obj, start, length); 4318 } 4319 4320 // Byte vector with large enough inline storage to allow constructing small 4321 // typed arrays without extra heap allocations. 4322 using ByteVector = 4323 js::Vector<uint8_t, FixedLengthTypedArrayObject::INLINE_BUFFER_LIMIT>; 4324 4325 static UniqueChars QuoteString(JSContext* cx, char16_t ch) { 4326 Sprinter sprinter(cx); 4327 if (!sprinter.init()) { 4328 return nullptr; 4329 } 4330 4331 StringEscape esc{}; 4332 js::EscapePrinter ep(sprinter, esc); 4333 ep.putChar(ch); 4334 4335 return sprinter.release(); 4336 } 4337 4338 namespace Hex { 4339 static constexpr int8_t InvalidChar = -1; 4340 4341 static constexpr auto DecodeTable() { 4342 std::array<int8_t, 256> result = {}; 4343 4344 // Initialize all elements to InvalidChar. 4345 for (auto& e : result) { 4346 e = InvalidChar; 4347 } 4348 4349 // Map the ASCII hexadecimal characters to their values. 4350 for (uint8_t i = 0; i < 128; ++i) { 4351 if (mozilla::IsAsciiHexDigit(char(i))) { 4352 result[i] = mozilla::AsciiAlphanumericToNumber(char(i)); 4353 } 4354 } 4355 4356 return result; 4357 } 4358 4359 static constexpr auto Table = DecodeTable(); 4360 } // namespace Hex 4361 4362 /** 4363 * FromHex ( string [ , maxLength ] ) 4364 * 4365 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-fromhex 4366 */ 4367 template <typename Ops, typename CharT> 4368 static size_t FromHex(const CharT* chars, size_t length, 4369 TypedArrayObject* tarray) { 4370 auto data = Ops::extract(tarray).template cast<uint8_t*>(); 4371 4372 static_assert(std::size(Hex::Table) == 256, 4373 "can access decode table using Latin-1 character"); 4374 4375 auto decodeChar = [&](CharT ch) -> int32_t { 4376 if constexpr (sizeof(CharT) == 1) { 4377 return Hex::Table[ch]; 4378 } else { 4379 return ch <= 255 ? Hex::Table[ch] : Hex::InvalidChar; 4380 } 4381 }; 4382 4383 auto decode2Chars = [&](const CharT* chars) { 4384 return (decodeChar(chars[0]) << 4) | (decodeChar(chars[1]) << 0); 4385 }; 4386 4387 auto decode4Chars = [&](const CharT* chars) { 4388 return (decodeChar(chars[2]) << 12) | (decodeChar(chars[3]) << 8) | 4389 (decodeChar(chars[0]) << 4) | (decodeChar(chars[1]) << 0); 4390 }; 4391 4392 // Step 4. 4393 size_t index = 0; 4394 4395 // Step 5. (Checked in caller.) 4396 MOZ_ASSERT(length % 2 == 0); 4397 4398 // Process eight characters per loop iteration. 4399 if (length >= 8) { 4400 // Align |data| to uint32_t. 4401 if (MOZ_UNLIKELY(data.unwrapValue() & 3)) { 4402 // Performs at most three iterations until |data| is aligned, reading up 4403 // to six characters. 4404 while (data.unwrapValue() & 3) { 4405 // Step 6.a and 6.d. 4406 uint32_t byte = decode2Chars(chars + index); 4407 4408 // Step 6.b. 4409 if (MOZ_UNLIKELY(int32_t(byte) < 0)) { 4410 return index; 4411 } 4412 MOZ_ASSERT(byte <= 0xff); 4413 4414 // Step 6.c. 4415 index += 2; 4416 4417 // Step 6.e. 4418 Ops::store(data++, uint8_t(byte)); 4419 } 4420 } 4421 4422 auto data32 = data.template cast<uint32_t*>(); 4423 4424 // Step 6. 4425 size_t lastValidIndex = length - 8; 4426 while (index <= lastValidIndex) { 4427 // Steps 6.a and 6.d. 4428 uint32_t word1 = decode4Chars(chars + index); 4429 4430 // Step 6.b. 4431 if (MOZ_UNLIKELY(int32_t(word1) < 0)) { 4432 break; 4433 } 4434 MOZ_ASSERT(word1 <= 0xffff); 4435 4436 // Step 6.a and 6.d. 4437 uint32_t word2 = decode4Chars(chars + index + 4); 4438 4439 // Step 6.b. 4440 if (MOZ_UNLIKELY(int32_t(word2) < 0)) { 4441 break; 4442 } 4443 MOZ_ASSERT(word2 <= 0xffff); 4444 4445 // Step 6.c. 4446 index += 4 * 2; 4447 4448 // Step 6.e. 4449 // 4450 // The word was constructed in little-endian order, so in the unlikely 4451 // case of a big-endian system we have to swap it. 4452 uint32_t word = 4453 mozilla::NativeEndian::swapFromLittleEndian((word2 << 16) | word1); 4454 Ops::store(data32++, word); 4455 } 4456 4457 data = data32.template cast<uint8_t*>(); 4458 } 4459 4460 // Step 6. 4461 while (index < length) { 4462 // Step 6.a and 6.d. 4463 uint32_t byte = decode2Chars(chars + index); 4464 4465 // Step 6.b. 4466 if (MOZ_UNLIKELY(int32_t(byte) < 0)) { 4467 return index; 4468 } 4469 MOZ_ASSERT(byte <= 0xff); 4470 4471 // Step 6.c. 4472 index += 2; 4473 4474 // Step 6.e. 4475 Ops::store(data++, uint8_t(byte)); 4476 } 4477 4478 // Step 7. 4479 return index; 4480 } 4481 4482 /** 4483 * FromHex ( string [ , maxLength ] ) 4484 * 4485 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-fromhex 4486 */ 4487 template <typename Ops> 4488 static size_t FromHex(JSLinearString* linear, size_t length, 4489 TypedArrayObject* tarray) { 4490 JS::AutoCheckCannotGC nogc; 4491 if (linear->hasLatin1Chars()) { 4492 return FromHex<Ops>(linear->latin1Chars(nogc), length, tarray); 4493 } 4494 return FromHex<Ops>(linear->twoByteChars(nogc), length, tarray); 4495 } 4496 4497 /** 4498 * FromHex ( string [ , maxLength ] ) 4499 * 4500 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-fromhex 4501 */ 4502 static bool FromHex(JSContext* cx, JSString* string, size_t maxLength, 4503 TypedArrayObject* tarray) { 4504 MOZ_ASSERT(tarray->type() == Scalar::Uint8); 4505 4506 // The underlying buffer must neither be detached nor shrunk. (It may have 4507 // been grown when it's a growable shared buffer and a concurrent thread 4508 // resized the buffer.) 4509 MOZ_ASSERT(!tarray->hasDetachedBuffer()); 4510 MOZ_ASSERT(tarray->length().valueOr(0) >= maxLength); 4511 4512 // Step 1. (Not applicable in our implementation.) 4513 4514 // Step 2. 4515 // 4516 // Each byte is encoded in two characters. 4517 size_t readLength = maxLength * 2; 4518 MOZ_ASSERT(readLength <= string->length()); 4519 4520 // Step 3. (Not applicable in our implementation.) 4521 4522 JSLinearString* linear = string->ensureLinear(cx); 4523 if (!linear) { 4524 return false; 4525 } 4526 4527 // Steps 4 and 6-7. 4528 size_t index; 4529 if (tarray->isSharedMemory()) { 4530 index = FromHex<SharedOps>(linear, readLength, tarray); 4531 } else { 4532 index = FromHex<UnsharedOps>(linear, readLength, tarray); 4533 } 4534 if (MOZ_UNLIKELY(index < readLength)) { 4535 char16_t c0 = linear->latin1OrTwoByteChar(index); 4536 char16_t c1 = linear->latin1OrTwoByteChar(index + 1); 4537 char16_t ch = !mozilla::IsAsciiHexDigit(c0) ? c0 : c1; 4538 if (auto str = QuoteString(cx, ch)) { 4539 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 4540 JSMSG_TYPED_ARRAY_BAD_HEX_DIGIT, str.get()); 4541 } 4542 return false; 4543 } 4544 return true; 4545 } 4546 4547 namespace Base64 { 4548 static constexpr int8_t InvalidChar = -1; 4549 4550 static constexpr auto DecodeTable(const char (&alphabet)[65]) { 4551 std::array<int8_t, 256> result = {}; 4552 4553 // Initialize all elements to InvalidChar. 4554 for (auto& e : result) { 4555 e = InvalidChar; 4556 } 4557 4558 // Map the base64 characters to their values. 4559 for (uint8_t i = 0; i < 64; ++i) { 4560 result[alphabet[i]] = i; 4561 } 4562 4563 return result; 4564 } 4565 } // namespace Base64 4566 4567 namespace Base64::Encode { 4568 static constexpr const char Base64[] = 4569 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 4570 static_assert(std::char_traits<char>::length(Base64) == 64); 4571 4572 static constexpr const char Base64Url[] = 4573 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; 4574 static_assert(std::char_traits<char>::length(Base64Url) == 64); 4575 } // namespace Base64::Encode 4576 4577 namespace Base64::Decode { 4578 static constexpr auto Base64 = DecodeTable(Base64::Encode::Base64); 4579 static_assert(Base64.size() == 256, 4580 "256 elements to allow access through Latin-1 characters"); 4581 4582 static constexpr auto Base64Url = DecodeTable(Base64::Encode::Base64Url); 4583 static_assert(Base64Url.size() == 256, 4584 "256 elements to allow access through Latin-1 characters"); 4585 } // namespace Base64::Decode 4586 4587 enum class Alphabet { 4588 /** 4589 * Standard base64 alphabet. 4590 */ 4591 Base64, 4592 4593 /** 4594 * URL and filename safe base64 alphabet. 4595 */ 4596 Base64Url, 4597 }; 4598 4599 enum class LastChunkHandling { 4600 /** 4601 * Allow partial chunks at the end of the input. 4602 */ 4603 Loose, 4604 4605 /** 4606 * Disallow partial chunks at the end of the input. 4607 */ 4608 Strict, 4609 4610 /** 4611 * Stop before partial chunks at the end of the input. 4612 */ 4613 StopBeforePartial, 4614 }; 4615 4616 enum class Base64Error { 4617 None, 4618 BadChar, 4619 BadCharAfterPadding, 4620 IncompleteChunk, 4621 MissingPadding, 4622 ExtraBits, 4623 }; 4624 4625 struct Base64Result { 4626 Base64Error error; 4627 size_t index; 4628 size_t written; 4629 4630 bool isError() const { return error != Base64Error::None; } 4631 4632 static auto Ok(size_t index, size_t written) { 4633 return Base64Result{Base64Error::None, index, written}; 4634 } 4635 4636 static auto Error(Base64Error error) { 4637 MOZ_ASSERT(error != Base64Error::None); 4638 return Base64Result{error, 0, 0}; 4639 } 4640 4641 static auto ErrorAt(Base64Error error, size_t index) { 4642 MOZ_ASSERT(error != Base64Error::None); 4643 return Base64Result{error, index, 0}; 4644 } 4645 }; 4646 4647 static void ReportBase64Error(JSContext* cx, Base64Result result, 4648 JSLinearString* string) { 4649 MOZ_ASSERT(result.isError()); 4650 switch (result.error) { 4651 case Base64Error::None: 4652 break; 4653 case Base64Error::BadChar: 4654 if (auto str = 4655 QuoteString(cx, string->latin1OrTwoByteChar(result.index))) { 4656 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 4657 JSMSG_TYPED_ARRAY_BAD_BASE64_CHAR, str.get()); 4658 } 4659 return; 4660 case Base64Error::BadCharAfterPadding: 4661 if (auto str = 4662 QuoteString(cx, string->latin1OrTwoByteChar(result.index))) { 4663 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 4664 JSMSG_TYPED_ARRAY_BAD_BASE64_AFTER_PADDING, 4665 str.get()); 4666 } 4667 return; 4668 case Base64Error::IncompleteChunk: 4669 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 4670 JSMSG_TYPED_ARRAY_BAD_INCOMPLETE_CHUNK); 4671 return; 4672 case Base64Error::MissingPadding: 4673 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 4674 JSMSG_TYPED_ARRAY_MISSING_BASE64_PADDING); 4675 return; 4676 case Base64Error::ExtraBits: 4677 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 4678 JSMSG_TYPED_ARRAY_EXTRA_BASE64_BITS); 4679 return; 4680 } 4681 MOZ_CRASH("unexpected base64 error"); 4682 } 4683 4684 /** 4685 * FromBase64 ( string, alphabet, lastChunkHandling [ , maxLength ] ) 4686 * 4687 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-frombase64 4688 */ 4689 template <class Ops, typename CharT> 4690 static auto FromBase64(const CharT* chars, size_t length, Alphabet alphabet, 4691 LastChunkHandling lastChunkHandling, 4692 SharedMem<uint8_t*> data, size_t maxLength) { 4693 const SharedMem<uint8_t*> dataBegin = data; 4694 const SharedMem<uint8_t*> dataEnd = data + maxLength; 4695 4696 auto canAppend = [&](size_t n) { return data + n <= dataEnd; }; 4697 4698 auto written = [&]() { return data.unwrap() - dataBegin.unwrap(); }; 4699 4700 // DecodeBase64Chunk ( chunk [ , throwOnExtraBits ] ) 4701 // 4702 // Encode a complete base64 chunk. 4703 auto decodeChunk = [&](uint32_t chunk) { 4704 MOZ_ASSERT(chunk <= 0xffffff); 4705 MOZ_ASSERT(canAppend(3)); 4706 Ops::store(data++, uint8_t(chunk >> 16)); 4707 Ops::store(data++, uint8_t(chunk >> 8)); 4708 Ops::store(data++, uint8_t(chunk)); 4709 }; 4710 4711 // DecodeBase64Chunk ( chunk [ , throwOnExtraBits ] ) 4712 // 4713 // Encode a three element partial base64 chunk. 4714 auto decodeChunk3 = [&](uint32_t chunk) { 4715 MOZ_ASSERT(chunk <= 0x3ffff); 4716 MOZ_ASSERT(canAppend(2)); 4717 Ops::store(data++, uint8_t(chunk >> 10)); 4718 Ops::store(data++, uint8_t(chunk >> 2)); 4719 }; 4720 4721 // DecodeBase64Chunk ( chunk [ , throwOnExtraBits ] ) 4722 // 4723 // Encode a two element partial base64 chunk. 4724 auto decodeChunk2 = [&](uint32_t chunk) { 4725 MOZ_ASSERT(chunk <= 0xfff); 4726 MOZ_ASSERT(canAppend(1)); 4727 Ops::store(data++, uint8_t(chunk >> 4)); 4728 }; 4729 4730 // DecodeBase64Chunk ( chunk [ , throwOnExtraBits ] ) 4731 // 4732 // Encode a partial base64 chunk. 4733 auto decodePartialChunk = [&](uint32_t chunk, uint32_t chunkLength) { 4734 MOZ_ASSERT(chunkLength == 2 || chunkLength == 3); 4735 chunkLength == 2 ? decodeChunk2(chunk) : decodeChunk3(chunk); 4736 }; 4737 4738 // Steps 1-2. (Not applicable in our implementation.) 4739 4740 // Step 3. 4741 if (maxLength == 0) { 4742 return Base64Result::Ok(0, 0); 4743 } 4744 MOZ_ASSERT(canAppend(1), "can append at least one byte if maxLength > 0"); 4745 4746 // Step 8. 4747 // 4748 // Current string index. 4749 size_t index = 0; 4750 4751 // Step 9. (Passed as parameter) 4752 4753 static_assert(std::size(Base64::Decode::Base64) == 256 && 4754 std::size(Base64::Decode::Base64Url) == 256, 4755 "can access decode tables using Latin-1 character"); 4756 4757 const auto& decode = alphabet == Alphabet::Base64 ? Base64::Decode::Base64 4758 : Base64::Decode::Base64Url; 4759 4760 auto decodeChar = [&](CharT ch) -> int32_t { 4761 if constexpr (sizeof(CharT) == 1) { 4762 return decode[ch]; 4763 } else { 4764 return ch <= 255 ? decode[ch] : Base64::InvalidChar; 4765 } 4766 }; 4767 4768 auto decode4Chars = [&](const CharT* chars) { 4769 return (decodeChar(chars[0]) << 18) | (decodeChar(chars[1]) << 12) | 4770 (decodeChar(chars[2]) << 6) | (decodeChar(chars[3])); 4771 }; 4772 4773 // Initial loop to process only full chunks. Doesn't perform any error 4774 // reporting and expects that at least four characters can be read per loop 4775 // iteration and that the output has enough space for a decoded chunk. 4776 if (length >= 4) { 4777 size_t lastValidIndex = length - 4; 4778 while (canAppend(3) && index <= lastValidIndex) { 4779 // Fast path: Read four consecutive characters. 4780 4781 // Step 10.a. (Performed in slow path.) 4782 4783 // Step 10.b. (Moved out of loop.) 4784 4785 // Steps 10.c and 10.e-g. 4786 uint32_t chunk = decode4Chars(chars + index); 4787 4788 // Steps 10.h-i. (Not applicable in this loop.) 4789 4790 // Steps 10.d and 10.j-l. 4791 if (MOZ_LIKELY(int32_t(chunk) >= 0)) { 4792 // Step 10.j-l. 4793 decodeChunk(chunk); 4794 4795 // Step 10.d. 4796 index += 4; 4797 continue; 4798 } 4799 4800 // Slow path: Read four characters, ignoring whitespace. 4801 4802 // Steps 10.a and 10.b. 4803 CharT part[4]; 4804 size_t i = index; 4805 size_t j = 0; 4806 while (i < length && j < 4) { 4807 auto ch = chars[i++]; 4808 4809 // Step 10.a. 4810 if (mozilla::IsAsciiWhitespace(ch)) { 4811 continue; 4812 } 4813 4814 // Step 10.c. 4815 part[j++] = ch; 4816 } 4817 4818 // Steps 10.d-l. 4819 if (MOZ_LIKELY(j == 4)) { 4820 // Steps 10.e-g. 4821 uint32_t chunk = decode4Chars(part); 4822 4823 // Steps 10.h-i. (Not applicable in this loop.) 4824 4825 // Steps 10.d and 10.j-l. 4826 if (MOZ_LIKELY(int32_t(chunk) >= 0)) { 4827 // Step 10.j-l. 4828 decodeChunk(chunk); 4829 4830 // Step 10.d. 4831 index = i; 4832 continue; 4833 } 4834 } 4835 4836 // Padding or invalid characters, or end of input. The next loop will 4837 // process any characters left in the input. 4838 break; 4839 } 4840 4841 // Step 10.b.ii. 4842 if (index == length) { 4843 return Base64Result::Ok(length, written()); 4844 } 4845 4846 // Step 10.l.v. (Reordered) 4847 if (!canAppend(1)) { 4848 MOZ_ASSERT(written() > 0); 4849 return Base64Result::Ok(index, written()); 4850 } 4851 } 4852 4853 // Step 4. 4854 // 4855 // String index after the last fully read base64 chunk. 4856 size_t read = index; 4857 4858 // Step 5. (Not applicable in our implementation.) 4859 4860 // Step 6. 4861 // 4862 // Current base64 chunk, a uint24 number. 4863 uint32_t chunk = 0; 4864 4865 // Step 7. 4866 // 4867 // Current base64 chunk length, in the range [0..4]. 4868 size_t chunkLength = 0; 4869 4870 // Step 10. 4871 for (; index < length; index++) { 4872 // Step 10.c. (Reordered) 4873 auto ch = chars[index]; 4874 4875 // Step 10.a. 4876 if (mozilla::IsAsciiWhitespace(ch)) { 4877 continue; 4878 } 4879 4880 // Step 10.b. (Moved out of loop.) 4881 4882 // Step 10.d. (Performed in for-loop step.) 4883 4884 // Step 10.e. 4885 if (ch == '=') { 4886 break; 4887 } 4888 4889 // Steps 10.f-g. 4890 uint32_t value = decodeChar(ch); 4891 if (MOZ_UNLIKELY(int32_t(value) < 0)) { 4892 return Base64Result::ErrorAt(Base64Error::BadChar, index); 4893 } 4894 MOZ_ASSERT(value <= 0x7f); 4895 4896 // Step 10.h. (Not applicable in our implementation.) 4897 4898 // Step 10.i. 4899 if (chunkLength > 1 && !canAppend(chunkLength)) { 4900 return Base64Result::Ok(read, written()); 4901 } 4902 4903 // Step 10.j. 4904 chunk = (chunk << 6) | value; 4905 4906 // Step 10.k. 4907 chunkLength += 1; 4908 4909 // Step 10.l. (Full chunks are processed in the initial loop.) 4910 MOZ_ASSERT(chunkLength < 4); 4911 } 4912 4913 // Step 10.b. 4914 if (index == length) { 4915 // Step 10.b.i. 4916 if (chunkLength > 0) { 4917 // Step 10.b.i.1. 4918 if (lastChunkHandling == LastChunkHandling::StopBeforePartial) { 4919 return Base64Result::Ok(read, written()); 4920 } 4921 4922 // Steps 10.b.i.2-3. 4923 if (lastChunkHandling == LastChunkHandling::Loose) { 4924 // Step 10.b.i.2.a. 4925 if (chunkLength == 1) { 4926 return Base64Result::Error(Base64Error::IncompleteChunk); 4927 } 4928 MOZ_ASSERT(chunkLength == 2 || chunkLength == 3); 4929 4930 // Step 10.b.i.2.b. 4931 decodePartialChunk(chunk, chunkLength); 4932 } else { 4933 // Step 10.b.i.3.a. 4934 MOZ_ASSERT(lastChunkHandling == LastChunkHandling::Strict); 4935 4936 // Step 10.b.i.3.b. 4937 return Base64Result::Error(Base64Error::IncompleteChunk); 4938 } 4939 } 4940 4941 // Step 10.b.ii. 4942 return Base64Result::Ok(length, written()); 4943 } 4944 4945 // Step 10.e. 4946 MOZ_ASSERT(index < length); 4947 MOZ_ASSERT(chars[index] == '='); 4948 4949 // Step 10.e.i. 4950 if (chunkLength < 2) { 4951 return Base64Result::Error(Base64Error::IncompleteChunk); 4952 } 4953 MOZ_ASSERT(chunkLength == 2 || chunkLength == 3); 4954 4955 // Step 10.e.ii. (Inlined SkipAsciiWhitespace) 4956 while (++index < length) { 4957 auto ch = chars[index]; 4958 if (!mozilla::IsAsciiWhitespace(ch)) { 4959 break; 4960 } 4961 } 4962 4963 // Step 10.e.iii. 4964 if (chunkLength == 2) { 4965 // Step 10.e.iii.1. 4966 if (index == length) { 4967 // Step 10.e.iii.1.a. 4968 if (lastChunkHandling == LastChunkHandling::StopBeforePartial) { 4969 return Base64Result::Ok(read, written()); 4970 } 4971 4972 // Step 10.e.iii.1.b. 4973 return Base64Result::Error(Base64Error::MissingPadding); 4974 } 4975 4976 // Step 10.e.iii.2. 4977 auto ch = chars[index]; 4978 4979 // Step 10.e.iii.3. 4980 if (ch == '=') { 4981 // Step 10.e.iii.3.a. (Inlined SkipAsciiWhitespace) 4982 while (++index < length) { 4983 auto ch = chars[index]; 4984 if (!mozilla::IsAsciiWhitespace(ch)) { 4985 break; 4986 } 4987 } 4988 } 4989 } 4990 4991 // Step 10.e.iv. 4992 if (index < length) { 4993 return Base64Result::ErrorAt(Base64Error::BadCharAfterPadding, index); 4994 } 4995 4996 // Steps 10.e.v-vi. 4997 if (lastChunkHandling == LastChunkHandling::Strict) { 4998 uint32_t extraBitsMask = chunkLength == 2 ? 0xf : 0x3; 4999 if ((chunk & extraBitsMask) != 0) { 5000 return Base64Result::Error(Base64Error::ExtraBits); 5001 } 5002 } 5003 5004 // Step 10.e.vii. 5005 decodePartialChunk(chunk, chunkLength); 5006 5007 // Step 10.e.viii. 5008 return Base64Result::Ok(length, written()); 5009 } 5010 5011 /** 5012 * FromBase64 ( string, alphabet, lastChunkHandling [ , maxLength ] ) 5013 * 5014 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-frombase64 5015 */ 5016 template <class Ops> 5017 static auto FromBase64(JSLinearString* string, Alphabet alphabet, 5018 LastChunkHandling lastChunkHandling, 5019 SharedMem<uint8_t*> data, size_t maxLength) { 5020 JS::AutoCheckCannotGC nogc; 5021 if (string->hasLatin1Chars()) { 5022 return FromBase64<Ops>(string->latin1Chars(nogc), string->length(), 5023 alphabet, lastChunkHandling, data, maxLength); 5024 } 5025 return FromBase64<Ops>(string->twoByteChars(nogc), string->length(), alphabet, 5026 lastChunkHandling, data, maxLength); 5027 } 5028 5029 /** 5030 * FromBase64 ( string, alphabet, lastChunkHandling [ , maxLength ] ) 5031 * 5032 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-frombase64 5033 */ 5034 static auto FromBase64(JSLinearString* string, Alphabet alphabet, 5035 LastChunkHandling lastChunkHandling, 5036 TypedArrayObject* tarray, size_t maxLength) { 5037 MOZ_ASSERT(tarray->type() == Scalar::Uint8); 5038 5039 // The underlying buffer must neither be detached nor shrunk. (It may have 5040 // been grown when it's a growable shared buffer and a concurrent thread 5041 // resized the buffer.) 5042 MOZ_ASSERT(!tarray->hasDetachedBuffer()); 5043 MOZ_ASSERT(tarray->length().valueOr(0) >= maxLength); 5044 5045 auto data = tarray->dataPointerEither().cast<uint8_t*>(); 5046 5047 if (tarray->isSharedMemory()) { 5048 return FromBase64<SharedOps>(string, alphabet, lastChunkHandling, data, 5049 maxLength); 5050 } 5051 return FromBase64<UnsharedOps>(string, alphabet, lastChunkHandling, data, 5052 maxLength); 5053 } 5054 5055 /** 5056 * FromBase64 ( string, alphabet, lastChunkHandling [ , maxLength ] ) 5057 * 5058 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-frombase64 5059 */ 5060 static auto FromBase64(JSLinearString* string, Alphabet alphabet, 5061 LastChunkHandling lastChunkHandling, ByteVector& bytes) { 5062 auto data = SharedMem<uint8_t*>::unshared(bytes.begin()); 5063 size_t maxLength = bytes.length(); 5064 return FromBase64<UnsharedOps>(string, alphabet, lastChunkHandling, data, 5065 maxLength); 5066 } 5067 5068 /** 5069 * Uint8Array.fromBase64 ( string [ , options ] ) 5070 * Uint8Array.prototype.setFromBase64 ( string [ , options ] ) 5071 * Uint8Array.prototype.toBase64 ( [ options ] ) 5072 * 5073 * Helper to retrieve the "alphabet" option. 5074 */ 5075 static bool GetAlphabetOption(JSContext* cx, Handle<JSObject*> options, 5076 Alphabet* result) { 5077 Rooted<Value> value(cx); 5078 if (!GetProperty(cx, options, options, cx->names().alphabet, &value)) { 5079 return false; 5080 } 5081 5082 if (value.isUndefined()) { 5083 *result = Alphabet::Base64; 5084 return true; 5085 } 5086 5087 if (!value.isString()) { 5088 return ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, 5089 value, nullptr, "not a string"); 5090 } 5091 5092 auto* linear = value.toString()->ensureLinear(cx); 5093 if (!linear) { 5094 return false; 5095 } 5096 5097 if (StringEqualsLiteral(linear, "base64")) { 5098 *result = Alphabet::Base64; 5099 return true; 5100 } 5101 5102 if (StringEqualsLiteral(linear, "base64url")) { 5103 *result = Alphabet::Base64Url; 5104 return true; 5105 } 5106 5107 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 5108 JSMSG_TYPED_ARRAY_BAD_BASE64_ALPHABET); 5109 return false; 5110 } 5111 5112 /** 5113 * Uint8Array.fromBase64 ( string [ , options ] ) 5114 * Uint8Array.prototype.setFromBase64 ( string [ , options ] ) 5115 * 5116 * Helper to retrieve the "lastChunkHandling" option. 5117 */ 5118 static bool GetLastChunkHandlingOption(JSContext* cx, Handle<JSObject*> options, 5119 LastChunkHandling* result) { 5120 Rooted<Value> value(cx); 5121 if (!GetProperty(cx, options, options, cx->names().lastChunkHandling, 5122 &value)) { 5123 return false; 5124 } 5125 5126 if (value.isUndefined()) { 5127 *result = LastChunkHandling::Loose; 5128 return true; 5129 } 5130 5131 if (!value.isString()) { 5132 return ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, 5133 value, nullptr, "not a string"); 5134 } 5135 5136 auto* linear = value.toString()->ensureLinear(cx); 5137 if (!linear) { 5138 return false; 5139 } 5140 5141 if (StringEqualsLiteral(linear, "loose")) { 5142 *result = LastChunkHandling::Loose; 5143 return true; 5144 } 5145 5146 if (StringEqualsLiteral(linear, "strict")) { 5147 *result = LastChunkHandling::Strict; 5148 return true; 5149 } 5150 5151 if (StringEqualsLiteral(linear, "stop-before-partial")) { 5152 *result = LastChunkHandling::StopBeforePartial; 5153 return true; 5154 } 5155 5156 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 5157 JSMSG_TYPED_ARRAY_BAD_BASE64_LAST_CHUNK_HANDLING); 5158 return false; 5159 } 5160 5161 enum class OmitPadding : bool { No, Yes }; 5162 5163 /** 5164 * Uint8Array.prototype.toBase64 ( [ options ] ) 5165 * 5166 * Helper to retrieve the "omitPadding" option. 5167 */ 5168 static bool GetOmitPaddingOption(JSContext* cx, Handle<JSObject*> options, 5169 OmitPadding* result) { 5170 Rooted<Value> value(cx); 5171 if (!GetProperty(cx, options, options, cx->names().omitPadding, &value)) { 5172 return false; 5173 } 5174 5175 *result = static_cast<OmitPadding>(JS::ToBoolean(value)); 5176 return true; 5177 } 5178 5179 /** 5180 * Uint8Array.fromBase64 ( string [ , options ] ) 5181 * 5182 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.frombase64 5183 */ 5184 static bool uint8array_fromBase64(JSContext* cx, unsigned argc, Value* vp) { 5185 CallArgs args = CallArgsFromVp(argc, vp); 5186 5187 // Step 1. 5188 if (!args.get(0).isString()) { 5189 return ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, 5190 args.get(0), nullptr, "not a string"); 5191 } 5192 Rooted<JSString*> string(cx, args[0].toString()); 5193 5194 // Steps 2-9. 5195 auto alphabet = Alphabet::Base64; 5196 auto lastChunkHandling = LastChunkHandling::Loose; 5197 if (args.hasDefined(1)) { 5198 // Step 2. (Inlined GetOptionsObject) 5199 Rooted<JSObject*> options( 5200 cx, RequireObjectArg(cx, "options", "fromBase64", args[1])); 5201 if (!options) { 5202 return false; 5203 } 5204 5205 // Steps 3-6. 5206 if (!GetAlphabetOption(cx, options, &alphabet)) { 5207 return false; 5208 } 5209 5210 // Steps 7-9. 5211 if (!GetLastChunkHandlingOption(cx, options, &lastChunkHandling)) { 5212 return false; 5213 } 5214 } 5215 5216 // Compute the output byte length. Four input characters are decoded into 5217 // three bytes, so the output length can't be larger than ⌈length × 3/4⌉. 5218 auto outLength = mozilla::CheckedInt<size_t>{string->length()}; 5219 outLength += 3; 5220 outLength /= 4; 5221 outLength *= 3; 5222 MOZ_ASSERT(outLength.isValid(), "can't overflow"); 5223 5224 static_assert(JSString::MAX_LENGTH <= TypedArrayObject::ByteLengthLimit, 5225 "string length doesn't exceed maximum typed array length"); 5226 5227 // Step 10. 5228 ByteVector bytes(cx); 5229 if (!bytes.resizeUninitialized(outLength.value())) { 5230 return false; 5231 } 5232 5233 JSLinearString* linear = string->ensureLinear(cx); 5234 if (!linear) { 5235 return false; 5236 } 5237 5238 auto result = FromBase64(linear, alphabet, lastChunkHandling, bytes); 5239 if (MOZ_UNLIKELY(result.isError())) { 5240 ReportBase64Error(cx, result, linear); 5241 return false; 5242 } 5243 MOZ_ASSERT(result.index <= linear->length()); 5244 MOZ_ASSERT(result.written <= bytes.length()); 5245 5246 // Step 11. 5247 size_t resultLength = result.written; 5248 5249 // Step 12. 5250 auto* tarray = 5251 TypedArrayObjectTemplate<uint8_t>::fromLength(cx, resultLength); 5252 if (!tarray) { 5253 return false; 5254 } 5255 5256 // Step 13. 5257 auto target = SharedMem<uint8_t*>::unshared(tarray->dataPointerUnshared()); 5258 auto source = SharedMem<uint8_t*>::unshared(bytes.begin()); 5259 UnsharedOps::podCopy(target, source, resultLength); 5260 5261 // Step 14. 5262 args.rval().setObject(*tarray); 5263 return true; 5264 } 5265 5266 /** 5267 * Uint8Array.fromHex ( string ) 5268 * 5269 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.fromhex 5270 */ 5271 static bool uint8array_fromHex(JSContext* cx, unsigned argc, Value* vp) { 5272 CallArgs args = CallArgsFromVp(argc, vp); 5273 5274 // Step 1. 5275 if (!args.get(0).isString()) { 5276 return ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, 5277 args.get(0), nullptr, "not a string"); 5278 } 5279 Rooted<JSString*> string(cx, args[0].toString()); 5280 5281 // FromHex, step 2. 5282 size_t stringLength = string->length(); 5283 5284 // FromHex, step 3. 5285 if (stringLength % 2 != 0) { 5286 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 5287 JSMSG_TYPED_ARRAY_BAD_HEX_STRING_LENGTH); 5288 return false; 5289 } 5290 5291 // Step 4. (Reordered) 5292 size_t resultLength = stringLength / 2; 5293 5294 // Step 5. (Reordered) 5295 Rooted<TypedArrayObject*> tarray( 5296 cx, TypedArrayObjectTemplate<uint8_t>::fromLength(cx, resultLength)); 5297 if (!tarray) { 5298 return false; 5299 } 5300 5301 // Steps 2-3. 5302 if (!FromHex(cx, string, resultLength, tarray)) { 5303 return false; 5304 } 5305 5306 // Step 6. 5307 args.rval().setObject(*tarray); 5308 return true; 5309 } 5310 5311 /** 5312 * Uint8Array.prototype.setFromBase64 ( string [ , options ] ) 5313 * 5314 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.setfrombase64 5315 */ 5316 static bool uint8array_setFromBase64(JSContext* cx, const CallArgs& args) { 5317 Rooted<TypedArrayObject*> tarray( 5318 cx, &args.thisv().toObject().as<TypedArrayObject>()); 5319 5320 // Additional step from Immutable ArrayBuffer proposal. 5321 if (tarray->is<ImmutableTypedArrayObject>()) { 5322 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 5323 JSMSG_ARRAYBUFFER_IMMUTABLE); 5324 return false; 5325 } 5326 5327 // Step 3. 5328 if (!args.get(0).isString()) { 5329 return ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, 5330 args.get(0), nullptr, "not a string"); 5331 } 5332 Rooted<JSString*> string(cx, args[0].toString()); 5333 5334 // Steps 4-11. 5335 auto alphabet = Alphabet::Base64; 5336 auto lastChunkHandling = LastChunkHandling::Loose; 5337 if (args.hasDefined(1)) { 5338 // Step 2. (Inlined GetOptionsObject) 5339 Rooted<JSObject*> options( 5340 cx, RequireObjectArg(cx, "options", "setFromBase64", args[1])); 5341 if (!options) { 5342 return false; 5343 } 5344 5345 // Steps 3-6. 5346 if (!GetAlphabetOption(cx, options, &alphabet)) { 5347 return false; 5348 } 5349 5350 // Steps 7-9. 5351 if (!GetLastChunkHandlingOption(cx, options, &lastChunkHandling)) { 5352 return false; 5353 } 5354 } 5355 5356 // Steps 12-14. 5357 auto length = tarray->length(); 5358 if (!length) { 5359 ReportOutOfBounds(cx, tarray); 5360 return false; 5361 } 5362 5363 // Steps 15-18. 5364 size_t readLength = 0; 5365 size_t written = 0; 5366 if (*length > 0) { 5367 JSLinearString* linear = string->ensureLinear(cx); 5368 if (!linear) { 5369 return false; 5370 } 5371 5372 auto result = 5373 FromBase64(linear, alphabet, lastChunkHandling, tarray, *length); 5374 if (MOZ_UNLIKELY(result.isError())) { 5375 ReportBase64Error(cx, result, linear); 5376 return false; 5377 } 5378 MOZ_ASSERT(result.index <= linear->length()); 5379 MOZ_ASSERT(result.written <= *length); 5380 5381 readLength = result.index; 5382 written = result.written; 5383 } 5384 5385 // Steps 19-21. (Not applicable in our implementation.) 5386 5387 // Step 22. 5388 Rooted<PlainObject*> result(cx, NewPlainObject(cx)); 5389 if (!result) { 5390 return false; 5391 } 5392 5393 // Step 23. 5394 Rooted<Value> readValue(cx, NumberValue(readLength)); 5395 if (!DefineDataProperty(cx, result, cx->names().read, readValue)) { 5396 return false; 5397 } 5398 5399 // Step 24. 5400 Rooted<Value> writtenValue(cx, NumberValue(written)); 5401 if (!DefineDataProperty(cx, result, cx->names().written, writtenValue)) { 5402 return false; 5403 } 5404 5405 // Step 25. 5406 args.rval().setObject(*result); 5407 return true; 5408 } 5409 5410 /** 5411 * Uint8Array.prototype.setFromBase64 ( string [ , options ] ) 5412 * 5413 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.setfrombase64 5414 */ 5415 static bool uint8array_setFromBase64(JSContext* cx, unsigned argc, Value* vp) { 5416 CallArgs args = CallArgsFromVp(argc, vp); 5417 5418 // Steps 1-2. 5419 return CallNonGenericMethod<IsUint8ArrayObject, uint8array_setFromBase64>( 5420 cx, args); 5421 } 5422 5423 /** 5424 * Uint8Array.prototype.setFromHex ( string ) 5425 * 5426 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.setfromhex 5427 */ 5428 static bool uint8array_setFromHex(JSContext* cx, const CallArgs& args) { 5429 Rooted<TypedArrayObject*> tarray( 5430 cx, &args.thisv().toObject().as<TypedArrayObject>()); 5431 5432 // Additional step from Immutable ArrayBuffer proposal. 5433 if (tarray->is<ImmutableTypedArrayObject>()) { 5434 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 5435 JSMSG_ARRAYBUFFER_IMMUTABLE); 5436 return false; 5437 } 5438 5439 // Step 3. 5440 if (!args.get(0).isString()) { 5441 return ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, 5442 args.get(0), nullptr, "not a string"); 5443 } 5444 Rooted<JSString*> string(cx, args[0].toString()); 5445 5446 // Steps 4-6. 5447 auto byteLength = tarray->length(); 5448 if (!byteLength) { 5449 ReportOutOfBounds(cx, tarray); 5450 return false; 5451 } 5452 5453 // FromHex, step 2. 5454 size_t stringLength = string->length(); 5455 5456 // FromHex, step 3. 5457 if (stringLength % 2 != 0) { 5458 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 5459 JSMSG_TYPED_ARRAY_BAD_HEX_STRING_LENGTH); 5460 return false; 5461 } 5462 5463 // |string| encodes |stringLength / 2| bytes, but we can't write more bytes 5464 // than there is space in |tarray|. 5465 size_t maxLength = std::min(*byteLength, stringLength / 2); 5466 5467 // Steps 7-9. 5468 if (!FromHex(cx, string, maxLength, tarray)) { 5469 return false; 5470 } 5471 5472 // Steps 10-13. (Not applicable in our implementation.) 5473 5474 // Step 14. 5475 Rooted<PlainObject*> result(cx, NewPlainObject(cx)); 5476 if (!result) { 5477 return false; 5478 } 5479 5480 // Step 15. 5481 // 5482 // Each byte is encoded in two characters, so the number of read characters is 5483 // exactly twice as large as |maxLength|. 5484 Rooted<Value> readValue(cx, NumberValue(maxLength * 2)); 5485 if (!DefineDataProperty(cx, result, cx->names().read, readValue)) { 5486 return false; 5487 } 5488 5489 // Step 16. 5490 // 5491 // |maxLength| was constrained to the number of bytes written on success. 5492 Rooted<Value> writtenValue(cx, NumberValue(maxLength)); 5493 if (!DefineDataProperty(cx, result, cx->names().written, writtenValue)) { 5494 return false; 5495 } 5496 5497 // Step 17. 5498 args.rval().setObject(*result); 5499 return true; 5500 } 5501 5502 /** 5503 * Uint8Array.prototype.setFromHex ( string ) 5504 * 5505 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.setfromhex 5506 */ 5507 static bool uint8array_setFromHex(JSContext* cx, unsigned argc, Value* vp) { 5508 CallArgs args = CallArgsFromVp(argc, vp); 5509 5510 // Steps 1-2. 5511 return CallNonGenericMethod<IsUint8ArrayObject, uint8array_setFromHex>(cx, 5512 args); 5513 } 5514 5515 template <typename Ops> 5516 static void ToBase64(TypedArrayObject* tarray, size_t length, Alphabet alphabet, 5517 OmitPadding omitPadding, JSStringBuilder& sb) { 5518 const auto& base64Chars = alphabet == Alphabet::Base64 5519 ? Base64::Encode::Base64 5520 : Base64::Encode::Base64Url; 5521 5522 auto encode = [&base64Chars](uint32_t value) { 5523 return base64Chars[value & 0x3f]; 5524 }; 5525 5526 // Our implementation directly converts the bytes to their string 5527 // representation instead of first collecting them into an intermediate list. 5528 auto data = Ops::extract(tarray).template cast<uint8_t*>(); 5529 auto toRead = length; 5530 5531 if (toRead >= 12) { 5532 // Align |data| to uint32_t. 5533 if (MOZ_UNLIKELY(data.unwrapValue() & 3)) { 5534 // Performs at most three iterations until |data| is aligned, reading up 5535 // to nine bytes. 5536 while (data.unwrapValue() & 3) { 5537 // Combine three input bytes into a single uint24 value. 5538 auto byte0 = Ops::load(data++); 5539 auto byte1 = Ops::load(data++); 5540 auto byte2 = Ops::load(data++); 5541 auto u24 = (uint32_t(byte0) << 16) | (uint32_t(byte1) << 8) | byte2; 5542 5543 // Encode the uint24 value as base64. 5544 char chars[] = { 5545 encode(u24 >> 18), 5546 encode(u24 >> 12), 5547 encode(u24 >> 6), 5548 encode(u24 >> 0), 5549 }; 5550 sb.infallibleAppend(chars, sizeof(chars)); 5551 5552 MOZ_ASSERT(toRead >= 3); 5553 toRead -= 3; 5554 } 5555 } 5556 5557 auto data32 = data.template cast<uint32_t*>(); 5558 for (; toRead >= 12; toRead -= 12) { 5559 // Read three 32-bit words. 5560 auto word0 = mozilla::NativeEndian::swapToBigEndian(Ops::load(data32++)); 5561 auto word1 = mozilla::NativeEndian::swapToBigEndian(Ops::load(data32++)); 5562 auto word2 = mozilla::NativeEndian::swapToBigEndian(Ops::load(data32++)); 5563 5564 // Split into four uint24 values. 5565 auto u24_0 = word0 >> 8; 5566 auto u24_1 = (word0 << 16) | (word1 >> 16); 5567 auto u24_2 = (word1 << 8) | (word2 >> 24); 5568 auto u24_3 = word2; 5569 5570 // Encode the uint24 values as base64 and write in blocks of eight 5571 // characters. 5572 char chars1[] = { 5573 encode(u24_0 >> 18), encode(u24_0 >> 12), 5574 encode(u24_0 >> 6), encode(u24_0 >> 0), 5575 5576 encode(u24_1 >> 18), encode(u24_1 >> 12), 5577 encode(u24_1 >> 6), encode(u24_1 >> 0), 5578 }; 5579 sb.infallibleAppend(chars1, sizeof(chars1)); 5580 5581 char chars2[] = { 5582 encode(u24_2 >> 18), encode(u24_2 >> 12), 5583 encode(u24_2 >> 6), encode(u24_2 >> 0), 5584 5585 encode(u24_3 >> 18), encode(u24_3 >> 12), 5586 encode(u24_3 >> 6), encode(u24_3 >> 0), 5587 }; 5588 sb.infallibleAppend(chars2, sizeof(chars2)); 5589 } 5590 data = data32.template cast<uint8_t*>(); 5591 } 5592 5593 for (; toRead >= 3; toRead -= 3) { 5594 // Combine three input bytes into a single uint24 value. 5595 auto byte0 = Ops::load(data++); 5596 auto byte1 = Ops::load(data++); 5597 auto byte2 = Ops::load(data++); 5598 auto u24 = (uint32_t(byte0) << 16) | (uint32_t(byte1) << 8) | byte2; 5599 5600 // Encode the uint24 value as base64. 5601 char chars[] = { 5602 encode(u24 >> 18), 5603 encode(u24 >> 12), 5604 encode(u24 >> 6), 5605 encode(u24 >> 0), 5606 }; 5607 sb.infallibleAppend(chars, sizeof(chars)); 5608 } 5609 5610 // Trailing two and one element bytes are optionally padded with '='. 5611 if (toRead == 2) { 5612 // Combine two input bytes into a single uint24 value. 5613 auto byte0 = Ops::load(data++); 5614 auto byte1 = Ops::load(data++); 5615 auto u24 = (uint32_t(byte0) << 16) | (uint32_t(byte1) << 8); 5616 5617 // Encode the uint24 value as base64, optionally including padding. 5618 sb.infallibleAppend(encode(u24 >> 18)); 5619 sb.infallibleAppend(encode(u24 >> 12)); 5620 sb.infallibleAppend(encode(u24 >> 6)); 5621 if (omitPadding == OmitPadding::No) { 5622 sb.infallibleAppend('='); 5623 } 5624 } else if (toRead == 1) { 5625 // Combine one input byte into a single uint24 value. 5626 auto byte0 = Ops::load(data++); 5627 auto u24 = uint32_t(byte0) << 16; 5628 5629 // Encode the uint24 value as base64, optionally including padding. 5630 sb.infallibleAppend(encode(u24 >> 18)); 5631 sb.infallibleAppend(encode(u24 >> 12)); 5632 if (omitPadding == OmitPadding::No) { 5633 sb.infallibleAppend('='); 5634 sb.infallibleAppend('='); 5635 } 5636 } else { 5637 MOZ_ASSERT(toRead == 0); 5638 } 5639 } 5640 5641 /** 5642 * Uint8Array.prototype.toBase64 ( [ options ] ) 5643 * 5644 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.tobase64 5645 */ 5646 static bool uint8array_toBase64(JSContext* cx, const CallArgs& args) { 5647 Rooted<TypedArrayObject*> tarray( 5648 cx, &args.thisv().toObject().as<TypedArrayObject>()); 5649 5650 // Steps 3-7. 5651 auto alphabet = Alphabet::Base64; 5652 auto omitPadding = OmitPadding::No; 5653 if (args.hasDefined(0)) { 5654 // Step 3. (Inlined GetOptionsObject) 5655 Rooted<JSObject*> options( 5656 cx, RequireObjectArg(cx, "options", "toBase64", args[0])); 5657 if (!options) { 5658 return false; 5659 } 5660 5661 // Steps 4-6. 5662 if (!GetAlphabetOption(cx, options, &alphabet)) { 5663 return false; 5664 } 5665 5666 // Step 7. 5667 if (!GetOmitPaddingOption(cx, options, &omitPadding)) { 5668 return false; 5669 } 5670 } 5671 5672 // Step 8. (Partial) 5673 auto length = tarray->length(); 5674 if (!length) { 5675 ReportOutOfBounds(cx, tarray); 5676 return false; 5677 } 5678 5679 // Compute the output string length. Three input bytes are encoded as four 5680 // characters, so the output length is ⌈length × 4/3⌉. When omitting padding, 5681 // the output length is length + ⌈length / 3⌉. 5682 auto outLength = mozilla::CheckedInt<size_t>{*length}; 5683 outLength += 2; 5684 outLength /= 3; 5685 if (omitPadding == OmitPadding::No) { 5686 outLength *= 4; 5687 } else { 5688 outLength += *length; 5689 } 5690 if (!outLength.isValid() || outLength.value() > JSString::MAX_LENGTH) { 5691 ReportAllocationOverflow(cx); 5692 return false; 5693 } 5694 5695 JSStringBuilder sb(cx); 5696 if (!sb.reserve(outLength.value())) { 5697 return false; 5698 } 5699 5700 // Steps 9-10. 5701 if (tarray->isSharedMemory()) { 5702 ToBase64<SharedOps>(tarray, *length, alphabet, omitPadding, sb); 5703 } else { 5704 ToBase64<UnsharedOps>(tarray, *length, alphabet, omitPadding, sb); 5705 } 5706 5707 MOZ_ASSERT(sb.length() == outLength.value(), "all characters were written"); 5708 5709 // Step 11. 5710 auto* str = sb.finishString(); 5711 if (!str) { 5712 return false; 5713 } 5714 5715 args.rval().setString(str); 5716 return true; 5717 } 5718 5719 /** 5720 * Uint8Array.prototype.toBase64 ( [ options ] ) 5721 * 5722 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.tobase64 5723 */ 5724 static bool uint8array_toBase64(JSContext* cx, unsigned argc, Value* vp) { 5725 CallArgs args = CallArgsFromVp(argc, vp); 5726 5727 // Steps 1-2. 5728 return CallNonGenericMethod<IsUint8ArrayObject, uint8array_toBase64>(cx, 5729 args); 5730 } 5731 5732 template <typename Ops> 5733 static void ToHex(TypedArrayObject* tarray, size_t length, 5734 JSStringBuilder& sb) { 5735 // NB: Lower case hex digits. 5736 static constexpr char HexDigits[] = "0123456789abcdef"; 5737 static_assert(std::char_traits<char>::length(HexDigits) == 16); 5738 5739 // Process multiple bytes per loop to reduce number of calls to 5740 // infallibleAppend. Choose four bytes because tested compilers can optimize 5741 // this amount of bytes into a single write operation. 5742 constexpr size_t BYTES_PER_LOOP = 4; 5743 5744 size_t alignedLength = length & ~(BYTES_PER_LOOP - 1); 5745 5746 // Steps 3 and 5. 5747 // 5748 // Our implementation directly converts the bytes to their string 5749 // representation instead of first collecting them into an intermediate list. 5750 auto data = Ops::extract(tarray).template cast<uint8_t*>(); 5751 for (size_t index = 0; index < alignedLength;) { 5752 char chars[BYTES_PER_LOOP * 2]; 5753 5754 for (size_t i = 0; i < BYTES_PER_LOOP; ++i) { 5755 auto byte = Ops::load(data + index++); 5756 chars[i * 2 + 0] = HexDigits[byte >> 4]; 5757 chars[i * 2 + 1] = HexDigits[byte & 0xf]; 5758 } 5759 5760 sb.infallibleAppend(chars, sizeof(chars)); 5761 } 5762 5763 // Write the remaining characters. 5764 for (size_t index = alignedLength; index < length;) { 5765 char chars[2]; 5766 5767 auto byte = Ops::load(data + index++); 5768 chars[0] = HexDigits[byte >> 4]; 5769 chars[1] = HexDigits[byte & 0xf]; 5770 5771 sb.infallibleAppend(chars, sizeof(chars)); 5772 } 5773 } 5774 5775 /** 5776 * Uint8Array.prototype.toHex ( ) 5777 * 5778 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.tohex 5779 */ 5780 static bool uint8array_toHex(JSContext* cx, const CallArgs& args) { 5781 Rooted<TypedArrayObject*> tarray( 5782 cx, &args.thisv().toObject().as<TypedArrayObject>()); 5783 5784 // Step 3. (Partial) 5785 auto length = tarray->length(); 5786 if (!length) { 5787 ReportOutOfBounds(cx, tarray); 5788 return false; 5789 } 5790 5791 // |length| is limited by |ByteLengthLimit|, which ensures that multiplying it 5792 // by two won't overflow. 5793 static_assert(TypedArrayObject::ByteLengthLimit <= 5794 std::numeric_limits<size_t>::max() / 2); 5795 MOZ_ASSERT(*length <= TypedArrayObject::ByteLengthLimit); 5796 5797 // Compute the output string length. Each byte is encoded as two characters, 5798 // so the output length is exactly twice as large as |length|. 5799 size_t outLength = *length * 2; 5800 if (outLength > JSString::MAX_LENGTH) { 5801 ReportAllocationOverflow(cx); 5802 return false; 5803 } 5804 5805 // Step 4. 5806 JSStringBuilder sb(cx); 5807 if (!sb.reserve(outLength)) { 5808 return false; 5809 } 5810 5811 // Steps 3 and 5. 5812 if (tarray->isSharedMemory()) { 5813 ToHex<SharedOps>(tarray, *length, sb); 5814 } else { 5815 ToHex<UnsharedOps>(tarray, *length, sb); 5816 } 5817 5818 MOZ_ASSERT(sb.length() == outLength, "all characters were written"); 5819 5820 // Step 6. 5821 auto* str = sb.finishString(); 5822 if (!str) { 5823 return false; 5824 } 5825 5826 args.rval().setString(str); 5827 return true; 5828 } 5829 5830 /** 5831 * Uint8Array.prototype.toHex ( ) 5832 * 5833 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.tohex 5834 */ 5835 static bool uint8array_toHex(JSContext* cx, unsigned argc, Value* vp) { 5836 CallArgs args = CallArgsFromVp(argc, vp); 5837 5838 // Steps 1-2. 5839 return CallNonGenericMethod<IsUint8ArrayObject, uint8array_toHex>(cx, args); 5840 } 5841 5842 /* static */ const JSFunctionSpec TypedArrayObject::protoFunctions[] = { 5843 JS_INLINABLE_FN("subarray", TypedArray_subarray, 2, 0, TypedArraySubarray), 5844 JS_INLINABLE_FN("set", TypedArray_set, 1, 0, TypedArraySet), 5845 JS_FN("copyWithin", TypedArray_copyWithin, 2, 0), 5846 JS_SELF_HOSTED_FN("every", "TypedArrayEvery", 1, 0), 5847 JS_INLINABLE_FN("fill", TypedArray_fill, 1, 0, TypedArrayFill), 5848 JS_SELF_HOSTED_FN("filter", "TypedArrayFilter", 1, 0), 5849 JS_SELF_HOSTED_FN("find", "TypedArrayFind", 1, 0), 5850 JS_SELF_HOSTED_FN("findIndex", "TypedArrayFindIndex", 1, 0), 5851 JS_SELF_HOSTED_FN("findLast", "TypedArrayFindLast", 1, 0), 5852 JS_SELF_HOSTED_FN("findLastIndex", "TypedArrayFindLastIndex", 1, 0), 5853 JS_SELF_HOSTED_FN("forEach", "TypedArrayForEach", 1, 0), 5854 JS_FN("indexOf", TypedArray_indexOf, 1, 0), 5855 JS_FN("join", TypedArray_join, 1, 0), 5856 JS_FN("lastIndexOf", TypedArray_lastIndexOf, 1, 0), 5857 JS_SELF_HOSTED_FN("map", "TypedArrayMap", 1, 0), 5858 JS_SELF_HOSTED_FN("reduce", "TypedArrayReduce", 1, 0), 5859 JS_SELF_HOSTED_FN("reduceRight", "TypedArrayReduceRight", 1, 0), 5860 JS_FN("reverse", TypedArray_reverse, 0, 0), 5861 JS_FN("slice", TypedArray_slice, 2, 0), 5862 JS_SELF_HOSTED_FN("some", "TypedArraySome", 1, 0), 5863 JS_TRAMPOLINE_FN("sort", TypedArrayObject::sort, 1, 0, TypedArraySort), 5864 JS_SELF_HOSTED_FN("entries", "TypedArrayEntries", 0, 0), 5865 JS_SELF_HOSTED_FN("keys", "TypedArrayKeys", 0, 0), 5866 JS_SELF_HOSTED_FN("values", "$TypedArrayValues", 0, 0), 5867 JS_SELF_HOSTED_SYM_FN(iterator, "$TypedArrayValues", 0, 0), 5868 JS_FN("includes", TypedArray_includes, 1, 0), 5869 JS_SELF_HOSTED_FN("toString", "ArrayToString", 0, 0), 5870 JS_SELF_HOSTED_FN("toLocaleString", "TypedArrayToLocaleString", 2, 0), 5871 JS_SELF_HOSTED_FN("at", "TypedArrayAt", 1, 0), 5872 JS_FN("toReversed", TypedArray_toReversed, 0, 0), 5873 JS_SELF_HOSTED_FN("toSorted", "TypedArrayToSorted", 1, 0), 5874 JS_FN("with", TypedArray_with, 2, 0), 5875 JS_FS_END, 5876 }; 5877 5878 /* static */ const JSFunctionSpec TypedArrayObject::staticFunctions[] = { 5879 JS_SELF_HOSTED_FN("from", "TypedArrayStaticFrom", 3, 0), 5880 JS_SELF_HOSTED_FN("of", "TypedArrayStaticOf", 0, 0), 5881 JS_FS_END, 5882 }; 5883 5884 /* static */ const JSPropertySpec TypedArrayObject::staticProperties[] = { 5885 JS_SELF_HOSTED_SYM_GET(species, "$TypedArraySpecies", 0), 5886 JS_PS_END, 5887 }; 5888 5889 static JSObject* CreateSharedTypedArrayPrototype(JSContext* cx, 5890 JSProtoKey key) { 5891 return GlobalObject::createBlankPrototype( 5892 cx, cx->global(), &TypedArrayObject::sharedTypedArrayPrototypeClass); 5893 } 5894 5895 static const ClassSpec TypedArrayObjectSharedTypedArrayPrototypeClassSpec = { 5896 GenericCreateConstructor<TypedArrayConstructor, 0, gc::AllocKind::FUNCTION>, 5897 CreateSharedTypedArrayPrototype, 5898 TypedArrayObject::staticFunctions, 5899 TypedArrayObject::staticProperties, 5900 TypedArrayObject::protoFunctions, 5901 TypedArrayObject::protoAccessors, 5902 GenericFinishInit<WhichHasRealmFuseProperty::ProtoAndCtor>, 5903 ClassSpec::DontDefineConstructor, 5904 }; 5905 5906 /* static */ const JSClass TypedArrayObject::sharedTypedArrayPrototypeClass = { 5907 "TypedArrayPrototype", 5908 JSCLASS_HAS_CACHED_PROTO(JSProto_TypedArray), 5909 JS_NULL_CLASS_OPS, 5910 &TypedArrayObjectSharedTypedArrayPrototypeClassSpec, 5911 }; 5912 5913 namespace { 5914 5915 // This default implementation is only valid for integer types less 5916 // than 32-bits in size. 5917 template <typename NativeType> 5918 bool TypedArrayObjectTemplate<NativeType>::getElementPure( 5919 TypedArrayObject* tarray, size_t index, Value* vp) { 5920 static_assert(sizeof(NativeType) < 4, 5921 "this method must only handle NativeType values that are " 5922 "always exact int32_t values"); 5923 5924 *vp = Int32Value(getIndex(tarray, index)); 5925 return true; 5926 } 5927 5928 // We need to specialize for floats and other integer types. 5929 template <> 5930 bool TypedArrayObjectTemplate<int32_t>::getElementPure(TypedArrayObject* tarray, 5931 size_t index, 5932 Value* vp) { 5933 *vp = Int32Value(getIndex(tarray, index)); 5934 return true; 5935 } 5936 5937 template <> 5938 bool TypedArrayObjectTemplate<uint32_t>::getElementPure( 5939 TypedArrayObject* tarray, size_t index, Value* vp) { 5940 uint32_t val = getIndex(tarray, index); 5941 *vp = NumberValue(val); 5942 return true; 5943 } 5944 5945 template <> 5946 bool TypedArrayObjectTemplate<float16>::getElementPure(TypedArrayObject* tarray, 5947 size_t index, 5948 Value* vp) { 5949 float16 f16 = getIndex(tarray, index); 5950 /* 5951 * Doubles in typed arrays could be typed-punned arrays of integers. This 5952 * could allow user code to break the engine-wide invariant that only 5953 * canonical nans are stored into jsvals, which means user code could 5954 * confuse the engine into interpreting a double-typed jsval as an 5955 * object-typed jsval. 5956 * 5957 * This could be removed for platforms/compilers known to convert a 32-bit 5958 * non-canonical nan to a 64-bit canonical nan. 5959 */ 5960 *vp = JS::CanonicalizedDoubleValue(static_cast<double>(f16)); 5961 return true; 5962 } 5963 5964 template <> 5965 bool TypedArrayObjectTemplate<float>::getElementPure(TypedArrayObject* tarray, 5966 size_t index, Value* vp) { 5967 float val = getIndex(tarray, index); 5968 double dval = val; 5969 5970 /* 5971 * Doubles in typed arrays could be typed-punned arrays of integers. This 5972 * could allow user code to break the engine-wide invariant that only 5973 * canonical nans are stored into jsvals, which means user code could 5974 * confuse the engine into interpreting a double-typed jsval as an 5975 * object-typed jsval. 5976 * 5977 * This could be removed for platforms/compilers known to convert a 32-bit 5978 * non-canonical nan to a 64-bit canonical nan. 5979 */ 5980 *vp = JS::CanonicalizedDoubleValue(dval); 5981 return true; 5982 } 5983 5984 template <> 5985 bool TypedArrayObjectTemplate<double>::getElementPure(TypedArrayObject* tarray, 5986 size_t index, Value* vp) { 5987 double val = getIndex(tarray, index); 5988 5989 /* 5990 * Doubles in typed arrays could be typed-punned arrays of integers. This 5991 * could allow user code to break the engine-wide invariant that only 5992 * canonical nans are stored into jsvals, which means user code could 5993 * confuse the engine into interpreting a double-typed jsval as an 5994 * object-typed jsval. 5995 */ 5996 *vp = JS::CanonicalizedDoubleValue(val); 5997 return true; 5998 } 5999 6000 template <> 6001 bool TypedArrayObjectTemplate<int64_t>::getElementPure(TypedArrayObject* tarray, 6002 size_t index, 6003 Value* vp) { 6004 return false; 6005 } 6006 6007 template <> 6008 bool TypedArrayObjectTemplate<uint64_t>::getElementPure( 6009 TypedArrayObject* tarray, size_t index, Value* vp) { 6010 return false; 6011 } 6012 } /* anonymous namespace */ 6013 6014 namespace { 6015 6016 template <typename NativeType> 6017 bool TypedArrayObjectTemplate<NativeType>::getElement(JSContext* cx, 6018 TypedArrayObject* tarray, 6019 size_t index, 6020 MutableHandleValue val) { 6021 MOZ_ALWAYS_TRUE(getElementPure(tarray, index, val.address())); 6022 return true; 6023 } 6024 6025 template <> 6026 bool TypedArrayObjectTemplate<int64_t>::getElement(JSContext* cx, 6027 TypedArrayObject* tarray, 6028 size_t index, 6029 MutableHandleValue val) { 6030 int64_t n = getIndex(tarray, index); 6031 BigInt* res = BigInt::createFromInt64(cx, n); 6032 if (!res) { 6033 return false; 6034 } 6035 val.setBigInt(res); 6036 return true; 6037 } 6038 6039 template <> 6040 bool TypedArrayObjectTemplate<uint64_t>::getElement(JSContext* cx, 6041 TypedArrayObject* tarray, 6042 size_t index, 6043 MutableHandleValue val) { 6044 uint64_t n = getIndex(tarray, index); 6045 BigInt* res = BigInt::createFromUint64(cx, n); 6046 if (!res) { 6047 return false; 6048 } 6049 val.setBigInt(res); 6050 return true; 6051 } 6052 } /* anonymous namespace */ 6053 6054 namespace js { 6055 6056 template <> 6057 bool TypedArrayObject::getElement<CanGC>(JSContext* cx, size_t index, 6058 MutableHandleValue val) { 6059 switch (type()) { 6060 #define GET_ELEMENT(_, T, N) \ 6061 case Scalar::N: \ 6062 return TypedArrayObjectTemplate<T>::getElement(cx, this, index, val); 6063 JS_FOR_EACH_TYPED_ARRAY(GET_ELEMENT) 6064 #undef GET_ELEMENT 6065 case Scalar::MaxTypedArrayViewType: 6066 case Scalar::Int64: 6067 case Scalar::Simd128: 6068 break; 6069 } 6070 6071 MOZ_CRASH("Unknown TypedArray type"); 6072 } 6073 6074 template <> 6075 bool TypedArrayObject::getElement<NoGC>( 6076 JSContext* cx, size_t index, 6077 typename MaybeRooted<Value, NoGC>::MutableHandleType vp) { 6078 return getElementPure(index, vp.address()); 6079 } 6080 6081 } // namespace js 6082 6083 bool TypedArrayObject::getElementPure(size_t index, Value* vp) { 6084 switch (type()) { 6085 #define GET_ELEMENT_PURE(_, T, N) \ 6086 case Scalar::N: \ 6087 return TypedArrayObjectTemplate<T>::getElementPure(this, index, vp); 6088 JS_FOR_EACH_TYPED_ARRAY(GET_ELEMENT_PURE) 6089 #undef GET_ELEMENT 6090 case Scalar::MaxTypedArrayViewType: 6091 case Scalar::Int64: 6092 case Scalar::Simd128: 6093 break; 6094 } 6095 6096 MOZ_CRASH("Unknown TypedArray type"); 6097 } 6098 6099 /* static */ 6100 bool TypedArrayObject::getElements(JSContext* cx, 6101 Handle<TypedArrayObject*> tarray, 6102 size_t length, Value* vp) { 6103 MOZ_ASSERT(length <= tarray->length().valueOr(0)); 6104 MOZ_ASSERT_IF(length > 0, !tarray->hasDetachedBuffer()); 6105 6106 switch (tarray->type()) { 6107 #define GET_ELEMENTS(_, T, N) \ 6108 case Scalar::N: \ 6109 for (size_t i = 0; i < length; ++i, ++vp) { \ 6110 if (!TypedArrayObjectTemplate<T>::getElement( \ 6111 cx, tarray, i, MutableHandleValue::fromMarkedLocation(vp))) { \ 6112 return false; \ 6113 } \ 6114 } \ 6115 return true; 6116 JS_FOR_EACH_TYPED_ARRAY(GET_ELEMENTS) 6117 #undef GET_ELEMENTS 6118 case Scalar::MaxTypedArrayViewType: 6119 case Scalar::Int64: 6120 case Scalar::Simd128: 6121 break; 6122 } 6123 6124 MOZ_CRASH("Unknown TypedArray type"); 6125 } 6126 6127 /*** 6128 *** JS impl 6129 ***/ 6130 6131 /* 6132 * TypedArrayObject boilerplate 6133 */ 6134 6135 static const JSClassOps TypedArrayClassOps = { 6136 nullptr, // addProperty 6137 nullptr, // delProperty 6138 nullptr, // enumerate 6139 nullptr, // newEnumerate 6140 nullptr, // resolve 6141 nullptr, // mayResolve 6142 FixedLengthTypedArrayObject::finalize, // finalize 6143 nullptr, // call 6144 nullptr, // construct 6145 ArrayBufferViewObject::trace, // trace 6146 }; 6147 6148 static const JSClassOps ResizableTypedArrayClassOps = { 6149 nullptr, // addProperty 6150 nullptr, // delProperty 6151 nullptr, // enumerate 6152 nullptr, // newEnumerate 6153 nullptr, // resolve 6154 nullptr, // mayResolve 6155 nullptr, // finalize 6156 nullptr, // call 6157 nullptr, // construct 6158 ArrayBufferViewObject::trace, // trace 6159 }; 6160 6161 static const JSClassOps ImmutableTypedArrayClassOps = { 6162 nullptr, // addProperty 6163 nullptr, // delProperty 6164 nullptr, // enumerate 6165 nullptr, // newEnumerate 6166 nullptr, // resolve 6167 nullptr, // mayResolve 6168 nullptr, // finalize 6169 nullptr, // call 6170 nullptr, // construct 6171 ArrayBufferViewObject::trace, // trace 6172 }; 6173 6174 static const ClassExtension TypedArrayClassExtension = { 6175 FixedLengthTypedArrayObject::objectMoved, // objectMovedOp 6176 }; 6177 6178 static const JSPropertySpec 6179 static_prototype_properties[Scalar::MaxTypedArrayViewType][2] = { 6180 #define IMPL_TYPED_ARRAY_PROPERTIES(ExternalType, NativeType, Name) \ 6181 { \ 6182 JS_INT32_PS("BYTES_PER_ELEMENT", \ 6183 TypedArrayObjectTemplate<NativeType>::BYTES_PER_ELEMENT, \ 6184 JSPROP_READONLY | JSPROP_PERMANENT), \ 6185 JS_PS_END, \ 6186 }, 6187 6188 JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_PROPERTIES) 6189 #undef IMPL_TYPED_ARRAY_PROPERTIES 6190 }; 6191 6192 static const JSFunctionSpec uint8array_static_methods[] = { 6193 JS_FN("fromBase64", uint8array_fromBase64, 1, 0), 6194 JS_FN("fromHex", uint8array_fromHex, 1, 0), 6195 JS_FS_END, 6196 }; 6197 6198 static const JSFunctionSpec uint8array_methods[] = { 6199 JS_FN("setFromBase64", uint8array_setFromBase64, 1, 0), 6200 JS_FN("setFromHex", uint8array_setFromHex, 1, 0), 6201 JS_FN("toBase64", uint8array_toBase64, 0, 0), 6202 JS_FN("toHex", uint8array_toHex, 0, 0), 6203 JS_FS_END, 6204 }; 6205 6206 static constexpr const JSFunctionSpec* TypedArrayStaticMethods( 6207 Scalar::Type type) { 6208 if (type == Scalar::Uint8) { 6209 return uint8array_static_methods; 6210 } 6211 return nullptr; 6212 } 6213 6214 static constexpr const JSFunctionSpec* TypedArrayMethods(Scalar::Type type) { 6215 if (type == Scalar::Uint8) { 6216 return uint8array_methods; 6217 } 6218 return nullptr; 6219 } 6220 6221 static const ClassSpec 6222 TypedArrayObjectClassSpecs[Scalar::MaxTypedArrayViewType] = { 6223 #define IMPL_TYPED_ARRAY_CLASS_SPEC(ExternalType, NativeType, Name) \ 6224 { \ 6225 TypedArrayObjectTemplate<NativeType>::createConstructor, \ 6226 TypedArrayObjectTemplate<NativeType>::createPrototype, \ 6227 TypedArrayStaticMethods(Scalar::Type::Name), \ 6228 static_prototype_properties[Scalar::Type::Name], \ 6229 TypedArrayMethods(Scalar::Type::Name), \ 6230 static_prototype_properties[Scalar::Type::Name], \ 6231 GenericFinishInit<WhichHasRealmFuseProperty::Proto>, \ 6232 JSProto_TypedArray, \ 6233 }, 6234 6235 JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_CLASS_SPEC) 6236 #undef IMPL_TYPED_ARRAY_CLASS_SPEC 6237 }; 6238 6239 // Class definitions for fixed length, resizable, and immutable typed arrays. 6240 // Stored into a 2-dimensional array to ensure the classes are in contiguous 6241 // memory. 6242 const JSClass TypedArrayObject::anyClasses[3][Scalar::MaxTypedArrayViewType] = { 6243 // Class definitions for fixed length typed arrays. 6244 { 6245 #define IMPL_TYPED_ARRAY_CLASS(ExternalType, NativeType, Name) \ 6246 { \ 6247 #Name "Array", \ 6248 JSCLASS_HAS_RESERVED_SLOTS(TypedArrayObject::RESERVED_SLOTS) | \ 6249 JSCLASS_HAS_CACHED_PROTO(JSProto_##Name##Array) | \ 6250 JSCLASS_DELAY_METADATA_BUILDER | JSCLASS_SKIP_NURSERY_FINALIZE | \ 6251 JSCLASS_BACKGROUND_FINALIZE, \ 6252 &TypedArrayClassOps, \ 6253 &TypedArrayObjectClassSpecs[Scalar::Type::Name], \ 6254 &TypedArrayClassExtension, \ 6255 }, 6256 6257 JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_CLASS) 6258 #undef IMPL_TYPED_ARRAY_CLASS 6259 }, 6260 6261 // Class definitions for immutable typed arrays. 6262 { 6263 #define IMPL_TYPED_ARRAY_CLASS(ExternalType, NativeType, Name) \ 6264 { \ 6265 #Name "Array", \ 6266 JSCLASS_HAS_RESERVED_SLOTS(ImmutableTypedArrayObject::RESERVED_SLOTS) | \ 6267 JSCLASS_HAS_CACHED_PROTO(JSProto_##Name##Array) | \ 6268 JSCLASS_DELAY_METADATA_BUILDER, \ 6269 &ImmutableTypedArrayClassOps, \ 6270 &TypedArrayObjectClassSpecs[Scalar::Type::Name], \ 6271 JS_NULL_CLASS_EXT, \ 6272 }, 6273 6274 JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_CLASS) 6275 #undef IMPL_TYPED_ARRAY_CLASS 6276 }, 6277 6278 // Class definitions for resizable typed arrays. 6279 { 6280 #define IMPL_TYPED_ARRAY_CLASS(ExternalType, NativeType, Name) \ 6281 { \ 6282 #Name "Array", \ 6283 JSCLASS_HAS_RESERVED_SLOTS(ResizableTypedArrayObject::RESERVED_SLOTS) | \ 6284 JSCLASS_HAS_CACHED_PROTO(JSProto_##Name##Array) | \ 6285 JSCLASS_DELAY_METADATA_BUILDER, \ 6286 &ResizableTypedArrayClassOps, \ 6287 &TypedArrayObjectClassSpecs[Scalar::Type::Name], \ 6288 JS_NULL_CLASS_EXT, \ 6289 }, 6290 6291 JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_CLASS) 6292 #undef IMPL_TYPED_ARRAY_CLASS 6293 }, 6294 }; 6295 6296 // The various typed array prototypes are supposed to 1) be normal objects, 6297 // 2) stringify to "[object <name of constructor>]", and 3) (Gecko-specific) 6298 // be xrayable. The first and second requirements mandate (in the absence of 6299 // @@toStringTag) a custom class. The third requirement mandates that each 6300 // prototype's class have the relevant typed array's cached JSProtoKey in them. 6301 // Thus we need one class with cached prototype per kind of typed array, with a 6302 // delegated ClassSpec. 6303 // 6304 // Actually ({}).toString.call(Uint8Array.prototype) should throw, because 6305 // Uint8Array.prototype lacks the the typed array internal slots. (Same as 6306 // with %TypedArray%.prototype.) It's not clear this is desirable (see 6307 // above), but it's what we've always done, so keep doing it till we 6308 // implement @@toStringTag or ES6 changes. 6309 const JSClass TypedArrayObject::protoClasses[Scalar::MaxTypedArrayViewType] = { 6310 #define IMPL_TYPED_ARRAY_PROTO_CLASS(ExternalType, NativeType, Name) \ 6311 { \ 6312 #Name "Array.prototype", \ 6313 JSCLASS_HAS_CACHED_PROTO(JSProto_##Name##Array), \ 6314 JS_NULL_CLASS_OPS, \ 6315 &TypedArrayObjectClassSpecs[Scalar::Type::Name], \ 6316 }, 6317 6318 JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_PROTO_CLASS) 6319 #undef IMPL_TYPED_ARRAY_PROTO_CLASS 6320 }; 6321 6322 bool js::IsTypedArrayConstructor(const JSObject* obj) { 6323 #define CHECK_TYPED_ARRAY_CONSTRUCTOR(_, T, N) \ 6324 if (IsNativeFunction(obj, TypedArrayObjectTemplate<T>::class_constructor)) { \ 6325 return true; \ 6326 } 6327 JS_FOR_EACH_TYPED_ARRAY(CHECK_TYPED_ARRAY_CONSTRUCTOR) 6328 #undef CHECK_TYPED_ARRAY_CONSTRUCTOR 6329 return false; 6330 } 6331 6332 bool js::IsTypedArrayConstructor(HandleValue v, Scalar::Type type) { 6333 return IsNativeFunction(v, TypedArrayConstructorNative(type)); 6334 } 6335 6336 JSNative js::TypedArrayConstructorNative(Scalar::Type type) { 6337 #define TYPED_ARRAY_CONSTRUCTOR_NATIVE(_, T, N) \ 6338 if (type == Scalar::N) { \ 6339 return TypedArrayObjectTemplate<T>::class_constructor; \ 6340 } 6341 JS_FOR_EACH_TYPED_ARRAY(TYPED_ARRAY_CONSTRUCTOR_NATIVE) 6342 #undef TYPED_ARRAY_CONSTRUCTOR_NATIVE 6343 6344 MOZ_CRASH("unexpected typed array type"); 6345 } 6346 6347 Scalar::Type js::TypedArrayConstructorType(const JSFunction* fun) { 6348 if (!fun->isNativeFun()) { 6349 return Scalar::MaxTypedArrayViewType; 6350 } 6351 6352 #define CHECK_TYPED_ARRAY_CONSTRUCTOR(_, T, N) \ 6353 if (fun->native() == TypedArrayObjectTemplate<T>::class_constructor) { \ 6354 return Scalar::N; \ 6355 } 6356 JS_FOR_EACH_TYPED_ARRAY(CHECK_TYPED_ARRAY_CONSTRUCTOR) 6357 #undef CHECK_TYPED_ARRAY_CONSTRUCTOR 6358 6359 return Scalar::MaxTypedArrayViewType; 6360 } 6361 6362 bool js::IsBufferSource(JSContext* cx, JSObject* object, bool allowShared, 6363 bool allowResizable, SharedMem<uint8_t*>* dataPointer, 6364 size_t* byteLength) { 6365 if (object->is<TypedArrayObject>()) { 6366 Rooted<TypedArrayObject*> view(cx, &object->as<TypedArrayObject>()); 6367 if (!allowShared && view->isSharedMemory()) { 6368 return false; 6369 } 6370 if (!allowResizable && JS::IsResizableArrayBufferView(object)) { 6371 return false; 6372 } 6373 // Ensure the pointer we pass out won't move as long as you properly root 6374 // it. This is only needed for non-shared memory. 6375 if (!view->isSharedMemory() && 6376 !ArrayBufferViewObject::ensureNonInline(cx, view)) { 6377 return false; 6378 } 6379 *dataPointer = view->dataPointerEither().cast<uint8_t*>(); 6380 *byteLength = view->byteLength().valueOr(0); 6381 return true; 6382 } 6383 6384 if (object->is<DataViewObject>()) { 6385 Rooted<DataViewObject*> view(cx, &object->as<DataViewObject>()); 6386 if (!allowShared && view->isSharedMemory()) { 6387 return false; 6388 } 6389 if (!allowResizable && JS::IsResizableArrayBufferView(object)) { 6390 return false; 6391 } 6392 // Ensure the pointer we pass out won't move as long as you properly root 6393 // it. This is only needed for non-shared memory. 6394 if (!view->isSharedMemory() && 6395 !ArrayBufferViewObject::ensureNonInline(cx, view)) { 6396 return false; 6397 } 6398 *dataPointer = view->dataPointerEither().cast<uint8_t*>(); 6399 *byteLength = view->byteLength().valueOr(0); 6400 return true; 6401 } 6402 6403 if (object->is<ArrayBufferObject>()) { 6404 Rooted<ArrayBufferObject*> buffer(cx, &object->as<ArrayBufferObject>()); 6405 if (!allowResizable && buffer->isResizable()) { 6406 return false; 6407 } 6408 // Ensure the pointer we pass out won't move as long as you properly root 6409 // it. 6410 if (!ArrayBufferObject::ensureNonInline(cx, buffer)) { 6411 return false; 6412 } 6413 *dataPointer = buffer->dataPointerEither(); 6414 *byteLength = buffer->byteLength(); 6415 return true; 6416 } 6417 6418 if (allowShared && object->is<SharedArrayBufferObject>()) { 6419 SharedArrayBufferObject& buffer = object->as<SharedArrayBufferObject>(); 6420 if (!allowResizable && buffer.isResizable()) { 6421 return false; 6422 } 6423 // This will always be locked and out of line. 6424 *dataPointer = buffer.dataPointerShared(); 6425 *byteLength = buffer.byteLength(); 6426 return true; 6427 } 6428 6429 return false; 6430 } 6431 6432 template <typename CharT> 6433 static inline bool StringIsInfinity(mozilla::Range<const CharT> s) { 6434 static constexpr std::string_view Infinity = "Infinity"; 6435 6436 // Compilers optimize this to one |cmp| instruction on x64 resp. two for x86, 6437 // when the input is a Latin-1 string, because the string "Infinity" is 6438 // exactly eight characters long, so it can be represented as a single uint64 6439 // value. 6440 return s.length() == Infinity.length() && 6441 EqualChars(s.begin().get(), Infinity.data(), Infinity.length()); 6442 } 6443 6444 template <typename CharT> 6445 static inline bool StringIsNaN(mozilla::Range<const CharT> s) { 6446 static constexpr std::string_view NaN = "NaN"; 6447 6448 // "NaN" is not as nicely optimizable as "Infinity", but oh well. 6449 return s.length() == NaN.length() && 6450 EqualChars(s.begin().get(), NaN.data(), NaN.length()); 6451 } 6452 6453 template <typename CharT> 6454 static mozilla::Maybe<uint64_t> StringToTypedArrayIndexSlow( 6455 mozilla::Range<const CharT> s) { 6456 const mozilla::RangedPtr<const CharT> start = s.begin(); 6457 const mozilla::RangedPtr<const CharT> end = s.end(); 6458 6459 const CharT* actualEnd; 6460 double result = js_strtod(start.get(), end.get(), &actualEnd); 6461 6462 // The complete string must have been parsed. 6463 if (actualEnd != end.get()) { 6464 return mozilla::Nothing(); 6465 } 6466 6467 // Now convert it back to a string. 6468 ToCStringBuf cbuf; 6469 size_t cstrlen; 6470 const char* cstr = js::NumberToCString(&cbuf, result, &cstrlen); 6471 MOZ_ASSERT(cstr); 6472 6473 // Both strings must be equal for a canonical numeric index string. 6474 if (s.length() != cstrlen || !EqualChars(start.get(), cstr, cstrlen)) { 6475 return mozilla::Nothing(); 6476 } 6477 6478 // Directly perform IsInteger() check and encode negative and non-integer 6479 // indices as OOB. 6480 // See 9.4.5.2 [[HasProperty]], steps 3.b.iii and 3.b.v. 6481 // See 9.4.5.3 [[DefineOwnProperty]], steps 3.b.i and 3.b.iii. 6482 // See 9.4.5.8 IntegerIndexedElementGet, steps 5 and 8. 6483 // See 9.4.5.9 IntegerIndexedElementSet, steps 6 and 9. 6484 if (result < 0 || !IsInteger(result)) { 6485 return mozilla::Some(UINT64_MAX); 6486 } 6487 6488 // Anything equals-or-larger than 2^53 is definitely OOB, encode it 6489 // accordingly so that the cast to uint64_t is well defined. 6490 if (result >= DOUBLE_INTEGRAL_PRECISION_LIMIT) { 6491 return mozilla::Some(UINT64_MAX); 6492 } 6493 6494 // The string is an actual canonical numeric index. 6495 return mozilla::Some(result); 6496 } 6497 6498 template <typename CharT> 6499 mozilla::Maybe<uint64_t> js::StringToTypedArrayIndex( 6500 mozilla::Range<const CharT> s) { 6501 mozilla::RangedPtr<const CharT> cp = s.begin(); 6502 const mozilla::RangedPtr<const CharT> end = s.end(); 6503 6504 MOZ_ASSERT(cp < end, "caller must check for empty strings"); 6505 6506 bool negative = false; 6507 if (*cp == '-') { 6508 negative = true; 6509 if (++cp == end) { 6510 return mozilla::Nothing(); 6511 } 6512 } 6513 6514 if (!IsAsciiDigit(*cp)) { 6515 // Check for "NaN", "Infinity", or "-Infinity". 6516 if ((!negative && StringIsNaN<CharT>({cp, end})) || 6517 StringIsInfinity<CharT>({cp, end})) { 6518 return mozilla::Some(UINT64_MAX); 6519 } 6520 return mozilla::Nothing(); 6521 } 6522 6523 uint32_t digit = AsciiDigitToNumber(*cp++); 6524 6525 // Don't allow leading zeros. 6526 if (digit == 0 && cp != end) { 6527 // The string may be of the form "0.xyz". The exponent form isn't possible 6528 // when the string starts with "0". 6529 if (*cp == '.') { 6530 return StringToTypedArrayIndexSlow(s); 6531 } 6532 return mozilla::Nothing(); 6533 } 6534 6535 uint64_t index = digit; 6536 6537 for (; cp < end; cp++) { 6538 if (!IsAsciiDigit(*cp)) { 6539 // Take the slow path when the string has fractional parts or an exponent. 6540 if (*cp == '.' || *cp == 'e') { 6541 return StringToTypedArrayIndexSlow(s); 6542 } 6543 return mozilla::Nothing(); 6544 } 6545 6546 digit = AsciiDigitToNumber(*cp); 6547 6548 static_assert( 6549 uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT) < (UINT64_MAX - 10) / 10, 6550 "2^53 is way below UINT64_MAX, so |10 * index + digit| can't overflow"); 6551 6552 index = 10 * index + digit; 6553 6554 // Also take the slow path when the string is larger-or-equals 2^53. 6555 if (index >= uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT)) { 6556 return StringToTypedArrayIndexSlow(s); 6557 } 6558 } 6559 6560 if (negative) { 6561 return mozilla::Some(UINT64_MAX); 6562 } 6563 return mozilla::Some(index); 6564 } 6565 6566 template mozilla::Maybe<uint64_t> js::StringToTypedArrayIndex( 6567 mozilla::Range<const char16_t> s); 6568 6569 template mozilla::Maybe<uint64_t> js::StringToTypedArrayIndex( 6570 mozilla::Range<const Latin1Char> s); 6571 6572 bool js::SetTypedArrayElement(JSContext* cx, Handle<TypedArrayObject*> obj, 6573 uint64_t index, HandleValue v, 6574 ObjectOpResult& result) { 6575 switch (obj->type()) { 6576 #define SET_TYPED_ARRAY_ELEMENT(_, T, N) \ 6577 case Scalar::N: \ 6578 return TypedArrayObjectTemplate<T>::setElement(cx, obj, index, v, result); 6579 JS_FOR_EACH_TYPED_ARRAY(SET_TYPED_ARRAY_ELEMENT) 6580 #undef SET_TYPED_ARRAY_ELEMENT 6581 case Scalar::MaxTypedArrayViewType: 6582 case Scalar::Int64: 6583 case Scalar::Simd128: 6584 break; 6585 } 6586 6587 MOZ_CRASH("Unsupported TypedArray type"); 6588 } 6589 6590 // ES2025 draft rev ac21460fedf4b926520b06c9820bdbebad596a8b 6591 // 10.4.5.3 [[DefineOwnProperty]], step 1.b. 6592 bool js::DefineTypedArrayElement(JSContext* cx, Handle<TypedArrayObject*> obj, 6593 uint64_t index, 6594 Handle<PropertyDescriptor> desc, 6595 ObjectOpResult& result) { 6596 // These are all substeps of 1.b. 6597 6598 // Step i. 6599 if (index >= obj->length().valueOr(0)) { 6600 if (obj->hasDetachedBuffer()) { 6601 return result.fail(JSMSG_TYPED_ARRAY_DETACHED); 6602 } 6603 return result.fail(JSMSG_DEFINE_BAD_INDEX); 6604 } 6605 6606 // TypedArray elements are modifiable unless the backing buffer is immutable. 6607 bool modifiable = !obj->is<ImmutableTypedArrayObject>(); 6608 6609 // Step ii. 6610 if (desc.hasConfigurable() && desc.configurable() != modifiable) { 6611 return result.fail(JSMSG_CANT_REDEFINE_PROP); 6612 } 6613 6614 // Step iii. 6615 if (desc.hasEnumerable() && !desc.enumerable()) { 6616 return result.fail(JSMSG_CANT_REDEFINE_PROP); 6617 } 6618 6619 // Step iv. 6620 if (desc.isAccessorDescriptor()) { 6621 return result.fail(JSMSG_CANT_REDEFINE_PROP); 6622 } 6623 6624 // Step v. 6625 if (desc.hasWritable() && desc.writable() != modifiable) { 6626 return result.fail(JSMSG_CANT_REDEFINE_PROP); 6627 } 6628 6629 // Step vi. 6630 if (desc.hasValue()) { 6631 if (modifiable) { 6632 return SetTypedArrayElement(cx, obj, index, desc.value(), result); 6633 } 6634 6635 Rooted<Value> currentValue(cx); 6636 if (!obj->getElement<CanGC>(cx, index, ¤tValue)) { 6637 return false; 6638 } 6639 6640 bool sameValue; 6641 if (!SameValue(cx, desc.value(), currentValue, &sameValue)) { 6642 return false; 6643 } 6644 6645 if (!sameValue) { 6646 return result.fail(JSMSG_CANT_REDEFINE_PROP); 6647 } 6648 } 6649 6650 // Step vii. 6651 return result.succeed(); 6652 } 6653 6654 template <typename T, typename U> 6655 static constexpr typename std::enable_if_t< 6656 std::numeric_limits<T>::is_integer && !std::numeric_limits<T>::is_signed, U> 6657 UnsignedSortValue(U val) { 6658 return val; 6659 } 6660 6661 template <typename T, typename U> 6662 static constexpr typename std::enable_if_t< 6663 std::numeric_limits<T>::is_integer && std::numeric_limits<T>::is_signed, U> 6664 UnsignedSortValue(U val) { 6665 // Flip sign bit. 6666 return val ^ static_cast<U>(std::numeric_limits<T>::min()); 6667 } 6668 6669 template <typename T, typename UnsignedT> 6670 static constexpr 6671 typename std::enable_if_t<!std::numeric_limits<T>::is_integer, UnsignedT> 6672 UnsignedSortValue(UnsignedT val) { 6673 // Flip sign bit for positive numbers; flip all bits for negative numbers, 6674 // except negative NaNs. 6675 using FloatingPoint = mozilla::FloatingPoint<T>; 6676 static_assert(std::is_same_v<typename FloatingPoint::Bits, UnsignedT>, 6677 "FloatingPoint::Bits matches the unsigned int representation"); 6678 6679 // FF80'0000 is negative infinity, (FF80'0000, FFFF'FFFF] are all NaNs with 6680 // the sign-bit set (and the equivalent holds for double and float16 values). 6681 // So any value larger than negative infinity is a negative NaN. 6682 constexpr UnsignedT NegativeInfinity = mozilla::InfinityBits<T, 1>::value; 6683 if (val > NegativeInfinity) { 6684 return val; 6685 } 6686 if (val & FloatingPoint::kSignBit) { 6687 return ~val; 6688 } 6689 return val ^ FloatingPoint::kSignBit; 6690 } 6691 6692 template <typename T, typename U> 6693 static constexpr 6694 typename std::enable_if_t<std::numeric_limits<T>::is_integer, U> 6695 ToCountingSortKey(U val) { 6696 return UnsignedSortValue<T, U>(val); 6697 } 6698 6699 template <typename T, typename U> 6700 static constexpr 6701 typename std::enable_if_t<std::numeric_limits<T>::is_integer, U> 6702 FromCountingSortKey(U val) { 6703 // ToCountingSortKey for integers is a self-inverse function. 6704 return ToCountingSortKey<T, U>(val); 6705 } 6706 6707 /** 6708 * UnsignedSortValue for floating point values isn't reversible, so we use a 6709 * different implementation for counting sort to keep NaN payloads unmodified 6710 * for consistency with TypedArrayRadixSort and TypedArrayStdSort. 6711 * 6712 * Mapping overview: 6713 * - Largest -NaN = FFFF -> FFFF 6714 * - Smallest -NaN = FC01 -> FC01 6715 * - Negative infinity = FC00 -> 0000 6716 * - Negative zero = 8000 -> 7C00 6717 * - Positive zero = 0000 -> 7C01 6718 * - Positive infinity = 7C00 -> F801 6719 * - Smallest +NaN = 7C01 -> F802 6720 * - Largest +NaN = 7FFF -> FC00 6721 */ 6722 template <typename T, typename U> 6723 static constexpr typename std::enable_if_t<std::is_same_v<T, js::float16>, U> 6724 ToCountingSortKey(U val) { 6725 using FloatingPoint = mozilla::FloatingPoint<T>; 6726 static_assert(std::is_same_v<typename FloatingPoint::Bits, U>, 6727 "FloatingPoint::Bits matches the unsigned int representation"); 6728 6729 constexpr U PositiveInfinity = mozilla::InfinityBits<T, 0>::value; 6730 constexpr U NegativeInfinity = mozilla::InfinityBits<T, 1>::value; 6731 6732 // Any value larger than negative infinity is a negative NaN. Place those at 6733 // the very end. 6734 if (val > NegativeInfinity) { 6735 return val; 6736 } 6737 6738 // Map negative values, starting at negative infinity which is mapped to zero. 6739 if (val & FloatingPoint::kSignBit) { 6740 return NegativeInfinity - val; 6741 } 6742 6743 // Map positive values right after the last negative value (negative zero). 6744 return val + (PositiveInfinity + 1); 6745 } 6746 6747 /** 6748 * Reverse the mapping from ToCountingSortKey. 6749 */ 6750 template <typename T, typename U> 6751 static constexpr typename std::enable_if_t<std::is_same_v<T, js::float16>, U> 6752 FromCountingSortKey(U val) { 6753 using FloatingPoint = mozilla::FloatingPoint<T>; 6754 static_assert(std::is_same_v<typename FloatingPoint::Bits, U>, 6755 "FloatingPoint::Bits matches the unsigned int representation"); 6756 6757 constexpr U PositiveInfinity = mozilla::InfinityBits<T, 0>::value; 6758 constexpr U NegativeInfinity = mozilla::InfinityBits<T, 1>::value; 6759 6760 // Negative NaN are unchanged. 6761 if (val > NegativeInfinity) { 6762 return val; 6763 } 6764 6765 // Any value larger than 0x7C00 was a positive number, including positive NaN. 6766 if (val > PositiveInfinity) { 6767 return val - (PositiveInfinity + 1); 6768 } 6769 6770 // Any other value was a negative number, excluding negative NaN. 6771 return NegativeInfinity - val; 6772 } 6773 6774 template <typename T> 6775 static typename std::enable_if_t<std::numeric_limits<T>::is_integer> 6776 TypedArrayStdSort(SharedMem<void*> data, size_t length) { 6777 T* unwrapped = data.cast<T*>().unwrapUnshared(); 6778 std::sort(unwrapped, unwrapped + length); 6779 } 6780 6781 template <typename T> 6782 static typename std::enable_if_t<!std::numeric_limits<T>::is_integer> 6783 TypedArrayStdSort(SharedMem<void*> data, size_t length) { 6784 // Sort on the unsigned representation for performance reasons. 6785 using UnsignedT = 6786 typename mozilla::UnsignedStdintTypeForSize<sizeof(T)>::Type; 6787 UnsignedT* unwrapped = data.cast<UnsignedT*>().unwrapUnshared(); 6788 std::sort(unwrapped, unwrapped + length, [](UnsignedT x, UnsignedT y) { 6789 constexpr auto SortValue = UnsignedSortValue<T, UnsignedT>; 6790 return SortValue(x) < SortValue(y); 6791 }); 6792 } 6793 6794 template <typename T, typename Ops> 6795 static typename std::enable_if_t<std::is_same_v<Ops, UnsharedOps>, bool> 6796 TypedArrayStdSort(JSContext* cx, TypedArrayObject* typedArray, size_t length) { 6797 TypedArrayStdSort<T>(typedArray->dataPointerEither(), length); 6798 return true; 6799 } 6800 6801 template <typename T, typename Ops> 6802 static typename std::enable_if_t<std::is_same_v<Ops, SharedOps>, bool> 6803 TypedArrayStdSort(JSContext* cx, TypedArrayObject* typedArray, size_t length) { 6804 // Always create a copy when sorting shared memory backed typed arrays to 6805 // ensure concurrent write accesses doesn't lead to UB when calling std::sort. 6806 auto ptr = cx->make_pod_array<T>(length); 6807 if (!ptr) { 6808 return false; 6809 } 6810 SharedMem<T*> unshared = SharedMem<T*>::unshared(ptr.get()); 6811 SharedMem<T*> data = typedArray->dataPointerShared().cast<T*>(); 6812 6813 Ops::podCopy(unshared, data, length); 6814 6815 TypedArrayStdSort<T>(unshared.template cast<void*>(), length); 6816 6817 Ops::podCopy(data, unshared, length); 6818 6819 return true; 6820 } 6821 6822 template <typename T, typename Ops> 6823 static bool TypedArrayCountingSort(JSContext* cx, TypedArrayObject* typedArray, 6824 size_t length) { 6825 // Determined by performance testing. 6826 if (length <= 64) { 6827 return TypedArrayStdSort<T, Ops>(cx, typedArray, length); 6828 } 6829 6830 // Map signed values onto the unsigned range when storing in buffer. 6831 using UnsignedT = 6832 typename mozilla::UnsignedStdintTypeForSize<sizeof(T)>::Type; 6833 6834 constexpr size_t InlineStorage = sizeof(T) == 1 ? 256 : 0; 6835 Vector<size_t, InlineStorage> buffer(cx); 6836 if (!buffer.resize(size_t(std::numeric_limits<UnsignedT>::max()) + 1)) { 6837 return false; 6838 } 6839 6840 SharedMem<UnsignedT*> data = 6841 typedArray->dataPointerEither().cast<UnsignedT*>(); 6842 6843 // Populate the buffer. 6844 for (size_t i = 0; i < length; i++) { 6845 UnsignedT val = ToCountingSortKey<T, UnsignedT>(Ops::load(data + i)); 6846 buffer[val]++; 6847 } 6848 6849 // Traverse the buffer in order and write back elements to array. 6850 UnsignedT val = UnsignedT(-1); // intentional overflow on first increment 6851 for (size_t i = 0; i < length;) { 6852 size_t j; 6853 do { 6854 j = buffer[++val]; 6855 } while (j == 0); 6856 6857 // Invariant: sum(buffer[val:]) == length-i 6858 MOZ_ASSERT(j <= length - i); 6859 6860 for (; j > 0; j--) { 6861 Ops::store(data + i++, FromCountingSortKey<T, UnsignedT>(val)); 6862 } 6863 } 6864 6865 return true; 6866 } 6867 6868 template <typename T, typename U, typename Ops> 6869 static void SortByColumn(SharedMem<U*> data, size_t length, SharedMem<U*> aux, 6870 uint8_t col) { 6871 static_assert(std::is_unsigned_v<U>, "SortByColumn sorts on unsigned values"); 6872 static_assert(std::is_same_v<Ops, UnsharedOps>, 6873 "SortByColumn only works on unshared data"); 6874 6875 // |counts| is used to compute the starting index position for each key. 6876 // Letting counts[0] always be 0, simplifies the transform step below. 6877 // Example: 6878 // 6879 // Computing frequency counts for the input [1 2 1] gives: 6880 // 0 1 2 3 ... (keys) 6881 // 0 0 2 1 (frequencies) 6882 // 6883 // Transforming frequencies to indexes gives: 6884 // 0 1 2 3 ... (keys) 6885 // 0 0 2 3 (indexes) 6886 6887 constexpr size_t R = 256; 6888 6889 // Initialize all entries to zero. 6890 size_t counts[R + 1] = {}; 6891 6892 const auto ByteAtCol = [col](U x) { 6893 U y = UnsignedSortValue<T, U>(x); 6894 return static_cast<uint8_t>(y >> (col * 8)); 6895 }; 6896 6897 // Compute frequency counts. 6898 for (size_t i = 0; i < length; i++) { 6899 U val = Ops::load(data + i); 6900 uint8_t b = ByteAtCol(val); 6901 counts[b + 1]++; 6902 } 6903 6904 // Transform counts to indices. 6905 std::partial_sum(std::begin(counts), std::end(counts), std::begin(counts)); 6906 6907 // Distribute 6908 for (size_t i = 0; i < length; i++) { 6909 U val = Ops::load(data + i); 6910 uint8_t b = ByteAtCol(val); 6911 size_t j = counts[b]++; 6912 MOZ_ASSERT(j < length, 6913 "index is in bounds when |data| can't be modified concurrently"); 6914 UnsharedOps::store(aux + j, val); 6915 } 6916 6917 // Copy back 6918 Ops::podCopy(data, aux, length); 6919 } 6920 6921 template <typename T, typename Ops> 6922 static bool TypedArrayRadixSort(JSContext* cx, TypedArrayObject* typedArray, 6923 size_t length) { 6924 // Determined by performance testing. 6925 constexpr size_t StdSortMinCutoff = sizeof(T) == 2 ? 64 : 256; 6926 6927 // Radix sort uses O(n) additional space, limit this space to 64 MB. 6928 constexpr size_t StdSortMaxCutoff = (64 * 1024 * 1024) / sizeof(T); 6929 6930 if constexpr (sizeof(T) == 2) { 6931 // Radix sort uses |n * sizeof(T)| additional space, whereas counting sort 6932 // uses |65536 * sizeof(size_t)| additional space. When the additional 6933 // space needed for radix sort exceeds the space needed for counting sort, 6934 // switch over to counting sort. 6935 // 6936 // It's faster to switch slightly earlier, so subtract a constant from the 6937 // exact value. This constant (2048) was determined by performance testing. 6938 constexpr size_t CountingSortMaxCutoff = 6939 65536 * (sizeof(size_t) / sizeof(T)) - 2048; 6940 static_assert(CountingSortMaxCutoff < StdSortMaxCutoff); 6941 6942 if (length >= CountingSortMaxCutoff) { 6943 return TypedArrayCountingSort<T, Ops>(cx, typedArray, length); 6944 } 6945 } 6946 6947 if (length <= StdSortMinCutoff || length >= StdSortMaxCutoff) { 6948 return TypedArrayStdSort<T, Ops>(cx, typedArray, length); 6949 } 6950 6951 using UnsignedT = 6952 typename mozilla::UnsignedStdintTypeForSize<sizeof(T)>::Type; 6953 6954 auto ptr = cx->make_zeroed_pod_array<UnsignedT>(length); 6955 if (!ptr) { 6956 return false; 6957 } 6958 SharedMem<UnsignedT*> aux = SharedMem<UnsignedT*>::unshared(ptr.get()); 6959 6960 SharedMem<UnsignedT*> data = 6961 typedArray->dataPointerEither().cast<UnsignedT*>(); 6962 6963 // Always create a copy when sorting shared memory backed typed arrays to 6964 // ensure concurrent write accesses don't lead to computing bad indices. 6965 SharedMem<UnsignedT*> unshared; 6966 SharedMem<UnsignedT*> shared; 6967 UniquePtr<UnsignedT[], JS::FreePolicy> ptrUnshared; 6968 if constexpr (std::is_same_v<Ops, SharedOps>) { 6969 ptrUnshared = cx->make_pod_array<UnsignedT>(length); 6970 if (!ptrUnshared) { 6971 return false; 6972 } 6973 unshared = SharedMem<UnsignedT*>::unshared(ptrUnshared.get()); 6974 shared = data; 6975 6976 Ops::podCopy(unshared, shared, length); 6977 6978 data = unshared; 6979 } 6980 6981 for (uint8_t col = 0; col < sizeof(UnsignedT); col++) { 6982 SortByColumn<T, UnsignedT, UnsharedOps>(data, length, aux, col); 6983 } 6984 6985 if constexpr (std::is_same_v<Ops, SharedOps>) { 6986 Ops::podCopy(shared, unshared, length); 6987 } 6988 6989 return true; 6990 } 6991 6992 using TypedArraySortFn = bool (*)(JSContext*, TypedArrayObject*, size_t length); 6993 6994 template <typename T, typename Ops> 6995 static constexpr typename std::enable_if_t<sizeof(T) == 1, TypedArraySortFn> 6996 TypedArraySort() { 6997 return TypedArrayCountingSort<T, Ops>; 6998 } 6999 7000 template <typename T, typename Ops> 7001 static constexpr typename std::enable_if_t<sizeof(T) == 2 || sizeof(T) == 4, 7002 TypedArraySortFn> 7003 TypedArraySort() { 7004 return TypedArrayRadixSort<T, Ops>; 7005 } 7006 7007 template <typename T, typename Ops> 7008 static constexpr typename std::enable_if_t<sizeof(T) == 8, TypedArraySortFn> 7009 TypedArraySort() { 7010 return TypedArrayStdSort<T, Ops>; 7011 } 7012 7013 static bool TypedArraySortWithoutComparator(JSContext* cx, 7014 TypedArrayObject* typedArray, 7015 size_t len) { 7016 bool isShared = typedArray->isSharedMemory(); 7017 switch (typedArray->type()) { 7018 #define SORT(_, T, N) \ 7019 case Scalar::N: \ 7020 if (isShared) { \ 7021 if (!TypedArraySort<T, SharedOps>()(cx, typedArray, len)) { \ 7022 return false; \ 7023 } \ 7024 } else { \ 7025 if (!TypedArraySort<T, UnsharedOps>()(cx, typedArray, len)) { \ 7026 return false; \ 7027 } \ 7028 } \ 7029 break; 7030 JS_FOR_EACH_TYPED_ARRAY(SORT) 7031 #undef SORT 7032 default: 7033 MOZ_CRASH("Unsupported TypedArray type"); 7034 } 7035 return true; 7036 } 7037 7038 static MOZ_ALWAYS_INLINE bool TypedArraySortPrologue(JSContext* cx, 7039 Handle<Value> thisv, 7040 Handle<Value> comparefn, 7041 ArraySortData* d, 7042 bool* done) { 7043 // https://tc39.es/ecma262/#sec-%typedarray%.prototype.sort 7044 // 23.2.3.29 %TypedArray%.prototype.sort ( comparefn ) 7045 7046 // Step 1. 7047 if (MOZ_UNLIKELY(!comparefn.isUndefined() && !IsCallable(comparefn))) { 7048 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 7049 JSMSG_BAD_TYPEDARRAY_SORT_ARG); 7050 return false; 7051 } 7052 7053 // Steps 2-3. 7054 Rooted<TypedArrayObject*> tarrayUnwrapped( 7055 cx, UnwrapAndTypeCheckValue<TypedArrayObject>(cx, thisv, [cx, &thisv]() { 7056 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, 7057 JSMSG_INCOMPATIBLE_METHOD, "sort", "method", 7058 InformalValueTypeName(thisv)); 7059 })); 7060 if (!tarrayUnwrapped) { 7061 return false; 7062 } 7063 auto arrayLength = tarrayUnwrapped->length(); 7064 if (!arrayLength) { 7065 ReportOutOfBounds(cx, tarrayUnwrapped); 7066 return false; 7067 } 7068 7069 // Additional step from Immutable ArrayBuffer proposal. 7070 if (tarrayUnwrapped->is<ImmutableTypedArrayObject>()) { 7071 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 7072 JSMSG_ARRAYBUFFER_IMMUTABLE); 7073 return false; 7074 } 7075 7076 // Step 4. 7077 size_t len = *arrayLength; 7078 7079 // Arrays with less than two elements remain unchanged when sorted. 7080 if (len <= 1) { 7081 d->setReturnValue(&thisv.toObject()); 7082 *done = true; 7083 return true; 7084 } 7085 7086 // Fast path for sorting without a comparator. 7087 if (comparefn.isUndefined()) { 7088 if (!TypedArraySortWithoutComparator(cx, tarrayUnwrapped, len)) { 7089 return false; 7090 } 7091 d->setReturnValue(&thisv.toObject()); 7092 *done = true; 7093 return true; 7094 } 7095 7096 // Ensure length * 2 (used below) doesn't overflow UINT32_MAX. 7097 if (MOZ_UNLIKELY(len > UINT32_MAX / 2)) { 7098 ReportAllocationOverflow(cx); 7099 return false; 7100 } 7101 7102 // Merge sort requires extra scratch space. 7103 bool needsScratchSpace = len > ArraySortData::InsertionSortMaxLength; 7104 7105 Rooted<ArraySortData::ValueVector> vec(cx); 7106 if (MOZ_UNLIKELY(!vec.resize(needsScratchSpace ? (2 * len) : len))) { 7107 ReportOutOfMemory(cx); 7108 return false; 7109 } 7110 7111 // Copy elements to JS Value vector. 7112 if (!TypedArrayObject::getElements(cx, tarrayUnwrapped, len, vec.begin())) { 7113 return false; 7114 } 7115 7116 d->init(&thisv.toObject(), &comparefn.toObject(), std::move(vec.get()), len, 7117 len); 7118 7119 // Continue in ArraySortData::sortTypedArrayWithComparator. 7120 MOZ_ASSERT(!*done); 7121 return true; 7122 } 7123 7124 // Copies sorted elements back to the typed array. 7125 template <typename T, typename Ops> 7126 static void StoreSortedElements(TypedArrayObject* tarray, Value* elements, 7127 size_t len) { 7128 SharedMem<T*> data = tarray->dataPointerEither().cast<T*>(); 7129 for (size_t i = 0; i < len; i++) { 7130 T val; 7131 if constexpr (!std::numeric_limits<T>::is_integer) { 7132 val = elements[i].toDouble(); 7133 } else if constexpr (std::is_same_v<T, int64_t>) { 7134 val = BigInt::toInt64(elements[i].toBigInt()); 7135 } else if constexpr (std::is_same_v<T, uint64_t>) { 7136 val = BigInt::toUint64(elements[i].toBigInt()); 7137 } else if constexpr (std::is_same_v<T, uint32_t>) { 7138 val = uint32_t(elements[i].toNumber()); 7139 } else { 7140 val = elements[i].toInt32(); 7141 } 7142 Ops::store(data + i, val); 7143 } 7144 } 7145 7146 // static 7147 ArraySortResult ArraySortData::sortTypedArrayWithComparator(ArraySortData* d) { 7148 ArraySortResult result = 7149 sortWithComparatorShared<ArraySortKind::TypedArray>(d); 7150 if (result != ArraySortResult::Done) { 7151 return result; 7152 } 7153 7154 // Copy sorted elements to the typed array. 7155 JSContext* cx = d->cx(); 7156 Rooted<TypedArrayObject*> tarrayUnwrapped( 7157 cx, UnwrapAndDowncastObject<TypedArrayObject>(cx, d->obj_)); 7158 if (MOZ_UNLIKELY(!tarrayUnwrapped)) { 7159 return ArraySortResult::Failure; 7160 } 7161 7162 auto length = tarrayUnwrapped->length(); 7163 if (MOZ_LIKELY(length)) { 7164 size_t len = std::min<size_t>(*length, d->denseLen); 7165 Value* elements = d->list; 7166 bool isShared = tarrayUnwrapped->isSharedMemory(); 7167 switch (tarrayUnwrapped->type()) { 7168 #define SORT(_, T, N) \ 7169 case Scalar::N: \ 7170 if (isShared) { \ 7171 StoreSortedElements<T, SharedOps>(tarrayUnwrapped, elements, len); \ 7172 } else { \ 7173 StoreSortedElements<T, UnsharedOps>(tarrayUnwrapped, elements, len); \ 7174 } \ 7175 break; 7176 JS_FOR_EACH_TYPED_ARRAY(SORT) 7177 #undef SORT 7178 default: 7179 MOZ_CRASH("Unsupported TypedArray type"); 7180 } 7181 } 7182 7183 d->freeMallocData(); 7184 d->setReturnValue(d->obj_); 7185 return ArraySortResult::Done; 7186 } 7187 7188 // https://tc39.es/ecma262/#sec-%typedarray%.prototype.sort 7189 // 23.2.3.29 %TypedArray%.prototype.sort ( comparefn ) 7190 // static 7191 bool TypedArrayObject::sort(JSContext* cx, unsigned argc, Value* vp) { 7192 AutoJSMethodProfilerEntry pseudoFrame(cx, "[TypedArray].prototype", "sort"); 7193 CallArgs args = CallArgsFromVp(argc, vp); 7194 7195 // If we have a comparator argument, use the JIT trampoline implementation 7196 // instead. This avoids a performance cliff (especially with large arrays) 7197 // because C++ => JIT calls are much slower than Trampoline => JIT calls. 7198 if (args.hasDefined(0) && jit::IsBaselineInterpreterEnabled()) { 7199 return CallTrampolineNativeJitCode( 7200 cx, jit::TrampolineNative::TypedArraySort, args); 7201 } 7202 7203 Rooted<ArraySortData> data(cx, cx); 7204 7205 // On all return paths other than ArraySortData::sortTypedArrayWithComparator 7206 // returning Done, we call freeMallocData to not fail debug assertions. This 7207 // matches the JIT trampoline where we can't rely on C++ destructors. 7208 auto freeData = 7209 mozilla::MakeScopeExit([&]() { data.get().freeMallocData(); }); 7210 7211 bool done = false; 7212 if (!TypedArraySortPrologue(cx, args.thisv(), args.get(0), data.address(), 7213 &done)) { 7214 return false; 7215 } 7216 if (done) { 7217 args.rval().set(data.get().returnValue()); 7218 return true; 7219 } 7220 7221 FixedInvokeArgs<2> callArgs(cx); 7222 Rooted<Value> rval(cx); 7223 7224 while (true) { 7225 ArraySortResult res = 7226 ArraySortData::sortTypedArrayWithComparator(data.address()); 7227 switch (res) { 7228 case ArraySortResult::Failure: 7229 return false; 7230 7231 case ArraySortResult::Done: 7232 freeData.release(); 7233 args.rval().set(data.get().returnValue()); 7234 return true; 7235 7236 case ArraySortResult::CallJS: 7237 case ArraySortResult::CallJSSameRealmNoUnderflow: 7238 MOZ_ASSERT(data.get().comparatorThisValue().isUndefined()); 7239 MOZ_ASSERT(&args[0].toObject() == data.get().comparator()); 7240 callArgs[0].set(data.get().comparatorArg(0)); 7241 callArgs[1].set(data.get().comparatorArg(1)); 7242 if (!js::Call(cx, args[0], UndefinedHandleValue, callArgs, &rval)) { 7243 return false; 7244 } 7245 data.get().setComparatorReturnValue(rval); 7246 break; 7247 } 7248 } 7249 } 7250 7251 ArraySortResult js::TypedArraySortFromJit( 7252 JSContext* cx, jit::TrampolineNativeFrameLayout* frame) { 7253 AutoJSMethodProfilerEntry pseudoFrame(cx, "[TypedArray].prototype", "sort"); 7254 // Initialize the ArraySortData class stored in the trampoline frame. 7255 void* dataUninit = frame->getFrameData<ArraySortData>(); 7256 auto* data = new (dataUninit) ArraySortData(cx); 7257 7258 Rooted<Value> thisv(cx, frame->thisv()); 7259 Rooted<Value> comparefn(cx); 7260 if (frame->numActualArgs() > 0) { 7261 comparefn = frame->actualArgs()[0]; 7262 } 7263 7264 bool done = false; 7265 if (!TypedArraySortPrologue(cx, thisv, comparefn, data, &done)) { 7266 return ArraySortResult::Failure; 7267 } 7268 if (done) { 7269 data->freeMallocData(); 7270 return ArraySortResult::Done; 7271 } 7272 7273 return ArraySortData::sortTypedArrayWithComparator(data); 7274 } 7275 7276 /* JS Public API */ 7277 7278 #define IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(ExternalType, NativeType, Name) \ 7279 JS_PUBLIC_API JSObject* JS_New##Name##Array(JSContext* cx, \ 7280 size_t nelements) { \ 7281 return TypedArrayObjectTemplate<NativeType>::fromLength(cx, nelements); \ 7282 } \ 7283 \ 7284 JS_PUBLIC_API JSObject* JS_New##Name##ArrayFromArray(JSContext* cx, \ 7285 HandleObject other) { \ 7286 return TypedArrayObjectTemplate<NativeType>::fromArray(cx, other); \ 7287 } \ 7288 \ 7289 JS_PUBLIC_API JSObject* JS_New##Name##ArrayWithBuffer( \ 7290 JSContext* cx, HandleObject arrayBuffer, size_t byteOffset, \ 7291 int64_t length) { \ 7292 return TypedArrayObjectTemplate<NativeType>::fromBuffer( \ 7293 cx, arrayBuffer, byteOffset, length); \ 7294 } \ 7295 \ 7296 JS_PUBLIC_API JSObject* js::Unwrap##Name##Array(JSObject* obj) { \ 7297 obj = obj->maybeUnwrapIf<TypedArrayObject>(); \ 7298 if (!obj) { \ 7299 return nullptr; \ 7300 } \ 7301 const JSClass* clasp = obj->getClass(); \ 7302 if (clasp != FixedLengthTypedArrayObjectTemplate< \ 7303 NativeType>::instanceClass() && \ 7304 clasp != \ 7305 ResizableTypedArrayObjectTemplate<NativeType>::instanceClass() && \ 7306 clasp != \ 7307 ImmutableTypedArrayObjectTemplate<NativeType>::instanceClass()) { \ 7308 return nullptr; \ 7309 } \ 7310 return obj; \ 7311 } \ 7312 \ 7313 JS_PUBLIC_API ExternalType* JS_Get##Name##ArrayLengthAndData( \ 7314 JSObject* obj, size_t* length, bool* isSharedMemory, \ 7315 const JS::AutoRequireNoGC& nogc) { \ 7316 TypedArrayObject* tarr = obj->maybeUnwrapAs<TypedArrayObject>(); \ 7317 if (!tarr) { \ 7318 return nullptr; \ 7319 } \ 7320 mozilla::Span<ExternalType> span = \ 7321 JS::TypedArray<JS::Scalar::Name>::fromObject(tarr).getData( \ 7322 isSharedMemory, nogc); \ 7323 *length = span.Length(); \ 7324 return span.data(); \ 7325 } \ 7326 \ 7327 JS_PUBLIC_API ExternalType* JS_Get##Name##ArrayData( \ 7328 JSObject* obj, bool* isSharedMemory, const JS::AutoRequireNoGC& nogc) { \ 7329 size_t length; \ 7330 return JS_Get##Name##ArrayLengthAndData(obj, &length, isSharedMemory, \ 7331 nogc); \ 7332 } \ 7333 JS_PUBLIC_API JSObject* JS_GetObjectAs##Name##Array( \ 7334 JSObject* obj, size_t* length, bool* isShared, ExternalType** data) { \ 7335 obj = js::Unwrap##Name##Array(obj); \ 7336 if (!obj) { \ 7337 return nullptr; \ 7338 } \ 7339 TypedArrayObject* tarr = &obj->as<TypedArrayObject>(); \ 7340 *length = tarr->length().valueOr(0); \ 7341 *isShared = tarr->isSharedMemory(); \ 7342 *data = static_cast<ExternalType*>(tarr->dataPointerEither().unwrap( \ 7343 /*safe - caller sees isShared flag*/)); \ 7344 return obj; \ 7345 } 7346 7347 JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS) 7348 #undef IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS 7349 7350 JS_PUBLIC_API bool JS_IsTypedArrayObject(JSObject* obj) { 7351 return obj->canUnwrapAs<TypedArrayObject>(); 7352 } 7353 7354 JS_PUBLIC_API size_t JS_GetTypedArrayLength(JSObject* obj) { 7355 TypedArrayObject* tarr = obj->maybeUnwrapAs<TypedArrayObject>(); 7356 if (!tarr) { 7357 return 0; 7358 } 7359 return tarr->length().valueOr(0); 7360 } 7361 7362 JS_PUBLIC_API size_t JS_GetTypedArrayByteOffset(JSObject* obj) { 7363 TypedArrayObject* tarr = obj->maybeUnwrapAs<TypedArrayObject>(); 7364 if (!tarr) { 7365 return 0; 7366 } 7367 return tarr->byteOffset().valueOr(0); 7368 } 7369 7370 JS_PUBLIC_API size_t JS_GetTypedArrayByteLength(JSObject* obj) { 7371 TypedArrayObject* tarr = obj->maybeUnwrapAs<TypedArrayObject>(); 7372 if (!tarr) { 7373 return 0; 7374 } 7375 return tarr->byteLength().valueOr(0); 7376 } 7377 7378 JS_PUBLIC_API bool JS_GetTypedArraySharedness(JSObject* obj) { 7379 TypedArrayObject* tarr = obj->maybeUnwrapAs<TypedArrayObject>(); 7380 if (!tarr) { 7381 return false; 7382 } 7383 return tarr->isSharedMemory(); 7384 } 7385 7386 JS_PUBLIC_API JS::Scalar::Type JS_GetArrayBufferViewType(JSObject* obj) { 7387 ArrayBufferViewObject* view = obj->maybeUnwrapAs<ArrayBufferViewObject>(); 7388 if (!view) { 7389 return Scalar::MaxTypedArrayViewType; 7390 } 7391 7392 if (view->is<TypedArrayObject>()) { 7393 return view->as<TypedArrayObject>().type(); 7394 } 7395 if (view->is<DataViewObject>()) { 7396 return Scalar::MaxTypedArrayViewType; 7397 } 7398 MOZ_CRASH("invalid ArrayBufferView type"); 7399 } 7400 7401 JS_PUBLIC_API size_t JS_MaxMovableTypedArraySize() { 7402 return FixedLengthTypedArrayObject::INLINE_BUFFER_LIMIT; 7403 } 7404 7405 namespace JS { 7406 7407 const JSClass* const TypedArray_base::fixedLengthClasses = 7408 TypedArrayObject::fixedLengthClasses; 7409 const JSClass* const TypedArray_base::immutableClasses = 7410 TypedArrayObject::immutableClasses; 7411 const JSClass* const TypedArray_base::resizableClasses = 7412 TypedArrayObject::resizableClasses; 7413 7414 #define INSTANTIATE(ExternalType, NativeType, Name) \ 7415 template class TypedArray<JS::Scalar::Name>; 7416 JS_FOR_EACH_TYPED_ARRAY(INSTANTIATE) 7417 #undef INSTANTIATE 7418 7419 JS::ArrayBufferOrView JS::ArrayBufferOrView::unwrap(JSObject* maybeWrapped) { 7420 if (!maybeWrapped) { 7421 return JS::ArrayBufferOrView(nullptr); 7422 } 7423 auto* ab = maybeWrapped->maybeUnwrapIf<ArrayBufferObjectMaybeShared>(); 7424 if (ab) { 7425 return ArrayBufferOrView::fromObject(ab); 7426 } 7427 7428 return ArrayBufferView::unwrap(maybeWrapped); 7429 } 7430 7431 bool JS::ArrayBufferOrView::isDetached() const { 7432 MOZ_ASSERT(obj); 7433 if (obj->is<ArrayBufferObjectMaybeShared>()) { 7434 return obj->as<ArrayBufferObjectMaybeShared>().isDetached(); 7435 } else { 7436 return obj->as<ArrayBufferViewObject>().hasDetachedBuffer(); 7437 } 7438 } 7439 7440 bool JS::ArrayBufferOrView::isResizable() const { 7441 MOZ_ASSERT(obj); 7442 if (obj->is<ArrayBufferObjectMaybeShared>()) { 7443 return obj->as<ArrayBufferObjectMaybeShared>().isResizable(); 7444 } else { 7445 return obj->as<ArrayBufferViewObject>().hasResizableBuffer(); 7446 } 7447 } 7448 7449 bool JS::ArrayBufferOrView::isImmutable() const { 7450 MOZ_ASSERT(obj); 7451 if (obj->is<ArrayBufferObjectMaybeShared>()) { 7452 return obj->as<ArrayBufferObjectMaybeShared>().isImmutable(); 7453 } else { 7454 return obj->as<ArrayBufferViewObject>().hasImmutableBuffer(); 7455 } 7456 } 7457 7458 JS::TypedArray_base JS::TypedArray_base::fromObject(JSObject* unwrapped) { 7459 if (unwrapped && unwrapped->is<TypedArrayObject>()) { 7460 return TypedArray_base(unwrapped); 7461 } 7462 return TypedArray_base(nullptr); 7463 } 7464 7465 // Template getData function for TypedArrays, implemented here because 7466 // it requires internal APIs. 7467 template <JS::Scalar::Type EType> 7468 typename mozilla::Span<typename TypedArray<EType>::DataType> 7469 TypedArray<EType>::getData(bool* isSharedMemory, const AutoRequireNoGC&) { 7470 using ExternalType = TypedArray<EType>::DataType; 7471 if (!obj) { 7472 return nullptr; 7473 } 7474 TypedArrayObject* tarr = &obj->as<TypedArrayObject>(); 7475 MOZ_ASSERT(tarr); 7476 *isSharedMemory = tarr->isSharedMemory(); 7477 return {static_cast<ExternalType*>(tarr->dataPointerEither().unwrap( 7478 /*safe - caller sees isShared*/)), 7479 tarr->length().valueOr(0)}; 7480 }; 7481 7482 // Force the method defined above to actually be instantianted in this 7483 // compilation unit and emitted into the object file, since otherwise a binary 7484 // could include the header file and emit an undefined symbol that would not be 7485 // satisfied by the linker. (This happens with opt gtest, at least. In a DEBUG 7486 // build, the header contains a call to this function so it will always be 7487 // emitted.) 7488 #define INSTANTIATE_GET_DATA(a, b, Name) \ 7489 template mozilla::Span<typename TypedArray<JS::Scalar::Name>::DataType> \ 7490 TypedArray<JS::Scalar::Name>::getData(bool* isSharedMemory, \ 7491 const AutoRequireNoGC&); 7492 JS_FOR_EACH_TYPED_ARRAY(INSTANTIATE_GET_DATA) 7493 #undef INSTANTIATE_GET_DATA 7494 7495 } /* namespace JS */