mirror of
https://github.com/nodejs/node.git
synced 2026-11-09 15:11:11 -05:00
`NewIsolate()` creates every isolate from the snapshot blob the first isolate in the process used, because V8 shares the read-only heap between isolates, and did so by keeping a pointer to the first `CreateParams`. When that blob came from an `EmbedderSnapshotData` the embedder had since released, e.g. a second `CommonEnvironmentSetup::CreateFromSnapshot()` after the first setup and its snapshot were destroyed, V8 deserialized freed memory. Record the first blob and external references under a mutex instead of copying the caller's `CreateParams`, and make `~SnapshotData()` leave that one blob allocated, since its owner can go away before the last isolate is created. Nothing is copied and `node` itself is unaffected. embedtest grows an `--embedder-run-twice` switch so the sequence can be tested. Refs: https://github.com/nodejs/node/pull/45885 Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com> PR-URL: https://github.com/nodejs/node/pull/65779 Refs: https://github.com/nodejs/node/pull/32984 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
399 lines
14 KiB
C++
399 lines
14 KiB
C++
#ifdef NDEBUG
|
|
#undef NDEBUG
|
|
#endif
|
|
#include <assert.h>
|
|
#include "cppgc/platform.h"
|
|
#include "executable_wrapper.h"
|
|
#include "node.h"
|
|
|
|
#include <algorithm>
|
|
|
|
// 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<std::string>& args,
|
|
const std::vector<std::string>& exec_args);
|
|
static int RunSnapshotWithIsolateSettings(
|
|
MultiIsolatePlatform* platform,
|
|
const node::EmbedderSnapshotData* snapshot,
|
|
const std::vector<std::string>& args,
|
|
const std::vector<std::string>& exec_args);
|
|
|
|
// --create-v8-startup-blob <file>: 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<v8::ArrayBuffer::Allocator> 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<bool*>(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<char> 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<std::string> args(argv, argv + argc);
|
|
uint32_t flags =
|
|
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;
|
|
auto it =
|
|
std::find(args.begin(), args.end(), "--no-harvest-builtin-code-cache");
|
|
if (it != args.end()) {
|
|
args.erase(it);
|
|
flags |= node::ProcessInitializationFlags::kNoHarvestBuiltinCodeCache;
|
|
}
|
|
std::shared_ptr<node::InitializationResult> result =
|
|
node::InitializeOncePerProcess(
|
|
args, static_cast<node::ProcessInitializationFlags::Flags>(flags));
|
|
|
|
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<MultiIsolatePlatform> platform =
|
|
MultiIsolatePlatform::Create(4);
|
|
V8::InitializePlatform(platform.get());
|
|
cppgc::InitializeProcess(platform->GetPageAllocator());
|
|
V8::Initialize();
|
|
|
|
// --embedder-run-twice: two sequential instances in one process, each
|
|
// loading (and freeing) its own copy of the snapshot.
|
|
std::vector<std::string> instance_args = result->args();
|
|
auto twice = std::find(
|
|
instance_args.begin(), instance_args.end(), "--embedder-run-twice");
|
|
int runs = 1;
|
|
if (twice != instance_args.end()) {
|
|
instance_args.erase(twice);
|
|
runs = 2;
|
|
}
|
|
int ret = 0;
|
|
for (int i = 0; i < runs && ret == 0; i++) {
|
|
ret = RunNodeInstance(platform.get(), instance_args, result->exec_args());
|
|
}
|
|
|
|
V8::Dispose();
|
|
V8::DisposePlatform();
|
|
|
|
node::TearDownOncePerProcess();
|
|
return ret;
|
|
}
|
|
|
|
int RunNodeInstance(MultiIsolatePlatform* platform,
|
|
const std::vector<std::string>& args,
|
|
const std::vector<std::string>& 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<std::string> filtered_args;
|
|
bool is_building_snapshot = false;
|
|
bool snapshot_as_file = false;
|
|
bool with_isolate_settings = false;
|
|
std::optional<node::SnapshotConfig> 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<node::SnapshotFlags>(
|
|
static_cast<uint32_t>(snapshot_config.value().flags) |
|
|
static_cast<uint32_t>(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<int>(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<char> 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<std::string> errors;
|
|
std::unique_ptr<CommonEnvironmentSetup> 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<Value> 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<char> 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<std::string>& args,
|
|
const std::vector<std::string>& exec_args) {
|
|
uv_loop_t loop;
|
|
int ret = uv_loop_init(&loop);
|
|
assert(ret == 0);
|
|
|
|
std::shared_ptr<node::ArrayBufferAllocator> allocator =
|
|
node::ArrayBufferAllocator::Create();
|
|
node::IsolateSettings settings;
|
|
settings.prepare_stack_trace_callback = [](Local<Context> context,
|
|
Local<Value> exception,
|
|
Local<v8::Array> trace) {
|
|
return MaybeLocal<Value>(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<node::IsolateData, decltype(&node::FreeIsolateData)>
|
|
isolate_data(node::CreateIsolateData(
|
|
isolate, &loop, platform, allocator.get(), snapshot),
|
|
node::FreeIsolateData);
|
|
std::unique_ptr<Environment, decltype(&node::FreeEnvironment)> env(
|
|
node::CreateEnvironment(
|
|
isolate_data.get(), Local<Context>(), 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<bool*>(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;
|
|
}
|