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/global_env.h"
19 #include "ecmascript/jobs/micro_job_queue.h"
20 #include "ecmascript/js_object-inl.h"
21 #include "ecmascript/linked_hash_table.h"
22
23 namespace panda::ecmascript {
24 // -------------------------------CellRecordVector-----------------------------------
Append(const JSThread * thread,const JSHandle<CellRecordVector> & vector,const JSHandle<JSTaggedValue> & value)25 JSHandle<CellRecordVector> CellRecordVector::Append(const JSThread *thread, const JSHandle<CellRecordVector> &vector,
26 const JSHandle<JSTaggedValue> &value)
27 {
28 JSHandle<WeakVector> oldVector(vector);
29 JSHandle<WeakVector> newVector = WeakVector::FillOrAppend(thread, oldVector, value);
30 return JSHandle<CellRecordVector>(newVector);
31 }
32
IsEmpty(const JSThread * thread)33 bool CellRecordVector::IsEmpty(const JSThread *thread)
34 {
35 if (Empty()) {
36 return true;
37 }
38 for (uint32_t i = 0; i < GetEnd(); i++) {
39 JSTaggedValue value = Get(thread, i);
40 if (!value.IsHole()) {
41 return false;
42 }
43 }
44 return true;
45 }
46
47 // ---------------------------JSFinalizationRegistry-----------------------------------
Register(JSThread * thread,JSHandle<JSTaggedValue> target,JSHandle<JSTaggedValue> heldValue,JSHandle<JSTaggedValue> unregisterToken,JSHandle<JSFinalizationRegistry> obj)48 void JSFinalizationRegistry::Register(JSThread *thread, JSHandle<JSTaggedValue> target,
49 JSHandle<JSTaggedValue> heldValue,
50 JSHandle<JSTaggedValue> unregisterToken, JSHandle<JSFinalizationRegistry> obj)
51 {
52 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
53 JSHandle<CellRecord> cellRecord = factory->NewCellRecord();
54 cellRecord->SetToWeakRefTarget(thread, target.GetTaggedValue());
55 cellRecord->SetHeldValue(thread, heldValue);
56 JSHandle<JSTaggedValue> cell(cellRecord);
57 // If unregisterToken is undefined, we use vector to store
58 // otherwise we use hash map to store to facilitate subsequent delete operations
59 if (!unregisterToken->IsUndefined()) {
60 JSHandle<LinkedHashMap> maybeUnregister(thread, obj->GetMaybeUnregister(thread));
61 JSHandle<CellRecordVector> array(thread, JSTaggedValue::Undefined());
62 if (maybeUnregister->Has(thread, unregisterToken.GetTaggedValue())) {
63 array = JSHandle<CellRecordVector>(thread, maybeUnregister->Get(thread, unregisterToken.GetTaggedValue()));
64 } else {
65 array = JSHandle<CellRecordVector>(CellRecordVector::Create(thread));
66 }
67 array = CellRecordVector::Append(thread, array, cell);
68 JSHandle<JSTaggedValue> arrayValue(array);
69 maybeUnregister = LinkedHashMap::SetWeakRef(thread, maybeUnregister, unregisterToken, arrayValue);
70 obj->SetMaybeUnregister(thread, maybeUnregister);
71 } else {
72 JSHandle<CellRecordVector> noUnregister(thread, obj->GetNoUnregister(thread));
73 noUnregister = CellRecordVector::Append(thread, noUnregister, cell);
74 obj->SetNoUnregister(thread, noUnregister);
75 }
76 JSFinalizationRegistry::AddFinRegLists(thread, obj);
77 }
78
Unregister(JSThread * thread,JSHandle<JSTaggedValue> UnregisterToken,JSHandle<JSFinalizationRegistry> obj)79 bool JSFinalizationRegistry::Unregister(JSThread *thread, JSHandle<JSTaggedValue> UnregisterToken,
80 JSHandle<JSFinalizationRegistry> obj)
81 {
82 // Because we have stored the object that may be unregistered in the hash map when registering,
83 // at this time we just need to find it in the map and delete it
84 JSHandle<LinkedHashMap> maybeUnregister(thread, obj->GetMaybeUnregister(thread));
85 int entry = maybeUnregister->FindElement(thread, UnregisterToken.GetTaggedValue());
86 if (entry == -1) {
87 return false;
88 }
89 maybeUnregister->RemoveEntry(thread, entry);
90 JSHandle<LinkedHashMap> newMaybeUnregister = LinkedHashMap::Shrink(thread, maybeUnregister);
91 obj->SetMaybeUnregister(thread, newMaybeUnregister);
92 return true;
93 }
94
CleanFinRegLists(JSThread * thread,JSHandle<JSFinalizationRegistry> obj)95 void JSFinalizationRegistry::CleanFinRegLists(JSThread *thread, JSHandle<JSFinalizationRegistry> obj)
96 {
97 EcmaVM* vm = thread->GetEcmaVM();
98 if (obj->GetPrev(thread).IsNull() && obj->GetNext(thread).IsNull()) {
99 vm->SetFinRegLists(JSTaggedValue::Hole());
100 return;
101 }
102 if (!obj->GetPrev(thread).IsNull()) {
103 JSHandle<JSFinalizationRegistry> prev(thread, obj->GetPrev(thread));
104 prev->SetNext(thread, obj->GetNext(thread));
105 }
106 if (!obj->GetNext(thread).IsNull()) {
107 JSHandle<JSFinalizationRegistry> next(thread, obj->GetNext(thread));
108 next->SetPrev(thread, obj->GetPrev(thread));
109 } else {
110 vm->SetFinRegLists(obj->GetPrev(thread));
111 }
112 obj->SetPrev(thread, JSTaggedValue::Null());
113 obj->SetNext(thread, JSTaggedValue::Null());
114 }
115
CheckAndCall(JSThread * thread)116 void JSFinalizationRegistry::CheckAndCall(JSThread *thread)
117 {
118 if (thread->GetCheckAndCallEnterState()) {
119 return;
120 }
121 [[maybe_unused]] EcmaHandleScope handleScope(thread);
122 CheckAndCallScope scope(thread);
123 EcmaVM* vm = thread->GetEcmaVM();
124 JSHandle<JSTaggedValue> prev(thread, vm->GetFinRegLists());
125
126 if (prev->IsHole()) {
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(thread));
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(thread, 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(thread).IsUndefined()) {
155 JSHandle<TaggedArray> argv = thread->GetEcmaVM()->GetFactory()->NewTaggedArray(1);
156 argv->Set(thread, 0, cellRecord->GetHeldValue(thread));
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 = ecmaVm->GetMicroJobQueue();
177 ObjectFactory *factory = ecmaVm->GetFactory();
178 JSHandle<JSFunction> func(thread, obj->GetCleanupCallback(thread));
179 JSHandle<CellRecordVector> noUnregister(thread, obj->GetNoUnregister(thread));
180 if (!noUnregister->Empty()) {
181 uint32_t noUnregisterLen = noUnregister->GetEnd();
182 for (uint32_t i = 0; i < noUnregisterLen; ++i) {
183 JSTaggedValue value = noUnregister->Get(thread, 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(thread).IsUndefined()) {
190 JSHandle<TaggedArray> argv = factory->NewTaggedArray(1);
191 argv->Set(thread, 0, cellRecord->GetHeldValue(thread));
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(thread));
198 int index = 0;
199 int totalElements = maybeUnregister->NumberOfElements() + maybeUnregister->NumberOfDeletedElements();
200 while (index < totalElements) {
201 if (!maybeUnregister->GetKey(thread, index++).IsHole()) {
202 JSHandle<CellRecordVector> cellVect(thread, maybeUnregister->GetValue(thread, 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(thread) && 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(thread).IsNull() || !next->GetNext(thread).IsNull()) {
226 return;
227 }
228 EcmaVM* vm = thread->GetEcmaVM();
229 JSHandle<JSTaggedValue> lists(thread, vm->GetFinRegLists());
230 if (!lists->IsHole()) {
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(thread, next.GetTaggedValue(), prev.GetTaggedValue())) {
235 return;
236 }
237 prev->SetNext(thread, next);
238 next->SetPrev(thread, prev);
239 }
240 vm->SetFinRegLists(next.GetTaggedValue());
241 }
242 } // namespace
243