From 510cc12a6e71807955434cc0f31e1a7f2f865a06 Mon Sep 17 00:00:00 2001 From: Paolo Insogna Date: Fri, 2 Oct 2026 12:41:03 +0200 Subject: [PATCH 1/4] deps,lib,tools: remove jitless and lite modes Signed-off-by: Paolo Insogna --- BUILDING.md | 3 ++ configure.py | 15 ++---- deps/v8/src/flags/flag-definitions.h | 24 +++++---- deps/v8/src/flags/flags.cc | 4 ++ doc/api/cli.md | 17 ------ doc/api/errors.md | 20 ++++--- doc/node.1 | 7 --- lib/eslint.config_partial.mjs | 4 +- lib/internal/errors.js | 3 -- lib/internal/freeze_intrinsics.js | 22 ++++---- lib/internal/util.js | 4 -- src/node_options.cc | 4 -- test/es-module/test-import-cjs-jitless.mjs | 6 --- test/es-module/test-typescript.mjs | 11 ---- test/parallel/test-cli-no-jitless.js | 52 +++++++++++++++++++ test/parallel/test-cli-node-options.js | 1 - test/parallel/test-freeze-intrinsics.js | 5 +- .../test-internal-webidl-buffer-source.js | 4 +- tools/v8_gypfiles/features.gypi | 8 --- 19 files changed, 107 insertions(+), 107 deletions(-) delete mode 100644 test/es-module/test-import-cjs-jitless.mjs create mode 100644 test/parallel/test-cli-no-jitless.js diff --git a/BUILDING.md b/BUILDING.md index 6fc93ca2ad0e..795019c77db9 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -98,6 +98,9 @@ and libc version. The table below lists the support tier for each supported combination. A list of [supported compile toolchains](#supported-toolchains) is also supplied for tier 1 platforms. +Node.js requires WebAssembly for built-in functionality, including `fetch()` and +TypeScript support. JIT-less execution and V8 lite mode are not supported. + **For production applications, run Node.js on supported platforms only (Tier 1 or 2).** Node.js does not support a platform version if a vendor has expired support diff --git a/configure.py b/configure.py index e769236cebe8..eec2250da6b1 100755 --- a/configure.py +++ b/configure.py @@ -1224,14 +1224,6 @@ default=False, help='compile V8 with debug checks and runtime debugging features enabled') -parser.add_argument('--v8-lite-mode', - action='store_true', - dest='v8_lite_mode', - default=False, - help='compile V8 in lite mode for constrained environments (lowers V8 '+ - 'memory footprint, but also implies no just-in-time compilation ' + - 'support, thus much slower execution)') - parser.add_argument('--v8-enable-object-print', action='store_true', dest='v8_enable_object_print', @@ -1318,6 +1310,10 @@ (options, args) = parser.parse_known_args() +# Reject the removed option before forwarding unknown arguments to GYP. +if any(arg.split('=', 1)[0] == '--v8-lite-mode' for arg in args): + parser.error('--v8-lite-mode is no longer supported; Node.js requires WebAssembly') + # Expand ~ in the install prefix now, it gets written to multiple files. options.prefix = str(Path(options.prefix or '').expanduser()) @@ -2231,9 +2227,8 @@ def configure_library(lib, output, pkgname=None): def configure_v8(o, configs): set_configuration_variable(configs, 'v8_enable_v8_checks', release=0, debug=1) - o['variables']['v8_enable_webassembly'] = 0 if options.v8_lite_mode else 1 + o['variables']['v8_enable_webassembly'] = 1 o['variables']['v8_enable_javascript_promise_hooks'] = 1 - o['variables']['v8_enable_lite_mode'] = 1 if options.v8_lite_mode else 0 is_gdbjit_supported_arch = ( 'x64' in o['variables']['target_arch'] or 'ia32' in o['variables']['target_arch'] or diff --git a/deps/v8/src/flags/flag-definitions.h b/deps/v8/src/flags/flag-definitions.h index 15d4341ff84b..5024060ee3a0 100644 --- a/deps/v8/src/flags/flag-definitions.h +++ b/deps/v8/src/flags/flag-definitions.h @@ -451,8 +451,13 @@ DEFINE_NEG_IMPLICATION(stress_snapshot, incremental_marking) #define V8_LITE_MODE_BOOL false #endif -DEFINE_BOOL(lite_mode, V8_LITE_MODE_BOOL, - "enables trade-off of performance for memory savings") +// Node.js requires WebAssembly for built-in functionality, including fetch and +// TypeScript. Retain the constants for V8 internals, but do not register these +// unsupported modes with the flag parser. +#if defined(FLAG_MODE_DECLARE) +DEFINE_BOOL_READONLY(lite_mode, false, + "enables trade-off of performance for memory savings") +#endif // Lite mode implies other flags to trade-off performance for memory. DEFINE_IMPLICATION(lite_mode, jitless) @@ -937,15 +942,14 @@ DEFINE_STRING( "Select which native code sequence to use for wasm trace instruction: " "default or cpuid") -#ifdef V8_JITLESS -#define V8_JITLESS_BOOL true -DEFINE_BOOL_READONLY(jitless, true, - "Disable runtime allocation of executable memory.") -#else +#if defined(V8_JITLESS) || defined(V8_LITE_MODE) +#error Node.js does not support interpreter-only V8 builds. +#endif #define V8_JITLESS_BOOL false -DEFINE_BOOL(jitless, V8_LITE_MODE_BOOL, - "Disable runtime allocation of executable memory.") -#endif // V8_JITLESS +#if defined(FLAG_MODE_DECLARE) +DEFINE_BOOL_READONLY(jitless, false, + "Disable runtime allocation of executable memory.") +#endif // Jitless V8 has a few implications: // Field type tracking is only used by TurboFan. diff --git a/deps/v8/src/flags/flags.cc b/deps/v8/src/flags/flags.cc index ac051cb784b6..a05828af0747 100644 --- a/deps/v8/src/flags/flags.cc +++ b/deps/v8/src/flags/flags.cc @@ -1042,6 +1042,10 @@ class ImplicationProcessor { bool EnforceImplications() { bool changed = false; + // Node.js retains these constants without exposing them as runtime flags. + const auto& jitless = v8_flags.jitless; + const auto& lite_mode = v8_flags.lite_mode; + // For each flag, alias with a mutable reference so that implications don't // need the v8_flags prefix. #define FLAG_MODE_APPLY_NAME(name) \ diff --git a/doc/api/cli.md b/doc/api/cli.md index b57714a0bfa2..5d1e5e2b6da1 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -2196,19 +2196,6 @@ added: v0.7.7 Opens the REPL even if stdin does not appear to be a terminal. -### `--jitless` - - - -> Stability: 1 - Experimental. This flag is inherited from V8 and is subject to -> change upstream. - -Disable [runtime allocation of executable memory][jitless]. This may be -required on some platforms for security reasons. It can also reduce attack -surface on other platforms, but the performance impact may be severe. - ### `--localstorage-file=file` + +A feature requiring WebAssembly was used in an environment where WebAssembly +was unavailable. Node.js now requires WebAssembly and no longer supports +JIT-less execution. + ### `ERR_WORKER_UNSUPPORTED_EXTENSION` diff --git a/doc/node.1 b/doc/node.1 index 36a7cc720440..2a4d702820b2 100644 --- a/doc/node.1 +++ b/doc/node.1 @@ -1175,11 +1175,6 @@ See V8 Inspector integration for Node.js for further explanation on Node.js debu .It Fl i , Fl -interactive Opens the REPL even if stdin does not appear to be a terminal. . -.It Fl -jitless -Disable runtime allocation of executable memory. This may be -required on some platforms for security reasons. It can also reduce attack -surface on other platforms, but the performance impact may be severe. -. .It Fl -localstorage-file Ns = Ns Ar file The file used to store \fBlocalStorage\fR data. If the file does not exist, it is created the first time \fBlocalStorage\fR is accessed. The same file may be shared @@ -2536,8 +2531,6 @@ V8 options that are allowed are: .It \fB--interpreted-frames-native-stack\fR .It -\fB--jitless\fR -.It \fB--max-heap-size\fR .It \fB--max-old-space-size\fR diff --git a/lib/eslint.config_partial.mjs b/lib/eslint.config_partial.mjs index 9240b00ac709..59b13b6a07da 100644 --- a/lib/eslint.config_partial.mjs +++ b/lib/eslint.config_partial.mjs @@ -298,8 +298,8 @@ export default [ name: 'URLSearchParams', message: "Use `const { URLSearchParams } = require('internal/url');` instead of the global.", }, - // WebAssembly is not available in primordials because it can be - // disabled with --jitless CLI flag. + // WebAssembly is unavailable during snapshot building and must be + // accessed lazily at runtime. { name: 'WebAssembly', message: 'Use `const { WebAssembly } = globalThis;` instead of the global.', diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 0e80f682817d..e7ef5aeba96f 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1981,9 +1981,6 @@ E('ERR_VM_MODULE_NOT_MODULE', 'Provided module is not an instance of Module', Error); E('ERR_VM_MODULE_STATUS', 'Module status %s', Error); E('ERR_WASI_ALREADY_STARTED', 'WASI instance has already started', Error); -E('ERR_WEBASSEMBLY_NOT_SUPPORTED', - 'WebAssembly is not supported in this environment, but is required for %s', - Error); E('ERR_WEBASSEMBLY_RESPONSE', 'WebAssembly response %s', TypeError); E('ERR_WORKER_HANDLE_NOT_TRANSFERABLE', '%s cannot be transferred in its current state; it must be a freshly ' + diff --git a/lib/internal/freeze_intrinsics.js b/lib/internal/freeze_intrinsics.js index 5dd425cb0934..9f1dbae7c9a5 100644 --- a/lib/internal/freeze_intrinsics.js +++ b/lib/internal/freeze_intrinsics.js @@ -363,18 +363,16 @@ module.exports = function() { ArrayPrototypePush(intrinsics, SharedArrayBuffer); } - if (typeof WebAssembly !== 'undefined') { - ArrayPrototypePush(intrinsicPrototypes, - WebAssembly.Module.prototype, - WebAssembly.Instance.prototype, - WebAssembly.Table.prototype, - WebAssembly.Memory.prototype, - WebAssembly.CompileError.prototype, - WebAssembly.LinkError.prototype, - WebAssembly.RuntimeError.prototype, - ); - ArrayPrototypePush(intrinsics, WebAssembly); - } + ArrayPrototypePush(intrinsicPrototypes, + WebAssembly.Module.prototype, + WebAssembly.Instance.prototype, + WebAssembly.Table.prototype, + WebAssembly.Memory.prototype, + WebAssembly.CompileError.prototype, + WebAssembly.LinkError.prototype, + WebAssembly.RuntimeError.prototype, + ); + ArrayPrototypePush(intrinsics, WebAssembly); if (typeof Intl !== 'undefined') { ArrayPrototypePush(intrinsicPrototypes, diff --git a/lib/internal/util.js b/lib/internal/util.js index 2f72e636ab90..54600a50b6d7 100644 --- a/lib/internal/util.js +++ b/lib/internal/util.js @@ -45,7 +45,6 @@ const { SymbolPrototypeGetDescription, SymbolReplace, SymbolSplit, - globalThis, } = primordials; const { @@ -53,7 +52,6 @@ const { ERR_NO_CRYPTO, ERR_NO_TYPESCRIPT, ERR_UNKNOWN_SIGNAL, - ERR_WEBASSEMBLY_NOT_SUPPORTED, }, isErrorStackTraceLimitWritable, overrideStackTrace, @@ -245,8 +243,6 @@ function assertCrypto() { function assertTypeScript() { if (noTypeScript) throw new ERR_NO_TYPESCRIPT(); - if (globalThis.WebAssembly === undefined) - throw new ERR_WEBASSEMBLY_NOT_SUPPORTED('TypeScript'); } /** diff --git a/src/node_options.cc b/src/node_options.cc index 917f6a4f47cc..d43bf3b8fd1a 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -1568,10 +1568,6 @@ PerIsolateOptionsParser::PerIsolateOptionsParser( "disallow eval and friends", V8Option{}, kAllowedInEnvvar); - AddOption("--jitless", - "disable runtime allocation of executable memory", - V8Option{}, - kAllowedInEnvvar); AddOption("--report-uncaught-exception", "generate diagnostic report on uncaught exceptions", BOOL_FIELD(report_uncaught_exception), diff --git a/test/es-module/test-import-cjs-jitless.mjs b/test/es-module/test-import-cjs-jitless.mjs deleted file mode 100644 index 39f10491fa28..000000000000 --- a/test/es-module/test-import-cjs-jitless.mjs +++ /dev/null @@ -1,6 +0,0 @@ -// Flags: --jitless - -// Tests that importing a CJS module works in JIT-less mode (i.e. falling back to the -// JS parser if WASM is not available). -import '../common/index.mjs'; -import '../fixtures/empty.cjs'; diff --git a/test/es-module/test-typescript.mjs b/test/es-module/test-typescript.mjs index fd58d1d990a5..9efe10ad61bf 100644 --- a/test/es-module/test-typescript.mjs +++ b/test/es-module/test-typescript.mjs @@ -318,14 +318,3 @@ test('execute invalid TypeScript syntax', async () => { assert.strictEqual(result.stdout, ''); assert.strictEqual(result.code, 1); }); - -test('expect error when executing a TypeScript file with --jitless', async () => { - const result = await spawnPromisified(process.execPath, [ - '--jitless', - fixtures.path('typescript/ts/test-typescript.ts'), - ]); - - assert.match(result.stderr, /ERR_WEBASSEMBLY_NOT_SUPPORTED/); - assert.match(result.stderr, /WebAssembly is not supported in this environment, but is required for TypeScript/); - assert.strictEqual(result.code, 1); -}); diff --git a/test/parallel/test-cli-no-jitless.js b/test/parallel/test-cli-no-jitless.js new file mode 100644 index 000000000000..8fb48f906815 --- /dev/null +++ b/test/parallel/test-cli-no-jitless.js @@ -0,0 +1,52 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { spawnSync } = require('child_process'); + +// Interpreter-only modes must not bypass Node's WebAssembly requirement, +// including through V8's alternate flag spellings and runtime flag API. +for (const flag of [ + '--jitless', '--lite-mode', '--lite_mode', '-jitless', '-lite_mode', + '--jitless=true', '--lite_mode=true', '--no-jitless', '--no-lite-mode', +]) { + const result = spawnSync(process.execPath, [flag, '-e', ''], { encoding: 'utf8' }); + assert.strictEqual(result.status, 9, result.stderr); + assert.strictEqual(result.signal, null); + assert.match(result.stderr, /bad option:/); + + const runtime = spawnSync(process.execPath, ['-e', ` + require('v8').setFlagsFromString(${JSON.stringify(flag)}); + const bytes = Uint8Array.of(0, 97, 115, 109, 1, 0, 0, 0); + new WebAssembly.Instance(new WebAssembly.Module(bytes)); + `], { encoding: 'utf8' }); + assert.strictEqual(runtime.status, 0, runtime.stderr); + assert.match(runtime.stderr, /unrecognized flag/); +} + +const options = spawnSync(process.execPath, ['--v8-options'], { encoding: 'utf8' }); +assert.strictEqual(options.status, 0, options.stderr); +assert.doesNotMatch(options.stdout, /--(?:jitless|lite[-_]mode)\b/); + +for (const flag of ['--jitless', '--lite-mode', '--lite_mode']) { + assert.strictEqual(process.allowedNodeEnvironmentFlags.has(flag), false); + if (!process.config.variables.node_without_node_options) { + const result = spawnSync(process.execPath, ['-e', ''], { + encoding: 'utf8', + env: { ...process.env, NODE_OPTIONS: flag }, + }); + assert.strictEqual(result.status, 9, result.stderr); + assert.match(result.stderr, /is not allowed in NODE_OPTIONS/); + } +} + +// Workers must inherit a runtime with working WebAssembly as well. +const { Worker } = require('worker_threads'); +const worker = new Worker(` + const { parentPort } = require('worker_threads'); + const bytes = Uint8Array.of(0, 97, 115, 109, 1, 0, 0, 0); + new WebAssembly.Instance(new WebAssembly.Module(bytes)); + parentPort.postMessage(typeof WebAssembly); +`, { eval: true }); +worker.on('message', common.mustCall((value) => assert.strictEqual(value, 'object'))); +worker.on('exit', common.mustCall((code) => assert.strictEqual(code, 0))); diff --git a/test/parallel/test-cli-node-options.js b/test/parallel/test-cli-node-options.js index 375c46c11ab7..f6832ab0c79b 100644 --- a/test/parallel/test-cli-node-options.js +++ b/test/parallel/test-cli-node-options.js @@ -76,7 +76,6 @@ if (common.hasCrypto) { expect('--abort_on-uncaught_exception', 'B\n'); expect('--disallow-code-generation-from-strings', 'B\n'); expect('--expose-gc', 'B\n'); -expect('--jitless', 'B\n'); expect('--max-old-space-size=0', 'B\n'); expect('--max-semi-space-size=0', 'B\n'); expect('--stack-trace-limit=100', diff --git a/test/parallel/test-freeze-intrinsics.js b/test/parallel/test-freeze-intrinsics.js index b3a2503d1d5b..e96d2c7832be 100644 --- a/test/parallel/test-freeze-intrinsics.js +++ b/test/parallel/test-freeze-intrinsics.js @@ -1,8 +1,11 @@ -// Flags: --frozen-intrinsics --jitless +// Flags: --frozen-intrinsics 'use strict'; require('../common'); const assert = require('assert'); +assert(Object.isFrozen(WebAssembly)); +assert(Object.isFrozen(WebAssembly.Module.prototype)); + assert.throws( () => Object.defineProperty = 'asdf', TypeError diff --git a/test/parallel/test-internal-webidl-buffer-source.js b/test/parallel/test-internal-webidl-buffer-source.js index 8e81f42a9456..1c50880f49fa 100644 --- a/test/parallel/test-internal-webidl-buffer-source.js +++ b/test/parallel/test-internal-webidl-buffer-source.js @@ -326,9 +326,7 @@ test('Shared buffer growability checks do not read JavaScript properties', () => } }); -test('Shared WebAssembly buffer growability is checked per buffer', { - skip: typeof WebAssembly === 'undefined', -}, () => { +test('Shared WebAssembly buffer growability is checked per buffer', () => { const memory = new WebAssembly.Memory({ initial: 1, maximum: 2, shared: true }); for (const [buffer, growable] of [ [memory.buffer, false], diff --git a/tools/v8_gypfiles/features.gypi b/tools/v8_gypfiles/features.gypi index 95717992bcab..5bf6b6f5de41 100644 --- a/tools/v8_gypfiles/features.gypi +++ b/tools/v8_gypfiles/features.gypi @@ -309,11 +309,6 @@ # This option can be on unconditionally. 'v8_enable_temporal_systemicu%': 1, - # Lite mode disables a number of performance optimizations to reduce memory - # at the cost of performance. - # Sets --DV8_LITE_MODE. - 'v8_enable_lite_mode%': 0, - # Enable the Turbofan compiler. # Sets -dV8_ENABLE_TURBOFAN 'v8_enable_turbofan%': 1, @@ -351,9 +346,6 @@ ['v8_enable_future==1', { 'defines': ['V8_ENABLE_FUTURE',], }], - ['v8_enable_lite_mode==1', { - 'defines': ['V8_LITE_MODE',], - }], ['v8_enable_gdbjit==1', { 'defines': ['ENABLE_GDB_JIT_INTERFACE',], }], From 83ca596e309ab68a6c483836fcf55f48f34f1170 Mon Sep 17 00:00:00 2001 From: Paolo Insogna Date: Sat, 3 Oct 2026 09:21:58 +0200 Subject: [PATCH 2/4] fixup Signed-off-by: Paolo Insogna --- test/doctool/test-doc-api-json.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/doctool/test-doc-api-json.mjs b/test/doctool/test-doc-api-json.mjs index ff063e018d0e..3496652a819b 100644 --- a/test/doctool/test-doc-api-json.mjs +++ b/test/doctool/test-doc-api-json.mjs @@ -159,4 +159,4 @@ for await (const dirent of await fs.opendir(new URL('../../out/doc/api/', import } assert.strictEqual(numberOfDeprecatedSections, 49); // Increase this number every time a new API is deprecated. -assert.strictEqual(numberOfRemovedAPIs, 46); // Increase this number every time a section is marked as removed. +assert.strictEqual(numberOfRemovedAPIs, 47); // Increase this number every time a section is marked as removed. From 80dc3b1d9d6839e4d59d470c78336e1bcdb6d779 Mon Sep 17 00:00:00 2001 From: Paolo Insogna Date: Sat, 3 Oct 2026 10:05:48 +0200 Subject: [PATCH 3/4] fixup Signed-off-by: Paolo Insogna --- deps/v8/src/flags/flag-definitions.h | 24 ++++++++---------- deps/v8/src/flags/flags.cc | 4 --- src/node.cc | 10 ++++++++ src/node_v8.cc | 4 +++ test/parallel/test-cli-no-jitless.js | 37 +++++++++++++++++++++------- 5 files changed, 52 insertions(+), 27 deletions(-) diff --git a/deps/v8/src/flags/flag-definitions.h b/deps/v8/src/flags/flag-definitions.h index 5024060ee3a0..15d4341ff84b 100644 --- a/deps/v8/src/flags/flag-definitions.h +++ b/deps/v8/src/flags/flag-definitions.h @@ -451,13 +451,8 @@ DEFINE_NEG_IMPLICATION(stress_snapshot, incremental_marking) #define V8_LITE_MODE_BOOL false #endif -// Node.js requires WebAssembly for built-in functionality, including fetch and -// TypeScript. Retain the constants for V8 internals, but do not register these -// unsupported modes with the flag parser. -#if defined(FLAG_MODE_DECLARE) -DEFINE_BOOL_READONLY(lite_mode, false, - "enables trade-off of performance for memory savings") -#endif +DEFINE_BOOL(lite_mode, V8_LITE_MODE_BOOL, + "enables trade-off of performance for memory savings") // Lite mode implies other flags to trade-off performance for memory. DEFINE_IMPLICATION(lite_mode, jitless) @@ -942,14 +937,15 @@ DEFINE_STRING( "Select which native code sequence to use for wasm trace instruction: " "default or cpuid") -#if defined(V8_JITLESS) || defined(V8_LITE_MODE) -#error Node.js does not support interpreter-only V8 builds. -#endif -#define V8_JITLESS_BOOL false -#if defined(FLAG_MODE_DECLARE) -DEFINE_BOOL_READONLY(jitless, false, +#ifdef V8_JITLESS +#define V8_JITLESS_BOOL true +DEFINE_BOOL_READONLY(jitless, true, "Disable runtime allocation of executable memory.") -#endif +#else +#define V8_JITLESS_BOOL false +DEFINE_BOOL(jitless, V8_LITE_MODE_BOOL, + "Disable runtime allocation of executable memory.") +#endif // V8_JITLESS // Jitless V8 has a few implications: // Field type tracking is only used by TurboFan. diff --git a/deps/v8/src/flags/flags.cc b/deps/v8/src/flags/flags.cc index a05828af0747..ac051cb784b6 100644 --- a/deps/v8/src/flags/flags.cc +++ b/deps/v8/src/flags/flags.cc @@ -1042,10 +1042,6 @@ class ImplicationProcessor { bool EnforceImplications() { bool changed = false; - // Node.js retains these constants without exposing them as runtime flags. - const auto& jitless = v8_flags.jitless; - const auto& lite_mode = v8_flags.lite_mode; - // For each flag, alias with a mutable reference so that implications don't // need the v8_flags prefix. #define FLAG_MODE_APPLY_NAME(name) \ diff --git a/src/node.cc b/src/node.cc index d0b5310626c3..ff4cb4b6ac58 100644 --- a/src/node.cc +++ b/src/node.cc @@ -48,6 +48,10 @@ #include "node_version.h" #include "permission/env_permission.h" +#if defined(V8_JITLESS) || defined(V8_LITE_MODE) +#error Node.js does not support interpreter-only V8 builds. +#endif + #if HAVE_OPENSSL #include "ncrypto.h" #if OPENSSL_VERSION_MAJOR >= 3 @@ -1108,6 +1112,12 @@ static ExitCode InitializeNodeWithArgsInternal( allow_env_sources.Finish(); + // Node.js requires WebAssembly for built-in functionality. Override these + // modes after all option sources, before V8 applies their implications. + // Explicitly allow overriding even when contradiction checks are enabled. + V8::SetFlagsFromString("--allow-overwriting-for-next-flag --no-lite-mode " + "--allow-overwriting-for-next-flag --no-jitless"); + // Every option source has now been parsed, so cross-source option // constraints can finally be validated. CheckGlobalBenchOptions(errors); diff --git a/src/node_v8.cc b/src/node_v8.cc index 14b74f4b6d42..4dc948113fbc 100644 --- a/src/node_v8.cc +++ b/src/node_v8.cc @@ -272,6 +272,10 @@ void SetFlagsFromString(const FunctionCallbackInfo& args) { CHECK(args[0]->IsString()); Utf8Value flags(args.GetIsolate(), args[0]); V8::SetFlagsFromString(flags.out(), flags.length()); + // Runtime flags must not re-enable modes that disable required WebAssembly + // support, even when contradiction checks are enabled. + V8::SetFlagsFromString("--allow-overwriting-for-next-flag --no-lite-mode " + "--allow-overwriting-for-next-flag --no-jitless"); } void StartCpuProfile(const FunctionCallbackInfo& args) { diff --git a/test/parallel/test-cli-no-jitless.js b/test/parallel/test-cli-no-jitless.js index 8fb48f906815..2d75bf42da98 100644 --- a/test/parallel/test-cli-no-jitless.js +++ b/test/parallel/test-cli-no-jitless.js @@ -4,29 +4,48 @@ const common = require('../common'); const assert = require('assert'); const { spawnSync } = require('child_process'); -// Interpreter-only modes must not bypass Node's WebAssembly requirement, -// including through V8's alternate flag spellings and runtime flag API. +// Interpreter-only modes are overridden to preserve Node's WebAssembly +// requirement, including alternate spellings and the runtime flag API. +const wasm = ` + const assert = require('assert'); + assert.strictEqual(typeof WebAssembly, 'object'); + const bytes = Uint8Array.of(0, 97, 115, 109, 1, 0, 0, 0); + new WebAssembly.Instance(new WebAssembly.Module(bytes)); +`; + for (const flag of [ '--jitless', '--lite-mode', '--lite_mode', '-jitless', '-lite_mode', '--jitless=true', '--lite_mode=true', '--no-jitless', '--no-lite-mode', ]) { - const result = spawnSync(process.execPath, [flag, '-e', ''], { encoding: 'utf8' }); - assert.strictEqual(result.status, 9, result.stderr); + const result = spawnSync(process.execPath, [flag, '-e', wasm], { encoding: 'utf8' }); + assert.strictEqual(result.status, 0, result.stderr); assert.strictEqual(result.signal, null); - assert.match(result.stderr, /bad option:/); + assert.strictEqual(result.stderr, ''); const runtime = spawnSync(process.execPath, ['-e', ` require('v8').setFlagsFromString(${JSON.stringify(flag)}); - const bytes = Uint8Array.of(0, 97, 115, 109, 1, 0, 0, 0); - new WebAssembly.Instance(new WebAssembly.Module(bytes)); + ${wasm} + // A new isolate must also retain WebAssembly after changing runtime flags. + new (require('worker_threads').Worker)(${JSON.stringify(wasm)}, { eval: true }); `], { encoding: 'utf8' }); assert.strictEqual(runtime.status, 0, runtime.stderr); - assert.match(runtime.stderr, /unrecognized flag/); + assert.strictEqual(runtime.stderr, ''); +} + +// Node's overrides must not be treated as contradictory user-supplied flags. +for (const flag of ['--jitless', '--lite-mode']) { + const result = spawnSync(process.execPath, [ + '--abort-on-contradictory-flags', flag, '-e', wasm, + ], { encoding: 'utf8' }); + assert.strictEqual(result.status, 0, result.stderr); + assert.strictEqual(result.signal, null); + assert.strictEqual(result.stderr, ''); } const options = spawnSync(process.execPath, ['--v8-options'], { encoding: 'utf8' }); assert.strictEqual(options.status, 0, options.stderr); -assert.doesNotMatch(options.stdout, /--(?:jitless|lite[-_]mode)\b/); +assert.match(options.stdout, /--jitless\b/); +assert.match(options.stdout, /--lite-mode\b/); for (const flag of ['--jitless', '--lite-mode', '--lite_mode']) { assert.strictEqual(process.allowedNodeEnvironmentFlags.has(flag), false); From 5ca01e767760f64df4e6ebe9cee0987abe1d6182 Mon Sep 17 00:00:00 2001 From: Paolo Insogna Date: Sat, 3 Oct 2026 14:56:34 +0200 Subject: [PATCH 4/4] fixup Signed-off-by: Paolo Insogna --- test/parallel/test-cli-no-jitless.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/test/parallel/test-cli-no-jitless.js b/test/parallel/test-cli-no-jitless.js index 2d75bf42da98..96555e7c88f9 100644 --- a/test/parallel/test-cli-no-jitless.js +++ b/test/parallel/test-cli-no-jitless.js @@ -15,7 +15,7 @@ const wasm = ` for (const flag of [ '--jitless', '--lite-mode', '--lite_mode', '-jitless', '-lite_mode', - '--jitless=true', '--lite_mode=true', '--no-jitless', '--no-lite-mode', + '--no-jitless', '--no-lite-mode', ]) { const result = spawnSync(process.execPath, [flag, '-e', wasm], { encoding: 'utf8' }); assert.strictEqual(result.status, 0, result.stderr); @@ -32,6 +32,25 @@ for (const flag of [ assert.strictEqual(runtime.stderr, ''); } +// V8 boolean flags reject explicit values, even for overridden modes. +for (const flag of ['--jitless=true', '--lite_mode=true']) { + const result = spawnSync(process.execPath, [flag, '-e', wasm], { encoding: 'utf8' }); + assert.strictEqual(result.status, 9, result.stderr); + assert.strictEqual(result.signal, null); + assert.match(result.stderr, /illegal value for flag .* of type bool/); + + // The runtime API reports invalid syntax without throwing, and Node must + // still override any flags V8 changed before reporting the error. + const runtime = spawnSync(process.execPath, ['-e', ` + require('v8').setFlagsFromString(${JSON.stringify(flag)}); + ${wasm} + new (require('worker_threads').Worker)(${JSON.stringify(wasm)}, { eval: true }); + `], { encoding: 'utf8' }); + assert.strictEqual(runtime.status, 0, runtime.stderr); + assert.strictEqual(runtime.signal, null); + assert.match(runtime.stderr, /illegal value for flag .* of type bool/); +} + // Node's overrides must not be treated as contradictory user-supplied flags. for (const flag of ['--jitless', '--lite-mode']) { const result = spawnSync(process.execPath, [