1 /*
2  * Copyright (c) 2021 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/builtins/builtins_map.h"
17 
18 #include "ecmascript/base/builtins_base.h"
19 #include "ecmascript/ecma_runtime_call_info.h"
20 #include "ecmascript/ecma_string.h"
21 #include "ecmascript/ecma_vm.h"
22 #include "ecmascript/global_env.h"
23 #include "ecmascript/js_array.h"
24 #include "ecmascript/js_handle.h"
25 #include "ecmascript/js_hclass.h"
26 #include "ecmascript/js_map.h"
27 #include "ecmascript/js_map_iterator.h"
28 #include "ecmascript/js_object-inl.h"
29 #include "ecmascript/js_tagged_value.h"
30 #include "ecmascript/js_thread.h"
31 #include "ecmascript/object_factory.h"
32 #include "ecmascript/tagged_array-inl.h"
33 #include "ecmascript/tests/test_helper.h"
34 #include "ecmascript/shared_objects/js_shared_map_iterator.h"
35 
36 using namespace panda::ecmascript;
37 using namespace panda::ecmascript::builtins;
38 
39 namespace panda::test {
40 using BuiltinsMap = ecmascript::builtins::BuiltinsMap;
41 using JSMap = ecmascript::JSMap;
42 
43 class BuiltinsMapTest : public BaseTestWithScope<false> {
44 public:
45     class TestClass : public base::BuiltinsBase {
46     public:
TestFunc(EcmaRuntimeCallInfo *argv)47         static JSTaggedValue TestFunc(EcmaRuntimeCallInfo *argv)
48         {
49             int num = GetCallArg(argv, 0)->GetInt();
50             JSArray *jsArray = JSArray::Cast(GetThis(argv)->GetTaggedObject());
51             int length = jsArray->GetArrayLength() + num;
52             jsArray->SetArrayLength(argv->GetThread(), length);
53             return JSTaggedValue::Undefined();
54         }
55     };
56 };
57 
CreateBuiltinsMap(JSThread *thread)58 JSMap *CreateBuiltinsMap(JSThread *thread)
59 {
60     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
61     JSHandle<JSFunction> newTarget(env->GetBuiltinsMapFunction());
62     // 4 : test case
63     auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue(*newTarget), 4);
64     ecmaRuntimeCallInfo->SetFunction(newTarget.GetTaggedValue());
65     ecmaRuntimeCallInfo->SetThis(JSTaggedValue::Undefined());
66 
67     [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo);
68     JSTaggedValue result = BuiltinsMap::MapConstructor(ecmaRuntimeCallInfo);
69     TestHelper::TearDownFrame(thread, prev);
70 
71     EXPECT_TRUE(result.IsECMAObject());
72     JSMap *jsMap = JSMap::Cast(reinterpret_cast<TaggedObject *>(result.GetRawData()));
73     return jsMap;
74 }
75 
76 enum class AlgorithmType {
77     SET,
78     FOR_EACH,
79     HAS,
80 };
81 
MapAlgorithm(JSThread *thread, JSTaggedValue thisArg, std::vector<JSTaggedValue>& args, int32_t argLen, AlgorithmType type)82 JSTaggedValue MapAlgorithm(JSThread *thread, JSTaggedValue thisArg, std::vector<JSTaggedValue>& args,
83     int32_t argLen, AlgorithmType type)
84 {
85     auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue::Undefined(), argLen);
86     ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
87     ecmaRuntimeCallInfo->SetThis(thisArg);
88     for (size_t i = 0; i < args.size(); i++) {
89         ecmaRuntimeCallInfo->SetCallArg(i, args[i]);
90     }
91     auto prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo);
92     JSTaggedValue result;
93     switch (type) {
94         case AlgorithmType::SET:
95             result = BuiltinsMap::Set(ecmaRuntimeCallInfo);
96             break;
97         case AlgorithmType::FOR_EACH:
98             result = BuiltinsMap::ForEach(ecmaRuntimeCallInfo);
99             break;
100         case AlgorithmType::HAS:
101             result = BuiltinsMap::Has(ecmaRuntimeCallInfo);
102             break;
103         default:
104             break;
105     }
106     TestHelper::TearDownFrame(thread, prev);
107     return result;
108 }
109 
110 // new Map("abrupt").toString()
HWTEST_F_L0(BuiltinsMapTest, CreateAndGetSize)111 HWTEST_F_L0(BuiltinsMapTest, CreateAndGetSize)
112 {
113     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
114     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
115     JSHandle<JSFunction> newTarget(env->GetBuiltinsMapFunction());
116     JSHandle<JSMap> map(thread, CreateBuiltinsMap(thread));
117 
118     auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue::Undefined(), 6);
119     ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
120     ecmaRuntimeCallInfo->SetThis(map.GetTaggedValue());
121     ecmaRuntimeCallInfo->SetCallArg(0, JSTaggedValue::Undefined());
122 
123     {
124         [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo);
125         JSTaggedValue result = BuiltinsMap::GetSize(ecmaRuntimeCallInfo);
126         TestHelper::TearDownFrame(thread, prev);
127 
128         EXPECT_EQ(result.GetRawData(), JSTaggedValue(0).GetRawData());
129     }
130     JSHandle<TaggedArray> array(factory->NewTaggedArray(5));
131     for (int i = 0; i < 5; i++) {
132         JSHandle<TaggedArray> internalArray(factory->NewTaggedArray(2));
133         internalArray->Set(thread, 0, JSTaggedValue(i));
134         internalArray->Set(thread, 1, JSTaggedValue(i));
135         auto arr = JSArray::CreateArrayFromList(thread, internalArray);
136         array->Set(thread, i, arr);
137     }
138     JSHandle<JSArray> values = JSArray::CreateArrayFromList(thread, array);
139     auto ecmaRuntimeCallInfo1 = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue::Undefined(), 6);
140     ecmaRuntimeCallInfo1->SetFunction(newTarget.GetTaggedValue());
141     ecmaRuntimeCallInfo1->SetThis(map.GetTaggedValue());
142     ecmaRuntimeCallInfo1->SetCallArg(0, values.GetTaggedValue());
143     ecmaRuntimeCallInfo1->SetNewTarget(newTarget.GetTaggedValue());
144     {
145         [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo1);
146         JSTaggedValue result1 = BuiltinsMap::MapConstructor(ecmaRuntimeCallInfo1);
147         TestHelper::TearDownFrame(thread, prev);
148 
149         EXPECT_EQ(JSMap::Cast(reinterpret_cast<TaggedObject *>(result1.GetRawData()))->GetSize(), 5);
150     }
151 }
152 
HWTEST_F_L0(BuiltinsMapTest, SetAndHas)153 HWTEST_F_L0(BuiltinsMapTest, SetAndHas)
154 {
155     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
156     // create jsMap
157     JSHandle<JSMap> map(thread, CreateBuiltinsMap(thread));
158     JSHandle<JSTaggedValue> key(factory->NewFromASCII("key"));
159 
160     auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue::Undefined(), 8);
161     ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
162     ecmaRuntimeCallInfo->SetThis(map.GetTaggedValue());
163     ecmaRuntimeCallInfo->SetCallArg(0, key.GetTaggedValue());
164     ecmaRuntimeCallInfo->SetCallArg(1, JSTaggedValue(static_cast<int32_t>(1)));
165 
166     JSMap *jsMap;
167     {
168         [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo);
169         JSTaggedValue result1 = BuiltinsMap::Has(ecmaRuntimeCallInfo);
170 
171         EXPECT_EQ(result1.GetRawData(), JSTaggedValue::False().GetRawData());
172 
173         // test Set()
174         JSTaggedValue result2 = BuiltinsMap::Set(ecmaRuntimeCallInfo);
175 
176         EXPECT_TRUE(result2.IsECMAObject());
177         jsMap = JSMap::Cast(reinterpret_cast<TaggedObject *>(result2.GetRawData()));
178         EXPECT_EQ(jsMap->GetSize(), 1);
179     }
180 
181     // test Has()
182     std::vector<JSTaggedValue> args{key.GetTaggedValue(), JSTaggedValue(static_cast<int32_t>(1))}; // 1:value
183     auto result3 = MapAlgorithm(thread, JSTaggedValue(jsMap), args, 8, AlgorithmType::HAS); // 8: arg len
184     EXPECT_EQ(result3.GetRawData(), JSTaggedValue::True().GetRawData());
185 }
186 
HWTEST_F_L0(BuiltinsMapTest, ForEach)187 HWTEST_F_L0(BuiltinsMapTest, ForEach)
188 {
189     // generate a map has 5 entries{key1:0,key2:1,key3:2,key4:3,key5:4}
190     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
191     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
192     JSHandle<JSMap> map(thread, CreateBuiltinsMap(thread));
193     char keyArray[] = "key0";
194     for (uint32_t i = 0; i < 5; i++) {
195         keyArray[3] = '1' + i;
196         JSHandle<JSTaggedValue> key(factory->NewFromASCII(keyArray));
197         std::vector<JSTaggedValue> args{key.GetTaggedValue(), JSTaggedValue(static_cast<int32_t>(i))};
198         auto result1 = MapAlgorithm(thread, map.GetTaggedValue(), args, 8, AlgorithmType::SET);
199 
200         EXPECT_TRUE(result1.IsECMAObject());
201         JSMap *jsMap = JSMap::Cast(reinterpret_cast<TaggedObject *>(result1.GetRawData()));
202         EXPECT_EQ(jsMap->GetSize(), static_cast<int>(i) + 1);
203     }
204     JSHandle<JSArray> jsArray(JSArray::ArrayCreate(thread, JSTaggedNumber(0)));
205     JSHandle<JSFunction> func = factory->NewJSFunction(env, reinterpret_cast<void *>(TestClass::TestFunc));
206 
207     std::vector<JSTaggedValue> args{func.GetTaggedValue(), jsArray.GetTaggedValue()};
208     auto result2 = MapAlgorithm(thread, map.GetTaggedValue(), args, 8, AlgorithmType::FOR_EACH);
209 
210     EXPECT_EQ(result2.GetRawData(), JSTaggedValue::VALUE_UNDEFINED);
211     EXPECT_EQ(jsArray->GetArrayLength(), 10U);
212 }
213 
HWTEST_F_L0(BuiltinsMapTest, DeleteAndRemove)214 HWTEST_F_L0(BuiltinsMapTest, DeleteAndRemove)
215 {
216     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
217     // create jsMap
218     JSHandle<JSMap> map(thread, CreateBuiltinsMap(thread));
219 
220     // add 40 keys
221     char keyArray[] = "key0";
222     for (uint32_t i = 0; i < 40; i++) {
223         keyArray[3] = '1' + i;
224         JSHandle<JSTaggedValue> key(thread, factory->NewFromASCII(keyArray).GetTaggedValue());
225         std::vector<JSTaggedValue> args{key.GetTaggedValue(), JSTaggedValue(static_cast<int32_t>(i))};
226         auto result1 = MapAlgorithm(thread, map.GetTaggedValue(), args, 8, AlgorithmType::SET);
227 
228         EXPECT_TRUE(result1.IsECMAObject());
229         JSMap *jsMap = JSMap::Cast(reinterpret_cast<TaggedObject *>(result1.GetRawData()));
230         EXPECT_EQ(jsMap->GetSize(), static_cast<int>(i) + 1);
231     }
232     // whether jsMap has delete key
233     keyArray[3] = '1' + 8;
234     JSHandle<JSTaggedValue> deleteKey(factory->NewFromASCII(keyArray));
235 
236     auto ecmaRuntimeCallInfo1 = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue::Undefined(), 6);
237     ecmaRuntimeCallInfo1->SetFunction(JSTaggedValue::Undefined());
238     ecmaRuntimeCallInfo1->SetThis(map.GetTaggedValue());
239     ecmaRuntimeCallInfo1->SetCallArg(0, deleteKey.GetTaggedValue());
240 
241     [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo1);
242     JSTaggedValue result2 = BuiltinsMap::Has(ecmaRuntimeCallInfo1);
243     TestHelper::TearDownFrame(thread, prev);
244 
245     EXPECT_EQ(result2.GetRawData(), JSTaggedValue::True().GetRawData());
246 
247     // delete
248     [[maybe_unused]] auto prev1 = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo1);
249     JSTaggedValue result3 = BuiltinsMap::Delete(ecmaRuntimeCallInfo1);
250     TestHelper::TearDownFrame(thread, prev1);
251     EXPECT_EQ(result3.GetRawData(), JSTaggedValue::True().GetRawData());
252 
253     // check deleteKey is deleted
254     [[maybe_unused]] auto prev2 = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo1);
255     JSTaggedValue result4 = BuiltinsMap::Has(ecmaRuntimeCallInfo1);
256     TestHelper::TearDownFrame(thread, prev2);
257     EXPECT_EQ(result4.GetRawData(), JSTaggedValue::False().GetRawData());
258     [[maybe_unused]] auto prev3 = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo1);
259     JSTaggedValue result5 = BuiltinsMap::GetSize(ecmaRuntimeCallInfo1);
260     TestHelper::TearDownFrame(thread, prev3);
261     EXPECT_EQ(result5.GetRawData(), JSTaggedValue(39).GetRawData());
262 
263     // clear
264     [[maybe_unused]] auto prev4 = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo1);
265     JSTaggedValue result6 = BuiltinsMap::Clear(ecmaRuntimeCallInfo1);
266     TestHelper::TearDownFrame(thread, prev4);
267     EXPECT_EQ(result6.GetRawData(), JSTaggedValue::VALUE_UNDEFINED);
268     EXPECT_EQ(map->GetSize(), 0);
269 }
270 
HWTEST_F_L0(BuiltinsMapTest, Species)271 HWTEST_F_L0(BuiltinsMapTest, Species)
272 {
273     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
274     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
275     JSHandle<JSTaggedValue> map(thread, CreateBuiltinsMap(thread));
276 
277     // test species
278     JSHandle<JSTaggedValue> speciesSymbol = env->GetSpeciesSymbol();
279     EXPECT_TRUE(!speciesSymbol.GetTaggedValue().IsUndefined());
280 
281     JSHandle<JSFunction> newTarget(env->GetBuiltinsMapFunction());
282 
283     JSTaggedValue value =
284         JSObject::GetProperty(thread, JSHandle<JSTaggedValue>(newTarget), speciesSymbol).GetValue().GetTaggedValue();
285     JSHandle<JSTaggedValue> valueHandle(thread, value);
286     EXPECT_EQ(value, newTarget.GetTaggedValue());
287 
288     // to string tag
289     JSHandle<JSTaggedValue> toStringTagSymbol = env->GetToStringTagSymbol();
290     JSHandle<EcmaString> stringTag(JSObject::GetProperty(thread, map, toStringTagSymbol).GetValue());
291     JSHandle<EcmaString> str = factory->NewFromASCII("Map");
292     EXPECT_TRUE(!stringTag.GetTaggedValue().IsUndefined());
293     EXPECT_TRUE(EcmaStringAccessor::StringsAreEqual(*str, *stringTag));
294 
295     JSHandle<JSFunction> constructor = JSHandle<JSFunction>::Cast(JSTaggedValue::ToObject(thread, valueHandle));
296     EXPECT_EQ(JSTaggedValue::GetPrototype(thread, map), constructor->GetFunctionPrototype());
297 
298     JSHandle<JSTaggedValue> key1(factory->NewFromASCII("set"));
299     JSTaggedValue value1 = JSObject::GetProperty(thread, map, key1).GetValue().GetTaggedValue();
300     EXPECT_FALSE(value1.IsUndefined());
301 
302     JSHandle<JSTaggedValue> key2(factory->NewFromASCII("has"));
303     JSTaggedValue value2 = JSObject::GetProperty(thread, map, key1).GetValue().GetTaggedValue();
304     EXPECT_FALSE(value2.IsUndefined());
305 
306     JSHandle<JSTaggedValue> key3(factory->NewFromASCII("clear"));
307     JSTaggedValue value3 = JSObject::GetProperty(thread, map, key1).GetValue().GetTaggedValue();
308     EXPECT_FALSE(value3.IsUndefined());
309 
310     JSHandle<JSTaggedValue> key4(factory->NewFromASCII("size"));
311     JSTaggedValue value4 = JSObject::GetProperty(thread, map, key1).GetValue().GetTaggedValue();
312     EXPECT_FALSE(value4.IsUndefined());
313 
314     JSHandle<JSTaggedValue> key5(factory->NewFromASCII("delete"));
315     JSTaggedValue value5 = JSObject::GetProperty(thread, map, key1).GetValue().GetTaggedValue();
316     EXPECT_FALSE(value5.IsUndefined());
317 
318     JSHandle<JSTaggedValue> key6(factory->NewFromASCII("forEach"));
319     JSTaggedValue value6 = JSObject::GetProperty(thread, map, key1).GetValue().GetTaggedValue();
320     EXPECT_FALSE(value6.IsUndefined());
321 
322     JSHandle<JSTaggedValue> key7(factory->NewFromASCII("get"));
323     JSTaggedValue value7 = JSObject::GetProperty(thread, map, key1).GetValue().GetTaggedValue();
324     EXPECT_FALSE(value7.IsUndefined());
325 }
326 
HWTEST_F_L0(BuiltinsMapTest, GetIterator)327 HWTEST_F_L0(BuiltinsMapTest, GetIterator)
328 {
329     JSHandle<JSTaggedValue> map(thread, CreateBuiltinsMap(thread));
330     auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue::Undefined(), 4);
331     ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
332     ecmaRuntimeCallInfo->SetThis(map.GetTaggedValue());
333     [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo);
334 
335     // test Values()
336     JSTaggedValue result = BuiltinsMap::Values(ecmaRuntimeCallInfo);
337     JSHandle<JSMapIterator> iter(thread, result);
338     EXPECT_TRUE(iter->IsJSMapIterator());
339     EXPECT_EQ(IterationKind::VALUE, iter->GetIterationKind());
340     EXPECT_EQ(JSMap::Cast(map.GetTaggedValue().GetTaggedObject())->GetLinkedMap(), iter->GetIteratedMap());
341 
342     // test Keys()
343     JSTaggedValue result1 = BuiltinsMap::Keys(ecmaRuntimeCallInfo);
344     JSHandle<JSMapIterator> iter1(thread, result1);
345     EXPECT_TRUE(iter1->IsJSMapIterator());
346     EXPECT_EQ(IterationKind::KEY, iter1->GetIterationKind());
347 
348     // test entries()
349     JSTaggedValue result2 = BuiltinsMap::Entries(ecmaRuntimeCallInfo);
350     JSHandle<JSMapIterator> iter2(thread, result2);
351     EXPECT_TRUE(iter2->IsJSMapIterator());
352     EXPECT_EQ(IterationKind::KEY_AND_VALUE, iter2->GetIterationKind());
353     TestHelper::TearDownFrame(thread, prev);
354 }
355 
HWTEST_F_L0(BuiltinsMapTest, Exception)356 HWTEST_F_L0(BuiltinsMapTest, Exception)
357 {
358     JSHandle<JSTaggedValue> map(thread, CreateBuiltinsMap(thread));
359     auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue::Undefined(), 4);
360     ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
361     ecmaRuntimeCallInfo->SetThis(map.GetTaggedValue());
362     [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo);
363     auto result = JSSharedMapIterator::CreateMapIterator(thread, map, IterationKind::KEY);
364     EXPECT_TRUE(result->IsUndefined());
365     auto result1 = JSSharedMapIterator::NextInternal(thread, map);
366     EXPECT_EQ(result1, JSTaggedValue::Exception());
367     TestHelper::TearDownFrame(thread, prev);
368 }
369 
370 }  // namespace panda::test
371