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