• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2022-2024 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/js_finalization_registry.h"
17 
18 #include "ecmascript/ecma_context.h"
19 #include "ecmascript/global_env.h"
20 #include "ecmascript/jobs/micro_job_queue.h"
21 #include "ecmascript/js_object-inl.h"
22 #include "ecmascript/linked_hash_table.h"
23 
24 namespace panda::ecmascript {
25 // -------------------------------CellRecordVector-----------------------------------
Append(const JSThread * thread,const JSHandle<CellRecordVector> & vector,const JSHandle<JSTaggedValue> & value)26 JSHandle<CellRecordVector> CellRecordVector::Append(const JSThread *thread, const JSHandle<CellRecordVector> &vector,
27                                                     const JSHandle<JSTaggedValue> &value)
28 {
29     JSHandle<WeakVector> oldVector(vector);
30     JSHandle<WeakVector> newVector = WeakVector::FillOrAppend(thread, oldVector, value);
31     return JSHandle<CellRecordVector>(newVector);
32 }
33 
IsEmpty()34 bool CellRecordVector::IsEmpty()
35 {
36     if (Empty()) {
37         return true;
38     }
39     for (uint32_t i = 0; i < GetEnd(); i++) {
40         JSTaggedValue value = Get(i);
41         if (!value.IsHole()) {
42             return false;
43         }
44     }
45     return true;
46 }
47 
48 // ---------------------------JSFinalizationRegistry-----------------------------------
Register(JSThread * thread,JSHandle<JSTaggedValue> target,JSHandle<JSTaggedValue> heldValue,JSHandle<JSTaggedValue> unregisterToken,JSHandle<JSFinalizationRegistry> obj)49 void JSFinalizationRegistry::Register(JSThread *thread, JSHandle<JSTaggedValue> target,
50                                       JSHandle<JSTaggedValue> heldValue,
51                                       JSHandle<JSTaggedValue> unregisterToken, JSHandle<JSFinalizationRegistry> obj)
52 {
53     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
54     JSHandle<CellRecord> cellRecord = factory->NewCellRecord();
55     cellRecord->SetToWeakRefTarget(thread, target.GetTaggedValue());
56     cellRecord->SetHeldValue(thread, heldValue);
57     JSHandle<JSTaggedValue> cell(cellRecord);
58     // If unregisterToken is undefined, we use vector to store
59     // otherwise we use hash map to store to facilitate subsequent delete operations
60     if (!unregisterToken->IsUndefined()) {
61         JSHandle<LinkedHashMap> maybeUnregister(thread, obj->GetMaybeUnregister());
62         JSHandle<CellRecordVector> array(thread, JSTaggedValue::Undefined());
63         if (maybeUnregister->Has(thread, unregisterToken.GetTaggedValue())) {
64             array = JSHandle<CellRecordVector>(thread, maybeUnregister->Get(thread, unregisterToken.GetTaggedValue()));
65         } else {
66             array = JSHandle<CellRecordVector>(CellRecordVector::Create(thread));
67         }
68         array = CellRecordVector::Append(thread, array, cell);
69         JSHandle<JSTaggedValue> arrayValue(array);
70         maybeUnregister = LinkedHashMap::SetWeakRef(thread, maybeUnregister, unregisterToken, arrayValue);
71         obj->SetMaybeUnregister(thread, maybeUnregister);
72     } else {
73         JSHandle<CellRecordVector> noUnregister(thread, obj->GetNoUnregister());
74         noUnregister = CellRecordVector::Append(thread, noUnregister, cell);
75         obj->SetNoUnregister(thread, noUnregister);
76     }
77     JSFinalizationRegistry::AddFinRegLists(thread, obj);
78 }
79 
Unregister(JSThread * thread,JSHandle<JSTaggedValue> UnregisterToken,JSHandle<JSFinalizationRegistry> obj)80 bool JSFinalizationRegistry::Unregister(JSThread *thread, JSHandle<JSTaggedValue> UnregisterToken,
81                                         JSHandle<JSFinalizationRegistry> obj)
82 {
83     // Because we have stored the object that may be unregistered in the hash map when registering,
84     // at this time we just need to find it in the map and delete it
85     JSHandle<LinkedHashMap> maybeUnregister(thread, obj->GetMaybeUnregister());
86     int entry = maybeUnregister->FindElement(thread, UnregisterToken.GetTaggedValue());
87     if (entry == -1) {
88         return false;
89     }
90     maybeUnregister->RemoveEntry(thread, entry);
91     JSHandle<LinkedHashMap> newMaybeUnregister = LinkedHashMap::Shrink(thread, maybeUnregister);
92     obj->SetMaybeUnregister(thread, newMaybeUnregister);
93     return true;
94 }
95 
CleanFinRegLists(JSThread * thread,JSHandle<JSFinalizationRegistry> obj)96 void JSFinalizationRegistry::CleanFinRegLists(JSThread *thread, JSHandle<JSFinalizationRegistry> obj)
97 {
98     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
99     if (obj->GetPrev().IsNull() && obj->GetNext().IsNull()) {
100         env->SetFinRegLists(thread, JSTaggedValue::Undefined());
101         return;
102     }
103     if (!obj->GetPrev().IsNull()) {
104         JSHandle<JSFinalizationRegistry> prev(thread, obj->GetPrev());
105         prev->SetNext(thread, obj->GetNext());
106     }
107     if (!obj->GetNext().IsNull()) {
108         JSHandle<JSFinalizationRegistry> next(thread, obj->GetNext());
109         next->SetPrev(thread, obj->GetPrev());
110     } else {
111         env->SetFinRegLists(thread, obj->GetPrev());
112     }
113     obj->SetPrev(thread, JSTaggedValue::Null());
114     obj->SetNext(thread, JSTaggedValue::Null());
115 }
116 
CheckAndCall(JSThread * thread)117 void JSFinalizationRegistry::CheckAndCall(JSThread *thread)
118 {
119     if (thread->GetCheckAndCallEnterState()) {
120         return;
121     }
122     CheckAndCallScope scope(thread);
123     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
124     JSHandle<JSTaggedValue> prev = env->GetFinRegLists();
125 
126     if (prev->IsUndefined()) {
127         return;
128     }
129     JSMutableHandle<JSTaggedValue> current(thread, prev.GetTaggedValue());
130     JSMutableHandle<JSFinalizationRegistry> jsFinalizationRegistry(thread, current.GetTaggedValue());
131     while (!current->IsNull()) {
132         jsFinalizationRegistry.Update(current.GetTaggedValue());
133         current.Update(jsFinalizationRegistry->GetPrev());
134         if (CleanupFinalizationRegistry(thread, jsFinalizationRegistry)) {
135             // If the objects registered on the current JSFinalizationRegistry object have all been gc,
136             // then we clean up this JSFinalizationRegistry object from the FinRegLists
137             CleanFinRegLists(thread, jsFinalizationRegistry);
138         }
139     }
140 }
141 
DealCallBackOfMap(JSThread * thread,JSHandle<CellRecordVector> & cellVect,JSHandle<job::MicroJobQueue> & job,JSHandle<JSFunction> & func)142 void DealCallBackOfMap(JSThread *thread, JSHandle<CellRecordVector> &cellVect,
143     JSHandle<job::MicroJobQueue> &job, JSHandle<JSFunction> &func)
144 {
145     if (!cellVect->Empty()) {
146         uint32_t cellVectLen = cellVect->GetEnd();
147         for (uint32_t i = 0; i < cellVectLen; ++i) {
148             JSTaggedValue value = cellVect->Get(i);
149             if (value.IsHole()) {
150                 continue;
151             }
152             JSHandle<CellRecord> cellRecord(thread, value);
153             // if WeakRefTarget have been gc, set callback to job and delete
154             if (cellRecord->GetFromWeakRefTarget().IsUndefined()) {
155                 JSHandle<TaggedArray> argv = thread->GetEcmaVM()->GetFactory()->NewTaggedArray(1);
156                 argv->Set(thread, 0, cellRecord->GetHeldValue());
157                 job::MicroJobQueue::EnqueueJob(thread, job, job::QueueType::QUEUE_PROMISE, func, argv);
158                 cellVect->Delete(thread, i);
159             }
160         }
161     }
162 }
163 
CleanupFinalizationRegistry(JSThread * thread,JSHandle<JSFinalizationRegistry> obj)164 bool JSFinalizationRegistry::CleanupFinalizationRegistry(JSThread *thread, JSHandle<JSFinalizationRegistry> obj)
165 {
166     // 1. Assert: finalizationRegistry has [[Cells]] and [[CleanupCallback]] internal slots.
167     // 2. Let callback be finalizationRegistry.[[CleanupCallback]].
168     // 3. While finalizationRegistry.[[Cells]] contains a Record cell such that cell.[[WeakRefTarget]] is empty,
169     // an implementation may perform the following steps:
170     //     a. Choose any such cell.
171     //     b. Remove cell from finalizationRegistry.[[Cells]].
172     //     c. Perform ? HostCallJobCallback(callback, undefined, « cell.[[HeldValue]] »).
173     // 4. Return unused.
174     ASSERT(obj->IsECMAObject());
175     auto ecmaVm = thread->GetEcmaVM();
176     JSHandle<job::MicroJobQueue> job = thread->GetCurrentEcmaContext()->GetMicroJobQueue();
177     ObjectFactory *factory = ecmaVm->GetFactory();
178     JSHandle<JSFunction> func(thread, obj->GetCleanupCallback());
179     JSHandle<CellRecordVector> noUnregister(thread, obj->GetNoUnregister());
180     if (!noUnregister->Empty()) {
181         uint32_t noUnregisterLen = noUnregister->GetEnd();
182         for (uint32_t i = 0; i < noUnregisterLen; ++i) {
183             JSTaggedValue value = noUnregister->Get(i);
184             if (value.IsHole()) {
185                 continue;
186             }
187             JSHandle<CellRecord> cellRecord(thread, value);
188             // if WeakRefTarget have been gc, set callback to job and delete
189             if (cellRecord->GetFromWeakRefTarget().IsUndefined()) {
190                 JSHandle<TaggedArray> argv = factory->NewTaggedArray(1);
191                 argv->Set(thread, 0, cellRecord->GetHeldValue());
192                 job::MicroJobQueue::EnqueueJob(thread, job, job::QueueType::QUEUE_PROMISE, func, argv);
193                 noUnregister->Delete(thread, i);
194             }
195         }
196     }
197     JSMutableHandle<LinkedHashMap> maybeUnregister(thread, obj->GetMaybeUnregister());
198     int index = 0;
199     int totalElements = maybeUnregister->NumberOfElements() + maybeUnregister->NumberOfDeletedElements();
200     while (index < totalElements) {
201         if (!maybeUnregister->GetKey(index++).IsHole()) {
202             JSHandle<CellRecordVector> cellVect(thread, maybeUnregister->GetValue(index - 1));
203             DealCallBackOfMap(thread, cellVect, job, func);
204             if (!cellVect->Empty()) {
205                 continue;
206             }
207             maybeUnregister->RemoveEntry(thread, index - 1);
208         }
209     }
210     JSHandle<LinkedHashMap> newMap = LinkedHashMap::Shrink(thread, maybeUnregister);
211     obj->SetMaybeUnregister(thread, newMap);
212     // Determine whether the objects registered on the current JSFinalizationRegistry object
213     // have all been gc
214     int remainSize = newMap->NumberOfElements() + newMap->NumberOfDeletedElements();
215     if (noUnregister->IsEmpty() && remainSize == 0) {
216         return true;
217     }
218     return false;
219 }
220 
AddFinRegLists(JSThread * thread,JSHandle<JSFinalizationRegistry> next)221 void JSFinalizationRegistry::AddFinRegLists(JSThread *thread, JSHandle<JSFinalizationRegistry> next)
222 {
223     // If any of prev and next is not null, it means that the current object is already in the linked list,
224     // ignore this addition
225     if (!next->GetPrev().IsNull() || !next->GetNext().IsNull()) {
226         return;
227     }
228     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
229     JSHandle<JSTaggedValue> lists = env->GetFinRegLists();
230     if (!lists->IsUndefined()) {
231         JSHandle<JSFinalizationRegistry> prev(lists);
232         // Prevent the same object from being connected end to end,
233         // which should not happen and will lead to an infinite loop
234         if (JSTaggedValue::SameValue(next.GetTaggedValue(), prev.GetTaggedValue())) {
235             return;
236         }
237         prev->SetNext(thread, next);
238         next->SetPrev(thread, prev);
239     }
240     env->SetFinRegLists(thread, next);
241 }
242 } // namespace