* Copyright (c) 2021-2022 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 "native_engine/native_engine.h"
#include <cstdint>
#include <memory>
#if !defined(WINDOWS_PLATFORM) && !defined(MAC_PLATFORM) && !defined(IOS_PLATFORM) && !defined(LINUX_PLATFORM)
#include <sys/epoll.h>
#endif
#ifdef IOS_PLATFORM
#include <sys/event.h>
#endif
#ifdef ENABLE_HITRACE
#include "hitrace_meter.h"
#endif
#include "native_engine/native_utils.h"
#include "unicode/ucnv.h"
#include "securec.h"
constexpr size_t NAME_BUFFER_SIZE = 64;
static constexpr auto PANDA_MAIN_FUNCTION = "_GLOBAL::func_main_0";
static constexpr int32_t API11 = 11;
static constexpr int32_t API_VERSION_MASK = 1000;
using panda::JSValueRef;
using panda::Local;
using panda::LocalScope;
using panda::ObjectRef;
using panda::StringRef;
namespace {
const char* g_errorMessages[] = {
nullptr,
"Invalid parameter",
"Need object",
"Need string",
"Need string or symbol",
"Need function",
"Need number",
"Need boolean",
"Need array",
"Generic failure",
"An exception is blocking",
"Asynchronous work cancelled",
"Escape called twice",
"Handle scope mismatch",
"Callback scope mismatch",
"Asynchronous work queue is full",
"Asynchronous work handle is closing",
"Need bigint",
"Need date",
"Need arraybuffer",
"Need detachable arraybuffer",
};
}
thread_local static GetContainerScopeIdCallback g_getContainerScopeIdFunc;
thread_local static ContainerScopeCallback g_initContainerScopeFunc;
thread_local static ContainerScopeCallback g_finishContainerScopeFunc;
std::mutex NativeEngine::g_alivedEngineMutex_;
std::unordered_set<NativeEngine*> NativeEngine::g_alivedEngine_;
uint64_t NativeEngine::g_lastEngineId_ = 1;
std::mutex NativeEngine::g_mainThreadEngineMutex_;
NativeEngine* NativeEngine::g_mainThreadEngine_;
NapiErrorManager* NapiErrorManager::instance_ = NULL;
static std::mutex g_errorManagerInstanceMutex;
NativeEngine::NativeEngine(void* jsEngine) : jsEngine_(jsEngine),
criticalScopeCounter_(std::make_shared<uint64_t>(0))
{
SetMainThreadEngine(this);
SetAlive();
InitUvField();
workerThreadState_ = new WorkerThreadState();
}
NativeEngine::NativeEngine(NativeEngine* parent) : jsEngine_(parent->jsEngine_),
workerThreadState_(nullptr),
criticalScopeCounter_(parent->criticalScopeCounter_)
{
SetAlive();
InitUvField();
}
void NativeEngine::InitUvField()
{
if (memset_s(&uvAsync_, sizeof(uvAsync_), 0, sizeof(uvAsync_)) != EOK) {
HILOG_ERROR("failed to init uvAsync_");
return;
}
if (memset_s(&uvSem_, sizeof(uvSem_), 0, sizeof(uvSem_)) != EOK) {
HILOG_ERROR("failed to init uvSem_");
return;
}
#if !defined(PREVIEW)
if (memset_s(&uvThread_, sizeof(uvThread_), 0, sizeof(uvThread_)) != EOK) {
HILOG_ERROR("failed to init uvThread_");
return;
}
#endif
}
NativeEngine::~NativeEngine()
{
HILOG_DEBUG("NativeEngine::~NativeEngine");
isInDestructor_ = true;
if (cleanEnv_ != nullptr) {
cleanEnv_();
}
if (workerThreadState_ != nullptr) {
delete workerThreadState_;
}
std::lock_guard<std::mutex> insLock(instanceDataLock_);
FinalizerInstanceData();
RunInstanceFinalizer();
}
void NativeEngine::Init()
{
HILOG_DEBUG("NativeEngine::Init");
InitWithoutUV();
loop_ = new (std::nothrow)uv_loop_t;
if (loop_ == nullptr) {
HILOG_ERROR("failed to create uv_loop, async task interface would not work");
return;
}
if (uv_loop_init(loop_) != EOK) {
HILOG_ERROR("failed to init uv_loop, async task interface would not work");
delete loop_;
loop_ = nullptr;
return;
}
uv_async_init(loop_, &uvAsync_, nullptr);
uv_sem_init(&uvSem_, 0);
NativeEvent::CreateDefaultFunction(this, defaultFunc_, eventMutex_);
}
void NativeEngine::Deinit()
{
HILOG_INFO("NativeEngine");
if (loop_ != nullptr) {
NativeEvent::DestoryDefaultFunction(true, defaultFunc_, eventMutex_);
uv_sem_destroy(&uvSem_);
uv_close((uv_handle_t*)&uvAsync_, nullptr);
}
RunCleanup();
NotifyVMIgnoreFinalizeCallback();
DeinitWithoutUV();
SetDead();
SetStopping(true);
if (loop_ == nullptr) {
return;
}
if (uv_loop_close(loop_) != EOK) {
HILOG_WARN("faild to close uv_loop, rerun it.");
uv_run(loop_, UV_RUN_DEFAULT);
if (uv_loop_close(loop_) != EOK) {
HILOG_ERROR("faild to close uv_loop, after reran.");
}
};
delete loop_;
loop_ = nullptr;
}
void NativeEngine::InitWithoutUV()
{
moduleManager_ = NativeModuleManager::GetInstance();
referenceManager_ = new NativeReferenceManager();
callbackScopeManager_ = new NativeCallbackScopeManager();
tid_ = pthread_self();
sysTid_ = GetCurSysTid();
}
void NativeEngine::DeinitWithoutUV()
{
if (referenceManager_ != nullptr) {
delete referenceManager_;
referenceManager_ = nullptr;
}
if (callbackScopeManager_ != nullptr) {
delete callbackScopeManager_;
callbackScopeManager_ = nullptr;
}
}
NativeReferenceManager* NativeEngine::GetReferenceManager()
{
return referenceManager_;
}
NativeModuleManager* NativeEngine::GetModuleManager()
{
return moduleManager_;
}
NativeCallbackScopeManager* NativeEngine::GetCallbackScopeManager()
{
return callbackScopeManager_;
}
uv_loop_t* NativeEngine::GetUVLoop() const
{
return loop_;
}
pthread_t NativeEngine::GetTid() const
{
return tid_;
}
ThreadId NativeEngine::GetCurSysTid()
{
return reinterpret_cast<ThreadId>(panda::JSNApi::GetCurrentThreadId());
}
bool NativeEngine::ReinitUVLoop()
{
if (defaultFunc_ != nullptr) {
NativeEvent::DestoryDefaultFunction(true, defaultFunc_, eventMutex_);
}
if (loop_ != nullptr) {
auto const ensureClosing = [](uv_handle_t *handle, void *arg) {
if (!uv_is_closing(handle)) {
uv_close(handle, nullptr);
}
};
uv_walk(loop_, ensureClosing, nullptr);
while (uv_run(loop_, UV_RUN_DEFAULT) != 0) {};
uv_loop_close(loop_);
delete loop_;
loop_ = nullptr;
}
tid_ = pthread_self();
sysTid_ = GetCurSysTid();
UpdateCrossThreadCheckStatus();
loop_ = new (std::nothrow)uv_loop_t;
if (loop_ == nullptr) {
HILOG_ERROR("failed to create uv_loop, async task interface would not work");
return false;
}
if (uv_loop_init(loop_) != EOK) {
HILOG_ERROR("failed to init uv_loop, async task interface would not work");
delete loop_;
loop_ = nullptr;
return false;
}
uv_async_init(loop_, &uvAsync_, nullptr);
uv_sem_init(&uvSem_, 0);
NativeEvent::CreateDefaultFunction(this, defaultFunc_, eventMutex_);
panda::JSNApi::NotifyEnvInitialized(const_cast<EcmaVM*>(GetEcmaVm()));
return true;
}
void NativeEngine::Loop(LoopMode mode, bool needSync)
{
if (loop_ == nullptr) {
HILOG_ERROR("uv loop is nullptr");
return;
}
bool more = true;
switch (mode) {
case LoopMode::LOOP_DEFAULT:
more = uv_run(loop_, UV_RUN_DEFAULT);
break;
case LoopMode::LOOP_ONCE:
more = uv_run(loop_, UV_RUN_ONCE);
break;
case LoopMode::LOOP_NOWAIT:
more = uv_run(loop_, UV_RUN_NOWAIT);
break;
default:
return;
}
if (more == false) {
uv_loop_alive(loop_);
}
if (needSync) {
uv_sem_post(&uvSem_);
}
}
void NativeEngine::SetAlive()
{
if (id_ != 0) {
HILOG_FATAL("id of native engine cannot set twice");
}
std::lock_guard<std::mutex> alivedEngLock(g_alivedEngineMutex_);
g_alivedEngine_.emplace(this);
id_ = g_lastEngineId_++;
return;
}
NativeAsyncWork* NativeEngine::CreateAsyncWork(napi_value asyncResource, napi_value asyncResourceName,
NativeAsyncExecuteCallback execute, NativeAsyncCompleteCallback complete, void* data)
{
(void)asyncResource;
(void)asyncResourceName;
char name[NAME_BUFFER_SIZE] = {0};
if (asyncResourceName != nullptr) {
auto val = LocalValueFromJsValue(asyncResourceName);
[[maybe_unused]] size_t strLength = 0;
auto vm = GetEcmaVm();
LocalScope scope(vm);
auto str = val->ToString(vm);
char* buffer = name;
if (buffer == nullptr) {
strLength = static_cast<size_t>(str->Utf8Length(vm, true) - 1);
} else if (NAME_BUFFER_SIZE != 0) {
uint32_t copied = str->WriteUtf8(vm, buffer, NAME_BUFFER_SIZE - 1, true) - 1;
buffer[copied] = '\0';
strLength = copied;
} else {
strLength = 0;
}
}
return new NativeAsyncWork(this, execute, complete, name, data);
}
NativeAsyncWork* NativeEngine::CreateAsyncWork(const std::string& asyncResourceName, NativeAsyncExecuteCallback execute,
NativeAsyncCompleteCallback complete, void* data)
{
return new NativeAsyncWork(this, execute, complete, asyncResourceName, data);
}
NativeSafeAsyncWork* NativeEngine::CreateSafeAsyncWork(napi_value func, napi_value asyncResource,
napi_value asyncResourceName, size_t maxQueueSize, size_t threadCount, void* finalizeData,
NativeFinalize finalizeCallback, void* context, NativeThreadSafeFunctionCallJs callJsCallback)
{
return new NativeSafeAsyncWork(this, func, asyncResource, asyncResourceName, maxQueueSize, threadCount,
finalizeData, finalizeCallback, context, callJsCallback);
}
NativeErrorExtendedInfo* NativeEngine::GetLastError()
{
return &lastError_;
}
void NativeEngine::SetLastError(int errorCode, uint32_t engineErrorCode, void* engineReserved)
{
lastError_.errorCode = errorCode;
lastError_.engineErrorCode = engineErrorCode;
lastError_.message = g_errorMessages[lastError_.errorCode];
lastError_.reserved = engineReserved;
}
static void SubEncodeToUtf8(const EcmaVM* vm,
Local<JSValueRef>& nativeValue,
Local<StringRef>& nativeString,
char* buffer,
uint32_t* written,
size_t bufferSize,
int32_t* nchars)
{
int32_t length = static_cast<int32_t>(nativeString->Length(vm));
uint32_t pos = 0;
uint32_t writableSize = bufferSize;
int32_t i = 0;
panda::Local<ObjectRef> strObj = nativeValue->ToObject(vm);
for (; i < length; i++) {
panda::Local<StringRef> str = strObj->Get(vm, i)->ToString(vm);
uint32_t len = str->Utf8Length(vm) - 1;
if (len > writableSize) {
break;
}
str->WriteUtf8(vm, (buffer + pos), writableSize);
writableSize -= len;
pos += len;
}
*nchars = i;
HILOG_DEBUG("EncodeWriteUtf8 the result of buffer: %{public}s", buffer);
*written = pos;
}
void NativeEngine::EncodeToUtf8(napi_value value, char* buffer, uint32_t* written, size_t bufferSize, int32_t* nchars)
{
auto nativeValue = LocalValueFromJsValue(value);
if (nativeValue->IsNull() || nchars == nullptr || written == nullptr) {
HILOG_ERROR("NativeEngine EncodeToUtf8 args is nullptr");
return;
}
auto vm = GetEcmaVm();
LocalScope scope(vm);
auto nativeString = nativeValue->ToString(vm);
if (!nativeString->IsString(vm)) {
HILOG_ERROR("nativeValue not is string");
return;
}
if (buffer == nullptr) {
HILOG_ERROR("buffer is null");
return;
}
SubEncodeToUtf8(vm, nativeValue, nativeString, buffer, written, bufferSize, nchars);
}
static void SubEncodeToChinese(const EcmaVM* vm,
Local<JSValueRef>& nativeValue,
Local<StringRef>& nativeString,
std::string& buffer,
const char* encode)
{
uint32_t length = nativeString->Length(vm);
uint32_t pos = 0;
const int32_t writableSize = 22;
std::string tempBuf = "";
tempBuf.resize(writableSize + 1);
UErrorCode errorCode = U_ZERO_ERROR;
const char* encFrom = "utf8";
panda::Local<ObjectRef> strObj = nativeValue->ToObject(vm);
for (uint32_t i = 0; i < length; i++) {
panda::Local<StringRef> str = strObj->Get(vm, i)->ToString(vm);
uint32_t len = str->Utf8Length(vm) - 1;
if ((pos + len) >= writableSize) {
char outBuf[writableSize] = {0};
ucnv_convert(encode, encFrom, outBuf, writableSize, tempBuf.c_str(), pos, &errorCode);
if (errorCode != U_ZERO_ERROR) {
HILOG_ERROR("ucnv_convert is failed : ErrorCode = %{public}d", static_cast<int32_t>(errorCode));
return;
}
buffer += outBuf;
tempBuf.clear();
pos = 0;
}
str->WriteUtf8(vm, (tempBuf.data() + pos), pos + len + 1);
pos += len;
}
if (pos > 0) {
char outBuf[writableSize] = {0};
ucnv_convert(encode, encFrom, outBuf, writableSize, tempBuf.c_str(), pos, &errorCode);
if (errorCode != U_ZERO_ERROR) {
HILOG_ERROR("ucnv_convert is failed : ErrorCode = %{public}d", static_cast<int32_t>(errorCode));
return;
}
buffer += outBuf;
}
}
void NativeEngine::EncodeToChinese(napi_value value, std::string& buffer, const std::string& encoding)
{
if (value == nullptr) {
HILOG_ERROR("nativeValue GetInterface is nullptr");
return;
}
auto nativeValue = LocalValueFromJsValue(value);
auto vm = GetEcmaVm();
LocalScope scope(vm);
auto nativeString = nativeValue->ToString(vm);
if (!nativeString->IsString(vm)) {
HILOG_ERROR("nativeValue not is string");
return;
}
auto encode = encoding.c_str();
if (encode == nullptr) {
HILOG_ERROR("encoding is nullptr");
return;
}
SubEncodeToChinese(vm, nativeValue, nativeString, buffer, encode);
}
#if !defined(PREVIEW)
void NativeEngine::CheckUVLoop()
{
checkUVLoop_ = true;
uv_thread_create(&uvThread_, NativeEngine::UVThreadRunner, this);
}
void NativeEngine::UVThreadRunner(void* nativeEngine)
{
std::string name("UVLoop");
#ifdef IOS_PLATFORM
pthread_setname_np(name.c_str());
#else
pthread_setname_np(pthread_self(), name.c_str());
#endif
auto engine = static_cast<NativeEngine*>(nativeEngine);
engine->PostLoopTask();
while (engine->checkUVLoop_) {
int32_t fd = uv_backend_fd(engine->loop_);
int32_t timeout = uv_backend_timeout(engine->loop_);
int32_t result = -1;
#ifdef IOS_PLATFORM
struct kevent events[1];
struct timespec spec;
static const int32_t mSec = 1000;
static const int32_t uSec = 1000000;
if (timeout != -1) {
spec.tv_sec = timeout / mSec;
spec.tv_nsec = (timeout % mSec) * uSec;
}
result = kevent(fd, NULL, 0, events, 1, timeout == -1 ? NULL : &spec);
#else
struct epoll_event ev;
result = epoll_wait(fd, &ev, 1, timeout);
#endif
if (!engine->checkUVLoop_) {
HILOG_INFO("break thread after epoll wait");
break;
}
if (result >= 0) {
engine->PostLoopTask();
} else {
HILOG_ERROR("epoll wait fail: result: %{public}d, errno: %{public}d", result, errno);
}
if (!engine->checkUVLoop_) {
HILOG_INFO("break thread after post loop task");
break;
}
}
}
void NativeEngine::CancelCheckUVLoop()
{
checkUVLoop_ = false;
uv_async_send(&uvAsync_);
uv_sem_post(&uvSem_);
uv_thread_join(&uvThread_);
}
void NativeEngine::PostLoopTask()
{
postTask_(true);
uv_sem_wait(&uvSem_);
}
#endif
void NativeEngine::SetPostTask(PostTask postTask)
{
postTask_ = postTask;
}
void NativeEngine::TriggerPostTask()
{
if (postTask_ == nullptr) {
HILOG_ERROR("postTask_ is nullptr");
return;
}
postTask_(false);
}
void* NativeEngine::GetJsEngine()
{
return jsEngine_;
}
void NativeEngine::SetInitWorkerFunc(InitWorkerFunc func)
{
initWorkerFunc_ = func;
}
InitWorkerFunc NativeEngine::GetInitWorkerFunc() const
{
return initWorkerFunc_;
}
void NativeEngine::SetGetAssetFunc(GetAssetFunc func)
{
getAssetFunc_ = func;
}
GetAssetFunc NativeEngine::GetGetAssetFunc() const
{
return getAssetFunc_;
}
void NativeEngine::SetOffWorkerFunc(OffWorkerFunc func)
{
offWorkerFunc_ = func;
}
OffWorkerFunc NativeEngine::GetOffWorkerFunc() const
{
return offWorkerFunc_;
}
void NativeEngine::SetReleaseWorkerSafeMemFunc(ReleaseWorkerSafeMemFunc func)
{
if (func == nullptr) {
return;
}
panda::JSNApi::SetReleaseSecureMemCallback(func);
}
bool NativeEngine::CallInitWorkerFunc(NativeEngine* engine)
{
if (initWorkerFunc_ != nullptr) {
initWorkerFunc_(engine);
return true;
}
return false;
}
bool NativeEngine::CallGetAssetFunc(const std::string& uri, uint8_t** buffer, size_t* bufferSize,
std::vector<uint8_t>& content, std::string& ami, bool &useSecureMem, void** mapper, bool isRestrictedWorker)
{
if (getAssetFunc_ != nullptr) {
getAssetFunc_(uri, buffer, bufferSize, content, ami, useSecureMem, mapper, isRestrictedWorker);
return true;
}
return false;
}
bool NativeEngine::CallOffWorkerFunc(NativeEngine* engine)
{
if (offWorkerFunc_ != nullptr) {
offWorkerFunc_(engine);
return true;
}
return false;
}
void NativeEngine::SetGetContainerScopeIdFunc(GetContainerScopeIdCallback func)
{
if (g_getContainerScopeIdFunc != nullptr) {
return;
}
g_getContainerScopeIdFunc = func;
}
void NativeEngine::SetInitContainerScopeFunc(ContainerScopeCallback func)
{
if (g_initContainerScopeFunc != nullptr) {
return;
}
g_initContainerScopeFunc = func;
}
void NativeEngine::SetFinishContainerScopeFunc(ContainerScopeCallback func)
{
if (g_finishContainerScopeFunc != nullptr) {
return;
}
g_finishContainerScopeFunc = func;
}
int32_t NativeEngine::GetContainerScopeIdFunc()
{
int32_t scopeId = -1;
if (g_getContainerScopeIdFunc != nullptr) {
scopeId = g_getContainerScopeIdFunc();
}
return scopeId;
}
bool NativeEngine::InitContainerScopeFunc(int32_t id)
{
if (g_initContainerScopeFunc != nullptr) {
g_initContainerScopeFunc(id);
return true;
}
return false;
}
bool NativeEngine::FinishContainerScopeFunc(int32_t id)
{
if (g_finishContainerScopeFunc != nullptr) {
g_finishContainerScopeFunc(id);
return true;
}
return false;
}
#if !defined(PREVIEW)
void NativeEngine::CallDebuggerPostTaskFunc(std::function<void()>&& task)
{
if (debuggerPostTaskFunc_ != nullptr) {
debuggerPostTaskFunc_(std::move(task));
}
}
void NativeEngine::SetDebuggerPostTaskFunc(DebuggerPostTask func)
{
debuggerPostTaskFunc_ = func;
}
#endif
void NativeEngine::SetHostEngine(NativeEngine* engine)
{
hostEngine_ = engine;
}
NativeEngine* NativeEngine::GetHostEngine() const
{
return hostEngine_;
}
void NativeEngine::SetApiVersion(int32_t apiVersion)
{
apiVersion_ = apiVersion;
realApiVersion_ = apiVersion % API_VERSION_MASK;
EcmaVM* vm = const_cast<EcmaVM*>(GetEcmaVm());
panda::JSNApi::SetVMAPIVersion(vm, apiVersion);
}
int32_t NativeEngine::GetApiVersion()
{
return apiVersion_;
}
int32_t NativeEngine::GetRealApiVersion()
{
return realApiVersion_;
}
bool NativeEngine::IsApplicationApiVersionAPI11Plus()
{
return apiVersion_ > API11;
}
napi_status NativeEngine::AddCleanupFinalizer(CleanupFinalizerCallBack fun, void* arg)
{
auto insertion_info = instanceFinalizer_.emplace(arg,
std::pair<CleanupFinalizerCallBack, uint64_t>(fun, instanceFinalizerCounter_++));
if (insertion_info.second) {
return napi_ok;
}
HILOG_ERROR("AddCleanupFinalizer Failed, this may cause memory leaks or unexpected behavior.");
return napi_generic_failure;
}
napi_status NativeEngine::RemoveCleanupFinalizer(CleanupFinalizerCallBack fun, void* arg)
{
auto cleanupHook = instanceFinalizer_.find(arg);
if (cleanupHook != instanceFinalizer_.end() && cleanupHook->second.first == fun) {
instanceFinalizer_.erase(arg);
return napi_ok;
}
const char *failedReason = cleanupHook == instanceFinalizer_.end() ?
"unregistered data" : "callback not equals to the last";
HILOG_ERROR("Failed, %{public}s, may cause memleak or exception.", failedReason);
return napi_generic_failure;
}
void NativeEngine::RunInstanceFinalizer()
{
HILOG_DEBUG("%{public}s, start.", __func__);
while (!instanceFinalizer_.empty()) {
#ifdef ENABLE_HITRACE
StartTrace(HITRACE_TAG_ACE,
"NativeEngine::RunInstanceFinalizer callbacks size: " +
std::to_string(instanceFinalizer_.size()));
#endif
using CleanupDataCallbackPair = std::pair<void*, std::pair<CleanupFinalizerCallBack, uint64_t>>;
std::vector<CleanupDataCallbackPair> callbacks(instanceFinalizer_.begin(), instanceFinalizer_.end());
std::sort(callbacks.begin(), callbacks.end(), [](const CleanupDataCallbackPair& a,
const CleanupDataCallbackPair& b) {
return a.second.second > b.second.second;
});
HILOG_DEBUG(
"NativeEngine::RunInstanceFinalizer callbacks size:%{public}d", (int32_t)callbacks.size());
for (const CleanupDataCallbackPair& cb : callbacks) {
void* data = cb.first;
if (instanceFinalizer_.find(data) == instanceFinalizer_.end()) {
continue;
}
CleanupFinalizerCallBack fun = cb.second.first;
if (fun != nullptr) {
fun(data);
}
instanceFinalizer_.erase(data);
}
#ifdef ENABLE_HITRACE
FinishTrace(HITRACE_TAG_ACE);
#endif
}
}
napi_status NativeEngine::AddCleanupHook(CleanupCallback fun, void* arg)
{
auto insertion_info = cleanupHooks_.emplace(arg,
std::pair<CleanupCallback, uint64_t>(fun, cleanupHookCounter_++));
if (insertion_info.second) {
return napi_ok;
}
std::string stack;
if (IsCrossThreadCheckEnabled()) {
if (DumpHybridStack(GetEcmaVm(), stack, 1, 8)) {
HILOG_ERROR("AddCleanupHook Failed, data cannot register multiple times."
"\n%{public}s", stack.c_str());
} else {
HILOG_ERROR("AddCleanupHook Failed, data cannot register multiple times, "
"backtrace failed or unsupported platform.");
}
} else {
HILOG_WARN("AddCleanupHook Failed, data cannot register multiple times, "
"enable cross-thread check for more information.");
}
return napi_generic_failure;
}
napi_status NativeEngine::RemoveCleanupHook(CleanupCallback fun, void* arg)
{
auto cleanupHook = cleanupHooks_.find(arg);
if (cleanupHook != cleanupHooks_.end() && cleanupHook->second.first == fun) {
cleanupHooks_.erase(arg);
return napi_ok;
}
const char *failedReason = cleanupHook == cleanupHooks_.end() ? "unregistered data"
: "callback not equals to the last";
std::string stack;
if (IsCrossThreadCheckEnabled()) {
if (DumpHybridStack(GetEcmaVm(), stack, 1, 8)) {
HILOG_ERROR("Failed, %{public}s"
".\n%{public}s", failedReason, stack.c_str());
} else {
HILOG_ERROR("Failed %{public}s, "
"backtrace failed or unsupported platform.", failedReason);
}
} else {
HILOG_WARN("Failed, %{public}s, "
"enable cross thread check for more information.", failedReason);
}
return napi_generic_failure;
}
void NativeEngine::RunCleanupHooks(bool waitTasks)
{
while (!cleanupHooks_.empty()) {
HILOG_DEBUG("NativeEngine::RunCleanup cleanupHooks_ is not empty");
using CleanupCallbackTuple = std::pair<void*, std::pair<CleanupCallback, uint64_t>>;
std::vector<CleanupCallbackTuple> callbacks(cleanupHooks_.begin(), cleanupHooks_.end());
std::sort(callbacks.begin(), callbacks.end(), [](const CleanupCallbackTuple& a, const CleanupCallbackTuple& b) {
return a.second.second > b.second.second;
});
HILOG_DEBUG(
"NativeEngine::RunCleanup cleanup_hooks callbacks size:%{public}d", (int32_t)callbacks.size());
for (const CleanupCallbackTuple& cb : callbacks) {
void* data = cb.first;
if (cleanupHooks_.find(data) == cleanupHooks_.end()) {
continue;
}
CleanupCallback fun = cb.second.first;
if (fun != nullptr) {
fun(data);
}
cleanupHooks_.erase(data);
}
if (waitTasks) {
CleanupHandles();
}
}
}
void NativeEngine::RunCleanup()
{
HILOG_DEBUG("%{public}s, start.", __func__);
CleanupHandles();
RunCleanupHooks(true);
if (loop_ == nullptr) {
return;
}
uv_run(loop_, UV_RUN_NOWAIT);
auto const ensureClosing = [](uv_handle_t *handle, void *arg) {
if (!uv_is_closing(handle)) {
uv_close(handle, nullptr);
}
};
uv_walk(loop_, ensureClosing, nullptr);
while (uv_run(loop_, UV_RUN_DEFAULT) != 0) {};
HILOG_DEBUG("%{public}s, end.", __func__);
}
void NativeEngine::CleanupHandles()
{
if (loop_ == nullptr) {
return;
}
while (requestWaiting_.load() > 0) {
HILOG_INFO("request waiting:%{public}d.",
requestWaiting_.load(std::memory_order_relaxed));
uv_run(loop_, UV_RUN_ONCE);
}
}
#define BODY_COUNTER_METHOD(_type, name, storage, LOAD) \
void NativeEngine::Increase##name##Counter() \
{ \
(storage)++; \
} \
\
void NativeEngine::Decrease##name##Counter() \
{ \
(storage)--; \
} \
\
bool NativeEngine::Has##name() const \
{ \
return LOAD((storage)) != 0; \
}
NAPI_COUNTER_METHOD(BODY_COUNTER_METHOD)
#undef BODY_COUNTER_METHOD
void NativeEngine::IncreaseCriticalScopeCounter()
{
(*criticalScopeCounter_)++;
}
void NativeEngine::DecreaseCriticalScopeCounter()
{
(*criticalScopeCounter_)--;
}
bool NativeEngine::HasCriticalScope() const
{
return (*criticalScopeCounter_) > 0;
}
bool NativeEngine::HasListeningCounter()
{
return HasListening();
}
uint64_t NativeEngine::GetNonCallbackRefCount()
{
return nonCallbackRefCounter_;
}
uint64_t NativeEngine::GetCallbackbleRefCount()
{
return callbackbleRefCounter_;
}
void NativeEngine::RegisterWorkerFunction(const NativeEngine* engine)
{
if (engine == nullptr) {
return;
}
SetInitWorkerFunc(engine->GetInitWorkerFunc());
SetGetAssetFunc(engine->GetGetAssetFunc());
SetOffWorkerFunc(engine->GetOffWorkerFunc());
}
napi_value NativeEngine::RunScriptForAbc(const char* path, char* entryPoint)
{
EcmaVM* vm = const_cast<EcmaVM*>(GetEcmaVm());
panda::EscapeLocalScope scope(vm);
std::string normalizedPath = panda::JSNApi::NormalizePath(path);
uint8_t* scriptContent = nullptr;
size_t scriptContentSize = 0;
std::vector<uint8_t> content;
std::string ami;
void* mapper = nullptr;
if (!GetAbcBuffer(normalizedPath.c_str(), &scriptContent, &scriptContentSize,
content, ami, &mapper, true)) {
HILOG_ERROR("RunScript: GetAbcBuffer failed");
return nullptr;
}
HILOG_DEBUG("RunScriptForAbc: GetAmi: %{private}s", ami.c_str());
if (scriptContentSize == 0) {
HILOG_ERROR("asset size is %{public}zu", scriptContentSize);
ThrowException("RunScriptForAbc: abc file is empty.");
return nullptr;
}
panda::JSNApi::Execute(vm, ami, entryPoint, false, panda::ecmascript::ExecuteTypes::NAPI);
if (panda::JSNApi::HasPendingException(vm)) {
HandleUncaughtException();
return nullptr;
}
Local<JSValueRef> undefObj = JSValueRef::Undefined(vm);
return JsValueFromLocalValue(scope.Escape(undefObj));
}
napi_value NativeEngine::RunScript(const char* path, char* entryPoint)
{
uint8_t* scriptContent = nullptr;
size_t scriptContentSize = 0;
std::vector<uint8_t> content;
std::string ami;
void* mapper = nullptr;
if (!GetAbcBuffer(path, &scriptContent, &scriptContentSize, content, ami, &mapper)) {
HILOG_ERROR("RunScript: GetAbcBuffer failed");
return nullptr;
}
HILOG_DEBUG("RunScript: GetAmi: %{private}s", ami.c_str());
if (scriptContentSize == 0) {
HILOG_ERROR("RunScript: buffer size is empty, please check abc path");
return nullptr;
}
napi_value result = RunActor(scriptContent, scriptContentSize, ami.c_str(), entryPoint, false, mapper);
return result;
}
napi_value NativeEngine::RunScriptInRestrictedThread(const char* path)
{
auto vm = GetEcmaVm();
panda::EscapeLocalScope scope(vm);
uint8_t* scriptContent = nullptr;
size_t scriptContentSize = 0;
std::vector<uint8_t> content;
std::string ami;
void* mapper = nullptr;
if (!GetAbcBuffer(path, &scriptContent, &scriptContentSize, content, ami, &mapper, true)) {
HILOG_ERROR("RunScriptInRestrictedThread: GetAbcBuffer failed");
return nullptr;
}
HILOG_DEBUG("RunScriptInRestrictedThread: GetAmi: %{private}s", ami.c_str());
panda::JSNApi::Execute(vm, ami, PANDA_MAIN_FUNCTION, false, panda::ecmascript::ExecuteTypes::NAPI);
if (panda::JSNApi::HasPendingException(vm)) {
HandleUncaughtException();
return nullptr;
}
Local<JSValueRef> undefObj = JSValueRef::Undefined(vm);
return JsValueFromLocalValue(scope.Escape(undefObj));
}
napi_value NativeEngine::GetAbcBufferAndRunActor(std::string pathStr, char *entryPoint)
{
uint8_t* scriptContent = nullptr;
size_t scriptContentSize = 0;
std::vector<uint8_t> content;
std::string workerAmi;
void* mapper = nullptr;
if (!GetAbcBuffer(pathStr.c_str(), &scriptContent, &scriptContentSize, content, workerAmi, &mapper)) {
HILOG_ERROR("GetAbcBufferAndRunActor: GetAbcBuffer error");
return nullptr;
}
napi_value result = RunActor(scriptContent, scriptContentSize, workerAmi.c_str(), entryPoint, false, mapper);
return result;
}
* If output is not secureMem, fullfill buffer, bufferSize with content data and size.
*/
bool NativeEngine::GetAbcBuffer(const char* path, uint8_t **buffer, size_t* bufferSize,
std::vector<uint8_t>& content, std::string& ami, void** mapper, bool isRestrictedWorker)
{
std::string pathStr(path);
bool useSecureMem = false;
if (!CallGetAssetFunc(pathStr, buffer, bufferSize, content, ami, useSecureMem, mapper, isRestrictedWorker)) {
HILOG_ERROR("Get asset error");
return false;
}
if (!useSecureMem) {
*buffer = content.data();
*bufferSize = content.size();
}
return true;
}
void NativeEngine::SetInstanceData(void* data, NativeFinalize finalize_cb, void* hint)
{
std::lock_guard<std::mutex> insLock(instanceDataLock_);
FinalizerInstanceData();
instanceDataInfo_.engine = this;
instanceDataInfo_.callback = finalize_cb;
instanceDataInfo_.nativeObject = data;
instanceDataInfo_.hint = hint;
}
void NativeEngine::GetInstanceData(void** data)
{
HILOG_DEBUG("NativeEngineWraper::%{public}s, start.", __func__);
std::lock_guard<std::mutex> insLock(instanceDataLock_);
if (data) {
*data = instanceDataInfo_.nativeObject;
}
}
void NativeEngine::FinalizerInstanceData(void)
{
if (instanceDataInfo_.engine != nullptr && instanceDataInfo_.callback != nullptr) {
if (IsCrossThreadCheckEnabled()) {
ExecuteCallback(__FUNCTION__, instanceDataInfo_.callback, instanceDataInfo_.engine,
instanceDataInfo_.nativeObject, instanceDataInfo_.hint);
} else {
instanceDataInfo_.callback(instanceDataInfo_.engine, instanceDataInfo_.nativeObject,
instanceDataInfo_.hint);
}
}
instanceDataInfo_.engine = nullptr;
instanceDataInfo_.callback = nullptr;
instanceDataInfo_.nativeObject = nullptr;
instanceDataInfo_.hint = nullptr;
}
const char* NativeEngine::GetModuleFileName()
{
HILOG_DEBUG("%{public}s, start.", __func__);
if (moduleFileName_.empty()) {
NativeModuleManager* moduleManager = GetModuleManager();
HILOG_DEBUG("NativeEngineWraper::GetFileName GetModuleManager");
if (moduleManager != nullptr) {
std::string moduleFileName = moduleManager->GetModuleFileName(moduleName_.c_str(), isAppModule_);
HILOG_INFO("NativeEngineWraper::GetFileName end filename:%{public}s", moduleFileName.c_str());
SetModuleFileName(moduleFileName);
}
}
return moduleFileName_.c_str();
}
void NativeEngine::SetModuleName(std::string& moduleName)
{
moduleName_ = moduleName;
}
void NativeEngine::SetModuleFileName(std::string& moduleFileName)
{
moduleFileName_ = moduleFileName;
}
void NativeEngine::SetExtensionInfos(std::unordered_map<std::string, int32_t>&& extensionInfos)
{
extensionInfos_ = extensionInfos;
}
const std::unordered_map<std::string, int32_t>& NativeEngine::GetExtensionInfos()
{
return extensionInfos_;
}
void NativeEngine::SetModuleLoadChecker(const std::shared_ptr<ModuleCheckerDelegate>& moduleCheckerDelegate)
{
NativeModuleManager* moduleManager = GetModuleManager();
if (!moduleManager) {
HILOG_ERROR("SetModuleLoadChecker failed, moduleManager is nullptr");
return;
}
moduleManager->SetModuleLoadChecker(moduleCheckerDelegate);
}
napi_status NativeEngine::RunEventLoop(napi_event_mode mode)
{
HILOG_DEBUG("%{public}s, start.", __func__);
if (loop_ == nullptr) {
HILOG_ERROR("nullptr loop in native engine");
return napi_status::napi_invalid_arg;
}
if (!IsNativeThread()) {
HILOG_ERROR("current thread is not native thread");
return napi_status::napi_generic_failure;
}
std::unique_lock<std::mutex> lock(loopRunningMutex_);
if (isLoopRunning_) {
HILOG_DEBUG("loop is already undered running state");
return napi_status::napi_ok;
}
isLoopRunning_ = true;
lock.unlock();
HILOG_DEBUG("uv loop is running with mode %{public}d", mode);
if (mode == napi_event_mode_default) {
uv_run(loop_, UV_RUN_DEFAULT);
}
if (mode == napi_event_mode_nowait) {
uv_run(loop_, UV_RUN_NOWAIT);
}
HILOG_DEBUG("uv loop is stopped");
lock.lock();
isLoopRunning_ = false;
return napi_status::napi_ok;
}
napi_status NativeEngine::StopEventLoop()
{
HILOG_DEBUG("%{public}s, start.", __func__);
if (loop_ == nullptr) {
HILOG_ERROR("nullptr loop in native engine");
return napi_status::napi_invalid_arg;
}
if (!IsNativeThread()) {
HILOG_ERROR("current thread is not native thread");
return napi_status::napi_generic_failure;
}
std::unique_lock<std::mutex> lock(loopRunningMutex_);
if (!isLoopRunning_) {
HILOG_DEBUG("loop is already undered stop state");
return napi_status::napi_ok;
}
HILOG_DEBUG("uv loop is running");
uv_stop(loop_);
HILOG_DEBUG("uv loop is stopped");
return napi_status::napi_ok;
}
void NativeEngine::ThrowException(const char* msg)
{
auto vm = GetEcmaVm();
Local<panda::JSValueRef> error = panda::Exception::Error(vm, StringRef::NewFromUtf8(vm, msg));
panda::JSNApi::ThrowException(vm, error);
}
NapiErrorManager* NapiErrorManager::GetInstance()
{
if (instance_ == NULL) {
std::lock_guard<std::mutex> lock(g_errorManagerInstanceMutex);
if (instance_ == NULL) {
instance_ = new NapiErrorManager();
HILOG_DEBUG("create error manager instance");
}
}
return instance_;
}
void NapiErrorManager::NotifyUncaughtException(napi_env env, napi_value exception, std::string name, uint32_t type)
{
auto hasOnErrorCallback = NapiErrorManager::GetInstance()->GetHasErrorCallback();
if (!hasOnErrorCallback) {
return;
}
if (!hasOnErrorCallback()) {
return;
}
auto callback = NapiErrorManager::GetInstance()->GetOnWorkerErrorCallback();
if (callback) {
callback(env, exception, name, type);
}
}
bool NapiErrorManager::NotifyUncaughtException(napi_env env, const std::string &summary, const std::string &name,
const std::string &message, const std::string &stack)
{
auto hasOnErrorCallback = NapiErrorManager::GetInstance()->GetHasErrorCallback();
if (!hasOnErrorCallback) {
return false;
}
if (!hasOnErrorCallback()) {
return false;
}
panda::JSNApi::GetAndClearUncaughtException(reinterpret_cast<NativeEngine*>(env)->GetEcmaVm());
auto callback = NapiErrorManager::GetInstance()->GetOnMainThreadErrorCallback();
if (!callback) {
return false;
}
return callback(env, summary, name, message, stack);
}
void NapiErrorManager::NotifyUnhandledRejection(napi_env env, napi_value* args, std::string name, uint32_t type)
{
auto hasAllUnhandledRejectionCallback = NapiErrorManager::GetInstance()->GetHasAllUnhandledRejectionCallback();
if (!hasAllUnhandledRejectionCallback) {
return;
}
if (!hasAllUnhandledRejectionCallback()) {
return;
}
auto callback = NapiErrorManager::GetInstance()->GetAllUnhandledRejectionCallback();
if (callback) {
callback(env, args, name, type);
}
}