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
36using namespace panda::ecmascript;
37using namespace panda::ecmascript::builtins;
38
39namespace panda::test {
40using BuiltinsMap = ecmascript::builtins::BuiltinsMap;
41using JSMap = ecmascript::JSMap;
42
43class BuiltinsMapTest : public BaseTestWithScope<false> {
44public:
45    class TestClass : public base::BuiltinsBase {
46    public:
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
58JSMap *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
76enum class AlgorithmType {
77    SET,
78    FOR_EACH,
79    HAS,
80};
81
82JSTaggedValue 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()
111HWTEST_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
153HWTEST_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
187HWTEST_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
214HWTEST_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
271HWTEST_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
327HWTEST_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
356HWTEST_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