1 /*
2 * Copyright (c) 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 #ifndef JSVM_ENV_H
17 #define JSVM_ENV_H
18 #include <functional>
19 #include <mutex>
20 #include <vector>
21
22 #include "jsvm_dfx.h"
23 #include "jsvm_inspector_agent.h"
24 #include "jsvm_reference.h"
25 #include "jsvm_types.h"
26 #include "jsvm_util.h"
27 #include "type_conversion.h"
28 #include "v8.h"
29
30 inline JSVM_Status ClearLastError(JSVM_Env env);
31
32 struct JSVM_Env__ final {
33 public:
JSVM_Env__final34 explicit JSVM_Env__(v8::Local<v8::Context> context, int32_t apiVersion)
35 : isolate(context->GetIsolate()), contextPersistent(isolate, context), apiVersion(apiVersion)
36 {
37 ClearLastError(this);
38 }
39
40 // Constructor for creating partial env.
41 explicit JSVM_Env__(v8::Isolate* isolate, int32_t apiVersion);
42
GetVersionfinal43 int32_t GetVersion()
44 {
45 return apiVersion;
46 }
47
48 using Callback = std::function<void(JSVM_Env)>;
49
RequestInterruptfinal50 inline void RequestInterrupt(Callback cb)
51 {
52 {
53 const std::lock_guard<std::mutex> lock(messageQueueMutex);
54 messageQueue.emplace_back(std::move(cb));
55 }
56 isolate->RequestInterrupt(
57 [](v8::Isolate* isolate, void* data) { static_cast<JSVM_Env__*>(data)->RunAndClearInterrupts(); }, this);
58 }
59
60 void RunAndClearInterrupts();
61
GetInspectorAgentfinal62 jsvm::InspectorAgent* GetInspectorAgent()
63 {
64 return inspectorAgent;
65 }
66
67 v8::Platform* platform();
68
contextfinal69 inline v8::Local<v8::Context> context() const
70 {
71 return v8impl::PersistentToLocal::Strong(contextPersistent);
72 }
73
CanCallIntoJSfinal74 bool CanCallIntoJS() const
75 {
76 return true;
77 }
78
HandleThrowfinal79 static inline void HandleThrow(JSVM_Env env, v8::Local<v8::Value> value)
80 {
81 if (env->IsTerminatedOrTerminating()) {
82 return;
83 }
84 env->isolate->ThrowException(value);
85 }
86
87 // i.e. whether v8 exited or is about to exit
IsTerminatedOrTerminatingfinal88 inline bool IsTerminatedOrTerminating()
89 {
90 return this->isolate->IsExecutionTerminating() || !CanCallIntoJS();
91 }
92
93 // v8 uses a special exception to indicate termination, the
94 // `handle_exception` callback should identify such case using
95 // IsTerminatedOrTerminating() before actually handle the exception
96 template<typename T, typename U = decltype(HandleThrow)>
97 inline void CallIntoModule(T&& call, U&& handle_exception = HandleThrow)
98 {
99 int openHandleScopesBefore = openHandleScopes;
100 int openCallbackScopesBefore = openCallbackScopes;
101 ClearLastError(this);
102 call(this);
103 CHECK_EQ(openHandleScopes, openHandleScopesBefore);
104 CHECK_EQ(openCallbackScopes, openCallbackScopesBefore);
105 if (!lastException.IsEmpty()) {
106 handle_exception(this, lastException.Get(this->isolate));
107 lastException.Reset();
108 }
109 }
110
111 // Call finalizer immediately.
CallFinalizerfinal112 void CallFinalizer(JSVM_Finalize cb, void* data, void* hint)
113 {
114 v8::HandleScope handleScope(isolate);
115 CallIntoModule([&](JSVM_Env env) { cb(env, data, hint); });
116 }
117
118 void DeleteMe();
119
CheckGCAccessfinal120 void CheckGCAccess()
121 {
122 if (inGcFinalizer) {
123 jsvm::OnFatalError(nullptr, "Finalizer is calling a function that may affect GC state.\n"
124 "The finalizers are run directly from GC and must not affect GC "
125 "state.\n"
126 "Use `node_api_post_finalizer` from inside of the finalizer to work "
127 "around this issue.\n"
128 "It schedules the call as a new task in the event loop.");
129 }
130 }
131
132 template<typename T>
133 JSVM_Script_Data__* NewJsvmData(T srcPtr, JSVM_Script_Data__::DataType type = JSVM_Script_Data__::kJsvmScript)
134 {
135 if (dataStack.empty() || openHandleScopes != dataStack.top().first) {
136 dataStack.emplace(openHandleScopes, std::vector<JSVM_Script_Data__*>());
137 }
138 auto newData = new JSVM_Script_Data__(srcPtr, false, type);
139 dataStack.top().second.push_back(newData);
140 return newData;
141 }
142
ReleaseJsvmDatafinal143 void ReleaseJsvmData()
144 {
145 if (dataStack.empty() || openHandleScopes != dataStack.top().first) {
146 return;
147 }
148 for (auto data : dataStack.top().second) {
149 if (!data->isGlobal) {
150 delete data;
151 }
152 }
153 dataStack.pop();
154 }
155
156 // Shortcut for context()->GetIsolate()
157 v8::Isolate* const isolate;
158 v8impl::Persistent<v8::Context> contextPersistent;
159
160 // Error info and execption
161 JSVM_ExtendedErrorInfo lastError;
162 v8impl::Persistent<v8::Value> lastException;
163
164 // We store references in two different lists, depending on whether they have
165 // `JSVM_Finalizer` callbacks, because we must first finalize the ones that
166 // have such a callback. See `~JSVM_Env__()` above for details.
167 v8impl::RefList userReferenceList;
168 v8impl::RefList finalizerList;
169
170 // Store v8::Data
171 std::stack<std::pair<int, std::vector<JSVM_Script_Data__*>>> dataStack;
172
173 // Store external instance data
174 void* instanceData = nullptr;
175
176 // Store v8::Locker
177 v8::Locker* locker = nullptr;
178
179 int32_t apiVersion;
180
181 int openHandleScopes = 0;
182 int openCallbackScopes = 0;
183 bool inGcFinalizer = false;
184
185 private:
186 // Used for inspector
187 jsvm::InspectorAgent* inspectorAgent;
188 std::mutex messageQueueMutex;
189 std::vector<Callback> messageQueue;
190
191 protected:
192 // Should not be deleted directly. Delete with `JSVM_Env__::DeleteMe()`
193 // instead.
194 ~JSVM_Env__() = default;
195 };
196
ClearLastError(JSVM_Env env)197 inline JSVM_Status ClearLastError(JSVM_Env env)
198 {
199 env->lastError.errorCode = JSVM_OK;
200 env->lastError.engineErrorCode = 0;
201 env->lastError.engineReserved = nullptr;
202 env->lastError.errorMessage = nullptr;
203 return JSVM_OK;
204 }
205
206 #endif