From 89da7ede33dc64c26b5509e3ff08005f0cc93527 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Fri, 14 Aug 2026 01:19:31 +0000 Subject: [PATCH] node:vm: accept undefined codeGeneration.strings/wasm like Node getNodeVMContextOptions() rejected an explicitly undefined strings or wasm key in codeGeneration/contextCodeGeneration with ERR_INVALID_ARG_TYPE. Node treats an undefined key the same as an omitted one (the default, true), and its own getContextOptions() builds the codeGeneration object with undefined holes, so skip the boolean check for undefined the way the sibling name/origin/microtaskMode reads already do. Covers createContext, vm.runInNewContext and Script#runInNewContext, which share this function. --- src/jsc/bindings/NodeVM.cpp | 4 ++-- test/js/node/vm/vm.test.ts | 42 +++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/jsc/bindings/NodeVM.cpp b/src/jsc/bindings/NodeVM.cpp index 54a3d25d2947..fbb09addb190 100644 --- a/src/jsc/bindings/NodeVM.cpp +++ b/src/jsc/bindings/NodeVM.cpp @@ -721,7 +721,7 @@ void getNodeVMContextOptions(JSGlobalObject* globalObject, JSC::VM& vm, JSC::Thr auto allowStringsValue = codeGenerationObject->getIfPropertyExists(globalObject, Identifier::fromString(vm, "strings"_s)); RETURN_IF_EXCEPTION(scope, ); - if (allowStringsValue) { + if (allowStringsValue && !allowStringsValue.isUndefined()) { if (!allowStringsValue.isBoolean()) { ERR::INVALID_ARG_TYPE(scope, globalObject, WTF::makeString("options."_s, codeGenerationKey, ".strings"_s), "boolean"_s, allowStringsValue); return; @@ -733,7 +733,7 @@ void getNodeVMContextOptions(JSGlobalObject* globalObject, JSC::VM& vm, JSC::Thr auto allowWasmValue = codeGenerationObject->getIfPropertyExists(globalObject, Identifier::fromString(vm, "wasm"_s)); RETURN_IF_EXCEPTION(scope, ); - if (allowWasmValue) { + if (allowWasmValue && !allowWasmValue.isUndefined()) { if (!allowWasmValue.isBoolean()) { ERR::INVALID_ARG_TYPE(scope, globalObject, WTF::makeString("options."_s, codeGenerationKey, ".wasm"_s), "boolean"_s, allowWasmValue); return; diff --git a/test/js/node/vm/vm.test.ts b/test/js/node/vm/vm.test.ts index d3d04239d179..cccc84eff7e0 100644 --- a/test/js/node/vm/vm.test.ts +++ b/test/js/node/vm/vm.test.ts @@ -1010,6 +1010,48 @@ describe("codeGeneration options", () => { const evalResult = runInContext("eval('5 + 5');", context); expect(evalResult).toBe(10); }); + + describe("strings/wasm set to undefined", () => { + // In Node an undefined strings/wasm is the same as an omitted one: + // createContext() destructures them with `= true` defaults, and + // getContextOptions() only validates them when `!== undefined` (it then + // hands createContext() a `{ strings, wasm }` object with undefined holes). + const WASM_BYTES = new Uint8Array([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]); + // Evaluated inside the context: reports which kinds of code generation it allows. + const probe = ` + let strings = true, wasm = true; + try { eval("1"); } catch { strings = false; } + try { new WebAssembly.Module(WASM_BYTES); } catch { wasm = false; } + "strings=" + strings + " wasm=" + wasm; + `; + + // createContext reads options.codeGeneration; vm.runInNewContext and + // Script#runInNewContext read options.contextCodeGeneration. + const entryPoints: Record string> = { + "createContext()": codeGeneration => runInContext(probe, createContext({ WASM_BYTES }, { codeGeneration })), + "runInNewContext()": contextCodeGeneration => runInNewContext(probe, { WASM_BYTES }, { contextCodeGeneration }), + "Script#runInNewContext()": contextCodeGeneration => + new Script(probe).runInNewContext({ WASM_BYTES }, { contextCodeGeneration }), + }; + + describe.each(Object.entries(entryPoints))("%s", (_, run) => { + test("undefined is accepted and means the default (allowed)", () => { + expect(run({ strings: undefined })).toBe("strings=true wasm=true"); + expect(run({ wasm: undefined })).toBe("strings=true wasm=true"); + expect(run({ strings: undefined, wasm: undefined })).toBe("strings=true wasm=true"); + }); + + test("undefined next to an explicit false leaves the false in effect", () => { + expect(run({ strings: false, wasm: undefined })).toBe("strings=false wasm=true"); + expect(run({ strings: undefined, wasm: false })).toBe("strings=true wasm=false"); + }); + + test.each([null, 0, 1, "false"])("%p is still rejected", value => { + expect(() => run({ strings: value })).toThrowWithCode(TypeError, "ERR_INVALID_ARG_TYPE"); + expect(() => run({ wasm: value })).toThrowWithCode(TypeError, "ERR_INVALID_ARG_TYPE"); + }); + }); + }); }); describe("context options with throwing getters", () => {