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