%PDF- %PDF-
Direktori : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/src/ |
Current File : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/src/node_sea.cc |
#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> #if !defined(DISABLE_SINGLE_EXECUTABLE_APPLICATION) using node::ExitCode; using v8::ArrayBuffer; using v8::BackingStore; using v8::Context; using v8::DataView; using v8::Function; using v8::FunctionCallbackInfo; using v8::HandleScope; using v8::Isolate; using v8::Local; 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() { 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; } } // anonymous namespace bool SeaResource::use_snapshot() const { return static_cast<bool>(flags & SeaFlags::kUseSnapshot); } 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)); } void GetCodeCache(const FunctionCallbackInfo<Value>& args) { if (!IsSingleExecutable()) { return; } Isolate* isolate = args.GetIsolate(); SeaResource sea_resource = FindSingleExecutableResource(); if (!static_cast<bool>(sea_resource.flags & SeaFlags::kUseCodeCache)) { return; } std::shared_ptr<BackingStore> backing_store = ArrayBuffer::NewBackingStore( const_cast<void*>( static_cast<const void*>(sea_resource.code_cache->data())), sea_resource.code_cache->length(), [](void* /* data */, size_t /* length */, void* /* deleter_data */) { // The code cache data blob is not freed here because it is a static // blob which is not allocated by the BackingStore allocator. }, nullptr); Local<ArrayBuffer> array_buffer = ArrayBuffer::New(isolate, backing_store); Local<DataView> data_view = DataView::New(array_buffer, 0, array_buffer->ByteLength()); args.GetReturnValue().Set(data_view); } void GetCodePath(const FunctionCallbackInfo<Value>& args) { DCHECK(IsSingleExecutable()); Isolate* isolate = args.GetIsolate(); SeaResource sea_resource = FindSingleExecutableResource(); Local<String> code_path; if (!String::NewFromUtf8(isolate, sea_resource.code_path.data(), NewStringType::kNormal, sea_resource.code_path.length()) .ToLocal(&code_path)) { return; } args.GetReturnValue().Set(code_path); } 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()) { 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)) { if (builds_snapshot_from_main) { FPrintF(stderr, "\"useCodeCache\" is redundant when \"useSnapshot\" is true\n"); } else { 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); } 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, "getCodePath", GetCodePath); SetMethod(context, target, "getCodeCache", GetCodeCache); SetMethod(context, target, "getAsset", GetAsset); } void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(IsSea); registry->Register(IsExperimentalSeaWarningNeeded); registry->Register(GetCodePath); registry->Register(GetCodeCache); registry->Register(GetAsset); } } // namespace sea } // namespace node NODE_BINDING_CONTEXT_AWARE_INTERNAL(sea, node::sea::Initialize) NODE_BINDING_EXTERNAL_REFERENCE(sea, node::sea::RegisterExternalReferences) #endif // !defined(DISABLE_SINGLE_EXECUTABLE_APPLICATION)