Skip to content

[JSC] ShadowRealm: importValue resolves an export whose value is undefined - #612

Open
robobun wants to merge 1 commit into
mainfrom
robobun/36b4607b/shadow-realm-import-value-undefined
Open

[JSC] ShadowRealm: importValue resolves an export whose value is undefined#612
robobun wants to merge 1 commit into
mainfrom
robobun/36b4607b/shadow-realm-import-value-undefined

Conversation

@robobun

@robobun robobun commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Problem

  • ShadowRealm.prototype.importValue rejects an export that exists with the value undefined:
    const r = new ShadowRealm();
    await r.importValue("data:text/javascript,export const v = undefined", "v");
    // TypeError: %ShadowRealm%.importValue requires |exportName| to exist in the |specifier|
    await r.importValue("data:text/javascript,export const v = null", "v");  // null, fine
    An export let ready; that the module assigns later rejects the same way until it is assigned. Node.js (--experimental-shadow-realm) resolves undefined.
  • The cause is the lookupBinding step of the importValue builtin (Source/JavaScriptCore/builtins/ShadowRealmPrototype.js): it read module[exportName] and treated undefined as missing. The proposal's ExportGetter tests HasOwnProperty(exports, name) and then reads the value. Upstream WebKit main has the same code.

Fix

  • Check the name with @Object.@hasOwn(module, exportName), then read it and wrap it as before. The error message for a missing name does not change.
  • For a module namespace object, hasOwn is answered from the export list without reading the binding (JSModuleNamespaceObject::getOwnPropertySlotCommon, HasProperty case). It is an own-property check, so a name the namespace would only inherit still rejects. That matters in Bun, where namespace objects have a prototype that carries an __esModule accessor: in would have let importValue(m, "__esModule") through.
  • Verified with a Debug+ASAN jsc: the extended JSTests/stress/shadow-realm-import-value.js (an undefined-valued export, a live let binding before and after assignment, and toString / __proto__ / __esModule still rejecting) passes with and without --useJIT=false. A Bun debug build on this branch passes the new importValue case in Bun's test/js/bun/jsc/shadow.test.js, which fails on the current pin.

Background

  • importValue(specifier, name) imports the module inside the shadow realm and resolves with one export, wrapped for the caller: a primitive as is, a callable as a wrapped function, anything else rejects with a TypeError. The namespace object itself never crosses the boundary, so this getter is the only place the export is looked up.

…fined

The ExportGetter of ShadowRealm.prototype.importValue checks
HasOwnProperty(exports, name) and then reads the value
(https://tc39.es/proposal-shadowrealm/#sec-export-getter-functions).
The builtin read the value and treated undefined as a missing export, so
`export const v = undefined`, or an `export let v` that is assigned
later, rejected with "requires |exportName| to exist". Check the name
with @object.@hasOwn on the namespace object instead, then read it. For
a module namespace object that is a lookup in the export list and does
not read the binding.

* JSTests/stress/shadow-realm-import-value.js:
* JSTests/stress/resources/shadow-realm-example-module.js:

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I reviewed this and didn't find any bugs — the fix looks correct and matches the spec's ExportGetter semantics — but since it changes observable behavior of a JSC builtin and both touched paths fall under jsc-reviewers in CODEOWNERS, a human sign-off is still worthwhile.

What was reviewed:

  • lookupBinding now uses @ Object.@ hasOwn (an existing intrinsic pattern also used in InjectedScriptSource.js), so it is not user-tamperable and stays an own-property check.
  • Confirmed inherited names (toString, __proto__, __esModule) still reject and exportName is already constrained to strings, so the namespace's own Symbol.toStringTag is not reachable.
  • New stress-test assertions throw on failure and add no logging, per JSTests conventions.
Extended reasoning...

Overview

The PR changes three files: the self-hosted ShadowRealmPrototype.js builtin (4 effective lines), the shadow-realm-import-value.js stress test, and its resource module. The functional change replaces module[exportName] === @ undefined with !@ Object.@ hasOwn(module, exportName) as the missing-export check in importValue's lookupBinding closure, and only reads module[exportName] after the presence check passes. This aligns with the ShadowRealm proposal's ExportGetter algorithm (HasOwnProperty → Get) and lets exports that exist with value undefined — including uninitialized-then-assigned let live bindings — resolve rather than reject.

Security risks

None identified. @ Object.@ hasOwn is a private-name intrinsic reference (the same pattern already appears in Source/JavaScriptCore/inspector/InjectedScriptSource.js), so user code cannot override either Object or hasOwn to influence the check. The check remains own-property only, so names a namespace object might inherit (relevant in Bun where the namespace prototype carries __esModule) continue to be rejected — the new tests cover toString, __proto__, and __esModule. The exportName is validated as a string before this closure is built, so the namespace's own Symbol.toStringTag is unreachable through this path.

Level of scrutiny

Moderate. The diff is tiny and the logic is straightforward, but it alters observable semantics of a spec-tracked JS engine API (ShadowRealm.prototype.importValue), and the touched directories (Source/JavaScriptCore, JSTests) are listed under @ WebKit/jsc-reviewers in .github/CODEOWNERS. Per the review guidelines, CODEOWNER-covered paths should get a human look even when the automated review is clean, so I'm deferring rather than approving.

Other factors

Test coverage is good: the stress test adds both positive cases (undefinedValue, notYet before/after setNotYet) and negative cases (inherited names), and the assertions (shouldBe, shouldThrowAsync) throw on failure with no printing, consistent with JSTests/CLAUDE.md requirements. The PR description reports verification with a Debug+ASAN jsc under both JIT and --useJIT=false, which is the expected workflow from the project docs.

@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Essentials

Run ID: 50292eb4-be77-4619-bbd0-b54c5af0a68c

📥 Commits

Reviewing files that changed from the base of the PR and between 10350dd and 43d0937.

📒 Files selected for processing (3)
  • JSTests/stress/resources/shadow-realm-example-module.js
  • JSTests/stress/shadow-realm-import-value.js
  • Source/JavaScriptCore/builtins/ShadowRealmPrototype.js

Included review availability: Your plan provides up to 5 included reviews per hour; 1 remains after this review.


Walkthrough

Changes

ShadowRealm importValue now accepts own exports with undefined values and rejects inherited names. Tests cover explicit undefined exports and live bindings that update after import.

ShadowRealm importValue behavior

Layer / File(s) Summary
Own export lookup and validation
Source/JavaScriptCore/builtins/ShadowRealmPrototype.js, JSTests/stress/resources/shadow-realm-example-module.js, JSTests/stress/shadow-realm-import-value.js
importValue checks that exportName is an own property before retrieving it. The test module adds undefined and mutable exports. Tests cover inherited names, __esModule, undefined values, and live binding updates.

Priority: ⬇️ Low

Severity of issue fixed: Low

Merge Risk: ⚪ Minimal · up to 43d09

importValue now accepts own exports whose value is undefined while continuing to reject missing or inherited names. The supplied test coverage and readiness assessment indicate no remaining merge-blocking risk.

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the problem, cause, fix, behavior, and validation. It does not include the required Bugzilla bug title and URL, reviewer line, or template-formatted changed-file and function … Add the associated Bugzilla bug title and URL, include a "Reviewed by NOBODY (OOPS!)." line or actual reviewer, and add the required changed-file and function list in the repository template format.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: allowing ShadowRealm importValue to resolve exports whose value is undefined.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description explains the problem, cause, fix, behavior, and validation. It does not include the required Bugzilla bug title and URL, reviewer line, or template-formatted changed-file and function list.

  • Fix all pre-merge checks with AI

Warning

Git: CodeRabbit could not clone the repository, so clone-backed analysis was skipped and this review may be incomplete. Verify repository clone access, such as SSH credentials, before requesting another full review. If clone access is intentionally unavailable, use path_filters to narrow the review scope.


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

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

Preview Builds

Commit Release Date
43d09378 autobuild-preview-pr-612-43d09378 2026-09-09 07:15:46 UTC

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant