• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2021-2022 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/base/array_helper.h"
17 
18 #include "ecmascript/base/typed_array_helper-inl.h"
19 #include "ecmascript/ecma_macros.h"
20 #include "ecmascript/ecma_vm.h"
21 #include "ecmascript/global_env.h"
22 #include "ecmascript/interpreter/interpreter.h"
23 #include "ecmascript/js_array.h"
24 #include "ecmascript/js_hclass.h"
25 #include "ecmascript/js_tagged_number.h"
26 #include "ecmascript/js_tagged_value-inl.h"
27 #include "ecmascript/object_fast_operator-inl.h"
28 
29 namespace panda::ecmascript::base {
GetStartIndex(JSThread * thread,const JSHandle<JSTaggedValue> & startIndexHandle,int64_t length)30 int64_t ArrayHelper::GetStartIndex(JSThread *thread, const JSHandle<JSTaggedValue> &startIndexHandle,
31                                    int64_t length)
32 {
33     // Common procedure to clamp fromIndexValue to the range [0, length].
34     // For integer case, conditional selection instructions (csel in ARM, cmov in x86, etc.)
35     // may be utilized by the compiler to minimize branching.
36     auto doClamp = [length](auto fromIndexValue) -> int64_t {
37         if (LIKELY(fromIndexValue >= 0)) {
38             // Including the case where fromIndexValue == Infinity
39             return (fromIndexValue >= length) ? length : static_cast<int64_t>(fromIndexValue);
40         }
41         auto plusLength = fromIndexValue + length;
42         if (plusLength >= 0) {
43             return static_cast<int64_t>(plusLength);
44         }
45         return 0; // Including the case where fromIndexValue == -Infinity
46     };
47     if (LIKELY(startIndexHandle->IsInt())) {
48         // Fast path: startIndexHandle is tagged int32.
49         return doClamp(startIndexHandle->GetInt());
50     }
51     // Slow path: startIndexHandle is targged double, or type conversion is involved.
52     JSTaggedNumber fromIndexTemp = JSTaggedValue::ToNumber(thread, startIndexHandle);
53     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, length);
54 
55     double fromIndexValue = base::NumberHelper::TruncateDouble(fromIndexTemp.GetNumber()); // NaN -> 0
56     return doClamp(fromIndexValue);
57 }
58 
GetStartIndexFromArgs(JSThread * thread,EcmaRuntimeCallInfo * argv,uint32_t argIndex,int64_t length)59 int64_t ArrayHelper::GetStartIndexFromArgs(JSThread *thread, EcmaRuntimeCallInfo *argv,
60                                            uint32_t argIndex, int64_t length)
61 {
62     uint32_t argc = argv->GetArgsNumber();
63     if (argc <= argIndex) {
64         return 0;
65     }
66     JSHandle<JSTaggedValue> arg = base::BuiltinsBase::GetCallArg(argv, argIndex);
67     return GetStartIndex(thread, arg, length);
68 }
69 
GetLastStartIndex(JSThread * thread,const JSHandle<JSTaggedValue> & startIndexHandle,int64_t length)70 int64_t ArrayHelper::GetLastStartIndex(JSThread *thread, const JSHandle<JSTaggedValue> &startIndexHandle,
71                                        int64_t length)
72 {
73     // Common procedure to clamp fromIndexValue to the range [-1, length-1].
74     auto doClamp = [length](auto fromIndexValue) -> int64_t {
75         if (LIKELY(fromIndexValue >= 0)) {
76             // Including the case where fromIndexValue == Infinity
77             return (length - 1 < fromIndexValue) ? (length - 1) : static_cast<int64_t>(fromIndexValue);
78         }
79         auto plusLength = fromIndexValue + length;
80         if (plusLength >= 0) {
81             return static_cast<int64_t>(plusLength);
82         }
83         return -1; // Including the case where fromIndexValue == -Infinity
84     };
85     if (LIKELY(startIndexHandle->IsInt())) {
86         // Fast path: startIndexHandle is tagged int32.
87         return doClamp(startIndexHandle->GetInt());
88     }
89     // Slow path: startIndexHandle is targged double, or type conversion is involved.
90     JSTaggedNumber fromIndexTemp = JSTaggedValue::ToNumber(thread, startIndexHandle);
91     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, -1);
92 
93     double fromIndexValue = base::NumberHelper::TruncateDouble(fromIndexTemp.GetNumber()); // NaN -> 0
94     return doClamp(fromIndexValue);
95 }
96 
GetLastStartIndexFromArgs(JSThread * thread,EcmaRuntimeCallInfo * argv,uint32_t argIndex,int64_t length)97 int64_t ArrayHelper::GetLastStartIndexFromArgs(JSThread *thread, EcmaRuntimeCallInfo *argv,
98                                                uint32_t argIndex, int64_t length)
99 {
100     uint32_t argc = argv->GetArgsNumber();
101     if (argc <= argIndex) {
102         return length - 1;
103     }
104     JSHandle<JSTaggedValue> arg = base::BuiltinsBase::GetCallArg(argv, argIndex);
105     return GetLastStartIndex(thread, arg, length);
106 }
107 
ElementIsStrictEqualTo(JSThread * thread,const JSHandle<JSTaggedValue> & thisObjVal,const JSHandle<JSTaggedValue> & keyHandle,const JSHandle<JSTaggedValue> & target)108 bool ArrayHelper::ElementIsStrictEqualTo(JSThread *thread, const JSHandle<JSTaggedValue> &thisObjVal,
109                                          const JSHandle<JSTaggedValue> &keyHandle,
110                                          const JSHandle<JSTaggedValue> &target)
111 {
112     bool exists = thisObjVal->IsTypedArray() || JSTaggedValue::HasProperty(thread, thisObjVal, keyHandle);
113     if (thread->HasPendingException() || !exists) {
114         return false;
115     }
116     JSHandle<JSTaggedValue> valueHandle = JSArray::FastGetPropertyByValue(thread, thisObjVal, keyHandle);
117     if (thread->HasPendingException()) {
118         return false;
119     }
120     return JSTaggedValue::StrictEqual(thread, target, valueHandle);
121 }
122 
IsConcatSpreadable(JSThread * thread,const JSHandle<JSTaggedValue> & obj)123 bool ArrayHelper::IsConcatSpreadable(JSThread *thread, const JSHandle<JSTaggedValue> &obj)
124 {
125     // 1. If Type(O) is not Object, return false.
126     if (!obj->IsECMAObject()) {
127         return false;
128     }
129 
130     // 2. Let spreadable be Get(O, @@isConcatSpreadable).
131     auto ecmaVm = thread->GetEcmaVM();
132     JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
133     JSHandle<JSTaggedValue> isConcatsprKey = env->GetIsConcatSpreadableSymbol();
134     JSTaggedValue spreadable = ObjectFastOperator::FastGetPropertyByValue(thread, obj.GetTaggedValue(),
135                                                                           isConcatsprKey.GetTaggedValue());
136     // 3. ReturnIfAbrupt(spreadable).
137     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
138 
139     // 4. If spreadable is not undefined, return ToBoolean(spreadable).
140     if (!spreadable.IsUndefined()) {
141         return spreadable.ToBoolean();
142     }
143 
144     // 5. Return IsArray(O).
145     return obj->IsArray(thread);
146 }
147 
148 // must use 'double' as return type, for sort result may double.
149 // let arr = [1,2,3,4,5,6]; arr.sort(() => Math.random() - 0.5);
SortCompare(JSThread * thread,const JSHandle<JSTaggedValue> & callbackfnHandle,const JSHandle<JSTaggedValue> & valueX,const JSHandle<JSTaggedValue> & valueY)150 double ArrayHelper::SortCompare(JSThread *thread, const JSHandle<JSTaggedValue> &callbackfnHandle,
151                                 const JSHandle<JSTaggedValue> &valueX, const JSHandle<JSTaggedValue> &valueY)
152 {
153     // 1. If x and y are both undefined, return +0.
154     if (valueX->IsHole()) {
155         if (valueY->IsHole()) {
156             return 0;
157         }
158         return 1;
159     }
160     if (valueY->IsHole()) {
161         return -1;
162     }
163     if (valueX->IsUndefined()) {
164         if (valueY->IsUndefined()) {
165             return 0;
166         }
167         // 2. If x is undefined, return 1.
168         return 1;
169     }
170     // 3. If y is undefined, return -1.
171     if (valueY->IsUndefined()) {
172         return -1;
173     }
174     // 4. If the argument comparefn is not undefined, then
175     // a. Let v be ToNumber(Call(comparefn, undefined, «x, y»)).
176     // b. ReturnIfAbrupt(v).
177     // c. If v is NaN, return +0.
178     // d. Return v.
179     if (!callbackfnHandle->IsUndefined() && !callbackfnHandle->IsNull()) {
180         JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
181         EcmaRuntimeCallInfo *info =
182             EcmaInterpreter::NewRuntimeCallInfo(thread, callbackfnHandle, undefined, undefined, 2); // 2: «x, y»
183         RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
184         info->SetCallArg(valueX.GetTaggedValue(), valueY.GetTaggedValue());
185         JSTaggedValue callResult = JSFunction::Call(info);
186         RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
187         if (callResult.IsInt()) {
188             return callResult.GetInt();
189         }
190         JSHandle<JSTaggedValue> testResult(thread, callResult);
191         JSTaggedNumber v = JSTaggedValue::ToNumber(thread, testResult);
192         RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
193         double value = v.GetNumber();
194         if (std::isnan(value)) {
195             return +0;
196         }
197         return value;
198     }
199     // 5. Let xString be ToString(x).
200     // 6. ReturnIfAbrupt(xString).
201     // 7. Let yString be ToString(y).
202     // 8. ReturnIfAbrupt(yString).
203     // 9. If xString < yString, return -1.
204     // 10. If xString > yString, return 1.
205     // 11. Return +0.
206     if (valueX->IsInt() && valueY->IsInt()) {
207         return JSTaggedValue::IntLexicographicCompare(valueX.GetTaggedValue(), valueY.GetTaggedValue());
208     }
209     if (valueX->IsString() && valueY->IsString()) {
210         return EcmaStringAccessor::Compare(thread->GetEcmaVM(),
211             JSHandle<EcmaString>(valueX), JSHandle<EcmaString>(valueY));
212     }
213     JSHandle<JSTaggedValue> xValueHandle(JSTaggedValue::ToString(thread, valueX));
214     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
215     JSHandle<JSTaggedValue> yValueHandle(JSTaggedValue::ToString(thread, valueY));
216     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
217     ComparisonResult compareResult = JSTaggedValue::Compare(thread, xValueHandle, yValueHandle);
218     if (compareResult == ComparisonResult::GREAT) {
219         return 1;
220     }
221     if (compareResult == ComparisonResult::LESS) {
222         return -1;
223     }
224     return 0;
225 }
226 
StringSortCompare(JSThread * thread,const JSHandle<JSTaggedValue> & valueX,const JSHandle<JSTaggedValue> & valueY)227 double ArrayHelper::StringSortCompare(JSThread *thread, const JSHandle<JSTaggedValue> &valueX,
228                                       const JSHandle<JSTaggedValue> &valueY)
229 {
230     ASSERT(valueX->IsString());
231     ASSERT(valueY->IsString());
232     // 9. If xString < yString, return -1.
233     // 10. If xString > yString, return 1.
234     // 11. Return +0.
235     auto xHandle = JSHandle<EcmaString>(valueX);
236     auto yHandle = JSHandle<EcmaString>(valueY);
237     int result = EcmaStringAccessor::Compare(thread->GetEcmaVM(), xHandle, yHandle);
238     if (result < 0) {
239         return -1;
240     }
241     if (result > 0) {
242         return 1;
243     }
244     return 0;
245 }
246 
GetLength(JSThread * thread,const JSHandle<JSTaggedValue> & thisHandle)247 int64_t ArrayHelper::GetLength(JSThread *thread, const JSHandle<JSTaggedValue> &thisHandle)
248 {
249     if (thisHandle->IsJSArray()) {
250         return JSArray::Cast(thisHandle->GetTaggedObject())->GetArrayLength();
251     }
252     if (thisHandle->IsTypedArray()) {
253         return JSHandle<JSTypedArray>::Cast(thisHandle)->GetArrayLength();
254     }
255     JSHandle<JSTaggedValue> lengthKey = thread->GlobalConstants()->GetHandledLengthString();
256     JSHandle<JSTaggedValue> lenResult = JSTaggedValue::GetProperty(thread, thisHandle, lengthKey).GetValue();
257     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
258     JSTaggedNumber len = JSTaggedValue::ToLength(thread, lenResult);
259     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
260     return len.GetNumber();
261 }
262 
GetArrayLength(JSThread * thread,const JSHandle<JSTaggedValue> & thisHandle)263 int64_t ArrayHelper::GetArrayLength(JSThread *thread, const JSHandle<JSTaggedValue> &thisHandle)
264 {
265     if (thisHandle->IsJSArray()) {
266         return JSArray::Cast(thisHandle->GetTaggedObject())->GetArrayLength();
267     }
268     JSHandle<JSTaggedValue> lengthKey = thread->GlobalConstants()->GetHandledLengthString();
269     JSHandle<JSTaggedValue> lenResult = JSTaggedValue::GetProperty(thread, thisHandle, lengthKey).GetValue();
270     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
271     JSTaggedNumber len = JSTaggedValue::ToLength(thread, lenResult);
272     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
273     return len.GetNumber();
274 }
275 
FlattenIntoArray(JSThread * thread,const JSHandle<JSObject> & newArrayHandle,const JSHandle<JSTaggedValue> & thisObjVal,const FlattenArgs & args,const JSHandle<JSTaggedValue> & mapperFunctionHandle,const JSHandle<JSTaggedValue> & thisArg)276 JSTaggedValue ArrayHelper::FlattenIntoArray(JSThread *thread, const JSHandle<JSObject> &newArrayHandle,
277                                             const JSHandle<JSTaggedValue> &thisObjVal, const FlattenArgs &args,
278                                             const JSHandle<JSTaggedValue> &mapperFunctionHandle,
279                                             const JSHandle<JSTaggedValue> &thisArg)
280 {
281     if (thread->DoAsmStackOverflowCheck()) {
282         return JSTaggedValue::Exception();
283     }
284     // 1. Assert: Type(target) is Object.
285     // 2. Assert: Type(source) is Object.
286     // 3. Assert: If mapperFunction is present, then ! IsCallable(mapperFunction) is true,
287     //    thisArg is present, and depth is 1.
288     ASSERT(mapperFunctionHandle->IsUndefined() || mapperFunctionHandle->IsCallable() ||
289            (!thisArg->IsUndefined() && args.depth == 1));
290     // 4. Let targetIndex be start.
291     // 5. Let sourceIndex be +0!.
292     FlattenArgs tempArgs;
293     tempArgs.start = args.start;
294     int64_t sourceIndex = 0;
295     JSMutableHandle<JSTaggedValue> p(thread, JSTaggedValue::Undefined());
296     JSMutableHandle<JSTaggedValue> element(thread, JSTaggedValue::Undefined());
297     JSMutableHandle<JSTaggedValue> targetIndexHandle(thread, JSTaggedValue::Undefined());
298     JSMutableHandle<JSTaggedValue> sourceIndexHandle(thread, JSTaggedValue::Undefined());
299     JSHandle<EcmaString> sourceIndexStr;
300     // 6. Repeat, while (sourceIndex) < sourceLen,
301     //     a. Let P be ! ToString(sourceIndex).
302     //     b. Let exists be ? HasProperty(source, P).
303     //     c. If exists is true, then
304     //         i. Let element be ? Get(source, P).
305     //     ii. If mapperFunction is present, then
306     //             1. Set element to ? Call(mapperFunction, thisArg, « element, sourceIndex, source »).
307     //     iii. Let shouldFlatten be false.
308     //     iv. If depth > 0, then
309     //             1. Set shouldFlatten to ? IsArray(element).
310     //         v. If shouldFlatten is true, then
311     //             1. If depth is +∞, let newDepth be +∞.
312     //             2. Else, let newDepth be depth - 1.
313     //             3. Let elementLen be ? LengthOfArrayLike(element).
314     //             4. Set targetIndex to ? FlattenIntoArray(target, element, elementLen, targetIndex, newDepth).
315     //     vi. Else,
316     //             1. If targetIndex ≥ 2^53 - 1, throw a TypeError exception.
317     //             2. Perform ? CreateDataPropertyOrThrow(target, ! ToString(!(targetIndex)), element).
318     //             3. Set targetIndex to targetIndex + 1.
319     //     d. Set sourceIndex to sourceIndex + 1!.
320     while (sourceIndex < args.sourceLen) {
321         sourceIndexHandle.Update(JSTaggedValue(sourceIndex));
322         sourceIndexStr = JSTaggedValue::ToString(thread, sourceIndexHandle);
323         RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
324         p.Update(sourceIndexStr.GetTaggedValue());
325         bool exists = JSTaggedValue::HasProperty(thread, thisObjVal, p);
326         RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
327         if (exists) {
328             element.Update(JSArray::FastGetPropertyByValue(thread, thisObjVal, p).GetTaggedValue());
329             RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
330             if (!mapperFunctionHandle->IsUndefined()) {
331                 const int32_t argsLength = 3; // 3: « element, sourceIndex, source »
332                 JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
333                 EcmaRuntimeCallInfo *info =
334                     EcmaInterpreter::NewRuntimeCallInfo(thread, mapperFunctionHandle, thisArg, undefined, argsLength);
335                 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
336                 info->SetCallArg(element.GetTaggedValue(), p.GetTaggedValue(), thisObjVal.GetTaggedValue());
337                 JSTaggedValue obj = JSFunction::Call(info);
338                 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
339                 element.Update(obj);
340             }
341             bool shouldFlatten = false;
342             if (args.depth > 0) {
343                 shouldFlatten = element->IsArray(thread);
344                 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
345             }
346             if (shouldFlatten) {
347                 tempArgs.depth = args.depth > POSITIVE_INFINITY ? POSITIVE_INFINITY : args.depth - 1;
348                 tempArgs.sourceLen = ArrayHelper::GetLength(thread, element);
349                 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
350                 JSTaggedValue TargetIndexObj = FlattenIntoArray(thread, newArrayHandle, element, tempArgs,
351                                                                 thread->GlobalConstants()->GetHandledUndefined(),
352                                                                 thread->GlobalConstants()->GetHandledUndefined());
353                 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
354                 targetIndexHandle.Update(TargetIndexObj);
355                 JSTaggedNumber targetIndexTemp = JSTaggedValue::ToNumber(thread, targetIndexHandle);
356                 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
357                 tempArgs.start = base::NumberHelper::TruncateDouble(targetIndexTemp.GetNumber());
358             } else {
359                 if (tempArgs.start > base::MAX_SAFE_INTEGER) {
360                     THROW_TYPE_ERROR_AND_RETURN(thread, "out of range.", JSTaggedValue::Exception());
361                 }
362                 sourceIndexHandle.Update(JSTaggedValue(tempArgs.start));
363                 sourceIndexStr = JSTaggedValue::ToString(thread, sourceIndexHandle);
364                 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
365                 targetIndexHandle.Update(sourceIndexStr.GetTaggedValue());
366                 JSObject::CreateDataPropertyOrThrow(thread, newArrayHandle, targetIndexHandle, element);
367                 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
368                 tempArgs.start++;
369             }
370         }
371         sourceIndex++;
372     }
373     // 7. Return targetIndex.
374     return BuiltinsBase::GetTaggedDouble(tempArgs.start);
375 }
376 
SortIndexedProperties(JSThread * thread,const JSHandle<JSTaggedValue> & thisObj,int64_t len,const JSHandle<JSTaggedValue> & callbackFnHandle,HolesType holes)377 JSHandle<TaggedArray> ArrayHelper::SortIndexedProperties(JSThread *thread, const JSHandle<JSTaggedValue> &thisObj,
378                                                          int64_t len, const JSHandle<JSTaggedValue> &callbackFnHandle,
379                                                          HolesType holes)
380 {
381     // 1. Let items be a new empty List.
382     JSHandle<TaggedArray> items(thread->GetEcmaVM()->GetFactory()->NewTaggedArray(len));
383     // 2. Let k be 0.
384     int64_t k = 0;
385     // 3. Repeat, while k < len,
386     //     a. Let Pk be ! ToString(��(k)).
387     //     b. If holes is skip-holes, then
388     //         i. Let kRead be ? HasProperty(obj, Pk).
389     //     c. Else,
390     //         i. Assert: holes is read-through-holes.
391     //         ii. Let kRead be true.
392     //     d. If kRead is true, then
393     //         i. Let kValue be ? Get(obj, Pk).
394     //         ii. Append kValue to items.
395     //     e. Set k to k + 1.
396     bool kRead = false;
397     JSMutableHandle<JSTaggedValue> pk(thread, JSTaggedValue::Undefined());
398 
399     int64_t index = 0;
400     while (k < len) {
401         if (holes == HolesType::SKIP_HOLES) {
402             pk.Update(JSTaggedValue(k));
403             kRead = JSTaggedValue::HasProperty(thread, thisObj, pk);
404             RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, items);
405         } else {
406             ASSERT(holes == HolesType::READ_THROUGH_HOLES);
407             kRead = true;
408         }
409         if (kRead) {
410             JSHandle<JSTaggedValue> kValue = JSArray::FastGetPropertyByValue(thread, thisObj, k);
411             RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, items);
412             items->Set(thread, index++, kValue.GetTaggedValue());
413         }
414         ++k;
415     }
416     if (index < k) {
417         items->Trim(thread, index);
418     }
419     // 4. Sort items using an implementation-defined sequence of calls to SortCompare.
420     // If any such call returns an abrupt completion,
421     // stop before performing any further calls to SortCompare and return that Completion Record.
422     JSArray::SortElements(thread, items, callbackFnHandle);
423     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, items);
424     // 5. Return items.
425     return items;
426 }
427 }  // namespace panda::ecmascript::base
428