• 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 
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