Skip to content
Open
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: 2 additions & 0 deletions quickjs.c
Original file line number Diff line number Diff line change
Expand Up @@ -32042,6 +32042,7 @@ static JSValue js_dynamic_import(JSContext *ctx, JSValueConst specifier,
static void js_set_module_evaluated(JSContext *ctx, JSModuleDef *m)
{
m->status = JS_MODULE_STATUS_EVALUATED;
m->async_evaluation = false;
if (!JS_IsUndefined(m->promise)) {
JSValue ret_val;
assert(m->cycle_root == m);
Expand Down Expand Up @@ -32137,6 +32138,7 @@ static JSValue js_async_module_execution_rejected(JSContext *ctx, JSValueConst t
module->eval_has_exception = true;
module->eval_exception = js_dup(error);
module->status = JS_MODULE_STATUS_EVALUATED;
module->async_evaluation = false;

for(i = 0; i < module->async_parent_modules_count; i++) {
JSModuleDef *m = module->async_parent_modules[i];
Expand Down
1 change: 0 additions & 1 deletion test262_errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ test262/test/language/module-code/ambiguous-export-bindings/import-and-export-pr
test262/test/language/module-code/ambiguous-export-bindings/namespace-unambiguous-if-export-star-as-from-and-import-star-as-and-export.js:74: SyntaxError: export 'foo' in module 'test262/test/language/module-code/ambiguous-export-bindings/nam' is ambiguous
test262/test/language/module-code/ambiguous-export-bindings/namespace-unambiguous-if-export-star-as-from.js:75: SyntaxError: export 'foo' in module 'test262/test/language/module-code/ambiguous-export-bindings/nam' is ambiguous
test262/test/language/module-code/ambiguous-export-bindings/namespace-unambiguous-if-import-star-as-and-export.js:74: SyntaxError: export 'foo' in module 'test262/test/language/module-code/ambiguous-export-bindings/nam' is ambiguous
test262/test/language/module-code/top-level-await/module-graphs-does-not-hang.js:10: TypeError: $DONE() not called
test262/test/language/module-code/top-level-await/rejection-order.js:20: TypeError: $DONE() not called
test262/test/language/statements/await-using/initializer-Symbol.asyncDispose-disposed-at-end-of-imported-module.js:11: SyntaxError: exported variable 'resource' does not exist
test262/test/language/statements/await-using/initializer-Symbol.dispose-disposed-at-end-of-imported-module.js:11: SyntaxError: exported variable 'resource' does not exist
Expand Down
5 changes: 5 additions & 0 deletions tests.conf
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,8 @@ tests/fixture_throwing_module.js
tests/fixture_reexport_source.js
tests/fixture_reexport_missing.js
tests/fixture_reexport_direct.js
tests/fixture_async_leaf.js
tests/fixture_async_sync_parent.js
tests/fixture_async_grandparent.js
tests/fixture_async_rejects.js
tests/fixture_async_rejects_parent.js
3 changes: 3 additions & 0 deletions tests/fixture_async_grandparent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/* imports the already evaluated sync parent: nothing here is async */
import { parent } from "./fixture_async_sync_parent.js";
export const grandparent = parent + 1;
3 changes: 3 additions & 0 deletions tests/fixture_async_leaf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/* a module with a top-level await: it evaluates asynchronously */
await Promise.resolve();
export const leaf = 1;
3 changes: 3 additions & 0 deletions tests/fixture_async_rejects.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/* an async module that rejects after its top-level await */
await Promise.resolve();
throw new Error("leaf boom");
3 changes: 3 additions & 0 deletions tests/fixture_async_rejects_parent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/* a sync module whose only asynchrony comes from the rejecting leaf */
import "./fixture_async_rejects.js";
export const unreachable = 1;
5 changes: 5 additions & 0 deletions tests/fixture_async_sync_parent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/* no top-level await of its own, but an async dependency: the module is
marked [[AsyncEvaluation]] while it waits for the leaf and must be
unmarked once it has run */
import { leaf } from "./fixture_async_leaf.js";
export const parent = leaf + 1;
70 changes: 70 additions & 0 deletions tests/module-async-evaluation-flag.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { assert } from "./assert.js";

/* [[AsyncEvaluation]] must be cleared once a module has finished evaluating.
A module that is only asynchronous because a dependency has a top-level
await keeps the flag set otherwise, and the *next* module that imports it
then treats a finished module as a pending async dependency: it registers
itself in [[AsyncParentModules]] of a module that will never notify its
parents again, so its evaluation promise never settles. */

/* Settling is decided against a bounded chain of microtask turns rather than
a timer: a working import settles in a handful of turns, a wedged one
never settles at all. */
function turns(n) {
let p = Promise.resolve();
for (let i = 0; i < n; i++)
p = p.then(() => {});
return p.then(() => "pending");
}

async function settles(promise) {
return Promise.race([promise.then(v => ({ ok: v }), e => ({ err: e })),
turns(200)]);
}

/* the leaf has the top-level await, the parent only inherits its asynchrony */
{
const parent = await settles(import("./fixture_async_sync_parent.js"));
assert(parent.ok !== undefined, true, "parent import settled");
assert(parent.ok.parent, 2);
}

/* importing the finished module again must not wedge */
{
const again = await settles(import("./fixture_async_sync_parent.js"));
assert(again.ok !== undefined, true, "re-import settled");
assert(again.ok.parent, 2);
}

/* and neither must a fresh module that depends on it: this is the one that
used to hang, because the finished parent still looked async */
{
const grand = await settles(import("./fixture_async_grandparent.js"));
assert(grand.ok !== undefined, true, "grandparent import settled");
assert(grand.ok.grandparent, 3);
}

/* the leaf itself is fine too, before and after */
{
const leaf = await settles(import("./fixture_async_leaf.js"));
assert(leaf.ok !== undefined, true, "leaf import settled");
assert(leaf.ok.leaf, 1);
}

/* a rejecting async module must clear the flag as well, so that a later
importer of a *different* finished module is unaffected and the rejected
one keeps reporting its error rather than hanging */
{
const bad = await settles(import("./fixture_async_rejects.js"));
assert(bad.err instanceof Error, true, "rejecting module settled");
assert(bad.err.message, "leaf boom");

const badAgain = await settles(import("./fixture_async_rejects.js"));
assert(badAgain.err instanceof Error, true, "re-import settled");
assert(badAgain.err.message, "leaf boom");

/* a module importing the failed one reports the same failure, promptly */
const importer = await settles(import("./fixture_async_rejects_parent.js"));
assert(importer.err instanceof Error, true, "importer settled");
assert(importer.err.message, "leaf boom");
}
Loading