7373#endif
7474
7575#ifdef NODE_ENABLE_VTUNE_PROFILING
76- #include " ../deps/v8/src/ third_party/vtune/v8-vtune.h"
76+ #include " ../deps/v8/third_party/vtune/v8-vtune.h"
7777#endif
7878
7979#include " large_pages/node_large_page.h"
9696// ========== global C headers ==========
9797
9898#include < fcntl.h> // _O_RDWR
99+ #include < sys/stat.h>
99100#include < sys/types.h>
100101
101102#if defined(NODE_HAVE_I18N_SUPPORT)
@@ -255,37 +256,33 @@ MaybeLocal<Value> StartExecution(Environment* env, const char* main_script_id) {
255256}
256257
257258// Convert the result returned by an intermediate main script into
258- // StartExecutionCallbackInfo . Currently the result is an array containing
259- // [process, requireFunction, cjsRunner]
260- std::optional<StartExecutionCallbackInfo > CallbackInfoFromArray (
261- Local<Context> context , Local<Value> result) {
259+ // StartExecutionCallbackInfoWithModule . Currently the result is an array
260+ // containing [process, requireFunction, runModule].
261+ std::optional<StartExecutionCallbackInfoWithModule > CallbackInfoFromArray (
262+ Environment* env , Local<Value> result) {
262263 CHECK (result->IsArray ());
263264 Local<Array> args = result.As <Array>();
264265 CHECK_EQ (args->Length (), 3 );
265- Local<Value> process_obj, require_fn, runcjs_fn;
266+ Local<Value> process_obj, require_fn, run_module;
267+ Local<Context> context = env->context ();
266268 if (!args->Get (context, 0 ).ToLocal (&process_obj) ||
267269 !args->Get (context, 1 ).ToLocal (&require_fn) ||
268- !args->Get (context, 2 ).ToLocal (&runcjs_fn )) {
270+ !args->Get (context, 2 ).ToLocal (&run_module )) {
269271 return std::nullopt ;
270272 }
271273 CHECK (process_obj->IsObject ());
272274 CHECK (require_fn->IsFunction ());
273- CHECK (runcjs_fn->IsFunction ());
274- // TODO(joyeecheung): some support for running ESM as an entrypoint
275- // is needed. The simplest API would be to add a run_esm to
276- // StartExecutionCallbackInfo which compiles, links (to builtins)
277- // and evaluates a SourceTextModule.
278- // TODO(joyeecheung): the env pointer should be part of
279- // StartExecutionCallbackInfo, otherwise embedders are forced to use
280- // lambdas to pass it into the callback, which can make the code
281- // difficult to read.
282- node::StartExecutionCallbackInfo info{process_obj.As <Object>(),
283- require_fn.As <Function>(),
284- runcjs_fn.As <Function>()};
275+ CHECK (run_module->IsFunction ());
276+ StartExecutionCallbackInfoWithModule info;
277+ info.set_env (env);
278+ info.set_process_object (process_obj.As <Object>());
279+ info.set_native_require (require_fn.As <Function>());
280+ info.set_run_module (run_module.As <Function>());
285281 return info;
286282}
287283
288- MaybeLocal<Value> StartExecution (Environment* env, StartExecutionCallback cb) {
284+ MaybeLocal<Value> StartExecution (Environment* env,
285+ StartExecutionCallbackWithModule cb) {
289286 InternalCallbackScope callback_scope (
290287 env,
291288 Object::New (env->isolate ()),
@@ -294,7 +291,7 @@ MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
294291
295292 // Only snapshot builder or embedder applications set the
296293 // callback.
297- if (cb != nullptr ) {
294+ if (cb) {
298295 EscapableHandleScope scope (env->isolate ());
299296
300297 Local<Value> result;
@@ -308,9 +305,9 @@ MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
308305 }
309306 }
310307
311- auto info = CallbackInfoFromArray (env-> context () , result);
308+ auto info = CallbackInfoFromArray (env, result);
312309 if (!info.has_value ()) {
313- MaybeLocal<Value>();
310+ return MaybeLocal<Value>();
314311 }
315312#if HAVE_INSPECTOR
316313 if (env->options ()->debug_options ().break_first_line ) {
@@ -765,6 +762,13 @@ static ExitCode ProcessGlobalArgsInternal(std::vector<std::string>* args,
765762 v8_args.emplace_back (" --harmony-import-attributes" );
766763 }
767764
765+ if (!per_process::cli_options->per_isolate ->max_old_space_size_percentage
766+ .empty ()) {
767+ v8_args.emplace_back (
768+ " --max_old_space_size=" +
769+ per_process::cli_options->per_isolate ->max_old_space_size );
770+ }
771+
768772 auto env_opts = per_process::cli_options->per_isolate ->per_env ;
769773 if (std::ranges::find (v8_args, " --abort-on-uncaught-exception" ) !=
770774 v8_args.end () ||
@@ -773,7 +777,8 @@ static ExitCode ProcessGlobalArgsInternal(std::vector<std::string>* args,
773777 env_opts->abort_on_uncaught_exception = true ;
774778 }
775779
776- if (env_opts->experimental_wasm_modules ) {
780+ if (std::ranges::find (v8_args, " --no-js-source-phase-imports" ) ==
781+ v8_args.end ()) {
777782 v8_args.emplace_back (" --js-source-phase-imports" );
778783 }
779784
@@ -866,6 +871,7 @@ static ExitCode InitializeNodeWithArgsInternal(
866871 HandleEnvOptions (per_process::cli_options->per_isolate ->per_env );
867872
868873 std::string node_options;
874+ std::string node_options_from_dotenv;
869875 auto env_files = node::Dotenv::GetDataFromArgs (*argv);
870876
871877 if (!env_files.empty ()) {
@@ -892,19 +898,26 @@ static ExitCode InitializeNodeWithArgsInternal(
892898 }
893899 }
894900
895- per_process::dotenv_file.AssignNodeOptionsIfAvailable (&node_options);
901+ per_process::dotenv_file.AssignNodeOptionsIfAvailable (
902+ &node_options_from_dotenv);
896903 }
897904
898905 std::string node_options_from_config;
899- if (auto path = per_process::config_reader.GetDataFromArgs (*argv)) {
900- switch (per_process::config_reader.ParseConfig (*path)) {
906+ auto config_path = per_process::config_reader.GetDataFromArgs (argv);
907+ if (per_process::config_reader.HasInvalidDefaultConfigFileArgument ()) {
908+ errors->push_back (" --experimental-default-config-file does not take an "
909+ " argument" );
910+ return ExitCode::kInvalidCommandLineArgument ;
911+ }
912+ if (config_path) {
913+ switch (per_process::config_reader.ParseConfig (*config_path)) {
901914 case ParseResult::Valid:
902915 break ;
903916 case ParseResult::InvalidContent:
904- errors->push_back (std::string (*path ) + " : invalid content" );
917+ errors->push_back (std::string (*config_path ) + " : invalid content" );
905918 break ;
906919 case ParseResult::FileError:
907- errors->push_back (std::string (*path ) + " : not found" );
920+ errors->push_back (std::string (*config_path ) + " : not found" );
908921 break ;
909922 default :
910923 UNREACHABLE ();
@@ -922,11 +935,22 @@ static ExitCode InitializeNodeWithArgsInternal(
922935 errors->emplace_back (" The number of NODE_OPTIONS doesn't match "
923936 " the number of flags in the config file" );
924937 }
925- node_options += node_options_from_config;
926938 }
927939
940+ node_options = node_options_from_config + node_options_from_dotenv;
941+
928942#if !defined(NODE_WITHOUT_NODE_OPTIONS)
929- if (!(flags & ProcessInitializationFlags::kDisableNodeOptionsEnv )) {
943+ bool should_parse_node_options =
944+ !(flags & ProcessInitializationFlags::kDisableNodeOptionsEnv );
945+ #ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION
946+ if (sea::IsSingleExecutable ()) {
947+ sea::SeaResource sea_resource = sea::FindSingleExecutableResource ();
948+ if (sea_resource.exec_argv_extension != sea::SeaExecArgvExtension::kEnv ) {
949+ should_parse_node_options = false ;
950+ }
951+ }
952+ #endif
953+ if (should_parse_node_options) {
930954 // NODE_OPTIONS environment variable is preferred over the file one.
931955 if (credentials::SafeGetenv (" NODE_OPTIONS" , &node_options) ||
932956 !node_options.empty ()) {
@@ -1032,6 +1056,45 @@ static ExitCode InitializeNodeWithArgsInternal(
10321056 return ExitCode::kNoFailure ;
10331057}
10341058
1059+ #if NODE_USE_V8_WASM_TRAP_HANDLER
1060+ bool CanEnableWebAssemblyTrapHandler () {
1061+ // On POSIX, the machine may have a limit on the amount of virtual memory
1062+ // available, if it's not enough to allocate at least one cage for WASM,
1063+ // then the trap-handler-based bound checks cannot be used.
1064+ #ifdef __POSIX__
1065+ struct rlimit lim;
1066+ if (getrlimit (RLIMIT_AS , &lim) != 0 || lim.rlim_cur == RLIM_INFINITY ) {
1067+ // Can't get the limit or there's no limit, assume trap handler can be
1068+ // enabled.
1069+ return true ;
1070+ }
1071+ uint64_t virtual_memory_available = static_cast <uint64_t >(lim.rlim_cur );
1072+
1073+ size_t byte_capacity = 64 * 1024 ; // 64KB, the minimum size of a WASM memory.
1074+ uint64_t cage_size_needed_32 = V8::GetWasmMemoryReservationSizeInBytes (
1075+ V8 ::WasmMemoryType::kMemory32 , byte_capacity);
1076+ uint64_t cage_size_needed_64 = V8::GetWasmMemoryReservationSizeInBytes (
1077+ V8 ::WasmMemoryType::kMemory64 , byte_capacity);
1078+ uint64_t cage_size_needed =
1079+ std::max (cage_size_needed_32, cage_size_needed_64);
1080+ bool can_enable = virtual_memory_available >= cage_size_needed;
1081+ per_process::Debug (DebugCategory::BOOTSTRAP ,
1082+ " Virtual memory available: %" PRIu64 " bytes,\n "
1083+ " cage size needed for 32-bit: %" PRIu64 " bytes,\n "
1084+ " cage size needed for 64-bit: %" PRIu64 " bytes,\n "
1085+ " Can%senable WASM trap handler\n " ,
1086+ virtual_memory_available,
1087+ cage_size_needed_32,
1088+ cage_size_needed_64,
1089+ can_enable ? " " : " not " );
1090+
1091+ return can_enable;
1092+ #else
1093+ return true ;
1094+ #endif // __POSIX__
1095+ }
1096+ #endif // NODE_USE_V8_WASM_TRAP_HANDLER
1097+
10351098static std::shared_ptr<InitializationResultImpl>
10361099InitializeOncePerProcessInternal (const std::vector<std::string>& args,
10371100 ProcessInitializationFlags::Flags flags =
@@ -1153,6 +1216,28 @@ InitializeOncePerProcessInternal(const std::vector<std::string>& args,
11531216 conf_file = per_process::cli_options->openssl_config .c_str ();
11541217 }
11551218
1219+ // If the configured OpenSSL config file is actually a directory (for
1220+ // example when an application sets `OPENSSL_CONF` to a directory), OpenSSL
1221+ // may attempt to fopen() it which yields an error and causes startup to
1222+ // fail. Detect and ignore directory paths here and emit a warning so the
1223+ // process can continue using default OpenSSL config instead.
1224+ if (conf_file != nullptr ) {
1225+ struct stat st;
1226+ if (stat (conf_file, &st) == 0 ) {
1227+ #if defined(S_ISDIR)
1228+ if (S_ISDIR (st.st_mode )) {
1229+ #else
1230+ if ((st.st_mode & S_IFMT ) == S_IFDIR ) {
1231+ #endif
1232+ std::string warning = " Warning: OPENSSL_CONF path is a directory; "
1233+ " ignoring: " ;
1234+ warning += conf_file;
1235+ fprintf (stderr, " %s\n " , warning.c_str ());
1236+ conf_file = nullptr ;
1237+ }
1238+ }
1239+ }
1240+
11561241 OPENSSL_INIT_SETTINGS * settings = OPENSSL_INIT_new ();
11571242 OPENSSL_INIT_set_config_filename (settings, conf_file);
11581243 OPENSSL_INIT_set_config_appname (settings, conf_section_name);
@@ -1163,14 +1248,11 @@ InitializeOncePerProcessInternal(const std::vector<std::string>& args,
11631248 OPENSSL_INIT_free (settings);
11641249
11651250 if (ERR_peek_error () != 0 ) {
1166- // XXX: ERR_GET_REASON does not return something that is
1167- // useful as an exit code at all.
1168- result->exit_code_ =
1169- static_cast <ExitCode>(ERR_GET_REASON (ERR_peek_error ()));
1170- result->early_return_ = true ;
1171- result->errors_ .emplace_back (" OpenSSL configuration error:\n " +
1172- GetOpenSSLErrorString ());
1173- return result;
1251+ std::string warning =
1252+ " Warning: OpenSSL configuration error:\n " + GetOpenSSLErrorString ();
1253+ fprintf (stderr, " %s\n " , warning.c_str ());
1254+
1255+ ERR_clear_error ();
11741256 }
11751257#else // OPENSSL_VERSION_MAJOR < 3
11761258 if (FIPS_mode ()) {
@@ -1207,7 +1289,7 @@ InitializeOncePerProcessInternal(const std::vector<std::string>& args,
12071289 }
12081290
12091291 if (!(flags & ProcessInitializationFlags::kNoInitializeNodeV8Platform )) {
1210- uv_thread_setname (" MainThread" );
1292+ uv_thread_setname (" node- MainThread" );
12111293 per_process::v8_platform.Initialize (
12121294 static_cast <int >(per_process::cli_options->v8_thread_pool_size ));
12131295 result->platform_ = per_process::v8_platform.Platform ();
@@ -1234,7 +1316,9 @@ InitializeOncePerProcessInternal(const std::vector<std::string>& args,
12341316 bool use_wasm_trap_handler =
12351317 !per_process::cli_options->disable_wasm_trap_handler ;
12361318 if (!(flags & ProcessInitializationFlags::kNoDefaultSignalHandling ) &&
1237- use_wasm_trap_handler) {
1319+ use_wasm_trap_handler && CanEnableWebAssemblyTrapHandler ()) {
1320+ per_process::Debug (DebugCategory::BOOTSTRAP ,
1321+ " Enabling WebAssembly trap handler for bounds checks\n " );
12381322#if defined(_WIN32)
12391323 constexpr ULONG first = TRUE ;
12401324 per_process::old_vectored_exception_handler =
@@ -1347,6 +1431,21 @@ ExitCode GenerateAndWriteSnapshotData(const SnapshotData** snapshot_data_ptr,
13471431 DCHECK (snapshot_config.builder_script_path .has_value ());
13481432 const std::string& builder_script =
13491433 snapshot_config.builder_script_path .value ();
1434+
1435+ // For the special builder node:generate_default_snapshot_source, generate
1436+ // the snapshot as C++ source and write it to snapshot.cc (for testing).
1437+ if (builder_script == " node:generate_default_snapshot_source" ) {
1438+ // Reset to empty to generate from scratch.
1439+ snapshot_config.builder_script_path = {};
1440+ exit_code =
1441+ node::SnapshotBuilder::GenerateAsSource (" snapshot.cc" ,
1442+ args_maybe_patched,
1443+ result->exec_args (),
1444+ snapshot_config,
1445+ true /* use_array_literals */ );
1446+ return exit_code;
1447+ }
1448+
13501449 // node:embedded_snapshot_main indicates that we are using the
13511450 // embedded snapshot and we are not supposed to clean it up.
13521451 if (builder_script == " node:embedded_snapshot_main" ) {
@@ -1508,14 +1607,21 @@ static ExitCode StartInternal(int argc, char** argv) {
15081607 uv_loop_configure (uv_default_loop (), UV_METRICS_IDLE_TIME );
15091608 std::string sea_config = per_process::cli_options->experimental_sea_config ;
15101609 if (!sea_config.empty ()) {
1511- #if !defined(DISABLE_SINGLE_EXECUTABLE_APPLICATION)
1512- return sea::BuildSingleExecutableBlob (
1513- sea_config, result->args (), result->exec_args ());
1514- #else
1610+ #if defined(DISABLE_SINGLE_EXECUTABLE_APPLICATION)
15151611 fprintf (stderr, " Single executable application is disabled.\n " );
15161612 return ExitCode::kGenericUserError ;
1517- #endif // !defined(DISABLE_SINGLE_EXECUTABLE_APPLICATION)
1613+ #else
1614+ return sea::WriteSingleExecutableBlob (
1615+ sea_config, result->args (), result->exec_args ());
1616+ #endif
15181617 }
1618+
1619+ sea_config = per_process::cli_options->build_sea ;
1620+ if (!sea_config.empty ()) {
1621+ return sea::BuildSingleExecutable (
1622+ sea_config, result->args (), result->exec_args ());
1623+ }
1624+
15191625 // --build-snapshot indicates that we are in snapshot building mode.
15201626 if (per_process::cli_options->per_isolate ->build_snapshot ) {
15211627 if (per_process::cli_options->per_isolate ->build_snapshot_config .empty () &&
0 commit comments