/* * Copyright (c) 2022-2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "ecmascript/js_finalization_registry.h" #include "ecmascript/ecma_context.h" #include "ecmascript/global_env.h" #include "ecmascript/jobs/micro_job_queue.h" #include "ecmascript/js_object-inl.h" #include "ecmascript/linked_hash_table.h" namespace panda::ecmascript { // -------------------------------CellRecordVector----------------------------------- JSHandle CellRecordVector::Append(const JSThread *thread, const JSHandle &vector, const JSHandle &value) { JSHandle oldVector(vector); JSHandle newVector = WeakVector::FillOrAppend(thread, oldVector, value); return JSHandle(newVector); } bool CellRecordVector::IsEmpty() { if (Empty()) { return true; } for (uint32_t i = 0; i < GetEnd(); i++) { JSTaggedValue value = Get(i); if (!value.IsHole()) { return false; } } return true; } // ---------------------------JSFinalizationRegistry----------------------------------- void JSFinalizationRegistry::Register(JSThread *thread, JSHandle target, JSHandle heldValue, JSHandle unregisterToken, JSHandle obj) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle cellRecord = factory->NewCellRecord(); cellRecord->SetToWeakRefTarget(thread, target.GetTaggedValue()); cellRecord->SetHeldValue(thread, heldValue); JSHandle cell(cellRecord); // If unregisterToken is undefined, we use vector to store // otherwise we use hash map to store to facilitate subsequent delete operations if (!unregisterToken->IsUndefined()) { JSHandle maybeUnregister(thread, obj->GetMaybeUnregister()); JSHandle array(thread, JSTaggedValue::Undefined()); if (maybeUnregister->Has(thread, unregisterToken.GetTaggedValue())) { array = JSHandle(thread, maybeUnregister->Get(thread, unregisterToken.GetTaggedValue())); } else { array = JSHandle(CellRecordVector::Create(thread)); } array = CellRecordVector::Append(thread, array, cell); JSHandle arrayValue(array); maybeUnregister = LinkedHashMap::SetWeakRef(thread, maybeUnregister, unregisterToken, arrayValue); obj->SetMaybeUnregister(thread, maybeUnregister); } else { JSHandle noUnregister(thread, obj->GetNoUnregister()); noUnregister = CellRecordVector::Append(thread, noUnregister, cell); obj->SetNoUnregister(thread, noUnregister); } JSFinalizationRegistry::AddFinRegLists(thread, obj); } bool JSFinalizationRegistry::Unregister(JSThread *thread, JSHandle UnregisterToken, JSHandle obj) { // Because we have stored the object that may be unregistered in the hash map when registering, // at this time we just need to find it in the map and delete it JSHandle maybeUnregister(thread, obj->GetMaybeUnregister()); int entry = maybeUnregister->FindElement(thread, UnregisterToken.GetTaggedValue()); if (entry == -1) { return false; } maybeUnregister->RemoveEntry(thread, entry); JSHandle newMaybeUnregister = LinkedHashMap::Shrink(thread, maybeUnregister); obj->SetMaybeUnregister(thread, newMaybeUnregister); return true; } void JSFinalizationRegistry::CleanFinRegLists(JSThread *thread, JSHandle obj) { JSHandle env = thread->GetEcmaVM()->GetGlobalEnv(); if (obj->GetPrev().IsNull() && obj->GetNext().IsNull()) { env->SetFinRegLists(thread, JSTaggedValue::Undefined()); return; } if (!obj->GetPrev().IsNull()) { JSHandle prev(thread, obj->GetPrev()); prev->SetNext(thread, obj->GetNext()); } if (!obj->GetNext().IsNull()) { JSHandle next(thread, obj->GetNext()); next->SetPrev(thread, obj->GetPrev()); } else { env->SetFinRegLists(thread, obj->GetPrev()); } obj->SetPrev(thread, JSTaggedValue::Null()); obj->SetNext(thread, JSTaggedValue::Null()); } void JSFinalizationRegistry::CheckAndCall(JSThread *thread) { if (thread->GetCheckAndCallEnterState()) { return; } CheckAndCallScope scope(thread); JSHandle env = thread->GetEcmaVM()->GetGlobalEnv(); JSHandle prev = env->GetFinRegLists(); if (prev->IsUndefined()) { return; } JSMutableHandle current(thread, prev.GetTaggedValue()); JSMutableHandle jsFinalizationRegistry(thread, current.GetTaggedValue()); while (!current->IsNull()) { jsFinalizationRegistry.Update(current.GetTaggedValue()); current.Update(jsFinalizationRegistry->GetPrev()); if (CleanupFinalizationRegistry(thread, jsFinalizationRegistry)) { // If the objects registered on the current JSFinalizationRegistry object have all been gc, // then we clean up this JSFinalizationRegistry object from the FinRegLists CleanFinRegLists(thread, jsFinalizationRegistry); } } } void DealCallBackOfMap(JSThread *thread, JSHandle &cellVect, JSHandle &job, JSHandle &func) { if (!cellVect->Empty()) { uint32_t cellVectLen = cellVect->GetEnd(); for (uint32_t i = 0; i < cellVectLen; ++i) { JSTaggedValue value = cellVect->Get(i); if (value.IsHole()) { continue; } JSHandle cellRecord(thread, value); // if WeakRefTarget have been gc, set callback to job and delete if (cellRecord->GetFromWeakRefTarget().IsUndefined()) { JSHandle argv = thread->GetEcmaVM()->GetFactory()->NewTaggedArray(1); argv->Set(thread, 0, cellRecord->GetHeldValue()); job::MicroJobQueue::EnqueueJob(thread, job, job::QueueType::QUEUE_PROMISE, func, argv); cellVect->Delete(thread, i); } } } } bool JSFinalizationRegistry::CleanupFinalizationRegistry(JSThread *thread, JSHandle obj) { // 1. Assert: finalizationRegistry has [[Cells]] and [[CleanupCallback]] internal slots. // 2. Let callback be finalizationRegistry.[[CleanupCallback]]. // 3. While finalizationRegistry.[[Cells]] contains a Record cell such that cell.[[WeakRefTarget]] is empty, // an implementation may perform the following steps: // a. Choose any such cell. // b. Remove cell from finalizationRegistry.[[Cells]]. // c. Perform ? HostCallJobCallback(callback, undefined, « cell.[[HeldValue]] »). // 4. Return unused. ASSERT(obj->IsECMAObject()); auto ecmaVm = thread->GetEcmaVM(); JSHandle job = thread->GetCurrentEcmaContext()->GetMicroJobQueue(); ObjectFactory *factory = ecmaVm->GetFactory(); JSHandle func(thread, obj->GetCleanupCallback()); JSHandle noUnregister(thread, obj->GetNoUnregister()); if (!noUnregister->Empty()) { uint32_t noUnregisterLen = noUnregister->GetEnd(); for (uint32_t i = 0; i < noUnregisterLen; ++i) { JSTaggedValue value = noUnregister->Get(i); if (value.IsHole()) { continue; } JSHandle cellRecord(thread, value); // if WeakRefTarget have been gc, set callback to job and delete if (cellRecord->GetFromWeakRefTarget().IsUndefined()) { JSHandle argv = factory->NewTaggedArray(1); argv->Set(thread, 0, cellRecord->GetHeldValue()); job::MicroJobQueue::EnqueueJob(thread, job, job::QueueType::QUEUE_PROMISE, func, argv); noUnregister->Delete(thread, i); } } } JSMutableHandle maybeUnregister(thread, obj->GetMaybeUnregister()); int index = 0; int totalElements = maybeUnregister->NumberOfElements() + maybeUnregister->NumberOfDeletedElements(); while (index < totalElements) { if (!maybeUnregister->GetKey(index++).IsHole()) { JSHandle cellVect(thread, maybeUnregister->GetValue(index - 1)); DealCallBackOfMap(thread, cellVect, job, func); if (!cellVect->Empty()) { continue; } maybeUnregister->RemoveEntry(thread, index - 1); } } JSHandle newMap = LinkedHashMap::Shrink(thread, maybeUnregister); obj->SetMaybeUnregister(thread, newMap); // Determine whether the objects registered on the current JSFinalizationRegistry object // have all been gc int remainSize = newMap->NumberOfElements() + newMap->NumberOfDeletedElements(); if (noUnregister->IsEmpty() && remainSize == 0) { return true; } return false; } void JSFinalizationRegistry::AddFinRegLists(JSThread *thread, JSHandle next) { // If any of prev and next is not null, it means that the current object is already in the linked list, // ignore this addition if (!next->GetPrev().IsNull() || !next->GetNext().IsNull()) { return; } JSHandle env = thread->GetEcmaVM()->GetGlobalEnv(); JSHandle lists = env->GetFinRegLists(); if (!lists->IsUndefined()) { JSHandle prev(lists); // Prevent the same object from being connected end to end, // which should not happen and will lead to an infinite loop if (JSTaggedValue::SameValue(next.GetTaggedValue(), prev.GetTaggedValue())) { return; } prev->SetNext(thread, next); next->SetPrev(thread, prev); } env->SetFinRegLists(thread, next); } } // namespace