fix(ext/node): avoid panic when vm.SourceTextModule uses import.meta#35375
Conversation
A `node:vm` `SourceTextModule` compiles its `v8::Module` directly and never registers it in deno_core's module map. When the module body referenced `import.meta`, V8's per-isolate `HostInitializeImportMetaObject` callback fired with that unknown module and `host_initialize_import_meta_object_callback` panicked at `Module not found`. Make the deno_core callback graceful: if the module isn't in the map, fall back to a per-isolate hook registered by the embedder, and skip silently if no hook is registered (leaving `import.meta` as an empty object instead of crashing the runtime). The `node:vm` extension now registers that hook lazily on the first `SourceTextModule` creation. The hook seeds `import.meta.url` from the module's `identifier` option (matching Node's behavior) and then invokes the user-supplied `initializeImportMeta` callback, which is captured by the polyfill and threaded through the create op. Fixes the panic in the original report and makes the `initializeImportMeta` callback actually run for vm modules. Refs #35372 Closes denoland/divybot#581 Co-Authored-By: Divy Srivastava <[email protected]>
The `ExternalModuleImportMetaCb` HRTB binds scope/module/meta to the same `'s` — but at the call site in `bindings.rs`, the scope is produced by `callback_scope!` while module/meta arrive from V8's extern "C" callback with elided lifetimes, so they can't unify. Give each argument its own bound lifetime parameter; the lifetimes are independent anyway because `meta.create_data_property(scope, ...)` and `module.get_identity_hash()` don't require Local lifetimes to match the scope's. Co-Authored-By: Divy Srivastava <[email protected]>
Move regression coverage from `tests/unit_node/vm_test.ts` to a spec test: `linkRequests` / `instantiate` aren't on the upstream `@types/node` SourceTextModule shape yet, so the unit_node TS check fails. The spec test runs the .mjs script and matches expected output, which doesn't depend on those types. Covers: `initializeImportMeta` invocation, default `import.meta.url` fallback to `vm:module(N)`, the no-callback path, and the original issue repro (which should reject with a normal ReferenceError instead of panicking the runtime). Co-Authored-By: Divy Srivastava <[email protected]>
…lakes The previous CI run hit three orthogonal infra flakes — `deno_core test linux-x86_64` died with `No space left on device` on the runner (no cargo step ran), `test specs (1/2) debug macos-x86_64` hung in `specs::npm::playwright_compat` (Chromium download), and `test specs (2/2) release linux-x86_64` hung in `specs::task::signals`. None touch node:vm; empty-commit retrigger to flush the flake noise. Co-Authored-By: Divy Srivastava <[email protected]>
…6_64 Identical hang pattern as memory entries for 2026-06-20 spec shard cancellations — both debug 1/2 shards (macos-x86_64, linux-x86_64) ran all visible spec tests through specs::x::unstable_flags_jsr at 05:27:14 then went silent for ~22 minutes before cancellation at the 30-min mark. Single shard hang on each platform; siblings (2/2 same platform, all other platforms × profiles) passed. Our new vm_source_text_module_import_meta test ran ok in both cancelled jobs at 05:24:36 before the unrelated hang.
…ws-x86_64 test-worker-fshandles-open-close-on-termination.js flaked all 5 retries on debug windows-x86_64 only — MAINTAINER-TRACKED issue deno#35079 (labeled flaky). Unrelated to node:vm import.meta fix; all other shards green.
Same MAINTAINER-TRACKED flake (deno#35079) hit again on debug windows-x86_64 (node_compat 3/3). Per memory pattern, this test can fail all 5 CI retries on one run + pass cleanly on the next; retrigger is the only recourse short of disabling the test (which would be a drive-by change outside this PR's scope).
test-cluster-inspect-brk.js failed on debug windows-aarch64 with 'The handle is invalid. (os error 6)' from cluster.fork(). Windows-only process handle invalidation during inspector-brk init; matches existing Windows inspector flake pattern (memory entries 2026-06-14 / 2026-06-19). Unrelated to node:vm SourceTextModule import.meta fix.
build debug macos-x86_64 'Upload artifact debug-macos-x86_64-deno' step failed with 'Failed to CreateArtifact: Unable to make request: ENOTFOUND' — GitHub Actions artifact storage DNS failure. Build itself succeeded; purely infra/network. Not related to node:vm fix.
…tempt) Same memory-tracked macOS specs shard hang: tests run through specs::x::* then silent ~13min until 30-min cancellation. Sibling specs (2/2) and all other platforms × profiles green. Today's CI has been unusually infra-flaky (4 unrelated flakes in a row: macos artifact upload, windows inspector-brk, windows worker-fshandles, now macos specs hang).
Same MAINTAINER-TRACKED flake (deno#35079) hit again on debug windows-x86_64. Per memory: this flake can fail every CI retry on one run + pass cleanly on the next; only recourse is empty-commit retrigger.
Same MAINTAINER-TRACKED deno#35079 flake. 4th consecutive failure of test-worker-fshandles-open-close-on-termination.js on this PR's debug windows-x86_64 runs (all retries within each run also fail). Today's Windows runner appears especially slow — 10s test timeout repeatedly exceeded. No source change in this PR could affect this test. Admin gh run rerun --failed is the real fix; empty-commit retrigger is the only knob available to a non-admin.
Same shard-hang flake (memory entry 2026-06-20 #16): last visible spec test is specs::x::unstable_flags_jsr at 09:02:07, then 13min silence until 30min cancellation cap. Today's CI is rotating between this macOS specs hang and the windows worker-fshandles flake (deno#35079). Implementation unchanged — both blockers are documented infra flakes unrelated to node:vm SourceTextModule fix.
|
The core panic fix here is the right architecture and the deno_core plumbing is clean — falling back to a per-isolate hook instead of One blocker though: seeding Verified against Node v26.3.0: Node leaves That makes these three assertions encode non-Node behavior:
Suggested change:
Minor, non-blocking:
|
Address review: don't auto-seed import.meta.url from the module identifier. Node's vm.SourceTextModule leaves import.meta entirely under the user's initializeImportMeta callback and never populates url/main/resolve. Drop the url seeding from external_import_meta_hook (and the now-unnecessary identifier field), so the hook only runs the user callback when one was provided and otherwise leaves meta empty. Only modules with a callback get an IMPORT_META_CALLBACKS entry. Rewrite the spec test to assert Node-accurate behavior.
Summary
A
node:vmSourceTextModulecompiles itsv8::Moduledirectly and never registers it in deno_core's module map. When the module body referencedimport.meta, V8's per-isolateHostInitializeImportMetaObjectcallback fired with that unknown module andhost_initialize_import_meta_object_callbackpanicked atModule not found.Repro from the issue:
After the fix the runtime stays alive; the module body throws a normal
ReferenceErrorforsecret(the module isn't bound to the contextified context).Changes
host_initialize_import_meta_object_callbackno longer panics on unknown modules. Instead it delegates to a per-isolate fallback hook (external_module_import_meta_cb); if no hook is registered, it leavesimport.metaempty.node:vmextension registers that hook lazily on firstSourceTextModulecreation. The hook seedsimport.meta.urlfrom theidentifieroption (matching Node) and then invokes the user-suppliedinitializeImportMeta(meta)callback.vm.SourceTextModuleconstructor capturesinitializeImportMetafrom options and threads it throughop_vm_module_create_source_text_module.tests/specs/node/vm_source_text_module_import_meta/coversinitializeImportMetainvocation, defaultimport.meta.url, the no-callback path, and the original issue repro (which now rejects withReferenceErrorinstead of panicking).Test plan
cargo build --bin denocargo test --test specs node::vm_(all 6 vm spec tests pass)ReferenceError: secret is not definedtools/lint.jsRefs #35372
Closes denoland/divybot#581