TypedArray.h (42984B)
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 file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef mozilla_dom_TypedArray_h 8 #define mozilla_dom_TypedArray_h 9 10 #include <type_traits> 11 #include <utility> 12 13 #include "js/ArrayBuffer.h" 14 #include "js/ArrayBufferMaybeShared.h" 15 #include "js/Context.h" 16 #include "js/GCAPI.h" // JS::AutoCheckCannotGC 17 #include "js/RootingAPI.h" // JS::Rooted 18 #include "js/ScalarType.h" // JS::Scalar::Type 19 #include "js/SharedArrayBuffer.h" 20 #include "js/experimental/TypedData.h" // js::Unwrap(Ui|I)nt(8|16|32)Array, js::Get(Ui|I)nt(8|16|32)ArrayLengthAndData, js::UnwrapUint8ClampedArray, js::GetUint8ClampedArrayLengthAndData, js::UnwrapFloat(32|64)Array, js::GetFloat(32|64)ArrayLengthAndData, JS_GetArrayBufferViewType 21 #include "js/friend/ErrorMessages.h" 22 #include "mozilla/Attributes.h" 23 #include "mozilla/Buffer.h" 24 #include "mozilla/ErrorResult.h" 25 #include "mozilla/Vector.h" 26 #include "mozilla/dom/BindingDeclarations.h" 27 #include "mozilla/dom/ScriptSettings.h" 28 #include "mozilla/dom/SpiderMonkeyInterface.h" 29 #include "nsIGlobalObject.h" 30 #include "nsWrapperCache.h" 31 #include "nsWrapperCacheInlines.h" 32 33 namespace mozilla::dom { 34 35 /* 36 * Various typed array classes for argument conversion. We have a base class 37 * that has a way of initializing a TypedArray from an existing typed array, and 38 * a subclass of the base class that supports creation of a relevant typed array 39 * or array buffer object. 40 * 41 * Accessing the data of a TypedArray is tricky. The underlying storage is in a 42 * JS object, which is subject to the JS GC. The memory for the array can be 43 * either inline in the JSObject or stored separately. If the data is stored 44 * inline then the exact location in memory of the data buffer can change as a 45 * result of a JS GC (which is a moving GC). Running JS code can also mutate the 46 * data (including the length). For all these reasons one has to be careful when 47 * holding a pointer to the data or keeping a local copy of the length value. On 48 * the other hand, most code that takes a TypedArray has to access its data at 49 * some point, to process it in some way. The TypedArray class tries to supply a 50 * number of helper APIs, so that the most common cases of processing the data 51 * can be done safely, without having to check the caller very closely for 52 * potential security issues. The main classes of processing TypedArray data 53 * are: 54 * 55 * 1) Appending a copy of the data (or of a subset of the data) to a different 56 * data structure 57 * 2) Copying the data (or a subset of the data) into a different data 58 * structure 59 * 3) Creating a new data structure with a copy of the data (or of a subset of 60 * the data) 61 * 4) Processing the data in some other way 62 * 63 * The APIs for the first 3 classes all return a boolean and take an optional 64 * argument named aCalculator. aCalculator should be a lambda taking a size_t 65 * argument which will be passed the total length of the data in the typed 66 * array. aCalculator is allowed to return a std::pair<size_t, size_t> or a 67 * Maybe<std::pair<size_t, size_t>>. The return value should contain the offset 68 * and the length of the subset of the data that should be appended, copied or 69 * used for creating a new datastructure. If the calculation can fail then 70 * aCalculator should return a Maybe<std::pair<size_t, size_t>>, with Nothing() 71 * signaling that the operation should be aborted. 72 * The return value of these APIs will be false if appending, copying or 73 * creating a structure with the new data failed, or if the optional aCalculator 74 * lambda returned Nothing(). 75 * 76 * Here are the different APIs: 77 * 78 * 1) Appending to a different data structure 79 * 80 * There are AppendDataTo helpers for nsCString, nsTArray<T>, 81 * FallibleTArray<T> and Vector<T>. The signatures are: 82 * 83 * template <typename... Calculator> 84 * [[nodiscard]] bool AppendDataTo(nsCString& aResult, Calculator&&... 85 * aCalculator) const; 86 * 87 * template <typename T, typename... Calculator> 88 * [[nodiscard]] bool AppendDataTo(nsTArray<T>& aResult, Calculator&&... 89 * aCalculator) const; 90 * 91 * template <typename T, typename... Calculator> 92 * [[nodiscard]] bool AppendDataTo(FallibleTArray<T>& aResult, 93 * Calculator&&... aCalculator) const; 94 * 95 * template <typename T, typename... Calculator> 96 * [[nodiscard]] bool AppendDataTo(Vector<T>& aResult, Calculator&&... 97 * aCalculator) const; 98 * 99 * The data (or the calculated subset) will be appended to aResult by using 100 * the appropriate fallible API. If the append fails then AppendDataTo will 101 * return false. The aCalculator optional argument is described above. 102 * 103 * Examples: 104 * 105 * Vector<uint32_t> array; 106 * if (!aUint32Array.AppendDataTo(array)) { 107 * aError.ThrowTypeError("Failed to copy data from typed array"); 108 * return; 109 * } 110 * 111 * size_t offset, length; 112 * … // Getting offset and length values from somewhere. 113 * FallibleTArray<float> array; 114 * if (!aFloat32Array.AppendDataTo(array, [&](const size_t& aLength) { 115 * size_t dataLength = std::min(aLength - offset, length); 116 * return std::make_pair(offset, dataLength); 117 * }) { 118 * aError.ThrowTypeError("Failed to copy data from typed array"); 119 * return; 120 * } 121 * 122 * size_t offset, length; 123 * … // Getting offset and length values from somewhere. 124 * FallibleTArray<float> array; 125 * if (!aFloat32Array.AppendDataTo(array, [&](const size_t& aLength) { 126 * if (aLength < offset + length) { 127 * return Maybe<std::pair<size_t, size_t>>(); 128 * } 129 * size_t dataLength = std::min(aLength - offset, length); 130 * return Some(std::make_pair(offset, dataLength)); 131 * })) { 132 * aError.ThrowTypeError("Failed to copy data from typed array"); 133 * return; 134 * } 135 * 136 * 137 * 2) Copying into a different data structure 138 * 139 * There is a CopyDataTo helper for a fixed-size buffer. The signature is: 140 * 141 * template <typename T, size_t N, typename... Calculator> 142 * [[nodiscard]] bool CopyDataTo(T (&aResult)[N], 143 * Calculator&&... aCalculator) const; 144 * 145 * The data (or the calculated subset) will be copied to aResult, starting 146 * at aResult[0]. If the length of the data to be copied is bigger than the 147 * size of the fixed-size buffer (N) then nothing will be copied and 148 * CopyDataTo will return false. The aCalculator optional argument is 149 * described above. 150 * 151 * Examples: 152 * 153 * float data[3]; 154 * if (!aFloat32Array.CopyDataTo(data)) { 155 * aError.ThrowTypeError("Typed array doesn't contain the right amount" 156 * "of data"); 157 * return; 158 * } 159 * 160 * size_t offset; 161 * … // Getting offset value from somewhere. 162 * uint32_t data[3]; 163 * if (!aUint32Array.CopyDataTo(data, [&](const size_t& aLength) { 164 * if (aLength - offset != std::size(data)) { 165 * aError.ThrowTypeError("Typed array doesn't contain the right" 166 * " amount of data"); 167 * return Maybe<std::pair<size_t, size_t>>(); 168 * } 169 * return Some(std::make_pair(offset, std::size(data))); 170 * }) { 171 * return; 172 * } 173 * 174 * 3) Creating a new data structure with a copy of the data (or a subset of the 175 * data) 176 * 177 * There are CreateFromData helper for creating a Vector<element_type>, a 178 * UniquePtr<element_type[]> and a Buffer<element_type>. 179 * 180 * template <typename... Calculator> 181 * [[nodiscard]] Maybe<Vector<element_type>> 182 * CreateFromData<Vector<element_type>>( 183 * Calculator&&... aCalculator) const; 184 * 185 * template <typename... Calculator> 186 * [[nodiscard]] Maybe<UniquePtr<element_type[]>> 187 * CreateFromData<UniquePtr<element_type[]>>( 188 * Calculator&&... aCalculator) const; 189 * 190 * template <typename... Calculator> 191 * [[nodiscard]] Maybe<Buffer<element_type>> 192 * CreateFromData<Buffer<element_type>>( 193 * Calculator&&... aCalculator) const; 194 * 195 * A new container will be created, and the data (or the calculated subset) 196 * will be copied to it. The container will be returned inside a Maybe<…>. 197 * If creating the container with the right size fails then Nothing() will 198 * be returned. The aCalculator optional argument is described above. 199 * 200 * Examples: 201 * 202 * Maybe<Buffer<uint8_t>> buffer = 203 * aUint8Array.CreateFromData<Buffer<uint8_t>>(); 204 * if (buffer.isNothing()) { 205 * aError.ThrowTypeError("Failed to create a buffer"); 206 * return; 207 * } 208 * 209 * size_t offset, length; 210 * … // Getting offset and length values from somewhere. 211 * Maybe<Buffer<uint8_t>> buffer = 212 * aUint8Array.CreateFromData<Buffer<uint8_t>>([&]( 213 * const size_t& aLength) { 214 * if (aLength - offset != std::size(data)) { 215 * aError.ThrowTypeError( 216 * "Typed array doesn't contain the right amount" of data"); 217 * return Maybe<std::pair<size_t, size_t>>(); 218 * } 219 * return Some(std::make_pair(offset, std::size(data))); 220 * }); 221 * if (buffer.isNothing()) { 222 * return; 223 * } 224 * 225 * 4) Processing the data in some other way 226 * 227 * This is the API for when none of the APIs above are appropriate for your 228 * usecase. As these are the most dangerous APIs you really should check 229 * first if you can't use one of the safer alternatives above. The reason 230 * these APIs are more dangerous is because they give access to the typed 231 * array's data directly, and the location of that data can be changed by 232 * the JS GC (due to generational and/or compacting collection). There are 233 * two APIs for processing data: 234 * 235 * template <typename Processor> 236 * [[nodiscard]] ProcessReturnType<Processor> ProcessData( 237 * Processor&& aProcessor) const; 238 * 239 * template <typename Processor> 240 * [[nodiscard]] ProcessReturnType<Processor> ProcessFixedData( 241 * Processor&& aProcessor) const; 242 * 243 * ProcessData will call the lambda with as arguments a |const Span<…>&| 244 * wrapping the data pointer and length for the data in the typed array, and 245 * a |JS::AutoCheckCannotGC&&|. The lambda will execute in a context where 246 * GC is not allowed. 247 * 248 * ProcessFixedData will call the lambda with as argument |const Span<…>&|. 249 * For small typed arrays where the data is stored inline in the typed 250 * array, and thus could move during GC, then the data will be copied into a 251 * fresh out-of-line allocation before the lambda is called. 252 * 253 * The signature of the lambdas for ProcessData and ProcessFixedData differ 254 * in that the ProcessData lambda will additionally be passed a nogc token 255 * to prevent GC from occurring and invalidating the data. If the processing 256 * you need to do in the lambda can't be proven to not GC then you should 257 * probably use ProcessFixedData instead. There are cases where you need to 258 * do something that can cause a GC but you don't actually need to access 259 * the data anymore. A good example would be throwing a JS exception and 260 * return. For those very specific cases you can call nogc.reset() before 261 * doing anything that causes a GC. Be extra careful to not access the data 262 * after you called nogc.reset(). 263 * 264 * Extra care must be taken to not let the Span<…> or any pointers derived 265 * from it escape the lambda, as the position in memory of the typed array's 266 * data can change again once we leave the lambda (invalidating the 267 * pointers). The lambda passed to ProcessData is not allowed to do anything 268 * that will trigger a GC, and the GC rooting hazard analysis will enforce 269 * that. 270 * 271 * Both ProcessData and ProcessFixedData will pin the typed array's length 272 * while calling the lambda, to block any changes to the length of the data. 273 * Note that this means that the lambda itself isn't allowed to change the 274 * length of the typed array's data. Any attempt to change the length will 275 * throw a JS exception. 276 * 277 * The return type of ProcessData and ProcessFixedData depends on the return 278 * type of the lambda, as they forward the return value from the lambda to 279 * the caller of ProcessData or ProcessFixedData. 280 * 281 * Examples: 282 * 283 * aUint32Array.ProcessData([] (const Span<uint32_t>& aData, 284 * JS::AutoCheckCannotGC&&) { 285 * for (size_t i = 0; i < aData.Length(); ++i) { 286 * aData[i] = i; 287 * } 288 * }); 289 * 290 * aUint32Array.ProcessData([&] (const Span<uint32_t>& aData, 291 * JS::AutoCheckCannotGC&& nogc) { 292 * for (size_t i = 0; i < aData.Length(); ++i) { 293 * if (!aData[i]) { 294 * nogc.reset(); 295 * ThrowJSException("Data shouldn't contain 0"); 296 * return; 297 * }; 298 * DoSomething(aData[i]); 299 * } 300 * }); 301 * 302 * uint8_t max = aUint8Array.ProcessData([] (const Span<uint8_t>& aData) { 303 * return std::max_element(aData.cbegin(), aData.cend()); 304 * }); 305 * 306 * aUint8Array.ProcessFixedData([] (const Span<uint8_t>& aData) { 307 * return CallFunctionThatMightGC(aData); 308 * }); 309 * 310 * 311 * In addition to the above APIs we provide helpers to call them on the typed 312 * array members of WebIDL unions. We have helpers for the 4 different sets of 313 * APIs above. The return value of the helpers depends on whether the union can 314 * contain a type other than a typed array. If the union can't contain a type 315 * other than a typed array then the return type is simply the type returned by 316 * the corresponding API above. If the union can contain a type other than a 317 * typed array then the return type of the helper is a Maybe<…> wrapping the 318 * actual return type, with Nothing() signifying that the union contained a 319 * non-typed array value. 320 * 321 * template <typename ToType, typename T> 322 * [[nodiscard]] auto AppendTypedArrayDataTo(const T& aUnion, 323 * ToType& aResult); 324 * 325 * template <typename ToType, typename T> 326 * [[nodiscard]] auto CreateFromTypedArrayData(const T& aUnion); 327 * 328 * template <typename T, typename Processor> 329 * [[nodiscard]] auto ProcessTypedArrays( 330 * const T& aUnion, Processor&& aProcessor); 331 * 332 * template <typename T, typename Processor> 333 * [[nodiscard]] auto ProcessTypedArraysFixed(const T& aUnion, 334 * Processor&& aProcessor); 335 * 336 */ 337 338 template <class ArrayT> 339 struct TypedArray_base : public SpiderMonkeyInterfaceObjectStorage, 340 AllTypedArraysBase { 341 using element_type = typename ArrayT::DataType; 342 343 TypedArray_base() = default; 344 TypedArray_base(TypedArray_base&& aOther) = default; 345 346 public: 347 inline bool Init(JSObject* obj) { 348 MOZ_ASSERT(!inited()); 349 mImplObj = mWrappedObj = ArrayT::unwrap(obj).asObject(); 350 return inited(); 351 } 352 353 // About shared memory: 354 // 355 // Any DOM TypedArray as well as any DOM ArrayBufferView can map the 356 // memory of either a JS ArrayBuffer or a JS SharedArrayBuffer. 357 // 358 // Code that elects to allow views that map shared memory to be used 359 // -- ie, code that "opts in to shared memory" -- should generally 360 // not access the raw data buffer with standard C++ mechanisms as 361 // that creates the possibility of C++ data races, which is 362 // undefined behavior. The JS engine will eventually export (bug 363 // 1225033) a suite of methods that avoid undefined behavior. 364 // 365 // Callers of Obj() that do not opt in to shared memory can produce 366 // better diagnostics by checking whether the JSObject in fact maps 367 // shared memory and throwing an error if it does. However, it is 368 // safe to use the value of Obj() without such checks. 369 // 370 // The DOM TypedArray abstraction prevents the underlying buffer object 371 // from being accessed directly, but JS_GetArrayBufferViewBuffer(Obj()) 372 // will obtain the buffer object. Code that calls that function must 373 // not assume the returned buffer is an ArrayBuffer. That is guarded 374 // against by an out parameter on that call that communicates the 375 // sharedness of the buffer. 376 // 377 // Finally, note that the buffer memory of a SharedArrayBuffer is 378 // not detachable. 379 380 public: 381 /** 382 * Helper functions to append a copy of this typed array's data to a 383 * container. Returns false if the allocation for copying the data fails. 384 * 385 * aCalculator is an optional argument to which one can pass a lambda 386 * expression that will calculate the offset and length of the data to copy 387 * out of the typed array. aCalculator will be called with one argument of 388 * type size_t set to the length of the data in the typed array. It is allowed 389 * to return a std::pair<size_t, size_t> or a Maybe<std::pair<size_t, size_t>> 390 * containing the offset and the length of the subset of the data that we 391 * should copy. If the calculation can fail then aCalculator should return a 392 * Maybe<std::pair<size_t, size_t>>, if .isNothing() returns true for the 393 * return value then AppendDataTo will return false and the data won't be 394 * copied. 395 */ 396 397 template <typename... Calculator> 398 [[nodiscard]] bool AppendDataTo(nsCString& aResult, 399 Calculator&&... aCalculator) const { 400 static_assert(sizeof...(aCalculator) <= 1, 401 "AppendDataTo takes at most one aCalculator"); 402 403 return ProcessDataHelper( 404 [&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) { 405 return aResult.Append(aData, fallible); 406 }, 407 std::forward<Calculator>(aCalculator)...); 408 } 409 410 template <typename T, typename... Calculator> 411 [[nodiscard]] bool AppendDataTo(nsTArray<T>& aResult, 412 Calculator&&... aCalculator) const { 413 static_assert(sizeof...(aCalculator) <= 1, 414 "AppendDataTo takes at most one aCalculator"); 415 416 return ProcessDataHelper( 417 [&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) { 418 return aResult.AppendElements(aData, fallible); 419 }, 420 std::forward<Calculator>(aCalculator)...); 421 } 422 423 template <typename T, typename... Calculator> 424 [[nodiscard]] bool AppendDataTo(FallibleTArray<T>& aResult, 425 Calculator&&... aCalculator) const { 426 static_assert(sizeof...(aCalculator) <= 1, 427 "AppendDataTo takes at most one aCalculator"); 428 429 return ProcessDataHelper( 430 [&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) { 431 return aResult.AppendElements(aData, fallible); 432 }, 433 std::forward<Calculator>(aCalculator)...); 434 } 435 436 template <typename T, typename... Calculator> 437 [[nodiscard]] bool AppendDataTo(Vector<T>& aResult, 438 Calculator&&... aCalculator) const { 439 static_assert(sizeof...(aCalculator) <= 1, 440 "AppendDataTo takes at most one aCalculator"); 441 442 return ProcessDataHelper( 443 [&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) { 444 return aResult.append(aData.Elements(), aData.Length()); 445 }, 446 std::forward<Calculator>(aCalculator)...); 447 } 448 449 /** 450 * Helper functions to copy this typed array's data to a container. This will 451 * clear any existing data in the container. 452 * 453 * See the comments for AppendDataTo for information on the aCalculator 454 * argument. 455 */ 456 457 template <typename T, size_t N, typename... Calculator> 458 [[nodiscard]] bool CopyDataTo(T (&aResult)[N], 459 Calculator&&... aCalculator) const { 460 static_assert(sizeof...(aCalculator) <= 1, 461 "CopyDataTo takes at most one aCalculator"); 462 463 return ProcessDataHelper( 464 [&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) { 465 if (aData.Length() != N) { 466 return false; 467 } 468 for (size_t i = 0; i < N; ++i) { 469 aResult[i] = aData[i]; 470 } 471 return true; 472 }, 473 std::forward<Calculator>(aCalculator)...); 474 } 475 476 template <typename T, size_t N, typename... Calculator> 477 [[nodiscard]] bool CopyDataTo(std::array<T, N>* const aResult, 478 Calculator&&... aCalculator) const { 479 static_assert(sizeof...(aCalculator) <= 1, 480 "CopyDataTo takes at most one aCalculator"); 481 482 return ProcessDataHelper( 483 [&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) { 484 if (aData.Length() != N) { 485 return false; 486 } 487 for (size_t i = 0; i < N; ++i) { 488 (*aResult).at(i) = aData[i]; 489 } 490 return true; 491 }, 492 std::forward<Calculator>(aCalculator)...); 493 } 494 495 /** 496 * Helper functions to copy this typed array's data to a newly created 497 * container. Returns Nothing() if creating the container with the right size 498 * fails. 499 * 500 * See the comments for AppendDataTo for information on the aCalculator 501 * argument. 502 */ 503 504 template <typename T, typename... Calculator, 505 typename IsVector = 506 std::enable_if_t<std::is_same_v<Vector<element_type>, T>>> 507 [[nodiscard]] Maybe<Vector<element_type>> CreateFromData( 508 Calculator&&... aCalculator) const { 509 static_assert(sizeof...(aCalculator) <= 1, 510 "CreateFromData takes at most one aCalculator"); 511 512 return CreateFromDataHelper<T>( 513 [&](const Span<const element_type>& aData, 514 Vector<element_type>& aResult) { 515 if (!aResult.initCapacity(aData.Length())) { 516 return false; 517 } 518 aResult.infallibleAppend(aData.Elements(), aData.Length()); 519 return true; 520 }, 521 std::forward<Calculator>(aCalculator)...); 522 } 523 524 template <typename T, typename... Calculator, 525 typename IsUniquePtr = 526 std::enable_if_t<std::is_same_v<T, UniquePtr<element_type[]>>>> 527 [[nodiscard]] Maybe<UniquePtr<element_type[]>> CreateFromData( 528 Calculator&&... aCalculator) const { 529 static_assert(sizeof...(aCalculator) <= 1, 530 "CreateFromData takes at most one aCalculator"); 531 532 return CreateFromDataHelper<T>( 533 [&](const Span<const element_type>& aData, 534 UniquePtr<element_type[]>& aResult) { 535 aResult = 536 MakeUniqueForOverwriteFallible<element_type[]>(aData.Length()); 537 if (!aResult.get()) { 538 return false; 539 } 540 memcpy(aResult.get(), aData.Elements(), aData.LengthBytes()); 541 return true; 542 }, 543 std::forward<Calculator>(aCalculator)...); 544 } 545 546 template <typename T, typename... Calculator, 547 typename IsBuffer = 548 std::enable_if_t<std::is_same_v<T, Buffer<element_type>>>> 549 [[nodiscard]] Maybe<Buffer<element_type>> CreateFromData( 550 Calculator&&... aCalculator) const { 551 static_assert(sizeof...(aCalculator) <= 1, 552 "CreateFromData takes at most one aCalculator"); 553 554 return CreateFromDataHelper<T>( 555 [&](const Span<const element_type>& aData, 556 Buffer<element_type>& aResult) { 557 Maybe<Buffer<element_type>> buffer = 558 Buffer<element_type>::CopyFrom(aData); 559 if (buffer.isNothing()) { 560 return false; 561 } 562 aResult = buffer.extract(); 563 return true; 564 }, 565 std::forward<Calculator>(aCalculator)...); 566 } 567 568 private: 569 template <typename Processor, typename R = decltype(std::declval<Processor>()( 570 std::declval<Span<element_type>>(), 571 std::declval<JS::AutoCheckCannotGC>()))> 572 using ProcessNoGCReturnType = R; 573 574 template <typename Processor> 575 [[nodiscard]] static inline ProcessNoGCReturnType<Processor> 576 CallProcessorNoGC(const Span<element_type>& aData, Processor&& aProcessor, 577 JS::AutoCheckCannotGC&& nogc) { 578 MOZ_ASSERT( 579 aData.IsEmpty() || aData.Elements(), 580 "We expect a non-null data pointer for typed arrays that aren't empty"); 581 582 return aProcessor(aData, std::move(nogc)); 583 } 584 585 template <typename Processor, typename R = decltype(std::declval<Processor>()( 586 std::declval<Span<element_type>>()))> 587 using ProcessReturnType = R; 588 589 template <typename Processor> 590 [[nodiscard]] static inline ProcessReturnType<Processor> CallProcessor( 591 const Span<element_type>& aData, Processor&& aProcessor) { 592 MOZ_ASSERT( 593 aData.IsEmpty() || aData.Elements(), 594 "We expect a non-null data pointer for typed arrays that aren't empty"); 595 596 return aProcessor(aData); 597 } 598 599 struct MOZ_STACK_CLASS LengthPinner { 600 explicit LengthPinner(const TypedArray_base* aTypedArray) 601 : mTypedArray(aTypedArray), 602 mWasPinned( 603 !JS::PinArrayBufferOrViewLength(aTypedArray->Obj(), true)) {} 604 ~LengthPinner() { 605 if (!mWasPinned) { 606 JS::PinArrayBufferOrViewLength(mTypedArray->Obj(), false); 607 } 608 } 609 610 private: 611 const TypedArray_base* mTypedArray; 612 bool mWasPinned; 613 }; 614 615 template <typename Processor, typename Calculator> 616 [[nodiscard]] bool ProcessDataHelper( 617 Processor&& aProcessor, Calculator&& aCalculateOffsetAndLength) const { 618 LengthPinner pinner(this); 619 620 JS::AutoCheckCannotGC nogc; // `data` is GC-sensitive. 621 Span<element_type> data = GetCurrentData(); 622 const auto& offsetAndLength = aCalculateOffsetAndLength(data.Length()); 623 size_t offset, length; 624 if constexpr (std::is_convertible_v<decltype(offsetAndLength), 625 std::pair<size_t, size_t>>) { 626 std::tie(offset, length) = offsetAndLength; 627 } else { 628 if (offsetAndLength.isNothing()) { 629 return false; 630 } 631 std::tie(offset, length) = offsetAndLength.value(); 632 } 633 634 return CallProcessorNoGC(data.Subspan(offset, length), 635 std::forward<Processor>(aProcessor), 636 std::move(nogc)); 637 } 638 639 template <bool AllowLargeTypedArrays = false, typename Processor> 640 [[nodiscard]] ProcessNoGCReturnType<Processor> ProcessDataHelper( 641 Processor&& aProcessor) const { 642 LengthPinner pinner(this); 643 // The data from GetCurrentData() is GC sensitive. 644 JS::AutoCheckCannotGC nogc; 645 return CallProcessorNoGC(GetCurrentData<AllowLargeTypedArrays>(), 646 std::forward<Processor>(aProcessor), 647 std::move(nogc)); 648 } 649 650 public: 651 template <bool AllowLargeTypedArrays = false, typename Processor> 652 [[nodiscard]] ProcessNoGCReturnType<Processor> ProcessData( 653 Processor&& aProcessor) const { 654 return ProcessDataHelper<AllowLargeTypedArrays>( 655 std::forward<Processor>(aProcessor)); 656 } 657 658 template <typename Processor> 659 [[nodiscard]] ProcessReturnType<Processor> ProcessFixedData( 660 Processor&& aProcessor) const { 661 mozilla::dom::AutoJSAPI jsapi; 662 if (!jsapi.Init(mImplObj)) { 663 #if defined(EARLY_BETA_OR_EARLIER) 664 if constexpr (std::is_same_v<ArrayT, JS::ArrayBufferView>) { 665 if (!mImplObj) { 666 MOZ_CRASH("Null mImplObj"); 667 } 668 if (!xpc::NativeGlobal(mImplObj)) { 669 MOZ_CRASH("Null xpc::NativeGlobal(mImplObj)"); 670 } 671 if (!xpc::NativeGlobal(mImplObj)->GetGlobalJSObject()) { 672 MOZ_CRASH("Null xpc::NativeGlobal(mImplObj)->GetGlobalJSObject()"); 673 } 674 } 675 #endif 676 MOZ_CRASH("Failed to get JSContext"); 677 } 678 #if defined(EARLY_BETA_OR_EARLIER) 679 if constexpr (std::is_same_v<ArrayT, JS::ArrayBufferView>) { 680 JS::Rooted<JSObject*> view(jsapi.cx(), 681 js::UnwrapArrayBufferView(mImplObj)); 682 if (!view) { 683 if (JSObject* unwrapped = js::CheckedUnwrapStatic(mImplObj)) { 684 if (!js::UnwrapArrayBufferView(unwrapped)) { 685 MOZ_CRASH( 686 "Null " 687 "js::UnwrapArrayBufferView(js::CheckedUnwrapStatic(mImplObj))"); 688 } 689 view = unwrapped; 690 } else { 691 MOZ_CRASH("Null js::CheckedUnwrapStatic(mImplObj)"); 692 } 693 } 694 } 695 #endif 696 JS::AutoBrittleMode abm(jsapi.cx()); 697 if (!JS::EnsureNonInlineArrayBufferOrView(jsapi.cx(), mImplObj)) { 698 MOZ_CRASH("small oom when moving inline data out-of-line"); 699 } 700 LengthPinner pinner(this); 701 702 return CallProcessor(GetCurrentData(), std::forward<Processor>(aProcessor)); 703 } 704 705 private: 706 template <bool AllowLargeTypedArrays = false> 707 Span<element_type> GetCurrentData() const { 708 MOZ_ASSERT(inited()); 709 MOZ_RELEASE_ASSERT( 710 !ArrayT::fromObject(mImplObj).isResizable(), 711 "Bindings must have checked ArrayBuffer{View} is non-resizable"); 712 MOZ_RELEASE_ASSERT( 713 !ArrayT::fromObject(mImplObj).isImmutable(), 714 "Bindings must have checked ArrayBuffer{View} is mutable"); 715 716 // Intentionally return a pointer and length that escape from a nogc region. 717 // Private so it can only be used in very limited situations. 718 JS::AutoCheckCannotGC nogc; 719 bool shared; 720 Span<element_type> span = 721 ArrayT::fromObject(mImplObj).getData(&shared, nogc); 722 if constexpr (!AllowLargeTypedArrays) { 723 MOZ_RELEASE_ASSERT(span.Length() <= INT32_MAX, 724 "Bindings must have checked ArrayBuffer{View} length"); 725 } 726 return span; 727 } 728 729 template <typename T, typename F, typename... Calculator> 730 [[nodiscard]] Maybe<T> CreateFromDataHelper( 731 F&& aCreator, Calculator&&... aCalculator) const { 732 Maybe<T> result; 733 bool ok = ProcessDataHelper( 734 [&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) { 735 result.emplace(); 736 return aCreator(aData, *result); 737 }, 738 std::forward<Calculator>(aCalculator)...); 739 740 if (!ok) { 741 return Nothing(); 742 } 743 744 return result; 745 } 746 747 TypedArray_base(const TypedArray_base&) = delete; 748 }; 749 750 template <class ArrayT> 751 struct TypedArray : public TypedArray_base<ArrayT> { 752 using Base = TypedArray_base<ArrayT>; 753 using element_type = typename Base::element_type; 754 755 TypedArray() = default; 756 757 TypedArray(TypedArray&& aOther) = default; 758 759 static inline JSObject* Create(JSContext* cx, nsWrapperCache* creator, 760 size_t length, ErrorResult& error) { 761 return CreateCommon(cx, creator, length, error).asObject(); 762 } 763 764 static inline JSObject* Create(JSContext* cx, size_t length, 765 ErrorResult& error) { 766 return CreateCommon(cx, length, error).asObject(); 767 } 768 769 static inline JSObject* Create(JSContext* cx, nsWrapperCache* creator, 770 Span<const element_type> data, 771 ErrorResult& error) { 772 ArrayT array = CreateCommon(cx, creator, data.Length(), error); 773 if (!error.Failed() && !data.IsEmpty()) { 774 CopyFrom(cx, data, array); 775 } 776 return array.asObject(); 777 } 778 779 static inline JSObject* Create(JSContext* cx, Span<const element_type> data, 780 ErrorResult& error) { 781 ArrayT array = CreateCommon(cx, data.Length(), error); 782 if (!error.Failed() && !data.IsEmpty()) { 783 CopyFrom(cx, data, array); 784 } 785 return array.asObject(); 786 } 787 788 private: 789 template <typename> 790 friend class TypedArrayCreator; 791 792 static inline ArrayT CreateCommon(JSContext* cx, nsWrapperCache* creator, 793 size_t length, ErrorResult& error) { 794 JS::Rooted<JSObject*> creatorWrapper(cx); 795 Maybe<JSAutoRealm> ar; 796 if (creator && (creatorWrapper = creator->GetWrapperPreserveColor())) { 797 ar.emplace(cx, creatorWrapper); 798 } 799 800 return CreateCommon(cx, length, error); 801 } 802 static inline ArrayT CreateCommon(JSContext* cx, size_t length, 803 ErrorResult& error) { 804 error.MightThrowJSException(); 805 ArrayT array = CreateCommon(cx, length); 806 if (array) { 807 return array; 808 } 809 error.StealExceptionFromJSContext(cx); 810 return ArrayT::fromObject(nullptr); 811 } 812 // NOTE: this leaves any exceptions on the JSContext, and the caller is 813 // required to deal with them. 814 static inline ArrayT CreateCommon(JSContext* cx, size_t length) { 815 return ArrayT::create(cx, length); 816 } 817 static inline void CopyFrom(JSContext* cx, 818 const Span<const element_type>& data, 819 ArrayT& dest) { 820 JS::AutoCheckCannotGC nogc; 821 bool isShared; 822 mozilla::Span<element_type> span = dest.getData(&isShared, nogc); 823 MOZ_ASSERT(span.size() == data.size(), 824 "Didn't create a large enough typed array object?"); 825 // Data will not be shared, until a construction protocol exists 826 // for constructing shared data. 827 MOZ_ASSERT(!isShared); 828 memcpy(span.Elements(), data.Elements(), data.LengthBytes()); 829 } 830 831 TypedArray(const TypedArray&) = delete; 832 }; 833 834 template <JS::Scalar::Type GetViewType(JSObject*)> 835 struct ArrayBufferView_base : public TypedArray_base<JS::ArrayBufferView> { 836 private: 837 using Base = TypedArray_base<JS::ArrayBufferView>; 838 839 public: 840 ArrayBufferView_base() : Base(), mType(JS::Scalar::MaxTypedArrayViewType) {} 841 842 ArrayBufferView_base(ArrayBufferView_base&& aOther) 843 : Base(std::move(aOther)), mType(aOther.mType) { 844 aOther.mType = JS::Scalar::MaxTypedArrayViewType; 845 } 846 847 private: 848 JS::Scalar::Type mType; 849 850 public: 851 inline bool Init(JSObject* obj) { 852 if (!Base::Init(obj)) { 853 return false; 854 } 855 856 mType = GetViewType(this->Obj()); 857 return true; 858 } 859 860 inline JS::Scalar::Type Type() const { 861 MOZ_ASSERT(this->inited()); 862 return mType; 863 } 864 }; 865 866 using Int8Array = TypedArray<JS::Int8Array>; 867 using Uint8Array = TypedArray<JS::Uint8Array>; 868 using Uint8ClampedArray = TypedArray<JS::Uint8ClampedArray>; 869 using Int16Array = TypedArray<JS::Int16Array>; 870 using Uint16Array = TypedArray<JS::Uint16Array>; 871 using Int32Array = TypedArray<JS::Int32Array>; 872 using Uint32Array = TypedArray<JS::Uint32Array>; 873 using Float32Array = TypedArray<JS::Float32Array>; 874 using Float64Array = TypedArray<JS::Float64Array>; 875 using BigInt64Array = TypedArray<JS::BigInt64Array>; 876 using BigUint64Array = TypedArray<JS::BigUint64Array>; 877 using ArrayBufferView = ArrayBufferView_base<JS_GetArrayBufferViewType>; 878 using ArrayBuffer = TypedArray<JS::ArrayBuffer>; 879 880 // A class for converting an nsTArray to a TypedArray 881 // Note: A TypedArrayCreator must not outlive the nsTArray it was created from. 882 // So this is best used to pass from things that understand nsTArray to 883 // things that understand TypedArray, as with ToJSValue. 884 template <typename TypedArrayType> 885 class MOZ_STACK_CLASS TypedArrayCreator { 886 using ValuesType = typename TypedArrayType::element_type; 887 using ArrayType = nsTArray<ValuesType>; 888 889 public: 890 explicit TypedArrayCreator(const ArrayType& aArray) : mValues(aArray) {} 891 explicit TypedArrayCreator(const nsCString& aString) : mValues(aString) {} 892 893 // NOTE: this leaves any exceptions on the JSContext, and the caller is 894 // required to deal with them. 895 JSObject* Create(JSContext* aCx) const { 896 auto array = TypedArrayType::CreateCommon(aCx, mValues.Length()); 897 if (array) { 898 TypedArrayType::CopyFrom(aCx, mValues, array); 899 } 900 return array.asObject(); 901 } 902 903 private: 904 Span<const ValuesType> mValues; 905 }; 906 907 namespace binding_detail { 908 909 template <typename Union, typename UnionMemberType, typename = int> 910 struct ApplyToTypedArray; 911 912 #define APPLY_IMPL(type) \ 913 template <typename Union> \ 914 struct ApplyToTypedArray<Union, type, decltype((void)&Union::Is##type, 0)> { \ 915 /* Return type of calling the lambda with a TypedArray 'type'. */ \ 916 template <typename F> \ 917 using FunReturnType = decltype(std::declval<F>()(std::declval<type>())); \ 918 \ 919 /* Whether the return type of calling the lambda with a TypedArray */ \ 920 /* 'type' is void. */ \ 921 template <typename F> \ 922 static constexpr bool FunReturnsVoid = \ 923 std::is_same_v<FunReturnType<F>, void>; \ 924 \ 925 /* The return type of calling Apply with a union that has 'type' as */ \ 926 /* one of its union member types depends on the return type of */ \ 927 /* calling the lambda. This return type will be bool if the lambda */ \ 928 /* returns void, or it will be a Maybe<…> with the inner type being */ \ 929 /* the actual return type of calling the lambda. If the union */ \ 930 /* contains a value of the right type, then calling Apply will return */ \ 931 /* either 'true', or 'Some(…)' containing the return value of calling */ \ 932 /* the lambda. If the union does not contain a value of the right */ \ 933 /* type, then calling Apply will return either 'false', or */ \ 934 /* 'Nothing()'. */ \ 935 template <typename F> \ 936 using ApplyReturnType = \ 937 std::conditional_t<FunReturnsVoid<F>, bool, Maybe<FunReturnType<F>>>; \ 938 \ 939 public: \ 940 template <typename F> \ 941 static ApplyReturnType<F> Apply(const Union& aUnion, F&& aFun) { \ 942 if (!aUnion.Is##type()) { \ 943 return ApplyReturnType<F>(); /* false or Nothing() */ \ 944 } \ 945 if constexpr (FunReturnsVoid<F>) { \ 946 std::forward<F>(aFun)(aUnion.GetAs##type()); \ 947 return true; \ 948 } else { \ 949 return Some(std::forward<F>(aFun)(aUnion.GetAs##type())); \ 950 } \ 951 } \ 952 }; 953 954 APPLY_IMPL(Int8Array) 955 APPLY_IMPL(Uint8Array) 956 APPLY_IMPL(Uint8ClampedArray) 957 APPLY_IMPL(Int16Array) 958 APPLY_IMPL(Uint16Array) 959 APPLY_IMPL(Int32Array) 960 APPLY_IMPL(Uint32Array) 961 APPLY_IMPL(Float32Array) 962 APPLY_IMPL(Float64Array) 963 APPLY_IMPL(BigInt64Array) 964 APPLY_IMPL(BigUint64Array) 965 APPLY_IMPL(ArrayBufferView) 966 APPLY_IMPL(ArrayBuffer) 967 968 #undef APPLY_IMPL 969 970 // The binding code generate creates an alias of this type for every WebIDL 971 // union that contains a typed array type, with the right value for H (which 972 // will be true if there are non-typedarray types in the union). 973 template <typename T, bool H, typename FirstUnionMember, 974 typename... UnionMembers> 975 struct ApplyToTypedArraysHelper { 976 static constexpr bool HasNonTypedArrayMembers = H; 977 template <typename Fun> 978 static auto Apply(const T& aUnion, Fun&& aFun) { 979 auto result = ApplyToTypedArray<T, FirstUnionMember>::Apply( 980 aUnion, std::forward<Fun>(aFun)); 981 if constexpr (sizeof...(UnionMembers) == 0) { 982 return result; 983 } else { 984 if (result) { 985 return result; 986 } else { 987 return ApplyToTypedArraysHelper<T, H, UnionMembers...>::template Apply< 988 Fun>(aUnion, std::forward<Fun>(aFun)); 989 } 990 } 991 } 992 }; 993 994 template <typename T, typename Fun> 995 auto ApplyToTypedArrays(const T& aUnion, Fun&& aFun) { 996 using ApplyToTypedArrays = typename T::ApplyToTypedArrays; 997 998 auto result = 999 ApplyToTypedArrays::template Apply<Fun>(aUnion, std::forward<Fun>(aFun)); 1000 if constexpr (ApplyToTypedArrays::HasNonTypedArrayMembers) { 1001 return result; 1002 } else { 1003 MOZ_ASSERT(result, "Didn't expect union members other than typed arrays"); 1004 1005 if constexpr (std::is_same_v<std::remove_cv_t< 1006 std::remove_reference_t<decltype(result)>>, 1007 bool>) { 1008 return; 1009 } else { 1010 return result.extract(); 1011 } 1012 } 1013 } 1014 1015 } // namespace binding_detail 1016 1017 template <typename T, typename ToType, 1018 std::enable_if_t<is_dom_union_with_typedarray_members<T>, int> = 0> 1019 [[nodiscard]] auto AppendTypedArrayDataTo(const T& aUnion, ToType& aResult) { 1020 return binding_detail::ApplyToTypedArrays( 1021 aUnion, [&](const auto& aTypedArray) { 1022 return aTypedArray.AppendDataTo(aResult); 1023 }); 1024 } 1025 1026 template <typename ToType, typename T, 1027 std::enable_if_t<is_dom_union_with_typedarray_members<T>, int> = 0> 1028 [[nodiscard]] auto CreateFromTypedArrayData(const T& aUnion) { 1029 return binding_detail::ApplyToTypedArrays( 1030 aUnion, [&](const auto& aTypedArray) { 1031 return aTypedArray.template CreateFromData<ToType>(); 1032 }); 1033 } 1034 1035 template <typename T, typename Processor, 1036 std::enable_if_t<is_dom_union_with_typedarray_members<T>, int> = 0> 1037 [[nodiscard]] auto ProcessTypedArrays(const T& aUnion, Processor&& aProcessor) { 1038 return binding_detail::ApplyToTypedArrays( 1039 aUnion, [&](const auto& aTypedArray) { 1040 return aTypedArray.ProcessData(std::forward<Processor>(aProcessor)); 1041 }); 1042 } 1043 1044 template <typename T, typename Processor, 1045 std::enable_if_t<is_dom_union_with_typedarray_members<T>, int> = 0> 1046 [[nodiscard]] auto ProcessTypedArraysFixed(const T& aUnion, 1047 Processor&& aProcessor) { 1048 return binding_detail::ApplyToTypedArrays( 1049 aUnion, [&](const auto& aTypedArray) { 1050 return aTypedArray.ProcessFixedData( 1051 std::forward<Processor>(aProcessor)); 1052 }); 1053 } 1054 1055 } // namespace mozilla::dom 1056 1057 #endif /* mozilla_dom_TypedArray_h */