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_finalization_registry.h"
17 #include "ecmascript/ecma_vm.h"
18 #include "ecmascript/global_env.h"
19 #include "ecmascript/jobs/micro_job_queue.h"
20 #include "ecmascript/js_array.h"
21 #include "ecmascript/js_array_iterator.h"
22 #include "ecmascript/js_finalization_registry.h"
23 #include "ecmascript/js_handle.h"
24 #include "ecmascript/js_hclass.h"
25 #include "ecmascript/js_object-inl.h"
26 #include "ecmascript/js_tagged_value.h"
27 #include "ecmascript/js_tagged_value-inl.h"
28 #include "ecmascript/js_thread.h"
29 
30 #include "ecmascript/object_factory.h"
31 #include "ecmascript/object_operator.h"
32 #include "ecmascript/tests/test_helper.h"
33 
34 using namespace panda::ecmascript;
35 using namespace panda::ecmascript::builtins;
36 using BuiltinsBase = panda::ecmascript::base::BuiltinsBase;
37 static int testValue = 0;
38 
39 namespace panda::test {
40 class BuiltinsFinalizationRegistryTest : public BaseTestWithScope<false> {
41 public:
42     class TestClass : public base::BuiltinsBase {
43     public:
cleanupCallback()44         static JSTaggedValue cleanupCallback()
45         {
46             ++testValue;
47             return JSTaggedValue::Undefined();
48         }
49     };
50 };
51 
CreateFinalizationRegistryConstructor(JSThread *thread)52 JSTaggedValue CreateFinalizationRegistryConstructor(JSThread *thread)
53 {
54     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
55     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
56 
57     JSHandle<JSFunction> finalizationRegistry(env->GetBuiltinsFinalizationRegistryFunction());
58     JSHandle<JSFunction> handleFunc = factory->NewJSFunction(
59         env, reinterpret_cast<void *>(BuiltinsFinalizationRegistryTest::TestClass::cleanupCallback));
60 
61     auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue(*finalizationRegistry), 6);
62     ecmaRuntimeCallInfo->SetFunction(finalizationRegistry.GetTaggedValue());
63     ecmaRuntimeCallInfo->SetThis(JSTaggedValue::Undefined());
64     ecmaRuntimeCallInfo->SetCallArg(0, handleFunc.GetTaggedValue());
65 
66     [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo);
67     JSTaggedValue res = BuiltinsFinalizationRegistry::FinalizationRegistryConstructor(ecmaRuntimeCallInfo);
68     TestHelper::TearDownFrame(thread, prev);
69     return res;
70 }
71 
72 // new FinalizationRegistry (cleanupCallback)
HWTEST_F_L0(BuiltinsFinalizationRegistryTest, FinalizationRegistryConstructor)73 HWTEST_F_L0(BuiltinsFinalizationRegistryTest, FinalizationRegistryConstructor)
74 {
75     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
76     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
77 
78     JSHandle<JSFunction> finalizationRegistry(env->GetBuiltinsFinalizationRegistryFunction());
79     JSHandle<JSFunction> handleFunc = factory->NewJSFunction(env, reinterpret_cast<void *>(TestClass::cleanupCallback));
80 
81     auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue(*finalizationRegistry), 6);
82     ecmaRuntimeCallInfo->SetFunction(finalizationRegistry.GetTaggedValue());
83     ecmaRuntimeCallInfo->SetThis(JSTaggedValue::Undefined());
84     ecmaRuntimeCallInfo->SetCallArg(0, handleFunc.GetTaggedValue());
85 
86     [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo);
87     JSTaggedValue result = BuiltinsFinalizationRegistry::FinalizationRegistryConstructor(ecmaRuntimeCallInfo);
88     ASSERT_TRUE(result.IsECMAObject());
89 }
90 
KeyValueCommon(JSThread* thread, JSHandle<JSTaggedValue>& target)91 void KeyValueCommon(JSThread* thread, JSHandle<JSTaggedValue>& target)
92 {
93     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
94     JSHandle<JSTaggedValue> key(factory->NewFromASCII("1"));
95     JSHandle<JSTaggedValue> value(thread, JSTaggedValue(1));
96     JSObject::SetProperty(thread, target, key, value);
97 }
98 
Common(JSThread* thread)99 JSHandle<JSFinalizationRegistry> Common(JSThread* thread)
100 {
101     JSTaggedValue result = CreateFinalizationRegistryConstructor(thread);
102     JSHandle<JSFinalizationRegistry> jsfinalizationRegistry(thread, result);
103     return jsfinalizationRegistry;
104 }
105 
RegisterUnRegisterCommon(JSThread *thread, JSHandle<JSFinalizationRegistry> &jsfinalizationRegistry, std::vector<JSTaggedValue> &args, int32_t maxArg, bool unRegister = false)106 void RegisterUnRegisterCommon(JSThread *thread, JSHandle<JSFinalizationRegistry> &jsfinalizationRegistry,
107                               std::vector<JSTaggedValue> &args, int32_t maxArg, bool unRegister = false)
108 {
109     auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue::Undefined(), maxArg);
110     ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
111     ecmaRuntimeCallInfo->SetThis(jsfinalizationRegistry.GetTaggedValue());
112     for (size_t i = 0; i < args.size(); i++) {
113         ecmaRuntimeCallInfo->SetCallArg(i, args[i]);
114     }
115 
116     [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo);
117     if (unRegister) {
118         BuiltinsFinalizationRegistry::Unregister(ecmaRuntimeCallInfo);
119     } else {
120         BuiltinsFinalizationRegistry::Register(ecmaRuntimeCallInfo);
121     }
122     TestHelper::TearDownFrame(thread, prev);
123 }
124 
125 // finalizationRegistry.Register(target, heldValue)
HWTEST_F_L0(BuiltinsFinalizationRegistryTest, Register0)126 HWTEST_F_L0(BuiltinsFinalizationRegistryTest, Register0)
127 {
128     testValue = 0;
129     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
130     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
131     JSHandle<JSTaggedValue> objectFunc = env->GetObjectFunction();
132 
133     auto jsfinalizationRegistry = Common(thread);
134     JSHandle<JSTaggedValue> target(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objectFunc), objectFunc));
135     KeyValueCommon(thread, target);
136 
137     std::vector<JSTaggedValue> args{target.GetTaggedValue(), JSTaggedValue(10)};
138 
139     RegisterUnRegisterCommon(thread, jsfinalizationRegistry, args, 8);
140     ASSERT_EQ(testValue, 0);
141 }
142 
143 // finalizationRegistry.Register(target, heldValue [ , unregisterToken ])
HWTEST_F_L0(BuiltinsFinalizationRegistryTest, Register1)144 HWTEST_F_L0(BuiltinsFinalizationRegistryTest, Register1)
145 {
146     testValue = 0;
147     auto jsfinalizationRegistry = Common(thread);
148     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
149     JSHandle<JSTaggedValue> objectFunc = thread->GetEcmaVM()->GetGlobalEnv()->GetObjectFunction();
150     JSHandle<JSTaggedValue> target(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objectFunc), objectFunc));
151     KeyValueCommon(thread, target);
152     std::vector<JSTaggedValue> args{target.GetTaggedValue(), JSTaggedValue(10), target.GetTaggedValue()};
153     RegisterUnRegisterCommon(thread, jsfinalizationRegistry, args, 10);
154     ASSERT_EQ(testValue, 0);
155 }
156 
157 // finalizationRegistry.Register(target, heldValue [ , unregisterToken ])
HWTEST_F_L0(BuiltinsFinalizationRegistryTest, Register2)158 HWTEST_F_L0(BuiltinsFinalizationRegistryTest, Register2)
159 {
160     testValue = 0;
161     EcmaVM *vm = thread->GetEcmaVM();
162     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
163     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
164     JSHandle<JSTaggedValue> objectFunc = env->GetObjectFunction();
165 
166     auto jsfinalizationRegistry = Common(thread);
167     vm->SetEnableForceGC(false);
168     JSTaggedValue target = JSTaggedValue::Undefined();
169     {
170         [[maybe_unused]] EcmaHandleScope handleScope(thread);
171         auto obj =
172             factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objectFunc), objectFunc);
173         target = obj.GetTaggedValue();
174         std::vector<JSTaggedValue> args{target, JSTaggedValue(10), target};
175         RegisterUnRegisterCommon(thread, jsfinalizationRegistry, args, 10);
176     }
177     vm->CollectGarbage(TriggerGCType::FULL_GC);
178     if (!thread->HasPendingException()) {
179         job::MicroJobQueue::ExecutePendingJob(thread, vm->GetJSThread()->GetCurrentEcmaContext()->GetMicroJobQueue());
180     }
181     vm->SetEnableForceGC(true);
182     ASSERT_EQ(testValue, 1);
183 }
184 
185 // finalizationRegistry.Register(target, heldValue [ , unregisterToken ])
HWTEST_F_L0(BuiltinsFinalizationRegistryTest, Register3)186 HWTEST_F_L0(BuiltinsFinalizationRegistryTest, Register3)
187 {
188     testValue = 0;
189     EcmaVM *vm = thread->GetEcmaVM();
190     auto jsfinalizationRegistry = Common(thread);
191 
192     vm->SetEnableForceGC(false);
193     JSTaggedValue target = JSTaggedValue::Undefined();
194     JSTaggedValue target1 = JSTaggedValue::Undefined();
195     {
196         ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
197         JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
198         JSHandle<JSTaggedValue> objectFunc = env->GetObjectFunction();
199         [[maybe_unused]] EcmaHandleScope handleScope(thread);
200         auto obj =
201             factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objectFunc), objectFunc);
202         auto obj1 =
203             factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objectFunc), objectFunc);
204         target = obj.GetTaggedValue();
205 
206         std::vector<JSTaggedValue> args{target, JSTaggedValue(10), target};
207         RegisterUnRegisterCommon(thread, jsfinalizationRegistry, args, 10);
208 
209         target1 = obj1.GetTaggedValue();
210         std::vector<JSTaggedValue> args1{target1, JSTaggedValue(10), target1};
211         RegisterUnRegisterCommon(thread, jsfinalizationRegistry, args, 10);
212     }
213     vm->CollectGarbage(TriggerGCType::FULL_GC);
214     if (!thread->HasPendingException()) {
215         job::MicroJobQueue::ExecutePendingJob(thread, vm->GetJSThread()->GetCurrentEcmaContext()->GetMicroJobQueue());
216     }
217     vm->SetEnableForceGC(true);
218     ASSERT_EQ(testValue, 2);
219 }
220 
221 // finalizationRegistry.Register(target, heldValue [ , unregisterToken ])
HWTEST_F_L0(BuiltinsFinalizationRegistryTest, Register4)222 HWTEST_F_L0(BuiltinsFinalizationRegistryTest, Register4)
223 {
224     testValue = 0;
225     EcmaVM *vm = thread->GetEcmaVM();
226     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
227     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
228     JSHandle<JSTaggedValue> objectFunc = env->GetObjectFunction();
229 
230     auto jsfinalizationRegistry = Common(thread);
231     auto jsfinalizationRegistry1 = Common(thread);
232     vm->SetEnableForceGC(false);
233     JSTaggedValue target = JSTaggedValue::Undefined();
234     JSTaggedValue target1 = JSTaggedValue::Undefined();
235     {
236         [[maybe_unused]] EcmaHandleScope handleScope(thread);
237         auto obj =
238             factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objectFunc), objectFunc);
239         auto obj1 =
240             factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objectFunc), objectFunc);
241         target = obj.GetTaggedValue();
242         target1 = obj1.GetTaggedValue();
243 
244         std::vector<JSTaggedValue> args{target, JSTaggedValue(10), target};
245         RegisterUnRegisterCommon(thread, jsfinalizationRegistry, args, 10);
246 
247         std::vector<JSTaggedValue> args1{target1, JSTaggedValue(10), target1};
248         RegisterUnRegisterCommon(thread, jsfinalizationRegistry1, args, 10);
249     }
250     vm->CollectGarbage(TriggerGCType::FULL_GC);
251     if (!thread->HasPendingException()) {
252         job::MicroJobQueue::ExecutePendingJob(thread, vm->GetJSThread()->GetCurrentEcmaContext()->GetMicroJobQueue());
253     }
254     vm->SetEnableForceGC(true);
255     ASSERT_EQ(testValue, 2);
256 }
257 
258 // finalizationRegistry.Register(target, heldValue [ , unregisterToken ])
HWTEST_F_L0(BuiltinsFinalizationRegistryTest, Register5)259 HWTEST_F_L0(BuiltinsFinalizationRegistryTest, Register5)
260 {
261     testValue = 0;
262     EcmaVM *vm = thread->GetEcmaVM();
263     auto jsfinalizationRegistry = Common(thread);
264     vm->SetEnableForceGC(false);
265     JSTaggedValue target = JSTaggedValue::Undefined();
266     JSTaggedValue target1 = JSTaggedValue::Undefined();
267     {
268         ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
269         JSHandle<JSTaggedValue> objectFunc = thread->GetEcmaVM()->GetGlobalEnv()->GetObjectFunction();
270         [[maybe_unused]] EcmaHandleScope handleScope(thread);
271         auto obj =
272             factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objectFunc), objectFunc);
273         auto obj1 =
274             factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objectFunc), objectFunc);
275         target = obj.GetTaggedValue();
276         target1 = obj1.GetTaggedValue();
277 
278         std::vector<JSTaggedValue> args{target, JSTaggedValue(10), target};
279         RegisterUnRegisterCommon(thread, jsfinalizationRegistry, args, 10);
280 
281         std::vector<JSTaggedValue> args1{target1, JSTaggedValue(10), target};
282         RegisterUnRegisterCommon(thread, jsfinalizationRegistry, args, 10);
283     }
284     vm->CollectGarbage(TriggerGCType::FULL_GC);
285     if (!thread->HasPendingException()) {
286         job::MicroJobQueue::ExecutePendingJob(thread, vm->GetJSThread()->GetCurrentEcmaContext()->GetMicroJobQueue());
287     }
288     vm->SetEnableForceGC(true);
289     ASSERT_EQ(testValue, 2);
290 }
291 
292 // finalizationRegistry.Unregister(unregisterToken ])
HWTEST_F_L0(BuiltinsFinalizationRegistryTest, Unregister1)293 HWTEST_F_L0(BuiltinsFinalizationRegistryTest, Unregister1)
294 {
295     testValue = 0;
296     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
297     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
298     JSHandle<JSTaggedValue> objectFunc = env->GetObjectFunction();
299 
300     auto jsfinalizationRegistry = Common(thread);
301     JSHandle<JSTaggedValue> target(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objectFunc), objectFunc));
302     KeyValueCommon(thread, target);
303 
304     std::vector<JSTaggedValue> args{target.GetTaggedValue(), JSTaggedValue(10), target.GetTaggedValue()};
305     RegisterUnRegisterCommon(thread, jsfinalizationRegistry, args, 10);
306 
307     std::vector<JSTaggedValue> args1{target.GetTaggedValue()};
308     RegisterUnRegisterCommon(thread, jsfinalizationRegistry, args1, 6, true);
309     ASSERT_EQ(testValue, 0);
310 }
311 
HWTEST_F_L0(BuiltinsFinalizationRegistryTest, Unregister2)312 HWTEST_F_L0(BuiltinsFinalizationRegistryTest, Unregister2)
313 {
314     testValue = 0;
315     EcmaVM *vm = thread->GetEcmaVM();
316     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
317     JSHandle<JSTaggedValue> objectFunc = thread->GetEcmaVM()->GetGlobalEnv()->GetObjectFunction();
318 
319     auto jsfinalizationRegistry = Common(thread);
320     vm->SetEnableForceGC(false);
321     JSTaggedValue target = JSTaggedValue::Undefined();
322     {
323         [[maybe_unused]] EcmaHandleScope handleScope(thread);
324         auto obj =
325             factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objectFunc), objectFunc);
326         target = obj.GetTaggedValue();
327 
328         std::vector<JSTaggedValue> args{target, JSTaggedValue(10), target};
329         RegisterUnRegisterCommon(thread, jsfinalizationRegistry, args, 10);
330 
331         std::vector<JSTaggedValue> args1{target};
332         RegisterUnRegisterCommon(thread, jsfinalizationRegistry, args1, 6, true);
333     }
334     vm->CollectGarbage(TriggerGCType::FULL_GC);
335     if (!thread->HasPendingException()) {
336         job::MicroJobQueue::ExecutePendingJob(thread, vm->GetJSThread()->GetCurrentEcmaContext()->GetMicroJobQueue());
337     }
338     vm->SetEnableForceGC(true);
339     ASSERT_EQ(testValue, 0);
340 }
341 
342 // finalizationRegistry.Register(target, heldValue [ , unregisterToken ]) target and unregisterToken Symbol
HWTEST_F_L0(BuiltinsFinalizationRegistryTest, RegisterTargetSymbol)343 HWTEST_F_L0(BuiltinsFinalizationRegistryTest, RegisterTargetSymbol)
344 {
345     testValue = 0;
346     EcmaVM *vm = thread->GetEcmaVM();
347 
348     auto jsfinalizationRegistry = Common(thread);
349 
350     vm->SetEnableForceGC(false);
351     JSTaggedValue target = JSTaggedValue::Undefined();
352     JSTaggedValue target1 = JSTaggedValue::Undefined();
353     {
354         [[maybe_unused]] EcmaHandleScope handleScope(thread);
355         JSHandle<JSSymbol> symbol1 = thread->GetEcmaVM()->GetFactory()->NewJSSymbol();
356         JSHandle<JSSymbol> symbol2 = thread->GetEcmaVM()->GetFactory()->NewJSSymbol();
357         target = symbol1.GetTaggedValue();
358         target1 = symbol2.GetTaggedValue();
359         std::vector<JSTaggedValue> args{target, JSTaggedValue(10), target};
360         RegisterUnRegisterCommon(thread, jsfinalizationRegistry, args, 10);
361 
362         std::vector<JSTaggedValue> args1{target1, JSTaggedValue(10), target1};
363         RegisterUnRegisterCommon(thread, jsfinalizationRegistry, args1, 10);
364     }
365     vm->CollectGarbage(TriggerGCType::FULL_GC);
366     if (!thread->HasPendingException()) {
367         job::MicroJobQueue::ExecutePendingJob(thread, vm->GetJSThread()->GetCurrentEcmaContext()->GetMicroJobQueue());
368     }
369     vm->SetEnableForceGC(true);
370     ASSERT_EQ(testValue, 2);
371 }
372 
373 // finalizationRegistry.Unregister(unregisterToken) unregisterToken Symbol
HWTEST_F_L0(BuiltinsFinalizationRegistryTest, UnregisterTokenSymbol)374 HWTEST_F_L0(BuiltinsFinalizationRegistryTest, UnregisterTokenSymbol)
375 {
376     testValue = 0;
377     EcmaVM *vm = thread->GetEcmaVM();
378 
379     auto jsfinalizationRegistry = Common(thread);
380     vm->SetEnableForceGC(false);
381     JSTaggedValue target = JSTaggedValue::Undefined();
382     {
383         [[maybe_unused]] EcmaHandleScope handleScope(thread);
384         JSHandle<JSSymbol> symbol = thread->GetEcmaVM()->GetFactory()->NewJSSymbol();
385         target = symbol.GetTaggedValue();
386 
387         std::vector<JSTaggedValue> args{target, JSTaggedValue(10), target};
388         RegisterUnRegisterCommon(thread, jsfinalizationRegistry, args, 10);
389 
390         std::vector<JSTaggedValue> args1{target};
391         RegisterUnRegisterCommon(thread, jsfinalizationRegistry, args1, 6, true);
392     }
393     vm->CollectGarbage(TriggerGCType::FULL_GC);
394     if (!thread->HasPendingException()) {
395         job::MicroJobQueue::ExecutePendingJob(thread, vm->GetJSThread()->GetCurrentEcmaContext()->GetMicroJobQueue());
396     }
397     vm->SetEnableForceGC(true);
398     ASSERT_EQ(testValue, 0);
399 }
400 }  // namespace panda::test
401