1 /*
2  * Copyright (c) 2024 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_shared_map.h"
17 
18 #include "ecmascript/interpreter/interpreter.h"
19 #include "ecmascript/js_function.h"
20 #include "ecmascript/linked_hash_table.h"
21 #include "ecmascript/shared_objects/concurrent_api_scope.h"
22 #include "ecmascript/shared_objects/js_shared_map.h"
23 #include "ecmascript/shared_objects/js_shared_map_iterator.h"
24 
25 namespace panda::ecmascript::builtins {
Constructor(EcmaRuntimeCallInfo *argv)26 JSTaggedValue BuiltinsSharedMap::Constructor(EcmaRuntimeCallInfo *argv)
27 {
28     BUILTINS_API_TRACE(argv->GetThread(), SharedMap, Constructor);
29     JSThread *thread = argv->GetThread();
30     [[maybe_unused]] EcmaHandleScope handleScope(thread);
31     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
32     // 1. If NewTarget is undefined, throw exception
33     JSHandle<JSTaggedValue> newTarget = GetNewTarget(argv);
34     if (newTarget->IsUndefined()) {
35         JSTaggedValue error = containers::ContainerError::BusinessError(
36             thread, containers::ErrorFlag::IS_NULL_ERROR, "The ArkTS Map's constructor cannot be directly invoked.");
37         THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
38     }
39     // 2.Let Map be OrdinaryCreateFromConstructor(NewTarget, "%MapPrototype%", «‍[[MapData]]» ).
40     JSHandle<JSTaggedValue> constructor = GetConstructor(argv);
41     ASSERT(constructor->IsJSSharedFunction() && constructor.GetTaggedValue().IsInSharedHeap());
42     JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(constructor), newTarget);
43     // 3.returnIfAbrupt()
44     RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
45     ASSERT(obj.GetTaggedValue().IsInSharedHeap());
46     JSHandle<JSSharedMap> map = JSHandle<JSSharedMap>::Cast(obj);
47 
48     // 4.Set map’s [[MapData]] internal slot to a new empty List.
49     JSHandle<LinkedHashMap> linkedMap = LinkedHashMap::Create(thread,
50         LinkedHashMap::MIN_CAPACITY, MemSpaceKind::SHARED);
51     map->SetLinkedMap(thread, linkedMap);
52     // add data into set from iterable
53     // 5.If iterable is not present, let iterable be undefined.
54     // 6.If iterable is either undefined or null, let iter be undefined.
55     JSHandle<JSTaggedValue> iterable = GetCallArg(argv, 0);
56     if (iterable->IsUndefined() || iterable->IsNull()) {
57         return map.GetTaggedValue();
58     }
59     if (!iterable->IsECMAObject()) {
60         THROW_TYPE_ERROR_AND_RETURN(thread, "iterable is not object", JSTaggedValue::Exception());
61     }
62     // Let adder be Get(map, "set").
63     JSHandle<JSTaggedValue> adderKey = thread->GlobalConstants()->GetHandledSetString();
64     JSHandle<JSTaggedValue> adder = JSObject::GetProperty(thread, JSHandle<JSTaggedValue>(map), adderKey).GetValue();
65     // ReturnIfAbrupt(adder).
66     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, adder.GetTaggedValue());
67     return AddEntriesFromIterable(thread, obj, iterable, adder, factory);
68 }
69 
Set(EcmaRuntimeCallInfo *argv)70 JSTaggedValue BuiltinsSharedMap::Set(EcmaRuntimeCallInfo *argv)
71 {
72     BUILTINS_API_TRACE(argv->GetThread(), SharedMap, Set);
73     JSThread *thread = argv->GetThread();
74     [[maybe_unused]] EcmaHandleScope handleScope(thread);
75     JSHandle<JSTaggedValue> self = GetThis(argv);
76     if (!self->IsJSSharedMap()) {
77         auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::BIND_ERROR,
78                                                                "The set method cannot be bound.");
79         THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
80     }
81     JSHandle<JSTaggedValue> key = GetCallArg(argv, 0);
82     JSHandle<JSTaggedValue> value = GetCallArg(argv, 1);
83     JSHandle<JSSharedMap> map(self);
84     JSSharedMap::Set(thread, map, key, value);
85     return map.GetTaggedValue();
86 }
87 
Clear(EcmaRuntimeCallInfo *argv)88 JSTaggedValue BuiltinsSharedMap::Clear(EcmaRuntimeCallInfo *argv)
89 {
90     BUILTINS_API_TRACE(argv->GetThread(), SharedMap, Clear);
91     JSThread *thread = argv->GetThread();
92     [[maybe_unused]] EcmaHandleScope handleScope(thread);
93     JSHandle<JSTaggedValue> self = GetThis(argv);
94     if (!self->IsJSSharedMap()) {
95         auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::BIND_ERROR,
96                                                                "The clear method cannot be bound.");
97         THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
98     }
99     JSHandle<JSSharedMap> map(self);
100     JSSharedMap::Clear(thread, map);
101     return JSTaggedValue::Undefined();
102 }
103 
Delete(EcmaRuntimeCallInfo *argv)104 JSTaggedValue BuiltinsSharedMap::Delete(EcmaRuntimeCallInfo *argv)
105 {
106     BUILTINS_API_TRACE(argv->GetThread(), SharedMap, Delete);
107     JSThread *thread = argv->GetThread();
108     [[maybe_unused]] EcmaHandleScope handleScope(thread);
109     JSHandle<JSTaggedValue> self = GetThis(argv);
110     if (!self->IsJSSharedMap()) {
111         auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::BIND_ERROR,
112                                                                "The delete method cannot be bound.");
113         THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
114     }
115     JSHandle<JSSharedMap> map(self);
116     JSHandle<JSTaggedValue> key = GetCallArg(argv, 0);
117     bool flag = JSSharedMap::Delete(thread, map, key);
118     return GetTaggedBoolean(flag);
119 }
120 
Has(EcmaRuntimeCallInfo *argv)121 JSTaggedValue BuiltinsSharedMap::Has(EcmaRuntimeCallInfo *argv)
122 {
123     BUILTINS_API_TRACE(argv->GetThread(), SharedMap, Has);
124     JSThread *thread = argv->GetThread();
125     [[maybe_unused]] EcmaHandleScope handleScope(thread);
126     JSHandle<JSTaggedValue> self(GetThis(argv));
127     if (!self->IsJSSharedMap()) {
128         auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::BIND_ERROR,
129                                                                "The has method cannot be bound.");
130         THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
131     }
132     JSHandle<JSSharedMap> map(self);
133     JSHandle<JSTaggedValue> key = GetCallArg(argv, 0);
134     bool flag = JSSharedMap::Has(thread, map, key.GetTaggedValue());
135     return GetTaggedBoolean(flag);
136 }
137 
Get(EcmaRuntimeCallInfo *argv)138 JSTaggedValue BuiltinsSharedMap::Get(EcmaRuntimeCallInfo *argv)
139 {
140     BUILTINS_API_TRACE(argv->GetThread(), SharedMap, Get);
141     JSThread *thread = argv->GetThread();
142     [[maybe_unused]] EcmaHandleScope handleScope(thread);
143     JSHandle<JSTaggedValue> self(GetThis(argv));
144     if (!self->IsJSSharedMap()) {
145         auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::BIND_ERROR,
146                                                                "The get method cannot be bound.");
147         THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
148     }
149     JSHandle<JSSharedMap> map(self);
150     JSHandle<JSTaggedValue> key = GetCallArg(argv, 0);
151     JSTaggedValue value = JSSharedMap::Get(thread, map, key.GetTaggedValue());
152     return value;
153 }
154 
ForEach(EcmaRuntimeCallInfo *argv)155 JSTaggedValue BuiltinsSharedMap::ForEach(EcmaRuntimeCallInfo *argv)
156 {
157     JSThread *thread = argv->GetThread();
158     BUILTINS_API_TRACE(thread, SharedMap, ForEach);
159     [[maybe_unused]] EcmaHandleScope handleScope(thread);
160     JSHandle<JSTaggedValue> self = GetThis(argv);
161     if (!self->IsJSSharedMap()) {
162         auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::BIND_ERROR,
163                                                                "The forEach method cannot be bound.");
164         THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
165     }
166     [[maybe_unused]] ConcurrentApiScope<JSSharedMap> scope(thread, self);
167     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
168     JSHandle<JSSharedMap> map(self);
169     JSHandle<JSTaggedValue> func(GetCallArg(argv, 0));
170     if (!func->IsCallable()) {
171         THROW_TYPE_ERROR_AND_RETURN(thread, "obj is not Callable", JSTaggedValue::Exception());
172     }
173     JSHandle<JSTaggedValue> thisArg = GetCallArg(argv, 1);
174     JSMutableHandle<LinkedHashMap> hashMap(thread, map->GetLinkedMap());
175     const uint32_t argsLength = 3;
176     int index = 0;
177     int totalElements = hashMap->NumberOfElements() + hashMap->NumberOfDeletedElements();
178     JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
179     // Repeat for each e that is an element of entries, in original insertion order
180     while (index < totalElements) {
181         JSHandle<JSTaggedValue> key(thread, hashMap->GetKey(index++));
182         if (!key->IsHole()) {
183             JSHandle<JSTaggedValue> value(thread, hashMap->GetValue(index - 1));
184             EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(
185                 thread, func, thisArg, undefined, argsLength);
186             RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
187             info->SetCallArg(value.GetTaggedValue(), key.GetTaggedValue(), map.GetTaggedValue());
188             JSTaggedValue ret = JSFunction::Call(info);
189             RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, ret);
190         }
191     }
192 
193     return JSTaggedValue::Undefined();
194 }
195 
Species(EcmaRuntimeCallInfo *argv)196 JSTaggedValue BuiltinsSharedMap::Species(EcmaRuntimeCallInfo *argv)
197 {
198     BUILTINS_API_TRACE(argv->GetThread(), SharedMap, Species);
199     return GetThis(argv).GetTaggedValue();
200 }
201 
GetSize(EcmaRuntimeCallInfo *argv)202 JSTaggedValue BuiltinsSharedMap::GetSize(EcmaRuntimeCallInfo *argv)
203 {
204     BUILTINS_API_TRACE(argv->GetThread(), SharedMap, GetSize);
205     JSThread *thread = argv->GetThread();
206     [[maybe_unused]] EcmaHandleScope handleScope(thread);
207     JSHandle<JSTaggedValue> self(GetThis(argv));
208     if (!self->IsJSSharedMap()) {
209         THROW_TYPE_ERROR_AND_RETURN(thread, "obj is not SharedMap", JSTaggedValue::Exception());
210     }
211     JSHandle<JSSharedMap> map(self);
212     uint32_t size = JSSharedMap::GetSize(thread, map);
213     return JSTaggedValue(size);
214 }
215 
Entries(EcmaRuntimeCallInfo *argv)216 JSTaggedValue BuiltinsSharedMap::Entries(EcmaRuntimeCallInfo *argv)
217 {
218     BUILTINS_API_TRACE(argv->GetThread(), SharedMap, Entries);
219     JSThread *thread = argv->GetThread();
220     [[maybe_unused]] EcmaHandleScope handleScope(thread);
221     JSHandle<JSTaggedValue> self = GetThis(argv);
222     if (!self->IsJSSharedMap()) {
223         auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::BIND_ERROR,
224                                                                "The entries method cannot be bound.");
225         THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
226     }
227     JSHandle<JSTaggedValue> iter = JSSharedMapIterator::CreateMapIterator(thread, self, IterationKind::KEY_AND_VALUE);
228     RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
229     return iter.GetTaggedValue();
230 }
231 
Keys(EcmaRuntimeCallInfo *argv)232 JSTaggedValue BuiltinsSharedMap::Keys(EcmaRuntimeCallInfo *argv)
233 {
234     BUILTINS_API_TRACE(argv->GetThread(), SharedMap, Keys);
235     JSThread *thread = argv->GetThread();
236     [[maybe_unused]] EcmaHandleScope handleScope(thread);
237     JSHandle<JSTaggedValue> self = GetThis(argv);
238     if (!self->IsJSSharedMap()) {
239         auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::BIND_ERROR,
240                                                                "The keys method cannot be bound.");
241         THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
242     }
243     JSHandle<JSTaggedValue> iter = JSSharedMapIterator::CreateMapIterator(thread, self, IterationKind::KEY);
244     RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
245     return iter.GetTaggedValue();
246 }
247 
Values(EcmaRuntimeCallInfo *argv)248 JSTaggedValue BuiltinsSharedMap::Values(EcmaRuntimeCallInfo *argv)
249 {
250     BUILTINS_API_TRACE(argv->GetThread(), SharedMap, Values);
251     JSThread *thread = argv->GetThread();
252     [[maybe_unused]] EcmaHandleScope handleScope(thread);
253     JSHandle<JSTaggedValue> self = GetThis(argv);
254     if (!self->IsJSSharedMap()) {
255         auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::BIND_ERROR,
256                                                                "The values method cannot be bound.");
257         THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
258     }
259     JSHandle<JSTaggedValue> iter = JSSharedMapIterator::CreateMapIterator(thread, self, IterationKind::VALUE);
260     RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
261     return iter.GetTaggedValue();
262 }
263 
AddEntriesFromIterable(JSThread *thread, const JSHandle<JSObject> &target, const JSHandle<JSTaggedValue> &iterable, const JSHandle<JSTaggedValue> &adder, ObjectFactory *factory)264 JSTaggedValue BuiltinsSharedMap::AddEntriesFromIterable(JSThread *thread, const JSHandle<JSObject> &target,
265     const JSHandle<JSTaggedValue> &iterable, const JSHandle<JSTaggedValue> &adder, ObjectFactory *factory)
266 {
267     BUILTINS_API_TRACE(thread, SharedMap, AddEntriesFromIterable);
268     // If IsCallable(adder) is false, throw a TypeError exception
269     if (!adder->IsCallable()) {
270         THROW_TYPE_ERROR_AND_RETURN(thread, "adder is not callable", adder.GetTaggedValue());
271     }
272     // Let iter be GetIterator(iterable).
273     JSHandle<JSTaggedValue> iter(JSIterator::GetIterator(thread, iterable));
274     // ReturnIfAbrupt(iter).
275     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, iter.GetTaggedValue());
276     JSHandle<JSTaggedValue> keyIndex(thread, JSTaggedValue(0));
277     JSHandle<JSTaggedValue> valueIndex(thread, JSTaggedValue(1));
278     JSHandle<JSTaggedValue> next = JSIterator::IteratorStep(thread, iter);
279     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, next.GetTaggedValue());
280     while (!next->IsFalse()) {
281         // Let nextValue be IteratorValue(next).
282         JSHandle<JSTaggedValue> nextValue(JSIterator::IteratorValue(thread, next));
283         // ReturnIfAbrupt(nextValue).
284         RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, next.GetTaggedValue());
285 
286         // If Type(nextItem) is not Object
287         if (!nextValue->IsECMAObject()) {
288             JSHandle<JSObject> typeError = factory->GetJSError(ErrorType::TYPE_ERROR,
289                                                                "nextItem is not Object", StackCheck::NO);
290             JSHandle<JSTaggedValue> record(
291                 factory->NewCompletionRecord(CompletionRecordType::THROW, JSHandle<JSTaggedValue>(typeError)));
292             JSTaggedValue ret = JSIterator::IteratorClose(thread, iter, record).GetTaggedValue();
293             if (!thread->HasPendingException()) {
294                 THROW_NEW_ERROR_AND_RETURN_VALUE(thread, typeError.GetTaggedValue(), ret);
295             }
296             return ret;
297         }
298         // Let k be Get(nextItem, "0").
299         JSHandle<JSTaggedValue> key = JSTaggedValue::GetProperty(thread, nextValue, keyIndex).GetValue();
300         // If k is an abrupt completion, return IteratorClose(iter, k).
301         if (thread->HasPendingException()) {
302             return JSIterator::IteratorCloseAndReturn(thread, iter);
303         }
304         // Let v be Get(nextItem, "1").
305         JSHandle<JSTaggedValue> value = JSTaggedValue::GetProperty(thread, nextValue, valueIndex).GetValue();
306         // If v is an abrupt completion, return IteratorClose(iter, v).
307         if (thread->HasPendingException()) {
308             return JSIterator::IteratorCloseAndReturn(thread, iter);
309         }
310         const uint32_t argsLength = 2;  // 2: key and value pair
311         JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
312         EcmaRuntimeCallInfo *info =
313             EcmaInterpreter::NewRuntimeCallInfo(thread, adder, JSHandle<JSTaggedValue>(target), undefined, argsLength);
314         RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, next.GetTaggedValue());
315         info->SetCallArg(key.GetTaggedValue(), value.GetTaggedValue());
316         JSFunction::Call(info);
317         // If status is an abrupt completion, return IteratorClose(iter, status).
318         if (thread->HasPendingException()) {
319             return JSIterator::IteratorCloseAndReturn(thread, iter);
320         }
321         // Let next be IteratorStep(iter).
322         next = JSIterator::IteratorStep(thread, iter);
323         // ReturnIfAbrupt(next).
324         RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, next.GetTaggedValue());
325     }
326     return target.GetTaggedValue();
327 }
328 }  // namespace panda::ecmascript::builtins
329