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
28 namespace panda::ecmascript::base {
IsConcatSpreadable(JSThread * thread,const JSHandle<JSTaggedValue> & obj)29 bool ArrayHelper::IsConcatSpreadable(JSThread *thread, const JSHandle<JSTaggedValue> &obj)
30 {
31 // 1. If Type(O) is not Object, return false.
32 if (!obj->IsECMAObject()) {
33 return false;
34 }
35
36 // 2. Let spreadable be Get(O, @@isConcatSpreadable).
37 auto ecmaVm = thread->GetEcmaVM();
38 JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
39 JSHandle<JSTaggedValue> isConcatsprKey = env->GetIsConcatSpreadableSymbol();
40 JSHandle<JSTaggedValue> spreadable = JSTaggedValue::GetProperty(thread, obj, isConcatsprKey).GetValue();
41 // 3. ReturnIfAbrupt(spreadable).
42 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
43
44 // 4. If spreadable is not undefined, return ToBoolean(spreadable).
45 if (!spreadable->IsUndefined()) {
46 return spreadable->ToBoolean();
47 }
48
49 // 5. Return IsArray(O).
50 return obj->IsArray(thread);
51 }
52
SortCompare(JSThread * thread,const JSHandle<JSTaggedValue> & callbackfnHandle,const JSHandle<JSTaggedValue> & valueX,const JSHandle<JSTaggedValue> & valueY)53 double ArrayHelper::SortCompare(JSThread *thread, const JSHandle<JSTaggedValue> &callbackfnHandle,
54 const JSHandle<JSTaggedValue> &valueX, const JSHandle<JSTaggedValue> &valueY)
55 {
56 // 1. If x and y are both undefined, return +0.
57 if (valueX->IsHole()) {
58 if (valueY->IsHole()) {
59 return 0;
60 }
61 return 1;
62 }
63 if (valueY->IsHole()) {
64 return -1;
65 }
66 if (valueX->IsUndefined()) {
67 if (valueY->IsUndefined()) {
68 return 0;
69 }
70 // 2. If x is undefined, return 1.
71 return 1;
72 }
73 // 3. If y is undefined, return -1.
74 if (valueY->IsUndefined()) {
75 return -1;
76 }
77 // 4. If the argument comparefn is not undefined, then
78 // a. Let v be ToNumber(Call(comparefn, undefined, «x, y»)).
79 // b. ReturnIfAbrupt(v).
80 // c. If v is NaN, return +0.
81 // d. Return v.
82 if (!callbackfnHandle->IsUndefined()) {
83 JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
84 EcmaRuntimeCallInfo *info =
85 EcmaInterpreter::NewRuntimeCallInfo(thread, callbackfnHandle, undefined, undefined, 2); // 2: «x, y»
86 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
87 info->SetCallArg(valueX.GetTaggedValue(), valueY.GetTaggedValue());
88 JSTaggedValue callResult = JSFunction::Call(info);
89 if (callResult.IsInt()) {
90 return callResult.GetInt();
91 }
92 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
93 JSHandle<JSTaggedValue> testResult(thread, callResult);
94 JSTaggedNumber v = JSTaggedValue::ToNumber(thread, testResult);
95 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
96 double value = v.GetNumber();
97 if (std::isnan(value)) {
98 return +0;
99 }
100 return value;
101 }
102 // 5. Let xString be ToString(x).
103 // 6. ReturnIfAbrupt(xString).
104 // 7. Let yString be ToString(y).
105 // 8. ReturnIfAbrupt(yString).
106 // 9. If xString < yString, return -1.
107 // 10. If xString > yString, return 1.
108 // 11. Return +0.
109 JSHandle<JSTaggedValue> xValueHandle(JSTaggedValue::ToString(thread, valueX));
110 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
111 JSHandle<JSTaggedValue> yValueHandle(JSTaggedValue::ToString(thread, valueY));
112 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
113 ComparisonResult compareResult = JSTaggedValue::Compare(thread, xValueHandle, yValueHandle);
114 return compareResult == ComparisonResult::GREAT ? 1 : 0;
115 }
116
GetLength(JSThread * thread,const JSHandle<JSTaggedValue> & thisHandle)117 int64_t ArrayHelper::GetLength(JSThread *thread, const JSHandle<JSTaggedValue> &thisHandle)
118 {
119 if (thisHandle->IsJSArray()) {
120 return JSArray::Cast(thisHandle->GetTaggedObject())->GetArrayLength();
121 }
122 if (thisHandle->IsTypedArray()) {
123 return JSHandle<JSTypedArray>::Cast(thisHandle)->GetArrayLength();
124 }
125 JSHandle<JSTaggedValue> lengthKey = thread->GlobalConstants()->GetHandledLengthString();
126 JSHandle<JSTaggedValue> lenResult = JSTaggedValue::GetProperty(thread, thisHandle, lengthKey).GetValue();
127 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
128 JSTaggedNumber len = JSTaggedValue::ToLength(thread, lenResult);
129 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
130 return len.GetNumber();
131 }
132
GetArrayLength(JSThread * thread,const JSHandle<JSTaggedValue> & thisHandle)133 int64_t ArrayHelper::GetArrayLength(JSThread *thread, const JSHandle<JSTaggedValue> &thisHandle)
134 {
135 if (thisHandle->IsJSArray()) {
136 return JSArray::Cast(thisHandle->GetTaggedObject())->GetArrayLength();
137 }
138 JSHandle<JSTaggedValue> lengthKey = thread->GlobalConstants()->GetHandledLengthString();
139 JSHandle<JSTaggedValue> lenResult = JSTaggedValue::GetProperty(thread, thisHandle, lengthKey).GetValue();
140 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
141 JSTaggedNumber len = JSTaggedValue::ToLength(thread, lenResult);
142 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
143 return len.GetNumber();
144 }
145
FlattenIntoArray(JSThread * thread,const JSHandle<JSObject> & newArrayHandle,const JSHandle<JSTaggedValue> & thisObjVal,const FlattenArgs & args,const JSHandle<JSTaggedValue> & mapperFunctionHandle,const JSHandle<JSTaggedValue> & thisArg)146 JSTaggedValue ArrayHelper::FlattenIntoArray(JSThread *thread, const JSHandle<JSObject> &newArrayHandle,
147 const JSHandle<JSTaggedValue> &thisObjVal, const FlattenArgs &args,
148 const JSHandle<JSTaggedValue> &mapperFunctionHandle,
149 const JSHandle<JSTaggedValue> &thisArg)
150 {
151 // 1. Assert: Type(target) is Object.
152 // 2. Assert: Type(source) is Object.
153 // 3. Assert: If mapperFunction is present, then ! IsCallable(mapperFunction) is true,
154 // thisArg is present, and depth is 1.
155 ASSERT(mapperFunctionHandle->IsUndefined() || mapperFunctionHandle->IsCallable() ||
156 (!thisArg->IsUndefined() && args.depth == 1));
157 // 4. Let targetIndex be start.
158 // 5. Let sourceIndex be +0!.
159 FlattenArgs tempArgs;
160 tempArgs.start = args.start;
161 int64_t sourceIndex = 0;
162 JSMutableHandle<JSTaggedValue> p(thread, JSTaggedValue::Undefined());
163 JSMutableHandle<JSTaggedValue> element(thread, JSTaggedValue::Undefined());
164 JSMutableHandle<JSTaggedValue> targetIndexHandle(thread, JSTaggedValue::Undefined());
165 JSMutableHandle<JSTaggedValue> sourceIndexHandle(thread, JSTaggedValue::Undefined());
166 JSHandle<EcmaString> sourceIndexStr;
167 // 6. Repeat, while (sourceIndex) < sourceLen,
168 // a. Let P be ! ToString(sourceIndex).
169 // b. Let exists be ? HasProperty(source, P).
170 // c. If exists is true, then
171 // i. Let element be ? Get(source, P).
172 // ii. If mapperFunction is present, then
173 // 1. Set element to ? Call(mapperFunction, thisArg, « element, sourceIndex, source »).
174 // iii. Let shouldFlatten be false.
175 // iv. If depth > 0, then
176 // 1. Set shouldFlatten to ? IsArray(element).
177 // v. If shouldFlatten is true, then
178 // 1. If depth is +∞, let newDepth be +∞.
179 // 2. Else, let newDepth be depth - 1.
180 // 3. Let elementLen be ? LengthOfArrayLike(element).
181 // 4. Set targetIndex to ? FlattenIntoArray(target, element, elementLen, targetIndex, newDepth).
182 // vi. Else,
183 // 1. If targetIndex ≥ 2^53 - 1, throw a TypeError exception.
184 // 2. Perform ? CreateDataPropertyOrThrow(target, ! ToString(!(targetIndex)), element).
185 // 3. Set targetIndex to targetIndex + 1.
186 // d. Set sourceIndex to sourceIndex + 1!.
187 while (sourceIndex < args.sourceLen) {
188 sourceIndexHandle.Update(JSTaggedValue(sourceIndex));
189 sourceIndexStr = JSTaggedValue::ToString(thread, sourceIndexHandle);
190 p.Update(sourceIndexStr.GetTaggedValue());
191 bool exists = JSTaggedValue::HasProperty(thread, thisObjVal, p);
192 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
193 if (exists) {
194 element.Update(JSArray::FastGetPropertyByValue(thread, thisObjVal, p).GetTaggedValue());
195 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
196 if (!mapperFunctionHandle->IsUndefined()) {
197 const int32_t argsLength = 3; // 3: « element, sourceIndex, source »
198 JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
199 EcmaRuntimeCallInfo *info =
200 EcmaInterpreter::NewRuntimeCallInfo(thread, mapperFunctionHandle, thisArg, undefined, argsLength);
201 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
202 info->SetCallArg(element.GetTaggedValue(), p.GetTaggedValue(), thisObjVal.GetTaggedValue());
203 JSTaggedValue obj = JSFunction::Call(info);
204 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
205 element.Update(obj);
206 }
207 bool shouldFlatten = false;
208 if (args.depth > 0) {
209 shouldFlatten = element->IsArray(thread);
210 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
211 }
212 if (shouldFlatten) {
213 tempArgs.depth = args.depth > POSITIVE_INFINITY ? POSITIVE_INFINITY : args.depth - 1;
214 tempArgs.sourceLen = ArrayHelper::GetLength(thread, element);
215 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
216 JSTaggedValue TargetIndexObj = FlattenIntoArray(thread, newArrayHandle, element, tempArgs,
217 thread->GlobalConstants()->GetHandledUndefined(),
218 thread->GlobalConstants()->GetHandledUndefined());
219 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
220 targetIndexHandle.Update(TargetIndexObj);
221 JSTaggedNumber targetIndexTemp = JSTaggedValue::ToNumber(thread, targetIndexHandle);
222 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
223 tempArgs.start = base::NumberHelper::TruncateDouble(targetIndexTemp.GetNumber());
224 } else {
225 if (tempArgs.start > base::MAX_SAFE_INTEGER) {
226 THROW_TYPE_ERROR_AND_RETURN(thread, "out of range.", JSTaggedValue::Exception());
227 }
228 sourceIndexHandle.Update(JSTaggedValue(tempArgs.start));
229 sourceIndexStr = JSTaggedValue::ToString(thread, sourceIndexHandle);
230 targetIndexHandle.Update(sourceIndexStr.GetTaggedValue());
231 JSObject::CreateDataPropertyOrThrow(thread, newArrayHandle, targetIndexHandle, element);
232 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
233 tempArgs.start++;
234 }
235 }
236 sourceIndex++;
237 }
238 // 7. Return targetIndex.
239 return BuiltinsBase::GetTaggedDouble(tempArgs.start);
240 }
241 } // namespace panda::ecmascript::base
242