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 23using namespace panda; 24using namespace panda::ecmascript; 25using namespace panda::ecmascript::builtins; 26 27static int testValue = 0; 28 29namespace panda::test { 30class JSFinalizationRegistryTest : public BaseTestWithScope<false> { 31public: 32 class TestClass : public base::BuiltinsBase { 33 public: 34 static JSTaggedValue callbackTest() 35 { 36 testValue++; 37 return JSTaggedValue::Undefined(); 38 } 39 }; 40}; 41 42static 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 */ 73HWTEST_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 107HWTEST_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 145static 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 190static 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 226HWTEST_F_L0(JSFinalizationRegistryTest, Register_003) 227{ 228 RegisterUnRegisterTestCommon(thread); 229 EXPECT_EQ(testValue, 1); 230} 231 232HWTEST_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 */ 244HWTEST_F_L0(JSFinalizationRegistryTest, Unregister_001) 245{ 246 RegisterUnRegisterTestCommon(thread, true); 247 EXPECT_EQ(testValue, 0); 248} 249 250HWTEST_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 */ 264HWTEST_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 */ 309HWTEST_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 */ 350HWTEST_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 */ 383HWTEST_F_L0(JSFinalizationRegistryTest, AddFinRegLists) 384{ 385 RegisterUnRegisterTestCommon(thread, false, true); 386 EXPECT_EQ(testValue, 1); 387} 388} // namespace panda::test