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