#include "node_native_module.h" #include "util-inl.h" namespace node { namespace native_module { using v8::Context; using v8::EscapableHandleScope; using v8::Function; using v8::Integer; using v8::Isolate; using v8::Local; using v8::MaybeLocal; using v8::Object; using v8::ScriptCompiler; using v8::ScriptOrigin; using v8::String; NativeModuleLoader NativeModuleLoader::instance_; NativeModuleLoader::NativeModuleLoader() : config_(GetConfig()) { LoadJavaScriptSource(); } NativeModuleLoader* NativeModuleLoader::GetInstance() { return &instance_; } bool NativeModuleLoader::Exists(const char* id) { return source_.find(id) != source_.end(); } bool NativeModuleLoader::Add(const char* id, const UnionBytes& source) { if (Exists(id)) { return false; } source_.emplace(id, source); return true; } Local NativeModuleLoader::GetSourceObject(Local context) { Isolate* isolate = context->GetIsolate(); Local out = Object::New(isolate); for (auto const& x : source_) { Local key = OneByteString(isolate, x.first.c_str(), x.first.size()); out->Set(context, key, x.second.ToStringChecked(isolate)).FromJust(); } return out; } Local NativeModuleLoader::GetConfigString(Isolate* isolate) { return config_.ToStringChecked(isolate); } std::vector NativeModuleLoader::GetModuleIds() { std::vector ids; ids.reserve(source_.size()); for (auto const& x : source_) { ids.emplace_back(x.first); } return ids; } void NativeModuleLoader::InitializeModuleCategories() { if (module_categories_.is_initialized) { DCHECK(!module_categories_.can_be_required.empty()); return; } std::vector prefixes = { #if !HAVE_OPENSSL "internal/crypto/", #endif // !HAVE_OPENSSL "internal/bootstrap/", "internal/per_context/", "internal/deps/", "internal/main/" }; module_categories_.can_be_required.emplace( "internal/deps/cjs-module-lexer/lexer"); module_categories_.cannot_be_required = std::set { #if !HAVE_INSPECTOR "inspector", "internal/util/inspector", #endif // !HAVE_INSPECTOR #if !NODE_USE_V8_PLATFORM || !defined(NODE_HAVE_I18N_SUPPORT) "trace_events", #endif // !NODE_USE_V8_PLATFORM #if !HAVE_OPENSSL "crypto", "crypto/promises", "https", "http2", "tls", "_tls_common", "_tls_wrap", "internal/http2/core", "internal/http2/compat", "internal/policy/manifest", "internal/process/policy", "internal/streams/lazy_transform", #endif // !HAVE_OPENSSL #if !NODE_EXPERIMENTAL_QUIC "internal/quic/core", "internal/quic/util", #endif "sys", // Deprecated. "wasi", // Experimental. "internal/test/binding", "internal/v8_prof_polyfill", "internal/v8_prof_processor", }; for (auto const& x : source_) { const std::string& id = x.first; for (auto const& prefix : prefixes) { if (prefix.length() > id.length()) { continue; } if (id.find(prefix) == 0 && module_categories_.can_be_required.count(id) == 0) { module_categories_.cannot_be_required.emplace(id); } } } for (auto const& x : source_) { const std::string& id = x.first; if (0 == module_categories_.cannot_be_required.count(id)) { module_categories_.can_be_required.emplace(id); } } module_categories_.is_initialized = true; } const std::set& NativeModuleLoader::GetCannotBeRequired() { InitializeModuleCategories(); return module_categories_.cannot_be_required; } const std::set& NativeModuleLoader::GetCanBeRequired() { InitializeModuleCategories(); return module_categories_.can_be_required; } bool NativeModuleLoader::CanBeRequired(const char* id) { return GetCanBeRequired().count(id) == 1; } bool NativeModuleLoader::CannotBeRequired(const char* id) { return GetCannotBeRequired().count(id) == 1; } NativeModuleCacheMap* NativeModuleLoader::code_cache() { return &code_cache_; } ScriptCompiler::CachedData* NativeModuleLoader::GetCodeCache( const char* id) const { Mutex::ScopedLock lock(code_cache_mutex_); const auto it = code_cache_.find(id); if (it == code_cache_.end()) { // The module has not been compiled before. return nullptr; } return it->second.get(); } MaybeLocal NativeModuleLoader::CompileAsModule( Local context, const char* id, NativeModuleLoader::Result* result) { Isolate* isolate = context->GetIsolate(); std::vector> parameters = { FIXED_ONE_BYTE_STRING(isolate, "exports"), FIXED_ONE_BYTE_STRING(isolate, "require"), FIXED_ONE_BYTE_STRING(isolate, "module"), FIXED_ONE_BYTE_STRING(isolate, "process"), FIXED_ONE_BYTE_STRING(isolate, "internalBinding"), FIXED_ONE_BYTE_STRING(isolate, "primordials")}; return LookupAndCompile(context, id, ¶meters, result); } #ifdef NODE_BUILTIN_MODULES_PATH static std::string OnDiskFileName(const char* id) { std::string filename = NODE_BUILTIN_MODULES_PATH; filename += "/"; if (strncmp(id, "internal/deps", strlen("internal/deps")) == 0) { id += strlen("internal/"); } else { filename += "lib/"; } filename += id; filename += ".js"; return filename; } #endif // NODE_BUILTIN_MODULES_PATH MaybeLocal NativeModuleLoader::LoadBuiltinModuleSource(Isolate* isolate, const char* id) { #ifdef NODE_BUILTIN_MODULES_PATH std::string filename = OnDiskFileName(id); uv_fs_t req; uv_file file = uv_fs_open(nullptr, &req, filename.c_str(), O_RDONLY, 0, nullptr); CHECK_GE(req.result, 0); uv_fs_req_cleanup(&req); auto defer_close = OnScopeLeave([file]() { uv_fs_t close_req; CHECK_EQ(0, uv_fs_close(nullptr, &close_req, file, nullptr)); uv_fs_req_cleanup(&close_req); }); std::string contents; char buffer[4096]; uv_buf_t buf = uv_buf_init(buffer, sizeof(buffer)); while (true) { const int r = uv_fs_read(nullptr, &req, file, &buf, 1, contents.length(), nullptr); CHECK_GE(req.result, 0); uv_fs_req_cleanup(&req); if (r <= 0) { break; } contents.append(buf.base, r); } return String::NewFromUtf8( isolate, contents.c_str(), v8::NewStringType::kNormal, contents.length()); #else const auto source_it = source_.find(id); CHECK_NE(source_it, source_.end()); return source_it->second.ToStringChecked(isolate); #endif // NODE_BUILTIN_MODULES_PATH } // Returns Local of the compiled module if return_code_cache // is false (we are only compiling the function). // Otherwise return a Local containing the cache. MaybeLocal NativeModuleLoader::LookupAndCompile( Local context, const char* id, std::vector>* parameters, NativeModuleLoader::Result* result) { Isolate* isolate = context->GetIsolate(); EscapableHandleScope scope(isolate); Local source; if (!LoadBuiltinModuleSource(isolate, id).ToLocal(&source)) { return {}; } std::string filename_s = std::string("node:") + id; Local filename = OneByteString(isolate, filename_s.c_str(), filename_s.size()); Local line_offset = Integer::New(isolate, 0); Local column_offset = Integer::New(isolate, 0); ScriptOrigin origin(filename, line_offset, column_offset, True(isolate)); ScriptCompiler::CachedData* cached_data = nullptr; { // Note: The lock here should not extend into the // `CompileFunctionInContext()` call below, because this function may // recurse if there is a syntax error during bootstrap (because the fatal // exception handler is invoked, which may load built-in modules). Mutex::ScopedLock lock(code_cache_mutex_); auto cache_it = code_cache_.find(id); if (cache_it != code_cache_.end()) { // Transfer ownership to ScriptCompiler::Source later. cached_data = cache_it->second.release(); code_cache_.erase(cache_it); } } const bool has_cache = cached_data != nullptr; ScriptCompiler::CompileOptions options = has_cache ? ScriptCompiler::kConsumeCodeCache : ScriptCompiler::kEagerCompile; ScriptCompiler::Source script_source(source, origin, cached_data); MaybeLocal maybe_fun = ScriptCompiler::CompileFunctionInContext(context, &script_source, parameters->size(), parameters->data(), 0, nullptr, options); // This could fail when there are early errors in the native modules, // e.g. the syntax errors Local fun; if (!maybe_fun.ToLocal(&fun)) { // In the case of early errors, v8 is already capable of // decorating the stack for us - note that we use CompileFunctionInContext // so there is no need to worry about wrappers. return MaybeLocal(); } // XXX(joyeecheung): this bookkeeping is not exactly accurate because // it only starts after the Environment is created, so the per_context.js // will never be in any of these two sets, but the two sets are only for // testing anyway. *result = (has_cache && !script_source.GetCachedData()->rejected) ? Result::kWithCache : Result::kWithoutCache; // Generate new cache for next compilation std::unique_ptr new_cached_data( ScriptCompiler::CreateCodeCacheForFunction(fun)); CHECK_NOT_NULL(new_cached_data); { Mutex::ScopedLock lock(code_cache_mutex_); // The old entry should've been erased by now so we can just emplace. // If another thread did the same thing in the meantime, that should not // be an issue. code_cache_.emplace(id, std::move(new_cached_data)); } return scope.Escape(fun); } } // namespace native_module } // namespace node