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 <list>
20 #include <mutex>
21 #include <vector>
22
23 #include "jsvm_dfx.h"
24 #include "jsvm_inspector_agent.h"
25 #include "jsvm_reference.h"
26 #include "jsvm_types.h"
27 #include "jsvm_util.h"
28 #include "memory_manager.h"
29 #include "type_conversion.h"
30 #include "v8.h"
31
32 inline JSVM_Status ClearLastError(JSVM_Env env);
33
34 struct JSVM_Env__ final {
35 public:
JSVM_Env__final36 explicit JSVM_Env__(v8::Local<v8::Context> context, int32_t apiVersion)
37 : isolate(context->GetIsolate()), contextPersistent(isolate, context), apiVersion(apiVersion)
38 {
39 ClearLastError(this);
40 }
41
42 // Constructor for creating partial env.
43 explicit JSVM_Env__(v8::Isolate* isolate, int32_t apiVersion);
44
GetVersionfinal45 int32_t GetVersion()
46 {
47 return apiVersion;
48 }
49
50 using Callback = std::function<void(JSVM_Env)>;
51
RequestInterruptfinal52 inline void RequestInterrupt(Callback cb)
53 {
54 {
55 const std::lock_guard<std::mutex> lock(messageQueueMutex);
56 messageQueue.emplace_back(std::move(cb));
57 }
58 isolate->RequestInterrupt(
59 [](v8::Isolate* isolate, void* data) { static_cast<JSVM_Env__*>(data)->RunAndClearInterrupts(); }, this);
60 }
61
62 void RunAndClearInterrupts();
63
GetInspectorAgentfinal64 jsvm::InspectorAgent* GetInspectorAgent()
65 {
66 return inspectorAgent;
67 }
68
69 v8::Platform* platform();
70
contextfinal71 inline v8::Local<v8::Context> context() const
72 {
73 return v8impl::PersistentToLocal::Strong(contextPersistent);
74 }
75
CanCallIntoJSfinal76 bool CanCallIntoJS() const
77 {
78 return true;
79 }
80
HandleThrowfinal81 static inline void HandleThrow(JSVM_Env env, v8::Local<v8::Value> value)
82 {
83 if (env->IsTerminatedOrTerminating()) {
84 return;
85 }
86 env->isolate->ThrowException(value);
87 }
88
89 // i.e. whether v8 exited or is about to exit
IsTerminatedOrTerminatingfinal90 inline bool IsTerminatedOrTerminating()
91 {
92 return this->isolate->IsExecutionTerminating() || !CanCallIntoJS();
93 }
94
95 // v8 uses a special exception to indicate termination, the
96 // `handle_exception` callback should identify such case using
97 // IsTerminatedOrTerminating() before actually handle the exception
98 template<typename T, typename U = decltype(HandleThrow)>
99 inline void CallIntoModule(T&& call, U&& handle_exception = HandleThrow)
100 {
101 int openHandleScopesBefore = openHandleScopes;
102 int openCallbackScopesBefore = openCallbackScopes;
103 ClearLastError(this);
104 call(this);
105 CHECK_EQ(openHandleScopes, openHandleScopesBefore);
106 CHECK_EQ(openCallbackScopes, openCallbackScopesBefore);
107 if (!lastException.IsEmpty()) {
108 handle_exception(this, lastException.Get(this->isolate));
109 lastException.Reset();
110 }
111 }
112
113 // Call finalizer immediately.
CallFinalizerfinal114 void CallFinalizer(JSVM_Finalize cb, void* data, void* hint)
115 {
116 v8::HandleScope handleScope(isolate);
117 CallIntoModule([&](JSVM_Env env) { cb(env, data, hint); });
118 }
119
120 void DeleteMe();
121
CheckGCAccessfinal122 void CheckGCAccess()
123 {
124 if (inGcFinalizer) {
125 jsvm::OnFatalError(nullptr, "Finalizer is calling a function that may affect GC state.\n"
126 "The finalizers are run directly from GC and must not affect GC "
127 "state.\n"
128 "Use `node_api_post_finalizer` from inside of the finalizer to work "
129 "around this issue.\n"
130 "It schedules the call as a new task in the event loop.");
131 }
132 }
133
134 template<typename T>
135 JSVM_Script_Data__* NewJsvmData(T srcPtr, JSVM_Script_Data__::DataType type = JSVM_Script_Data__::kJsvmScript)
136 {
137 if (dataStack.size() == 0 || openHandleScopes != dataStack.back().first) {
138 dataStack.emplace_back(openHandleScopes, std::list<JSVM_Script_Data__*>());
139 }
140 auto newData = new JSVM_Script_Data__(srcPtr, false, type);
141 dataStack.back().second.push_back(newData);
142 return newData;
143 }
144
ReleaseJsvmDatafinal145 void ReleaseJsvmData()
146 {
147 if (dataStack.size() == 0 || openHandleScopes != dataStack.back().first) {
148 return;
149 }
150 for (auto data : dataStack.back().second) {
151 if (!data->isGlobal) {
152 delete data;
153 }
154 }
155 dataStack.pop_back();
156 }
157
RetainJsvmDatafinal158 void RetainJsvmData(JSVM_Script_Data__* data)
159 {
160 for (auto iter = dataStack.rbegin(); iter != dataStack.rend(); ++iter) {
161 auto& current = iter->second;
162 auto globalData = std::find(current.begin(), current.end(), data);
163 if (globalData != current.end()) {
164 current.erase(globalData);
165 break;
166 }
167 }
168 }
169
CreateScopeTrackerfinal170 void CreateScopeTracker()
171 {
172 scopeTracker = new jsvm::ScopeLifecycleTracker();
173 }
174
GetScopeTrackerfinal175 jsvm::ScopeLifecycleTracker* GetScopeTracker()
176 {
177 if (scopeTracker == nullptr) {
178 CreateScopeTracker();
179 }
180 return scopeTracker;
181 }
182
183 // Shortcut for context()->GetIsolate()
184 v8::Isolate* const isolate;
185 v8impl::Persistent<v8::Context> contextPersistent;
186
187 // Error info and execption
188 v8impl::Persistent<v8::Value> lastException;
189
190 // We store references in two different lists, depending on whether they have
191 // `JSVM_Finalizer` callbacks, because we must first finalize the ones that
192 // have such a callback. See `~JSVM_Env__()` above for details.
193 v8impl::RefList userReferenceList;
194 v8impl::RefList finalizerList;
195
196 JSVM_ExtendedErrorInfo lastError;
197
198 // Store v8::Data
199 std::vector<std::pair<int, std::list<JSVM_Script_Data__*>>> dataStack;
200
201 // Store external instance data
202 void* instanceData = nullptr;
203
204 // Store v8::Locker
205 v8::Locker* locker = nullptr;
206
207 using ScopeMemoryManager = MemoryChunkList<
208 jsvm::MaxSize<v8impl::EscapableHandleScopeWrapper, v8impl::HandleScopeWrapper, v8::Context::Scope>()>;
209 ScopeMemoryManager scopeMemoryManager;
210
211 int32_t apiVersion;
212
213 int openHandleScopes = 0;
214 int openCallbackScopes = 0;
215 bool inGcFinalizer = false;
216 uint32_t debugFlags = 0;
217
218 private:
219 // Used for inspector
220 jsvm::InspectorAgent* inspectorAgent;
221 std::mutex messageQueueMutex;
222 std::vector<Callback> messageQueue;
223 // Used for scopeInfo
224 jsvm::ScopeLifecycleTracker* scopeTracker = nullptr;
225
226 protected:
227 // Should not be deleted directly. Delete with `JSVM_Env__::DeleteMe()`
228 // instead.
229 ~JSVM_Env__() = default;
230 };
231
ClearLastError(JSVM_Env env)232 inline JSVM_Status ClearLastError(JSVM_Env env)
233 {
234 env->lastError.errorCode = JSVM_OK;
235 env->lastError.engineErrorCode = 0;
236 env->lastError.engineReserved = nullptr;
237 env->lastError.errorMessage = nullptr;
238 return JSVM_OK;
239 }
240
241 #endif