• 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> & 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