1 #include "node_native_module.h"
2 #include "util-inl.h"
3
4 namespace node {
5 namespace native_module {
6
7 using v8::Context;
8 using v8::EscapableHandleScope;
9 using v8::Function;
10 using v8::Integer;
11 using v8::Isolate;
12 using v8::Local;
13 using v8::MaybeLocal;
14 using v8::Object;
15 using v8::ScriptCompiler;
16 using v8::ScriptOrigin;
17 using v8::String;
18
19 NativeModuleLoader NativeModuleLoader::instance_;
20
NativeModuleLoader()21 NativeModuleLoader::NativeModuleLoader() : config_(GetConfig()) {
22 LoadJavaScriptSource();
23 }
24
GetInstance()25 NativeModuleLoader* NativeModuleLoader::GetInstance() {
26 return &instance_;
27 }
28
Exists(const char * id)29 bool NativeModuleLoader::Exists(const char* id) {
30 return source_.find(id) != source_.end();
31 }
32
Add(const char * id,const UnionBytes & source)33 bool NativeModuleLoader::Add(const char* id, const UnionBytes& source) {
34 if (Exists(id)) {
35 return false;
36 }
37 source_.emplace(id, source);
38 return true;
39 }
40
GetSourceObject(Local<Context> context)41 Local<Object> NativeModuleLoader::GetSourceObject(Local<Context> context) {
42 Isolate* isolate = context->GetIsolate();
43 Local<Object> out = Object::New(isolate);
44 for (auto const& x : source_) {
45 Local<String> key = OneByteString(isolate, x.first.c_str(), x.first.size());
46 out->Set(context, key, x.second.ToStringChecked(isolate)).FromJust();
47 }
48 return out;
49 }
50
GetConfigString(Isolate * isolate)51 Local<String> NativeModuleLoader::GetConfigString(Isolate* isolate) {
52 return config_.ToStringChecked(isolate);
53 }
54
GetModuleIds()55 std::vector<std::string> NativeModuleLoader::GetModuleIds() {
56 std::vector<std::string> ids;
57 ids.reserve(source_.size());
58 for (auto const& x : source_) {
59 ids.emplace_back(x.first);
60 }
61 return ids;
62 }
63
InitializeModuleCategories()64 void NativeModuleLoader::InitializeModuleCategories() {
65 if (module_categories_.is_initialized) {
66 DCHECK(!module_categories_.can_be_required.empty());
67 return;
68 }
69
70 std::vector<std::string> prefixes = {
71 #if !HAVE_OPENSSL
72 "internal/crypto/",
73 #endif // !HAVE_OPENSSL
74
75 "internal/bootstrap/",
76 "internal/per_context/",
77 "internal/deps/",
78 "internal/main/"
79 };
80
81 module_categories_.can_be_required.emplace(
82 "internal/deps/cjs-module-lexer/lexer");
83
84 module_categories_.cannot_be_required = std::set<std::string> {
85 #if !HAVE_INSPECTOR
86 "inspector",
87 "internal/util/inspector",
88 #endif // !HAVE_INSPECTOR
89
90 #if !NODE_USE_V8_PLATFORM || !defined(NODE_HAVE_I18N_SUPPORT)
91 "trace_events",
92 #endif // !NODE_USE_V8_PLATFORM
93
94 #if !HAVE_OPENSSL
95 "crypto",
96 "https",
97 "http2",
98 "tls",
99 "_tls_common",
100 "_tls_wrap",
101 "internal/http2/core",
102 "internal/http2/compat",
103 "internal/policy/manifest",
104 "internal/process/policy",
105 "internal/streams/lazy_transform",
106 #endif // !HAVE_OPENSSL
107
108 "sys", // Deprecated.
109 "wasi", // Experimental.
110 "internal/test/binding",
111 "internal/v8_prof_polyfill",
112 "internal/v8_prof_processor",
113 };
114
115 for (auto const& x : source_) {
116 const std::string& id = x.first;
117 for (auto const& prefix : prefixes) {
118 if (prefix.length() > id.length()) {
119 continue;
120 }
121 if (id.find(prefix) == 0 &&
122 module_categories_.can_be_required.count(id) == 0) {
123 module_categories_.cannot_be_required.emplace(id);
124 }
125 }
126 }
127
128 for (auto const& x : source_) {
129 const std::string& id = x.first;
130 if (0 == module_categories_.cannot_be_required.count(id)) {
131 module_categories_.can_be_required.emplace(id);
132 }
133 }
134
135 module_categories_.is_initialized = true;
136 }
137
GetCannotBeRequired()138 const std::set<std::string>& NativeModuleLoader::GetCannotBeRequired() {
139 InitializeModuleCategories();
140 return module_categories_.cannot_be_required;
141 }
142
GetCanBeRequired()143 const std::set<std::string>& NativeModuleLoader::GetCanBeRequired() {
144 InitializeModuleCategories();
145 return module_categories_.can_be_required;
146 }
147
CanBeRequired(const char * id)148 bool NativeModuleLoader::CanBeRequired(const char* id) {
149 return GetCanBeRequired().count(id) == 1;
150 }
151
CannotBeRequired(const char * id)152 bool NativeModuleLoader::CannotBeRequired(const char* id) {
153 return GetCannotBeRequired().count(id) == 1;
154 }
155
code_cache()156 NativeModuleCacheMap* NativeModuleLoader::code_cache() {
157 return &code_cache_;
158 }
159
GetCodeCache(const char * id) const160 ScriptCompiler::CachedData* NativeModuleLoader::GetCodeCache(
161 const char* id) const {
162 Mutex::ScopedLock lock(code_cache_mutex_);
163 const auto it = code_cache_.find(id);
164 if (it == code_cache_.end()) {
165 // The module has not been compiled before.
166 return nullptr;
167 }
168 return it->second.get();
169 }
170
CompileAsModule(Local<Context> context,const char * id,NativeModuleLoader::Result * result)171 MaybeLocal<Function> NativeModuleLoader::CompileAsModule(
172 Local<Context> context,
173 const char* id,
174 NativeModuleLoader::Result* result) {
175 Isolate* isolate = context->GetIsolate();
176 std::vector<Local<String>> parameters = {
177 FIXED_ONE_BYTE_STRING(isolate, "exports"),
178 FIXED_ONE_BYTE_STRING(isolate, "require"),
179 FIXED_ONE_BYTE_STRING(isolate, "module"),
180 FIXED_ONE_BYTE_STRING(isolate, "process"),
181 FIXED_ONE_BYTE_STRING(isolate, "internalBinding"),
182 FIXED_ONE_BYTE_STRING(isolate, "primordials")};
183 return LookupAndCompile(context, id, ¶meters, result);
184 }
185
186 #ifdef NODE_BUILTIN_MODULES_PATH
OnDiskFileName(const char * id)187 static std::string OnDiskFileName(const char* id) {
188 std::string filename = NODE_BUILTIN_MODULES_PATH;
189 filename += "/";
190
191 if (strncmp(id, "internal/deps", strlen("internal/deps")) == 0) {
192 id += strlen("internal/");
193 } else {
194 filename += "lib/";
195 }
196 filename += id;
197 filename += ".js";
198
199 return filename;
200 }
201 #endif // NODE_BUILTIN_MODULES_PATH
202
LoadBuiltinModuleSource(Isolate * isolate,const char * id)203 MaybeLocal<String> NativeModuleLoader::LoadBuiltinModuleSource(Isolate* isolate,
204 const char* id) {
205 #ifdef NODE_BUILTIN_MODULES_PATH
206 std::string filename = OnDiskFileName(id);
207
208 uv_fs_t req;
209 uv_file file =
210 uv_fs_open(nullptr, &req, filename.c_str(), O_RDONLY, 0, nullptr);
211 CHECK_GE(req.result, 0);
212 uv_fs_req_cleanup(&req);
213
214 auto defer_close = OnScopeLeave([file]() {
215 uv_fs_t close_req;
216 CHECK_EQ(0, uv_fs_close(nullptr, &close_req, file, nullptr));
217 uv_fs_req_cleanup(&close_req);
218 });
219
220 std::string contents;
221 char buffer[4096];
222 uv_buf_t buf = uv_buf_init(buffer, sizeof(buffer));
223
224 while (true) {
225 const int r =
226 uv_fs_read(nullptr, &req, file, &buf, 1, contents.length(), nullptr);
227 CHECK_GE(req.result, 0);
228 uv_fs_req_cleanup(&req);
229 if (r <= 0) {
230 break;
231 }
232 contents.append(buf.base, r);
233 }
234
235 return String::NewFromUtf8(
236 isolate, contents.c_str(), v8::NewStringType::kNormal, contents.length());
237 #else
238 const auto source_it = source_.find(id);
239 CHECK_NE(source_it, source_.end());
240 return source_it->second.ToStringChecked(isolate);
241 #endif // NODE_BUILTIN_MODULES_PATH
242 }
243
244 // Returns Local<Function> of the compiled module if return_code_cache
245 // is false (we are only compiling the function).
246 // Otherwise return a Local<Object> containing the cache.
LookupAndCompile(Local<Context> context,const char * id,std::vector<Local<String>> * parameters,NativeModuleLoader::Result * result)247 MaybeLocal<Function> NativeModuleLoader::LookupAndCompile(
248 Local<Context> context,
249 const char* id,
250 std::vector<Local<String>>* parameters,
251 NativeModuleLoader::Result* result) {
252 Isolate* isolate = context->GetIsolate();
253 EscapableHandleScope scope(isolate);
254
255 Local<String> source;
256 if (!LoadBuiltinModuleSource(isolate, id).ToLocal(&source)) {
257 return {};
258 }
259
260 std::string filename_s = id + std::string(".js");
261 Local<String> filename =
262 OneByteString(isolate, filename_s.c_str(), filename_s.size());
263 Local<Integer> line_offset = Integer::New(isolate, 0);
264 Local<Integer> column_offset = Integer::New(isolate, 0);
265 ScriptOrigin origin(filename, line_offset, column_offset, True(isolate));
266
267 ScriptCompiler::CachedData* cached_data = nullptr;
268 {
269 // Note: The lock here should not extend into the
270 // `CompileFunctionInContext()` call below, because this function may
271 // recurse if there is a syntax error during bootstrap (because the fatal
272 // exception handler is invoked, which may load built-in modules).
273 Mutex::ScopedLock lock(code_cache_mutex_);
274 auto cache_it = code_cache_.find(id);
275 if (cache_it != code_cache_.end()) {
276 // Transfer ownership to ScriptCompiler::Source later.
277 cached_data = cache_it->second.release();
278 code_cache_.erase(cache_it);
279 }
280 }
281
282 const bool has_cache = cached_data != nullptr;
283 ScriptCompiler::CompileOptions options =
284 has_cache ? ScriptCompiler::kConsumeCodeCache
285 : ScriptCompiler::kEagerCompile;
286 ScriptCompiler::Source script_source(source, origin, cached_data);
287
288 MaybeLocal<Function> maybe_fun =
289 ScriptCompiler::CompileFunctionInContext(context,
290 &script_source,
291 parameters->size(),
292 parameters->data(),
293 0,
294 nullptr,
295 options);
296
297 // This could fail when there are early errors in the native modules,
298 // e.g. the syntax errors
299 Local<Function> fun;
300 if (!maybe_fun.ToLocal(&fun)) {
301 // In the case of early errors, v8 is already capable of
302 // decorating the stack for us - note that we use CompileFunctionInContext
303 // so there is no need to worry about wrappers.
304 return MaybeLocal<Function>();
305 }
306
307 // XXX(joyeecheung): this bookkeeping is not exactly accurate because
308 // it only starts after the Environment is created, so the per_context.js
309 // will never be in any of these two sets, but the two sets are only for
310 // testing anyway.
311
312 *result = (has_cache && !script_source.GetCachedData()->rejected)
313 ? Result::kWithCache
314 : Result::kWithoutCache;
315 // Generate new cache for next compilation
316 std::unique_ptr<ScriptCompiler::CachedData> new_cached_data(
317 ScriptCompiler::CreateCodeCacheForFunction(fun));
318 CHECK_NOT_NULL(new_cached_data);
319
320 {
321 Mutex::ScopedLock lock(code_cache_mutex_);
322 // The old entry should've been erased by now so we can just emplace.
323 // If another thread did the same thing in the meantime, that should not
324 // be an issue.
325 code_cache_.emplace(id, std::move(new_cached_data));
326 }
327
328 return scope.Escape(fun);
329 }
330
331 } // namespace native_module
332 } // namespace node
333