1 /*
2  * Copyright (c) 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 "containers_hashset.h"
17 
18 #include "ecmascript/containers/containers_errors.h"
19 #include "ecmascript/interpreter/interpreter.h"
20 #include "ecmascript/js_api/js_api_hashset.h"
21 #include "ecmascript/js_api/js_api_hashset_iterator.h"
22 #include "ecmascript/js_function.h"
23 
24 namespace panda::ecmascript::containers {
HashSetConstructor(EcmaRuntimeCallInfo *argv)25 JSTaggedValue ContainersHashSet::HashSetConstructor(EcmaRuntimeCallInfo *argv)
26 {
27     ASSERT(argv != nullptr);
28     JSThread *thread = argv->GetThread();
29     BUILTINS_API_TRACE(thread, HashSet, Constructor);
30     [[maybe_unused]] EcmaHandleScope handleScope(thread);
31     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
32     JSHandle<JSTaggedValue> newTarget = GetNewTarget(argv);
33     if (newTarget->IsUndefined()) {
34         JSTaggedValue error =
35             ContainerError::BusinessError(thread, ErrorFlag::IS_NULL_ERROR,
36                                           "The HashSet's constructor cannot be directly invoked");
37         THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
38     }
39 
40     JSHandle<JSTaggedValue> constructor = GetConstructor(argv);
41     JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(constructor), newTarget);
42     RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
43 
44     JSHandle<JSAPIHashSet> hashSet = JSHandle<JSAPIHashSet>::Cast(obj);
45     JSTaggedValue hashSetArray = TaggedHashArray::Create(thread);
46     hashSet->SetTable(thread, hashSetArray);
47     hashSet->SetSize(0);
48 
49     return hashSet.GetTaggedValue();
50 }
51 
GetIteratorObj(EcmaRuntimeCallInfo *argv)52 JSTaggedValue ContainersHashSet::GetIteratorObj(EcmaRuntimeCallInfo *argv)
53 {
54     ASSERT(argv != nullptr);
55     JSThread *thread = argv->GetThread();
56     BUILTINS_API_TRACE(thread, HashSet, GetIteratorObj);
57     [[maybe_unused]] EcmaHandleScope handleScope(thread);
58     JSHandle<JSTaggedValue> self = GetThis(argv);
59     if (!self->IsJSAPIHashSet()) {
60         if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPIHashSet()) {
61             self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
62         } else {
63             JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
64                                                                 "The Symbol.iterator method cannot be bound");
65             THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
66         }
67     }
68     JSHandle<JSTaggedValue> iter = JSAPIHashSetIterator::CreateHashSetIterator(thread, self, IterationKind::VALUE);
69     return iter.GetTaggedValue();
70 }
71 
Values(EcmaRuntimeCallInfo *argv)72 JSTaggedValue ContainersHashSet::Values(EcmaRuntimeCallInfo *argv)
73 {
74     ASSERT(argv != nullptr);
75     JSThread *thread = argv->GetThread();
76     BUILTINS_API_TRACE(thread, HashSet, Values);
77     [[maybe_unused]] EcmaHandleScope handleScope(thread);
78     JSHandle<JSTaggedValue> self = GetThis(argv);
79     if (!self->IsJSAPIHashSet()) {
80         if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPIHashSet()) {
81             self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
82         } else {
83             JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
84                                                                 "The values method cannot be bound");
85             THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
86         }
87     }
88     JSHandle<JSTaggedValue> iter = JSAPIHashSetIterator::CreateHashSetIterator(thread, self, IterationKind::VALUE);
89     return iter.GetTaggedValue();
90 }
91 
Entries(EcmaRuntimeCallInfo *argv)92 JSTaggedValue ContainersHashSet::Entries(EcmaRuntimeCallInfo *argv)
93 {
94     ASSERT(argv != nullptr);
95     JSThread *thread = argv->GetThread();
96     BUILTINS_API_TRACE(thread, HashSet, Entries);
97     [[maybe_unused]] EcmaHandleScope handleScope(thread);
98     JSHandle<JSTaggedValue> self = GetThis(argv);
99     if (!self->IsJSAPIHashSet()) {
100         if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPIHashSet()) {
101             self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
102         } else {
103             JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
104                                                                 "The entries method cannot be bound");
105             THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
106         }
107     }
108     JSHandle<JSTaggedValue> iter =
109         JSAPIHashSetIterator::CreateHashSetIterator(thread, self, IterationKind::KEY_AND_VALUE);
110     return iter.GetTaggedValue();
111 }
112 
Add(EcmaRuntimeCallInfo *argv)113 JSTaggedValue ContainersHashSet::Add(EcmaRuntimeCallInfo *argv)
114 {
115     ASSERT(argv != nullptr);
116     JSThread *thread = argv->GetThread();
117     BUILTINS_API_TRACE(thread, HashSet, Add);
118     [[maybe_unused]] EcmaHandleScope handleScope(thread);
119     JSHandle<JSTaggedValue> self = GetThis(argv);
120 
121     if (!self->IsJSAPIHashSet()) {
122         if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPIHashSet()) {
123             self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
124         } else {
125             JSTaggedValue error = ContainerError::BusinessError(thread, BIND_ERROR,
126                                                                 "The add method cannot be bound");
127             THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
128         }
129     }
130 
131     JSHandle<JSTaggedValue> value = GetCallArg(argv, 0);
132     JSHandle<JSAPIHashSet> hashSet = JSHandle<JSAPIHashSet>::Cast(self);
133     JSAPIHashSet::Add(thread, hashSet, value);
134     RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
135     return JSTaggedValue::True();
136 }
137 
Remove(EcmaRuntimeCallInfo *argv)138 JSTaggedValue ContainersHashSet::Remove(EcmaRuntimeCallInfo *argv)
139 {
140     ASSERT(argv != nullptr);
141     JSThread *thread = argv->GetThread();
142     BUILTINS_API_TRACE(thread, HashSet, Remove);
143     [[maybe_unused]] EcmaHandleScope handleScope(thread);
144     JSHandle<JSTaggedValue> self = GetThis(argv);
145 
146     if (!self->IsJSAPIHashSet()) {
147         if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPIHashSet()) {
148             self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
149         } else {
150             JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
151                                                                 "The remove method cannot be bound");
152             THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
153         }
154     }
155 
156     JSHandle<JSTaggedValue> key = GetCallArg(argv, 0);
157     JSHandle<JSAPIHashSet> hashSet = JSHandle<JSAPIHashSet>::Cast(self);
158     return JSAPIHashSet::Remove(thread, hashSet, key.GetTaggedValue());
159 }
160 
Has(EcmaRuntimeCallInfo *argv)161 JSTaggedValue ContainersHashSet::Has(EcmaRuntimeCallInfo *argv)
162 {
163     ASSERT(argv != nullptr);
164     JSThread *thread = argv->GetThread();
165     BUILTINS_API_TRACE(thread, HashSet, Has);
166     [[maybe_unused]] EcmaHandleScope handleScope(thread);
167     JSHandle<JSTaggedValue> self = GetThis(argv);
168 
169     if (!self->IsJSAPIHashSet()) {
170         if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPIHashSet()) {
171             self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
172         } else {
173             JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
174                                                                 "The has method cannot be bound");
175             THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
176         }
177     }
178     JSHandle<JSTaggedValue> value = GetCallArg(argv, 0);
179     JSHandle<JSAPIHashSet> jsHashSet = JSHandle<JSAPIHashSet>::Cast(self);
180     return jsHashSet->Has(thread, value.GetTaggedValue());
181 }
182 
Clear(EcmaRuntimeCallInfo *argv)183 JSTaggedValue ContainersHashSet::Clear(EcmaRuntimeCallInfo *argv)
184 {
185     ASSERT(argv != nullptr);
186     JSThread *thread = argv->GetThread();
187     BUILTINS_API_TRACE(thread, HashSet, Clear);
188     [[maybe_unused]] EcmaHandleScope handleScope(thread);
189     JSHandle<JSTaggedValue> self = GetThis(argv);
190 
191     if (!self->IsJSAPIHashSet()) {
192         if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPIHashSet()) {
193             self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
194         } else {
195             JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
196                                                                 "The clear method cannot be bound");
197             THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
198         }
199     }
200     JSHandle<JSAPIHashSet> jsHashSet = JSHandle<JSAPIHashSet>::Cast(self);
201     jsHashSet->Clear(thread);
202     return JSTaggedValue::Undefined();
203 }
204 
GetLength(EcmaRuntimeCallInfo *argv)205 JSTaggedValue ContainersHashSet::GetLength(EcmaRuntimeCallInfo *argv)
206 {
207     ASSERT(argv != nullptr);
208     JSThread *thread = argv->GetThread();
209     BUILTINS_API_TRACE(thread, HashSet, GetLength);
210     [[maybe_unused]] EcmaHandleScope handleScope(thread);
211     JSHandle<JSTaggedValue> self = GetThis(argv);
212 
213     if (!self->IsJSAPIHashSet()) {
214         if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPIHashSet()) {
215             self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
216         } else {
217             JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
218                                                                 "The getLength method cannot be bound");
219             THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
220         }
221     }
222 
223     JSHandle<JSAPIHashSet> jsHashSet = JSHandle<JSAPIHashSet>::Cast(self);
224     return jsHashSet->GetLength();
225 }
226 
IsEmpty(EcmaRuntimeCallInfo *argv)227 JSTaggedValue ContainersHashSet::IsEmpty(EcmaRuntimeCallInfo *argv)
228 {
229     ASSERT(argv != nullptr);
230     JSThread *thread = argv->GetThread();
231     BUILTINS_API_TRACE(thread, HashSet, IsEmpty);
232     [[maybe_unused]] EcmaHandleScope handleScope(thread);
233     JSHandle<JSTaggedValue> self = GetThis(argv);
234 
235     if (!self->IsJSAPIHashSet()) {
236         if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPIHashSet()) {
237             self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
238         } else {
239             JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
240                                                                 "The isEmpty method cannot be bound");
241             THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
242         }
243     }
244     JSHandle<JSAPIHashSet> jsHashSet = JSHandle<JSAPIHashSet>::Cast(self);
245     return jsHashSet->IsEmpty();
246 }
247 
ForEach(EcmaRuntimeCallInfo *argv)248 JSTaggedValue ContainersHashSet::ForEach(EcmaRuntimeCallInfo *argv)
249 {
250     ASSERT(argv != nullptr);
251     JSThread *thread = argv->GetThread();
252     BUILTINS_API_TRACE(thread, HashSet, ForEach);
253     [[maybe_unused]] EcmaHandleScope handleScope(thread);
254     JSHandle<JSTaggedValue> thisHandle = GetThis(argv);
255     if (!thisHandle->IsJSAPIHashSet()) {
256         if (thisHandle->IsJSProxy() && JSHandle<JSProxy>::Cast(thisHandle)->GetTarget().IsJSAPIHashSet()) {
257             thisHandle = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(thisHandle)->GetTarget());
258         } else {
259             JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
260                                                                 "The forEach method cannot be bound");
261             THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
262         }
263     }
264     JSHandle<JSTaggedValue> callbackFnHandle = GetCallArg(argv, 0);
265     if (!callbackFnHandle->IsCallable()) {
266         JSHandle<EcmaString> result = JSTaggedValue::ToString(thread, callbackFnHandle);
267         RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
268         CString errorMsg =
269             "The type of \"callbackfn\" must be callable. Received value is: " + ConvertToString(*result);
270         JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::TYPE_ERROR, errorMsg.c_str());
271         THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
272     }
273     JSHandle<JSTaggedValue> thisArgHandle = GetCallArg(argv, 1);
274     JSHandle<JSAPIHashSet> hashSet = JSHandle<JSAPIHashSet>::Cast(thisHandle);
275     JSHandle<TaggedHashArray> table(thread, hashSet->GetTable());
276     uint32_t len = table->GetLength();
277     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
278     JSMutableHandle<TaggedQueue> queue(thread, factory->NewTaggedQueue(0));
279     JSMutableHandle<TaggedNode> node(thread, JSTaggedValue::Undefined());
280     JSMutableHandle<JSTaggedValue> currentKey(thread, JSTaggedValue::Undefined());
281     uint32_t index = 0;
282     JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
283     while (index < len) {
284         node.Update(TaggedHashArray::GetCurrentNode(thread, queue, table, index));
285         if (!node.GetTaggedValue().IsHole()) {
286             currentKey.Update(node->GetKey());
287             EcmaRuntimeCallInfo *info =
288                 EcmaInterpreter::NewRuntimeCallInfo(thread, callbackFnHandle,
289                                                     thisArgHandle, undefined, 3); // 3: three args
290             RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
291             info->SetCallArg(currentKey.GetTaggedValue(), currentKey.GetTaggedValue(), thisHandle.GetTaggedValue());
292             JSTaggedValue funcResult = JSFunction::Call(info);
293             RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, funcResult);
294         }
295     }
296     return JSTaggedValue::Undefined();
297 }
298 } // namespace panda::ecmascript::containers
299