1 /*
2  * Copyright (c) 2021 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 #ifndef ECMASCRIPT_ECMA_GLOBAL_STORAGE_H
17 #define ECMASCRIPT_ECMA_GLOBAL_STORAGE_H
18 
19 #include "ecmascript/js_thread.h"
20 #include "ecmascript/mem/native_area_allocator.h"
21 #include "ecmascript/mem/c_containers.h"
22 #include "ecmascript/platform/backtrace.h"
23 #ifdef HOOK_ENABLE
24 #include "memory_trace.h"
25 #endif
26 
27 namespace panda::ecmascript {
28 class Node {
29 public:
GetObject() const30     JSTaggedType GetObject() const
31     {
32         return obj_;
33     }
34 
SetObject(JSTaggedType obj)35     void SetObject(JSTaggedType obj)
36     {
37         obj_ = obj;
38     }
39 
GetNext() const40     Node *GetNext() const
41     {
42         return next_;
43     }
44 
SetNext(Node *node)45     void SetNext(Node *node)
46     {
47         next_ = node;
48     }
49 
GetPrev() const50     Node *GetPrev() const
51     {
52         return prev_;
53     }
54 
SetPrev(Node *node)55     void SetPrev(Node *node)
56     {
57         prev_ = node;
58     }
59 
GetIndex() const60     uint32_t GetIndex() const
61     {
62         return index_;
63     }
64 
SetIndex(uint32_t index)65     void SetIndex(uint32_t index)
66     {
67         index_ = index;
68     }
69 
SetUsing(bool isUsing)70     void SetUsing(bool isUsing)
71     {
72         isUsing_ = isUsing;
73     }
74 
SetWeak(bool isWeak)75     void SetWeak(bool isWeak)
76     {
77         isWeak_ = isWeak;
78     }
79 
IsUsing() const80     bool IsUsing() const
81     {
82         return isUsing_;
83     }
84 
IsWeak() const85     bool IsWeak() const
86     {
87         return isWeak_;
88     }
89 
GetObjectAddress() const90     uintptr_t GetObjectAddress() const
91     {
92         return reinterpret_cast<uintptr_t>(&obj_);
93     }
94 
95     // If isUsing is true, it means that the node is being used, otherwise it means that node is be freed
Reset([[maybe_unused]] JSThread *thread, Node *next, JSTaggedType value, bool isUsing)96     void Reset([[maybe_unused]] JSThread *thread, Node *next, JSTaggedType value, bool isUsing)
97     {
98         SetPrev(nullptr);
99         SetNext(next);
100         SetObject(value);
101         SetUsing(isUsing);
102 #ifdef HOOK_ENABLE
103         memtrace((void *)next, sizeof(Node), "ArkJsGlobalHandle", isUsing);
104 #endif
105     }
106 
107 private:
108     JSTaggedType obj_;
109     Node *next_ {nullptr};
110     Node *prev_ {nullptr};
111     uint32_t index_ {0};
112     bool isUsing_ {false};
113     bool isWeak_ {false};
114 };
115 
116 class DebugNode : public Node {
117 public:
Reset(JSThread *thread, Node *next, JSTaggedType value, bool isUsing)118     void Reset(JSThread *thread, Node *next, JSTaggedType value, bool isUsing)
119     {
120         Node::Reset(thread, next, value, isUsing);
121         ResetDebugInfo();
122         if (isUsing && thread->IsStartGlobalLeakCheck()) {
123             if (JSTaggedValue(value).IsHeapObject()) {
124                 if (thread->EnableGlobalObjectLeakCheck()) {
125                     SaveBacktrace(thread, value);
126                 }
127             } else {
128                 if (thread->EnableGlobalPrimitiveLeakCheck()) {
129                     SaveBacktrace(thread, value);
130                 }
131             }
132         }
133     }
134 
GetMarkCount() const135     int32_t GetMarkCount() const
136     {
137         return markCount_;
138     }
139 
MarkCount()140     void MarkCount()
141     {
142         markCount_++;
143     }
144 
ResetDebugInfo()145     void ResetDebugInfo()
146     {
147         markCount_ = 0;
148         globalNumber_ = -1;
149     }
150 
GetGlobalNumber()151     int32_t GetGlobalNumber()
152     {
153         return globalNumber_;
154     }
155 
156 private:
SaveBacktrace(JSThread *thread, JSTaggedType value)157     void SaveBacktrace(JSThread *thread, JSTaggedType value)
158     {
159         globalNumber_ = static_cast<int32_t>(thread->IncreaseGlobalNumberCount());
160         std::ostringstream stack;
161         stack << "Global Handle Number:[" << globalNumber_ << "], value:0x" <<
162             std::hex << value << std::endl;
163         Backtrace(stack, true);
164         thread->WriteToStackTraceFd(stack);
165     }
166 
167     int32_t markCount_ {0};
168     // A number generated in the order of distribution.It Used to help locate global memory leaks.
169     int32_t globalNumber_ {-1};
170 };
171 
172 class WeakNode : public Node {
173 public:
SetReference(void *ref)174     void SetReference(void *ref)
175     {
176         reference_ = ref;
177     }
178 
GetReference() const179     void* GetReference() const
180     {
181         return reference_;
182     }
183 
SetFreeGlobalCallback(WeakClearCallback callback)184     void SetFreeGlobalCallback(WeakClearCallback callback)
185     {
186         freeGlobalCallback_ = callback;
187     }
188 
SetNativeFinalizeCallback(WeakClearCallback callback)189     void SetNativeFinalizeCallback(WeakClearCallback callback)
190     {
191         nativeFinalizeCallback_ = callback;
192     }
193 
GetNativeFinalizeCallback() const194     WeakClearCallback GetNativeFinalizeCallback() const
195     {
196         return nativeFinalizeCallback_;
197     }
198 
GetFreeGlobalCallback() const199     WeakClearCallback GetFreeGlobalCallback() const
200     {
201         return freeGlobalCallback_;
202     }
203 
CallFreeGlobalCallback()204     void CallFreeGlobalCallback()
205     {
206         if (freeGlobalCallback_ != nullptr) {
207             freeGlobalCallback_(reference_);
208         }
209     }
210 
CallNativeFinalizeCallback()211     void CallNativeFinalizeCallback()
212     {
213         if (nativeFinalizeCallback_ != nullptr) {
214             nativeFinalizeCallback_(reference_);
215         }
216     }
217 private:
218     void *reference_ {nullptr};
219     WeakClearCallback freeGlobalCallback_ {nullptr};
220     WeakClearCallback nativeFinalizeCallback_ {nullptr};
221 };
222 
223 template<typename T>
224 class NodeList {
225 public:
NodeList()226     NodeList()
227     {
228         bool isWeak = std::is_same<T, WeakNode>::value;
229         for (uint32_t i = 0; i < GLOBAL_BLOCK_SIZE; i++) {
230             nodeList_[i].SetIndex(i);
231             nodeList_[i].SetWeak(isWeak);
232         }
233     }
234     ~NodeList() = default;
235 
NodeToNodeList(T *node)236     inline static NodeList<T> *NodeToNodeList(T *node)
237     {
238         uintptr_t ptr = ToUintPtr(node) - node->GetIndex() * sizeof(T);
239         return reinterpret_cast<NodeList<T> *>(ptr);
240     }
241 
NewNode(JSThread *thread, JSTaggedType value)242     inline T *NewNode(JSThread *thread, JSTaggedType value)
243     {
244         if (IsFull()) {
245             return nullptr;
246         }
247         T *node = &nodeList_[index_++];
248         node->Reset(thread, usedList_, value, true);
249         if (usedList_ != nullptr) {
250             usedList_->SetPrev(node);
251         }
252 
253         usedList_ = node;
254         return node;
255     }
256 
GetFreeNode(JSThread *thread, JSTaggedType value)257     inline T *GetFreeNode(JSThread *thread, JSTaggedType value)
258     {
259         T *node = freeList_;
260         if (node != nullptr) {
261             freeList_ = reinterpret_cast<T *>(node->GetNext());
262             node->Reset(thread, usedList_, value, true);
263             if (usedList_ != nullptr) {
264                 usedList_->SetPrev(node);
265             }
266             usedList_ = node;
267         }
268         return node;
269     }
270 
FreeNode(JSThread *thread, T *node)271     inline void FreeNode(JSThread *thread, T *node)
272     {
273         if (node->GetPrev() != nullptr) {
274             node->GetPrev()->SetNext(node->GetNext());
275         }
276         if (node->GetNext() != nullptr) {
277             node->GetNext()->SetPrev(node->GetPrev());
278         }
279         if (node == usedList_) {
280             usedList_ = reinterpret_cast<T *>(node->GetNext());
281         }
282         node->Reset(thread, freeList_, JSTaggedValue::Undefined().GetRawData(), false);
283         if (node->IsWeak()) {
284             reinterpret_cast<WeakNode *>(node)->SetReference(nullptr);
285             reinterpret_cast<WeakNode *>(node)->SetFreeGlobalCallback(nullptr);
286             reinterpret_cast<WeakNode *>(node)->SetNativeFinalizeCallback(nullptr);
287         }
288         if (freeList_ != nullptr) {
289             freeList_->SetPrev(node);
290         }
291         freeList_ = node;
292     }
293 
LinkTo(NodeList<T> *prev)294     inline void LinkTo(NodeList<T> *prev)
295     {
296         next_ = nullptr;
297         prev_ = prev;
298         prev_->next_ = this;
299     }
RemoveList()300     inline void RemoveList()
301     {
302         if (next_ != nullptr) {
303             next_->SetPrev(prev_);
304         }
305         if (prev_ != nullptr) {
306             prev_->SetNext(next_);
307         }
308         if (freeNext_ != nullptr) {
309             freeNext_->SetFreePrev(freePrev_);
310         }
311         if (freePrev_ != nullptr) {
312             freePrev_->SetFreeNext(freeNext_);
313         }
314     }
315 
IsFull()316     inline bool IsFull()
317     {
318         return index_ >= GLOBAL_BLOCK_SIZE;
319     }
320 
HasFreeNode()321     inline bool HasFreeNode()
322     {
323         return freeList_ != nullptr;
324     }
325 
HasUsagedNode()326     inline bool HasUsagedNode()
327     {
328         return !IsFull() || usedList_ != nullptr;
329     }
330 
SetNext(NodeList *next)331     inline void SetNext(NodeList *next)
332     {
333         next_ = next;
334     }
GetNext() const335     inline NodeList<T> *GetNext() const
336     {
337         return next_;
338     }
339 
SetPrev(NodeList<T> *prev)340     inline void SetPrev(NodeList<T> *prev)
341     {
342         prev_ = prev;
343     }
GetPrev() const344     inline NodeList<T> *GetPrev() const
345     {
346         return prev_;
347     }
348 
SetFreeNext(NodeList<T> *next)349     inline void SetFreeNext(NodeList<T> *next)
350     {
351         freeNext_ = next;
352     }
GetFreeNext() const353     inline NodeList<T> *GetFreeNext() const
354     {
355         return freeNext_;
356     }
357 
SetFreePrev(NodeList<T> *prev)358     inline void SetFreePrev(NodeList<T> *prev)
359     {
360         freePrev_ = prev;
361     }
GetFreePrev() const362     inline NodeList<T> *GetFreePrev() const
363     {
364         return freePrev_;
365     }
366 
367     template<class Callback>
IterateUsageGlobal(Callback callback)368     inline void IterateUsageGlobal(Callback callback)
369     {
370         T *next = usedList_;
371         T *current = nullptr;
372         while (next != nullptr) {
373             current = next;
374             next = reinterpret_cast<T *>(current->GetNext());
375             ASSERT(current != next);
376             callback(current);
377         }
378     }
379 
380 private:
381     static const uint32_t GLOBAL_BLOCK_SIZE = 256;
382     T nodeList_[GLOBAL_BLOCK_SIZE];  // all
383     T *freeList_ {nullptr};  // dispose node
384     T *usedList_ {nullptr};  // usage node
385     uint32_t index_ {0};
386     NodeList<T> *next_ {nullptr};
387     NodeList<T> *prev_ {nullptr};
388     NodeList<T> *freeNext_ {nullptr};
389     NodeList<T> *freePrev_ {nullptr};
390 };
391 
392 template<typename T>
393 class EcmaGlobalStorage {
394 public:
395     using WeakClearCallback = void (*)(void *);
396 
EcmaGlobalStorage(JSThread *thread, NativeAreaAllocator *allocator)397     EcmaGlobalStorage(JSThread *thread, NativeAreaAllocator *allocator)
398         : thread_(thread), allocator_(allocator)
399     {
400         topGlobalNodes_ = lastGlobalNodes_ = allocator_->New<NodeList<T>>();
401         topWeakGlobalNodes_ = lastWeakGlobalNodes_ = allocator_->New<NodeList<WeakNode>>();
402     }
403 
~EcmaGlobalStorage()404     ~EcmaGlobalStorage()
405     {
406         auto *weakNext = topWeakGlobalNodes_;
407         NodeList<WeakNode> *weakCurrent = nullptr;
408         while (weakNext != nullptr) {
409             weakCurrent = weakNext;
410             weakNext = weakCurrent->GetNext();
411             weakCurrent->IterateUsageGlobal([] (WeakNode *node) {
412                 node->SetUsing(false);
413                 node->SetObject(JSTaggedValue::Undefined().GetRawData());
414                 node->CallFreeGlobalCallback();
415                 node->CallNativeFinalizeCallback();
416             });
417             allocator_->Delete(weakCurrent);
418         }
419 
420         auto *next = topGlobalNodes_;
421         NodeList<T> *current = nullptr;
422         while (next != nullptr) {
423             current = next;
424             next = current->GetNext();
425             current->IterateUsageGlobal([] (T *node) {
426                 node->SetUsing(false);
427                 node->SetObject(JSTaggedValue::Undefined().GetRawData());
428             });
429             allocator_->Delete(current);
430         }
431     }
432 
NewGlobalHandle(JSTaggedType value)433     inline uintptr_t NewGlobalHandle(JSTaggedType value)
434     {
435         return NewGlobalHandleImplement(&lastGlobalNodes_, &freeListNodes_, value);
436     }
437 
DisposeGlobalHandle(uintptr_t nodeAddr)438     inline void DisposeGlobalHandle(uintptr_t nodeAddr)
439     {
440         T *node = reinterpret_cast<T *>(nodeAddr);
441         if (!node->IsUsing()) {
442             return;
443         }
444         if (node->IsWeak()) {
445             DisposeGlobalHandleInner(reinterpret_cast<WeakNode *>(node), &weakFreeListNodes_, &topWeakGlobalNodes_,
446                 &lastWeakGlobalNodes_);
447         } else {
448             DisposeGlobalHandleInner(node, &freeListNodes_, &topGlobalNodes_, &lastGlobalNodes_);
449         }
450     }
451 
SetWeak(uintptr_t nodeAddr, void *ref = nullptr, WeakClearCallback freeGlobalCallBack = nullptr, WeakClearCallback nativeFinalizeCallBack = nullptr)452     inline uintptr_t SetWeak(uintptr_t nodeAddr, void *ref = nullptr, WeakClearCallback freeGlobalCallBack = nullptr,
453                              WeakClearCallback nativeFinalizeCallBack = nullptr)
454     {
455         auto value = reinterpret_cast<T *>(nodeAddr)->GetObject();
456         DisposeGlobalHandle(nodeAddr);
457         uintptr_t addr = NewGlobalHandleImplement(&lastWeakGlobalNodes_, &weakFreeListNodes_, value);
458         WeakNode *node = reinterpret_cast<WeakNode *>(addr);
459         node->SetReference(ref);
460         node->SetFreeGlobalCallback(freeGlobalCallBack);
461         node->SetNativeFinalizeCallback(nativeFinalizeCallBack);
462         return addr;
463     }
464 
ClearWeak(uintptr_t nodeAddr)465     inline uintptr_t ClearWeak(uintptr_t nodeAddr)
466     {
467         auto value = reinterpret_cast<T *>(nodeAddr)->GetObject();
468         DisposeGlobalHandle(nodeAddr);
469         uintptr_t ret = NewGlobalHandleImplement(&lastGlobalNodes_, &freeListNodes_, value);
470         return ret;
471     }
IsWeak(uintptr_t addr) const472     inline bool IsWeak(uintptr_t addr) const
473     {
474         T *node = reinterpret_cast<T *>(addr);
475         return node->IsWeak();
476     }
477 
478     template<class Callback>
IterateUsageGlobal(Callback callback)479     void IterateUsageGlobal(Callback callback)
480     {
481         NodeList<T> *next = topGlobalNodes_;
482         NodeList<T> *current = nullptr;
483         while (next != nullptr) {
484             current = next;
485             next = current->GetNext();
486             ASSERT(current != next);
487             current->IterateUsageGlobal(callback);
488         }
489     }
490 
491     template<class Callback>
IterateWeakUsageGlobal(Callback callback)492     void IterateWeakUsageGlobal(Callback callback)
493     {
494         NodeList<WeakNode> *next = topWeakGlobalNodes_;
495         NodeList<WeakNode> *current = nullptr;
496         while (next != nullptr) {
497             current = next;
498             next = current->GetNext();
499             ASSERT(current != next);
500             current->IterateUsageGlobal(callback);
501         }
502     }
503 
504 private:
505     NO_COPY_SEMANTIC(EcmaGlobalStorage);
506     NO_MOVE_SEMANTIC(EcmaGlobalStorage);
507 
508     template<typename S>
DisposeGlobalHandleInner(S *node, NodeList<S> **freeList, NodeList<S> **topNodes, NodeList<S> **lastNodes)509     inline void DisposeGlobalHandleInner(S *node, NodeList<S> **freeList, NodeList<S> **topNodes,
510                                     NodeList<S> **lastNodes)
511     {
512         NodeList<S> *list = NodeList<S>::NodeToNodeList(node);
513         list->FreeNode(thread_, node);
514 
515         // If NodeList has no usage node, then delete NodeList
516         if (!list->HasUsagedNode() && (*topNodes != *lastNodes)) {
517             list->RemoveList();
518             if (*freeList == list) {
519                 *freeList = list->GetFreeNext();
520             }
521             if (*topNodes == list) {
522                 *topNodes = list->GetNext();
523             }
524             if (*lastNodes == list) {
525                 *lastNodes = list->GetPrev();
526             }
527             allocator_->Delete(list);
528         } else {
529             // Add to freeList
530             if (list != *freeList && list->GetFreeNext() == nullptr && list->GetFreePrev() == nullptr) {
531                 list->SetFreeNext(*freeList);
532                 if (*freeList != nullptr) {
533                     (*freeList)->SetFreePrev(list);
534                 }
535                 *freeList = list;
536             }
537         }
538     }
539 
540     template<typename S>
NewGlobalHandleImplement(NodeList<S> **storage, NodeList<S> **freeList, JSTaggedType value)541     inline uintptr_t NewGlobalHandleImplement(NodeList<S> **storage, NodeList<S> **freeList, JSTaggedType value)
542     {
543 #if ECMASCRIPT_ENABLE_NEW_HANDLE_CHECK
544         thread_->CheckJSTaggedType(value);
545 #endif
546         if (!(*storage)->IsFull()) {
547             // alloc new block
548             S *node = (*storage)->NewNode(thread_, value);
549             ASSERT(node != nullptr);
550             return node->GetObjectAddress();
551         }
552         if (*freeList != nullptr) {
553             // use free_list node
554             S *node = (*freeList)->GetFreeNode(thread_, value);
555             ASSERT(node != nullptr);
556             if (!(*freeList)->HasFreeNode()) {
557                 auto next = (*freeList)->GetFreeNext();
558                 (*freeList)->SetFreeNext(nullptr);
559                 (*freeList)->SetFreePrev(nullptr);
560                 if (next != nullptr) {
561                     next->SetFreePrev(nullptr);
562                 }
563                 *freeList = next;
564             }
565             return node->GetObjectAddress();
566         }
567         auto block = allocator_->New<NodeList<S>>();
568         if (block == nullptr) {
569             LOG_ECMA(FATAL) << "NewGlobalHandleImplement:block is nullptr";
570         }
571         block->LinkTo(*storage);
572         *storage = block;
573 
574         // use node in block finally
575         S *node = (*storage)->NewNode(thread_, value);
576         ASSERT(node != nullptr);
577         return node->GetObjectAddress();
578     }
579 
580     [[maybe_unused]] JSThread *thread_ {nullptr};
581     NativeAreaAllocator *allocator_ {nullptr};
582     NodeList<T> *topGlobalNodes_ {nullptr};
583     NodeList<T> *lastGlobalNodes_ {nullptr};
584     NodeList<T> *freeListNodes_ {nullptr};
585 
586     NodeList<WeakNode> *topWeakGlobalNodes_ {nullptr};
587     NodeList<WeakNode> *lastWeakGlobalNodes_ {nullptr};
588     NodeList<WeakNode> *weakFreeListNodes_ {nullptr};
589 };
590 }  // namespace panda::ecmascript
591 #endif  // ECMASCRIPT_ECMA_GLOBAL_STORAGE_H
592