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