mirror of
https://github.com/nodejs/node.git
synced 2026-11-09 15:11:11 -05:00
When both useCodeCache and useSnapshot are set, we generate the snapshot and skip the generation of the code cache since the snapshot already includes the code cache. But we previously still persist the code cache setting in the flags that got serialized into the SEA, so the resulting executable would still try to read the code cache even if it's not added to the SEA, leading to a flaky crash caused by OOB on some platforms. This patch fixes the crash by ignoring the code cache setting when generating the flag if both snapshot and code cache is configured. PR-URL: https://github.com/nodejs/node/pull/54120 Fixes: https://github.com/nodejs/node/issues/50740 Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: Richard Lau <rlau@redhat.com>
667 lines
22 KiB
C++
667 lines
22 KiB
C++
#include "node_sea.h"
|
|
|
|
#include "blob_serializer_deserializer-inl.h"
|
|
#include "debug_utils-inl.h"
|
|
#include "env-inl.h"
|
|
#include "json_parser.h"
|
|
#include "node_contextify.h"
|
|
#include "node_errors.h"
|
|
#include "node_external_reference.h"
|
|
#include "node_internals.h"
|
|
#include "node_snapshot_builder.h"
|
|
#include "node_union_bytes.h"
|
|
#include "node_v8_platform-inl.h"
|
|
#include "util-inl.h"
|
|
|
|
// The POSTJECT_SENTINEL_FUSE macro is a string of random characters selected by
|
|
// the Node.js project that is present only once in the entire binary. It is
|
|
// used by the postject_has_resource() function to efficiently detect if a
|
|
// resource has been injected. See
|
|
// https://github.com/nodejs/postject/blob/35343439cac8c488f2596d7c4c1dddfec1fddcae/postject-api.h#L42-L45.
|
|
#define POSTJECT_SENTINEL_FUSE "NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2"
|
|
#include "postject-api.h"
|
|
#undef POSTJECT_SENTINEL_FUSE
|
|
|
|
#include <memory>
|
|
#include <string_view>
|
|
#include <tuple>
|
|
#include <vector>
|
|
|
|
using node::ExitCode;
|
|
using v8::ArrayBuffer;
|
|
using v8::BackingStore;
|
|
using v8::Context;
|
|
using v8::Function;
|
|
using v8::FunctionCallbackInfo;
|
|
using v8::HandleScope;
|
|
using v8::Isolate;
|
|
using v8::Local;
|
|
using v8::MaybeLocal;
|
|
using v8::NewStringType;
|
|
using v8::Object;
|
|
using v8::ScriptCompiler;
|
|
using v8::String;
|
|
using v8::Value;
|
|
|
|
namespace node {
|
|
namespace sea {
|
|
|
|
namespace {
|
|
|
|
SeaFlags operator|(SeaFlags x, SeaFlags y) {
|
|
return static_cast<SeaFlags>(static_cast<uint32_t>(x) |
|
|
static_cast<uint32_t>(y));
|
|
}
|
|
|
|
SeaFlags operator&(SeaFlags x, SeaFlags y) {
|
|
return static_cast<SeaFlags>(static_cast<uint32_t>(x) &
|
|
static_cast<uint32_t>(y));
|
|
}
|
|
|
|
SeaFlags operator|=(/* NOLINT (runtime/references) */ SeaFlags& x, SeaFlags y) {
|
|
return x = x | y;
|
|
}
|
|
|
|
class SeaSerializer : public BlobSerializer<SeaSerializer> {
|
|
public:
|
|
SeaSerializer()
|
|
: BlobSerializer<SeaSerializer>(
|
|
per_process::enabled_debug_list.enabled(DebugCategory::SEA)) {}
|
|
|
|
template <typename T,
|
|
std::enable_if_t<!std::is_same<T, std::string>::value>* = nullptr,
|
|
std::enable_if_t<!std::is_arithmetic<T>::value>* = nullptr>
|
|
size_t Write(const T& data);
|
|
};
|
|
|
|
template <>
|
|
size_t SeaSerializer::Write(const SeaResource& sea) {
|
|
sink.reserve(SeaResource::kHeaderSize + sea.main_code_or_snapshot.size());
|
|
|
|
Debug("Write SEA magic %x\n", kMagic);
|
|
size_t written_total = WriteArithmetic<uint32_t>(kMagic);
|
|
|
|
uint32_t flags = static_cast<uint32_t>(sea.flags);
|
|
Debug("Write SEA flags %x\n", flags);
|
|
written_total += WriteArithmetic<uint32_t>(flags);
|
|
DCHECK_EQ(written_total, SeaResource::kHeaderSize);
|
|
|
|
Debug("Write SEA code path %p, size=%zu\n",
|
|
sea.code_path.data(),
|
|
sea.code_path.size());
|
|
written_total +=
|
|
WriteStringView(sea.code_path, StringLogMode::kAddressAndContent);
|
|
|
|
Debug("Write SEA resource %s %p, size=%zu\n",
|
|
sea.use_snapshot() ? "snapshot" : "code",
|
|
sea.main_code_or_snapshot.data(),
|
|
sea.main_code_or_snapshot.size());
|
|
written_total +=
|
|
WriteStringView(sea.main_code_or_snapshot,
|
|
sea.use_snapshot() ? StringLogMode::kAddressOnly
|
|
: StringLogMode::kAddressAndContent);
|
|
|
|
if (sea.code_cache.has_value()) {
|
|
Debug("Write SEA resource code cache %p, size=%zu\n",
|
|
sea.code_cache->data(),
|
|
sea.code_cache->size());
|
|
written_total +=
|
|
WriteStringView(sea.code_cache.value(), StringLogMode::kAddressOnly);
|
|
}
|
|
|
|
if (!sea.assets.empty()) {
|
|
Debug("Write SEA resource assets size %zu\n", sea.assets.size());
|
|
written_total += WriteArithmetic<size_t>(sea.assets.size());
|
|
for (auto const& [key, content] : sea.assets) {
|
|
Debug("Write SEA resource asset %s at %p, size=%zu\n",
|
|
key,
|
|
content.data(),
|
|
content.size());
|
|
written_total += WriteStringView(key, StringLogMode::kAddressAndContent);
|
|
written_total += WriteStringView(content, StringLogMode::kAddressOnly);
|
|
}
|
|
}
|
|
return written_total;
|
|
}
|
|
|
|
class SeaDeserializer : public BlobDeserializer<SeaDeserializer> {
|
|
public:
|
|
explicit SeaDeserializer(std::string_view v)
|
|
: BlobDeserializer<SeaDeserializer>(
|
|
per_process::enabled_debug_list.enabled(DebugCategory::SEA), v) {}
|
|
|
|
template <typename T,
|
|
std::enable_if_t<!std::is_same<T, std::string>::value>* = nullptr,
|
|
std::enable_if_t<!std::is_arithmetic<T>::value>* = nullptr>
|
|
T Read();
|
|
};
|
|
|
|
template <>
|
|
SeaResource SeaDeserializer::Read() {
|
|
uint32_t magic = ReadArithmetic<uint32_t>();
|
|
Debug("Read SEA magic %x\n", magic);
|
|
|
|
CHECK_EQ(magic, kMagic);
|
|
SeaFlags flags(static_cast<SeaFlags>(ReadArithmetic<uint32_t>()));
|
|
Debug("Read SEA flags %x\n", static_cast<uint32_t>(flags));
|
|
CHECK_EQ(read_total, SeaResource::kHeaderSize);
|
|
|
|
std::string_view code_path =
|
|
ReadStringView(StringLogMode::kAddressAndContent);
|
|
Debug(
|
|
"Read SEA code path %p, size=%zu\n", code_path.data(), code_path.size());
|
|
|
|
bool use_snapshot = static_cast<bool>(flags & SeaFlags::kUseSnapshot);
|
|
std::string_view code =
|
|
ReadStringView(use_snapshot ? StringLogMode::kAddressOnly
|
|
: StringLogMode::kAddressAndContent);
|
|
|
|
Debug("Read SEA resource %s %p, size=%zu\n",
|
|
use_snapshot ? "snapshot" : "code",
|
|
code.data(),
|
|
code.size());
|
|
|
|
std::string_view code_cache;
|
|
if (static_cast<bool>(flags & SeaFlags::kUseCodeCache)) {
|
|
code_cache = ReadStringView(StringLogMode::kAddressOnly);
|
|
Debug("Read SEA resource code cache %p, size=%zu\n",
|
|
code_cache.data(),
|
|
code_cache.size());
|
|
}
|
|
|
|
std::unordered_map<std::string_view, std::string_view> assets;
|
|
if (static_cast<bool>(flags & SeaFlags::kIncludeAssets)) {
|
|
size_t assets_size = ReadArithmetic<size_t>();
|
|
Debug("Read SEA resource assets size %zu\n", assets_size);
|
|
for (size_t i = 0; i < assets_size; ++i) {
|
|
std::string_view key = ReadStringView(StringLogMode::kAddressAndContent);
|
|
std::string_view content = ReadStringView(StringLogMode::kAddressOnly);
|
|
Debug("Read SEA resource asset %s at %p, size=%zu\n",
|
|
key,
|
|
content.data(),
|
|
content.size());
|
|
assets.emplace(key, content);
|
|
}
|
|
}
|
|
return {flags, code_path, code, code_cache, assets};
|
|
}
|
|
|
|
std::string_view FindSingleExecutableBlob() {
|
|
#if !defined(DISABLE_SINGLE_EXECUTABLE_APPLICATION)
|
|
CHECK(IsSingleExecutable());
|
|
static const std::string_view result = []() -> std::string_view {
|
|
size_t size;
|
|
#ifdef __APPLE__
|
|
postject_options options;
|
|
postject_options_init(&options);
|
|
options.macho_segment_name = "NODE_SEA";
|
|
const char* blob = static_cast<const char*>(
|
|
postject_find_resource("NODE_SEA_BLOB", &size, &options));
|
|
#else
|
|
const char* blob = static_cast<const char*>(
|
|
postject_find_resource("NODE_SEA_BLOB", &size, nullptr));
|
|
#endif
|
|
return {blob, size};
|
|
}();
|
|
per_process::Debug(DebugCategory::SEA,
|
|
"Found SEA blob %p, size=%zu\n",
|
|
result.data(),
|
|
result.size());
|
|
return result;
|
|
#else
|
|
UNREACHABLE();
|
|
#endif // !defined(DISABLE_SINGLE_EXECUTABLE_APPLICATION)
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
bool SeaResource::use_snapshot() const {
|
|
return static_cast<bool>(flags & SeaFlags::kUseSnapshot);
|
|
}
|
|
|
|
bool SeaResource::use_code_cache() const {
|
|
return static_cast<bool>(flags & SeaFlags::kUseCodeCache);
|
|
}
|
|
|
|
SeaResource FindSingleExecutableResource() {
|
|
static const SeaResource sea_resource = []() -> SeaResource {
|
|
std::string_view blob = FindSingleExecutableBlob();
|
|
per_process::Debug(DebugCategory::SEA,
|
|
"Found SEA resource %p, size=%zu\n",
|
|
blob.data(),
|
|
blob.size());
|
|
SeaDeserializer deserializer(blob);
|
|
return deserializer.Read<SeaResource>();
|
|
}();
|
|
return sea_resource;
|
|
}
|
|
|
|
bool IsSingleExecutable() {
|
|
return postject_has_resource();
|
|
}
|
|
|
|
void IsSea(const FunctionCallbackInfo<Value>& args) {
|
|
args.GetReturnValue().Set(IsSingleExecutable());
|
|
}
|
|
|
|
void IsExperimentalSeaWarningNeeded(const FunctionCallbackInfo<Value>& args) {
|
|
bool is_building_sea =
|
|
!per_process::cli_options->experimental_sea_config.empty();
|
|
if (is_building_sea) {
|
|
args.GetReturnValue().Set(true);
|
|
return;
|
|
}
|
|
|
|
if (!IsSingleExecutable()) {
|
|
args.GetReturnValue().Set(false);
|
|
return;
|
|
}
|
|
|
|
SeaResource sea_resource = FindSingleExecutableResource();
|
|
args.GetReturnValue().Set(!static_cast<bool>(
|
|
sea_resource.flags & SeaFlags::kDisableExperimentalSeaWarning));
|
|
}
|
|
|
|
std::tuple<int, char**> FixupArgsForSEA(int argc, char** argv) {
|
|
// Repeats argv[0] at position 1 on argv as a replacement for the missing
|
|
// entry point file path.
|
|
if (IsSingleExecutable()) {
|
|
static std::vector<char*> new_argv;
|
|
new_argv.reserve(argc + 2);
|
|
new_argv.emplace_back(argv[0]);
|
|
new_argv.insert(new_argv.end(), argv, argv + argc);
|
|
new_argv.emplace_back(nullptr);
|
|
argc = new_argv.size() - 1;
|
|
argv = new_argv.data();
|
|
}
|
|
|
|
return {argc, argv};
|
|
}
|
|
|
|
namespace {
|
|
|
|
struct SeaConfig {
|
|
std::string main_path;
|
|
std::string output_path;
|
|
SeaFlags flags = SeaFlags::kDefault;
|
|
std::unordered_map<std::string, std::string> assets;
|
|
};
|
|
|
|
std::optional<SeaConfig> ParseSingleExecutableConfig(
|
|
const std::string& config_path) {
|
|
std::string config;
|
|
int r = ReadFileSync(&config, config_path.c_str());
|
|
if (r != 0) {
|
|
const char* err = uv_strerror(r);
|
|
FPrintF(stderr,
|
|
"Cannot read single executable configuration from %s: %s\n",
|
|
config_path,
|
|
err);
|
|
return std::nullopt;
|
|
}
|
|
|
|
SeaConfig result;
|
|
JSONParser parser;
|
|
if (!parser.Parse(config)) {
|
|
FPrintF(stderr, "Cannot parse JSON from %s\n", config_path);
|
|
return std::nullopt;
|
|
}
|
|
|
|
result.main_path =
|
|
parser.GetTopLevelStringField("main").value_or(std::string());
|
|
if (result.main_path.empty()) {
|
|
FPrintF(stderr,
|
|
"\"main\" field of %s is not a non-empty string\n",
|
|
config_path);
|
|
return std::nullopt;
|
|
}
|
|
|
|
result.output_path =
|
|
parser.GetTopLevelStringField("output").value_or(std::string());
|
|
if (result.output_path.empty()) {
|
|
FPrintF(stderr,
|
|
"\"output\" field of %s is not a non-empty string\n",
|
|
config_path);
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<bool> disable_experimental_sea_warning =
|
|
parser.GetTopLevelBoolField("disableExperimentalSEAWarning");
|
|
if (!disable_experimental_sea_warning.has_value()) {
|
|
FPrintF(stderr,
|
|
"\"disableExperimentalSEAWarning\" field of %s is not a Boolean\n",
|
|
config_path);
|
|
return std::nullopt;
|
|
}
|
|
if (disable_experimental_sea_warning.value()) {
|
|
result.flags |= SeaFlags::kDisableExperimentalSeaWarning;
|
|
}
|
|
|
|
std::optional<bool> use_snapshot = parser.GetTopLevelBoolField("useSnapshot");
|
|
if (!use_snapshot.has_value()) {
|
|
FPrintF(
|
|
stderr, "\"useSnapshot\" field of %s is not a Boolean\n", config_path);
|
|
return std::nullopt;
|
|
}
|
|
if (use_snapshot.value()) {
|
|
result.flags |= SeaFlags::kUseSnapshot;
|
|
}
|
|
|
|
std::optional<bool> use_code_cache =
|
|
parser.GetTopLevelBoolField("useCodeCache");
|
|
if (!use_code_cache.has_value()) {
|
|
FPrintF(
|
|
stderr, "\"useCodeCache\" field of %s is not a Boolean\n", config_path);
|
|
return std::nullopt;
|
|
}
|
|
if (use_code_cache.value()) {
|
|
if (use_snapshot.value()) {
|
|
// TODO(joyeecheung): code cache in snapshot should be configured by
|
|
// separate snapshot configurations.
|
|
FPrintF(stderr,
|
|
"\"useCodeCache\" is redundant when \"useSnapshot\" is true\n");
|
|
} else {
|
|
result.flags |= SeaFlags::kUseCodeCache;
|
|
}
|
|
}
|
|
|
|
auto assets_opt = parser.GetTopLevelStringDict("assets");
|
|
if (!assets_opt.has_value()) {
|
|
FPrintF(stderr,
|
|
"\"assets\" field of %s is not a map of strings\n",
|
|
config_path);
|
|
return std::nullopt;
|
|
} else if (!assets_opt.value().empty()) {
|
|
result.flags |= SeaFlags::kIncludeAssets;
|
|
result.assets = std::move(assets_opt.value());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
ExitCode GenerateSnapshotForSEA(const SeaConfig& config,
|
|
const std::vector<std::string>& args,
|
|
const std::vector<std::string>& exec_args,
|
|
const std::string& builder_script_content,
|
|
const SnapshotConfig& snapshot_config,
|
|
std::vector<char>* snapshot_blob) {
|
|
SnapshotData snapshot;
|
|
// TODO(joyeecheung): make the arguments configurable through the JSON
|
|
// config or a programmatic API.
|
|
std::vector<std::string> patched_args = {args[0], config.main_path};
|
|
ExitCode exit_code = SnapshotBuilder::Generate(&snapshot,
|
|
patched_args,
|
|
exec_args,
|
|
builder_script_content,
|
|
snapshot_config);
|
|
if (exit_code != ExitCode::kNoFailure) {
|
|
return exit_code;
|
|
}
|
|
auto& persistents = snapshot.env_info.principal_realm.persistent_values;
|
|
auto it = std::find_if(
|
|
persistents.begin(), persistents.end(), [](const PropInfo& prop) {
|
|
return prop.name == "snapshot_deserialize_main";
|
|
});
|
|
if (it == persistents.end()) {
|
|
FPrintF(
|
|
stderr,
|
|
"%s does not invoke "
|
|
"v8.startupSnapshot.setDeserializeMainFunction(), which is required "
|
|
"for snapshot scripts used to build single executable applications."
|
|
"\n",
|
|
config.main_path);
|
|
return ExitCode::kGenericUserError;
|
|
}
|
|
// We need the temporary variable for copy elision.
|
|
std::vector<char> temp = snapshot.ToBlob();
|
|
*snapshot_blob = std::move(temp);
|
|
return ExitCode::kNoFailure;
|
|
}
|
|
|
|
std::optional<std::string> GenerateCodeCache(std::string_view main_path,
|
|
std::string_view main_script) {
|
|
RAIIIsolate raii_isolate(SnapshotBuilder::GetEmbeddedSnapshotData());
|
|
Isolate* isolate = raii_isolate.get();
|
|
|
|
v8::Isolate::Scope isolate_scope(isolate);
|
|
HandleScope handle_scope(isolate);
|
|
|
|
Local<Context> context = Context::New(isolate);
|
|
Context::Scope context_scope(context);
|
|
|
|
errors::PrinterTryCatch bootstrapCatch(
|
|
isolate, errors::PrinterTryCatch::kPrintSourceLine);
|
|
|
|
Local<String> filename;
|
|
if (!String::NewFromUtf8(isolate,
|
|
main_path.data(),
|
|
NewStringType::kNormal,
|
|
main_path.length())
|
|
.ToLocal(&filename)) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
Local<String> content;
|
|
if (!String::NewFromUtf8(isolate,
|
|
main_script.data(),
|
|
NewStringType::kNormal,
|
|
main_script.length())
|
|
.ToLocal(&content)) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::vector<Local<String>> parameters = {
|
|
FIXED_ONE_BYTE_STRING(isolate, "exports"),
|
|
FIXED_ONE_BYTE_STRING(isolate, "require"),
|
|
FIXED_ONE_BYTE_STRING(isolate, "module"),
|
|
FIXED_ONE_BYTE_STRING(isolate, "__filename"),
|
|
FIXED_ONE_BYTE_STRING(isolate, "__dirname"),
|
|
};
|
|
|
|
// TODO(RaisinTen): Using the V8 code cache prevents us from using `import()`
|
|
// in the SEA code. Support it.
|
|
// Refs: https://github.com/nodejs/node/pull/48191#discussion_r1213271430
|
|
Local<Function> fn;
|
|
if (!contextify::CompileFunction(context, filename, content, ¶meters)
|
|
.ToLocal(&fn)) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::unique_ptr<ScriptCompiler::CachedData> cache{
|
|
ScriptCompiler::CreateCodeCacheForFunction(fn)};
|
|
std::string code_cache(cache->data, cache->data + cache->length);
|
|
return code_cache;
|
|
}
|
|
|
|
int BuildAssets(const std::unordered_map<std::string, std::string>& config,
|
|
std::unordered_map<std::string, std::string>* assets) {
|
|
for (auto const& [key, path] : config) {
|
|
std::string blob;
|
|
int r = ReadFileSync(&blob, path.c_str());
|
|
if (r != 0) {
|
|
const char* err = uv_strerror(r);
|
|
FPrintF(stderr, "Cannot read asset %s: %s\n", path.c_str(), err);
|
|
return r;
|
|
}
|
|
assets->emplace(key, std::move(blob));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
ExitCode GenerateSingleExecutableBlob(
|
|
const SeaConfig& config,
|
|
const std::vector<std::string>& args,
|
|
const std::vector<std::string>& exec_args) {
|
|
std::string main_script;
|
|
// TODO(joyeecheung): unify the file utils.
|
|
int r = ReadFileSync(&main_script, config.main_path.c_str());
|
|
if (r != 0) {
|
|
const char* err = uv_strerror(r);
|
|
FPrintF(stderr, "Cannot read main script %s:%s\n", config.main_path, err);
|
|
return ExitCode::kGenericUserError;
|
|
}
|
|
|
|
std::vector<char> snapshot_blob;
|
|
bool builds_snapshot_from_main =
|
|
static_cast<bool>(config.flags & SeaFlags::kUseSnapshot);
|
|
if (builds_snapshot_from_main) {
|
|
// TODO(joyeecheung): allow passing snapshot configuration in SEA configs.
|
|
SnapshotConfig snapshot_config;
|
|
snapshot_config.builder_script_path = main_script;
|
|
ExitCode exit_code = GenerateSnapshotForSEA(
|
|
config, args, exec_args, main_script, snapshot_config, &snapshot_blob);
|
|
if (exit_code != ExitCode::kNoFailure) {
|
|
return exit_code;
|
|
}
|
|
}
|
|
|
|
std::optional<std::string_view> optional_sv_code_cache;
|
|
std::string code_cache;
|
|
if (static_cast<bool>(config.flags & SeaFlags::kUseCodeCache)) {
|
|
std::optional<std::string> optional_code_cache =
|
|
GenerateCodeCache(config.main_path, main_script);
|
|
if (!optional_code_cache.has_value()) {
|
|
FPrintF(stderr, "Cannot generate V8 code cache\n");
|
|
return ExitCode::kGenericUserError;
|
|
}
|
|
code_cache = optional_code_cache.value();
|
|
optional_sv_code_cache = code_cache;
|
|
}
|
|
|
|
std::unordered_map<std::string, std::string> assets;
|
|
if (!config.assets.empty() && BuildAssets(config.assets, &assets) != 0) {
|
|
return ExitCode::kGenericUserError;
|
|
}
|
|
std::unordered_map<std::string_view, std::string_view> assets_view;
|
|
for (auto const& [key, content] : assets) {
|
|
assets_view.emplace(key, content);
|
|
}
|
|
SeaResource sea{
|
|
config.flags,
|
|
config.main_path,
|
|
builds_snapshot_from_main
|
|
? std::string_view{snapshot_blob.data(), snapshot_blob.size()}
|
|
: std::string_view{main_script.data(), main_script.size()},
|
|
optional_sv_code_cache,
|
|
assets_view};
|
|
|
|
SeaSerializer serializer;
|
|
serializer.Write(sea);
|
|
|
|
uv_buf_t buf = uv_buf_init(serializer.sink.data(), serializer.sink.size());
|
|
r = WriteFileSync(config.output_path.c_str(), buf);
|
|
if (r != 0) {
|
|
const char* err = uv_strerror(r);
|
|
FPrintF(stderr, "Cannot write output to %s:%s\n", config.output_path, err);
|
|
return ExitCode::kGenericUserError;
|
|
}
|
|
|
|
FPrintF(stderr,
|
|
"Wrote single executable preparation blob to %s\n",
|
|
config.output_path);
|
|
return ExitCode::kNoFailure;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
ExitCode BuildSingleExecutableBlob(const std::string& config_path,
|
|
const std::vector<std::string>& args,
|
|
const std::vector<std::string>& exec_args) {
|
|
std::optional<SeaConfig> config_opt =
|
|
ParseSingleExecutableConfig(config_path);
|
|
if (config_opt.has_value()) {
|
|
ExitCode code =
|
|
GenerateSingleExecutableBlob(config_opt.value(), args, exec_args);
|
|
return code;
|
|
}
|
|
|
|
return ExitCode::kGenericUserError;
|
|
}
|
|
|
|
void GetAsset(const FunctionCallbackInfo<Value>& args) {
|
|
CHECK_EQ(args.Length(), 1);
|
|
CHECK(args[0]->IsString());
|
|
Utf8Value key(args.GetIsolate(), args[0]);
|
|
SeaResource sea_resource = FindSingleExecutableResource();
|
|
if (sea_resource.assets.empty()) {
|
|
return;
|
|
}
|
|
auto it = sea_resource.assets.find(*key);
|
|
if (it == sea_resource.assets.end()) {
|
|
return;
|
|
}
|
|
// We cast away the constness here, the JS land should ensure that
|
|
// the data is not mutated.
|
|
std::unique_ptr<v8::BackingStore> store = ArrayBuffer::NewBackingStore(
|
|
const_cast<char*>(it->second.data()),
|
|
it->second.size(),
|
|
[](void*, size_t, void*) {},
|
|
nullptr);
|
|
Local<ArrayBuffer> ab = ArrayBuffer::New(args.GetIsolate(), std::move(store));
|
|
args.GetReturnValue().Set(ab);
|
|
}
|
|
|
|
MaybeLocal<Value> LoadSingleExecutableApplication(
|
|
const StartExecutionCallbackInfo& info) {
|
|
// Here we are currently relying on the fact that in NodeMainInstance::Run(),
|
|
// env->context() is entered.
|
|
Local<Context> context = Isolate::GetCurrent()->GetCurrentContext();
|
|
Environment* env = Environment::GetCurrent(context);
|
|
SeaResource sea = FindSingleExecutableResource();
|
|
|
|
CHECK(!sea.use_snapshot());
|
|
// TODO(joyeecheung): this should be an external string. Refactor UnionBytes
|
|
// and make it easy to create one based on static content on the fly.
|
|
Local<Value> main_script =
|
|
ToV8Value(env->context(), sea.main_code_or_snapshot).ToLocalChecked();
|
|
return info.run_cjs->Call(
|
|
env->context(), Null(env->isolate()), 1, &main_script);
|
|
}
|
|
|
|
bool MaybeLoadSingleExecutableApplication(Environment* env) {
|
|
#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION
|
|
if (!IsSingleExecutable()) {
|
|
return false;
|
|
}
|
|
|
|
SeaResource sea = FindSingleExecutableResource();
|
|
|
|
if (sea.use_snapshot()) {
|
|
// The SEA preparation blob building process should already enforce this,
|
|
// this check is just here to guard against the unlikely case where
|
|
// the SEA preparation blob has been manually modified by someone.
|
|
CHECK(!env->snapshot_deserialize_main().IsEmpty());
|
|
LoadEnvironment(env, StartExecutionCallback{});
|
|
return true;
|
|
}
|
|
|
|
LoadEnvironment(env, LoadSingleExecutableApplication);
|
|
return true;
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
void Initialize(Local<Object> target,
|
|
Local<Value> unused,
|
|
Local<Context> context,
|
|
void* priv) {
|
|
SetMethod(context, target, "isSea", IsSea);
|
|
SetMethod(context,
|
|
target,
|
|
"isExperimentalSeaWarningNeeded",
|
|
IsExperimentalSeaWarningNeeded);
|
|
SetMethod(context, target, "getAsset", GetAsset);
|
|
}
|
|
|
|
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
|
|
registry->Register(IsSea);
|
|
registry->Register(IsExperimentalSeaWarningNeeded);
|
|
registry->Register(GetAsset);
|
|
}
|
|
|
|
} // namespace sea
|
|
} // namespace node
|
|
|
|
NODE_BINDING_CONTEXT_AWARE_INTERNAL(sea, node::sea::Initialize)
|
|
NODE_BINDING_EXTERNAL_REFERENCE(sea, node::sea::RegisterExternalReferences)
|