Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion scripts/build/deps/webkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* for local mode. Override via `--webkit-version=<hash>` to test a branch.
* From https://github.com/oven-sh/WebKit releases.
*/
export const WEBKIT_VERSION = "299c5323879e79af282d7bb7bac8b8446a0be3f3";
export const WEBKIT_VERSION = "autobuild-preview-pr-639-411bf2c6";

/**
* WebKit (JavaScriptCore) — the JS engine.
Expand Down
21 changes: 19 additions & 2 deletions test/js/node/vm/vm-import-callback-lifetime-fixture.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions test/js/node/vm/vm-script-fetcher-leak.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ describe("node:vm importModuleDynamically lifetime", () => {
"alive-runInContext": { result: "hooked", referrer: "Script" },
"alive-compileFunction": { result: "hooked", referrer: "function" },
"alive-module": { result: "hooked", referrer: "SourceTextModule" },
"alive-sameSourceModules": {
first: { result: "hooked by first", referrer: "SourceTextModule" },
second: { result: "hooked by second", referrer: "SourceTextModule" },
},
"freed-perScriptClosures": { alive: 0 },
"stringFilename": { result: "ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING" },
};
Expand All @@ -134,6 +138,7 @@ describe("node:vm importModuleDynamically lifetime", () => {
"alive-runInContext",
"alive-compileFunction",
"alive-module",
"alive-sameSourceModules",
])("%s while collecting continuously", async scenario => {
const result = await bunRun([fixture, scenario], { BUN_JSC_collectContinuously: "1" });
expect(result).toSpawn(JSON.stringify(expected[scenario]));
Expand Down
66 changes: 66 additions & 0 deletions test/js/node/vm/vm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
runInThisContext,
Script,
SourceTextModule,
SyntheticModule,
} from "node:vm";

function capture(_: any, _1?: any) {}
Expand Down Expand Up @@ -2107,6 +2108,71 @@ describe("node:vm SourceTextModule cyclic graph linking", () => {
});
});

describe("node:vm SourceTextModule instances with the same identifier and source text", () => {
// JSC lets module records with the same key and source text in one global object run the same
// ModuleProgramExecutable. What import() and stack traces report comes from that executable's source,
// which is also where node:vm keeps a module's importModuleDynamically hook and its line/column offsets.
// JSC finds the executable through a weak map, and a module's functions keep it alive. So every source here
// exports a function and every module stays reachable.
const modules: SourceTextModule[] = [];

async function load(tag: string) {
const dependency = new SyntheticModule(["tag"], function () {
this.setExport("tag", tag);
});
await dependency.link(() => {});
await dependency.evaluate();

const calls: { specifier: string; referrerIsThisModule: boolean }[] = [];
const module = new SourceTextModule(
`export const fromTopLevel = import("dep");
export function fromFunction() { return import("dep"); }`,
{
identifier: "same-identifier-and-source.mjs",
importModuleDynamically(specifier, referrer) {
calls.push({ specifier, referrerIsThisModule: referrer === module });
return dependency;
},
},
);
modules.push(module);
await module.link(() => {});
await module.evaluate();
const namespace = module.namespace as any;
return { calls, imported: [(await namespace.fromTopLevel).tag, (await namespace.fromFunction()).tag] };
}

test("each module calls its own importModuleDynamically", async () => {
const first = await load("first");
const second = await load("second");
const calls = [
{ specifier: "dep", referrerIsThisModule: true },
{ specifier: "dep", referrerIsThisModule: true },
];
expect({ first, second }).toEqual({
first: { calls, imported: ["first", "first"] },
second: { calls, imported: ["second", "second"] },
});
});

test("each module reports positions with its own lineOffset", async () => {
async function lineOfThrow(identifier: string, lineOffset: number) {
const module = new SourceTextModule(`export function where() { return new Error("here").stack; }`, {
identifier,
lineOffset,
});
modules.push(module);
await module.link(() => {});
await module.evaluate();
return Number(/:(\d+):\d+\)?$/m.exec((module.namespace as any).where())![1]);
}
const shifted = await lineOfThrow("same-identifier-other-offset.mjs", 100);
const unshifted = await lineOfThrow("same-identifier-other-offset.mjs", 0);
expect(unshifted).toBe(await lineOfThrow("never-seen-identifier.mjs", 0));
expect(shifted).toBeGreaterThan(unshifted);
});
});

test("node:vm Object.defineProperty on the context global when the sandbox is an uncacheable dictionary holding an accessor for a built-in", async () => {
// Regression: NodeVMGlobalObject::defineOwnProperty used a single PropertySlot
// for both the global-object lookup and the sandbox lookup. When the first
Expand Down
Loading