1/*
2 * Copyright (c) 2023 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 <condition_variable>
17
18#include "ecmascript/napi/include/dfx_jsnapi.h"
19#include "ecmascript/napi/include/jsnapi.h"
20#include "ecmascript/tests/test_helper.h"
21
22namespace panda::test {
23using namespace panda;
24using namespace panda::ecmascript;
25using FunctionCallbackInfo = Local<JSValueRef>(*)(JsiRuntimeCallInfo *);
26std::condition_variable g_semaphore;
27
28class ThreadTerminationTest : public testing::Test {
29public:
30    static void SetUpTestCase()
31    {
32        GTEST_LOG_(INFO) << "SetUpTestCase";
33    }
34
35    static void TearDownTestCase()
36    {
37        GTEST_LOG_(INFO) << "TearDownCase";
38    }
39
40    void SetUp() override
41    {
42        TestHelper::CreateEcmaVMWithScope(vm_, thread_, scope_);
43    }
44
45    void TearDown() override
46    {
47        TestHelper::DestroyEcmaVMWithScope(vm_, scope_);
48    }
49
50    static Local<JSValueRef> TerminateThread(JsiRuntimeCallInfo *runtimeCallInfo);
51    static Local<JSValueRef> Fail(JsiRuntimeCallInfo *runtimeCallInfo);
52    static Local<JSValueRef> Signal(JsiRuntimeCallInfo *runtimeCallInfo);
53
54    static void RegisterGlobalTemplate(const EcmaVM *vm, FunctionCallbackInfo terminate, FunctionCallbackInfo fail,
55                                       FunctionCallbackInfo signal)
56    {
57        [[maybe_unused]] EcmaHandleScope handleScope(vm->GetJSThread());
58        Local<ObjectRef> globalObj = JSNApi::GetGlobalObject(vm);
59        globalObj->Set(vm, StringRef::NewFromUtf8(vm, "terminate"), FunctionRef::New(
60            const_cast<panda::EcmaVM*>(vm), terminate));
61        globalObj->Set(vm, StringRef::NewFromUtf8(vm, "fail"), FunctionRef::New(
62            const_cast<panda::EcmaVM*>(vm), fail));
63        globalObj->Set(vm, StringRef::NewFromUtf8(vm, "signal"), FunctionRef::New(
64            const_cast<panda::EcmaVM*>(vm), Signal));
65    }
66
67protected:
68    EcmaVM *vm_ {nullptr};
69    JSThread *thread_ = {nullptr};
70    EcmaHandleScope *scope_ {nullptr};
71};
72
73class TerminatorThread {
74public:
75    explicit TerminatorThread(EcmaVM *vm) : vm_(vm) {}
76    ~TerminatorThread() = default;
77
78    void Run()
79    {
80        thread_ = std::thread([this]() {
81            std::unique_lock<std::mutex> lock(mutex_);
82            g_semaphore.wait(lock);
83            DFXJSNApi::TerminateExecution(vm_);
84        });
85    }
86
87    void RunWithSleep()
88    {
89        thread_ = std::thread([this]() {
90            usleep(1000000); // 1000000 : 1s for loop to execute
91            DFXJSNApi::TerminateExecution(vm_);
92        });
93    }
94
95    void Join()
96    {
97        thread_.join();
98    }
99
100private:
101    EcmaVM *vm_ {nullptr};
102    std::mutex mutex_;
103    std::thread thread_;
104};
105
106Local<JSValueRef> ThreadTerminationTest::TerminateThread(JsiRuntimeCallInfo *runtimeCallInfo)
107{
108    EcmaVM *vm = runtimeCallInfo->GetVM();
109    DFXJSNApi::TerminateExecution(vm);
110    return JSValueRef::Undefined(vm);
111}
112
113Local<JSValueRef> ThreadTerminationTest::Fail([[maybe_unused]]JsiRuntimeCallInfo *runtimeCallInfo)
114{
115    UNREACHABLE();
116}
117
118Local<JSValueRef> ThreadTerminationTest::Signal(JsiRuntimeCallInfo *runtimeCallInfo)
119{
120    EcmaVM *vm = runtimeCallInfo->GetVM();
121    g_semaphore.notify_one();
122    return JSValueRef::Undefined(vm);
123}
124
125HWTEST_F_L0(ThreadTerminationTest, TerminateFromThreadItself)
126{
127    JSThread *thread = vm_->GetAssociatedJSThread();
128    EcmaContext::MountContext(thread);
129    JSNApi::EnableUserUncaughtErrorHandler(vm_);
130    RegisterGlobalTemplate(vm_, TerminateThread, Fail, Signal);
131    TryCatch tryCatch(vm_);
132    std::string baseFileName = MODULE_ABC_PATH "termination_1.abc";
133    JSNApi::Execute(vm_, baseFileName, "termination_1");
134    EXPECT_TRUE(tryCatch.HasCaught());
135    EcmaContext::UnmountContext(thread);
136}
137
138HWTEST_F_L0(ThreadTerminationTest, TerminateFromOtherThread)
139{
140    JSThread *thread = vm_->GetAssociatedJSThread();
141    EcmaContext::MountContext(thread);
142    TerminatorThread terminatorThread(vm_);
143    terminatorThread.Run();
144    JSNApi::EnableUserUncaughtErrorHandler(vm_);
145    RegisterGlobalTemplate(vm_, TerminateThread, Fail, Signal);
146    TryCatch tryCatch(vm_);
147    std::string baseFileName = MODULE_ABC_PATH "termination_2.abc";
148    JSNApi::Execute(vm_, baseFileName, "termination_2");
149    EXPECT_TRUE(tryCatch.HasCaught());
150    terminatorThread.Join();
151    EcmaContext::UnmountContext(thread);
152}
153
154HWTEST_F_L0(ThreadTerminationTest, TerminateFromOtherThreadWithoutCall)
155{
156    JSThread *thread = vm_->GetAssociatedJSThread();
157    EcmaContext::MountContext(thread);
158    TerminatorThread terminatorThread(vm_);
159    terminatorThread.RunWithSleep();
160    JSNApi::EnableUserUncaughtErrorHandler(vm_);
161    RegisterGlobalTemplate(vm_, TerminateThread, Fail, Signal);
162    TryCatch tryCatch(vm_);
163    std::string baseFileName = MODULE_ABC_PATH "termination_3.abc";
164    JSNApi::Execute(vm_, baseFileName, "termination_3");
165    EXPECT_TRUE(tryCatch.HasCaught());
166    terminatorThread.Join();
167    EcmaContext::UnmountContext(thread);
168}
169
170HWTEST_F_L0(ThreadTerminationTest, TerminateClearArrayJoinStack)
171{
172    JSThread *thread = vm_->GetAssociatedJSThread();
173    EcmaContext::MountContext(thread);
174    JSNApi::EnableUserUncaughtErrorHandler(vm_);
175    RegisterGlobalTemplate(vm_, TerminateThread, Fail, Signal);
176    TryCatch tryCatch(vm_);
177    std::string baseFileName = MODULE_ABC_PATH "termination_4.abc";
178    JSNApi::Execute(vm_, baseFileName, "termination_4");
179    EXPECT_TRUE(tryCatch.HasCaught());
180    EcmaContext::UnmountContext(thread);
181}
182
183HWTEST_F_L0(ThreadTerminationTest, TerminateInMicroTask)
184{
185    JSThread *thread = vm_->GetAssociatedJSThread();
186    EcmaContext::MountContext(thread);
187    JSNApi::EnableUserUncaughtErrorHandler(vm_);
188    RegisterGlobalTemplate(vm_, TerminateThread, Fail, Signal);
189    TryCatch tryCatch(vm_);
190    std::string baseFileName = MODULE_ABC_PATH "termination_5.abc";
191    JSNApi::Execute(vm_, baseFileName, "termination_5");
192    EcmaContext::UnmountContext(thread);
193}
194
195HWTEST_F_L0(ThreadTerminationTest, TerminateWithoutExecutingMicroTask)
196{
197    JSThread *thread = vm_->GetAssociatedJSThread();
198    EcmaContext::MountContext(thread);
199    JSNApi::EnableUserUncaughtErrorHandler(vm_);
200    RegisterGlobalTemplate(vm_, TerminateThread, Fail, Signal);
201    TryCatch tryCatch(vm_);
202    std::string baseFileName = MODULE_ABC_PATH "termination_6.abc";
203    JSNApi::Execute(vm_, baseFileName, "termination_6");
204    EXPECT_TRUE(tryCatch.HasCaught());
205    EcmaContext::UnmountContext(thread);
206}
207} // namespace panda::test
208