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
34using namespace panda::ecmascript;
35using namespace panda::ecmascript::builtins;
36using BuiltinsBase = panda::ecmascript::base::BuiltinsBase;
37static int testValue = 0;
38
39namespace panda::test {
40class BuiltinsFinalizationRegistryTest : public BaseTestWithScope<false> {
41public:
42    class TestClass : public base::BuiltinsBase {
43    public:
44        static JSTaggedValue cleanupCallback()
45        {
46            ++testValue;
47            return JSTaggedValue::Undefined();
48        }
49    };
50};
51
52JSTaggedValue 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)
73HWTEST_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
91void 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
99JSHandle<JSFinalizationRegistry> Common(JSThread* thread)
100{
101    JSTaggedValue result = CreateFinalizationRegistryConstructor(thread);
102    JSHandle<JSFinalizationRegistry> jsfinalizationRegistry(thread, result);
103    return jsfinalizationRegistry;
104}
105
106void 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)
126HWTEST_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 ])
144HWTEST_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 ])
158HWTEST_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 ])
186HWTEST_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 ])
222HWTEST_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 ])
259HWTEST_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 ])
293HWTEST_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
312HWTEST_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
343HWTEST_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
374HWTEST_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