• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "ecmascript/builtins/builtins_finalization_registry.h"
17 #include "ecmascript/global_env.h"
18 #include "ecmascript/linked_hash_table.h"
19 #include "ecmascript/jobs/micro_job_queue.h"
20 #include "ecmascript/js_finalization_registry.h"
21 #include "ecmascript/tests/test_helper.h"
22 
23 using namespace panda;
24 using namespace panda::ecmascript;
25 using namespace panda::ecmascript::builtins;
26 
27 static int testValue = 0;
28 
29 namespace panda::test {
30 class JSFinalizationRegistryTest : public BaseTestWithScope<false> {
31 public:
32     class TestClass : public base::BuiltinsBase {
33     public:
callbackTest()34         static JSTaggedValue callbackTest()
35         {
36             testValue++;
37             return JSTaggedValue::Undefined();
38         }
39     };
40 };
41 
CreateFinalizationRegistry(JSThread * thread)42 static JSHandle<JSTaggedValue> CreateFinalizationRegistry(JSThread *thread)
43 {
44     auto vm = thread->GetEcmaVM();
45     auto factory = vm->GetFactory();
46     auto env = vm->GetGlobalEnv();
47 
48     JSHandle<JSFunction> finaRegFunc(env->GetBuiltinsFinalizationRegistryFunction());
49     JSHandle<JSFunction> callbackFunc = factory->NewJSFunction(env, reinterpret_cast<void *>(
50         JSFinalizationRegistryTest::TestClass::callbackTest));
51     auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue(*finaRegFunc), 6);
52     ecmaRuntimeCallInfo->SetFunction(finaRegFunc.GetTaggedValue());
53     ecmaRuntimeCallInfo->SetThis(JSTaggedValue::Undefined());
54     ecmaRuntimeCallInfo->SetCallArg(0, callbackFunc.GetTaggedValue());
55 
56     auto prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo);
57     JSTaggedValue finalizationRegistry =
58         BuiltinsFinalizationRegistry::FinalizationRegistryConstructor(ecmaRuntimeCallInfo);
59     JSHandle<JSTaggedValue> finalizationRegistryHandle(thread, finalizationRegistry);
60     TestHelper::TearDownFrame(thread, prev);
61     return finalizationRegistryHandle;
62 }
63 
64 /**
65  * @tc.name: Register
66  * @tc.desc: Register the target object to the JSFinalizationRegistry object, and call the callback method after the
67  *           target object is garbage collected. And a unregistration token, which is passed to the unregister method
68  *           when the finalizer is no longer needed. The held value, which is used to represent that object when
69  *           cleaning it up in the finalizer.
70  * @tc.type: FUNC
71  * @tc.require:
72  */
HWTEST_F_L0(JSFinalizationRegistryTest,Register_001)73 HWTEST_F_L0(JSFinalizationRegistryTest, Register_001)
74 {
75     testValue = 0;
76     auto vm = thread->GetEcmaVM();
77     auto factory = vm->GetFactory();
78     auto env = vm->GetGlobalEnv();
79 
80     JSHandle<JSTaggedValue> objectFunc = env->GetObjectFunction();
81     JSHandle<JSTaggedValue> target(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objectFunc), objectFunc));
82     JSHandle<JSTaggedValue> key(factory->NewFromASCII("key1"));
83     JSHandle<JSTaggedValue> value(thread, JSTaggedValue(1));
84     JSObject::SetProperty(thread, target, key, value);
85     JSHandle<JSTaggedValue> heldValue(thread, JSTaggedValue(100));
86     JSHandle<JSTaggedValue> unregisterToken(thread, JSTaggedValue::Undefined());
87     JSHandle<JSTaggedValue> constructor = CreateFinalizationRegistry(thread);
88     JSHandle<JSFinalizationRegistry> finaRegObj(thread, constructor.GetTaggedValue());
89 
90     // If unregisterToken is undefined, use vector to store
91     JSHandle<CellRecord> cellRecord = factory->NewCellRecord();
92     cellRecord->SetToWeakRefTarget(thread, target.GetTaggedValue());
93     cellRecord->SetHeldValue(thread, heldValue);
94     JSHandle<JSTaggedValue> cell(cellRecord);
95     JSHandle<CellRecordVector> expectNoUnregister(thread, finaRegObj->GetNoUnregister());
96     expectNoUnregister = CellRecordVector::Append(thread, expectNoUnregister, cell);
97 
98     JSFinalizationRegistry::Register(thread, target, heldValue, unregisterToken, finaRegObj);
99     JSHandle<JSTaggedValue> noUnregister(thread, finaRegObj->GetNoUnregister());
100     JSHandle<JSTaggedValue> finRegLists = env->GetFinRegLists();
101     EXPECT_EQ(finRegLists.GetTaggedValue().GetRawData(),
102         JSHandle<JSTaggedValue>::Cast(finaRegObj).GetTaggedValue().GetRawData());
103     EXPECT_EQ(expectNoUnregister.GetTaggedValue().GetRawData(), noUnregister.GetTaggedValue().GetRawData());
104     EXPECT_EQ(testValue, 0);
105 }
106 
HWTEST_F_L0(JSFinalizationRegistryTest,Register_002)107 HWTEST_F_L0(JSFinalizationRegistryTest, Register_002)
108 {
109     testValue = 0;
110     auto vm = thread->GetEcmaVM();
111     auto factory = vm->GetFactory();
112     auto env = vm->GetGlobalEnv();
113 
114     JSHandle<JSTaggedValue> objectFunc = env->GetObjectFunction();
115     JSHandle<JSTaggedValue> target(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objectFunc), objectFunc));
116     JSHandle<JSTaggedValue> key(factory->NewFromASCII("key2"));
117     JSHandle<JSTaggedValue> value(thread, JSTaggedValue(2));
118     JSObject::SetProperty(thread, target, key, value);
119     JSHandle<JSTaggedValue> heldValue(thread, JSTaggedValue(100));
120     JSHandle<JSTaggedValue> unregisterToken = target;
121     JSHandle<JSTaggedValue> constructor = CreateFinalizationRegistry(thread);
122     JSHandle<JSFinalizationRegistry> finaRegObj(thread, constructor.GetTaggedValue());
123 
124     // If unregisterToken is not undefined, use hash map to store to facilitate subsequent delete operations
125     JSHandle<CellRecord> cellRecord = factory->NewCellRecord();
126     cellRecord->SetToWeakRefTarget(thread, target.GetTaggedValue());
127     cellRecord->SetHeldValue(thread, heldValue);
128     JSHandle<JSTaggedValue> cell(cellRecord);
129     JSHandle<CellRecordVector> array = JSHandle<CellRecordVector>(CellRecordVector::Create(thread));
130     array = CellRecordVector::Append(thread, array, cell);
131     JSHandle<JSTaggedValue> arrayValue(array);
132     JSHandle<LinkedHashMap> expectMaybeUnregister(thread, finaRegObj->GetMaybeUnregister());
133     expectMaybeUnregister = LinkedHashMap::SetWeakRef(thread, expectMaybeUnregister, unregisterToken, arrayValue);
134 
135     JSFinalizationRegistry::Register(thread, target, heldValue, unregisterToken, finaRegObj);
136     JSHandle<JSTaggedValue> maybeUnregister(thread, finaRegObj->GetMaybeUnregister());
137     EXPECT_EQ(expectMaybeUnregister.GetTaggedValue().GetRawData(), maybeUnregister.GetTaggedValue().GetRawData());
138 
139     JSHandle<JSTaggedValue> finRegLists = env->GetFinRegLists();
140     EXPECT_EQ(finRegLists.GetTaggedValue().GetRawData(),
141         JSHandle<JSTaggedValue>::Cast(finaRegObj).GetTaggedValue().GetRawData());
142     EXPECT_EQ(testValue, 0);
143 }
144 
RegisterUnRegisterTestCommon(JSThread * thread,bool testUnRegister=false,bool addFinReg=false)145 static void RegisterUnRegisterTestCommon(JSThread *thread, bool testUnRegister = false, bool addFinReg = false)
146 {
147     testValue = 0;
148     auto vm = thread->GetEcmaVM();
149     auto factory = vm->GetFactory();
150     auto env = vm->GetGlobalEnv();
151 
152     JSHandle<JSTaggedValue> objectFunc = env->GetObjectFunction();
153     vm->SetEnableForceGC(false);
154     JSHandle<JSTaggedValue> target(thread, JSTaggedValue::Undefined());
155     {
156         [[maybe_unused]] EcmaHandleScope handleScope(thread);
157         auto obj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objectFunc), objectFunc);
158         target = JSHandle<JSTaggedValue>::Cast(obj);
159         JSHandle<JSTaggedValue> heldValue(thread, JSTaggedValue(100)); // 100: tag value
160         JSHandle<JSTaggedValue> unregisterToken = testUnRegister? target :
161             JSHandle<JSTaggedValue>(thread, JSTaggedValue::Undefined());
162         JSHandle<JSTaggedValue> constructor = CreateFinalizationRegistry(thread);
163         JSHandle<JSFinalizationRegistry> finaRegObj(thread, constructor.GetTaggedValue());
164         if (addFinReg) {
165             JSHandle<CellRecord> cellRecord = factory->NewCellRecord();
166             cellRecord->SetToWeakRefTarget(thread, target.GetTaggedValue());
167             cellRecord->SetHeldValue(thread, heldValue);
168             JSHandle<JSTaggedValue> cell(cellRecord);
169             JSHandle<CellRecordVector> noUnregister(thread, finaRegObj->GetNoUnregister());
170             noUnregister = CellRecordVector::Append(thread, noUnregister, cell);
171             finaRegObj->SetNoUnregister(thread, noUnregister);
172             JSFinalizationRegistry::AddFinRegLists(thread, finaRegObj);
173             JSHandle<JSTaggedValue> finRegLists = env->GetFinRegLists();
174             EXPECT_EQ(finRegLists.GetTaggedValue(), JSHandle<JSTaggedValue>::Cast(finaRegObj).GetTaggedValue());
175         } else {
176             JSFinalizationRegistry::Register(thread, target, heldValue, unregisterToken, finaRegObj);
177             if (testUnRegister) {
178                 JSFinalizationRegistry::Unregister(thread, target, finaRegObj);
179             }
180         }
181         EXPECT_EQ(testValue, 0);
182     }
183     vm->CollectGarbage(TriggerGCType::FULL_GC);
184     if (!thread->HasPendingException()) {
185         job::MicroJobQueue::ExecutePendingJob(thread, thread->GetCurrentEcmaContext()->GetMicroJobQueue());
186     }
187     vm->SetEnableForceGC(true);
188 }
189 
RegisterUnRegisterTestCommonTwoTarget(JSThread * thread,bool testUnRegister=false)190 static void RegisterUnRegisterTestCommonTwoTarget(JSThread *thread, bool testUnRegister = false)
191 {
192     testValue = 0;
193     auto vm = thread->GetEcmaVM();
194     auto factory = vm->GetFactory();
195     auto env = vm->GetGlobalEnv();
196 
197     JSHandle<JSTaggedValue> objectFunc = env->GetObjectFunction();
198     vm->SetEnableForceGC(false);
199     JSHandle<JSTaggedValue> target1(thread, JSTaggedValue::Undefined());
200     JSHandle<JSTaggedValue> target2(thread, JSTaggedValue::Undefined());
201     {
202         [[maybe_unused]] EcmaHandleScope handleScope(thread);
203         auto obj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objectFunc), objectFunc);
204         target1 = JSHandle<JSTaggedValue>::Cast(obj);
205         target2 = JSHandle<JSTaggedValue>::Cast(obj);
206         JSHandle<JSTaggedValue> heldValue(thread, JSTaggedValue(100)); // 100: held value
207         JSHandle<JSTaggedValue> unregisterToken = JSHandle<JSTaggedValue>::Cast(obj);
208         JSHandle<JSTaggedValue> constructor = CreateFinalizationRegistry(thread);
209         JSHandle<JSFinalizationRegistry> finaRegObj1(thread, constructor.GetTaggedValue());
210         JSHandle<JSFinalizationRegistry> finaRegObj2(thread, constructor.GetTaggedValue());
211 
212         JSFinalizationRegistry::Register(thread, target1, heldValue, unregisterToken, finaRegObj1);
213         if (testUnRegister) {
214             JSFinalizationRegistry::Unregister(thread, target1, finaRegObj1);
215         }
216         JSFinalizationRegistry::Register(thread, target2, heldValue, unregisterToken, finaRegObj2);
217         EXPECT_EQ(testValue, 0);
218     }
219     vm->CollectGarbage(TriggerGCType::FULL_GC);
220     if (!thread->HasPendingException()) {
221         job::MicroJobQueue::ExecutePendingJob(thread, thread->GetCurrentEcmaContext()->GetMicroJobQueue());
222     }
223     vm->SetEnableForceGC(true);
224 }
225 
HWTEST_F_L0(JSFinalizationRegistryTest,Register_003)226 HWTEST_F_L0(JSFinalizationRegistryTest, Register_003)
227 {
228     RegisterUnRegisterTestCommon(thread);
229     EXPECT_EQ(testValue, 1);
230 }
231 
HWTEST_F_L0(JSFinalizationRegistryTest,Register_004)232 HWTEST_F_L0(JSFinalizationRegistryTest, Register_004)
233 {
234     RegisterUnRegisterTestCommonTwoTarget(thread);
235     EXPECT_EQ(testValue, 2);
236 }
237 
238 /**
239  * @tc.name: Unregister
240  * @tc.desc: Unregister the JSFinalizationRegistry object from the finalization registry lists.
241  * @tc.type: FUNC
242  * @tc.require:
243  */
HWTEST_F_L0(JSFinalizationRegistryTest,Unregister_001)244 HWTEST_F_L0(JSFinalizationRegistryTest, Unregister_001)
245 {
246     RegisterUnRegisterTestCommon(thread, true);
247     EXPECT_EQ(testValue, 0);
248 }
249 
HWTEST_F_L0(JSFinalizationRegistryTest,Unregister_002)250 HWTEST_F_L0(JSFinalizationRegistryTest, Unregister_002)
251 {
252     RegisterUnRegisterTestCommonTwoTarget(thread, true);
253     // only trigger second finalization callback
254     EXPECT_EQ(testValue, 1);
255 }
256 
257 /**
258  * @tc.name: CheckAndCall
259  * @tc.desc: Check whether each JSFinalizationRegistry object in finalization registry lists has been cleared. If so,
260  *           clear the JSFinalizationRegistry object from the lists.
261  * @tc.type: FUNC
262  * @tc.require:
263  */
HWTEST_F_L0(JSFinalizationRegistryTest,CheckAndCall)264 HWTEST_F_L0(JSFinalizationRegistryTest, CheckAndCall)
265 {
266     testValue = 0;
267     auto vm = thread->GetEcmaVM();
268     auto factory = vm->GetFactory();
269     auto env = vm->GetGlobalEnv();
270 
271     JSHandle<JSTaggedValue> objectFunc = env->GetObjectFunction();
272     JSHandle<JSTaggedValue> target(thread, JSTaggedValue::Undefined());
273     JSHandle<JSTaggedValue> constructor = CreateFinalizationRegistry(thread);
274     JSHandle<JSFinalizationRegistry> finaRegObj(thread, constructor.GetTaggedValue());
275     JSHandle<JSTaggedValue> finRegLists(thread, JSTaggedValue::Undefined());
276     vm->SetEnableForceGC(false);
277     {
278         [[maybe_unused]] EcmaHandleScope handleScope(thread);
279         auto obj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objectFunc), objectFunc);
280         target = JSHandle<JSTaggedValue>::Cast(obj);
281         JSHandle<JSTaggedValue> heldValue(thread, JSTaggedValue(100));
282         JSHandle<JSTaggedValue> unregisterToken(thread, JSTaggedValue::Undefined());
283         JSFinalizationRegistry::Register(thread, target, heldValue, unregisterToken, finaRegObj);
284         EXPECT_EQ(testValue, 0);
285     }
286     finRegLists = env->GetFinRegLists();
287     EXPECT_EQ(finRegLists.GetTaggedValue(), JSHandle<JSTaggedValue>::Cast(finaRegObj).GetTaggedValue());
288     vm->CollectGarbage(TriggerGCType::FULL_GC);
289     if (!thread->HasPendingException()) {
290         job::MicroJobQueue::ExecutePendingJob(thread, thread->GetCurrentEcmaContext()->GetMicroJobQueue());
291     }
292     vm->SetEnableForceGC(true);
293     EXPECT_EQ(testValue, 1);
294     // If all objects registered in the current JSFinalizationRegistry are garbage collected,
295     // clear the JSFinalizationRegistry from the list
296     JSFinalizationRegistry::CheckAndCall(thread);
297     finRegLists = env->GetFinRegLists();
298     EXPECT_EQ(finRegLists.GetTaggedValue(), JSTaggedValue::Undefined());
299 }
300 
301 /**
302  * @tc.name: CleanupFinalizationRegistry
303  * @tc.desc: Check current JSFinalizationRegistry, While contains any record cell such that cell and WeakRefTarget is
304  *           empty, than remove cell from JSFinalizationRegistry and return whether there are still objects that have
305  *           not been cleaned up.
306  * @tc.type: FUNC
307  * @tc.require:
308  */
HWTEST_F_L0(JSFinalizationRegistryTest,CleanupFinalizationRegistry)309 HWTEST_F_L0(JSFinalizationRegistryTest, CleanupFinalizationRegistry)
310 {
311     testValue = 0;
312     auto vm = thread->GetEcmaVM();
313     auto factory = vm->GetFactory();
314     auto env = vm->GetGlobalEnv();
315 
316     JSHandle<JSTaggedValue> objectFunc = env->GetObjectFunction();
317     JSHandle<JSTaggedValue> target(thread, JSTaggedValue::Undefined());
318     JSHandle<JSTaggedValue> constructor = CreateFinalizationRegistry(thread);
319     JSHandle<JSFinalizationRegistry> finaRegObj(thread, constructor.GetTaggedValue());
320     bool isCleared = false;
321     vm->SetEnableForceGC(false);
322     {
323         [[maybe_unused]] EcmaHandleScope handleScope(thread);
324         auto obj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objectFunc), objectFunc);
325         target = JSHandle<JSTaggedValue>::Cast(obj);
326         JSHandle<JSTaggedValue> heldValue(thread, JSTaggedValue(100));
327         JSHandle<JSTaggedValue> unregisterToken(thread, JSTaggedValue::Undefined());
328         JSFinalizationRegistry::Register(thread, target, heldValue, unregisterToken, finaRegObj);
329         EXPECT_EQ(testValue, 0);
330 
331         isCleared = JSFinalizationRegistry::CleanupFinalizationRegistry(thread, finaRegObj);
332         EXPECT_FALSE(isCleared);
333     }
334     vm->CollectGarbage(TriggerGCType::FULL_GC);
335     if (!thread->HasPendingException()) {
336         job::MicroJobQueue::ExecutePendingJob(thread, thread->GetCurrentEcmaContext()->GetMicroJobQueue());
337     }
338     vm->SetEnableForceGC(true);
339     EXPECT_EQ(testValue, 1);
340     isCleared = JSFinalizationRegistry::CleanupFinalizationRegistry(thread, finaRegObj);
341     EXPECT_TRUE(isCleared);
342 }
343 
344 /**
345  * @tc.name: CleanFinRegLists
346  * @tc.desc: Clear the JSFinalizationRegistry object from the finalization registry lists.
347  * @tc.type: FUNC
348  * @tc.require:
349  */
HWTEST_F_L0(JSFinalizationRegistryTest,CleanFinRegLists)350 HWTEST_F_L0(JSFinalizationRegistryTest, CleanFinRegLists)
351 {
352     testValue = 0;
353     auto vm = thread->GetEcmaVM();
354     auto factory = vm->GetFactory();
355     auto env = vm->GetGlobalEnv();
356 
357     JSHandle<JSTaggedValue> objectFunc = env->GetObjectFunction();
358     JSHandle<JSTaggedValue> target(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objectFunc), objectFunc));
359     JSHandle<JSTaggedValue> key(factory->NewFromASCII("key3"));
360     JSHandle<JSTaggedValue> value(thread, JSTaggedValue(3));
361     JSObject::SetProperty(thread, target, key, value);
362     JSHandle<JSTaggedValue> heldValue(thread, JSTaggedValue(100));
363     JSHandle<JSTaggedValue> unregisterToken(thread, JSTaggedValue::Undefined());
364     JSHandle<JSTaggedValue> constructor = CreateFinalizationRegistry(thread);
365     JSHandle<JSFinalizationRegistry> finaRegObj(thread, constructor.GetTaggedValue());
366 
367     JSFinalizationRegistry::Register(thread, target, heldValue, unregisterToken, finaRegObj);
368     JSHandle<JSTaggedValue> finRegLists = env->GetFinRegLists();
369     EXPECT_EQ(finRegLists.GetTaggedValue(), JSHandle<JSTaggedValue>::Cast(finaRegObj).GetTaggedValue());
370     EXPECT_EQ(testValue, 0);
371 
372     JSFinalizationRegistry::CleanFinRegLists(thread, finaRegObj);
373     finRegLists = env->GetFinRegLists();
374     EXPECT_EQ(finRegLists.GetTaggedValue(), JSTaggedValue::Undefined());
375 }
376 
377 /**
378  * @tc.name: AddFinRegLists
379  * @tc.desc: Add a JSFinalizationRegistry object to the finalization registry lists.
380  * @tc.type: FUNC
381  * @tc.require:
382  */
HWTEST_F_L0(JSFinalizationRegistryTest,AddFinRegLists)383 HWTEST_F_L0(JSFinalizationRegistryTest, AddFinRegLists)
384 {
385     RegisterUnRegisterTestCommon(thread, false, true);
386     EXPECT_EQ(testValue, 1);
387 }
388 }  // namespace panda::test