From 2926864a02e1b8fdd06f65a71e65573799d3052f Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Wed, 9 Sep 2026 06:52:46 +0000 Subject: [PATCH] ShadowRealm: importValue resolves an export whose value is undefined WEBKIT_VERSION points at the preview build of oven-sh/WebKit#612. ShadowRealm.prototype.importValue treated an export whose value is undefined as missing. The ExportGetter checks HasOwnProperty(exports, name) and then reads the value, so `export const v = undefined` and an `export let v` that is assigned later now resolve. --- scripts/build/deps/webkit.ts | 2 +- test/js/bun/jsc/shadow.test.js | 39 +++++++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/scripts/build/deps/webkit.ts b/scripts/build/deps/webkit.ts index 7e8ddecce2ac..9de8a2e48df0 100644 --- a/scripts/build/deps/webkit.ts +++ b/scripts/build/deps/webkit.ts @@ -3,7 +3,7 @@ * for local mode. Override via `--webkit-version=` to test a branch. * From https://github.com/oven-sh/WebKit releases. */ -export const WEBKIT_VERSION = "2e2aa2290fac856d6f451ceacb58f7f5b44dd057"; +export const WEBKIT_VERSION = "autobuild-preview-pr-612-43d09378"; /** * WebKit (JavaScriptCore) — the JS engine. diff --git a/test/js/bun/jsc/shadow.test.js b/test/js/bun/jsc/shadow.test.js index 3fdca5391413..248540b6b02e 100644 --- a/test/js/bun/jsc/shadow.test.js +++ b/test/js/bun/jsc/shadow.test.js @@ -1,4 +1,6 @@ -import { expect, it } from "bun:test"; +import { describe, expect, it } from "bun:test"; +import { tempDir } from "harness"; +import { join } from "node:path"; it("shadow realm works", () => { const red = new ShadowRealm(); @@ -8,3 +10,38 @@ it("shadow realm works", () => { expect(globalThis.someValue).toBe(1); expect(result).toBe(2); }); + +describe("importValue", () => { + // https://tc39.es/proposal-shadowrealm/#sec-export-getter-functions checks HasOwnProperty(exports, name), then reads + // the value. An export whose value is undefined exists. + it("resolves an export whose value is undefined", async () => { + using dir = tempDir("shadow-realm-import-value", { + "mod.mjs": ` + export const undefinedValue = undefined; + export let notYet; + export function setNotYet(v) { notYet = v; } + export const nullValue = null; + `, + }); + const mod = join(String(dir), "mod.mjs"); + const realm = new ShadowRealm(); + expect(await realm.importValue(mod, "undefinedValue")).toBe(undefined); + expect(await realm.importValue(mod, "nullValue")).toBe(null); + // A live binding that is still undefined reads its current value each time. + expect(await realm.importValue(mod, "notYet")).toBe(undefined); + (await realm.importValue(mod, "setNotYet"))("now"); + expect(await realm.importValue(mod, "notYet")).toBe("now"); + // A name that is not exported still rejects. That includes names an ordinary object would inherit, and __esModule, + // which Bun's module namespace objects inherit from their prototype: the check is HasOwnProperty. + for (const name of ["missing", "toString", "__proto__", "constructor", "then", "__esModule"]) { + let error; + try { + await realm.importValue(mod, name); + } catch (e) { + error = e; + } + expect(error).toBeInstanceOf(TypeError); + expect(error.message).toBe("%ShadowRealm%.importValue requires |exportName| to exist in the |specifier|"); + } + }); +});