ArrayPrototype.cpp
1 /* 2 * Copyright (C) 1999-2000 Harri Porten (porten@kde.org) 3 * Copyright (C) 2003-2020 Apple Inc. All rights reserved. 4 * Copyright (C) 2003 Peter Kelly (pmk@post.com) 5 * Copyright (C) 2006 Alexey Proskuryakov (ap@nypop.com) 6 * 7 * This library is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Lesser General Public 9 * License as published by the Free Software Foundation; either 10 * version 2 of the License, or (at your option) any later version. 11 * 12 * This library is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this library; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 20 * USA 21 * 22 */ 23 24 #include "config.h" 25 #include "ArrayPrototype.h" 26 27 #include "ArrayConstructor.h" 28 #include "BuiltinNames.h" 29 #include "IntegrityInlines.h" 30 #include "JSArrayInlines.h" 31 #include "JSArrayIterator.h" 32 #include "JSCBuiltins.h" 33 #include "JSCInlines.h" 34 #include "JSImmutableButterfly.h" 35 #include "JSStringJoiner.h" 36 #include "ObjectConstructor.h" 37 #include "StringRecursionChecker.h" 38 #include <algorithm> 39 #include <wtf/Assertions.h> 40 41 namespace JSC { 42 43 static JSC_DECLARE_HOST_FUNCTION(arrayProtoFuncToLocaleString); 44 static JSC_DECLARE_HOST_FUNCTION(arrayProtoFuncJoin); 45 static JSC_DECLARE_HOST_FUNCTION(arrayProtoFuncKeys); 46 static JSC_DECLARE_HOST_FUNCTION(arrayProtoFuncEntries); 47 static JSC_DECLARE_HOST_FUNCTION(arrayProtoFuncPop); 48 static JSC_DECLARE_HOST_FUNCTION(arrayProtoFuncPush); 49 static JSC_DECLARE_HOST_FUNCTION(arrayProtoFuncReverse); 50 static JSC_DECLARE_HOST_FUNCTION(arrayProtoFuncShift); 51 static JSC_DECLARE_HOST_FUNCTION(arrayProtoFuncSlice); 52 static JSC_DECLARE_HOST_FUNCTION(arrayProtoFuncSplice); 53 static JSC_DECLARE_HOST_FUNCTION(arrayProtoFuncUnShift); 54 static JSC_DECLARE_HOST_FUNCTION(arrayProtoFuncIndexOf); 55 static JSC_DECLARE_HOST_FUNCTION(arrayProtoFuncLastIndexOf); 56 57 // ------------------------------ ArrayPrototype ---------------------------- 58 59 const ClassInfo ArrayPrototype::s_info = {"Array", &JSArray::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(ArrayPrototype)}; 60 61 ArrayPrototype* ArrayPrototype::create(VM& vm, JSGlobalObject* globalObject, Structure* structure) 62 { 63 ArrayPrototype* prototype = new (NotNull, allocateCell<ArrayPrototype>(vm.heap)) ArrayPrototype(vm, structure); 64 prototype->finishCreation(vm, globalObject); 65 return prototype; 66 } 67 68 // ECMA 15.4.4 69 ArrayPrototype::ArrayPrototype(VM& vm, Structure* structure) 70 : JSArray(vm, structure, nullptr) 71 { 72 } 73 74 void ArrayPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) 75 { 76 Base::finishCreation(vm); 77 ASSERT(inherits(vm, info())); 78 79 putDirectWithoutTransition(vm, vm.propertyNames->toString, globalObject->arrayProtoToStringFunction(), static_cast<unsigned>(PropertyAttribute::DontEnum)); 80 putDirectWithoutTransition(vm, vm.propertyNames->builtinNames().valuesPublicName(), globalObject->arrayProtoValuesFunction(), static_cast<unsigned>(PropertyAttribute::DontEnum)); 81 putDirectWithoutTransition(vm, vm.propertyNames->iteratorSymbol, globalObject->arrayProtoValuesFunction(), static_cast<unsigned>(PropertyAttribute::DontEnum)); 82 83 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->toLocaleString, arrayProtoFuncToLocaleString, static_cast<unsigned>(PropertyAttribute::DontEnum), 0); 84 JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().concatPublicName(), arrayPrototypeConcatCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum)); 85 JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().fillPublicName(), arrayPrototypeFillCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum)); 86 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->join, arrayProtoFuncJoin, static_cast<unsigned>(PropertyAttribute::DontEnum), 1); 87 JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION("pop", arrayProtoFuncPop, static_cast<unsigned>(PropertyAttribute::DontEnum), 0, ArrayPopIntrinsic); 88 JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().pushPublicName(), arrayProtoFuncPush, static_cast<unsigned>(PropertyAttribute::DontEnum), 1, ArrayPushIntrinsic); 89 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("reverse", arrayProtoFuncReverse, static_cast<unsigned>(PropertyAttribute::DontEnum), 0); 90 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().shiftPublicName(), arrayProtoFuncShift, static_cast<unsigned>(PropertyAttribute::DontEnum), 0); 91 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().shiftPrivateName(), arrayProtoFuncShift, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly, 0); 92 JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->slice, arrayProtoFuncSlice, static_cast<unsigned>(PropertyAttribute::DontEnum), 2, ArraySliceIntrinsic); 93 JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().sortPublicName(), arrayPrototypeSortCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum)); 94 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("splice", arrayProtoFuncSplice, static_cast<unsigned>(PropertyAttribute::DontEnum), 2); 95 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("unshift", arrayProtoFuncUnShift, static_cast<unsigned>(PropertyAttribute::DontEnum), 1); 96 JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().everyPublicName(), arrayPrototypeEveryCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum)); 97 JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().forEachPublicName(), arrayPrototypeForEachCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum)); 98 JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().somePublicName(), arrayPrototypeSomeCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum)); 99 JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION("indexOf", arrayProtoFuncIndexOf, static_cast<unsigned>(PropertyAttribute::DontEnum), 1, ArrayIndexOfIntrinsic); 100 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("lastIndexOf", arrayProtoFuncLastIndexOf, static_cast<unsigned>(PropertyAttribute::DontEnum), 1); 101 JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().filterPublicName(), arrayPrototypeFilterCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum)); 102 JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().flatPublicName(), arrayPrototypeFlatCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum)); 103 JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().flatMapPublicName(), arrayPrototypeFlatMapCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum)); 104 JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().reducePublicName(), arrayPrototypeReduceCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum)); 105 JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().reduceRightPublicName(), arrayPrototypeReduceRightCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum)); 106 JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().mapPublicName(), arrayPrototypeMapCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum)); 107 JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().keysPublicName(), arrayProtoFuncKeys, static_cast<unsigned>(PropertyAttribute::DontEnum), 0, ArrayKeysIntrinsic); 108 JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().entriesPublicName(), arrayProtoFuncEntries, static_cast<unsigned>(PropertyAttribute::DontEnum), 0, ArrayEntriesIntrinsic); 109 JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().findPublicName(), arrayPrototypeFindCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum)); 110 JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().findIndexPublicName(), arrayPrototypeFindIndexCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum)); 111 JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().includesPublicName(), arrayPrototypeIncludesCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum)); 112 JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().copyWithinPublicName(), arrayPrototypeCopyWithinCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum)); 113 114 if (Options::useAtMethod()) 115 JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().atPublicName(), arrayPrototypeAtCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum)); 116 117 putDirectWithoutTransition(vm, vm.propertyNames->builtinNames().entriesPrivateName(), getDirect(vm, vm.propertyNames->builtinNames().entriesPublicName()), static_cast<unsigned>(PropertyAttribute::ReadOnly)); 118 putDirectWithoutTransition(vm, vm.propertyNames->builtinNames().forEachPrivateName(), getDirect(vm, vm.propertyNames->builtinNames().forEachPublicName()), static_cast<unsigned>(PropertyAttribute::ReadOnly)); 119 putDirectWithoutTransition(vm, vm.propertyNames->builtinNames().keysPrivateName(), getDirect(vm, vm.propertyNames->builtinNames().keysPublicName()), static_cast<unsigned>(PropertyAttribute::ReadOnly)); 120 putDirectWithoutTransition(vm, vm.propertyNames->builtinNames().valuesPrivateName(), globalObject->arrayProtoValuesFunction(), static_cast<unsigned>(PropertyAttribute::ReadOnly)); 121 122 JSObject* unscopables = constructEmptyObject(vm, globalObject->nullPrototypeObjectStructure()); 123 unscopables->convertToDictionary(vm); 124 const Identifier* const unscopableNames[] = { 125 &vm.propertyNames->builtinNames().copyWithinPublicName(), 126 &vm.propertyNames->builtinNames().entriesPublicName(), 127 &vm.propertyNames->builtinNames().fillPublicName(), 128 &vm.propertyNames->builtinNames().findPublicName(), 129 &vm.propertyNames->builtinNames().findIndexPublicName(), 130 &vm.propertyNames->builtinNames().flatPublicName(), 131 &vm.propertyNames->builtinNames().flatMapPublicName(), 132 &vm.propertyNames->builtinNames().includesPublicName(), 133 &vm.propertyNames->builtinNames().keysPublicName(), 134 &vm.propertyNames->builtinNames().valuesPublicName() 135 }; 136 if (Options::useAtMethod()) 137 unscopables->putDirect(vm, vm.propertyNames->builtinNames().atPublicName(), jsBoolean(true)); 138 for (const auto* unscopableName : unscopableNames) 139 unscopables->putDirect(vm, *unscopableName, jsBoolean(true)); 140 putDirectWithoutTransition(vm, vm.propertyNames->unscopablesSymbol, unscopables, PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly); 141 } 142 143 // ------------------------------ Array Functions ---------------------------- 144 145 static ALWAYS_INLINE JSValue getProperty(JSGlobalObject* globalObject, JSObject* object, uint64_t index) 146 { 147 VM& vm = globalObject->vm(); 148 auto scope = DECLARE_THROW_SCOPE(vm); 149 150 if (JSValue result = object->tryGetIndexQuickly(index)) 151 return result; 152 // We want to perform get and has in the same operation. 153 // We can only do so when this behavior is not observable. The 154 // only time it is observable is when we encounter an opaque objects (ProxyObject and JSModuleNamespaceObject) 155 // somewhere in the prototype chain. 156 PropertySlot slot(object, PropertySlot::InternalMethodType::HasProperty); 157 bool hasProperty = object->getPropertySlot(globalObject, index, slot); 158 EXCEPTION_ASSERT(!scope.exception() || !hasProperty); 159 if (!hasProperty) 160 return { }; 161 if (UNLIKELY(slot.isTaintedByOpaqueObject())) 162 RELEASE_AND_RETURN(scope, object->get(globalObject, index)); 163 164 RELEASE_AND_RETURN(scope, slot.getValue(globalObject, index)); 165 } 166 167 static ALWAYS_INLINE void setLength(JSGlobalObject* globalObject, VM& vm, JSObject* obj, uint64_t value) 168 { 169 auto scope = DECLARE_THROW_SCOPE(vm); 170 static constexpr bool throwException = true; 171 if (LIKELY(isJSArray(obj))) { 172 if (UNLIKELY(value > UINT32_MAX)) { 173 throwRangeError(globalObject, scope, "Invalid array length"_s); 174 return; 175 } 176 scope.release(); 177 jsCast<JSArray*>(obj)->setLength(globalObject, static_cast<uint32_t>(value), throwException); 178 return; 179 } 180 scope.release(); 181 PutPropertySlot slot(obj, throwException); 182 obj->methodTable(vm)->put(obj, globalObject, vm.propertyNames->length, jsNumber(value), slot); 183 } 184 185 namespace ArrayPrototypeInternal { 186 static bool verbose = false; 187 } 188 189 static ALWAYS_INLINE bool speciesWatchpointIsValid(VM& vm, JSObject* thisObject) 190 { 191 JSGlobalObject* globalObject = thisObject->globalObject(vm); 192 ArrayPrototype* arrayPrototype = globalObject->arrayPrototype(); 193 194 if (globalObject->arraySpeciesWatchpointSet().stateOnJSThread() == ClearWatchpoint) { 195 dataLogLnIf(ArrayPrototypeInternal::verbose, "Initializing Array species watchpoints for Array.prototype: ", pointerDump(arrayPrototype), " with structure: ", pointerDump(arrayPrototype->structure(vm)), "\nand Array: ", pointerDump(globalObject->arrayConstructor()), " with structure: ", pointerDump(globalObject->arrayConstructor()->structure(vm))); 196 globalObject->tryInstallArraySpeciesWatchpoint(); 197 ASSERT(globalObject->arraySpeciesWatchpointSet().stateOnJSThread() != ClearWatchpoint); 198 } 199 200 return !thisObject->hasCustomProperties(vm) 201 && arrayPrototype == thisObject->getPrototypeDirect(vm) 202 && globalObject->arraySpeciesWatchpointSet().stateOnJSThread() == IsWatched; 203 } 204 205 enum class SpeciesConstructResult { 206 FastPath, 207 Exception, 208 CreatedObject 209 }; 210 211 static ALWAYS_INLINE std::pair<SpeciesConstructResult, JSObject*> speciesConstructArray(JSGlobalObject* globalObject, JSObject* thisObject, uint64_t length) 212 { 213 VM& vm = globalObject->vm(); 214 auto scope = DECLARE_THROW_SCOPE(vm); 215 216 auto exceptionResult = [] () { 217 return std::make_pair(SpeciesConstructResult::Exception, nullptr); 218 }; 219 220 // ECMA 9.4.2.3: https://tc39.github.io/ecma262/#sec-arrayspeciescreate 221 JSValue constructor = jsUndefined(); 222 bool thisIsArray = isArray(globalObject, thisObject); 223 RETURN_IF_EXCEPTION(scope, exceptionResult()); 224 if (LIKELY(thisIsArray)) { 225 // Fast path in the normal case where the user has not set an own constructor and the Array.prototype.constructor is normal. 226 // We need prototype check for subclasses of Array, which are Array objects but have a different prototype by default. 227 bool isValid = speciesWatchpointIsValid(vm, thisObject); 228 scope.assertNoException(); 229 if (LIKELY(isValid)) 230 return std::make_pair(SpeciesConstructResult::FastPath, nullptr); 231 232 constructor = thisObject->get(globalObject, vm.propertyNames->constructor); 233 RETURN_IF_EXCEPTION(scope, exceptionResult()); 234 if (constructor.isConstructor(vm)) { 235 JSObject* constructorObject = jsCast<JSObject*>(constructor); 236 bool isArrayConstructorFromAnotherRealm = globalObject != constructorObject->globalObject(vm) 237 && constructorObject->inherits<ArrayConstructor>(vm); 238 if (isArrayConstructorFromAnotherRealm) 239 return std::make_pair(SpeciesConstructResult::FastPath, nullptr); 240 } 241 if (constructor.isObject()) { 242 constructor = constructor.get(globalObject, vm.propertyNames->speciesSymbol); 243 RETURN_IF_EXCEPTION(scope, exceptionResult()); 244 if (constructor.isNull()) 245 return std::make_pair(SpeciesConstructResult::FastPath, nullptr); 246 } 247 } else { 248 // If isArray is false, return ? ArrayCreate(length). 249 return std::make_pair(SpeciesConstructResult::FastPath, nullptr); 250 } 251 252 if (constructor.isUndefined()) 253 return std::make_pair(SpeciesConstructResult::FastPath, nullptr); 254 255 MarkedArgumentBuffer args; 256 args.append(jsNumber(length)); 257 ASSERT(!args.hasOverflowed()); 258 JSObject* newObject = construct(globalObject, constructor, args, "Species construction did not get a valid constructor"); 259 RETURN_IF_EXCEPTION(scope, exceptionResult()); 260 return std::make_pair(SpeciesConstructResult::CreatedObject, newObject); 261 } 262 263 JSC_DEFINE_HOST_FUNCTION(arrayProtoFuncSpeciesCreate, (JSGlobalObject* globalObject, CallFrame* callFrame)) 264 { 265 VM& vm = globalObject->vm(); 266 auto scope = DECLARE_THROW_SCOPE(vm); 267 JSObject* object = asObject(callFrame->uncheckedArgument(0)); 268 uint64_t length = static_cast<uint64_t>(callFrame->uncheckedArgument(1).asNumber()); 269 270 std::pair<SpeciesConstructResult, JSObject*> speciesResult = speciesConstructArray(globalObject, object, length); 271 EXCEPTION_ASSERT(!!scope.exception() == (speciesResult.first == SpeciesConstructResult::Exception)); 272 if (UNLIKELY(speciesResult.first == SpeciesConstructResult::Exception)) 273 return { }; 274 if (speciesResult.first == SpeciesConstructResult::CreatedObject) 275 return JSValue::encode(speciesResult.second); 276 277 if (length > std::numeric_limits<unsigned>::max()) { 278 throwRangeError(globalObject, scope, "Array size is not a small enough positive integer."_s); 279 return { }; 280 } 281 282 RELEASE_AND_RETURN(scope, JSValue::encode(constructEmptyArray(globalObject, nullptr, static_cast<unsigned>(length)))); 283 } 284 285 static inline uint64_t argumentClampedIndexFromStartOrEnd(JSGlobalObject* globalObject, JSValue value, uint64_t length, uint64_t undefinedValue = 0) 286 { 287 if (value.isUndefined()) 288 return undefinedValue; 289 290 double indexDouble = value.toInteger(globalObject); 291 if (indexDouble < 0) { 292 indexDouble += length; 293 return indexDouble < 0 ? 0 : static_cast<uint64_t>(indexDouble); 294 } 295 return indexDouble > length ? length : static_cast<uint64_t>(indexDouble); 296 } 297 298 // The shift/unshift function implement the shift/unshift behaviour required 299 // by the corresponding array prototype methods, and by splice. In both cases, 300 // the methods are operating an an array or array like object. 301 // 302 // header currentCount (remainder) 303 // [------][------------][-----------] 304 // header resultCount (remainder) 305 // [------][-----------][-----------] 306 // 307 // The set of properties in the range 'header' must be unchanged. The set of 308 // properties in the range 'remainder' (where remainder = length - header - 309 // currentCount) will be shifted to the left or right as appropriate; in the 310 // case of shift this must be removing values, in the case of unshift this 311 // must be introducing new values. 312 313 template<JSArray::ShiftCountMode shiftCountMode> 314 void shift(JSGlobalObject* globalObject, JSObject* thisObj, uint64_t header, uint64_t currentCount, uint64_t resultCount, uint64_t length) 315 { 316 VM& vm = globalObject->vm(); 317 auto scope = DECLARE_THROW_SCOPE(vm); 318 319 RELEASE_ASSERT(currentCount > resultCount); 320 uint64_t count = currentCount - resultCount; 321 322 RELEASE_ASSERT(header <= length); 323 RELEASE_ASSERT(currentCount <= (length - header)); 324 325 if (isJSArray(thisObj)) { 326 JSArray* array = asArray(thisObj); 327 uint32_t header32 = static_cast<uint32_t>(header); 328 ASSERT(header32 == header); 329 if (array->length() == length && array->shiftCount<shiftCountMode>(globalObject, header32, static_cast<uint32_t>(count))) 330 return; 331 header = header32; 332 } 333 334 for (uint64_t k = header; k < length - currentCount; ++k) { 335 uint64_t from = k + currentCount; 336 uint64_t to = k + resultCount; 337 JSValue value = getProperty(globalObject, thisObj, from); 338 RETURN_IF_EXCEPTION(scope, void()); 339 if (value) { 340 thisObj->putByIndexInline(globalObject, to, value, true); 341 RETURN_IF_EXCEPTION(scope, void()); 342 } else { 343 bool success = thisObj->deleteProperty(globalObject, to); 344 RETURN_IF_EXCEPTION(scope, void()); 345 if (!success) { 346 throwTypeError(globalObject, scope, UnableToDeletePropertyError); 347 return; 348 } 349 } 350 } 351 for (uint64_t k = length; k > length - count; --k) { 352 bool success = thisObj->deleteProperty(globalObject, k - 1); 353 RETURN_IF_EXCEPTION(scope, void()); 354 if (!success) { 355 throwTypeError(globalObject, scope, UnableToDeletePropertyError); 356 return; 357 } 358 } 359 } 360 361 template<JSArray::ShiftCountMode shiftCountMode> 362 void unshift(JSGlobalObject* globalObject, JSObject* thisObj, uint64_t header, uint64_t currentCount, uint64_t resultCount, uint64_t length) 363 { 364 ASSERT(header <= maxSafeInteger()); 365 ASSERT(currentCount <= maxSafeInteger()); 366 ASSERT(resultCount <= maxSafeInteger()); 367 ASSERT(length <= maxSafeInteger()); 368 369 VM& vm = globalObject->vm(); 370 auto scope = DECLARE_THROW_SCOPE(vm); 371 372 RELEASE_ASSERT(resultCount > currentCount); 373 uint64_t count = resultCount - currentCount; 374 375 RELEASE_ASSERT(header <= length); 376 RELEASE_ASSERT(currentCount <= (length - header)); 377 378 if (isJSArray(thisObj)) { 379 // Spec says if we would produce an array of this size, we must throw a range error. 380 if (count + length > std::numeric_limits<uint32_t>::max()) { 381 throwRangeError(globalObject, scope, LengthExceededTheMaximumArrayLengthError); 382 return; 383 } 384 385 JSArray* array = asArray(thisObj); 386 if (array->length() == length) { 387 bool handled = array->unshiftCount<shiftCountMode>(globalObject, static_cast<uint32_t>(header), static_cast<uint32_t>(count)); 388 EXCEPTION_ASSERT(!scope.exception() || handled); 389 if (handled) 390 return; 391 } 392 } 393 394 for (uint64_t k = length - currentCount; k > header; --k) { 395 uint64_t from = k + currentCount - 1; 396 uint64_t to = k + resultCount - 1; 397 JSValue value = getProperty(globalObject, thisObj, from); 398 RETURN_IF_EXCEPTION(scope, void()); 399 if (value) { 400 thisObj->putByIndexInline(globalObject, to, value, true); 401 RETURN_IF_EXCEPTION(scope, void()); 402 } else { 403 bool success = thisObj->deleteProperty(globalObject, to); 404 RETURN_IF_EXCEPTION(scope, void()); 405 if (UNLIKELY(!success)) { 406 throwTypeError(globalObject, scope, UnableToDeletePropertyError); 407 return; 408 } 409 } 410 } 411 } 412 413 inline bool canUseFastJoin(const JSObject* thisObject) 414 { 415 switch (thisObject->indexingType()) { 416 case ALL_CONTIGUOUS_INDEXING_TYPES: 417 case ALL_INT32_INDEXING_TYPES: 418 case ALL_DOUBLE_INDEXING_TYPES: 419 case ALL_UNDECIDED_INDEXING_TYPES: 420 return true; 421 default: 422 break; 423 } 424 return false; 425 } 426 427 inline bool holesMustForwardToPrototype(VM& vm, JSObject* object) 428 { 429 return object->structure(vm)->holesMustForwardToPrototype(vm, object); 430 } 431 432 inline bool isHole(double value) 433 { 434 return std::isnan(value); 435 } 436 437 inline bool isHole(const WriteBarrier<Unknown>& value) 438 { 439 return !value; 440 } 441 442 template<typename T> 443 inline bool containsHole(T* data, unsigned length) 444 { 445 for (unsigned i = 0; i < length; ++i) { 446 if (isHole(data[i])) 447 return true; 448 } 449 return false; 450 } 451 452 inline JSValue fastJoin(JSGlobalObject* globalObject, JSObject* thisObject, StringView separator, unsigned length, bool& sawHoles, bool& genericCase) 453 { 454 VM& vm = globalObject->vm(); 455 auto scope = DECLARE_THROW_SCOPE(vm); 456 457 switch (thisObject->indexingType()) { 458 case ALL_INT32_INDEXING_TYPES: { 459 auto& butterfly = *thisObject->butterfly(); 460 if (UNLIKELY(length > butterfly.publicLength())) 461 break; 462 JSStringJoiner joiner(globalObject, separator, length); 463 RETURN_IF_EXCEPTION(scope, { }); 464 auto data = butterfly.contiguous().data(); 465 bool holesKnownToBeOK = false; 466 for (unsigned i = 0; i < length; ++i) { 467 JSValue value = data[i].get(); 468 if (LIKELY(value)) 469 joiner.appendNumber(vm, value.asInt32()); 470 else { 471 sawHoles = true; 472 if (!holesKnownToBeOK) { 473 if (holesMustForwardToPrototype(vm, thisObject)) 474 goto generalCase; 475 holesKnownToBeOK = true; 476 } 477 joiner.appendEmptyString(); 478 } 479 } 480 RELEASE_AND_RETURN(scope, joiner.join(globalObject)); 481 } 482 case ALL_CONTIGUOUS_INDEXING_TYPES: { 483 auto& butterfly = *thisObject->butterfly(); 484 if (UNLIKELY(length > butterfly.publicLength())) 485 break; 486 JSStringJoiner joiner(globalObject, separator, length); 487 RETURN_IF_EXCEPTION(scope, { }); 488 auto data = butterfly.contiguous().data(); 489 bool holesKnownToBeOK = false; 490 for (unsigned i = 0; i < length; ++i) { 491 if (JSValue value = data[i].get()) { 492 if (!joiner.appendWithoutSideEffects(globalObject, value)) 493 goto generalCase; 494 RETURN_IF_EXCEPTION(scope, { }); 495 } else { 496 sawHoles = true; 497 if (!holesKnownToBeOK) { 498 if (holesMustForwardToPrototype(vm, thisObject)) 499 goto generalCase; 500 holesKnownToBeOK = true; 501 } 502 joiner.appendEmptyString(); 503 } 504 } 505 RELEASE_AND_RETURN(scope, joiner.join(globalObject)); 506 } 507 case ALL_DOUBLE_INDEXING_TYPES: { 508 auto& butterfly = *thisObject->butterfly(); 509 if (UNLIKELY(length > butterfly.publicLength())) 510 break; 511 JSStringJoiner joiner(globalObject, separator, length); 512 RETURN_IF_EXCEPTION(scope, { }); 513 auto data = butterfly.contiguousDouble().data(); 514 bool holesKnownToBeOK = false; 515 for (unsigned i = 0; i < length; ++i) { 516 double value = data[i]; 517 if (LIKELY(!isHole(value))) 518 joiner.appendNumber(vm, value); 519 else { 520 sawHoles = true; 521 if (!holesKnownToBeOK) { 522 if (holesMustForwardToPrototype(vm, thisObject)) 523 goto generalCase; 524 holesKnownToBeOK = true; 525 } 526 joiner.appendEmptyString(); 527 } 528 } 529 RELEASE_AND_RETURN(scope, joiner.join(globalObject)); 530 } 531 case ALL_UNDECIDED_INDEXING_TYPES: { 532 if (length && holesMustForwardToPrototype(vm, thisObject)) 533 goto generalCase; 534 switch (separator.length()) { 535 case 0: 536 RELEASE_AND_RETURN(scope, jsEmptyString(vm)); 537 case 1: { 538 if (length <= 1) 539 RELEASE_AND_RETURN(scope, jsEmptyString(vm)); 540 if (separator.is8Bit()) 541 RELEASE_AND_RETURN(scope, repeatCharacter(globalObject, separator.characters8()[0], length - 1)); 542 RELEASE_AND_RETURN(scope, repeatCharacter(globalObject, separator.characters16()[0], length - 1)); 543 default: 544 JSString* result = jsEmptyString(vm); 545 if (length <= 1) 546 return result; 547 548 JSString* operand = jsString(vm, separator.toString()); 549 RETURN_IF_EXCEPTION(scope, { }); 550 unsigned count = length - 1; 551 for (;;) { 552 if (count & 1) { 553 result = jsString(globalObject, result, operand); 554 RETURN_IF_EXCEPTION(scope, { }); 555 } 556 count >>= 1; 557 if (!count) 558 return result; 559 operand = jsString(globalObject, operand, operand); 560 RETURN_IF_EXCEPTION(scope, { }); 561 } 562 } 563 } 564 } 565 } 566 567 generalCase: 568 genericCase = true; 569 JSStringJoiner joiner(globalObject, separator, length); 570 RETURN_IF_EXCEPTION(scope, { }); 571 for (unsigned i = 0; i < length; ++i) { 572 JSValue element = thisObject->getIndex(globalObject, i); 573 RETURN_IF_EXCEPTION(scope, { }); 574 joiner.append(globalObject, element); 575 RETURN_IF_EXCEPTION(scope, { }); 576 } 577 RELEASE_AND_RETURN(scope, joiner.join(globalObject)); 578 } 579 580 ALWAYS_INLINE JSValue fastJoin(JSGlobalObject* globalObject, JSObject* thisObject, StringView separator, unsigned length) 581 { 582 bool sawHoles = false; 583 bool genericCase = false; 584 return fastJoin(globalObject, thisObject, separator, length, sawHoles, genericCase); 585 } 586 587 inline bool canUseDefaultArrayJoinForToString(VM& vm, JSObject* thisObject) 588 { 589 JSGlobalObject* globalObject = thisObject->globalObject(); 590 591 if (globalObject->arrayJoinWatchpointSet().stateOnJSThread() != IsWatched) 592 return false; 593 594 Structure* structure = thisObject->structure(vm); 595 596 // This is the fast case. Many arrays will be an original array. 597 // We are doing very simple check here. If we do more complicated checks like looking into getDirect "join" of thisObject, 598 // it would be possible that just looking into "join" function will show the same performance. 599 return globalObject->isOriginalArrayStructure(structure); 600 } 601 602 JSC_DEFINE_HOST_FUNCTION(arrayProtoFuncToString, (JSGlobalObject* globalObject, CallFrame* callFrame)) 603 { 604 VM& vm = globalObject->vm(); 605 auto scope = DECLARE_THROW_SCOPE(vm); 606 JSValue thisValue = callFrame->thisValue().toThis(globalObject, ECMAMode::strict()); 607 608 // 1. Let array be the result of calling ToObject on the this value. 609 JSObject* thisObject = thisValue.toObject(globalObject); 610 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 611 612 Integrity::auditStructureID(vm, thisObject->structureID()); 613 if (!canUseDefaultArrayJoinForToString(vm, thisObject)) { 614 // 2. Let func be the result of calling the [[Get]] internal method of array with argument "join". 615 JSValue function = JSValue(thisObject).get(globalObject, vm.propertyNames->join); 616 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 617 618 // 3. If IsCallable(func) is false, then let func be the standard built-in method Object.prototype.toString (15.2.4.2). 619 bool customJoinCase = false; 620 if (!function.isCell()) 621 customJoinCase = true; 622 auto callData = getCallData(vm, function); 623 if (callData.type == CallData::Type::None) 624 customJoinCase = true; 625 626 if (UNLIKELY(customJoinCase)) 627 RELEASE_AND_RETURN(scope, JSValue::encode(jsMakeNontrivialString(globalObject, "[object ", thisObject->methodTable(vm)->className(thisObject, vm), "]"))); 628 629 // 4. Return the result of calling the [[Call]] internal method of func providing array as the this value and an empty arguments list. 630 if (!isJSArray(thisObject) || callData.type != CallData::Type::Native || callData.native.function != arrayProtoFuncJoin) 631 RELEASE_AND_RETURN(scope, JSValue::encode(call(globalObject, function, callData, thisObject, *vm.emptyList))); 632 } 633 634 ASSERT(isJSArray(thisValue)); 635 JSArray* thisArray = asArray(thisValue); 636 637 unsigned length = thisArray->length(); 638 639 StringRecursionChecker checker(globalObject, thisArray); 640 EXCEPTION_ASSERT(!scope.exception() || checker.earlyReturnValue()); 641 if (JSValue earlyReturnValue = checker.earlyReturnValue()) 642 return JSValue::encode(earlyReturnValue); 643 644 if (LIKELY(canUseFastJoin(thisArray))) { 645 const LChar comma = ','; 646 scope.release(); 647 648 bool isCoW = isCopyOnWrite(thisArray->indexingMode()); 649 JSImmutableButterfly* immutableButterfly = nullptr; 650 if (isCoW) { 651 immutableButterfly = JSImmutableButterfly::fromButterfly(thisArray->butterfly()); 652 auto iter = vm.heap.immutableButterflyToStringCache.find(immutableButterfly); 653 if (iter != vm.heap.immutableButterflyToStringCache.end()) 654 return JSValue::encode(iter->value); 655 } 656 657 bool sawHoles = false; 658 bool genericCase = false; 659 JSValue result = fastJoin(globalObject, thisArray, { &comma, 1 }, length, sawHoles, genericCase); 660 RETURN_IF_EXCEPTION(scope, { }); 661 662 if (!sawHoles && !genericCase && result && isJSString(result) && isCoW) { 663 ASSERT(JSImmutableButterfly::fromButterfly(thisArray->butterfly()) == immutableButterfly); 664 vm.heap.immutableButterflyToStringCache.add(immutableButterfly, jsCast<JSString*>(result)); 665 } 666 667 return JSValue::encode(result); 668 } 669 670 JSStringJoiner joiner(globalObject, ',', length); 671 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 672 673 for (unsigned i = 0; i < length; ++i) { 674 JSValue element = thisArray->tryGetIndexQuickly(i); 675 if (!element) { 676 element = thisArray->get(globalObject, i); 677 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 678 } 679 joiner.append(globalObject, element); 680 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 681 } 682 683 RELEASE_AND_RETURN(scope, JSValue::encode(joiner.join(globalObject))); 684 } 685 686 JSC_DEFINE_HOST_FUNCTION(arrayProtoFuncToLocaleString, (JSGlobalObject* globalObject, CallFrame* callFrame)) 687 { 688 VM& vm = globalObject->vm(); 689 auto scope = DECLARE_THROW_SCOPE(vm); 690 JSValue thisValue = callFrame->thisValue().toThis(globalObject, ECMAMode::strict()); 691 692 JSObject* thisObject = thisValue.toObject(globalObject); 693 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 694 uint64_t length = static_cast<uint64_t>(toLength(globalObject, thisObject)); 695 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 696 if (length > std::numeric_limits<unsigned>::max()) { 697 throwOutOfMemoryError(globalObject, scope); 698 return encodedJSValue(); 699 } 700 701 StringRecursionChecker checker(globalObject, thisObject); 702 EXCEPTION_ASSERT(!scope.exception() || checker.earlyReturnValue()); 703 if (JSValue earlyReturnValue = checker.earlyReturnValue()) 704 return JSValue::encode(earlyReturnValue); 705 706 JSStringJoiner stringJoiner(globalObject, ',', static_cast<uint32_t>(length)); 707 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 708 709 ArgList arguments(callFrame); 710 for (unsigned i = 0; i < length; ++i) { 711 JSValue element = thisObject->getIndex(globalObject, i); 712 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 713 if (element.isUndefinedOrNull()) 714 element = jsEmptyString(vm); 715 else { 716 JSValue conversionFunction = element.get(globalObject, vm.propertyNames->toLocaleString); 717 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 718 auto callData = getCallData(vm, conversionFunction); 719 if (callData.type != CallData::Type::None) { 720 element = call(globalObject, conversionFunction, callData, element, arguments); 721 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 722 } 723 } 724 stringJoiner.append(globalObject, element); 725 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 726 } 727 728 RELEASE_AND_RETURN(scope, JSValue::encode(stringJoiner.join(globalObject))); 729 } 730 731 static JSValue slowJoin(JSGlobalObject* globalObject, JSObject* thisObject, JSString* separator, uint64_t length) 732 { 733 VM& vm = globalObject->vm(); 734 auto scope = DECLARE_THROW_SCOPE(vm); 735 736 // 5. If len is zero, return the empty String. 737 if (!length) 738 return jsEmptyString(vm); 739 740 // 6. Let element0 be Get(O, "0"). 741 JSValue element0 = thisObject->getIndex(globalObject, 0); 742 RETURN_IF_EXCEPTION(scope, { }); 743 744 // 7. If element0 is undefined or null, let R be the empty String; otherwise, let R be ? ToString(element0). 745 JSString* r = nullptr; 746 if (element0.isUndefinedOrNull()) 747 r = jsEmptyString(vm); 748 else 749 r = element0.toString(globalObject); 750 RETURN_IF_EXCEPTION(scope, { }); 751 752 // 8. Let k be 1. 753 // 9. Repeat, while k < len 754 // 9.e Increase k by 1.. 755 for (uint64_t k = 1; k < length; ++k) { 756 // b. Let element be ? Get(O, ! ToString(k)). 757 JSValue element = thisObject->get(globalObject, Identifier::fromString(vm, AtomString::number(k))); 758 RETURN_IF_EXCEPTION(scope, { }); 759 760 // c. If element is undefined or null, let next be the empty String; otherwise, let next be ? ToString(element). 761 JSString* next = nullptr; 762 if (element.isUndefinedOrNull()) { 763 if (!separator->length()) 764 continue; 765 next = jsEmptyString(vm); 766 } else 767 next = element.toString(globalObject); 768 RETURN_IF_EXCEPTION(scope, { }); 769 770 // a. Let S be the String value produced by concatenating R and sep. 771 // d. Let R be a String value produced by concatenating S and next. 772 r = jsString(globalObject, r, separator, next); 773 RETURN_IF_EXCEPTION(scope, { }); 774 } 775 // 10. Return R. 776 return r; 777 } 778 779 JSC_DEFINE_HOST_FUNCTION(arrayProtoFuncJoin, (JSGlobalObject* globalObject, CallFrame* callFrame)) 780 { 781 VM& vm = globalObject->vm(); 782 auto scope = DECLARE_THROW_SCOPE(vm); 783 784 // 1. Let O be ? ToObject(this value). 785 JSObject* thisObject = callFrame->thisValue().toThis(globalObject, ECMAMode::strict()).toObject(globalObject); 786 EXCEPTION_ASSERT(!!scope.exception() == !thisObject); 787 if (UNLIKELY(!thisObject)) 788 return encodedJSValue(); 789 790 StringRecursionChecker checker(globalObject, thisObject); 791 EXCEPTION_ASSERT(!scope.exception() || checker.earlyReturnValue()); 792 if (JSValue earlyReturnValue = checker.earlyReturnValue()) 793 return JSValue::encode(earlyReturnValue); 794 795 // 2. Let len be ? ToLength(? Get(O, "length")). 796 double length = toLength(globalObject, thisObject); 797 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 798 799 // 3. If separator is undefined, let separator be the single-element String ",". 800 JSValue separatorValue = callFrame->argument(0); 801 if (separatorValue.isUndefined()) { 802 const LChar comma = ','; 803 804 if (UNLIKELY(length > std::numeric_limits<unsigned>::max() || !canUseFastJoin(thisObject))) { 805 uint64_t length64 = static_cast<uint64_t>(length); 806 ASSERT(static_cast<double>(length64) == length); 807 JSString* jsSeparator = jsSingleCharacterString(vm, comma); 808 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 809 810 RELEASE_AND_RETURN(scope, JSValue::encode(slowJoin(globalObject, thisObject, jsSeparator, length64))); 811 } 812 813 unsigned unsignedLength = static_cast<unsigned>(length); 814 ASSERT(static_cast<double>(unsignedLength) == length); 815 816 RELEASE_AND_RETURN(scope, JSValue::encode(fastJoin(globalObject, thisObject, { &comma, 1 }, unsignedLength))); 817 } 818 819 // 4. Let sep be ? ToString(separator). 820 JSString* jsSeparator = separatorValue.toString(globalObject); 821 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 822 823 if (UNLIKELY(length > std::numeric_limits<unsigned>::max() || !canUseFastJoin(thisObject))) { 824 uint64_t length64 = static_cast<uint64_t>(length); 825 ASSERT(static_cast<double>(length64) == length); 826 827 RELEASE_AND_RETURN(scope, JSValue::encode(slowJoin(globalObject, thisObject, jsSeparator, length64))); 828 } 829 830 auto viewWithString = jsSeparator->viewWithUnderlyingString(globalObject); 831 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 832 833 RELEASE_AND_RETURN(scope, JSValue::encode(fastJoin(globalObject, thisObject, viewWithString.view, length))); 834 } 835 836 inline EncodedJSValue createArrayIteratorObject(JSGlobalObject* globalObject, CallFrame* callFrame, IterationKind kind) 837 { 838 VM& vm = globalObject->vm(); 839 auto scope = DECLARE_THROW_SCOPE(vm); 840 841 JSObject* thisObject = callFrame->thisValue().toThis(globalObject, ECMAMode::strict()).toObject(globalObject); 842 EXCEPTION_ASSERT(!!scope.exception() == !thisObject); 843 UNUSED_PARAM(scope); 844 if (UNLIKELY(!thisObject)) 845 return encodedJSValue(); 846 847 return JSValue::encode(JSArrayIterator::create(vm, globalObject->arrayIteratorStructure(), thisObject, jsNumber(static_cast<unsigned>(kind)))); 848 } 849 850 JSC_DEFINE_HOST_FUNCTION(arrayProtoFuncValues, (JSGlobalObject* globalObject, CallFrame* callFrame)) 851 { 852 return createArrayIteratorObject(globalObject, callFrame, IterationKind::Values); 853 } 854 855 JSC_DEFINE_HOST_FUNCTION(arrayProtoFuncEntries, (JSGlobalObject* globalObject, CallFrame* callFrame)) 856 { 857 return createArrayIteratorObject(globalObject, callFrame, IterationKind::Entries); 858 } 859 860 JSC_DEFINE_HOST_FUNCTION(arrayProtoFuncKeys, (JSGlobalObject* globalObject, CallFrame* callFrame)) 861 { 862 return createArrayIteratorObject(globalObject, callFrame, IterationKind::Keys); 863 } 864 865 JSC_DEFINE_HOST_FUNCTION(arrayProtoFuncPop, (JSGlobalObject* globalObject, CallFrame* callFrame)) 866 { 867 VM& vm = globalObject->vm(); 868 auto scope = DECLARE_THROW_SCOPE(vm); 869 870 JSValue thisValue = callFrame->thisValue().toThis(globalObject, ECMAMode::strict()); 871 872 if (isJSArray(thisValue)) 873 RELEASE_AND_RETURN(scope, JSValue::encode(asArray(thisValue)->pop(globalObject))); 874 875 JSObject* thisObj = thisValue.toObject(globalObject); 876 EXCEPTION_ASSERT(!!scope.exception() == !thisObj); 877 if (UNLIKELY(!thisObj)) 878 return encodedJSValue(); 879 uint64_t length = static_cast<uint64_t>(toLength(globalObject, thisObj)); 880 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 881 882 if (length == 0) { 883 scope.release(); 884 setLength(globalObject, vm, thisObj, length); 885 return JSValue::encode(jsUndefined()); 886 } 887 888 static_assert(MAX_ARRAY_INDEX + 1 > MAX_ARRAY_INDEX); 889 uint64_t index = length - 1; 890 JSValue result = thisObj->get(globalObject, index); 891 RETURN_IF_EXCEPTION(scope, { }); 892 bool success = thisObj->deleteProperty(globalObject, index); 893 RETURN_IF_EXCEPTION(scope, { }); 894 if (UNLIKELY(!success)) { 895 throwTypeError(globalObject, scope, UnableToDeletePropertyError); 896 return { }; 897 } 898 899 scope.release(); 900 setLength(globalObject, vm, thisObj, index); 901 return JSValue::encode(result); 902 } 903 904 JSC_DEFINE_HOST_FUNCTION(arrayProtoFuncPush, (JSGlobalObject* globalObject, CallFrame* callFrame)) 905 { 906 VM& vm = globalObject->vm(); 907 auto scope = DECLARE_THROW_SCOPE(vm); 908 JSValue thisValue = callFrame->thisValue().toThis(globalObject, ECMAMode::strict()); 909 910 if (LIKELY(isJSArray(thisValue) && callFrame->argumentCount() == 1)) { 911 JSArray* array = asArray(thisValue); 912 scope.release(); 913 array->pushInline(globalObject, callFrame->uncheckedArgument(0)); 914 return JSValue::encode(jsNumber(array->length())); 915 } 916 917 JSObject* thisObj = thisValue.toObject(globalObject); 918 EXCEPTION_ASSERT(!!scope.exception() == !thisObj); 919 if (UNLIKELY(!thisObj)) 920 return encodedJSValue(); 921 uint64_t length = static_cast<uint64_t>(toLength(globalObject, thisObj)); 922 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 923 unsigned argCount = callFrame->argumentCount(); 924 925 if (UNLIKELY(length + argCount > static_cast<uint64_t>(maxSafeInteger()))) 926 return throwVMTypeError(globalObject, scope, "push cannot produce an array of length larger than (2 ** 53) - 1"_s); 927 928 for (unsigned n = 0; n < argCount; n++) { 929 thisObj->putByIndexInline(globalObject, length + n, callFrame->uncheckedArgument(n), true); 930 RETURN_IF_EXCEPTION(scope, { }); 931 } 932 933 uint64_t newLength = length + argCount; 934 scope.release(); 935 setLength(globalObject, vm, thisObj, newLength); 936 return JSValue::encode(jsNumber(newLength)); 937 } 938 939 JSC_DEFINE_HOST_FUNCTION(arrayProtoFuncReverse, (JSGlobalObject* globalObject, CallFrame* callFrame)) 940 { 941 VM& vm = globalObject->vm(); 942 auto scope = DECLARE_THROW_SCOPE(vm); 943 944 JSObject* thisObject = callFrame->thisValue().toThis(globalObject, ECMAMode::strict()).toObject(globalObject); 945 EXCEPTION_ASSERT(!!scope.exception() == !thisObject); 946 if (UNLIKELY(!thisObject)) 947 return encodedJSValue(); 948 949 uint64_t length = static_cast<uint64_t>(toLength(globalObject, thisObject)); 950 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 951 952 thisObject->ensureWritable(vm); 953 954 switch (thisObject->indexingType()) { 955 case ALL_CONTIGUOUS_INDEXING_TYPES: 956 case ALL_INT32_INDEXING_TYPES: { 957 auto& butterfly = *thisObject->butterfly(); 958 if (length > butterfly.publicLength()) 959 break; 960 auto data = butterfly.contiguous().data(); 961 if (containsHole(data, static_cast<uint32_t>(length)) && holesMustForwardToPrototype(vm, thisObject)) 962 break; 963 std::reverse(data, data + length); 964 if (!hasInt32(thisObject->indexingType())) 965 vm.heap.writeBarrier(thisObject); 966 return JSValue::encode(thisObject); 967 } 968 case ALL_DOUBLE_INDEXING_TYPES: { 969 auto& butterfly = *thisObject->butterfly(); 970 if (length > butterfly.publicLength()) 971 break; 972 auto data = butterfly.contiguousDouble().data(); 973 if (containsHole(data, static_cast<uint32_t>(length)) && holesMustForwardToPrototype(vm, thisObject)) 974 break; 975 std::reverse(data, data + length); 976 return JSValue::encode(thisObject); 977 } 978 case ALL_ARRAY_STORAGE_INDEXING_TYPES: { 979 auto& storage = *thisObject->butterfly()->arrayStorage(); 980 if (length > storage.vectorLength()) 981 break; 982 if (storage.hasHoles() && holesMustForwardToPrototype(vm, thisObject)) 983 break; 984 auto data = storage.vector().data(); 985 std::reverse(data, data + length); 986 vm.heap.writeBarrier(thisObject); 987 return JSValue::encode(thisObject); 988 } 989 } 990 991 uint64_t middle = length / 2; 992 for (uint64_t lower = 0; lower < middle; lower++) { 993 uint64_t upper = length - lower - 1; 994 bool lowerExists = thisObject->hasProperty(globalObject, lower); 995 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 996 JSValue lowerValue; 997 if (lowerExists) { 998 lowerValue = thisObject->get(globalObject, lower); 999 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 1000 } 1001 1002 bool upperExists = thisObject->hasProperty(globalObject, upper); 1003 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 1004 JSValue upperValue; 1005 if (upperExists) { 1006 upperValue = thisObject->get(globalObject, upper); 1007 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 1008 } 1009 1010 if (!lowerExists && !upperExists) { 1011 // Spec says to do nothing when neither lower nor upper exist. 1012 continue; 1013 } 1014 1015 if (upperExists) { 1016 thisObject->putByIndexInline(globalObject, lower, upperValue, true); 1017 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 1018 } else { 1019 bool success = thisObject->deleteProperty(globalObject, lower); 1020 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 1021 if (UNLIKELY(!success)) { 1022 throwTypeError(globalObject, scope, UnableToDeletePropertyError); 1023 return encodedJSValue(); 1024 } 1025 } 1026 1027 if (lowerExists) { 1028 thisObject->putByIndexInline(globalObject, upper, lowerValue, true); 1029 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 1030 } else { 1031 bool success = thisObject->deleteProperty(globalObject, upper); 1032 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 1033 if (UNLIKELY(!success)) { 1034 throwTypeError(globalObject, scope, UnableToDeletePropertyError); 1035 return encodedJSValue(); 1036 } 1037 } 1038 } 1039 return JSValue::encode(thisObject); 1040 } 1041 1042 JSC_DEFINE_HOST_FUNCTION(arrayProtoFuncShift, (JSGlobalObject* globalObject, CallFrame* callFrame)) 1043 { 1044 VM& vm = globalObject->vm(); 1045 auto scope = DECLARE_THROW_SCOPE(vm); 1046 JSObject* thisObj = callFrame->thisValue().toThis(globalObject, ECMAMode::strict()).toObject(globalObject); 1047 EXCEPTION_ASSERT(!!scope.exception() == !thisObj); 1048 if (UNLIKELY(!thisObj)) 1049 return encodedJSValue(); 1050 uint64_t length = static_cast<uint64_t>(toLength(globalObject, thisObj)); 1051 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 1052 1053 if (length == 0) { 1054 scope.release(); 1055 setLength(globalObject, vm, thisObj, length); 1056 return JSValue::encode(jsUndefined()); 1057 } 1058 1059 JSValue result = thisObj->getIndex(globalObject, 0); 1060 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 1061 shift<JSArray::ShiftCountForShift>(globalObject, thisObj, 0, 1, 0, length); 1062 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 1063 scope.release(); 1064 setLength(globalObject, vm, thisObj, length - 1); 1065 return JSValue::encode(result); 1066 } 1067 1068 JSC_DEFINE_HOST_FUNCTION(arrayProtoFuncSlice, (JSGlobalObject* globalObject, CallFrame* callFrame)) 1069 { 1070 // https://tc39.github.io/ecma262/#sec-array.prototype.slice 1071 VM& vm = globalObject->vm(); 1072 auto scope = DECLARE_THROW_SCOPE(vm); 1073 JSObject* thisObj = callFrame->thisValue().toThis(globalObject, ECMAMode::strict()).toObject(globalObject); 1074 EXCEPTION_ASSERT(!!scope.exception() == !thisObj); 1075 if (UNLIKELY(!thisObj)) 1076 return { }; 1077 uint64_t length = toLength(globalObject, thisObj); 1078 RETURN_IF_EXCEPTION(scope, { }); 1079 1080 uint64_t begin = argumentClampedIndexFromStartOrEnd(globalObject, callFrame->argument(0), length); 1081 RETURN_IF_EXCEPTION(scope, { }); 1082 uint64_t end = argumentClampedIndexFromStartOrEnd(globalObject, callFrame->argument(1), length, length); 1083 RETURN_IF_EXCEPTION(scope, { }); 1084 if (end < begin) 1085 end = begin; 1086 1087 std::pair<SpeciesConstructResult, JSObject*> speciesResult = speciesConstructArray(globalObject, thisObj, end - begin); 1088 // We can only get an exception if we call some user function. 1089 EXCEPTION_ASSERT(!!scope.exception() == (speciesResult.first == SpeciesConstructResult::Exception)); 1090 if (UNLIKELY(speciesResult.first == SpeciesConstructResult::Exception)) 1091 return { }; 1092 1093 bool okToDoFastPath = speciesResult.first == SpeciesConstructResult::FastPath && isJSArray(thisObj) && length == toLength(globalObject, thisObj); 1094 RETURN_IF_EXCEPTION(scope, { }); 1095 if (LIKELY(okToDoFastPath)) { 1096 if (JSArray* result = asArray(thisObj)->fastSlice(globalObject, static_cast<uint32_t>(begin), static_cast<uint32_t>(end - begin))) 1097 return JSValue::encode(result); 1098 } 1099 1100 JSObject* result; 1101 if (speciesResult.first == SpeciesConstructResult::CreatedObject) 1102 result = speciesResult.second; 1103 else { 1104 if (UNLIKELY(end - begin > std::numeric_limits<uint32_t>::max())) { 1105 throwRangeError(globalObject, scope, LengthExceededTheMaximumArrayLengthError); 1106 return encodedJSValue(); 1107 } 1108 result = constructEmptyArray(globalObject, nullptr, static_cast<uint32_t>(end - begin)); 1109 RETURN_IF_EXCEPTION(scope, { }); 1110 } 1111 1112 // Document that we need to keep the source array alive until after anything 1113 // that can GC (e.g. allocating the result array). 1114 thisObj->use(); 1115 1116 uint64_t n = 0; 1117 for (uint64_t k = begin; k < end; k++, n++) { 1118 JSValue v = getProperty(globalObject, thisObj, k); 1119 RETURN_IF_EXCEPTION(scope, { }); 1120 if (v) { 1121 result->putDirectIndex(globalObject, n, v, 0, PutDirectIndexShouldThrow); 1122 RETURN_IF_EXCEPTION(scope, { }); 1123 } 1124 } 1125 scope.release(); 1126 setLength(globalObject, vm, result, n); 1127 return JSValue::encode(result); 1128 } 1129 1130 JSC_DEFINE_HOST_FUNCTION(arrayProtoFuncSplice, (JSGlobalObject* globalObject, CallFrame* callFrame)) 1131 { 1132 // 15.4.4.12 1133 1134 VM& vm = globalObject->vm(); 1135 auto scope = DECLARE_THROW_SCOPE(vm); 1136 1137 JSObject* thisObj = callFrame->thisValue().toThis(globalObject, ECMAMode::strict()).toObject(globalObject); 1138 EXCEPTION_ASSERT(!!scope.exception() == !thisObj); 1139 if (UNLIKELY(!thisObj)) 1140 return encodedJSValue(); 1141 uint64_t length = static_cast<uint64_t>(toLength(globalObject, thisObj)); 1142 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 1143 1144 if (!callFrame->argumentCount()) { 1145 std::pair<SpeciesConstructResult, JSObject*> speciesResult = speciesConstructArray(globalObject, thisObj, 0); 1146 EXCEPTION_ASSERT(!!scope.exception() == (speciesResult.first == SpeciesConstructResult::Exception)); 1147 if (UNLIKELY(speciesResult.first == SpeciesConstructResult::Exception)) 1148 return encodedJSValue(); 1149 1150 JSObject* result; 1151 if (speciesResult.first == SpeciesConstructResult::CreatedObject) 1152 result = speciesResult.second; 1153 else { 1154 result = constructEmptyArray(globalObject, nullptr); 1155 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 1156 } 1157 1158 setLength(globalObject, vm, result, 0); 1159 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 1160 scope.release(); 1161 setLength(globalObject, vm, thisObj, length); 1162 return JSValue::encode(result); 1163 } 1164 1165 uint64_t actualStart = argumentClampedIndexFromStartOrEnd(globalObject, callFrame->argument(0), length); 1166 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 1167 1168 uint64_t actualDeleteCount = length - actualStart; 1169 if (callFrame->argumentCount() > 1) { 1170 double deleteCount = callFrame->uncheckedArgument(1).toInteger(globalObject); 1171 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 1172 if (deleteCount < 0) 1173 actualDeleteCount = 0; 1174 else if (deleteCount > length - actualStart) 1175 actualDeleteCount = length - actualStart; 1176 else 1177 actualDeleteCount = static_cast<uint64_t>(deleteCount); 1178 } 1179 unsigned itemCount = std::max<int>(callFrame->argumentCount() - 2, 0); 1180 if (UNLIKELY(length - actualDeleteCount + itemCount > static_cast<uint64_t>(maxSafeInteger()))) 1181 return throwVMTypeError(globalObject, scope, "Splice cannot produce an array of length larger than (2 ** 53) - 1"_s); 1182 1183 std::pair<SpeciesConstructResult, JSObject*> speciesResult = speciesConstructArray(globalObject, thisObj, actualDeleteCount); 1184 EXCEPTION_ASSERT(!!scope.exception() == (speciesResult.first == SpeciesConstructResult::Exception)); 1185 if (speciesResult.first == SpeciesConstructResult::Exception) 1186 return JSValue::encode(jsUndefined()); 1187 1188 JSObject* result = nullptr; 1189 bool okToDoFastPath = speciesResult.first == SpeciesConstructResult::FastPath && isJSArray(thisObj) && length == toLength(globalObject, thisObj); 1190 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 1191 if (LIKELY(okToDoFastPath)) 1192 result = asArray(thisObj)->fastSlice(globalObject, static_cast<uint32_t>(actualStart), static_cast<uint32_t>(actualDeleteCount)); 1193 1194 if (!result) { 1195 if (speciesResult.first == SpeciesConstructResult::CreatedObject) 1196 result = speciesResult.second; 1197 else { 1198 if (UNLIKELY(actualDeleteCount > std::numeric_limits<uint32_t>::max())) { 1199 throwRangeError(globalObject, scope, LengthExceededTheMaximumArrayLengthError); 1200 return encodedJSValue(); 1201 } 1202 result = JSArray::tryCreate(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithUndecided), static_cast<uint32_t>(actualDeleteCount)); 1203 if (UNLIKELY(!result)) { 1204 throwOutOfMemoryError(globalObject, scope); 1205 return encodedJSValue(); 1206 } 1207 } 1208 for (uint64_t k = 0; k < actualDeleteCount; ++k) { 1209 JSValue v = getProperty(globalObject, thisObj, k + actualStart); 1210 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 1211 if (UNLIKELY(!v)) 1212 continue; 1213 result->putDirectIndex(globalObject, k, v, 0, PutDirectIndexShouldThrow); 1214 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 1215 } 1216 setLength(globalObject, vm, result, actualDeleteCount); 1217 RETURN_IF_EXCEPTION(scope, { }); 1218 } 1219 1220 if (itemCount < actualDeleteCount) { 1221 shift<JSArray::ShiftCountForSplice>(globalObject, thisObj, actualStart, actualDeleteCount, itemCount, length); 1222 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 1223 } else if (itemCount > actualDeleteCount) { 1224 unshift<JSArray::ShiftCountForSplice>(globalObject, thisObj, actualStart, actualDeleteCount, itemCount, length); 1225 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 1226 } 1227 for (unsigned k = 0; k < itemCount; ++k) { 1228 thisObj->putByIndexInline(globalObject, k + actualStart, callFrame->uncheckedArgument(k + 2), true); 1229 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 1230 } 1231 1232 scope.release(); 1233 setLength(globalObject, vm, thisObj, length - actualDeleteCount + itemCount); 1234 return JSValue::encode(result); 1235 } 1236 1237 JSC_DEFINE_HOST_FUNCTION(arrayProtoFuncUnShift, (JSGlobalObject* globalObject, CallFrame* callFrame)) 1238 { 1239 VM& vm = globalObject->vm(); 1240 auto scope = DECLARE_THROW_SCOPE(vm); 1241 // 15.4.4.13 1242 1243 JSObject* thisObj = callFrame->thisValue().toThis(globalObject, ECMAMode::strict()).toObject(globalObject); 1244 EXCEPTION_ASSERT(!!scope.exception() == !thisObj); 1245 if (UNLIKELY(!thisObj)) 1246 return encodedJSValue(); 1247 uint64_t length = static_cast<uint64_t>(toLength(globalObject, thisObj)); 1248 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 1249 1250 unsigned nrArgs = callFrame->argumentCount(); 1251 if (nrArgs) { 1252 if (UNLIKELY(length + nrArgs > static_cast<uint64_t>(maxSafeInteger()))) 1253 return throwVMTypeError(globalObject, scope, "unshift cannot produce an array of length larger than (2 ** 53) - 1"_s); 1254 unshift<JSArray::ShiftCountForShift>(globalObject, thisObj, 0, 0, nrArgs, length); 1255 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 1256 } 1257 for (unsigned k = 0; k < nrArgs; ++k) { 1258 thisObj->putByIndexInline(globalObject, k, callFrame->uncheckedArgument(k), true); 1259 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 1260 } 1261 uint64_t newLength = length + nrArgs; 1262 scope.release(); 1263 setLength(globalObject, vm, thisObj, newLength); 1264 return JSValue::encode(jsNumber(newLength)); 1265 } 1266 1267 enum class IndexOfDirection { Forward, Backward }; 1268 template<IndexOfDirection direction> 1269 ALWAYS_INLINE JSValue fastIndexOf(JSGlobalObject* globalObject, VM& vm, JSArray* array, uint64_t length64, JSValue searchElement, uint64_t index64) 1270 { 1271 auto scope = DECLARE_THROW_SCOPE(vm); 1272 1273 bool canDoFastPath = array->canDoFastIndexedAccess(vm) 1274 && array->getArrayLength() == length64 // The effects in getting `index` could have changed the length of this array. 1275 && static_cast<uint32_t>(index64) == index64; 1276 if (!canDoFastPath) 1277 return JSValue(); 1278 1279 uint32_t length = static_cast<uint32_t>(length64); 1280 uint32_t index = static_cast<uint32_t>(index64); 1281 1282 switch (array->indexingType()) { 1283 case ALL_INT32_INDEXING_TYPES: { 1284 if (!searchElement.isNumber()) 1285 return jsNumber(-1); 1286 JSValue searchInt32; 1287 if (searchElement.isInt32()) 1288 searchInt32 = searchElement; 1289 else { 1290 double searchNumber = searchElement.asNumber(); 1291 if (!canBeInt32(searchNumber)) 1292 return jsNumber(-1); 1293 searchInt32 = jsNumber(static_cast<int32_t>(searchNumber)); 1294 } 1295 auto& butterfly = *array->butterfly(); 1296 auto data = butterfly.contiguous().data(); 1297 if (direction == IndexOfDirection::Forward) { 1298 for (; index < length; ++index) { 1299 // Array#indexOf uses `===` semantics (not HashMap isEqual semantics). 1300 // And the hole never matches against Int32 value. 1301 if (searchInt32 == data[index].get()) 1302 return jsNumber(index); 1303 } 1304 } else { 1305 do { 1306 ASSERT(index < length); 1307 // Array#lastIndexOf uses `===` semantics (not HashMap isEqual semantics). 1308 // And the hole never matches against Int32 value. 1309 if (searchInt32 == data[index].get()) 1310 return jsNumber(index); 1311 } while (index--); 1312 } 1313 return jsNumber(-1); 1314 } 1315 case ALL_CONTIGUOUS_INDEXING_TYPES: { 1316 auto& butterfly = *array->butterfly(); 1317 auto data = butterfly.contiguous().data(); 1318 1319 if (direction == IndexOfDirection::Forward) { 1320 for (; index < length; ++index) { 1321 JSValue value = data[index].get(); 1322 if (!value) 1323 continue; 1324 bool isEqual = JSValue::strictEqual(globalObject, searchElement, value); 1325 RETURN_IF_EXCEPTION(scope, { }); 1326 if (isEqual) 1327 return jsNumber(index); 1328 } 1329 } else { 1330 do { 1331 ASSERT(index < length); 1332 JSValue value = data[index].get(); 1333 if (!value) 1334 continue; 1335 bool isEqual = JSValue::strictEqual(globalObject, searchElement, value); 1336 RETURN_IF_EXCEPTION(scope, { }); 1337 if (isEqual) 1338 return jsNumber(index); 1339 } while (index--); 1340 } 1341 return jsNumber(-1); 1342 } 1343 case ALL_DOUBLE_INDEXING_TYPES: { 1344 if (!searchElement.isNumber()) 1345 return jsNumber(-1); 1346 double searchNumber = searchElement.asNumber(); 1347 auto& butterfly = *array->butterfly(); 1348 auto data = butterfly.contiguousDouble().data(); 1349 if (direction == IndexOfDirection::Forward) { 1350 for (; index < length; ++index) { 1351 // Array#indexOf uses `===` semantics (not HashMap isEqual semantics). 1352 // And the hole never matches since it is NaN. 1353 if (data[index] == searchNumber) 1354 return jsNumber(index); 1355 } 1356 } else { 1357 do { 1358 ASSERT(index < length); 1359 // Array#lastIndexOf uses `===` semantics (not HashMap isEqual semantics). 1360 // And the hole never matches since it is NaN. 1361 if (data[index] == searchNumber) 1362 return jsNumber(index); 1363 } while (index--); 1364 } 1365 return jsNumber(-1); 1366 } 1367 default: 1368 return JSValue(); 1369 } 1370 } 1371 1372 JSC_DEFINE_HOST_FUNCTION(arrayProtoFuncIndexOf, (JSGlobalObject* globalObject, CallFrame* callFrame)) 1373 { 1374 VM& vm = globalObject->vm(); 1375 auto scope = DECLARE_THROW_SCOPE(vm); 1376 1377 // 15.4.4.14 1378 JSObject* thisObject = callFrame->thisValue().toThis(globalObject, ECMAMode::strict()).toObject(globalObject); 1379 EXCEPTION_ASSERT(!!scope.exception() == !thisObject); 1380 if (UNLIKELY(!thisObject)) 1381 return { }; 1382 uint64_t length = static_cast<uint64_t>(toLength(globalObject, thisObject)); 1383 RETURN_IF_EXCEPTION(scope, { }); 1384 if (!length) 1385 return JSValue::encode(jsNumber(-1)); 1386 1387 uint64_t index = argumentClampedIndexFromStartOrEnd(globalObject, callFrame->argument(1), length); 1388 RETURN_IF_EXCEPTION(scope, { }); 1389 JSValue searchElement = callFrame->argument(0); 1390 1391 if (isJSArray(thisObject)) { 1392 JSValue result = fastIndexOf<IndexOfDirection::Forward>(globalObject, vm, asArray(thisObject), length, searchElement, index); 1393 RETURN_IF_EXCEPTION(scope, { }); 1394 if (result) 1395 return JSValue::encode(result); 1396 } 1397 1398 for (; index < length; ++index) { 1399 JSValue e = getProperty(globalObject, thisObject, index); 1400 RETURN_IF_EXCEPTION(scope, { }); 1401 if (!e) 1402 continue; 1403 bool isEqual = JSValue::strictEqual(globalObject, searchElement, e); 1404 RETURN_IF_EXCEPTION(scope, { }); 1405 if (isEqual) 1406 return JSValue::encode(jsNumber(index)); 1407 } 1408 1409 return JSValue::encode(jsNumber(-1)); 1410 } 1411 1412 JSC_DEFINE_HOST_FUNCTION(arrayProtoFuncLastIndexOf, (JSGlobalObject* globalObject, CallFrame* callFrame)) 1413 { 1414 VM& vm = globalObject->vm(); 1415 auto scope = DECLARE_THROW_SCOPE(vm); 1416 1417 // 15.4.4.15 1418 JSObject* thisObject = callFrame->thisValue().toThis(globalObject, ECMAMode::strict()).toObject(globalObject); 1419 EXCEPTION_ASSERT(!!scope.exception() == !thisObject); 1420 if (UNLIKELY(!thisObject)) 1421 return { }; 1422 uint64_t length = static_cast<uint64_t>(toLength(globalObject, thisObject)); 1423 RETURN_IF_EXCEPTION(scope, { }); 1424 if (!length) 1425 return JSValue::encode(jsNumber(-1)); 1426 1427 uint64_t index = length - 1; 1428 if (callFrame->argumentCount() >= 2) { 1429 JSValue fromValue = callFrame->uncheckedArgument(1); 1430 double fromDouble = fromValue.toInteger(globalObject); 1431 RETURN_IF_EXCEPTION(scope, { }); 1432 if (fromDouble < 0) { 1433 fromDouble += length; 1434 if (fromDouble < 0) 1435 return JSValue::encode(jsNumber(-1)); 1436 } 1437 if (fromDouble < length) 1438 index = static_cast<uint64_t>(fromDouble); 1439 } 1440 1441 JSValue searchElement = callFrame->argument(0); 1442 1443 if (isJSArray(thisObject)) { 1444 JSValue result = fastIndexOf<IndexOfDirection::Backward>(globalObject, vm, asArray(thisObject), length, searchElement, index); 1445 RETURN_IF_EXCEPTION(scope, { }); 1446 if (result) 1447 return JSValue::encode(result); 1448 } 1449 1450 do { 1451 ASSERT(index < length); 1452 JSValue e = getProperty(globalObject, thisObject, index); 1453 RETURN_IF_EXCEPTION(scope, { }); 1454 if (!e) 1455 continue; 1456 bool isEqual = JSValue::strictEqual(globalObject, searchElement, e); 1457 RETURN_IF_EXCEPTION(scope, { }); 1458 if (isEqual) 1459 return JSValue::encode(jsNumber(index)); 1460 } while (index--); 1461 1462 return JSValue::encode(jsNumber(-1)); 1463 } 1464 1465 static bool moveElements(JSGlobalObject* globalObject, VM& vm, JSArray* target, unsigned targetOffset, JSArray* source, unsigned sourceLength) 1466 { 1467 auto scope = DECLARE_THROW_SCOPE(vm); 1468 1469 if (LIKELY(!hasAnyArrayStorage(source->indexingType()) && !holesMustForwardToPrototype(vm, source))) { 1470 for (unsigned i = 0; i < sourceLength; ++i) { 1471 JSValue value = source->tryGetIndexQuickly(i); 1472 if (value) { 1473 target->putDirectIndex(globalObject, targetOffset + i, value, 0, PutDirectIndexShouldThrow); 1474 RETURN_IF_EXCEPTION(scope, false); 1475 } 1476 } 1477 } else { 1478 for (unsigned i = 0; i < sourceLength; ++i) { 1479 JSValue value = getProperty(globalObject, source, i); 1480 RETURN_IF_EXCEPTION(scope, false); 1481 if (value) { 1482 target->putDirectIndex(globalObject, targetOffset + i, value, 0, PutDirectIndexShouldThrow); 1483 RETURN_IF_EXCEPTION(scope, false); 1484 } 1485 } 1486 } 1487 return true; 1488 } 1489 1490 static EncodedJSValue concatAppendOne(JSGlobalObject* globalObject, VM& vm, JSArray* first, JSValue second) 1491 { 1492 auto scope = DECLARE_THROW_SCOPE(vm); 1493 1494 ASSERT(!isJSArray(second)); 1495 ASSERT(!shouldUseSlowPut(first->indexingType())); 1496 Butterfly* firstButterfly = first->butterfly(); 1497 unsigned firstArraySize = firstButterfly->publicLength(); 1498 1499 Checked<unsigned, RecordOverflow> checkedResultSize = firstArraySize; 1500 checkedResultSize += 1; 1501 if (UNLIKELY(checkedResultSize.hasOverflowed())) { 1502 throwOutOfMemoryError(globalObject, scope); 1503 return encodedJSValue(); 1504 } 1505 1506 unsigned resultSize = checkedResultSize.unsafeGet(); 1507 IndexingType type = first->mergeIndexingTypeForCopying(indexingTypeForValue(second) | IsArray); 1508 1509 if (type == NonArray) 1510 type = first->indexingType(); 1511 1512 Structure* resultStructure = globalObject->arrayStructureForIndexingTypeDuringAllocation(type); 1513 JSArray* result = JSArray::tryCreate(vm, resultStructure, resultSize); 1514 if (UNLIKELY(!result)) { 1515 throwOutOfMemoryError(globalObject, scope); 1516 return encodedJSValue(); 1517 } 1518 1519 bool success = result->appendMemcpy(globalObject, vm, 0, first); 1520 EXCEPTION_ASSERT(!scope.exception() || !success); 1521 if (!success) { 1522 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 1523 1524 bool success = moveElements(globalObject, vm, result, 0, first, firstArraySize); 1525 EXCEPTION_ASSERT(!scope.exception() == success); 1526 if (UNLIKELY(!success)) 1527 return encodedJSValue(); 1528 } 1529 1530 scope.release(); 1531 result->putDirectIndex(globalObject, firstArraySize, second); 1532 return JSValue::encode(result); 1533 1534 } 1535 1536 template<typename T> 1537 void clearElement(T& element) 1538 { 1539 element.clear(); 1540 } 1541 1542 template<> 1543 void clearElement(double& element) 1544 { 1545 element = PNaN; 1546 } 1547 1548 template<typename T> 1549 ALWAYS_INLINE void copyElements(T* buffer, unsigned offset, T* source, unsigned sourceSize, IndexingType sourceType) 1550 { 1551 if (sourceType != ArrayWithUndecided) { 1552 gcSafeMemcpy(buffer + offset, source, sizeof(JSValue) * sourceSize); 1553 return; 1554 } 1555 1556 for (unsigned i = sourceSize; i--;) 1557 clearElement<T>(buffer[i + offset]); 1558 }; 1559 1560 JSC_DEFINE_HOST_FUNCTION(arrayProtoPrivateFuncConcatMemcpy, (JSGlobalObject* globalObject, CallFrame* callFrame)) 1561 { 1562 ASSERT(callFrame->argumentCount() == 2); 1563 VM& vm = globalObject->vm(); 1564 auto scope = DECLARE_THROW_SCOPE(vm); 1565 1566 JSArray* firstArray = jsCast<JSArray*>(callFrame->uncheckedArgument(0)); 1567 1568 // This code assumes that neither array has set Symbol.isConcatSpreadable. If the first array 1569 // has indexed accessors then one of those accessors might change the value of Symbol.isConcatSpreadable 1570 // on the second argument. 1571 if (UNLIKELY(shouldUseSlowPut(firstArray->indexingType()))) 1572 return JSValue::encode(jsNull()); 1573 1574 // We need to check the species constructor here since checking it in the JS wrapper is too expensive for the non-optimizing tiers. 1575 bool isValid = speciesWatchpointIsValid(vm, firstArray); 1576 scope.assertNoException(); 1577 if (UNLIKELY(!isValid)) 1578 return JSValue::encode(jsNull()); 1579 1580 JSValue second = callFrame->uncheckedArgument(1); 1581 if (!isJSArray(second)) 1582 RELEASE_AND_RETURN(scope, concatAppendOne(globalObject, vm, firstArray, second)); 1583 1584 JSArray* secondArray = jsCast<JSArray*>(second); 1585 1586 Butterfly* firstButterfly = firstArray->butterfly(); 1587 Butterfly* secondButterfly = secondArray->butterfly(); 1588 1589 unsigned firstArraySize = firstButterfly->publicLength(); 1590 unsigned secondArraySize = secondButterfly->publicLength(); 1591 1592 Checked<unsigned, RecordOverflow> checkedResultSize = firstArraySize; 1593 checkedResultSize += secondArraySize; 1594 1595 if (UNLIKELY(checkedResultSize.hasOverflowed())) { 1596 throwOutOfMemoryError(globalObject, scope); 1597 return encodedJSValue(); 1598 } 1599 1600 unsigned resultSize = checkedResultSize.unsafeGet(); 1601 IndexingType firstType = firstArray->indexingType(); 1602 IndexingType secondType = secondArray->indexingType(); 1603 IndexingType type = firstArray->mergeIndexingTypeForCopying(secondType); 1604 if (type == NonArray || !firstArray->canFastCopy(vm, secondArray) || resultSize >= MIN_SPARSE_ARRAY_INDEX) { 1605 JSArray* result = constructEmptyArray(globalObject, nullptr, resultSize); 1606 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 1607 1608 bool success = moveElements(globalObject, vm, result, 0, firstArray, firstArraySize); 1609 EXCEPTION_ASSERT(!scope.exception() == success); 1610 if (UNLIKELY(!success)) 1611 return encodedJSValue(); 1612 success = moveElements(globalObject, vm, result, firstArraySize, secondArray, secondArraySize); 1613 EXCEPTION_ASSERT(!scope.exception() == success); 1614 if (UNLIKELY(!success)) 1615 return encodedJSValue(); 1616 1617 return JSValue::encode(result); 1618 } 1619 1620 Structure* resultStructure = globalObject->arrayStructureForIndexingTypeDuringAllocation(type); 1621 if (UNLIKELY(hasAnyArrayStorage(resultStructure->indexingType()))) 1622 return JSValue::encode(jsNull()); 1623 1624 ASSERT(!globalObject->isHavingABadTime()); 1625 ObjectInitializationScope initializationScope(vm); 1626 JSArray* result = JSArray::tryCreateUninitializedRestricted(initializationScope, resultStructure, resultSize); 1627 if (UNLIKELY(!result)) { 1628 throwOutOfMemoryError(globalObject, scope); 1629 return encodedJSValue(); 1630 } 1631 1632 if (type == ArrayWithDouble) { 1633 double* buffer = result->butterfly()->contiguousDouble().data(); 1634 copyElements(buffer, 0, firstButterfly->contiguousDouble().data(), firstArraySize, firstType); 1635 copyElements(buffer, firstArraySize, secondButterfly->contiguousDouble().data(), secondArraySize, secondType); 1636 1637 } else if (type != ArrayWithUndecided) { 1638 WriteBarrier<Unknown>* buffer = result->butterfly()->contiguous().data(); 1639 copyElements(buffer, 0, firstButterfly->contiguous().data(), firstArraySize, firstType); 1640 copyElements(buffer, firstArraySize, secondButterfly->contiguous().data(), secondArraySize, secondType); 1641 } 1642 1643 ASSERT(result->butterfly()->publicLength() == resultSize); 1644 return JSValue::encode(result); 1645 } 1646 1647 JSC_DEFINE_HOST_FUNCTION(arrayProtoPrivateFuncAppendMemcpy, (JSGlobalObject* globalObject, CallFrame* callFrame)) 1648 { 1649 ASSERT(callFrame->argumentCount() == 3); 1650 1651 VM& vm = globalObject->vm(); 1652 auto scope = DECLARE_THROW_SCOPE(vm); 1653 JSArray* resultArray = jsCast<JSArray*>(callFrame->uncheckedArgument(0)); 1654 JSArray* otherArray = jsCast<JSArray*>(callFrame->uncheckedArgument(1)); 1655 JSValue startValue = callFrame->uncheckedArgument(2); 1656 ASSERT(startValue.isUInt32AsAnyInt()); 1657 unsigned startIndex = startValue.asUInt32AsAnyInt(); 1658 bool success = resultArray->appendMemcpy(globalObject, vm, startIndex, otherArray); 1659 EXCEPTION_ASSERT(!scope.exception() || !success); 1660 if (success) 1661 return JSValue::encode(jsUndefined()); 1662 RETURN_IF_EXCEPTION(scope, encodedJSValue()); 1663 scope.release(); 1664 moveElements(globalObject, vm, resultArray, startIndex, otherArray, otherArray->length()); 1665 return JSValue::encode(jsUndefined()); 1666 } 1667 1668 } // namespace JSC