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 {
IsConcatSpreadable(JSThread * thread,const JSHandle<JSTaggedValue> & obj)30 bool ArrayHelper::IsConcatSpreadable(JSThread *thread, const JSHandle<JSTaggedValue> &obj)
31 {
32 // 1. If Type(O) is not Object, return false.
33 if (!obj->IsECMAObject()) {
34 return false;
35 }
36
37 // 2. Let spreadable be Get(O, @@isConcatSpreadable).
38 auto ecmaVm = thread->GetEcmaVM();
39 JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
40 JSHandle<JSTaggedValue> isConcatsprKey = env->GetIsConcatSpreadableSymbol();
41 JSTaggedValue spreadable = ObjectFastOperator::FastGetPropertyByValue(thread, obj.GetTaggedValue(),
42 isConcatsprKey.GetTaggedValue());
43 // 3. ReturnIfAbrupt(spreadable).
44 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
45
46 // 4. If spreadable is not undefined, return ToBoolean(spreadable).
47 if (!spreadable.IsUndefined()) {
48 return spreadable.ToBoolean();
49 }
50
51 // 5. Return IsArray(O).
52 return obj->IsArray(thread);
53 }
54
55 // must use 'double' as return type, for sort result may double.
56 // 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)57 double ArrayHelper::SortCompare(JSThread *thread, const JSHandle<JSTaggedValue> &callbackfnHandle,
58 const JSHandle<JSTaggedValue> &valueX, const JSHandle<JSTaggedValue> &valueY)
59 {
60 // 1. If x and y are both undefined, return +0.
61 if (valueX->IsHole()) {
62 if (valueY->IsHole()) {
63 return 0;
64 }
65 return 1;
66 }
67 if (valueY->IsHole()) {
68 return -1;
69 }
70 if (valueX->IsUndefined()) {
71 if (valueY->IsUndefined()) {
72 return 0;
73 }
74 // 2. If x is undefined, return 1.
75 return 1;
76 }
77 // 3. If y is undefined, return -1.
78 if (valueY->IsUndefined()) {
79 return -1;
80 }
81 // 4. If the argument comparefn is not undefined, then
82 // a. Let v be ToNumber(Call(comparefn, undefined, «x, y»)).
83 // b. ReturnIfAbrupt(v).
84 // c. If v is NaN, return +0.
85 // d. Return v.
86 if (!callbackfnHandle->IsUndefined() && !callbackfnHandle->IsNull()) {
87 JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
88 EcmaRuntimeCallInfo *info =
89 EcmaInterpreter::NewRuntimeCallInfo(thread, callbackfnHandle, undefined, undefined, 2); // 2: «x, y»
90 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
91 info->SetCallArg(valueX.GetTaggedValue(), valueY.GetTaggedValue());
92 JSTaggedValue callResult = JSFunction::Call(info);
93 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
94 if (callResult.IsInt()) {
95 return callResult.GetInt();
96 }
97 JSHandle<JSTaggedValue> testResult(thread, callResult);
98 JSTaggedNumber v = JSTaggedValue::ToNumber(thread, testResult);
99 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
100 double value = v.GetNumber();
101 if (std::isnan(value)) {
102 return +0;
103 }
104 return value;
105 }
106 // 5. Let xString be ToString(x).
107 // 6. ReturnIfAbrupt(xString).
108 // 7. Let yString be ToString(y).
109 // 8. ReturnIfAbrupt(yString).
110 // 9. If xString < yString, return -1.
111 // 10. If xString > yString, return 1.
112 // 11. Return +0.
113 JSHandle<JSTaggedValue> xValueHandle(JSTaggedValue::ToString(thread, valueX));
114 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
115 JSHandle<JSTaggedValue> yValueHandle(JSTaggedValue::ToString(thread, valueY));
116 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
117 ComparisonResult compareResult = JSTaggedValue::Compare(thread, xValueHandle, yValueHandle);
118 if (compareResult == ComparisonResult::GREAT) {
119 return 1;
120 }
121 if (compareResult == ComparisonResult::LESS) {
122 return -1;
123 }
124 return 0;
125 }
126
GetLength(JSThread * thread,const JSHandle<JSTaggedValue> & thisHandle)127 int64_t ArrayHelper::GetLength(JSThread *thread, const JSHandle<JSTaggedValue> &thisHandle)
128 {
129 if (thisHandle->IsJSArray()) {
130 return JSArray::Cast(thisHandle->GetTaggedObject())->GetArrayLength();
131 }
132 if (thisHandle->IsTypedArray()) {
133 return JSHandle<JSTypedArray>::Cast(thisHandle)->GetArrayLength();
134 }
135 JSHandle<JSTaggedValue> lengthKey = thread->GlobalConstants()->GetHandledLengthString();
136 JSHandle<JSTaggedValue> lenResult = JSTaggedValue::GetProperty(thread, thisHandle, lengthKey).GetValue();
137 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
138 JSTaggedNumber len = JSTaggedValue::ToLength(thread, lenResult);
139 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
140 return len.GetNumber();
141 }
142
GetArrayLength(JSThread * thread,const JSHandle<JSTaggedValue> & thisHandle)143 int64_t ArrayHelper::GetArrayLength(JSThread *thread, const JSHandle<JSTaggedValue> &thisHandle)
144 {
145 if (thisHandle->IsJSArray()) {
146 return JSArray::Cast(thisHandle->GetTaggedObject())->GetArrayLength();
147 }
148 JSHandle<JSTaggedValue> lengthKey = thread->GlobalConstants()->GetHandledLengthString();
149 JSHandle<JSTaggedValue> lenResult = JSTaggedValue::GetProperty(thread, thisHandle, lengthKey).GetValue();
150 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
151 JSTaggedNumber len = JSTaggedValue::ToLength(thread, lenResult);
152 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
153 return len.GetNumber();
154 }
155
FlattenIntoArray(JSThread * thread,const JSHandle<JSObject> & newArrayHandle,const JSHandle<JSTaggedValue> & thisObjVal,const FlattenArgs & args,const JSHandle<JSTaggedValue> & mapperFunctionHandle,const JSHandle<JSTaggedValue> & thisArg)156 JSTaggedValue ArrayHelper::FlattenIntoArray(JSThread *thread, const JSHandle<JSObject> &newArrayHandle,
157 const JSHandle<JSTaggedValue> &thisObjVal, const FlattenArgs &args,
158 const JSHandle<JSTaggedValue> &mapperFunctionHandle,
159 const JSHandle<JSTaggedValue> &thisArg)
160 {
161 // 1. Assert: Type(target) is Object.
162 // 2. Assert: Type(source) is Object.
163 // 3. Assert: If mapperFunction is present, then ! IsCallable(mapperFunction) is true,
164 // thisArg is present, and depth is 1.
165 ASSERT(mapperFunctionHandle->IsUndefined() || mapperFunctionHandle->IsCallable() ||
166 (!thisArg->IsUndefined() && args.depth == 1));
167 // 4. Let targetIndex be start.
168 // 5. Let sourceIndex be +0!.
169 FlattenArgs tempArgs;
170 tempArgs.start = args.start;
171 int64_t sourceIndex = 0;
172 JSMutableHandle<JSTaggedValue> p(thread, JSTaggedValue::Undefined());
173 JSMutableHandle<JSTaggedValue> element(thread, JSTaggedValue::Undefined());
174 JSMutableHandle<JSTaggedValue> targetIndexHandle(thread, JSTaggedValue::Undefined());
175 JSMutableHandle<JSTaggedValue> sourceIndexHandle(thread, JSTaggedValue::Undefined());
176 JSHandle<EcmaString> sourceIndexStr;
177 // 6. Repeat, while (sourceIndex) < sourceLen,
178 // a. Let P be ! ToString(sourceIndex).
179 // b. Let exists be ? HasProperty(source, P).
180 // c. If exists is true, then
181 // i. Let element be ? Get(source, P).
182 // ii. If mapperFunction is present, then
183 // 1. Set element to ? Call(mapperFunction, thisArg, « element, sourceIndex, source »).
184 // iii. Let shouldFlatten be false.
185 // iv. If depth > 0, then
186 // 1. Set shouldFlatten to ? IsArray(element).
187 // v. If shouldFlatten is true, then
188 // 1. If depth is +∞, let newDepth be +∞.
189 // 2. Else, let newDepth be depth - 1.
190 // 3. Let elementLen be ? LengthOfArrayLike(element).
191 // 4. Set targetIndex to ? FlattenIntoArray(target, element, elementLen, targetIndex, newDepth).
192 // vi. Else,
193 // 1. If targetIndex ≥ 2^53 - 1, throw a TypeError exception.
194 // 2. Perform ? CreateDataPropertyOrThrow(target, ! ToString(!(targetIndex)), element).
195 // 3. Set targetIndex to targetIndex + 1.
196 // d. Set sourceIndex to sourceIndex + 1!.
197 while (sourceIndex < args.sourceLen) {
198 sourceIndexHandle.Update(JSTaggedValue(sourceIndex));
199 sourceIndexStr = JSTaggedValue::ToString(thread, sourceIndexHandle);
200 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
201 p.Update(sourceIndexStr.GetTaggedValue());
202 bool exists = JSTaggedValue::HasProperty(thread, thisObjVal, p);
203 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
204 if (exists) {
205 element.Update(JSArray::FastGetPropertyByValue(thread, thisObjVal, p).GetTaggedValue());
206 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
207 if (!mapperFunctionHandle->IsUndefined()) {
208 const int32_t argsLength = 3; // 3: « element, sourceIndex, source »
209 JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
210 EcmaRuntimeCallInfo *info =
211 EcmaInterpreter::NewRuntimeCallInfo(thread, mapperFunctionHandle, thisArg, undefined, argsLength);
212 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
213 info->SetCallArg(element.GetTaggedValue(), p.GetTaggedValue(), thisObjVal.GetTaggedValue());
214 JSTaggedValue obj = JSFunction::Call(info);
215 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
216 element.Update(obj);
217 }
218 bool shouldFlatten = false;
219 if (args.depth > 0) {
220 shouldFlatten = element->IsArray(thread);
221 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
222 }
223 if (shouldFlatten) {
224 tempArgs.depth = args.depth > POSITIVE_INFINITY ? POSITIVE_INFINITY : args.depth - 1;
225 tempArgs.sourceLen = ArrayHelper::GetLength(thread, element);
226 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
227 JSTaggedValue TargetIndexObj = FlattenIntoArray(thread, newArrayHandle, element, tempArgs,
228 thread->GlobalConstants()->GetHandledUndefined(),
229 thread->GlobalConstants()->GetHandledUndefined());
230 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
231 targetIndexHandle.Update(TargetIndexObj);
232 JSTaggedNumber targetIndexTemp = JSTaggedValue::ToNumber(thread, targetIndexHandle);
233 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
234 tempArgs.start = base::NumberHelper::TruncateDouble(targetIndexTemp.GetNumber());
235 } else {
236 if (tempArgs.start > base::MAX_SAFE_INTEGER) {
237 THROW_TYPE_ERROR_AND_RETURN(thread, "out of range.", JSTaggedValue::Exception());
238 }
239 sourceIndexHandle.Update(JSTaggedValue(tempArgs.start));
240 sourceIndexStr = JSTaggedValue::ToString(thread, sourceIndexHandle);
241 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
242 targetIndexHandle.Update(sourceIndexStr.GetTaggedValue());
243 JSObject::CreateDataPropertyOrThrow(thread, newArrayHandle, targetIndexHandle, element);
244 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
245 tempArgs.start++;
246 }
247 }
248 sourceIndex++;
249 }
250 // 7. Return targetIndex.
251 return BuiltinsBase::GetTaggedDouble(tempArgs.start);
252 }
253
SortIndexedProperties(JSThread * thread,const JSHandle<JSObject> & thisObj,int64_t len,const JSHandle<JSTaggedValue> & callbackFnHandle,HolesType holes)254 JSTaggedValue ArrayHelper::SortIndexedProperties(JSThread *thread, const JSHandle<JSObject> &thisObj,
255 int64_t len, const JSHandle<JSTaggedValue> &callbackFnHandle,
256 HolesType holes)
257 {
258 // 1. Let items be a new empty List.
259 JSHandle<TaggedArray> items(thread->GetEcmaVM()->GetFactory()->NewTaggedArray(len));
260 // 2. Let k be 0.
261 int64_t k = 0;
262 // 3. Repeat, while k < len,
263 // a. Let Pk be ! ToString((k)).
264 // b. If holes is skip-holes, then
265 // i. Let kRead be ? HasProperty(obj, Pk).
266 // c. Else,
267 // i. Assert: holes is read-through-holes.
268 // ii. Let kRead be true.
269 // d. If kRead is true, then
270 // i. Let kValue be ? Get(obj, Pk).
271 // ii. Append kValue to items.
272 // e. Set k to k + 1.
273 bool kRead = false;
274 JSHandle<JSTaggedValue> thisObjVal(thisObj);
275 JSMutableHandle<JSTaggedValue> pk(thread, JSTaggedValue::Undefined());
276
277 while (k < len) {
278 if (holes == HolesType::SKIP_HOLES) {
279 pk.Update(JSTaggedValue(k));
280 kRead = JSTaggedValue::HasProperty(thread, thisObjVal, pk);
281 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
282 } else {
283 ASSERT(holes == HolesType::READ_THROUGH_HOLES);
284 kRead = true;
285 }
286 if (kRead) {
287 JSHandle<JSTaggedValue> kValue = JSArray::FastGetPropertyByValue(thread, thisObjVal, k);
288 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
289 items->Set(thread, k, kValue.GetTaggedValue());
290 }
291 ++k;
292 }
293 JSHandle<JSArray> array(JSArray::CreateArrayFromList(thread, items));
294 JSHandle<JSObject> arrayObj = JSHandle<JSObject>::Cast(array);
295 // 4. Sort items using an implementation-defined sequence of calls to SortCompare.
296 // If any such call returns an abrupt completion,
297 // stop before performing any further calls to SortCompare and return that Completion Record.
298 JSArray::Sort(thread, arrayObj, callbackFnHandle);
299 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
300 // 5. Return items.
301 return arrayObj.GetTaggedValue();
302 }
303 } // namespace panda::ecmascript::base
304