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