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