* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <condition_variable>
#include "ecmascript/napi/include/dfx_jsnapi.h"
#include "ecmascript/napi/include/jsnapi.h"
#include "ecmascript/tests/test_helper.h"
namespace panda::test {
using namespace panda;
using namespace panda::ecmascript;
using FunctionCallbackInfo = Local<JSValueRef>(*)(JsiRuntimeCallInfo *);
std::condition_variable g_semaphore;
class ThreadTerminationTest : public testing::Test {
public:
static void SetUpTestCase()
{
GTEST_LOG_(INFO) << "SetUpTestCase";
}
static void TearDownTestCase()
{
GTEST_LOG_(INFO) << "TearDownCase";
}
void SetUp() override
{
TestHelper::CreateEcmaVMWithScope(vm_, thread_, scope_);
}
void TearDown() override
{
TestHelper::DestroyEcmaVMWithScope(vm_, scope_);
}
static Local<JSValueRef> TerminateThread(JsiRuntimeCallInfo *runtimeCallInfo);
static Local<JSValueRef> Fail(JsiRuntimeCallInfo *runtimeCallInfo);
static Local<JSValueRef> Signal(JsiRuntimeCallInfo *runtimeCallInfo);
static void RegisterGlobalTemplate(const EcmaVM *vm, FunctionCallbackInfo terminate, FunctionCallbackInfo fail,
FunctionCallbackInfo signal)
{
[[maybe_unused]] EcmaHandleScope handleScope(vm->GetJSThread());
Local<ObjectRef> globalObj = JSNApi::GetGlobalObject(vm);
globalObj->Set(vm, StringRef::NewFromUtf8(vm, "terminate"), FunctionRef::New(
const_cast<panda::EcmaVM*>(vm), terminate));
globalObj->Set(vm, StringRef::NewFromUtf8(vm, "fail"), FunctionRef::New(
const_cast<panda::EcmaVM*>(vm), fail));
globalObj->Set(vm, StringRef::NewFromUtf8(vm, "signal"), FunctionRef::New(
const_cast<panda::EcmaVM*>(vm), Signal));
}
protected:
EcmaVM *vm_ {nullptr};
JSThread *thread_ = {nullptr};
EcmaHandleScope *scope_ {nullptr};
};
class TerminatorThread {
public:
explicit TerminatorThread(EcmaVM *vm) : vm_(vm) {}
~TerminatorThread() = default;
void Run()
{
thread_ = std::thread([this]() {
std::unique_lock<std::mutex> lock(mutex_);
g_semaphore.wait(lock);
DFXJSNApi::TerminateExecution(vm_);
});
}
void RunWithSleep()
{
thread_ = std::thread([this]() {
usleep(1000000);
DFXJSNApi::TerminateExecution(vm_);
});
}
void Join()
{
thread_.join();
}
private:
EcmaVM *vm_ {nullptr};
std::mutex mutex_;
std::thread thread_;
};
Local<JSValueRef> ThreadTerminationTest::TerminateThread(JsiRuntimeCallInfo *runtimeCallInfo)
{
EcmaVM *vm = runtimeCallInfo->GetVM();
DFXJSNApi::TerminateExecution(vm);
return JSValueRef::Undefined(vm);
}
Local<JSValueRef> ThreadTerminationTest::Fail([[maybe_unused]]JsiRuntimeCallInfo *runtimeCallInfo)
{
UNREACHABLE();
}
Local<JSValueRef> ThreadTerminationTest::Signal(JsiRuntimeCallInfo *runtimeCallInfo)
{
EcmaVM *vm = runtimeCallInfo->GetVM();
g_semaphore.notify_one();
return JSValueRef::Undefined(vm);
}
HWTEST_F_L0(ThreadTerminationTest, TerminateFromThreadItself)
{
JSThread *thread = vm_->GetAssociatedJSThread();
auto* factory = vm_->GetFactory();
JSHandle<GlobalEnv> globalEnv = factory->NewGlobalEnv();
SaveAndSwitchEnv(thread, globalEnv.GetTaggedValue());
JSNApi::EnableUserUncaughtErrorHandler(vm_);
RegisterGlobalTemplate(vm_, TerminateThread, Fail, Signal);
TryCatch tryCatch(vm_);
std::string baseFileName = MODULE_ABC_PATH "termination_1.abc";
JSNApi::Execute(vm_, baseFileName, "termination_1");
EXPECT_TRUE(tryCatch.HasCaught());
}
HWTEST_F_L0(ThreadTerminationTest, TerminateFromOtherThread)
{
JSThread *thread = vm_->GetAssociatedJSThread();
auto* factory = vm_->GetFactory();
JSHandle<GlobalEnv> globalEnv = factory->NewGlobalEnv();
SaveAndSwitchEnv(thread, globalEnv.GetTaggedValue());
TerminatorThread terminatorThread(vm_);
terminatorThread.Run();
JSNApi::EnableUserUncaughtErrorHandler(vm_);
RegisterGlobalTemplate(vm_, TerminateThread, Fail, Signal);
TryCatch tryCatch(vm_);
std::string baseFileName = MODULE_ABC_PATH "termination_2.abc";
JSNApi::Execute(vm_, baseFileName, "termination_2");
EXPECT_TRUE(tryCatch.HasCaught());
terminatorThread.Join();
}
HWTEST_F_L0(ThreadTerminationTest, TerminateFromOtherThreadWithoutCall)
{
JSThread *thread = vm_->GetAssociatedJSThread();
auto* factory = vm_->GetFactory();
JSHandle<GlobalEnv> globalEnv = factory->NewGlobalEnv();
SaveAndSwitchEnv(thread, globalEnv.GetTaggedValue());
TerminatorThread terminatorThread(vm_);
terminatorThread.RunWithSleep();
JSNApi::EnableUserUncaughtErrorHandler(vm_);
RegisterGlobalTemplate(vm_, TerminateThread, Fail, Signal);
TryCatch tryCatch(vm_);
std::string baseFileName = MODULE_ABC_PATH "termination_3.abc";
JSNApi::Execute(vm_, baseFileName, "termination_3");
EXPECT_TRUE(tryCatch.HasCaught());
terminatorThread.Join();
}
HWTEST_F_L0(ThreadTerminationTest, TerminateClearArrayJoinStack)
{
JSThread *thread = vm_->GetAssociatedJSThread();
auto* factory = vm_->GetFactory();
JSHandle<GlobalEnv> globalEnv = factory->NewGlobalEnv();
SaveAndSwitchEnv(thread, globalEnv.GetTaggedValue());
JSNApi::EnableUserUncaughtErrorHandler(vm_);
RegisterGlobalTemplate(vm_, TerminateThread, Fail, Signal);
TryCatch tryCatch(vm_);
std::string baseFileName = MODULE_ABC_PATH "termination_4.abc";
JSNApi::Execute(vm_, baseFileName, "termination_4");
EXPECT_TRUE(tryCatch.HasCaught());
}
HWTEST_F_L0(ThreadTerminationTest, TerminateInMicroTask)
{
JSThread *thread = vm_->GetAssociatedJSThread();
auto* factory = vm_->GetFactory();
JSHandle<GlobalEnv> globalEnv = factory->NewGlobalEnv();
SaveAndSwitchEnv(thread, globalEnv.GetTaggedValue());
JSNApi::EnableUserUncaughtErrorHandler(vm_);
RegisterGlobalTemplate(vm_, TerminateThread, Fail, Signal);
TryCatch tryCatch(vm_);
std::string baseFileName = MODULE_ABC_PATH "termination_5.abc";
JSNApi::Execute(vm_, baseFileName, "termination_5");
}
HWTEST_F_L0(ThreadTerminationTest, TerminateWithoutExecutingMicroTask)
{
JSThread *thread = vm_->GetAssociatedJSThread();
auto* factory = vm_->GetFactory();
JSHandle<GlobalEnv> globalEnv = factory->NewGlobalEnv();
SaveAndSwitchEnv(thread, globalEnv.GetTaggedValue());
JSNApi::EnableUserUncaughtErrorHandler(vm_);
RegisterGlobalTemplate(vm_, TerminateThread, Fail, Signal);
TryCatch tryCatch(vm_);
std::string baseFileName = MODULE_ABC_PATH "termination_6.abc";
JSNApi::Execute(vm_, baseFileName, "termination_6");
EXPECT_TRUE(tryCatch.HasCaught());
}
}