Skip to content

node:vm: accept undefined codeGeneration.strings/wasm like Node - #38314

Open
robobun wants to merge 1 commit into
mainfrom
farm/735b0334/vm-codegen-undefined-suboptions
Open

robobun wants to merge 1 commit into
mainfrom
farm/735b0334/vm-codegen-undefined-suboptions

Conversation

@robobun

@robobun robobun commented Aug 14, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • vm.createContext({}, { codeGeneration: { wasm: undefined } }) throws TypeError [ERR_INVALID_ARG_TYPE]: The "options.codeGeneration.wasm" property must be of type boolean. Received undefined. Same for strings, and for contextCodeGeneration.{strings,wasm} through vm.runInNewContext() and Script#runInNewContext(). Node accepts all of these.
  • Cause: getNodeVMContextOptions() in src/jsc/bindings/NodeVM.cpp (lines 724 and 736) reads the two keys with getIfPropertyExists(), which returns jsUndefined() (a non-empty value) for a key that is present but set to undefined, and then only checks isBoolean(). The name, origin, microtaskMode and codeGeneration reads a few lines above already skip undefined; these two did not.
  • This comes up with code that builds the options object the way Node's own lib/vm.js does: getContextOptions() passes codeGeneration: { strings, wasm } along with whichever of the two the caller left out still undefined, and user code that spreads optional settings produces the same shape.

Fix

  • Skip the boolean check when the value is undefined, so the key falls through to the default (allowStrings/allowWasm are initialized to true in NodeVMContextOptions). null, numbers and strings are still rejected with ERR_INVALID_ARG_TYPE.
  • Matches Node: createContext() destructures { strings = true, wasm = true } = codeGeneration before validateBoolean, and getContextOptions() only validates the two keys when they are !== undefined. Node rejects null for both, which this keeps.
  • All three entry points share getNodeVMContextOptions() (createContext reads codeGeneration, vm.runInNewContext and Script#runInNewContext read contextCodeGeneration), so one change covers them.
  • Independent of node:vm: run runInNewContext() in the sandbox's existing context #38326 (runInNewContext context reuse): that PR does not touch these two reads, and its Script#runInNewContext still validates contextCodeGeneration through getNodeVMContextOptions(), so the undefined keys need this change with or without it. The two PRs change different lines of NodeVM.cpp.
  • Test: test/js/node/vm/vm.test.ts, codeGeneration options > strings/wasm set to undefined. For each of the three entry points it checks that an undefined key is accepted and behaves as the default (eval and WebAssembly.Module both work), that an undefined key next to an explicit false leaves the false in effect, and that null/0/1/"false" are still rejected. The six acceptance tests fail on the current release (USE_SYSTEM_BUN=1) and pass with this change; the expected values were checked against node v26.3.0.
  • Also ran: the full test/js/node/vm/vm.test.ts (232 pass, 0 fail) and all 95 test/js/node/test/parallel/test-vm-*.js files (including test-vm-codegen.js and test-vm-basic.js) against the debug build, all passing.

Background

  • codeGeneration.strings / .wasm are per-context switches for whether code running in a vm context may compile code from strings (eval, new Function) and compile WebAssembly. Both default to true.
  • JSObject::getIfPropertyExists() returns an empty JSValue when the property is absent and the property's value (which may be jsUndefined()) when it exists, so if (value) alone distinguishes missing from present, not missing from undefined.

[stamp-90s] gate passed · iteration 0 · 2 files touched

fails on main (without fix)
ASAN without fix: 6 failed, 62 skipped
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/node/vm/vm.test.ts
bun test v1.4.0 (89da7ede3)

test/js/node/vm/vm.test.ts:
(pass) vm > runInContext() > can do nothing [36.11ms]
(pass) vm > runInContext() > can return a value [24.70ms]
(pass) vm > runInContext() > can return a complex value [25.24ms]
(pass) vm > runInContext() > can return the last value [23.78ms]
(pass) vm > runInContext() > new ArrayBuffer() in VM context doesn't crash [23.10ms]
(pass) vm > runInContext() > new SharedArrayBuffer() in VM context doesn't crash [22.76ms]
(pass) vm > runInContext() > new Uint8Array() in VM context doesn't crash [20.14ms]
(pass) vm > runInContext() > new Int8Array() in VM context doesn't crash [16.08ms]
(pass) vm > runInContext() > new Uint16Array() in VM context doesn't crash [15.42ms]
(pass) vm > runInContext() > new Int16Array() in VM context doesn't crash [16.66ms]
(pass) vm > runInContext() > new Uint32Array() in VM context doesn't crash [24.26ms]
(pass) vm > runInContext() > new Int32Array() in VM context doesn't crash [19.17ms]
(pass) vm > runInContext() > new Float32Arr
... (truncated)

release without fix: 6 failed, 62 skipped
bun test v1.4.0-canary.1 (b7a043103)

test/js/node/vm/vm.test.ts:
(pass) vm > runInContext() > can do nothing [0.50ms]
(pass) vm > runInContext() > can return a value [0.36ms]
(pass) vm > runInContext() > can return a complex value [0.32ms]
(pass) vm > runInContext() > can return the last value [0.19ms]
(pass) vm > runInContext() > new ArrayBuffer() in VM context doesn't crash [0.20ms]
(pass) vm > runInContext() > new SharedArrayBuffer() in VM context doesn't crash [0.23ms]
(pass) vm > runInContext() > new Uint8Array() in VM context doesn't crash [0.33ms]
(pass) vm > runInContext() > new Int8Array() in VM context doesn't crash [0.17ms]
(pass) vm > runInContext() > new Uint16Array() in VM context doesn't crash [0.16ms]
(pass) vm > runInContext() > new Int16Array() in VM context doesn't crash [0.15ms]
(pass) vm > runInContext() > new Uint32Array() in VM context doesn't crash [0.18ms]
(pass) vm > runInContext() > new Int32Array() in VM context doesn't crash [0.24ms]
(pass) vm > runInContext() > new Float32Array() in VM context doesn't crash [0.25ms]
(pass) vm > runInContext() > new Float64Array() in VM context doesn't crash [0.16ms]
(pass) vm > runInContext() > new Big
... (truncated)
passes on PR (with fix)
ASAN with fix: 62 skipped
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/node/vm/vm.test.ts
bun test v1.4.0 (89da7ede3)

test/js/node/vm/vm.test.ts:
(pass) vm > runInContext() > can do nothing [23.04ms]
(pass) vm > runInContext() > can return a value [16.53ms]
(pass) vm > runInContext() > can return a complex value [17.87ms]
(pass) vm > runInContext() > can return the last value [15.25ms]
(pass) vm > runInContext() > new ArrayBuffer() in VM context doesn't crash [18.93ms]
(pass) vm > runInContext() > new SharedArrayBuffer() in VM context doesn't crash [15.07ms]
(pass) vm > runInContext() > new Uint8Array() in VM context doesn't crash [16.30ms]
(pass) vm > runInContext() > new Int8Array() in VM context doesn't crash [16.14ms]
(pass) vm > runInContext() > new Uint16Array() in VM context doesn't crash [16.72ms]
(pass) vm > runInContext() > new Int16Array() in VM context doesn't crash [17.68ms]
(pass) vm > runInContext() > new Uint32Array() in VM context doesn't crash [18.72ms]
(pass) vm > runInContext() > new Int32Array() in VM context doesn't crash [15.52ms]
(pass) vm > runInContext() > new Float32Arr
... (truncated)

release with fix: 62 skipped
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped)
  target       linux-x64-gnu
  build type   Release
  build dir    ./build/release
  revision     89da7ede33
  features     baseline

22 deps, 123 codegen, 1176 objects in 782ms

ninja: Entering directory `/workspace/bun/build/release'
[1/1238] gen ErrorCode+*.h
[2/1238] install /workspace/bun
bun install v1.4.0-canary.1 (b7a043103)

Checked 107 installs across 153 packages (no changes) [18.00ms]
[3/1238] install /workspace/bun/packages/bun-error
bun install v1.4.0-canary.1 (b7a043103)

Checked 1 install across 2 packages (no changes) [1.00ms]
[4/1238] fetch libjpeg-turbo
[libjpeg-turbo] up to date
[5/1238] gen bindgenv2
[6/1238] fetch zlib
[zlib] up to date
[7/1238] gen ProcessBindingConstants.lut.h
Generating /workspace/bun/build/release/codegen/ProcessBindingConstants.lut.h from /workspace/bun/src/jsc/bindings/ProcessBindingConstants.cpp
[8/1238] fetch tinycc
[tinycc] up to date
[9/1237] install /workspace/bun/src/node-fallbacks
bun install v1.4.0-canary.1 (b7a043103)

Checked 129 installs across 147 packages (no changes) [41.00ms]
[10/1237] gen .bind.ts → GeneratedBindings.cpp
... (truncated)
diff hotspot
src/jsc/bindings/NodeVM.cpp |  4 ++--
 test/js/node/vm/vm.test.ts  | 42 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 44 insertions(+), 2 deletions(-)

gate history · 1 passed · 0 rejected · iteration 0

evidence per changed file
file                         reads  edits  tests
src/jsc/bindings/NodeVM.cpp      1      2      0
test/js/node/vm/vm.test.ts       2      2      0

self-review · no surviving concerns

26 concerns were raised and did not survive verification.

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.
@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 25 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a70569a8-3587-4d29-8105-c62b1a72070e

📥 Commits

Reviewing files that changed from the base of the PR and between b555e06 and 89da7ed.

📒 Files selected for processing (2)
  • src/jsc/bindings/NodeVM.cpp
  • test/js/node/vm/vm.test.ts

Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented Aug 14, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status: fix pushed, self-review done (no findings), waiting on CI.

Reproduced on bun 1.4.0 and main (b555e06) with:

const vm = require("node:vm");
vm.createContext({}, { codeGeneration: { wasm: undefined } });
// TypeError [ERR_INVALID_ARG_TYPE]: The "options.codeGeneration.wasm" property must be of type boolean. Received undefined

Same for strings, and for contextCodeGeneration.{strings,wasm} via vm.runInNewContext() and Script#runInNewContext(). node v26.3.0 accepts all four shapes and treats the key as omitted.

Test: test/js/node/vm/vm.test.ts (codeGeneration options > strings/wasm set to undefined), 6 of the 18 new cases fail on the current release and pass with this branch. Full vm.test.ts and all 95 test/js/node/test/parallel/test-vm-*.js files pass against the debug build.

Related but separate: #38326 (runInNewContext context reuse) changes different lines and still needs this change for undefined keys. The codeGeneration: [] / function container check a few lines above is also more lenient than Node's validateObject; that is a different bug and is tracked separately.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — two-line Node compat fix bringing codeGeneration.strings/.wasm in line with the sibling option reads.

What was reviewed:

  • The two && !value.isUndefined() guards match the pattern already used for name/origin/microtaskMode/codeGeneration a few lines above; allowStrings/allowWasm default to true in NodeVMContextOptions so skipping the assignment is correct.
  • Confirmed undefined → default (true) is not a security loosening — it's identical to the key being absent, which was already accepted.
  • Tests cover all three entry points sharing this helper, verify undefined next to explicit false keeps the false, and confirm null/0/1/"false" are still rejected with ERR_INVALID_ARG_TYPE.
Extended reasoning...

Overview

Two-character-class change in src/jsc/bindings/NodeVM.cpp (getNodeVMContextOptions): the if (allowStringsValue) and if (allowWasmValue) guards each gain && !value.isUndefined(), so a present-but-undefined codeGeneration.strings/.wasm falls through to the struct default (true) instead of failing the isBoolean() check. A new nested describe block in test/js/node/vm/vm.test.ts exercises the behaviour across createContext, vm.runInNewContext, and Script#runInNewContext.

Security risks

None. The only new accepted input is undefined, which now behaves exactly like an absent key (already accepted). The default is true (code generation allowed), so there is no accidental tightening or loosening relative to the pre-existing absent-key path. null, numbers, and strings still throw ERR_INVALID_ARG_TYPE, and the tests assert that.

Level of scrutiny

Low. This is a mechanical Node.js compat fix — the sibling reads for name, origin, microtaskMode, and the outer codeGeneration object in the same function already skip undefined with the exact same idiom; this just brings the two nested keys in line. NodeVMContextOptions::allowStrings/allowWasm are initialized to true (verified in NodeVM.h:80-81), so the fall-through is correct. No memory safety, GC, or exception-scope concerns: isUndefined() is a non-throwing tag check on a value already validated by RETURN_IF_EXCEPTION.

Other factors

  • CODEOWNERS does not cover either file.
  • Tests are well-structured: added to the existing codeGeneration options describe in the right file, use describe.each/test.each per harness conventions, cover the full entry-point matrix (all three callers of getNodeVMContextOptions), assert both the positive contract (undefined → default, mixed with explicit false) and the negative contract (non-boolean non-undefined still rejected with the specific error code).
  • PR description confirms USE_SYSTEM_BUN=1 failure and Node v26.3.0 parity, and that the full vm.test.ts and 95 test-vm-*.js parallel tests still pass.

@robobun

robobun commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 8:05 PM PT - Aug 13th, 2026

❌ @robobun, your commit 89da7ed has some failures in Build #95268 (All Failures)


🧪   To try this PR locally:

bunx bun-pr 38314

That installs a local version of the PR into your bun-38314 executable, so you can run:

bun-38314 --bun

@robobun

robobun commented Aug 14, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 12:53 AM PT - Aug 14th, 2026

✅ @robobun, your commit 89da7ede33dc64c26b5509e3ff08005f0cc93527 passed in Build #95268! 🎉


🧪   To try this PR locally:

bunx bun-pr 38314

That installs a local version of the PR into your bun-38314 executable, so you can run:

bun-38314 --bun

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant