diff --git a/src/jsvm_env.cpp b/src/jsvm_env.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7226973e3fd3b7d745bcc887cbb9848a97a9d053 --- /dev/null +++ b/src/jsvm_env.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2024 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 "jsvm_env.h" + +#include "libplatform/libplatform.h" + +void JSVM_Env__::RunAndClearInterrupts() +{ + while (messageQueue.size() > 0) { + std::vector messageQueueTmp {}; + { + const std::lock_guard lock(messageQueueMutex); + messageQueueTmp.swap(messageQueue); + } + jsvm::DebugSealHandleScope sealHandleScope(isolate); + + for (auto& cb : messageQueueTmp) { + cb(this); + } + } +} + +JSVM_Env__::JSVM_Env__(v8::Isolate* isolate, int32_t apiVersion) : isolate(isolate), apiVersion(apiVersion) +{ + inspectorAgent = jsvm::InspectorAgent::New(this); + ClearLastError(this); +} + +void JSVM_Env__::DeleteMe() +{ + v8impl::RefTracker::FinalizeAll(&finalizerList); + v8impl::RefTracker::FinalizeAll(&userReferenceList); + + { + v8::Context::Scope context_scope(context()); + if (inspectorAgent->IsActive()) { + inspectorAgent->WaitForDisconnect(); + } + delete inspectorAgent; + inspectorAgent = nullptr; + } + + // release lock + if (locker) { + delete locker; + locker = nullptr; + } + + delete this; +} + +#ifndef ENABLE_INSPECTOR +#include "jsvm_log.h" +namespace { +/* + * If inspector is not enabled, using fake jsvm inspect agent. + * All Interface in fake agent log error. + */ +class FakeAgent final : public jsvm::InspectorAgent { +public: + explicit FakeAgent(JSVM_Env env) + { + LogError(); + } + ~FakeAgent() override = default; + +public: + bool Start(const std::string& path, const std::string& hostName, int port, int pid = -1) override + { + LogError(); + return false; + } + + bool Start(const std::string& path, int pid) override + { + LogError(); + return false; + } + + void Stop() override + { + LogError(); + } + + bool IsActive() override + { + LogError(); + return false; + } + + void WaitForConnect() override + { + LogError(); + } + + void WaitForDisconnect() override + { + LogError(); + } + + void PauseOnNextJavascriptStatement(const std::string& reason) override + { + LogError(); + } + +private: + void LogError() + { + LOG(Error) << "JSVM Inspector is not enabled"; + } +}; + +} // namespace + +jsvm::InspectorAgent* jsvm::InspectorAgent::New(JSVM_Env env) +{ + return new FakeAgent(env); +} +#endif \ No newline at end of file diff --git a/src/jsvm_env.h b/src/jsvm_env.h new file mode 100644 index 0000000000000000000000000000000000000000..05e872484397ab952d83612746a6388cb9b2e3fa --- /dev/null +++ b/src/jsvm_env.h @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2024 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. + */ + +#ifndef JSVM_ENV_H +#define JSVM_ENV_H +#include +#include +#include + +#include "jsvm_dfx.h" +#include "jsvm_inspector_agent.h" +#include "jsvm_reference.h" +#include "jsvm_types.h" +#include "jsvm_util.h" +#include "type_conversion.h" +#include "v8.h" + +inline JSVM_Status ClearLastError(JSVM_Env env); + +struct JSVM_Env__ final { +public: + explicit JSVM_Env__(v8::Local context, int32_t apiVersion) + : isolate(context->GetIsolate()), contextPersistent(isolate, context), apiVersion(apiVersion) + { + ClearLastError(this); + } + + // Constructor for creating partial env. + explicit JSVM_Env__(v8::Isolate* isolate, int32_t apiVersion); + + int32_t GetVersion() + { + return apiVersion; + } + + using Callback = std::function; + + inline void RequestInterrupt(Callback cb) + { + { + const std::lock_guard lock(messageQueueMutex); + messageQueue.emplace_back(std::move(cb)); + } + isolate->RequestInterrupt( + [](v8::Isolate* isolate, void* data) { static_cast(data)->RunAndClearInterrupts(); }, this); + } + + void RunAndClearInterrupts(); + + jsvm::InspectorAgent* GetInspectorAgent() + { + return inspectorAgent; + } + + v8::Platform* platform(); + + inline v8::Local context() const + { + return v8impl::PersistentToLocal::Strong(contextPersistent); + } + + bool CanCallIntoJS() const + { + return true; + } + + static inline void HandleThrow(JSVM_Env env, v8::Local value) + { + if (env->IsTerminatedOrTerminating()) { + return; + } + env->isolate->ThrowException(value); + } + + // i.e. whether v8 exited or is about to exit + inline bool IsTerminatedOrTerminating() + { + return this->isolate->IsExecutionTerminating() || !CanCallIntoJS(); + } + + // v8 uses a special exception to indicate termination, the + // `handle_exception` callback should identify such case using + // IsTerminatedOrTerminating() before actually handle the exception + template + inline void CallIntoModule(T&& call, U&& handle_exception = HandleThrow) + { + int openHandleScopesBefore = openHandleScopes; + int openCallbackScopesBefore = openCallbackScopes; + ClearLastError(this); + call(this); + CHECK_EQ(openHandleScopes, openHandleScopesBefore); + CHECK_EQ(openCallbackScopes, openCallbackScopesBefore); + if (!lastException.IsEmpty()) { + handle_exception(this, lastException.Get(this->isolate)); + lastException.Reset(); + } + } + + // Call finalizer immediately. + void CallFinalizer(JSVM_Finalize cb, void* data, void* hint) + { + v8::HandleScope handleScope(isolate); + CallIntoModule([&](JSVM_Env env) { cb(env, data, hint); }); + } + + void DeleteMe(); + + void CheckGCAccess() + { + if (inGcFinalizer) { + jsvm::OnFatalError(nullptr, "Finalizer is calling a function that may affect GC state.\n" + "The finalizers are run directly from GC and must not affect GC " + "state.\n" + "Use `node_api_post_finalizer` from inside of the finalizer to work " + "around this issue.\n" + "It schedules the call as a new task in the event loop."); + } + } + + template + JSVM_Script_Data__* NewJsvmData(T srcPtr, JSVM_Script_Data__::DataType type = JSVM_Script_Data__::kJsvmScript) + { + if (dataStack.empty() || openHandleScopes != dataStack.top().first) { + dataStack.emplace(openHandleScopes, std::vector()); + } + auto newData = new JSVM_Script_Data__(srcPtr, false, type); + dataStack.top().second.push_back(newData); + return newData; + } + + void ReleaseJsvmData() + { + if (dataStack.empty() || openHandleScopes != dataStack.top().first) { + return; + } + for (auto data : dataStack.top().second) { + if (!data->isGlobal) { + delete data; + } + } + dataStack.pop(); + } + + // Shortcut for context()->GetIsolate() + v8::Isolate* const isolate; + v8impl::Persistent contextPersistent; + + // Error info and execption + JSVM_ExtendedErrorInfo lastError; + v8impl::Persistent lastException; + + // We store references in two different lists, depending on whether they have + // `JSVM_Finalizer` callbacks, because we must first finalize the ones that + // have such a callback. See `~JSVM_Env__()` above for details. + v8impl::RefList userReferenceList; + v8impl::RefList finalizerList; + + // Store v8::Data + std::stack>> dataStack; + + // Store external instance data + void* instanceData = nullptr; + + // Store v8::Locker + v8::Locker* locker = nullptr; + + int32_t apiVersion; + + int openHandleScopes = 0; + int openCallbackScopes = 0; + bool inGcFinalizer = false; + +private: + // Used for inspector + jsvm::InspectorAgent* inspectorAgent; + std::mutex messageQueueMutex; + std::vector messageQueue; + +protected: + // Should not be deleted directly. Delete with `JSVM_Env__::DeleteMe()` + // instead. + ~JSVM_Env__() = default; +}; + +inline JSVM_Status ClearLastError(JSVM_Env env) +{ + env->lastError.errorCode = JSVM_OK; + env->lastError.engineErrorCode = 0; + env->lastError.engineReserved = nullptr; + env->lastError.errorMessage = nullptr; + return JSVM_OK; +} + +#endif \ No newline at end of file diff --git a/src/jsvm_inspector_agent.h b/src/jsvm_inspector_agent.h new file mode 100644 index 0000000000000000000000000000000000000000..8fceb69467d54536acd392228d9ec335bad91f1a --- /dev/null +++ b/src/jsvm_inspector_agent.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 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. + */ + +/* Interface for JSVM inspector */ + +#ifndef JSVM_INSPECTOR_AGENT_H +#define JSVM_INSPECTOR_AGENT_H +#include + +#include "jsvm_types.h" + +namespace jsvm { +class InspectorAgent { +public: + static InspectorAgent* New(JSVM_Env); + virtual ~InspectorAgent() = default; + +public: + virtual bool Start(const std::string& path, const std::string& hostName, int port, int pid = -1) = 0; + + // Find avaliable port and open inspector. + virtual bool Start(const std::string& path, int pid) = 0; + + virtual void Stop() = 0; + + virtual bool IsActive() = 0; + + virtual void WaitForConnect() = 0; + + virtual void WaitForDisconnect() = 0; + + virtual void PauseOnNextJavascriptStatement(const std::string& reason) = 0; +}; +} // namespace jsvm + +#endif \ No newline at end of file