mirror of
https://github.com/nodejs/node.git
synced 2025-08-15 05:38:47 +02:00
src: use simdjson to parse SEA configuration
PR-URL: https://github.com/nodejs/node/pull/59323 Refs: https://github.com/nodejs/node/issues/59288 Reviewed-By: Darshan Sen <raisinten@gmail.com> Reviewed-By: Daniel Lemire <daniel@lemire.me> Reviewed-By: Ethan Arrowood <ethan@arrowood.dev>
This commit is contained in:
parent
af20ce5bcc
commit
013190dd9c
2 changed files with 156 additions and 144 deletions
170
src/node_sea.cc
170
src/node_sea.cc
|
@ -3,7 +3,6 @@
|
|||
#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"
|
||||
|
@ -11,6 +10,7 @@
|
|||
#include "node_snapshot_builder.h"
|
||||
#include "node_union_bytes.h"
|
||||
#include "node_v8_platform-inl.h"
|
||||
#include "simdjson.h"
|
||||
#include "util-inl.h"
|
||||
|
||||
// The POSTJECT_SENTINEL_FUSE macro is a string of random characters selected by
|
||||
|
@ -303,14 +303,119 @@ std::optional<SeaConfig> ParseSingleExecutableConfig(
|
|||
}
|
||||
|
||||
SeaConfig result;
|
||||
JSONParser parser;
|
||||
if (!parser.Parse(config)) {
|
||||
FPrintF(stderr, "Cannot parse JSON from %s\n", config_path);
|
||||
|
||||
simdjson::ondemand::parser parser;
|
||||
simdjson::ondemand::document document;
|
||||
simdjson::ondemand::object main_object;
|
||||
simdjson::error_code error =
|
||||
parser.iterate(simdjson::pad(config)).get(document);
|
||||
|
||||
if (!error) {
|
||||
error = document.get_object().get(main_object);
|
||||
}
|
||||
if (error) {
|
||||
FPrintF(stderr,
|
||||
"Cannot parse JSON from %s: %s\n",
|
||||
config_path,
|
||||
simdjson::error_message(error));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
result.main_path =
|
||||
parser.GetTopLevelStringField("main").value_or(std::string());
|
||||
bool use_snapshot_value = false;
|
||||
bool use_code_cache_value = false;
|
||||
|
||||
for (auto field : main_object) {
|
||||
std::string_view key;
|
||||
if (field.unescaped_key().get(key)) {
|
||||
FPrintF(stderr, "Cannot read key from %s\n", config_path);
|
||||
return std::nullopt;
|
||||
}
|
||||
if (key == "main") {
|
||||
if (field.value().get_string().get(result.main_path) ||
|
||||
result.main_path.empty()) {
|
||||
FPrintF(stderr,
|
||||
"\"main\" field of %s is not a non-empty string\n",
|
||||
config_path);
|
||||
return std::nullopt;
|
||||
}
|
||||
} else if (key == "output") {
|
||||
if (field.value().get_string().get(result.output_path) ||
|
||||
result.output_path.empty()) {
|
||||
FPrintF(stderr,
|
||||
"\"output\" field of %s is not a non-empty string\n",
|
||||
config_path);
|
||||
return std::nullopt;
|
||||
}
|
||||
} else if (key == "disableExperimentalSEAWarning") {
|
||||
bool disable_experimental_sea_warning;
|
||||
if (field.value().get_bool().get(disable_experimental_sea_warning)) {
|
||||
FPrintF(
|
||||
stderr,
|
||||
"\"disableExperimentalSEAWarning\" field of %s is not a Boolean\n",
|
||||
config_path);
|
||||
return std::nullopt;
|
||||
}
|
||||
if (disable_experimental_sea_warning) {
|
||||
result.flags |= SeaFlags::kDisableExperimentalSeaWarning;
|
||||
}
|
||||
} else if (key == "useSnapshot") {
|
||||
if (field.value().get_bool().get(use_snapshot_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;
|
||||
}
|
||||
} else if (key == "useCodeCache") {
|
||||
if (field.value().get_bool().get(use_code_cache_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;
|
||||
}
|
||||
} else if (key == "assets") {
|
||||
simdjson::ondemand::object assets_object;
|
||||
if (field.value().get_object().get(assets_object)) {
|
||||
FPrintF(stderr,
|
||||
"\"assets\" field of %s is not a map of strings\n",
|
||||
config_path);
|
||||
return std::nullopt;
|
||||
}
|
||||
simdjson::ondemand::value asset_value;
|
||||
for (auto asset_field : assets_object) {
|
||||
std::string_view key_str;
|
||||
std::string_view value_str;
|
||||
if (asset_field.unescaped_key().get(key_str) ||
|
||||
asset_field.value().get(asset_value) ||
|
||||
asset_value.get_string().get(value_str)) {
|
||||
FPrintF(stderr,
|
||||
"\"assets\" field of %s is not a map of strings\n",
|
||||
config_path);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
result.assets.emplace(key_str, value_str);
|
||||
}
|
||||
|
||||
if (!result.assets.empty()) {
|
||||
result.flags |= SeaFlags::kIncludeAssets;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (static_cast<bool>(result.flags & SeaFlags::kUseSnapshot) &&
|
||||
static_cast<bool>(result.flags & SeaFlags::kUseCodeCache)) {
|
||||
// TODO(joyeecheung): code cache in snapshot should be configured by
|
||||
// separate snapshot configurations.
|
||||
FPrintF(stderr,
|
||||
"\"useCodeCache\" is redundant when \"useSnapshot\" is true\n");
|
||||
}
|
||||
|
||||
if (result.main_path.empty()) {
|
||||
FPrintF(stderr,
|
||||
"\"main\" field of %s is not a non-empty string\n",
|
||||
|
@ -318,8 +423,6 @@ std::optional<SeaConfig> ParseSingleExecutableConfig(
|
|||
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",
|
||||
|
@ -327,57 +430,6 @@ std::optional<SeaConfig> ParseSingleExecutableConfig(
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,113 +5,88 @@
|
|||
require('../common');
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
const { writeFileSync, mkdirSync } = require('fs');
|
||||
const { spawnSync } = require('child_process');
|
||||
const assert = require('assert');
|
||||
const { spawnSyncAndAssert } = require('../common/child_process');
|
||||
|
||||
{
|
||||
tmpdir.refresh();
|
||||
const config = 'non-existent-relative.json';
|
||||
const child = spawnSync(
|
||||
spawnSyncAndAssert(
|
||||
process.execPath,
|
||||
['--experimental-sea-config', config], {
|
||||
cwd: tmpdir.path,
|
||||
}, {
|
||||
status: 1,
|
||||
stderr: /Cannot read single executable configuration from non-existent-relative\.json/
|
||||
});
|
||||
const stderr = child.stderr.toString();
|
||||
assert.strictEqual(child.status, 1);
|
||||
assert.match(
|
||||
stderr,
|
||||
/Cannot read single executable configuration from non-existent-relative\.json/
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
tmpdir.refresh();
|
||||
const config = tmpdir.resolve('non-existent-absolute.json');
|
||||
const child = spawnSync(
|
||||
spawnSyncAndAssert(
|
||||
process.execPath,
|
||||
['--experimental-sea-config', config], {
|
||||
cwd: tmpdir.path,
|
||||
}, {
|
||||
status: 1,
|
||||
stderr: /Cannot read single executable configuration from .*non-existent-absolute\.json/
|
||||
});
|
||||
const stderr = child.stderr.toString();
|
||||
assert.strictEqual(child.status, 1);
|
||||
assert(
|
||||
stderr.includes(
|
||||
`Cannot read single executable configuration from ${config}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
tmpdir.refresh();
|
||||
const config = tmpdir.resolve('invalid.json');
|
||||
writeFileSync(config, '\n{\n"main"', 'utf8');
|
||||
const child = spawnSync(
|
||||
spawnSyncAndAssert(
|
||||
process.execPath,
|
||||
['--experimental-sea-config', config], {
|
||||
cwd: tmpdir.path,
|
||||
}, {
|
||||
status: 1,
|
||||
stderr: /INCOMPLETE_ARRAY_OR_OBJECT/
|
||||
});
|
||||
const stderr = child.stderr.toString();
|
||||
assert.strictEqual(child.status, 1);
|
||||
assert.match(stderr, /SyntaxError: Expected ':' after property name/);
|
||||
assert(
|
||||
stderr.includes(
|
||||
`Cannot parse JSON from ${config}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
tmpdir.refresh();
|
||||
const config = tmpdir.resolve('empty.json');
|
||||
writeFileSync(config, '{}', 'utf8');
|
||||
const child = spawnSync(
|
||||
spawnSyncAndAssert(
|
||||
process.execPath,
|
||||
['--experimental-sea-config', config], {
|
||||
cwd: tmpdir.path,
|
||||
}, {
|
||||
status: 1,
|
||||
stderr: /"main" field of .*empty\.json is not a non-empty string/
|
||||
});
|
||||
const stderr = child.stderr.toString();
|
||||
assert.strictEqual(child.status, 1);
|
||||
assert(
|
||||
stderr.includes(
|
||||
`"main" field of ${config} is not a non-empty string`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
tmpdir.refresh();
|
||||
const config = tmpdir.resolve('no-main.json');
|
||||
writeFileSync(config, '{"output": "test.blob"}', 'utf8');
|
||||
const child = spawnSync(
|
||||
spawnSyncAndAssert(
|
||||
process.execPath,
|
||||
['--experimental-sea-config', config], {
|
||||
cwd: tmpdir.path,
|
||||
}, {
|
||||
status: 1,
|
||||
stderr: /"main" field of .*no-main\.json is not a non-empty string/
|
||||
});
|
||||
const stderr = child.stderr.toString();
|
||||
assert.strictEqual(child.status, 1);
|
||||
assert(
|
||||
stderr.includes(
|
||||
`"main" field of ${config} is not a non-empty string`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
tmpdir.refresh();
|
||||
const config = tmpdir.resolve('no-output.json');
|
||||
writeFileSync(config, '{"main": "bundle.js"}', 'utf8');
|
||||
const child = spawnSync(
|
||||
spawnSyncAndAssert(
|
||||
process.execPath,
|
||||
['--experimental-sea-config', config], {
|
||||
cwd: tmpdir.path,
|
||||
}, {
|
||||
status: 1,
|
||||
stderr: /"output" field of .*no-output\.json is not a non-empty string/
|
||||
});
|
||||
const stderr = child.stderr.toString();
|
||||
assert.strictEqual(child.status, 1);
|
||||
assert(
|
||||
stderr.includes(
|
||||
`"output" field of ${config} is not a non-empty string`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -124,32 +99,28 @@ const assert = require('assert');
|
|||
"disableExperimentalSEAWarning": "💥"
|
||||
}
|
||||
`, 'utf8');
|
||||
const child = spawnSync(
|
||||
spawnSyncAndAssert(
|
||||
process.execPath,
|
||||
['--experimental-sea-config', config], {
|
||||
cwd: tmpdir.path,
|
||||
}, {
|
||||
status: 1,
|
||||
stderr: /"disableExperimentalSEAWarning" field of .*invalid-disableExperimentalSEAWarning\.json is not a Boolean/
|
||||
});
|
||||
const stderr = child.stderr.toString();
|
||||
assert.strictEqual(child.status, 1);
|
||||
assert(
|
||||
stderr.includes(
|
||||
`"disableExperimentalSEAWarning" field of ${config} is not a Boolean`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
tmpdir.refresh();
|
||||
const config = tmpdir.resolve('nonexistent-main-relative.json');
|
||||
writeFileSync(config, '{"main": "bundle.js", "output": "sea.blob"}', 'utf8');
|
||||
const child = spawnSync(
|
||||
spawnSyncAndAssert(
|
||||
process.execPath,
|
||||
['--experimental-sea-config', config], {
|
||||
cwd: tmpdir.path,
|
||||
}, {
|
||||
status: 1,
|
||||
stderr: /Cannot read main script .*bundle\.js/
|
||||
});
|
||||
const stderr = child.stderr.toString();
|
||||
assert.strictEqual(child.status, 1);
|
||||
assert.match(stderr, /Cannot read main script bundle\.js/);
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -161,19 +132,14 @@ const assert = require('assert');
|
|||
output: 'sea.blob'
|
||||
});
|
||||
writeFileSync(config, configJson, 'utf8');
|
||||
const child = spawnSync(
|
||||
spawnSyncAndAssert(
|
||||
process.execPath,
|
||||
['--experimental-sea-config', config], {
|
||||
cwd: tmpdir.path,
|
||||
}, {
|
||||
status: 1,
|
||||
stderr: /Cannot read main script .*bundle\.js/
|
||||
});
|
||||
|
||||
const stderr = child.stderr.toString();
|
||||
assert.strictEqual(child.status, 1);
|
||||
assert(
|
||||
stderr.includes(
|
||||
`Cannot read main script ${main}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -188,19 +154,14 @@ const assert = require('assert');
|
|||
output,
|
||||
});
|
||||
writeFileSync(config, configJson, 'utf8');
|
||||
const child = spawnSync(
|
||||
spawnSyncAndAssert(
|
||||
process.execPath,
|
||||
['--experimental-sea-config', config], {
|
||||
cwd: tmpdir.path,
|
||||
}, {
|
||||
status: 1,
|
||||
stderr: /Cannot write output to .*output-dir/
|
||||
});
|
||||
|
||||
const stderr = child.stderr.toString();
|
||||
assert.strictEqual(child.status, 1);
|
||||
assert(
|
||||
stderr.includes(
|
||||
`Cannot write output to ${output}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -215,13 +176,12 @@ const assert = require('assert');
|
|||
output: 'output-dir'
|
||||
});
|
||||
writeFileSync(config, configJson, 'utf8');
|
||||
const child = spawnSync(
|
||||
spawnSyncAndAssert(
|
||||
process.execPath,
|
||||
['--experimental-sea-config', config], {
|
||||
cwd: tmpdir.path,
|
||||
}, {
|
||||
status: 1,
|
||||
stderr: /Cannot write output to output-dir/
|
||||
});
|
||||
|
||||
const stderr = child.stderr.toString();
|
||||
assert.strictEqual(child.status, 1);
|
||||
assert.match(stderr, /Cannot write output to output-dir/);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue