• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, &parameters, 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