• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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