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