1/*
2 * Copyright (c) 2023-2024 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/ecma_handle_scope.h"
17
18#include "ecmascript/ecma_context.h"
19
20namespace panda::ecmascript {
21EcmaHandleScope::EcmaHandleScope(JSThread *thread) : thread_(thread)
22{
23    auto context = thread_->GetCurrentEcmaContext();
24    OpenHandleScope(context);
25    OpenPrimitiveScope(context);
26}
27
28void EcmaHandleScope::OpenHandleScope(EcmaContext *context)
29{
30    prevNext_ = context->handleScopeStorageNext_;
31    prevEnd_ = context->handleScopeStorageEnd_;
32    prevHandleStorageIndex_ = context->currentHandleStorageIndex_;
33#ifdef ECMASCRIPT_ENABLE_HANDLE_LEAK_CHECK
34    context->HandleScopeCountAdd();
35    prevHandleScope_ = context->GetLastHandleScope();
36    context->SetLastHandleScope(this);
37#endif
38}
39
40void EcmaHandleScope::OpenPrimitiveScope(EcmaContext *context)
41{
42    prevPrimitiveNext_ = context->primitiveScopeStorageNext_;
43    prevPrimitiveEnd_ = context->primitiveScopeStorageEnd_;
44    prevPrimitiveStorageIndex_ = context->currentPrimitiveStorageIndex_;
45#ifdef ECMASCRIPT_ENABLE_HANDLE_LEAK_CHECK
46    context->PrimitiveScopeCountAdd();
47    prevPrimitiveScope_ = context->GetLastPrimitiveScope();
48    context->SetLastPrimitiveScope(this);
49#endif
50}
51
52EcmaHandleScope::~EcmaHandleScope()
53{
54    auto context = thread_->GetCurrentEcmaContext();
55    CloseHandleScope(context);
56    ClosePrimitiveScope(context);
57}
58
59void EcmaHandleScope::CloseHandleScope(EcmaContext *context)
60{
61#ifdef ECMASCRIPT_ENABLE_HANDLE_LEAK_CHECK
62    context->HandleScopeCountDec();
63    context->SetLastHandleScope(prevHandleScope_);
64    prevHandleScope_ = nullptr;
65#endif
66    context->handleScopeStorageNext_ = prevNext_;
67    if (context->handleScopeStorageEnd_ != prevEnd_) {
68        context->handleScopeStorageEnd_ = prevEnd_;
69        context->ShrinkHandleStorage(prevHandleStorageIndex_);
70    }
71}
72
73void EcmaHandleScope::ClosePrimitiveScope(EcmaContext *context)
74{
75#ifdef ECMASCRIPT_ENABLE_HANDLE_LEAK_CHECK
76    context->PrimitiveScopeCountDec();
77    context->SetLastPrimitiveScope(prevPrimitiveScope_);
78    prevPrimitiveScope_ = nullptr;
79#endif
80    context->primitiveScopeStorageNext_ = prevPrimitiveNext_;
81    if (context->primitiveScopeStorageEnd_ != prevPrimitiveEnd_) {
82        context->primitiveScopeStorageEnd_ = prevPrimitiveEnd_;
83        context->ShrinkPrimitiveStorage(prevPrimitiveStorageIndex_);
84    }
85}
86
87uintptr_t EcmaHandleScope::NewHandle(JSThread *thread, JSTaggedType value)
88{
89    CHECK_NO_HANDLE_ALLOC;
90#if ECMASCRIPT_ENABLE_THREAD_STATE_CHECK
91    if (UNLIKELY(!thread->IsInRunningStateOrProfiling())) {
92        LOG_ECMA(FATAL) << "New handle must be in jsthread running state";
93        UNREACHABLE();
94    }
95#endif
96    // Handle is a kind of GC_ROOT, and should only directly hold Obejct or Primitive, not a weak reference.
97    ASSERT(!JSTaggedValue(value).IsWeak());
98    auto context = thread->GetCurrentEcmaContext();
99#ifdef ECMASCRIPT_ENABLE_HANDLE_LEAK_CHECK
100    // Each Handle must be managed by HandleScope, otherwise it may cause Handle leakage.
101    if (context->handleScopeCount_ <= 0) {
102        LOG_ECMA(ERROR) << "New handle must be in handlescope" << context->handleScopeCount_;
103    }
104    static const long MAYBE_HANDLE_LEAK_TIME_MS = 5000;
105    if (context->GetLastHandleScope() != nullptr) {
106        float totalSpentTime = context->GetLastHandleScope()->scope_.TotalSpentTime();
107        if (totalSpentTime >= MAYBE_HANDLE_LEAK_TIME_MS) {
108            LOG_ECMA(INFO) << "New handle in scope count:" << context->handleScopeCount_
109                            << ", time:" << totalSpentTime << "ms";
110            std::ostringstream stack;
111            Backtrace(stack, true);
112            LOG_ECMA(INFO) << stack.str();
113        }
114    }
115#endif
116    auto result = context->handleScopeStorageNext_;
117    if (result == context->handleScopeStorageEnd_) {
118        result = reinterpret_cast<JSTaggedType *>(context->ExpandHandleStorage());
119    }
120#if ECMASCRIPT_ENABLE_NEW_HANDLE_CHECK
121    thread->CheckJSTaggedType(value);
122    if (result == nullptr) {
123        LOG_ECMA(ERROR) << "result is nullptr, New handle fail!";
124        return 0U;
125    }
126#endif
127    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
128    context->handleScopeStorageNext_ = result + 1;
129    *result = value;
130    return reinterpret_cast<uintptr_t>(result);
131}
132
133uintptr_t EcmaHandleScope::NewPrimitiveHandle(JSThread *thread, JSTaggedType value)
134{
135    CHECK_NO_HANDLE_ALLOC;
136    auto context = thread->GetCurrentEcmaContext();
137#ifdef ECMASCRIPT_ENABLE_HANDLE_LEAK_CHECK
138    // Each PrimitiveHandle must be managed by PrimitiveScope, otherwise it may cause Handle leakage.
139    if (context->primitiveScopeCount_ <= 0) {
140        LOG_ECMA(ERROR) << "New primitive must be in primitivescope" << context->primitiveScopeCount_;
141    }
142    static const long MAYBE_PRIMITIVE_LEAK_TIME_MS = 5000;
143    if (context->GetLastPrimitiveScope() != nullptr) {
144        float totalSpentTime = context->GetLastPrimitiveScope()->scope_.TotalSpentTime();
145        if (totalSpentTime >= MAYBE_PRIMITIVE_LEAK_TIME_MS) {
146            LOG_ECMA(INFO) << "New primitiveHandle in scope count:" << context->primitiveScopeCount_
147                            << ", time:" << totalSpentTime << "ms";
148            std::ostringstream stack;
149            Backtrace(stack, true);
150            LOG_ECMA(INFO) << stack.str();
151        }
152    }
153#endif
154    auto result = context->primitiveScopeStorageNext_;
155    if (result == context->primitiveScopeStorageEnd_) {
156        result = reinterpret_cast<JSTaggedType *>(context->ExpandPrimitiveStorage());
157    }
158#if ECMASCRIPT_ENABLE_NEW_HANDLE_CHECK
159    thread->CheckJSTaggedType(value);
160    if (result == nullptr) {
161        LOG_ECMA(ERROR) << "result is nullptr, New primitiveHandle fail!";
162        return 0U;
163    }
164#endif
165    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
166    context->primitiveScopeStorageNext_ = result + 1;
167    *result = value;
168    return reinterpret_cast<uintptr_t>(result);
169}
170}  // namespace panda::ecmascript