diff --git a/src/node_sea.cc b/src/node_sea.cc index 891d69ae973..461862d6790 100644 --- a/src/node_sea.cc +++ b/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 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(result.flags & SeaFlags::kUseSnapshot) && + static_cast(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 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 ParseSingleExecutableConfig( return std::nullopt; } - std::optional 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 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 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; } diff --git a/test/parallel/test-single-executable-blob-config-errors.js b/test/parallel/test-single-executable-blob-config-errors.js index 364a533c0c9..a30850010e2 100644 --- a/test/parallel/test-single-executable-blob-config-errors.js +++ b/test/parallel/test-single-executable-blob-config-errors.js @@ -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/); }