1/* 2 * Copyright (c) 2021-2024 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16#include "ecmascript/js_array.h" 17 18#include "ecmascript/interpreter/interpreter.h" 19#include "ecmascript/object_fast_operator-inl.h" 20 21namespace panda::ecmascript { 22using base::ArrayHelper; 23 24JSTaggedValue JSArray::LengthGetter([[maybe_unused]] JSThread *thread, const JSHandle<JSObject> &self) 25{ 26 return JSTaggedValue(JSArray::Cast(*self)->GetLength()); 27} 28 29bool JSArray::LengthSetter(JSThread *thread, const JSHandle<JSObject> &self, const JSHandle<JSTaggedValue> &value, 30 bool mayThrow) 31{ 32 uint32_t newLen = 0; 33 if (!JSTaggedValue::ToArrayLength(thread, value, &newLen) && mayThrow) { 34 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false); 35 } 36 37 uint32_t oldLen = JSArray::Cast(*self)->GetArrayLength(); 38 if (oldLen == newLen) { 39 return true; 40 } 41 42 if (!IsArrayLengthWritable(thread, self)) { 43 if (mayThrow) { 44 THROW_TYPE_ERROR_AND_RETURN(thread, GET_MESSAGE_STRING(SetReadOnlyProperty), false); 45 } 46 return false; 47 } 48 49 JSArray::SetCapacity(thread, self, oldLen, newLen); 50 uint32_t actualLen = JSArray::Cast(*self)->GetArrayLength(); 51 if (actualLen != newLen) { 52 if (mayThrow) { 53 THROW_TYPE_ERROR_AND_RETURN(thread, "Not all array elements is configurable", false); 54 } 55 return false; 56 } 57 58 return true; 59} 60 61JSHandle<JSTaggedValue> JSArray::ArrayCreate(JSThread *thread, JSTaggedNumber length, ArrayMode mode) 62{ 63 JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv(); 64 JSHandle<JSTaggedValue> arrayFunction = env->GetArrayFunction(); 65 return JSArray::ArrayCreate(thread, length, arrayFunction, mode); 66} 67 68// 9.4.2.2 ArrayCreate(length, proto) 69JSHandle<JSTaggedValue> JSArray::ArrayCreate(JSThread *thread, JSTaggedNumber length, 70 const JSHandle<JSTaggedValue> &newTarget, ArrayMode mode) 71{ 72 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); 73 // Assert: length is an integer Number ≥ 0. 74 ASSERT_PRINT(length.IsInteger() && length.GetNumber() >= 0, "length must be positive integer"); 75 // 2. If length is −0, let length be +0. 76 double arrayLength = length.GetNumber(); 77 if (arrayLength > MAX_ARRAY_INDEX) { 78 JSHandle<JSTaggedValue> exception(thread, JSTaggedValue::Exception()); 79 THROW_RANGE_ERROR_AND_RETURN(thread, "array length must equal or less than 2^32.", exception); 80 } 81 uint32_t normalArrayLength = length.ToUint32(); 82 83 // 8. Set the [[Prototype]] internal slot of A to proto. 84 JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv(); 85 JSHandle<JSFunction> arrayFunc(env->GetArrayFunction()); 86 JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(arrayFunc, newTarget); 87 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread); 88 // 9. Set the [[Extensible]] internal slot of A to true. 89 obj->GetJSHClass()->SetExtensible(true); 90 91 // 10. Perform OrdinaryDefineOwnProperty(A, "length", PropertyDescriptor{[[Value]]: length, [[Writable]]: 92 // true, [[Enumerable]]: false, [[Configurable]]: false}). 93 if (mode == ArrayMode::LITERAL) { 94 JSArray::Cast(*obj)->SetArrayLength(thread, normalArrayLength); 95 } else { 96 JSArray::SetCapacity(thread, obj, 0, normalArrayLength, true); 97 } 98 99 // For new Array(Len), the elementsKind should be Hole 100 if (thread->GetEcmaVM()->IsEnableElementsKind()) { 101 if ((newTarget.GetTaggedValue() == arrayFunc.GetTaggedValue()) && normalArrayLength != 0) { 102 JSHandle<JSArray> newArray(obj); 103 #if ECMASCRIPT_ENABLE_ELEMENTSKIND_ALWAY_GENERIC 104 JSHClass::TransitToElementsKind(thread, newArray, ElementsKind::GENERIC); 105 #else 106 JSHClass::TransitToElementsKind(thread, newArray, ElementsKind::HOLE); 107 #endif 108 } 109 } 110 111 return JSHandle<JSTaggedValue>(obj); 112} 113 114// 9.4.2.3 ArraySpeciesCreate(originalArray, length) 115JSTaggedValue JSArray::ArraySpeciesCreate(JSThread *thread, const JSHandle<JSObject> &originalArray, 116 JSTaggedNumber length) 117{ 118 JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv(); 119 const GlobalEnvConstants *globalConst = thread->GlobalConstants(); 120 // Assert: length is an integer Number ≥ 0. 121 ASSERT_PRINT(length.IsInteger() && length.GetNumber() >= 0, "length must be positive integer"); 122 // If length is −0, let length be +0. 123 int64_t arrayLength = length.GetNumber(); 124 if (arrayLength == -0) { 125 arrayLength = +0; 126 } 127 // Let C be undefined. 128 // Let isArray be IsArray(originalArray). 129 JSHandle<JSTaggedValue> originalValue(originalArray); 130 bool isArray = originalValue->IsArray(thread); 131 // ReturnIfAbrupt(isArray). 132 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); 133 // If isArray is true, then 134 JSHandle<JSTaggedValue> constructor(thread, JSTaggedValue::Undefined()); 135 if (isArray) { 136 // Let C be Get(originalArray, "constructor"). 137 auto *hclass = originalArray->GetJSHClass(); 138 JSTaggedValue proto = hclass->GetPrototype(); 139 if (hclass->IsJSArray() && !hclass->HasConstructor() && proto.IsJSArray()) { 140 return JSArray::ArrayCreate(thread, length).GetTaggedValue(); 141 } 142 JSHandle<JSTaggedValue> constructorKey = globalConst->GetHandledConstructorString(); 143 constructor = JSTaggedValue::GetProperty(thread, originalValue, constructorKey).GetValue(); 144 // ReturnIfAbrupt(C). 145 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); 146 // If IsConstructor(C) is true, then 147 if (constructor->IsConstructor()) { 148 // Let thisRealm be the running execution context’s Realm. 149 // Let realmC be GetFunctionRealm(C). 150 JSHandle<GlobalEnv> realmC = JSObject::GetFunctionRealm(thread, constructor); 151 // ReturnIfAbrupt(realmC). 152 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); 153 // If thisRealm and realmC are not the same Realm Record, then 154 if (*realmC != *env) { 155 JSTaggedValue realmArrayConstructor = realmC->GetArrayFunction().GetTaggedValue(); 156 // If SameValue(C, realmC.[[intrinsics]].[[%Array%]]) is true, let C be undefined. 157 if (JSTaggedValue::SameValue(constructor.GetTaggedValue(), realmArrayConstructor)) { 158 return JSArray::ArrayCreate(thread, length).GetTaggedValue(); 159 } 160 } 161 } 162 163 // If Type(C) is Object, then 164 if (constructor->IsECMAObject()) { 165 // Let C be Get(C, @@species). 166 JSHandle<JSTaggedValue> speciesSymbol = env->GetSpeciesSymbol(); 167 constructor = JSTaggedValue::GetProperty(thread, constructor, speciesSymbol).GetValue(); 168 // ReturnIfAbrupt(C). 169 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); 170 // If C is null, let C be undefined. 171 if (constructor->IsNull()) { 172 return JSArray::ArrayCreate(thread, length).GetTaggedValue(); 173 } 174 } 175 } 176 177 // If C is undefined, return ArrayCreate(length). 178 if (constructor->IsUndefined()) { 179 return JSArray::ArrayCreate(thread, length).GetTaggedValue(); 180 } 181 // If IsConstructor(C) is false, throw a TypeError exception. 182 if (!constructor->IsConstructor()) { 183 THROW_TYPE_ERROR_AND_RETURN(thread, "Not a constructor", JSTaggedValue::Exception()); 184 } 185 // Return Construct(C, «length»). 186 JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined(); 187 EcmaRuntimeCallInfo *info = 188 EcmaInterpreter::NewRuntimeCallInfo(thread, constructor, undefined, undefined, 1); 189 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception()); 190 info->SetCallArg(JSTaggedValue(arrayLength)); 191 JSTaggedValue result = JSFunction::Construct(info); 192 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); 193 194 // NOTEIf originalArray was created using the standard built-in Array constructor for 195 // a Realm that is not the Realm of the running execution context, then a new Array is 196 // created using the Realm of the running execution context. This maintains compatibility 197 // with Web browsers that have historically had that behaviour for the Array.prototype methods 198 // that now are defined using ArraySpeciesCreate. 199 return result; 200} 201 202void JSArray::SetCapacity(JSThread *thread, const JSHandle<JSObject> &array, 203 uint32_t oldLen, uint32_t newLen, bool isNew) 204{ 205 TaggedArray *element = TaggedArray::Cast(array->GetElements().GetTaggedObject()); 206 207 if (element->IsDictionaryMode()) { 208 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); 209 uint32_t numOfElements = array->GetNumberOfElements(); 210 uint32_t newNumOfElements = newLen; 211 212 if (newLen < oldLen && numOfElements != 0U) { 213 JSHandle<NumberDictionary> dictHandle(thread, element); 214 JSHandle<TaggedArray> newArr = factory->NewTaggedArray(numOfElements); 215 GetAllElementKeys(thread, array, 0, newArr); 216 for (uint32_t i = numOfElements - 1; i >= newLen; i--) { 217 JSTaggedValue value = newArr->Get(i); 218 uint32_t output = 0; 219 JSTaggedValue::StringToElementIndex(value, &output); 220 JSTaggedValue key(static_cast<int>(output)); 221 int entry = dictHandle->FindEntry(key); 222 auto attr = dictHandle->GetAttributes(entry).GetValue(); 223 PropertyAttributes propAttr(attr); 224 if (propAttr.IsConfigurable()) { 225 JSHandle<NumberDictionary> newDict = NumberDictionary::Remove(thread, dictHandle, entry); 226 array->SetElements(thread, newDict); 227 if (i == 0) { 228 newNumOfElements = i; 229 break; 230 } 231 } else { 232 newNumOfElements = i + 1; 233 break; 234 } 235 } 236 } 237 JSArray::Cast(*array)->SetArrayLength(thread, newNumOfElements); 238 return; 239 } 240 241 uint32_t capacity = element->GetLength(); 242 if (newLen <= capacity) { 243 // judge if need to cut down the array size, else fill the unused tail with holes 244 CheckAndCopyArray(thread, JSHandle<JSArray>(array)); 245 array->FillElementsWithHoles(thread, newLen, oldLen < capacity ? oldLen : capacity); 246 } 247 if (JSObject::ShouldTransToDict(oldLen, newLen)) { 248 JSObject::ElementsToDictionary(thread, array); 249 } else if (newLen > capacity) { 250 JSObject::GrowElementsCapacity(thread, array, newLen, isNew); 251 } 252 JSArray::Cast(*array)->SetArrayLength(thread, newLen); 253 254 // Update ElementsKind after reset array length. 255 // Add this switch because we do not support ElementsKind for instance from new Array 256 if (!array->IsElementDict()) { 257 ElementsKind oldKind = array->GetClass()->GetElementsKind(); 258 if (Elements::IsGeneric(oldKind)) { 259 return; 260 } 261#if ECMASCRIPT_ENABLE_ELEMENTSKIND_ALWAY_GENERIC 262 ElementsKind newKind = ElementsKind::GENERIC; 263 #else 264 ElementsKind newKind = ElementsKind::NONE; 265 #endif 266 for (uint32_t i = 0; i < newLen; ++i) { 267 JSTaggedValue val = ElementAccessor::Get(array, i); 268 newKind = Elements::ToElementsKind(val, newKind); 269 } 270 // elements length might not be zero when newLen is zero 271 uint32_t oldElementsLength = ElementAccessor::GetElementsLength(array); 272 if (newKind == ElementsKind::NONE && oldElementsLength != 0) { 273 JSHandle<TaggedArray> newTaggedArray = thread->GetEcmaVM()->GetFactory()->NewTaggedArray(oldElementsLength); 274 array->SetElements(thread, newTaggedArray); 275 if (!JSHClass::TransitToElementsKindUncheck(thread, array, newKind)) { 276 ASSERT(array->GetClass()->GetElementsKind() == ElementsKind::GENERIC); 277 } 278 } else if (newKind != oldKind) { 279 if (JSHClass::TransitToElementsKindUncheck(thread, array, newKind)) { 280 Elements::MigrateArrayWithKind(thread, array, oldKind, newKind); 281 } else { 282 // For the case that array has property transition, 283 // Its elementsKind should be GENERIC for now. 284 ASSERT(array->GetClass()->GetElementsKind() == ElementsKind::GENERIC); 285 } 286 } 287 } 288} 289 290bool JSArray::ArraySetLength(JSThread *thread, const JSHandle<JSObject> &array, const PropertyDescriptor &desc) 291{ 292 JSHandle<JSTaggedValue> lengthKeyHandle(thread->GlobalConstants()->GetHandledLengthString()); 293 294 // 1. If the [[Value]] field of Desc is absent, then 295 if (!desc.HasValue()) { 296 // 1a. Return OrdinaryDefineOwnProperty(A, "length", Desc). 297 return JSObject::OrdinaryDefineOwnProperty(thread, array, lengthKeyHandle, desc); 298 } 299 // 2. Let newLenDesc be a copy of Desc. 300 // (Actual copying is not necessary.) 301 PropertyDescriptor newLenDesc = desc; 302 // 3. - 7. Convert Desc.[[Value]] to newLen. 303 uint32_t newLen = 0; 304 if (!JSTaggedValue::ToArrayLength(thread, desc.GetValue(), &newLen)) { 305 THROW_RANGE_ERROR_AND_RETURN(thread, "array length must equal or less than 2^32.", false); 306 } 307 // 8. Set newLenDesc.[[Value]] to newLen. 308 // (Done below, if needed.) 309 // 9. Let oldLenDesc be OrdinaryGetOwnProperty(A, "length"). 310 PropertyDescriptor oldLenDesc(thread); 311 [[maybe_unused]] bool success = GetOwnProperty(thread, array, lengthKeyHandle, oldLenDesc); 312 // 10. (Assert) 313 ASSERT(success); 314 315 // 11. Let oldLen be oldLenDesc.[[Value]]. 316 uint32_t oldLen = 0; 317 JSTaggedValue::ToArrayLength(thread, oldLenDesc.GetValue(), &oldLen); 318 // 12. If newLen >= oldLen, then 319 if (newLen >= oldLen) { 320 // 8. Set newLenDesc.[[Value]] to newLen. 321 // 12a. Return OrdinaryDefineOwnProperty(A, "length", newLenDesc). 322 newLenDesc.SetValue(JSHandle<JSTaggedValue>(thread, JSTaggedValue(newLen))); 323 return JSObject::OrdinaryDefineOwnProperty(thread, array, lengthKeyHandle, newLenDesc); 324 } 325 // 13. If oldLenDesc.[[Writable]] is false, return false. 326 if (!oldLenDesc.IsWritable() || 327 // Also handle the {configurable: true} case since we later use 328 // JSArray::SetLength instead of OrdinaryDefineOwnProperty to change 329 // the length, and it doesn't have access to the descriptor anymore. 330 newLenDesc.IsConfigurable() || 331 (newLenDesc.HasEnumerable() && (newLenDesc.IsEnumerable() != oldLenDesc.IsEnumerable()))) { 332 return false; 333 } 334 // 14. If newLenDesc.[[Writable]] is absent or has the value true, 335 // let newWritable be true. 336 bool newWritable = false; 337 if (!newLenDesc.HasWritable() || newLenDesc.IsWritable()) { 338 newWritable = true; 339 } else { 340 // 15. Else, 341 // 15a. Need to defer setting the [[Writable]] attribute to false in case 342 // any elements cannot be deleted. 343 // 15b. Let newWritable be false. (It's initialized as "false" anyway.) 344 // 15c. Set newLenDesc.[[Writable]] to true. 345 // (Not needed.) 346 } 347 348 // Most of steps 16 through 19 is implemented by JSArray::SetCapacity. 349 JSArray::SetCapacity(thread, array, oldLen, newLen); 350 // Steps 19d-ii, 20. 351 if (!newWritable) { 352 PropertyDescriptor readonly(thread); 353 readonly.SetWritable(false); 354 success = JSObject::DefineOwnProperty(thread, array, lengthKeyHandle, readonly); 355 ASSERT_PRINT(success, "DefineOwnProperty of length must be success here!"); 356 } 357 358 // Steps 19d-v, 21. Return false if there were non-deletable elements. 359 uint32_t arrayLength = JSArray::Cast(*array)->GetArrayLength(); 360 return arrayLength == newLen; 361} 362 363bool JSArray::PropertyKeyToArrayIndex(JSThread *thread, const JSHandle<JSTaggedValue> &key, uint32_t *output) 364{ 365 return JSTaggedValue::ToArrayLength(thread, key, output) && *output <= JSArray::MAX_ARRAY_INDEX; 366} 367 368// 9.4.2.1 [[DefineOwnProperty]] ( P, Desc) 369bool JSArray::DefineOwnProperty(JSThread *thread, const JSHandle<JSObject> &array, const JSHandle<JSTaggedValue> &key, 370 const PropertyDescriptor &desc) 371{ 372 // 1. Assert: IsPropertyKey(P) is true. 373 ASSERT_PRINT(JSTaggedValue::IsPropertyKey(key), "Key is not a property key!"); 374 // 2. If P is "length", then 375 if (IsLengthString(thread, key)) { 376 // a. Return ArraySetLength(A, Desc). 377 return ArraySetLength(thread, array, desc); 378 } 379 // 3. Else if P is an array index, then 380 // already do in step 4. 381 // 4. Return OrdinaryDefineOwnProperty(A, P, Desc). 382 bool success = JSObject::OrdinaryDefineOwnProperty(thread, array, key, desc); 383 if (success) { 384 JSTaggedValue constructorKey = thread->GlobalConstants()->GetConstructorString(); 385 if (key.GetTaggedValue() == constructorKey) { 386 array->GetJSHClass()->SetHasConstructor(true); 387 return true; 388 } 389 } 390 return success; 391} 392 393bool JSArray::DefineOwnProperty(JSThread *thread, const JSHandle<JSObject> &array, uint32_t index, 394 const PropertyDescriptor &desc) 395{ 396 return JSObject::OrdinaryDefineOwnProperty(thread, array, index, desc); 397} 398 399bool JSArray::IsLengthString(JSThread *thread, const JSHandle<JSTaggedValue> &key) 400{ 401 return key.GetTaggedValue() == thread->GlobalConstants()->GetLengthString(); 402} 403 404// ecma6 7.3 Operations on Objects 405JSHandle<JSArray> JSArray::CreateArrayFromList(JSThread *thread, const JSHandle<TaggedArray> &elements) 406{ 407 // Assert: elements is a List whose elements are all ECMAScript language values. 408 // 2. Let array be ArrayCreate(0) (see 9.4.2.2). 409 uint32_t length = elements->GetLength(); 410 411 // 4. For each element e of elements 412 auto env = thread->GetEcmaVM()->GetGlobalEnv(); 413 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); 414 JSHandle<JSFunction> arrayFunc(env->GetArrayFunction()); 415 JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(arrayFunc); 416 obj->GetJSHClass()->SetExtensible(true); 417 JSArray::Cast(*obj)->SetArrayLength(thread, length); 418 419 obj->SetElements(thread, elements); 420 JSHandle<JSArray> arr(obj); 421 JSHClass::TransitToElementsKind(thread, arr, ElementsKind::GENERIC); 422 423 return arr; 424} 425 426// used for array contructor with (...items) 427JSHandle<JSArray> JSArray::CreateArrayFromList(JSThread *thread, const JSHandle<JSTaggedValue> &newtarget, 428 const JSHandle<TaggedArray> &elements) 429{ 430 // Assert: elements is a List whose elements are all ECMAScript language values. 431 uint32_t length = elements->GetLength(); 432 433 // create arr object 434 auto env = thread->GetEcmaVM()->GetGlobalEnv(); 435 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); 436 JSHandle<JSFunction> arrayFunc(env->GetArrayFunction()); 437 JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(arrayFunc, newtarget); 438 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread); 439 obj->GetJSHClass()->SetExtensible(true); 440 441 // set elements with initItems 442 JSHandle<JSArray> arr(obj); 443 arr->SetArrayLength(thread, length); 444 obj->SetElements(thread, elements); 445 446 return arr; 447} 448 449JSHandle<JSTaggedValue> JSArray::FastGetPropertyByValue(JSThread *thread, const JSHandle<JSTaggedValue> &obj, 450 uint32_t index) 451{ 452 auto result = ObjectFastOperator::FastGetPropertyByIndex(thread, obj.GetTaggedValue(), index); 453 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread); 454 return JSHandle<JSTaggedValue>(thread, result); 455} 456 457JSHandle<JSTaggedValue> JSArray::FastGetPropertyByValue(JSThread *thread, const JSHandle<JSTaggedValue> &obj, 458 const JSHandle<JSTaggedValue> &key) 459{ 460 auto result = ObjectFastOperator::FastGetPropertyByValue(thread, obj.GetTaggedValue(), key.GetTaggedValue()); 461 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread); 462 return JSHandle<JSTaggedValue>(thread, result); 463} 464 465bool JSArray::FastSetPropertyByValue(JSThread *thread, const JSHandle<JSTaggedValue> &obj, uint32_t index, 466 const JSHandle<JSTaggedValue> &value) 467{ 468 return ObjectFastOperator::FastSetPropertyByIndex(thread, obj.GetTaggedValue(), index, value.GetTaggedValue()); 469} 470 471bool JSArray::FastSetPropertyByValue(JSThread *thread, const JSHandle<JSTaggedValue> &obj, 472 const JSHandle<JSTaggedValue> &key, const JSHandle<JSTaggedValue> &value) 473{ 474 return ObjectFastOperator::FastSetPropertyByValue(thread, obj.GetTaggedValue(), key.GetTaggedValue(), 475 value.GetTaggedValue()); 476} 477 478bool JSArray::TryFastCreateDataProperty(JSThread *thread, const JSHandle<JSObject> &obj, uint32_t index, 479 const JSHandle<JSTaggedValue> &value, SCheckMode sCheckMode) 480{ 481 JSHandle<JSTaggedValue> objVal(obj); 482 if (!objVal->IsStableJSArray(thread)) { 483 // if JSArray is DictionaryMode goto slowPath 484 return JSObject::CreateDataPropertyOrThrow(thread, obj, index, value, sCheckMode); 485 } 486 487 uint32_t capacity = TaggedArray::Cast(obj->GetElements())->GetLength(); 488 ElementsKind kind = obj->GetJSHClass()->GetElementsKind(); 489 uint32_t len = JSHandle<JSArray>::Cast(obj)->GetArrayLength(); 490 if (index > len || kind != ElementsKind::HOLE_TAGGED) { 491 // goto slowPath 492 return JSObject::CreateDataPropertyOrThrow(thread, obj, index, value, sCheckMode); 493 } 494 495 if (index == len) { 496 // append situation 497 if (!IsArrayLengthWritable(thread, obj)) { 498 THROW_TYPE_ERROR_AND_RETURN(thread, "UnWritable ArrayLength", false); 499 } 500 501 uint32_t newLen = index + 1; 502 if (newLen > capacity) { 503 // needs to expand the capacity 504 return JSObject::CreateDataPropertyOrThrow(thread, obj, index, value, sCheckMode); 505 } 506 JSHandle<JSArray>::Cast(obj)->SetArrayLength(thread, newLen); 507 } 508 509 TaggedArray::Cast(obj->GetElements())->Set(thread, index, value); 510 return true; 511} 512 513// ecma2024 23.1.3.20 Array.prototype.sort(comparefn) 514JSTaggedValue JSArray::Sort(JSThread *thread, const JSHandle<JSTaggedValue> &obj, const JSHandle<JSTaggedValue> &fn) 515{ 516 ASSERT(fn->IsUndefined() || fn->IsCallable()); 517 // 3. Let len be ?LengthOfArrayLike(obj). 518 int64_t len = ArrayHelper::GetArrayLength(thread, obj); 519 // ReturnIfAbrupt(len). 520 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); 521 // If len is 0 or 1, no need to sort 522 if (len == 0 || len == 1) { 523 return obj.GetTaggedValue(); 524 } 525 526 // 4. Let SortCompare be a new Abstract Closure with parameters (x, y) that captures comparefn and performs 527 // the following steps when called: 528 // a. Return ? CompareArrayElements(x, y, comparefn). 529 // 5. Let sortedList be ? SortIndexedProperties(O, len, SortCompare, SKIP-HOLES). 530 JSHandle<TaggedArray> sortedList = 531 ArrayHelper::SortIndexedProperties(thread, obj, len, fn, base::HolesType::SKIP_HOLES); 532 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); 533 // 6. Let itemCount be the number of elements in sortedList. 534 uint32_t itemCount = sortedList->GetLength(); 535 536 // 7. Let j be 0. 537 uint32_t j = 0; 538 // 8. Repeat, while j < itemCount, 539 // a. Perform ! Set(obj, ! ToString((j)), sortedList[j], true). 540 // b. Set j to j + 1. 541 JSMutableHandle<JSTaggedValue> item(thread, JSTaggedValue::Undefined()); 542 while (j < itemCount) { 543 item.Update(sortedList->Get(j)); 544 JSArray::FastSetPropertyByValue(thread, obj, j, item); 545 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); 546 ++j; 547 } 548 // 9. NOTE: The call to SortIndexedProperties in step 5 uses SKIP-HOLES.The remaining indices are deleted to 549 // preserve the number of holes that were detected and excluded from the sort. 550 // 10. Repeat, while j < len, 551 // a. Perform ? DeletePropertyOrThrow(obj, ! ToString((j))). 552 // b. Set j to j + 1. 553 while (j < len) { 554 item.Update(JSTaggedValue(j)); 555 JSTaggedValue::DeletePropertyOrThrow(thread, obj, item); 556 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); 557 ++j; 558 } 559 560 return obj.GetTaggedValue(); 561} 562 563void JSArray::SortElements(JSThread *thread, const JSHandle<TaggedArray> &elements, const JSHandle<JSTaggedValue> &fn) 564{ 565 ASSERT(fn->IsUndefined() || fn->IsCallable()); 566 567 uint32_t len = elements->GetLength(); 568 // 64: if the elements is more than 64, use merge-sort algorithm. 569 if (len < 64) { 570 SortElementsByInsertionSort(thread, elements, len, fn); 571 } else { 572 SortElementsByMergeSort(thread, elements, fn, 0, len - 1); 573 } 574} 575 576void JSArray::SortElementsByMergeSort(JSThread *thread, const JSHandle<TaggedArray> &elements, 577 const JSHandle<JSTaggedValue> &fn, int64_t startIdx, int64_t endIdx) 578{ 579 if (startIdx >= endIdx) 580 return; 581 582 int64_t middleIdx = startIdx + (endIdx - startIdx) / 2; // 2: half 583 SortElementsByMergeSort(thread, elements, fn, startIdx, middleIdx); 584 SortElementsByMergeSort(thread, elements, fn, middleIdx + 1, endIdx); 585 MergeSortedElements(thread, elements, fn, startIdx, middleIdx, endIdx); 586} 587 588void JSArray::MergeSortedElements(JSThread *thread, const JSHandle<TaggedArray> &elements, 589 const JSHandle<JSTaggedValue> &fn, int64_t startIdx, 590 int64_t middleIdx, int64_t endIdx) 591{ 592 int64_t leftLength = middleIdx - startIdx + 1; 593 int64_t rightLength = endIdx - middleIdx; 594 595 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); 596 JSHandle<TaggedArray> leftArray = factory->NewTaggedArray(leftLength); 597 JSHandle<TaggedArray> rightArray = factory->NewTaggedArray(rightLength); 598 599 for (int64_t i = 0; i < leftLength; i++) { 600 leftArray->Set(thread, i, elements->Get(startIdx + i)); 601 } 602 for (int64_t j = 0; j < rightLength; j++) { 603 rightArray->Set(thread, j, elements->Get(static_cast<int32_t>(middleIdx + 1 + j))); 604 } 605 606 int64_t i = 0; 607 int64_t j = 0; 608 int64_t k = startIdx; 609 JSMutableHandle<JSTaggedValue> leftValue(thread, JSTaggedValue::Undefined()); 610 JSMutableHandle<JSTaggedValue> rightValue(thread, JSTaggedValue::Undefined()); 611 while (i < leftLength && j < rightLength) { 612 leftValue.Update(leftArray->Get(i)); 613 rightValue.Update(rightArray->Get(j)); 614 int64_t compareRet = base::ArrayHelper::SortCompare(thread, fn, leftValue, rightValue); 615 RETURN_IF_ABRUPT_COMPLETION(thread); 616 if (compareRet <= 0) { 617 elements->Set(thread, k, leftArray->Get(i)); 618 i++; 619 } else { 620 elements->Set(thread, k, rightArray->Get(j)); 621 j++; 622 } 623 k++; 624 } 625 626 while (i < leftLength) { 627 elements->Set(thread, k, leftArray->Get(i)); 628 i++; 629 k++; 630 } 631 632 while (j < rightLength) { 633 elements->Set(thread, k, rightArray->Get(j)); 634 j++; 635 k++; 636 } 637} 638 639void JSArray::SortElementsByInsertionSort(JSThread *thread, const JSHandle<TaggedArray> &elements, uint32_t len, 640 const JSHandle<JSTaggedValue> &fn) 641{ 642 if (len <= 1) 643 return; 644 645 JSMutableHandle<JSTaggedValue> presentValue(thread, JSTaggedValue::Undefined()); 646 JSMutableHandle<JSTaggedValue> middleValue(thread, JSTaggedValue::Undefined()); 647 JSMutableHandle<JSTaggedValue> previousValue(thread, JSTaggedValue::Undefined()); 648 for (uint32_t i = 1; i < len; i++) { 649 uint32_t beginIndex = 0; 650 uint32_t endIndex = i; 651 presentValue.Update(elements->Get(i)); 652 while (beginIndex < endIndex) { 653 uint32_t middleIndex = (beginIndex + endIndex) / 2; // 2 : half 654 middleValue.Update(elements->Get(middleIndex)); 655 double compareResult = base::ArrayHelper::SortCompare(thread, fn, middleValue, presentValue); 656 RETURN_IF_ABRUPT_COMPLETION(thread); 657 if (compareResult > 0) { 658 endIndex = middleIndex; 659 } else { 660 beginIndex = middleIndex + 1; 661 } 662 } 663 664 if (endIndex >= 0 && endIndex < i) { 665 for (uint32_t j = i; j > endIndex; j--) { 666 previousValue.Update(elements->Get(j - 1)); 667 elements->Set(thread, j, previousValue); 668 } 669 elements->Set(thread, endIndex, presentValue); 670 } 671 } 672} 673 674void JSArray::SortElementsByObject(JSThread *thread, const JSHandle<JSObject> &thisObjHandle, 675 const JSHandle<JSTaggedValue> &fn) 676{ 677 ASSERT(fn->IsUndefined() || fn->IsCallable()); 678 679 JSMutableHandle<JSTaggedValue> presentValue(thread, JSTaggedValue::Undefined()); 680 JSMutableHandle<JSTaggedValue> middleValue(thread, JSTaggedValue::Undefined()); 681 JSMutableHandle<JSTaggedValue> previousValue(thread, JSTaggedValue::Undefined()); 682 uint32_t len = ElementAccessor::GetElementsLength(thisObjHandle); 683 for (uint32_t i = 1; i < len; i++) { 684 uint32_t beginIndex = 0; 685 uint32_t endIndex = i; 686 presentValue.Update(ElementAccessor::Get(thisObjHandle, i)); 687 while (beginIndex < endIndex) { 688 uint32_t middleIndex = (beginIndex + endIndex) / 2; // 2 : half 689 middleValue.Update(ElementAccessor::Get(thisObjHandle, middleIndex)); 690 int32_t compareResult = base::ArrayHelper::SortCompare(thread, fn, middleValue, presentValue); 691 RETURN_IF_ABRUPT_COMPLETION(thread); 692 if (compareResult > 0) { 693 endIndex = middleIndex; 694 } else { 695 beginIndex = middleIndex + 1; 696 } 697 } 698 699 if (endIndex >= 0 && endIndex < i) { 700 for (uint32_t j = i; j > endIndex; j--) { 701 previousValue.Update(ElementAccessor::Get(thisObjHandle, j - 1)); 702 ElementAccessor::Set(thread, thisObjHandle, j, previousValue, false); 703 } 704 ElementAccessor::Set(thread, thisObjHandle, endIndex, presentValue, false); 705 } 706 } 707} 708 709bool JSArray::IncludeInSortedValue(JSThread *thread, const JSHandle<JSTaggedValue> &obj, 710 const JSHandle<JSTaggedValue> &value) 711{ 712 ASSERT(obj->IsJSArray()); 713 JSHandle<JSArray> arrayObj = JSHandle<JSArray>::Cast(obj); 714 int32_t length = static_cast<int32_t>(arrayObj->GetArrayLength()); 715 if (length == 0) { 716 return false; 717 } 718 int32_t left = 0; 719 int32_t right = length - 1; 720 while (left <= right) { 721 int32_t middle = (left + right) / 2; 722 JSHandle<JSTaggedValue> vv = JSArray::FastGetPropertyByValue(thread, obj, middle); 723 ComparisonResult res = JSTaggedValue::Compare(thread, vv, value); 724 if (res == ComparisonResult::EQUAL) { 725 return true; 726 } else if (res == ComparisonResult::LESS) { 727 left = middle + 1; 728 } else { 729 right = middle - 1; 730 } 731 } 732 return false; 733} 734 735JSHandle<TaggedArray> JSArray::ToTaggedArray(JSThread *thread, const JSHandle<JSTaggedValue> &obj) 736{ 737 ASSERT(obj->IsJSArray()); 738 JSHandle<JSArray> arrayObj = JSHandle<JSArray>::Cast(obj); 739 uint32_t length = arrayObj->GetArrayLength(); 740 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); 741 JSHandle<TaggedArray> taggedArray = factory->NewTaggedArray(length); 742 for (uint32_t idx = 0; idx < length; idx++) { 743 JSHandle<JSTaggedValue> vv = JSArray::FastGetPropertyByValue(thread, obj, idx); 744 taggedArray->Set(thread, idx, vv); 745 } 746 return taggedArray; 747} 748 749void JSArray::CheckAndCopyArray(const JSThread *thread, JSHandle<JSArray> obj) 750{ 751 JSHandle<TaggedArray> arr(thread, obj->GetElements()); 752 // Check whether array is shared in the nonmovable space before set properties and elements. 753 // If true, then really copy array in the semi space. 754 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); 755 if (arr.GetTaggedValue().IsCOWArray()) { 756 auto newArray = factory->CopyArray(arr, arr->GetLength(), arr->GetLength(), 757 JSTaggedValue::Hole(), MemSpaceType::SEMI_SPACE); 758 obj->SetElements(thread, newArray.GetTaggedValue()); 759 } 760 JSHandle<TaggedArray> prop(thread, obj->GetProperties()); 761 if (prop.GetTaggedValue().IsCOWArray()) { 762 auto newProps = factory->CopyArray(prop, prop->GetLength(), prop->GetLength(), 763 JSTaggedValue::Hole(), MemSpaceType::SEMI_SPACE); 764 obj->SetProperties(thread, newProps.GetTaggedValue()); 765 } 766} 767} // namespace panda::ecmascript 768