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