From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 4 Mar 2024 11:41:18 +0900 Subject: src: preload function for Environment Backport https://github.com/nodejs/node/pull/51539 diff --git a/lib/internal/process/pre_execution.js b/lib/internal/process/pre_execution.js index 2fc9e2da8aa920acffc101b2c341a5d9b16fe90a..b4272f01e84d7fec263dcad444d92459743780a8 100644 --- a/lib/internal/process/pre_execution.js +++ b/lib/internal/process/pre_execution.js @@ -196,6 +196,9 @@ function setupUserModules(forceDefaultLoader = false) { } = require('internal/modules/helpers'); assert(!hasStartedUserCJSExecution()); assert(!hasStartedUserESMExecution()); + if (getEmbedderOptions().hasEmbedderPreload) { + runEmbedderPreload(); + } // Do not enable preload modules if custom loaders are disabled. // For example, loader workers are responsible for doing this themselves. // And preload modules are not supported in ShadowRealm as well. @@ -713,6 +716,10 @@ function initializeFrozenIntrinsics() { } } +function runEmbedderPreload() { + internalBinding('mksnapshot').runEmbedderPreload(process, require); +} + function loadPreloadModules() { // For user code, we preload modules if `-r` is passed const preloadModules = getOptionValue('--require'); diff --git a/src/api/environment.cc b/src/api/environment.cc index 7c2e4430299e0d78539d43070942ea3a585b559c..7741b9358a965231671bce004de581f1c6665c2d 100644 --- a/src/api/environment.cc +++ b/src/api/environment.cc @@ -549,25 +549,31 @@ NODE_EXTERN std::unique_ptr GetInspectorParentHandle( #endif } -MaybeLocal LoadEnvironment( - Environment* env, - StartExecutionCallback cb) { +MaybeLocal LoadEnvironment(Environment* env, + StartExecutionCallback cb, + EmbedderPreloadCallback preload) { env->InitializeLibuv(); env->InitializeDiagnostics(); + if (preload) { + env->set_embedder_preload(std::move(preload)); + } return StartExecution(env, cb); } MaybeLocal LoadEnvironment(Environment* env, - std::string_view main_script_source_utf8) { + std::string_view main_script_source_utf8, + EmbedderPreloadCallback preload) { CHECK_NOT_NULL(main_script_source_utf8.data()); return LoadEnvironment( - env, [&](const StartExecutionCallbackInfo& info) -> MaybeLocal { + env, + [&](const StartExecutionCallbackInfo& info) -> MaybeLocal { Local main_script = ToV8Value(env->context(), main_script_source_utf8).ToLocalChecked(); return info.run_cjs->Call( env->context(), Null(env->isolate()), 1, &main_script); - }); + }, + std::move(preload)); } Environment* GetCurrentEnvironment(Local context) { diff --git a/src/env-inl.h b/src/env-inl.h index ac4295f495e240331a183b4a0a22d7437fc85271..63ce35ba68b48a55d8150395304bf86c2bf23aae 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -430,6 +430,14 @@ inline builtins::BuiltinLoader* Environment::builtin_loader() { return &builtin_loader_; } +inline const EmbedderPreloadCallback& Environment::embedder_preload() const { + return embedder_preload_; +} + +inline void Environment::set_embedder_preload(EmbedderPreloadCallback fn) { + embedder_preload_ = std::move(fn); +} + inline double Environment::new_async_id() { async_hooks()->async_id_fields()[AsyncHooks::kAsyncIdCounter] += 1; return async_hooks()->async_id_fields()[AsyncHooks::kAsyncIdCounter]; diff --git a/src/env.h b/src/env.h index f0a1d5ef2b45d4c27c5660177c9805acee19e5f2..910c69b6d1d17ef25201dbb39d3d074f4f3f011f 100644 --- a/src/env.h +++ b/src/env.h @@ -1003,6 +1003,9 @@ class Environment : public MemoryRetainer { #endif // HAVE_INSPECTOR + inline const EmbedderPreloadCallback& embedder_preload() const; + inline void set_embedder_preload(EmbedderPreloadCallback fn); + inline void set_process_exit_handler( std::function&& handler); @@ -1213,6 +1216,7 @@ class Environment : public MemoryRetainer { std::unique_ptr principal_realm_ = nullptr; builtins::BuiltinLoader builtin_loader_; + EmbedderPreloadCallback embedder_preload_; // Used by allocate_managed_buffer() and release_managed_buffer() to keep // track of the BackingStore for a given pointer. diff --git a/src/node.h b/src/node.h index 5ade64874ec08474f05db226cf19a043fa26592e..b3cc7a9f1866111c197b20068275c496fd4aace0 100644 --- a/src/node.h +++ b/src/node.h @@ -746,12 +746,33 @@ struct StartExecutionCallbackInfo { using StartExecutionCallback = std::function(const StartExecutionCallbackInfo&)>; +using EmbedderPreloadCallback = + std::function process, + v8::Local require)>; +// Run initialization for the environment. +// +// The |preload| function, usually used by embedders to inject scripts, +// will be run by Node.js before Node.js executes the entry point. +// The function is guaranteed to run before the user land module loader running +// any user code, so it is safe to assume that at this point, no user code has +// been run yet. +// The function will be executed with preload(process, require), and the passed +// require function has access to internal Node.js modules. There is no +// stability guarantee about the internals exposed to the internal require +// function. Expect breakages when updating Node.js versions if the embedder +// imports internal modules with the internal require function. +// Worker threads created in the environment will also respect The |preload| +// function, so make sure the function is thread-safe. NODE_EXTERN v8::MaybeLocal LoadEnvironment( Environment* env, - StartExecutionCallback cb); + StartExecutionCallback cb, + EmbedderPreloadCallback preload = nullptr); NODE_EXTERN v8::MaybeLocal LoadEnvironment( - Environment* env, std::string_view main_script_source_utf8); + Environment* env, + std::string_view main_script_source_utf8, + EmbedderPreloadCallback preload = nullptr); NODE_EXTERN void FreeEnvironment(Environment* env); // Set a callback that is called when process.exit() is called from JS, diff --git a/src/node_options.cc b/src/node_options.cc index a02dcca456556a7cbb2a262edc5fa4a3b25297ed..92adb8323abac2c7ab956fe1adf95cfab62bef6a 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -1304,6 +1304,12 @@ void GetEmbedderOptions(const FunctionCallbackInfo& args) { .IsNothing()) return; + if (ret->Set(context, + FIXED_ONE_BYTE_STRING(env->isolate(), "hasEmbedderPreload"), + Boolean::New(isolate, env->embedder_preload() != nullptr)) + .IsNothing()) + return; + args.GetReturnValue().Set(ret); } diff --git a/src/node_snapshotable.cc b/src/node_snapshotable.cc index 0309aba6bdc24afe0d1fd24e7af6f472910363d7..55a65deeea6e74b77cb5b97da88e37779f336ce2 100644 --- a/src/node_snapshotable.cc +++ b/src/node_snapshotable.cc @@ -1417,6 +1417,17 @@ void SerializeSnapshotableObjects(Realm* realm, }); } +void RunEmbedderPreload(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(env->embedder_preload()); + CHECK_EQ(args.Length(), 2); + Local process_obj = args[0]; + Local require_fn = args[1]; + CHECK(process_obj->IsObject()); + CHECK(require_fn->IsFunction()); + env->embedder_preload()(env, process_obj, require_fn); +} + void CompileSerializeMain(const FunctionCallbackInfo& args) { CHECK(args[0]->IsString()); Local filename = args[0].As(); @@ -1540,6 +1551,7 @@ void CreatePerContextProperties(Local target, void CreatePerIsolateProperties(IsolateData* isolate_data, Local target) { Isolate* isolate = isolate_data->isolate(); + SetMethod(isolate, target, "runEmbedderPreload", RunEmbedderPreload); SetMethod(isolate, target, "compileSerializeMain", CompileSerializeMain); SetMethod(isolate, target, "setSerializeCallback", SetSerializeCallback); SetMethod(isolate, target, "setDeserializeCallback", SetDeserializeCallback); @@ -1552,6 +1564,7 @@ void CreatePerIsolateProperties(IsolateData* isolate_data, } void RegisterExternalReferences(ExternalReferenceRegistry* registry) { + registry->Register(RunEmbedderPreload); registry->Register(CompileSerializeMain); registry->Register(SetSerializeCallback); registry->Register(SetDeserializeCallback); diff --git a/src/node_worker.cc b/src/node_worker.cc index 8b45c0265befaf68af07622d220ebdf96dc94ac4..196eb3bccaee87b52a46b693a061864ae6a5e3b9 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -62,6 +62,7 @@ Worker::Worker(Environment* env, thread_id_(AllocateEnvironmentThreadId()), name_(name), env_vars_(env_vars), + embedder_preload_(env->embedder_preload()), snapshot_data_(snapshot_data) { Debug(this, "Creating new worker instance with thread id %llu", thread_id_.id); @@ -386,8 +387,12 @@ void Worker::Run() { } Debug(this, "Created message port for worker %llu", thread_id_.id); - if (LoadEnvironment(env_.get(), StartExecutionCallback{}).IsEmpty()) + if (LoadEnvironment(env_.get(), + StartExecutionCallback{}, + std::move(embedder_preload_)) + .IsEmpty()) { return; + } Debug(this, "Loaded environment for worker %llu", thread_id_.id); } diff --git a/src/node_worker.h b/src/node_worker.h index 531e2b5287010f9206ab4fd7f4dd0f3dec9fe55c..07fd7b460654e169e8b6822474dc3cc70fcec4c0 100644 --- a/src/node_worker.h +++ b/src/node_worker.h @@ -114,6 +114,7 @@ class Worker : public AsyncWrap { std::unique_ptr child_port_data_; std::shared_ptr env_vars_; + EmbedderPreloadCallback embedder_preload_; // A raw flag that is used by creator and worker threads to // sync up on pre-mature termination of worker - while in the diff --git a/test/cctest/test_environment.cc b/test/cctest/test_environment.cc index 9b8124081542876ce7245e4aede6810e9bf0e2eb..64e38c83006a004ebc3519a5e9f8b04263244514 100644 --- a/test/cctest/test_environment.cc +++ b/test/cctest/test_environment.cc @@ -778,3 +778,31 @@ TEST_F(EnvironmentTest, RequestInterruptAtExit) { context->Exit(); } + +TEST_F(EnvironmentTest, EmbedderPreload) { + v8::HandleScope handle_scope(isolate_); + v8::Local context = node::NewContext(isolate_); + v8::Context::Scope context_scope(context); + + node::EmbedderPreloadCallback preload = [](node::Environment* env, + v8::Local process, + v8::Local require) { + CHECK(process->IsObject()); + CHECK(require->IsFunction()); + process.As() + ->Set(env->context(), + v8::String::NewFromUtf8Literal(env->isolate(), "prop"), + v8::String::NewFromUtf8Literal(env->isolate(), "preload")) + .Check(); + }; + + std::unique_ptr env( + node::CreateEnvironment(isolate_data_, context, {}, {}), + node::FreeEnvironment); + + v8::Local main_ret = + node::LoadEnvironment(env.get(), "return process.prop;", preload) + .ToLocalChecked(); + node::Utf8Value main_ret_str(isolate_, main_ret); + EXPECT_EQ(std::string(*main_ret_str), "preload"); +}