Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
8b4fa180bf Fix vm.SourceTextModule to support top-level await
Fixes #22287

The issue was that when JavaScriptCore encounters modules with top-level await,
the `link()` method returns `Synchronousness::Async`, but the implementation
was asserting with a "TODO" message instead of handling it properly.

In JavaScriptCore, modules with top-level await report the linking phase as
async, but the linking itself can complete synchronously. The actual async
behavior happens during evaluation, not linking. This matches Node.js behavior
where linking is always synchronous.

Changes:
- Remove assertion failures in NodeVMSourceTextModule::link() and NodeVMSyntheticModule::link()
- Add comments explaining the async linking behavior
- Basic top-level await now works correctly

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-31 23:32:22 +00:00
3 changed files with 100 additions and 2 deletions

View File

@@ -364,7 +364,14 @@ JSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSArray* spec
RETURN_IF_EXCEPTION(scope, {});
if (sync == Synchronousness::Async) {
RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("TODO(@heimskr): async SourceTextModule linking");
// In JavaScriptCore, when a module contains top-level await, the linking phase
// reports as async, but the actual async behavior happens during evaluation, not linking.
// The linking itself can complete synchronously even for modules with top-level await.
//
// This matches Node.js behavior where linking is always synchronous and
// asynchronous behavior only occurs during evaluation.
//
// So we can safely mark the module as linked and proceed.
}
status(Status::Linked);

View File

@@ -148,7 +148,11 @@ JSValue NodeVMSyntheticModule::link(JSGlobalObject* globalObject, JSArray* speci
RETURN_IF_EXCEPTION(scope, {});
if (sync == Synchronousness::Async) {
RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("TODO(@heimskr): async SyntheticModule linking");
// In JavaScriptCore, when a module contains top-level await, the linking phase
// reports as async, but the actual async behavior happens during evaluation, not linking.
// The linking itself can complete synchronously even for modules with top-level await.
//
// So we can safely mark the module as linked and proceed.
}
status(Status::Linked);

View File

@@ -0,0 +1,87 @@
import { test, expect } from "bun:test";
import vm from "node:vm";
test("vm.SourceTextModule should support top-level await", async () => {
const source = `
await (async () => {
env.log("top-level await works");
})();
`;
const context = vm.createContext({
env: {
log: (msg: string) => {
// Store the message for testing
context.loggedMessage = msg;
}
},
loggedMessage: null
});
const mainModule = new vm.SourceTextModule(source, { context });
expect(mainModule.status).toBe("unlinked");
await mainModule.link(async (specifier) => {
throw new Error(`Failed to resolve module: ${specifier}`);
});
expect(mainModule.status).toBe("linked");
await mainModule.evaluate();
expect(mainModule.status).toBe("evaluated");
expect(context.loggedMessage).toBe("top-level await works");
});
test("vm.SourceTextModule basic top-level await functionality", async () => {
const source = `
await (async () => {
env.result = "success";
})();
`;
const context = vm.createContext({
env: {
result: ""
}
});
const mainModule = new vm.SourceTextModule(source, { context });
await mainModule.link(async (specifier) => {
throw new Error(`Failed to resolve module: ${specifier}`);
});
await mainModule.evaluate();
expect(mainModule.status).toBe("evaluated");
expect(context.env.result).toBe("success");
});
test("vm.SourceTextModule should handle top-level await with error", async () => {
const source = `
await (async () => {
throw new Error("async error");
})();
`;
const context = vm.createContext({});
const mainModule = new vm.SourceTextModule(source, { context });
await mainModule.link(async (specifier) => {
throw new Error(`Failed to resolve module: ${specifier}`);
});
let errorThrown = false;
try {
await mainModule.evaluate();
} catch (error) {
errorThrown = true;
expect(error.message).toBe("async error");
}
expect(errorThrown).toBe(true);
// Note: The module status behavior for async errors might vary between implementations
// In some cases, the module might remain "evaluated" even if the promise rejects
});