diff --git a/node.cc b/node.cc new file mode 100644 index 00000000..d46c78a9 --- /dev/null +++ b/node.cc @@ -0,0 +1,1140 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "node.h" + +// ========== local headers ========== + +#include "debug_utils.h" +#include "env-inl.h" +#include "memory_tracker-inl.h" +#include "node_binding.h" +#include "node_internals.h" +#include "node_main_instance.h" +#include "node_metadata.h" +#include "node_native_module_env.h" +#include "node_options-inl.h" +#include "node_perf.h" +#include "node_process.h" +#include "node_revert.h" +#include "node_v8_platform-inl.h" +#include "node_version.h" + +#if HAVE_OPENSSL +#include "node_crypto.h" +#endif + +#if defined(NODE_HAVE_I18N_SUPPORT) +#include "node_i18n.h" +#endif + +#if HAVE_INSPECTOR +#include "inspector_agent.h" +#include "inspector_io.h" +#endif + +#if defined HAVE_DTRACE || defined HAVE_ETW +#include "node_dtrace.h" +#endif + +#if NODE_USE_V8_PLATFORM +#include "libplatform/libplatform.h" +#endif // NODE_USE_V8_PLATFORM +#include "v8-profiler.h" + +#if HAVE_INSPECTOR +#include "inspector/worker_inspector.h" // ParentInspectorHandle +#endif + +#ifdef NODE_ENABLE_LARGE_CODE_PAGES +#include "large_pages/node_large_page.h" +#endif + +#ifdef NODE_REPORT +#include "node_report.h" +#endif + +#if defined(__APPLE__) || defined(__linux__) +#define NODE_USE_V8_WASM_TRAP_HANDLER 1 +#else +#define NODE_USE_V8_WASM_TRAP_HANDLER 0 +#endif + +#if NODE_USE_V8_WASM_TRAP_HANDLER +#include +#include "v8-wasm-trap-handler-posix.h" +#endif // NODE_USE_V8_WASM_TRAP_HANDLER + +// ========== global C headers ========== + +#include // _O_RDWR +#include + +#if defined(NODE_HAVE_I18N_SUPPORT) +#include +#include +#endif + + +#if defined(LEAK_SANITIZER) +#include +#endif + +#if defined(_MSC_VER) +#include +#include +#define STDIN_FILENO 0 +#else +#include +#include // getrlimit, setrlimit +#include // tcgetattr, tcsetattr +#include // STDIN_FILENO, STDERR_FILENO +#endif + +// ========== global C++ headers ========== + +#include +#include // PATH_MAX +#include +#include +#include +#include + +#include +#include + +namespace node { + +using native_module::NativeModuleEnv; + +using v8::EscapableHandleScope; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::HandleScope; +using v8::Isolate; +using v8::Local; +using v8::Maybe; +using v8::MaybeLocal; +using v8::Object; +using v8::String; +using v8::Undefined; +using v8::V8; +using v8::Value; + +namespace per_process { + +// node_revert.h +// Bit flag used to track security reverts. +unsigned int reverted_cve = 0; + +// util.h +// Tells whether the per-process V8::Initialize() is called and +// if it is safe to call v8::Isolate::GetCurrent(). +bool v8_initialized = false; + +// node_internals.h +// process-relative uptime base in nanoseconds, initialized in node::Start() +uint64_t node_start_time; +// Tells whether --prof is passed. +bool v8_is_profiling = false; + +// node_v8_platform-inl.h +struct V8Platform v8_platform; +} // namespace per_process + +#ifdef __POSIX__ +void SignalExit(int signo, siginfo_t* info, void* ucontext) { + ResetStdio(); + raise(signo); +} +#endif // __POSIX__ + +MaybeLocal ExecuteBootstrapper(Environment* env, + const char* id, + std::vector>* parameters, + std::vector>* arguments) { + EscapableHandleScope scope(env->isolate()); + MaybeLocal maybe_fn = + NativeModuleEnv::LookupAndCompile(env->context(), id, parameters, env); + + if (maybe_fn.IsEmpty()) { + return MaybeLocal(); + } + + Local fn = maybe_fn.ToLocalChecked(); + MaybeLocal result = fn->Call(env->context(), + Undefined(env->isolate()), + arguments->size(), + arguments->data()); + + // If there was an error during bootstrap, it must be unrecoverable + // (e.g. max call stack exceeded). Clear the stack so that the + // AsyncCallbackScope destructor doesn't fail on the id check. + // There are only two ways to have a stack size > 1: 1) the user manually + // called MakeCallback or 2) user awaited during bootstrap, which triggered + // _tickCallback(). + if (result.IsEmpty()) { + env->async_hooks()->clear_async_id_stack(); + } + + return scope.EscapeMaybe(result); +} + +#if HAVE_INSPECTOR +int Environment::InitializeInspector( + std::unique_ptr parent_handle) { + std::string inspector_path; + if (parent_handle) { + DCHECK(!is_main_thread()); + inspector_path = parent_handle->url(); + inspector_agent_->SetParentHandle(std::move(parent_handle)); + } else { + inspector_path = argv_.size() > 1 ? argv_[1].c_str() : ""; + } + + CHECK(!inspector_agent_->IsListening()); + // Inspector agent can't fail to start, but if it was configured to listen + // right away on the websocket port and fails to bind/etc, this will return + // false. + inspector_agent_->Start(inspector_path, + options_->debug_options(), + inspector_host_port(), + is_main_thread()); + if (options_->debug_options().inspector_enabled && + !inspector_agent_->IsListening()) { + return 12; // Signal internal error + } + + profiler::StartProfilers(this); + + if (inspector_agent_->options().break_node_first_line) { + inspector_agent_->PauseOnNextJavascriptStatement("Break at bootstrap"); + } + + return 0; +} +#endif // HAVE_INSPECTOR && NODE_USE_V8_PLATFORM + +void Environment::InitializeDiagnostics() { + isolate_->GetHeapProfiler()->AddBuildEmbedderGraphCallback( + Environment::BuildEmbedderGraph, this); + if (options_->trace_uncaught) + isolate_->SetCaptureStackTraceForUncaughtExceptions(true); + +#if defined HAVE_DTRACE || defined HAVE_ETW + InitDTrace(this); +#endif +} + +MaybeLocal Environment::BootstrapInternalLoaders() { + EscapableHandleScope scope(isolate_); + + // Create binding loaders + std::vector> loaders_params = { + process_string(), + FIXED_ONE_BYTE_STRING(isolate_, "getLinkedBinding"), + FIXED_ONE_BYTE_STRING(isolate_, "getInternalBinding"), + primordials_string()}; + std::vector> loaders_args = { + process_object(), + NewFunctionTemplate(binding::GetLinkedBinding) + ->GetFunction(context()) + .ToLocalChecked(), + NewFunctionTemplate(binding::GetInternalBinding) + ->GetFunction(context()) + .ToLocalChecked(), + primordials()}; + + // Bootstrap internal loaders + Local loader_exports; + if (!ExecuteBootstrapper( + this, "internal/bootstrap/loaders", &loaders_params, &loaders_args) + .ToLocal(&loader_exports)) { + return MaybeLocal(); + } + CHECK(loader_exports->IsObject()); + Local loader_exports_obj = loader_exports.As(); + Local internal_binding_loader = + loader_exports_obj->Get(context(), internal_binding_string()) + .ToLocalChecked(); + CHECK(internal_binding_loader->IsFunction()); + set_internal_binding_loader(internal_binding_loader.As()); + Local require = + loader_exports_obj->Get(context(), require_string()).ToLocalChecked(); + CHECK(require->IsFunction()); + set_native_module_require(require.As()); + + return scope.Escape(loader_exports); +} + +MaybeLocal Environment::BootstrapNode() { + EscapableHandleScope scope(isolate_); + + Local global = context()->Global(); + // TODO(joyeecheung): this can be done in JS land now. + global->Set(context(), FIXED_ONE_BYTE_STRING(isolate_, "global"), global) + .Check(); + + // process, require, internalBinding, primordials + std::vector> node_params = { + process_string(), + require_string(), + internal_binding_string(), + primordials_string()}; + std::vector> node_args = { + process_object(), + native_module_require(), + internal_binding_loader(), + primordials()}; + + MaybeLocal result = ExecuteBootstrapper( + this, "internal/bootstrap/node", &node_params, &node_args); + + if (result.IsEmpty()) { + return scope.EscapeMaybe(result); + } + + if (is_main_thread()) { + result = ExecuteBootstrapper(this, + "internal/bootstrap/switches/is_main_thread", + &node_params, + &node_args); + } else { + result = + ExecuteBootstrapper(this, + "internal/bootstrap/switches/is_not_main_thread", + &node_params, + &node_args); + } + + if (result.IsEmpty()) { + return scope.EscapeMaybe(result); + } + + if (owns_process_state()) { + result = ExecuteBootstrapper( + this, + "internal/bootstrap/switches/does_own_process_state", + &node_params, + &node_args); + } else { + result = ExecuteBootstrapper( + this, + "internal/bootstrap/switches/does_not_own_process_state", + &node_params, + &node_args); + } + + if (result.IsEmpty()) { + return scope.EscapeMaybe(result); + } + + Local env_var_proxy; + if (!CreateEnvVarProxy(context(), isolate_, as_callback_data()) + .ToLocal(&env_var_proxy) || + process_object() + ->Set( + context(), FIXED_ONE_BYTE_STRING(isolate_, "env"), env_var_proxy) + .IsNothing()) { + return MaybeLocal(); + } + + return scope.EscapeMaybe(result); +} + +MaybeLocal Environment::RunBootstrapping() { + EscapableHandleScope scope(isolate_); + + CHECK(!has_run_bootstrapping_code()); + + if (BootstrapInternalLoaders().IsEmpty()) { + return MaybeLocal(); + } + + Local result; + if (!BootstrapNode().ToLocal(&result)) { + return MaybeLocal(); + } + + // Make sure that no request or handle is created during bootstrap - + // if necessary those should be done in pre-execution. + // Usually, doing so would trigger the checks present in the ReqWrap and + // HandleWrap classes, so this is only a consistency check. + CHECK(req_wrap_queue()->IsEmpty()); + CHECK(handle_wrap_queue()->IsEmpty()); + + set_has_run_bootstrapping_code(true); + + return scope.Escape(result); +} + +void MarkBootstrapComplete(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + env->performance_state()->Mark( + performance::NODE_PERFORMANCE_MILESTONE_BOOTSTRAP_COMPLETE); +} + +MaybeLocal StartExecution(Environment* env, const char* main_script_id) { + EscapableHandleScope scope(env->isolate()); + CHECK_NOT_NULL(main_script_id); + + std::vector> parameters = { + env->process_string(), + env->require_string(), + env->internal_binding_string(), + env->primordials_string(), + FIXED_ONE_BYTE_STRING(env->isolate(), "markBootstrapComplete")}; + + std::vector> arguments = { + env->process_object(), + env->native_module_require(), + env->internal_binding_loader(), + env->primordials(), + env->NewFunctionTemplate(MarkBootstrapComplete) + ->GetFunction(env->context()) + .ToLocalChecked()}; + + return scope.EscapeMaybe( + ExecuteBootstrapper(env, main_script_id, ¶meters, &arguments)); +} + +MaybeLocal StartMainThreadExecution(Environment* env) { + // To allow people to extend Node in different ways, this hook allows + // one to drop a file lib/_third_party_main.js into the build + // directory which will be executed instead of Node's normal loading. + if (NativeModuleEnv::Exists("_third_party_main")) { + return StartExecution(env, "internal/main/run_third_party_main"); + } + + std::string first_argv; + if (env->argv().size() > 1) { + first_argv = env->argv()[1]; + } + + if (first_argv == "inspect" || first_argv == "debug") { + return StartExecution(env, "internal/main/inspect"); + } + + if (per_process::cli_options->print_help) { + return StartExecution(env, "internal/main/print_help"); + } + + + if (env->options()->prof_process) { + return StartExecution(env, "internal/main/prof_process"); + } + + // -e/--eval without -i/--interactive + if (env->options()->has_eval_string && !env->options()->force_repl) { + return StartExecution(env, "internal/main/eval_string"); + } + + if (env->options()->syntax_check_only) { + return StartExecution(env, "internal/main/check_syntax"); + } + + if (!first_argv.empty() && first_argv != "-") { + return StartExecution(env, "internal/main/run_main_module"); + } + + if (env->options()->force_repl || uv_guess_handle(STDIN_FILENO) == UV_TTY) { + return StartExecution(env, "internal/main/repl"); + } + + return StartExecution(env, "internal/main/eval_stdin"); +} + +void LoadEnvironment(Environment* env) { + CHECK(env->is_main_thread()); + // TODO(joyeecheung): Not all of the execution modes in + // StartMainThreadExecution() make sense for embedders. Pick the + // useful ones out, and allow embedders to customize the entry + // point more directly without using _third_party_main.js + USE(StartMainThreadExecution(env)); +} + +#ifdef __POSIX__ +typedef void (*sigaction_cb)(int signo, siginfo_t* info, void* ucontext); +#endif +#if NODE_USE_V8_WASM_TRAP_HANDLER +static std::atomic previous_sigsegv_action; + +void TrapWebAssemblyOrContinue(int signo, siginfo_t* info, void* ucontext) { + if (!v8::TryHandleWebAssemblyTrapPosix(signo, info, ucontext)) { + sigaction_cb prev = previous_sigsegv_action.load(); + if (prev != nullptr) { + prev(signo, info, ucontext); + } else { + // Reset to the default signal handler, i.e. cause a hard crash. + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_DFL; + CHECK_EQ(sigaction(signo, &sa, nullptr), 0); + + ResetStdio(); + raise(signo); + } + } +} +#endif // NODE_USE_V8_WASM_TRAP_HANDLER + +#ifdef __POSIX__ +void RegisterSignalHandler(int signal, + sigaction_cb handler, + bool reset_handler) { + CHECK_NOT_NULL(handler); +#if NODE_USE_V8_WASM_TRAP_HANDLER + if (signal == SIGSEGV) { + CHECK(previous_sigsegv_action.is_lock_free()); + CHECK(!reset_handler); + previous_sigsegv_action.store(handler); + return; + } +#endif // NODE_USE_V8_WASM_TRAP_HANDLER + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = handler; + sa.sa_flags = reset_handler ? SA_RESETHAND : 0; + sigfillset(&sa.sa_mask); + CHECK_EQ(sigaction(signal, &sa, nullptr), 0); +} + +#endif // __POSIX__ + +#ifdef __POSIX__ +static struct { + int flags; + bool isatty; + struct stat stat; + struct termios termios; +} stdio[1 + STDERR_FILENO]; +#endif // __POSIX__ + + +inline void PlatformInit() { +#ifdef __POSIX__ +#if HAVE_INSPECTOR + sigset_t sigmask; + sigemptyset(&sigmask); + sigaddset(&sigmask, SIGUSR1); + const int err = pthread_sigmask(SIG_SETMASK, &sigmask, nullptr); +#endif // HAVE_INSPECTOR + + // Make sure file descriptors 0-2 are valid before we start logging anything. + for (auto& s : stdio) { + const int fd = &s - stdio; + if (fstat(fd, &s.stat) == 0) + continue; + // Anything but EBADF means something is seriously wrong. We don't + // have to special-case EINTR, fstat() is not interruptible. + if (errno != EBADF) + ABORT(); + if (fd != open("/dev/null", O_RDWR)) + ABORT(); + if (fstat(fd, &s.stat) != 0) + ABORT(); + } + +#if HAVE_INSPECTOR + CHECK_EQ(err, 0); +#endif // HAVE_INSPECTOR + + // TODO(addaleax): NODE_SHARED_MODE does not really make sense here. +#ifndef NODE_SHARED_MODE + // Restore signal dispositions, the parent process may have changed them. + struct sigaction act; + memset(&act, 0, sizeof(act)); + + // The hard-coded upper limit is because NSIG is not very reliable; on Linux, + // it evaluates to 32, 34 or 64, depending on whether RT signals are enabled. + // Counting up to SIGRTMIN doesn't work for the same reason. + for (unsigned nr = 1; nr < kMaxSignal; nr += 1) { + if (nr == SIGKILL || nr == SIGSTOP) + continue; + act.sa_handler = (nr == SIGPIPE || nr == SIGXFSZ) ? SIG_IGN : SIG_DFL; + CHECK_EQ(0, sigaction(nr, &act, nullptr)); + } +#endif // !NODE_SHARED_MODE + + // Record the state of the stdio file descriptors so we can restore it + // on exit. Needs to happen before installing signal handlers because + // they make use of that information. + for (auto& s : stdio) { + const int fd = &s - stdio; + int err; + + do + s.flags = fcntl(fd, F_GETFL); + while (s.flags == -1 && errno == EINTR); // NOLINT + CHECK_NE(s.flags, -1); + + if (uv_guess_handle(fd) != UV_TTY) continue; + s.isatty = true; + + do + err = tcgetattr(fd, &s.termios); + while (err == -1 && errno == EINTR); // NOLINT + CHECK_EQ(err, 0); + } + + RegisterSignalHandler(SIGINT, SignalExit, true); + RegisterSignalHandler(SIGTERM, SignalExit, true); + +#if NODE_USE_V8_WASM_TRAP_HANDLER + // Tell V8 to disable emitting WebAssembly + // memory bounds checks. This means that we have + // to catch the SIGSEGV in TrapWebAssemblyOrContinue + // and pass the signal context to V8. + { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = TrapWebAssemblyOrContinue; + CHECK_EQ(sigaction(SIGSEGV, &sa, nullptr), 0); + } + V8::EnableWebAssemblyTrapHandler(false); +#endif // NODE_USE_V8_WASM_TRAP_HANDLER + + // Raise the open file descriptor limit. + struct rlimit lim; + if (getrlimit(RLIMIT_NOFILE, &lim) == 0 && lim.rlim_cur != lim.rlim_max) { + // Do a binary search for the limit. + rlim_t min = lim.rlim_cur; + rlim_t max = 1 << 20; + // But if there's a defined upper bound, don't search, just set it. + if (lim.rlim_max != RLIM_INFINITY) { + min = lim.rlim_max; + max = lim.rlim_max; + } + do { + lim.rlim_cur = min + (max - min) / 2; + if (setrlimit(RLIMIT_NOFILE, &lim)) { + max = lim.rlim_cur; + } else { + min = lim.rlim_cur; + } + } while (min + 1 < max); + } +#endif // __POSIX__ +#ifdef _WIN32 + for (int fd = 0; fd <= 2; ++fd) { + auto handle = reinterpret_cast(_get_osfhandle(fd)); + if (handle == INVALID_HANDLE_VALUE || + GetFileType(handle) == FILE_TYPE_UNKNOWN) { + // Ignore _close result. If it fails or not depends on used Windows + // version. We will just check _open result. + _close(fd); + if (fd != _open("nul", _O_RDWR)) + ABORT(); + } + } +#endif // _WIN32 +} + + +// Safe to call more than once and from signal handlers. +void ResetStdio() { + uv_tty_reset_mode(); +#ifdef __POSIX__ + for (auto& s : stdio) { + const int fd = &s - stdio; + + struct stat tmp; + if (-1 == fstat(fd, &tmp)) { + CHECK_EQ(errno, EBADF); // Program closed file descriptor. + continue; + } + + bool is_same_file = + (s.stat.st_dev == tmp.st_dev && s.stat.st_ino == tmp.st_ino); + if (!is_same_file) continue; // Program reopened file descriptor. + + int flags; + do + flags = fcntl(fd, F_GETFL); + while (flags == -1 && errno == EINTR); // NOLINT + CHECK_NE(flags, -1); + + // Restore the O_NONBLOCK flag if it changed. + if (O_NONBLOCK & (flags ^ s.flags)) { + flags &= ~O_NONBLOCK; + flags |= s.flags & O_NONBLOCK; + + int err; + do + err = fcntl(fd, F_SETFL, flags); + while (err == -1 && errno == EINTR); // NOLINT + CHECK_NE(err, -1); + } + + if (s.isatty) { + sigset_t sa; + int err; + + // We might be a background job that doesn't own the TTY so block SIGTTOU + // before making the tcsetattr() call, otherwise that signal suspends us. + sigemptyset(&sa); + sigaddset(&sa, SIGTTOU); + + CHECK_EQ(0, pthread_sigmask(SIG_BLOCK, &sa, nullptr)); + do + err = tcsetattr(fd, TCSANOW, &s.termios); + while (err == -1 && errno == EINTR); // NOLINT + CHECK_EQ(0, pthread_sigmask(SIG_UNBLOCK, &sa, nullptr)); + CHECK_EQ(0, err); + } + } +#endif // __POSIX__ +} + + +int ProcessGlobalArgs(std::vector* args, + std::vector* exec_args, + std::vector* errors, + OptionEnvvarSettings settings) { + // Parse a few arguments which are specific to Node. + std::vector v8_args; + + Mutex::ScopedLock lock(per_process::cli_options_mutex); + options_parser::Parse( + args, + exec_args, + &v8_args, + per_process::cli_options.get(), + settings, + errors); + + if (!errors->empty()) return 9; + + std::string revert_error; + for (const std::string& cve : per_process::cli_options->security_reverts) { + Revert(cve.c_str(), &revert_error); + if (!revert_error.empty()) { + errors->emplace_back(std::move(revert_error)); + return 12; + } + } + + auto env_opts = per_process::cli_options->per_isolate->per_env; + if (std::find(v8_args.begin(), v8_args.end(), + "--abort-on-uncaught-exception") != v8_args.end() || + std::find(v8_args.begin(), v8_args.end(), + "--abort_on_uncaught_exception") != v8_args.end()) { + env_opts->abort_on_uncaught_exception = true; + } + + // TODO(bnoordhuis) Intercept --prof arguments and start the CPU profiler + // manually? That would give us a little more control over its runtime + // behavior but it could also interfere with the user's intentions in ways + // we fail to anticipate. Dillema. + if (std::find(v8_args.begin(), v8_args.end(), "--prof") != v8_args.end()) { + per_process::v8_is_profiling = true; + } + +#ifdef __POSIX__ + // Block SIGPROF signals when sleeping in epoll_wait/kevent/etc. Avoids the + // performance penalty of frequent EINTR wakeups when the profiler is running. + // Only do this for v8.log profiling, as it breaks v8::CpuProfiler users. + if (per_process::v8_is_profiling) { + uv_loop_configure(uv_default_loop(), UV_LOOP_BLOCK_SIGNAL, SIGPROF); + } +#endif + + std::vector v8_args_as_char_ptr(v8_args.size()); + if (v8_args.size() > 0) { + for (size_t i = 0; i < v8_args.size(); ++i) + v8_args_as_char_ptr[i] = &v8_args[i][0]; + int argc = v8_args.size(); + V8::SetFlagsFromCommandLine(&argc, &v8_args_as_char_ptr[0], true); + v8_args_as_char_ptr.resize(argc); + } + + // Anything that's still in v8_argv is not a V8 or a node option. + for (size_t i = 1; i < v8_args_as_char_ptr.size(); i++) + errors->push_back("bad option: " + std::string(v8_args_as_char_ptr[i])); + + if (v8_args_as_char_ptr.size() > 1) return 9; + + return 0; +} + +static std::atomic_bool init_called{false}; + +int InitializeNodeWithArgs(std::vector* argv, + std::vector* exec_argv, + std::vector* errors) { + // Make sure InitializeNodeWithArgs() is called only once. + CHECK(!init_called.exchange(true)); + + // Initialize node_start_time to get relative uptime. + per_process::node_start_time = uv_hrtime(); + + // Register built-in modules + binding::RegisterBuiltinModules(); + + // Make inherited handles noninheritable. + uv_disable_stdio_inheritance(); + +#ifdef NODE_REPORT + // Cache the original command line to be + // used in diagnostic reports. + per_process::cli_options->cmdline = *argv; +#endif // NODE_REPORT + +#if defined(NODE_V8_OPTIONS) + // Should come before the call to V8::SetFlagsFromCommandLine() + // so the user can disable a flag --foo at run-time by passing + // --no_foo from the command line. + V8::SetFlagsFromString(NODE_V8_OPTIONS, sizeof(NODE_V8_OPTIONS) - 1); +#endif + + std::shared_ptr default_env_options = + per_process::cli_options->per_isolate->per_env; + { + std::string text; + default_env_options->pending_deprecation = + credentials::SafeGetenv("NODE_PENDING_DEPRECATION", &text) && + text[0] == '1'; + } + + // Allow for environment set preserving symlinks. + { + std::string text; + default_env_options->preserve_symlinks = + credentials::SafeGetenv("NODE_PRESERVE_SYMLINKS", &text) && + text[0] == '1'; + } + + { + std::string text; + default_env_options->preserve_symlinks_main = + credentials::SafeGetenv("NODE_PRESERVE_SYMLINKS_MAIN", &text) && + text[0] == '1'; + } + + if (default_env_options->redirect_warnings.empty()) { + credentials::SafeGetenv("NODE_REDIRECT_WARNINGS", + &default_env_options->redirect_warnings); + } + +#if !defined(NODE_WITHOUT_NODE_OPTIONS) + std::string node_options; + + if (credentials::SafeGetenv("NODE_OPTIONS", &node_options)) { + std::vector env_argv; + // [0] is expected to be the program name, fill it in from the real argv. + env_argv.push_back(argv->at(0)); + + bool is_in_string = false; + bool will_start_new_arg = true; + for (std::string::size_type index = 0; + index < node_options.size(); + ++index) { + char c = node_options.at(index); + + // Backslashes escape the following character + if (c == '\\' && is_in_string) { + if (index + 1 == node_options.size()) { + errors->push_back("invalid value for NODE_OPTIONS " + "(invalid escape)\n"); + return 9; + } else { + c = node_options.at(++index); + } + } else if (c == ' ' && !is_in_string) { + will_start_new_arg = true; + continue; + } else if (c == '"') { + is_in_string = !is_in_string; + continue; + } + + if (will_start_new_arg) { + env_argv.emplace_back(std::string(1, c)); + will_start_new_arg = false; + } else { + env_argv.back() += c; + } + } + + if (is_in_string) { + errors->push_back("invalid value for NODE_OPTIONS " + "(unterminated string)\n"); + return 9; + } + + const int exit_code = ProcessGlobalArgs(&env_argv, + nullptr, + errors, + kAllowedInEnvironment); + if (exit_code != 0) return exit_code; + } +#endif + + const int exit_code = ProcessGlobalArgs(argv, + exec_argv, + errors, + kDisallowedInEnvironment); + if (exit_code != 0) return exit_code; + + // Set the process.title immediately after processing argv if --title is set. + if (!per_process::cli_options->title.empty()) + uv_set_process_title(per_process::cli_options->title.c_str()); + +#if defined(NODE_HAVE_I18N_SUPPORT) + // If the parameter isn't given, use the env variable. + if (per_process::cli_options->icu_data_dir.empty()) + credentials::SafeGetenv("NODE_ICU_DATA", + &per_process::cli_options->icu_data_dir); + +#ifdef NODE_ICU_DEFAULT_DATA_DIR + // If neither the CLI option nor the environment variable was specified, + // fall back to the configured default + if (per_process::cli_options->icu_data_dir.empty()) { + // Check whether the NODE_ICU_DEFAULT_DATA_DIR contains the right data + // file and can be read. + static const char full_path[] = + NODE_ICU_DEFAULT_DATA_DIR "/" U_ICUDATA_NAME ".dat"; + + FILE* f = fopen(full_path, "rb"); + + if (f != nullptr) { + fclose(f); + per_process::cli_options->icu_data_dir = NODE_ICU_DEFAULT_DATA_DIR; + } + } +#endif // NODE_ICU_DEFAULT_DATA_DIR + + // Initialize ICU. + // If icu_data_dir is empty here, it will load the 'minimal' data. + if (!i18n::InitializeICUDirectory(per_process::cli_options->icu_data_dir)) { + errors->push_back("could not initialize ICU " + "(check NODE_ICU_DATA or --icu-data-dir parameters)\n"); + return 9; + } + per_process::metadata.versions.InitializeIntlVersions(); +#endif + + NativeModuleEnv::InitializeCodeCache(); + + // We should set node_is_initialized here instead of in node::Start, + // otherwise embedders using node::Init to initialize everything will not be + // able to set it and native modules will not load for them. + node_is_initialized = true; + return 0; +} + +// TODO(addaleax): Deprecate and eventually remove this. +void Init(int* argc, + const char** argv, + int* exec_argc, + const char*** exec_argv) { + std::vector argv_(argv, argv + *argc); // NOLINT + std::vector exec_argv_; + std::vector errors; + + // This (approximately) duplicates some logic that has been moved to + // node::Start(), with the difference that here we explicitly call `exit()`. + int exit_code = InitializeNodeWithArgs(&argv_, &exec_argv_, &errors); + + for (const std::string& error : errors) + fprintf(stderr, "%s: %s\n", argv_.at(0).c_str(), error.c_str()); + if (exit_code != 0) exit(exit_code); + + if (per_process::cli_options->print_version) { + printf("%s\n", NODE_VERSION); + exit(0); + } + + if (per_process::cli_options->print_bash_completion) { + std::string completion = options_parser::GetBashCompletion(); + printf("%s\n", completion.c_str()); + exit(0); + } + + if (per_process::cli_options->print_v8_help) { + // Doesn't return. + V8::SetFlagsFromString("--help", static_cast(6)); + UNREACHABLE(); + } + + *argc = argv_.size(); + *exec_argc = exec_argv_.size(); + // These leak memory, because, in the original code of this function, no + // extra allocations were visible. This should be okay because this function + // is only supposed to be called once per process, though. + *exec_argv = Malloc(*exec_argc); + for (int i = 0; i < *exec_argc; ++i) + (*exec_argv)[i] = strdup(exec_argv_[i].c_str()); + for (int i = 0; i < *argc; ++i) + argv[i] = strdup(argv_[i].c_str()); +} + +InitializationResult InitializeOncePerProcess(int argc, char** argv) { + atexit(ResetStdio); + PlatformInit(); + + CHECK_GT(argc, 0); + + // Hack around with the argv pointer. Used for process.title = "blah". + argv = uv_setup_args(argc, argv); + + InitializationResult result; + result.args = std::vector(argv, argv + argc); + std::vector errors; + + // This needs to run *before* V8::Initialize(). + { + result.exit_code = + InitializeNodeWithArgs(&(result.args), &(result.exec_args), &errors); + for (const std::string& error : errors) + fprintf(stderr, "%s: %s\n", result.args.at(0).c_str(), error.c_str()); + if (result.exit_code != 0) { + result.early_return = true; + return result; + } + } + +#if defined(NODE_ENABLE_LARGE_CODE_PAGES) && NODE_ENABLE_LARGE_CODE_PAGES + if (per_process::cli_options->use_largepages == "on" || + per_process::cli_options->use_largepages == "silent") { + if (node::IsLargePagesEnabled()) { + if (node::MapStaticCodeToLargePages() != 0 && + per_process::cli_options->use_largepages != "silent") { + fprintf(stderr, + "Mapping code to large pages failed. Reverting to default page " + "size.\n"); + } + } else if (per_process::cli_options->use_largepages != "silent") { + fprintf(stderr, "Large pages are not enabled.\n"); + } + } +#else + if (per_process::cli_options->use_largepages == "on") { + fprintf(stderr, "Mapping to large pages is not supported.\n"); + } +#endif // NODE_ENABLE_LARGE_CODE_PAGES + + if (per_process::cli_options->print_version) { + printf("%s\n", NODE_VERSION); + result.exit_code = 0; + result.early_return = true; + return result; + } + + if (per_process::cli_options->print_bash_completion) { + std::string completion = options_parser::GetBashCompletion(); + printf("%s\n", completion.c_str()); + exit(0); + } + + if (per_process::cli_options->print_v8_help) { + // Doesn't return. + V8::SetFlagsFromString("--help", static_cast(6)); + UNREACHABLE(); + } + +#if HAVE_OPENSSL + { + std::string extra_ca_certs; + if (credentials::SafeGetenv("NODE_EXTRA_CA_CERTS", &extra_ca_certs)) + crypto::UseExtraCaCerts(extra_ca_certs); + } +#ifdef NODE_FIPS_MODE + // In the case of FIPS builds we should make sure + // the random source is properly initialized first. + OPENSSL_init(); +#endif // NODE_FIPS_MODE + // V8 on Windows doesn't have a good source of entropy. Seed it from + // OpenSSL's pool. + V8::SetEntropySource(crypto::EntropySource); +#endif // HAVE_OPENSSL + + per_process::v8_platform.Initialize( + per_process::cli_options->v8_thread_pool_size); + V8::Initialize(); + performance::performance_v8_start = PERFORMANCE_NOW(); + per_process::v8_initialized = true; + return result; +} + +void TearDownOncePerProcess() { + per_process::v8_initialized = false; + V8::Dispose(); + + // uv_run cannot be called from the time before the beforeExit callback + // runs until the program exits unless the event loop has any referenced + // handles after beforeExit terminates. This prevents unrefed timers + // that happen to terminate during shutdown from being run unsafely. + // Since uv_run cannot be called, uv_async handles held by the platform + // will never be fully cleaned up. + per_process::v8_platform.Dispose(); +} + +int Start(int argc, char** argv) { + InitializationResult result = InitializeOncePerProcess(argc, argv); + if (result.early_return) { + return result.exit_code; + } + + { + Isolate::CreateParams params; + const std::vector* indexes = nullptr; + std::vector external_references; + + bool force_no_snapshot = + per_process::cli_options->per_isolate->no_node_snapshot; + if (!force_no_snapshot) { + v8::StartupData* blob = NodeMainInstance::GetEmbeddedSnapshotBlob(); + if (blob != nullptr) { + // TODO(joyeecheung): collect external references and set it in + // params.external_references. + external_references.push_back(reinterpret_cast(nullptr)); + params.external_references = external_references.data(); + params.snapshot_blob = blob; + indexes = NodeMainInstance::GetIsolateDataIndexes(); + } + } + + NodeMainInstance main_instance(¶ms, + uv_default_loop(), + per_process::v8_platform.Platform(), + result.args, + result.exec_args, + indexes); + result.exit_code = main_instance.Run(); + } + + TearDownOncePerProcess(); + return result.exit_code; +} + +int Stop(Environment* env) { + env->ExitEnv(); + return 0; +} + +} // namespace node + +#if !HAVE_INSPECTOR +void Initialize() {} + +NODE_MODULE_CONTEXT_AWARE_INTERNAL(inspector, Initialize) +#endif // !HAVE_INSPECTOR diff --git a/p.txt b/p.txt new file mode 100644 index 00000000..7bea023a --- /dev/null +++ b/p.txt @@ -0,0 +1,1137 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "node.h" + +// ========== local headers ========== + +#include "debug_utils.h" +#include "env-inl.h" +#include "memory_tracker-inl.h" +#include "node_binding.h" +#include "node_internals.h" +#include "node_main_instance.h" +#include "node_metadata.h" +#include "node_native_module_env.h" +#include "node_options-inl.h" +#include "node_perf.h" +#include "node_process.h" +#include "node_revert.h" +#include "node_v8_platform-inl.h" +#include "node_version.h" + +#if HAVE_OPENSSL +#include "node_crypto.h" +#endif + +#if defined(NODE_HAVE_I18N_SUPPORT) +#include "node_i18n.h" +#endif + +#if HAVE_INSPECTOR +#include "inspector_agent.h" +#include "inspector_io.h" +#endif + +#if defined HAVE_DTRACE || defined HAVE_ETW +#include "node_dtrace.h" +#endif + +#if NODE_USE_V8_PLATFORM +#include "libplatform/libplatform.h" +#endif // NODE_USE_V8_PLATFORM +#include "v8-profiler.h" + +#if HAVE_INSPECTOR +#include "inspector/worker_inspector.h" // ParentInspectorHandle +#endif + +#ifdef NODE_ENABLE_LARGE_CODE_PAGES +#include "large_pages/node_large_page.h" +#endif + +#ifdef NODE_REPORT +#include "node_report.h" +#endif + +#if defined(__APPLE__) || defined(__linux__) +#define NODE_USE_V8_WASM_TRAP_HANDLER 1 +#else +#define NODE_USE_V8_WASM_TRAP_HANDLER 0 +#endif + +#if NODE_USE_V8_WASM_TRAP_HANDLER +#include +#include "v8-wasm-trap-handler-posix.h" +#endif // NODE_USE_V8_WASM_TRAP_HANDLER + +// ========== global C headers ========== + +#include // _O_RDWR +#include + +#if defined(NODE_HAVE_I18N_SUPPORT) +#include +#include +#endif + + +#if defined(LEAK_SANITIZER) +#include +#endif + +#if defined(_MSC_VER) +#include +#include +#define STDIN_FILENO 0 +#else +#include +#include // getrlimit, setrlimit +#include // tcgetattr, tcsetattr +#include // STDIN_FILENO, STDERR_FILENO +#endif + +// ========== global C++ headers ========== + +#include +#include // PATH_MAX +#include +#include +#include +#include + +#include +#include + +namespace node { + +using native_module::NativeModuleEnv; + +using v8::EscapableHandleScope; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::HandleScope; +using v8::Isolate; +using v8::Local; +using v8::Maybe; +using v8::MaybeLocal; +using v8::Object; +using v8::String; +using v8::Undefined; +using v8::V8; +using v8::Value; + +namespace per_process { + +// node_revert.h +// Bit flag used to track security reverts. +unsigned int reverted_cve = 0; + +// util.h +// Tells whether the per-process V8::Initialize() is called and +// if it is safe to call v8::Isolate::GetCurrent(). +bool v8_initialized = false; + +// node_internals.h +// process-relative uptime base in nanoseconds, initialized in node::Start() +uint64_t node_start_time; +// Tells whether --prof is passed. +bool v8_is_profiling = false; + +// node_v8_platform-inl.h +struct V8Platform v8_platform; +} // namespace per_process + +#ifdef __POSIX__ +void SignalExit(int signo, siginfo_t* info, void* ucontext) { + ResetStdio(); + raise(signo); +} +#endif // __POSIX__ + +MaybeLocal ExecuteBootstrapper(Environment* env, + const char* id, + std::vector>* parameters, + std::vector>* arguments) { + EscapableHandleScope scope(env->isolate()); + MaybeLocal maybe_fn = + NativeModuleEnv::LookupAndCompile(env->context(), id, parameters, env); + + if (maybe_fn.IsEmpty()) { + return MaybeLocal(); + } + + Local fn = maybe_fn.ToLocalChecked(); + MaybeLocal result = fn->Call(env->context(), + Undefined(env->isolate()), + arguments->size(), + arguments->data()); + + // If there was an error during bootstrap, it must be unrecoverable + // (e.g. max call stack exceeded). Clear the stack so that the + // AsyncCallbackScope destructor doesn't fail on the id check. + // There are only two ways to have a stack size > 1: 1) the user manually + // called MakeCallback or 2) user awaited during bootstrap, which triggered + // _tickCallback(). + if (result.IsEmpty()) { + env->async_hooks()->clear_async_id_stack(); + } + + return scope.EscapeMaybe(result); +} + +#if HAVE_INSPECTOR +int Environment::InitializeInspector( + std::unique_ptr parent_handle) { + std::string inspector_path; + if (parent_handle) { + DCHECK(!is_main_thread()); + inspector_path = parent_handle->url(); + inspector_agent_->SetParentHandle(std::move(parent_handle)); + } else { + inspector_path = argv_.size() > 1 ? argv_[1].c_str() : ""; + } + + CHECK(!inspector_agent_->IsListening()); + // Inspector agent can't fail to start, but if it was configured to listen + // right away on the websocket port and fails to bind/etc, this will return + // false. + inspector_agent_->Start(inspector_path, + options_->debug_options(), + inspector_host_port(), + is_main_thread()); + if (options_->debug_options().inspector_enabled && + !inspector_agent_->IsListening()) { + return 12; // Signal internal error + } + + profiler::StartProfilers(this); + + if (inspector_agent_->options().break_node_first_line) { + inspector_agent_->PauseOnNextJavascriptStatement("Break at bootstrap"); + } + + return 0; +} +#endif // HAVE_INSPECTOR && NODE_USE_V8_PLATFORM + +void Environment::InitializeDiagnostics() { + isolate_->GetHeapProfiler()->AddBuildEmbedderGraphCallback( + Environment::BuildEmbedderGraph, this); + if (options_->trace_uncaught) + isolate_->SetCaptureStackTraceForUncaughtExceptions(true); + +#if defined HAVE_DTRACE || defined HAVE_ETW + InitDTrace(this); +#endif +} + +MaybeLocal Environment::BootstrapInternalLoaders() { + EscapableHandleScope scope(isolate_); + + // Create binding loaders + std::vector> loaders_params = { + process_string(), + FIXED_ONE_BYTE_STRING(isolate_, "getLinkedBinding"), + FIXED_ONE_BYTE_STRING(isolate_, "getInternalBinding"), + primordials_string()}; + std::vector> loaders_args = { + process_object(), + NewFunctionTemplate(binding::GetLinkedBinding) + ->GetFunction(context()) + .ToLocalChecked(), + NewFunctionTemplate(binding::GetInternalBinding) + ->GetFunction(context()) + .ToLocalChecked(), + primordials()}; + + // Bootstrap internal loaders + Local loader_exports; + if (!ExecuteBootstrapper( + this, "internal/bootstrap/loaders", &loaders_params, &loaders_args) + .ToLocal(&loader_exports)) { + return MaybeLocal(); + } + CHECK(loader_exports->IsObject()); + Local loader_exports_obj = loader_exports.As(); + Local internal_binding_loader = + loader_exports_obj->Get(context(), internal_binding_string()) + .ToLocalChecked(); + CHECK(internal_binding_loader->IsFunction()); + set_internal_binding_loader(internal_binding_loader.As()); + Local require = + loader_exports_obj->Get(context(), require_string()).ToLocalChecked(); + CHECK(require->IsFunction()); + set_native_module_require(require.As()); + + return scope.Escape(loader_exports); +} + +MaybeLocal Environment::BootstrapNode() { + EscapableHandleScope scope(isolate_); + + Local global = context()->Global(); + // TODO(joyeecheung): this can be done in JS land now. + global->Set(context(), FIXED_ONE_BYTE_STRING(isolate_, "global"), global) + .Check(); + + // process, require, internalBinding, primordials + std::vector> node_params = { + process_string(), + require_string(), + internal_binding_string(), + primordials_string()}; + std::vector> node_args = { + process_object(), + native_module_require(), + internal_binding_loader(), + primordials()}; + + MaybeLocal result = ExecuteBootstrapper( + this, "internal/bootstrap/node", &node_params, &node_args); + + if (result.IsEmpty()) { + return scope.EscapeMaybe(result); + } + + if (is_main_thread()) { + result = ExecuteBootstrapper(this, + "internal/bootstrap/switches/is_main_thread", + &node_params, + &node_args); + } else { + result = + ExecuteBootstrapper(this, + "internal/bootstrap/switches/is_not_main_thread", + &node_params, + &node_args); + } + + if (result.IsEmpty()) { + return scope.EscapeMaybe(result); + } + + if (owns_process_state()) { + result = ExecuteBootstrapper( + this, + "internal/bootstrap/switches/does_own_process_state", + &node_params, + &node_args); + } else { + result = ExecuteBootstrapper( + this, + "internal/bootstrap/switches/does_not_own_process_state", + &node_params, + &node_args); + } + + if (result.IsEmpty()) { + return scope.EscapeMaybe(result); + } + + Local env_var_proxy; + if (!CreateEnvVarProxy(context(), isolate_, as_callback_data()) + .ToLocal(&env_var_proxy) || + process_object() + ->Set( + context(), FIXED_ONE_BYTE_STRING(isolate_, "env"), env_var_proxy) + .IsNothing()) { + return MaybeLocal(); + } + + return scope.EscapeMaybe(result); +} + +MaybeLocal Environment::RunBootstrapping() { + EscapableHandleScope scope(isolate_); + + CHECK(!has_run_bootstrapping_code()); + + if (BootstrapInternalLoaders().IsEmpty()) { + return MaybeLocal(); + } + + Local result; + if (!BootstrapNode().ToLocal(&result)) { + return MaybeLocal(); + } + + // Make sure that no request or handle is created during bootstrap - + // if necessary those should be done in pre-execution. + // Usually, doing so would trigger the checks present in the ReqWrap and + // HandleWrap classes, so this is only a consistency check. + CHECK(req_wrap_queue()->IsEmpty()); + CHECK(handle_wrap_queue()->IsEmpty()); + + set_has_run_bootstrapping_code(true); + + return scope.Escape(result); +} + +void MarkBootstrapComplete(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + env->performance_state()->Mark( + performance::NODE_PERFORMANCE_MILESTONE_BOOTSTRAP_COMPLETE); +} + +MaybeLocal StartExecution(Environment* env, const char* main_script_id) { + EscapableHandleScope scope(env->isolate()); + CHECK_NOT_NULL(main_script_id); + + std::vector> parameters = { + env->process_string(), + env->require_string(), + env->internal_binding_string(), + env->primordials_string(), + FIXED_ONE_BYTE_STRING(env->isolate(), "markBootstrapComplete")}; + + std::vector> arguments = { + env->process_object(), + env->native_module_require(), + env->internal_binding_loader(), + env->primordials(), + env->NewFunctionTemplate(MarkBootstrapComplete) + ->GetFunction(env->context()) + .ToLocalChecked()}; + + return scope.EscapeMaybe( + ExecuteBootstrapper(env, main_script_id, ¶meters, &arguments)); +} + +MaybeLocal StartMainThreadExecution(Environment* env) { + // To allow people to extend Node in different ways, this hook allows + // one to drop a file lib/_third_party_main.js into the build + // directory which will be executed instead of Node's normal loading. + if (NativeModuleEnv::Exists("_third_party_main")) { + return StartExecution(env, "internal/main/run_third_party_main"); + } + + std::string first_argv; + if (env->argv().size() > 1) { + first_argv = env->argv()[1]; + } + + if (first_argv == "inspect" || first_argv == "debug") { + return StartExecution(env, "internal/main/inspect"); + } + + if (per_process::cli_options->print_help) { + return StartExecution(env, "internal/main/print_help"); + } + + + if (env->options()->prof_process) { + return StartExecution(env, "internal/main/prof_process"); + } + + // -e/--eval without -i/--interactive + if (env->options()->has_eval_string && !env->options()->force_repl) { + return StartExecution(env, "internal/main/eval_string"); + } + + if (env->options()->syntax_check_only) { + return StartExecution(env, "internal/main/check_syntax"); + } + + if (!first_argv.empty() && first_argv != "-") { + return StartExecution(env, "internal/main/run_main_module"); + } + + if (env->options()->force_repl || uv_guess_handle(STDIN_FILENO) == UV_TTY) { + return StartExecution(env, "internal/main/repl"); + } + + return StartExecution(env, "internal/main/eval_stdin"); +} + +void LoadEnvironment(Environment* env) { + CHECK(env->is_main_thread()); + // TODO(joyeecheung): Not all of the execution modes in + // StartMainThreadExecution() make sense for embedders. Pick the + // useful ones out, and allow embedders to customize the entry + // point more directly without using _third_party_main.js + USE(StartMainThreadExecution(env)); +} + +#ifdef __POSIX__ +typedef void (*sigaction_cb)(int signo, siginfo_t* info, void* ucontext); +#endif +#if NODE_USE_V8_WASM_TRAP_HANDLER +static std::atomic previous_sigsegv_action; + +void TrapWebAssemblyOrContinue(int signo, siginfo_t* info, void* ucontext) { + if (!v8::TryHandleWebAssemblyTrapPosix(signo, info, ucontext)) { + sigaction_cb prev = previous_sigsegv_action.load(); + if (prev != nullptr) { + prev(signo, info, ucontext); + } else { + // Reset to the default signal handler, i.e. cause a hard crash. + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_DFL; + CHECK_EQ(sigaction(signo, &sa, nullptr), 0); + + ResetStdio(); + raise(signo); + } + } +} +#endif // NODE_USE_V8_WASM_TRAP_HANDLER + +#ifdef __POSIX__ +void RegisterSignalHandler(int signal, + sigaction_cb handler, + bool reset_handler) { + CHECK_NOT_NULL(handler); +#if NODE_USE_V8_WASM_TRAP_HANDLER + if (signal == SIGSEGV) { + CHECK(previous_sigsegv_action.is_lock_free()); + CHECK(!reset_handler); + previous_sigsegv_action.store(handler); + return; + } +#endif // NODE_USE_V8_WASM_TRAP_HANDLER + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = handler; + sa.sa_flags = reset_handler ? SA_RESETHAND : 0; + sigfillset(&sa.sa_mask); + CHECK_EQ(sigaction(signal, &sa, nullptr), 0); +} + +#endif // __POSIX__ + +#ifdef __POSIX__ +static struct { + int flags; + bool isatty; + struct stat stat; + struct termios termios; +} stdio[1 + STDERR_FILENO]; +#endif // __POSIX__ + + +inline void PlatformInit() { +#ifdef __POSIX__ +#if HAVE_INSPECTOR + sigset_t sigmask; + sigemptyset(&sigmask); + sigaddset(&sigmask, SIGUSR1); + const int err = pthread_sigmask(SIG_SETMASK, &sigmask, nullptr); +#endif // HAVE_INSPECTOR + + // Make sure file descriptors 0-2 are valid before we start logging anything. + for (auto& s : stdio) { + const int fd = &s - stdio; + if (fstat(fd, &s.stat) == 0) + continue; + // Anything but EBADF means something is seriously wrong. We don't + // have to special-case EINTR, fstat() is not interruptible. + if (errno != EBADF) + ABORT(); + if (fd != open("/dev/null", O_RDWR)) + ABORT(); + if (fstat(fd, &s.stat) != 0) + ABORT(); + } + +#if HAVE_INSPECTOR + CHECK_EQ(err, 0); +#endif // HAVE_INSPECTOR + + // TODO(addaleax): NODE_SHARED_MODE does not really make sense here. +#ifndef NODE_SHARED_MODE + // Restore signal dispositions, the parent process may have changed them. + struct sigaction act; + memset(&act, 0, sizeof(act)); + + // The hard-coded upper limit is because NSIG is not very reliable; on Linux, + // it evaluates to 32, 34 or 64, depending on whether RT signals are enabled. + // Counting up to SIGRTMIN doesn't work for the same reason. + for (unsigned nr = 1; nr < kMaxSignal; nr += 1) { + if (nr == SIGKILL || nr == SIGSTOP) + continue; + act.sa_handler = (nr == SIGPIPE || nr == SIGXFSZ) ? SIG_IGN : SIG_DFL; + CHECK_EQ(0, sigaction(nr, &act, nullptr)); + } +#endif // !NODE_SHARED_MODE + + // Record the state of the stdio file descriptors so we can restore it + // on exit. Needs to happen before installing signal handlers because + // they make use of that information. + for (auto& s : stdio) { + const int fd = &s - stdio; + int err; + + do + s.flags = fcntl(fd, F_GETFL); + while (s.flags == -1 && errno == EINTR); // NOLINT + CHECK_NE(s.flags, -1); + + if (uv_guess_handle(fd) != UV_TTY) continue; + s.isatty = true; + + do + err = tcgetattr(fd, &s.termios); + while (err == -1 && errno == EINTR); // NOLINT + CHECK_EQ(err, 0); + } + + RegisterSignalHandler(SIGINT, SignalExit, true); + RegisterSignalHandler(SIGTERM, SignalExit, true); + +#if NODE_USE_V8_WASM_TRAP_HANDLER + // Tell V8 to disable emitting WebAssembly + // memory bounds checks. This means that we have + // to catch the SIGSEGV in TrapWebAssemblyOrContinue + // and pass the signal context to V8. + { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = TrapWebAssemblyOrContinue; + CHECK_EQ(sigaction(SIGSEGV, &sa, nullptr), 0); + } + V8::EnableWebAssemblyTrapHandler(false); +#endif // NODE_USE_V8_WASM_TRAP_HANDLER + + // Raise the open file descriptor limit. + struct rlimit lim; + if (getrlimit(RLIMIT_NOFILE, &lim) == 0 && lim.rlim_cur != lim.rlim_max) { + // Do a binary search for the limit. + rlim_t min = lim.rlim_cur; + rlim_t max = 1 << 20; + // But if there's a defined upper bound, don't search, just set it. + if (lim.rlim_max != RLIM_INFINITY) { + min = lim.rlim_max; + max = lim.rlim_max; + } + do { + lim.rlim_cur = min + (max - min) / 2; + if (setrlimit(RLIMIT_NOFILE, &lim)) { + max = lim.rlim_cur; + } else { + min = lim.rlim_cur; + } + } while (min + 1 < max); + } +#endif // __POSIX__ +#ifdef _WIN32 + for (int fd = 0; fd <= 2; ++fd) { + auto handle = reinterpret_cast(_get_osfhandle(fd)); + if (handle == INVALID_HANDLE_VALUE || + GetFileType(handle) == FILE_TYPE_UNKNOWN) { + // Ignore _close result. If it fails or not depends on used Windows + // version. We will just check _open result. + _close(fd); + if (fd != _open("nul", _O_RDWR)) + ABORT(); + } + } +#endif // _WIN32 +} + + +// Safe to call more than once and from signal handlers. +void ResetStdio() { + uv_tty_reset_mode(); +#ifdef __POSIX__ + for (auto& s : stdio) { + const int fd = &s - stdio; + + struct stat tmp; + if (-1 == fstat(fd, &tmp)) { + CHECK_EQ(errno, EBADF); // Program closed file descriptor. + continue; + } + + bool is_same_file = + (s.stat.st_dev == tmp.st_dev && s.stat.st_ino == tmp.st_ino); + if (!is_same_file) continue; // Program reopened file descriptor. + + int flags; + do + flags = fcntl(fd, F_GETFL); + while (flags == -1 && errno == EINTR); // NOLINT + CHECK_NE(flags, -1); + + // Restore the O_NONBLOCK flag if it changed. + if (O_NONBLOCK & (flags ^ s.flags)) { + flags &= ~O_NONBLOCK; + flags |= s.flags & O_NONBLOCK; + + int err; + do + err = fcntl(fd, F_SETFL, flags); + while (err == -1 && errno == EINTR); // NOLINT + CHECK_NE(err, -1); + } + + if (s.isatty) { + sigset_t sa; + int err; + + // We might be a background job that doesn't own the TTY so block SIGTTOU + // before making the tcsetattr() call, otherwise that signal suspends us. + sigemptyset(&sa); + sigaddset(&sa, SIGTTOU); + + CHECK_EQ(0, pthread_sigmask(SIG_BLOCK, &sa, nullptr)); + do + err = tcsetattr(fd, TCSANOW, &s.termios); + while (err == -1 && errno == EINTR); // NOLINT + CHECK_EQ(0, pthread_sigmask(SIG_UNBLOCK, &sa, nullptr)); + CHECK_EQ(0, err); + } + } +#endif // __POSIX__ +} + + +int ProcessGlobalArgs(std::vector* args, + std::vector* exec_args, + std::vector* errors, + OptionEnvvarSettings settings) { + // Parse a few arguments which are specific to Node. + std::vector v8_args; + + Mutex::ScopedLock lock(per_process::cli_options_mutex); + options_parser::Parse( + args, + exec_args, + &v8_args, + per_process::cli_options.get(), + settings, + errors); + + if (!errors->empty()) return 9; + + std::string revert_error; + for (const std::string& cve : per_process::cli_options->security_reverts) { + Revert(cve.c_str(), &revert_error); + if (!revert_error.empty()) { + errors->emplace_back(std::move(revert_error)); + return 12; + } + } + + auto env_opts = per_process::cli_options->per_isolate->per_env; + if (std::find(v8_args.begin(), v8_args.end(), + "--abort-on-uncaught-exception") != v8_args.end() || + std::find(v8_args.begin(), v8_args.end(), + "--abort_on_uncaught_exception") != v8_args.end()) { + env_opts->abort_on_uncaught_exception = true; + } + + // TODO(bnoordhuis) Intercept --prof arguments and start the CPU profiler + // manually? That would give us a little more control over its runtime + // behavior but it could also interfere with the user's intentions in ways + // we fail to anticipate. Dillema. + if (std::find(v8_args.begin(), v8_args.end(), "--prof") != v8_args.end()) { + per_process::v8_is_profiling = true; + } + +#ifdef __POSIX__ + // Block SIGPROF signals when sleeping in epoll_wait/kevent/etc. Avoids the + // performance penalty of frequent EINTR wakeups when the profiler is running. + // Only do this for v8.log profiling, as it breaks v8::CpuProfiler users. + if (per_process::v8_is_profiling) { + uv_loop_configure(uv_default_loop(), UV_LOOP_BLOCK_SIGNAL, SIGPROF); + } +#endif + + std::vector v8_args_as_char_ptr(v8_args.size()); + if (v8_args.size() > 0) { + for (size_t i = 0; i < v8_args.size(); ++i) + v8_args_as_char_ptr[i] = &v8_args[i][0]; + int argc = v8_args.size(); + V8::SetFlagsFromCommandLine(&argc, &v8_args_as_char_ptr[0], true); + v8_args_as_char_ptr.resize(argc); + } + + // Anything that's still in v8_argv is not a V8 or a node option. + for (size_t i = 1; i < v8_args_as_char_ptr.size(); i++) + errors->push_back("bad option: " + std::string(v8_args_as_char_ptr[i])); + + if (v8_args_as_char_ptr.size() > 1) return 9; + + return 0; +} + +static std::atomic_bool init_called{false}; + +int InitializeNodeWithArgs(std::vector* argv, + std::vector* exec_argv, + std::vector* errors) { + // Make sure InitializeNodeWithArgs() is called only once. + CHECK(!init_called.exchange(true)); + + // Initialize node_start_time to get relative uptime. + per_process::node_start_time = uv_hrtime(); + + // Register built-in modules + binding::RegisterBuiltinModules(); + + // Make inherited handles noninheritable. + uv_disable_stdio_inheritance(); + +#ifdef NODE_REPORT + // Cache the original command line to be + // used in diagnostic reports. + per_process::cli_options->cmdline = *argv; +#endif // NODE_REPORT + +#if defined(NODE_V8_OPTIONS) + // Should come before the call to V8::SetFlagsFromCommandLine() + // so the user can disable a flag --foo at run-time by passing + // --no_foo from the command line. + V8::SetFlagsFromString(NODE_V8_OPTIONS, sizeof(NODE_V8_OPTIONS) - 1); +#endif + + std::shared_ptr default_env_options = + per_process::cli_options->per_isolate->per_env; + { + std::string text; + default_env_options->pending_deprecation = + credentials::SafeGetenv("NODE_PENDING_DEPRECATION", &text) && + text[0] == '1'; + } + + // Allow for environment set preserving symlinks. + { + std::string text; + default_env_options->preserve_symlinks = + credentials::SafeGetenv("NODE_PRESERVE_SYMLINKS", &text) && + text[0] == '1'; + } + + { + std::string text; + default_env_options->preserve_symlinks_main = + credentials::SafeGetenv("NODE_PRESERVE_SYMLINKS_MAIN", &text) && + text[0] == '1'; + } + + if (default_env_options->redirect_warnings.empty()) { + credentials::SafeGetenv("NODE_REDIRECT_WARNINGS", + &default_env_options->redirect_warnings); + } + +#if !defined(NODE_WITHOUT_NODE_OPTIONS) + std::string node_options; + + if (credentials::SafeGetenv("NODE_OPTIONS", &node_options)) { + std::vector env_argv; + // [0] is expected to be the program name, fill it in from the real argv. + env_argv.push_back(argv->at(0)); + + bool is_in_string = false; + bool will_start_new_arg = true; + for (std::string::size_type index = 0; + index < node_options.size(); + ++index) { + char c = node_options.at(index); + + // Backslashes escape the following character + if (c == '\\' && is_in_string) { + if (index + 1 == node_options.size()) { + errors->push_back("invalid value for NODE_OPTIONS " + "(invalid escape)\n"); + return 9; + } else { + c = node_options.at(++index); + } + } else if (c == ' ' && !is_in_string) { + will_start_new_arg = true; + continue; + } else if (c == '"') { + is_in_string = !is_in_string; + continue; + } + + if (will_start_new_arg) { + env_argv.emplace_back(std::string(1, c)); + will_start_new_arg = false; + } else { + env_argv.back() += c; + } + } + + if (is_in_string) { + errors->push_back("invalid value for NODE_OPTIONS " + "(unterminated string)\n"); + return 9; + } + + const int exit_code = ProcessGlobalArgs(&env_argv, + nullptr, + errors, + kAllowedInEnvironment); + if (exit_code != 0) return exit_code; + } +#endif + + const int exit_code = XXX0; + if (exit_code != 0) return exit_code; + + // Set the process.title immediately after processing argv if --title is set. + if (!per_process::cli_options->title.empty()) + uv_set_process_title(per_process::cli_options->title.c_str()); + +#if defined(NODE_HAVE_I18N_SUPPORT) + // If the parameter isn't given, use the env variable. + if (per_process::cli_options->icu_data_dir.empty()) + credentials::SafeGetenv("NODE_ICU_DATA", + &per_process::cli_options->icu_data_dir); + +#ifdef NODE_ICU_DEFAULT_DATA_DIR + // If neither the CLI option nor the environment variable was specified, + // fall back to the configured default + if (per_process::cli_options->icu_data_dir.empty()) { + // Check whether the NODE_ICU_DEFAULT_DATA_DIR contains the right data + // file and can be read. + static const char full_path[] = + NODE_ICU_DEFAULT_DATA_DIR "/" U_ICUDATA_NAME ".dat"; + + FILE* f = fopen(full_path, "rb"); + + if (f != nullptr) { + fclose(f); + per_process::cli_options->icu_data_dir = NODE_ICU_DEFAULT_DATA_DIR; + } + } +#endif // NODE_ICU_DEFAULT_DATA_DIR + + // Initialize ICU. + // If icu_data_dir is empty here, it will load the 'minimal' data. + if (!i18n::InitializeICUDirectory(per_process::cli_options->icu_data_dir)) { + errors->push_back("could not initialize ICU " + "(check NODE_ICU_DATA or --icu-data-dir parameters)\n"); + return 9; + } + per_process::metadata.versions.InitializeIntlVersions(); +#endif + + NativeModuleEnv::InitializeCodeCache(); + + // We should set node_is_initialized here instead of in node::Start, + // otherwise embedders using node::Init to initialize everything will not be + // able to set it and native modules will not load for them. + node_is_initialized = true; + return 0; +} + +// TODO(addaleax): Deprecate and eventually remove this. +void Init(int* argc, + const char** argv, + int* exec_argc, + const char*** exec_argv) { + std::vector argv_(argv, argv + *argc); // NOLINT + std::vector exec_argv_; + std::vector errors; + + // This (approximately) duplicates some logic that has been moved to + // node::Start(), with the difference that here we explicitly call `exit()`. + int exit_code = InitializeNodeWithArgs(&argv_, &exec_argv_, &errors); + + for (const std::string& error : errors) + fprintf(stderr, "%s: %s\n", argv_.at(0).c_str(), error.c_str()); + if (exit_code != 0) exit(exit_code); + + if (per_process::cli_options->print_version) { + printf("%s\n", NODE_VERSION); + exit(0); + } + + if (per_process::cli_options->print_bash_completion) { + std::string completion = options_parser::GetBashCompletion(); + printf("%s\n", completion.c_str()); + exit(0); + } + + if (per_process::cli_options->print_v8_help) { + // Doesn't return. + V8::SetFlagsFromString("--help", static_cast(6)); + UNREACHABLE(); + } + + *argc = argv_.size(); + *exec_argc = exec_argv_.size(); + // These leak memory, because, in the original code of this function, no + // extra allocations were visible. This should be okay because this function + // is only supposed to be called once per process, though. + *exec_argv = Malloc(*exec_argc); + for (int i = 0; i < *exec_argc; ++i) + (*exec_argv)[i] = strdup(exec_argv_[i].c_str()); + for (int i = 0; i < *argc; ++i) + argv[i] = strdup(argv_[i].c_str()); +} + +InitializationResult InitializeOncePerProcess(int argc, char** argv) { + atexit(ResetStdio); + PlatformInit(); + + CHECK_GT(argc, 0); + + // Hack around with the argv pointer. Used for process.title = "blah". + argv = uv_setup_args(argc, argv); + + InitializationResult result; + result.args = std::vector(argv, argv + argc); + std::vector errors; + + // This needs to run *before* V8::Initialize(). + { + result.exit_code = + InitializeNodeWithArgs(&(result.args), &(result.exec_args), &errors); + for (const std::string& error : errors) + fprintf(stderr, "%s: %s\n", result.args.at(0).c_str(), error.c_str()); + if (result.exit_code != 0) { + result.early_return = true; + return result; + } + } + +#if defined(NODE_ENABLE_LARGE_CODE_PAGES) && NODE_ENABLE_LARGE_CODE_PAGES + if (per_process::cli_options->use_largepages == "on" || + per_process::cli_options->use_largepages == "silent") { + if (node::IsLargePagesEnabled()) { + if (node::MapStaticCodeToLargePages() != 0 && + per_process::cli_options->use_largepages != "silent") { + fprintf(stderr, + "Mapping code to large pages failed. Reverting to default page " + "size.\n"); + } + } else if (per_process::cli_options->use_largepages != "silent") { + fprintf(stderr, "Large pages are not enabled.\n"); + } + } +#else + if (per_process::cli_options->use_largepages == "on") { + fprintf(stderr, "Mapping to large pages is not supported.\n"); + } +#endif // NODE_ENABLE_LARGE_CODE_PAGES + + if (per_process::cli_options->print_version) { + printf("%s\n", NODE_VERSION); + result.exit_code = 0; + result.early_return = true; + return result; + } + + if (per_process::cli_options->print_bash_completion) { + std::string completion = options_parser::GetBashCompletion(); + printf("%s\n", completion.c_str()); + exit(0); + } + + if (per_process::cli_options->print_v8_help) { + // Doesn't return. + V8::SetFlagsFromString("--help", static_cast(6)); + UNREACHABLE(); + } + +#if HAVE_OPENSSL + { + std::string extra_ca_certs; + if (credentials::SafeGetenv("NODE_EXTRA_CA_CERTS", &extra_ca_certs)) + crypto::UseExtraCaCerts(extra_ca_certs); + } +#ifdef NODE_FIPS_MODE + // In the case of FIPS builds we should make sure + // the random source is properly initialized first. + OPENSSL_init(); +#endif // NODE_FIPS_MODE + // V8 on Windows doesn't have a good source of entropy. Seed it from + // OpenSSL's pool. + V8::SetEntropySource(crypto::EntropySource); +#endif // HAVE_OPENSSL + + per_process::v8_platform.Initialize( + per_process::cli_options->v8_thread_pool_size); + V8::Initialize(); + performance::performance_v8_start = PERFORMANCE_NOW(); + per_process::v8_initialized = true; + return result; +} + +void TearDownOncePerProcess() { + per_process::v8_initialized = false; + V8::Dispose(); + + // uv_run cannot be called from the time before the beforeExit callback + // runs until the program exits unless the event loop has any referenced + // handles after beforeExit terminates. This prevents unrefed timers + // that happen to terminate during shutdown from being run unsafely. + // Since uv_run cannot be called, uv_async handles held by the platform + // will never be fully cleaned up. + per_process::v8_platform.Dispose(); +} + +int Start(int argc, char** argv) { + InitializationResult result = InitializeOncePerProcess(argc, argv); + if (result.early_return) { + return result.exit_code; + } + + { + Isolate::CreateParams params; + const std::vector* indexes = nullptr; + std::vector external_references; + + bool force_no_snapshot = + per_process::cli_options->per_isolate->no_node_snapshot; + if (!force_no_snapshot) { + v8::StartupData* blob = NodeMainInstance::GetEmbeddedSnapshotBlob(); + if (blob != nullptr) { + // TODO(joyeecheung): collect external references and set it in + // params.external_references. + external_references.push_back(reinterpret_cast(nullptr)); + params.external_references = external_references.data(); + params.snapshot_blob = blob; + indexes = NodeMainInstance::GetIsolateDataIndexes(); + } + } + + NodeMainInstance main_instance(¶ms, + uv_default_loop(), + per_process::v8_platform.Platform(), + result.args, + result.exec_args, + indexes); + result.exit_code = main_instance.Run(); + } + + TearDownOncePerProcess(); + return result.exit_code; +} + +int Stop(Environment* env) { + env->ExitEnv(); + return 0; +} + +} // namespace node + +#if !HAVE_INSPECTOR +void Initialize() {} + +NODE_MODULE_CONTEXT_AWARE_INTERNAL(inspector, Initialize) +#endif // !HAVE_INSPECTOR diff --git a/vendor/nexe/exe.js b/vendor/nexe/exe.js index 954e687f..b3fd4def 100644 --- a/vendor/nexe/exe.js +++ b/vendor/nexe/exe.js @@ -852,9 +852,10 @@ function _monkeyPatchMainCc(compiler, complete) { }); const finalContents = mainC.replace( - /(?