JSGenericTypedArrayViewInlines.h
1 /* 2 * Copyright (C) 2013-2019 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #pragma once 27 28 #include "ArrayBufferView.h" 29 #include "DeferGC.h" 30 #include "Error.h" 31 #include "ExceptionHelpers.h" 32 #include "JSArrayBuffer.h" 33 #include "JSCellInlines.h" 34 #include "JSGenericTypedArrayView.h" 35 #include "TypeError.h" 36 #include "TypedArrays.h" 37 #include <wtf/text/StringConcatenateNumbers.h> 38 39 namespace JSC { 40 41 template<typename Adaptor> 42 JSGenericTypedArrayView<Adaptor>::JSGenericTypedArrayView( 43 VM& vm, ConstructionContext& context) 44 : Base(vm, context) 45 { 46 } 47 48 template<typename Adaptor> 49 JSGenericTypedArrayView<Adaptor>* JSGenericTypedArrayView<Adaptor>::create( 50 JSGlobalObject* globalObject, Structure* structure, unsigned length) 51 { 52 VM& vm = globalObject->vm(); 53 auto scope = DECLARE_THROW_SCOPE(vm); 54 ConstructionContext context(vm, structure, length, sizeof(typename Adaptor::Type)); 55 if (!context) { 56 throwOutOfMemoryError(globalObject, scope); 57 return nullptr; 58 } 59 JSGenericTypedArrayView* result = 60 new (NotNull, allocateCell<JSGenericTypedArrayView>(vm.heap)) 61 JSGenericTypedArrayView(vm, context); 62 result->finishCreation(vm); 63 return result; 64 } 65 66 template<typename Adaptor> 67 JSGenericTypedArrayView<Adaptor>* JSGenericTypedArrayView<Adaptor>::createWithFastVector( 68 JSGlobalObject* globalObject, Structure* structure, unsigned length, void* vector) 69 { 70 VM& vm = globalObject->vm(); 71 ConstructionContext context(structure, length, vector); 72 RELEASE_ASSERT(context); 73 JSGenericTypedArrayView* result = 74 new (NotNull, allocateCell<JSGenericTypedArrayView>(vm.heap)) 75 JSGenericTypedArrayView(vm, context); 76 result->finishCreation(vm); 77 return result; 78 } 79 80 template<typename Adaptor> 81 JSGenericTypedArrayView<Adaptor>* JSGenericTypedArrayView<Adaptor>::createUninitialized(JSGlobalObject* globalObject, Structure* structure, unsigned length) 82 { 83 VM& vm = globalObject->vm(); 84 auto scope = DECLARE_THROW_SCOPE(vm); 85 ConstructionContext context( 86 vm, structure, length, sizeof(typename Adaptor::Type), 87 ConstructionContext::DontInitialize); 88 if (!context) { 89 throwOutOfMemoryError(globalObject, scope); 90 return nullptr; 91 } 92 JSGenericTypedArrayView* result = 93 new (NotNull, allocateCell<JSGenericTypedArrayView>(vm.heap)) 94 JSGenericTypedArrayView(vm, context); 95 result->finishCreation(vm); 96 return result; 97 } 98 99 template<typename Adaptor> 100 JSGenericTypedArrayView<Adaptor>* JSGenericTypedArrayView<Adaptor>::create( 101 JSGlobalObject* globalObject, Structure* structure, RefPtr<ArrayBuffer>&& buffer, 102 unsigned byteOffset, unsigned length) 103 { 104 VM& vm = globalObject->vm(); 105 auto scope = DECLARE_THROW_SCOPE(vm); 106 size_t size = sizeof(typename Adaptor::Type); 107 ASSERT(buffer); 108 if (!ArrayBufferView::verifySubRangeLength(*buffer, byteOffset, length, size)) { 109 throwException(globalObject, scope, createRangeError(globalObject, "Length out of range of buffer")); 110 return nullptr; 111 } 112 if (!ArrayBufferView::verifyByteOffsetAlignment(byteOffset, size)) { 113 throwException(globalObject, scope, createRangeError(globalObject, "Byte offset is not aligned")); 114 return nullptr; 115 } 116 ConstructionContext context(vm, structure, WTFMove(buffer), byteOffset, length); 117 ASSERT(context); 118 JSGenericTypedArrayView* result = 119 new (NotNull, allocateCell<JSGenericTypedArrayView>(vm.heap)) 120 JSGenericTypedArrayView(vm, context); 121 result->finishCreation(vm); 122 return result; 123 } 124 125 template<typename Adaptor> 126 JSGenericTypedArrayView<Adaptor>* JSGenericTypedArrayView<Adaptor>::create( 127 VM& vm, Structure* structure, RefPtr<typename Adaptor::ViewType>&& impl) 128 { 129 ConstructionContext context(vm, structure, impl->possiblySharedBuffer(), impl->byteOffset(), impl->length()); 130 ASSERT(context); 131 JSGenericTypedArrayView* result = 132 new (NotNull, allocateCell<JSGenericTypedArrayView>(vm.heap)) 133 JSGenericTypedArrayView(vm, context); 134 result->finishCreation(vm); 135 return result; 136 } 137 138 template<typename Adaptor> 139 JSGenericTypedArrayView<Adaptor>* JSGenericTypedArrayView<Adaptor>::create( 140 Structure* structure, JSGlobalObject* globalObject, 141 RefPtr<typename Adaptor::ViewType>&& impl) 142 { 143 return create(globalObject->vm(), structure, WTFMove(impl)); 144 } 145 146 template<typename Adaptor> 147 bool JSGenericTypedArrayView<Adaptor>::validateRange( 148 JSGlobalObject* globalObject, unsigned offset, unsigned length) 149 { 150 VM& vm = globalObject->vm(); 151 auto scope = DECLARE_THROW_SCOPE(vm); 152 if (canAccessRangeQuickly(offset, length)) 153 return true; 154 155 throwException(globalObject, scope, createRangeError(globalObject, "Range consisting of offset and length are out of bounds")); 156 return false; 157 } 158 159 template<typename Adaptor> 160 template<typename OtherAdaptor> 161 bool JSGenericTypedArrayView<Adaptor>::setWithSpecificType( 162 JSGlobalObject* globalObject, unsigned offset, JSGenericTypedArrayView<OtherAdaptor>* other, 163 unsigned otherOffset, unsigned length, CopyType type) 164 { 165 // Handle the hilarious case: the act of getting the length could have resulted 166 // in detaching. Well, no. That'll never happen because there cannot be 167 // side-effects on getting the length of a typed array. But predicting where there 168 // are, or aren't, side-effects is a fool's game so we resort to this cheap 169 // check. Worst case, if we're wrong, people start seeing less things get copied 170 // but we won't have a security vulnerability. 171 length = std::min(length, other->length()); 172 173 RELEASE_ASSERT(other->canAccessRangeQuickly(otherOffset, length)); 174 if (!validateRange(globalObject, offset, length)) 175 return false; 176 177 // This method doesn't support copying between the same array. Note that 178 // set() will only call this if the types differ, which implicitly guarantees 179 // that we can't be the same array. This is relevant because the way we detect 180 // non-overlapping is by checking if either (a) either array doesn't have a 181 // backing buffer or (b) the backing buffers are different, but that doesn't 182 // catch the case where it's the *same* array - fortunately though, this code 183 // path never needs to worry about that case. 184 ASSERT(static_cast<JSCell*>(this) != static_cast<JSCell*>(other)); 185 186 // 1) If the two arrays are non-overlapping, we can copy in any order we like 187 // and we don't need an intermediate buffer. Arrays are definitely 188 // non-overlapping if either one of them has no backing buffer (that means 189 // that it *owns* its philosophical backing buffer) or if they have 190 // different backing buffers. 191 // 2) If the two arrays overlap but have the same element size, we can do a 192 // memmove-like copy where we flip-flop direction based on which vector 193 // starts before the other: 194 // A) If the destination vector is before the source vector, then a forward 195 // copy is in order. 196 // B) If the destination vector is after the source vector, then a backward 197 // copy is in order. 198 // 3) If we have different element sizes and there is a chance of overlap then 199 // we need an intermediate vector. 200 201 // NB. Comparisons involving elementSize will be constant-folded by template 202 // specialization. 203 204 unsigned otherElementSize = sizeof(typename OtherAdaptor::Type); 205 206 // Handle cases (1) and (2A). 207 if (!hasArrayBuffer() || !other->hasArrayBuffer() 208 || existingBuffer() != other->existingBuffer() 209 || (elementSize == otherElementSize && vector() <= other->vector()) 210 || type == CopyType::LeftToRight) { 211 for (unsigned i = 0; i < length; ++i) { 212 setIndexQuicklyToNativeValue( 213 offset + i, OtherAdaptor::template convertTo<Adaptor>( 214 other->getIndexQuicklyAsNativeValue(i + otherOffset))); 215 } 216 return true; 217 } 218 219 // Now we either have (2B) or (3) - so first we try to cover (2B). 220 if (elementSize == otherElementSize) { 221 for (unsigned i = length; i--;) { 222 setIndexQuicklyToNativeValue( 223 offset + i, OtherAdaptor::template convertTo<Adaptor>( 224 other->getIndexQuicklyAsNativeValue(i + otherOffset))); 225 } 226 return true; 227 } 228 229 // Fail: we need an intermediate transfer buffer (i.e. case (3)). 230 Vector<typename Adaptor::Type, 32> transferBuffer(length); 231 for (unsigned i = length; i--;) { 232 transferBuffer[i] = OtherAdaptor::template convertTo<Adaptor>( 233 other->getIndexQuicklyAsNativeValue(i + otherOffset)); 234 } 235 for (unsigned i = length; i--;) 236 setIndexQuicklyToNativeValue(offset + i, transferBuffer[i]); 237 238 return true; 239 } 240 241 template<typename Adaptor> 242 bool JSGenericTypedArrayView<Adaptor>::set( 243 JSGlobalObject* globalObject, unsigned offset, JSObject* object, unsigned objectOffset, unsigned length, CopyType type) 244 { 245 VM& vm = globalObject->vm(); 246 auto scope = DECLARE_THROW_SCOPE(vm); 247 248 const ClassInfo* ci = object->classInfo(vm); 249 if (ci->typedArrayStorageType == Adaptor::typeValue) { 250 // The super fast case: we can just memcpy since we're the same type. 251 JSGenericTypedArrayView* other = jsCast<JSGenericTypedArrayView*>(object); 252 length = std::min(length, other->length()); 253 254 RELEASE_ASSERT(other->canAccessRangeQuickly(objectOffset, length)); 255 bool success = validateRange(globalObject, offset, length); 256 EXCEPTION_ASSERT(!scope.exception() == success); 257 if (!success) 258 return false; 259 260 memmove(typedVector() + offset, other->typedVector() + objectOffset, length * elementSize); 261 return true; 262 } 263 264 switch (ci->typedArrayStorageType) { 265 case TypeInt8: 266 RELEASE_AND_RETURN(scope, setWithSpecificType<Int8Adaptor>( 267 globalObject, offset, jsCast<JSInt8Array*>(object), objectOffset, length, type)); 268 case TypeInt16: 269 RELEASE_AND_RETURN(scope, setWithSpecificType<Int16Adaptor>( 270 globalObject, offset, jsCast<JSInt16Array*>(object), objectOffset, length, type)); 271 case TypeInt32: 272 RELEASE_AND_RETURN(scope, setWithSpecificType<Int32Adaptor>( 273 globalObject, offset, jsCast<JSInt32Array*>(object), objectOffset, length, type)); 274 case TypeUint8: 275 RELEASE_AND_RETURN(scope, setWithSpecificType<Uint8Adaptor>( 276 globalObject, offset, jsCast<JSUint8Array*>(object), objectOffset, length, type)); 277 case TypeUint8Clamped: 278 RELEASE_AND_RETURN(scope, setWithSpecificType<Uint8ClampedAdaptor>( 279 globalObject, offset, jsCast<JSUint8ClampedArray*>(object), objectOffset, length, type)); 280 case TypeUint16: 281 RELEASE_AND_RETURN(scope, setWithSpecificType<Uint16Adaptor>( 282 globalObject, offset, jsCast<JSUint16Array*>(object), objectOffset, length, type)); 283 case TypeUint32: 284 RELEASE_AND_RETURN(scope, setWithSpecificType<Uint32Adaptor>( 285 globalObject, offset, jsCast<JSUint32Array*>(object), objectOffset, length, type)); 286 case TypeFloat32: 287 RELEASE_AND_RETURN(scope, setWithSpecificType<Float32Adaptor>( 288 globalObject, offset, jsCast<JSFloat32Array*>(object), objectOffset, length, type)); 289 case TypeFloat64: 290 RELEASE_AND_RETURN(scope, setWithSpecificType<Float64Adaptor>( 291 globalObject, offset, jsCast<JSFloat64Array*>(object), objectOffset, length, type)); 292 case NotTypedArray: 293 case TypeDataView: { 294 bool success = validateRange(globalObject, offset, length); 295 EXCEPTION_ASSERT(!scope.exception() == success); 296 if (!success) 297 return false; 298 299 // We could optimize this case. But right now, we don't. 300 for (unsigned i = 0; i < length; ++i) { 301 JSValue value = object->get(globalObject, i + objectOffset); 302 RETURN_IF_EXCEPTION(scope, false); 303 bool success = setIndex(globalObject, offset + i, value); 304 EXCEPTION_ASSERT(!scope.exception() || !success); 305 if (!success) { 306 if (isDetached()) 307 throwTypeError(globalObject, scope, typedArrayBufferHasBeenDetachedErrorMessage); 308 return false; 309 } 310 } 311 return true; 312 } } 313 314 RELEASE_ASSERT_NOT_REACHED(); 315 return false; 316 } 317 318 template<typename Adaptor> 319 RefPtr<typename Adaptor::ViewType> JSGenericTypedArrayView<Adaptor>::possiblySharedTypedImpl() 320 { 321 return Adaptor::ViewType::tryCreate(possiblySharedBuffer(), byteOffset(), length()); 322 } 323 324 template<typename Adaptor> 325 RefPtr<typename Adaptor::ViewType> JSGenericTypedArrayView<Adaptor>::unsharedTypedImpl() 326 { 327 return Adaptor::ViewType::tryCreate(unsharedBuffer(), byteOffset(), length()); 328 } 329 330 template<typename Adaptor> 331 ArrayBuffer* JSGenericTypedArrayView<Adaptor>::existingBuffer() 332 { 333 return existingBufferInButterfly(); 334 } 335 336 template<typename Adaptor> 337 bool JSGenericTypedArrayView<Adaptor>::getOwnPropertySlot( 338 JSObject* object, JSGlobalObject* globalObject, PropertyName propertyName, PropertySlot& slot) 339 { 340 JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(object); 341 342 if (Optional<uint32_t> index = parseIndex(propertyName)) { 343 static_assert(std::is_final_v<JSGenericTypedArrayView<Adaptor>>, "getOwnPropertySlotByIndex must not be overridden"); 344 return getOwnPropertySlotByIndex(thisObject, globalObject, index.value(), slot); 345 } 346 347 if (isCanonicalNumericIndexString(propertyName)) 348 return false; 349 350 return Base::getOwnPropertySlot(thisObject, globalObject, propertyName, slot); 351 } 352 353 template<typename Adaptor> 354 bool JSGenericTypedArrayView<Adaptor>::put( 355 JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, 356 PutPropertySlot& slot) 357 { 358 JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(cell); 359 360 if (Optional<uint32_t> index = parseIndex(propertyName)) { 361 static_assert(std::is_final_v<JSGenericTypedArrayView<Adaptor>>, "putByIndex must not be overridden"); 362 return putByIndex(thisObject, globalObject, index.value(), value, slot.isStrictMode()); 363 } 364 365 if (isCanonicalNumericIndexString(propertyName)) { 366 // Cases like '-0', '1.1', etc. are still obliged to give the RHS a chance to throw. 367 toNativeFromValue<Adaptor>(globalObject, value); 368 return true; 369 } 370 371 return Base::put(thisObject, globalObject, propertyName, value, slot); 372 } 373 374 template<typename Adaptor> 375 bool JSGenericTypedArrayView<Adaptor>::defineOwnProperty( 376 JSObject* object, JSGlobalObject* globalObject, PropertyName propertyName, 377 const PropertyDescriptor& descriptor, bool shouldThrow) 378 { 379 VM& vm = globalObject->vm(); 380 auto scope = DECLARE_THROW_SCOPE(vm); 381 JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(object); 382 383 if (Optional<uint32_t> index = parseIndex(propertyName)) { 384 auto throwTypeErrorIfNeeded = [&] (const char* errorMessage) -> bool { 385 if (shouldThrow) 386 throwTypeError(globalObject, scope, makeString(errorMessage, *index)); 387 return false; 388 }; 389 390 if (index.value() >= thisObject->m_length) 391 return false; 392 393 if (descriptor.isAccessorDescriptor()) 394 return throwTypeErrorIfNeeded("Attempting to store accessor property on a typed array at index: "); 395 396 if (descriptor.configurablePresent() && !descriptor.configurable()) 397 return throwTypeErrorIfNeeded("Attempting to store non-configurable property on a typed array at index: "); 398 399 if (descriptor.enumerablePresent() && !descriptor.enumerable()) 400 return throwTypeErrorIfNeeded("Attempting to store non-enumerable property on a typed array at index: "); 401 402 if (descriptor.writablePresent() && !descriptor.writable()) 403 return throwTypeErrorIfNeeded("Attempting to store non-writable property on a typed array at index: "); 404 405 if (descriptor.value()) 406 RELEASE_AND_RETURN(scope, thisObject->setIndex(globalObject, index.value(), descriptor.value())); 407 408 return true; 409 } 410 411 if (isCanonicalNumericIndexString(propertyName)) 412 return false; 413 414 RELEASE_AND_RETURN(scope, Base::defineOwnProperty(thisObject, globalObject, propertyName, descriptor, shouldThrow)); 415 } 416 417 template<typename Adaptor> 418 bool JSGenericTypedArrayView<Adaptor>::deleteProperty( 419 JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, DeletePropertySlot& slot) 420 { 421 JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(cell); 422 423 if (Optional<uint32_t> index = parseIndex(propertyName)) { 424 static_assert(std::is_final_v<JSGenericTypedArrayView<Adaptor>>, "deletePropertyByIndex must not be overridden"); 425 return deletePropertyByIndex(thisObject, globalObject, index.value()); 426 } 427 428 if (isCanonicalNumericIndexString(propertyName)) 429 return true; 430 431 return Base::deleteProperty(thisObject, globalObject, propertyName, slot); 432 } 433 434 template<typename Adaptor> 435 bool JSGenericTypedArrayView<Adaptor>::getOwnPropertySlotByIndex( 436 JSObject* object, JSGlobalObject*, unsigned propertyName, PropertySlot& slot) 437 { 438 JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(object); 439 440 if (thisObject->isDetached() || !thisObject->canGetIndexQuickly(propertyName)) 441 return false; 442 443 slot.setValue(thisObject, static_cast<unsigned>(PropertyAttribute::None), thisObject->getIndexQuickly(propertyName)); 444 return true; 445 } 446 447 template<typename Adaptor> 448 bool JSGenericTypedArrayView<Adaptor>::putByIndex( 449 JSCell* cell, JSGlobalObject* globalObject, unsigned propertyName, JSValue value, bool) 450 { 451 JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(cell); 452 thisObject->setIndex(globalObject, propertyName, value); 453 return true; 454 } 455 456 template<typename Adaptor> 457 bool JSGenericTypedArrayView<Adaptor>::deletePropertyByIndex( 458 JSCell* cell, JSGlobalObject*, unsigned propertyName) 459 { 460 // Integer-indexed elements can't be deleted, so we must return false when the index is valid. 461 JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(cell); 462 return thisObject->isDetached() || propertyName >= thisObject->m_length; 463 } 464 465 template<typename Adaptor> 466 void JSGenericTypedArrayView<Adaptor>::getOwnPropertyNames( 467 JSObject* object, JSGlobalObject* globalObject, PropertyNameArray& array, DontEnumPropertiesMode mode) 468 { 469 VM& vm = globalObject->vm(); 470 JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(object); 471 472 if (array.includeStringProperties()) { 473 for (unsigned i = 0; i < thisObject->m_length; ++i) 474 array.add(Identifier::from(vm, i)); 475 } 476 477 thisObject->getOwnNonIndexPropertyNames(globalObject, array, mode); 478 } 479 480 template<typename Adaptor> 481 size_t JSGenericTypedArrayView<Adaptor>::estimatedSize(JSCell* cell, VM& vm) 482 { 483 JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(cell); 484 485 if (thisObject->m_mode == OversizeTypedArray) 486 return Base::estimatedSize(thisObject, vm) + thisObject->byteSize(); 487 if (thisObject->m_mode == FastTypedArray && thisObject->hasVector()) 488 return Base::estimatedSize(thisObject, vm) + thisObject->byteSize(); 489 490 return Base::estimatedSize(thisObject, vm); 491 } 492 493 template<typename Adaptor> 494 void JSGenericTypedArrayView<Adaptor>::visitChildren(JSCell* cell, SlotVisitor& visitor) 495 { 496 JSGenericTypedArrayView* thisObject = jsCast<JSGenericTypedArrayView*>(cell); 497 ASSERT_GC_OBJECT_INHERITS(thisObject, info()); 498 Base::visitChildren(thisObject, visitor); 499 500 TypedArrayMode mode; 501 void* vector; 502 size_t byteSize; 503 504 { 505 auto locker = holdLock(thisObject->cellLock()); 506 mode = thisObject->m_mode; 507 vector = thisObject->vector(); 508 byteSize = thisObject->byteSize(); 509 } 510 511 switch (mode) { 512 case FastTypedArray: { 513 if (vector) 514 visitor.markAuxiliary(vector); 515 break; 516 } 517 518 case OversizeTypedArray: { 519 visitor.reportExtraMemoryVisited(byteSize); 520 break; 521 } 522 523 case WastefulTypedArray: 524 break; 525 526 case DataViewMode: 527 RELEASE_ASSERT_NOT_REACHED(); 528 break; 529 } 530 } 531 532 } // namespace JSC