#ifdef NDEBUG #undef NDEBUG #endif #include #include "cppgc/platform.h" #include "executable_wrapper.h" #include "node.h" #include // Note: This file is being referred to from doc/api/embedding.md, and excerpts // from it are included in the documentation. Try to keep these in sync. // Snapshot support is not part of the embedder API docs yet due to its // experimental nature, although it is of course documented in node.h. using node::CommonEnvironmentSetup; using node::Environment; using node::MultiIsolatePlatform; using v8::Context; using v8::HandleScope; using v8::Isolate; using v8::Local; using v8::Locker; using v8::MaybeLocal; using v8::V8; using v8::Value; static int RunNodeInstance(MultiIsolatePlatform* platform, const std::vector& args, const std::vector& exec_args); static int RunSnapshotWithIsolateSettings( MultiIsolatePlatform* platform, const node::EmbedderSnapshotData* snapshot, const std::vector& args, const std::vector& exec_args); // --create-v8-startup-blob : a plain V8 startup blob (what V8's // mksnapshot produces), to test building the Node.js snapshot on top of one. static int CreateV8StartupBlob(MultiIsolatePlatform* platform, const std::string& path) { std::unique_ptr allocator( v8::ArrayBuffer::Allocator::NewDefaultAllocator()); v8::Isolate::CreateParams params; params.array_buffer_allocator = allocator.get(); uv_loop_t loop; assert(uv_loop_init(&loop) == 0); v8::Isolate* isolate = v8::Isolate::Allocate(); platform->RegisterIsolate(isolate, &loop); v8::StartupData blob; { v8::SnapshotCreator creator(isolate, params); { v8::HandleScope handle_scope(isolate); creator.SetDefaultContext(v8::Context::New(isolate)); } blob = creator.CreateBlob(v8::SnapshotCreator::FunctionCodeHandling::kClear); } bool platform_finished = false; platform->AddIsolateFinishedCallback( isolate, [](void* data) { bool* finished = static_cast(data); *finished = true; }, &platform_finished); platform->DisposeIsolate(isolate); while (!platform_finished) uv_run(&loop, UV_RUN_ONCE); uv_loop_close(&loop); assert(blob.data != nullptr); FILE* fp = fopen(path.c_str(), "wb"); assert(fp != nullptr); size_t written = fwrite(blob.data, blob.raw_size, 1, fp); assert(written == 1); fclose(fp); delete[] blob.data; return 0; } static std::vector base_blob_bytes; static v8::StartupData base_blob{nullptr, 0}; NODE_MAIN(int argc, node::argv_type raw_argv[]) { char** argv = nullptr; node::FixupMain(argc, raw_argv, &argv); std::vector args(argv, argv + argc); std::shared_ptr result = node::InitializeOncePerProcess( args, { node::ProcessInitializationFlags::kNoInitializeV8, node::ProcessInitializationFlags::kNoInitializeNodeV8Platform, // This is used to test NODE_REPL_EXTERNAL_MODULE is disabled with // kDisableNodeOptionsEnv. If other tests need NODE_OPTIONS // support in the future, split this configuration out as a // command line option. node::ProcessInitializationFlags::kDisableNodeOptionsEnv, node::ProcessInitializationFlags::kNoInitializeCppgc, }); for (const std::string& error : result->errors()) fprintf(stderr, "%s: %s\n", args[0].c_str(), error.c_str()); if (result->early_return() != 0) { return result->exit_code(); } std::unique_ptr platform = MultiIsolatePlatform::Create(4); V8::InitializePlatform(platform.get()); cppgc::InitializeProcess(platform->GetPageAllocator()); V8::Initialize(); int ret = RunNodeInstance(platform.get(), result->args(), result->exec_args()); V8::Dispose(); V8::DisposePlatform(); node::TearDownOncePerProcess(); return ret; } int RunNodeInstance(MultiIsolatePlatform* platform, const std::vector& args, const std::vector& exec_args) { int exit_code = 0; // Format of the arguments of this binary: // Building snapshot: // embedtest js_code_to_eval arg1 arg2... // --embedder-snapshot-blob blob-path // --embedder-snapshot-create // [--embedder-snapshot-as-file] // [--without-code-cache] // Running snapshot: // embedtest --embedder-snapshot-blob blob-path // [--embedder-snapshot-as-file] // [--embedder-isolate-settings] // arg1 arg2... // No snapshot: // embedtest arg1 arg2... node::EmbedderSnapshotData::Pointer snapshot; std::string binary_path = args[0]; std::vector filtered_args; bool is_building_snapshot = false; bool snapshot_as_file = false; bool with_isolate_settings = false; std::optional snapshot_config; std::string snapshot_blob_path; for (size_t i = 0; i < args.size(); ++i) { const std::string& arg = args[i]; if (arg == "--embedder-snapshot-create") { is_building_snapshot = true; } else if (arg == "--embedder-snapshot-as-file") { snapshot_as_file = true; } else if (arg == "--embedder-isolate-settings") { with_isolate_settings = true; } else if (arg == "--without-code-cache") { if (!snapshot_config.has_value()) { snapshot_config = node::SnapshotConfig{}; } snapshot_config.value().flags = static_cast( static_cast(snapshot_config.value().flags) | static_cast(node::SnapshotFlags::kWithoutCodeCache)); } else if (arg == "--embedder-snapshot-blob") { assert(i + 1 < args.size()); snapshot_blob_path = args[i + 1]; i++; } else if (arg == "--create-v8-startup-blob") { assert(i + 1 < args.size()); return CreateV8StartupBlob(platform, args[i + 1]); } else if (arg == "--embedder-snapshot-base-blob") { assert(i + 1 < args.size()); FILE* fp = fopen(args[i + 1].c_str(), "rb"); assert(fp != nullptr); fseek(fp, 0, SEEK_END); base_blob_bytes.resize(ftell(fp)); fseek(fp, 0, SEEK_SET); size_t read = fread(base_blob_bytes.data(), base_blob_bytes.size(), 1, fp); assert(read == 1); fclose(fp); base_blob = {base_blob_bytes.data(), static_cast(base_blob_bytes.size())}; if (!snapshot_config.has_value()) { snapshot_config = node::SnapshotConfig{}; } snapshot_config.value().base_blob = &base_blob; i++; } else { filtered_args.push_back(arg); } } if (!snapshot_blob_path.empty() && !is_building_snapshot) { FILE* fp = fopen(snapshot_blob_path.c_str(), "rb"); assert(fp != nullptr); if (snapshot_as_file) { snapshot = node::EmbedderSnapshotData::FromFile(fp); } else { uv_fs_t req = uv_fs_t(); int statret = uv_fs_stat(nullptr, &req, snapshot_blob_path.c_str(), nullptr); assert(statret == 0); size_t filesize = req.statbuf.st_size; uv_fs_req_cleanup(&req); std::vector vec(filesize); size_t read = fread(vec.data(), filesize, 1, fp); assert(read == 1); snapshot = node::EmbedderSnapshotData::FromBlob(vec); } assert(snapshot); int ret = fclose(fp); assert(ret == 0); } if (is_building_snapshot) { // It contains at least the binary path, the code to snapshot, // and --embedder-snapshot-create (which is filtered, so at least // 2 arguments should remain after filtering). assert(filtered_args.size() >= 2); // Insert an anonymous filename as process.argv[1]. filtered_args.insert(filtered_args.begin() + 1, node::GetAnonymousMainPath()); } if (snapshot && with_isolate_settings) { return RunSnapshotWithIsolateSettings( platform, snapshot.get(), filtered_args, exec_args); } std::vector errors; std::unique_ptr setup; if (snapshot) { setup = CommonEnvironmentSetup::CreateFromSnapshot( platform, &errors, snapshot.get(), filtered_args, exec_args); } else if (is_building_snapshot) { if (snapshot_config.has_value()) { setup = CommonEnvironmentSetup::CreateForSnapshotting( platform, &errors, filtered_args, exec_args, snapshot_config.value()); } else { setup = CommonEnvironmentSetup::CreateForSnapshotting( platform, &errors, filtered_args, exec_args); } } else { setup = CommonEnvironmentSetup::Create( platform, &errors, filtered_args, exec_args); } if (!setup) { for (const std::string& err : errors) fprintf(stderr, "%s: %s\n", binary_path.c_str(), err.c_str()); return 1; } Isolate* isolate = setup->isolate(); Environment* env = setup->env(); { Locker locker(isolate); Isolate::Scope isolate_scope(isolate); HandleScope handle_scope(isolate); Context::Scope context_scope(setup->context()); MaybeLocal loadenv_ret; if (snapshot) { // Deserializing snapshot loadenv_ret = node::LoadEnvironment(env, node::StartExecutionCallback{}); } else if (is_building_snapshot) { // Environment created for snapshotting must set process.argv[1] to // the name of the main script, which was inserted above. loadenv_ret = node::LoadEnvironment( env, "const assert = require('assert');" "assert(require('v8').startupSnapshot.isBuildingSnapshot());" "globalThis.embedVars = { nön_ascıı: '🏳️‍🌈' };" "globalThis.require = require;" "require('vm').runInThisContext(process.argv[2]);"); } else { loadenv_ret = node::LoadEnvironment( env, "const publicRequire = require('module').createRequire(process.cwd() " "+ '/');" "globalThis.require = publicRequire;" "globalThis.embedVars = { nön_ascıı: '🏳️‍🌈' };" "require('vm').runInThisContext(process.argv[1]);"); } if (loadenv_ret.IsEmpty()) // There has been a JS exception. return 1; exit_code = node::SpinEventLoop(env).FromMaybe(1); if (!snapshot_blob_path.empty() && is_building_snapshot) { snapshot = setup->CreateSnapshot(); assert(snapshot); FILE* fp = fopen(snapshot_blob_path.c_str(), "wb"); assert(fp != nullptr); if (snapshot_as_file) { snapshot->ToFile(fp); } else { const std::vector vec = snapshot->ToBlob(); size_t written = fwrite(vec.data(), vec.size(), 1, fp); assert(written == 1); } int ret = fclose(fp); assert(ret == 0); } } node::Stop(env); return exit_code; } // CommonEnvironmentSetup does not take IsolateSettings, so this goes through // NewIsolate()/CreateIsolateData()/CreateEnvironment() directly. static int RunSnapshotWithIsolateSettings( MultiIsolatePlatform* platform, const node::EmbedderSnapshotData* snapshot, const std::vector& args, const std::vector& exec_args) { uv_loop_t loop; int ret = uv_loop_init(&loop); assert(ret == 0); std::shared_ptr allocator = node::ArrayBufferAllocator::Create(); node::IsolateSettings settings; settings.prepare_stack_trace_callback = [](Local context, Local exception, Local trace) { return MaybeLocal(v8::String::NewFromUtf8Literal( v8::Isolate::GetCurrent(), "stack trace prepared by the embedder")); }; Isolate* isolate = node::NewIsolate(allocator, &loop, platform, snapshot, settings); assert(isolate != nullptr); int exit_code = 1; { Locker locker(isolate); Isolate::Scope isolate_scope(isolate); HandleScope handle_scope(isolate); std::unique_ptr isolate_data(node::CreateIsolateData( isolate, &loop, platform, allocator.get(), snapshot), node::FreeIsolateData); std::unique_ptr env( node::CreateEnvironment( isolate_data.get(), Local(), args, exec_args), node::FreeEnvironment); assert(env); Context::Scope context_scope(node::GetMainContext(env.get())); if (!node::LoadEnvironment(env.get(), node::StartExecutionCallback{}) .IsEmpty()) { exit_code = node::SpinEventLoop(env.get()).FromMaybe(1); } node::Stop(env.get()); } bool platform_finished = false; platform->AddIsolateFinishedCallback( isolate, [](void* data) { bool* finished = static_cast(data); *finished = true; }, &platform_finished); platform->DisposeIsolate(isolate); while (!platform_finished) uv_run(&loop, UV_RUN_ONCE); ret = uv_loop_close(&loop); assert(ret == 0); return exit_code; }