diff --git a/quickjs.c b/quickjs.c index bbac00c33..ca047acb0 100644 --- a/quickjs.c +++ b/quickjs.c @@ -32138,14 +32138,6 @@ static JSValue js_async_module_execution_rejected(JSContext *ctx, JSValueConst t module->eval_exception = js_dup(error); module->status = JS_MODULE_STATUS_EVALUATED; - for(i = 0; i < module->async_parent_modules_count; i++) { - JSModuleDef *m = module->async_parent_modules[i]; - JSValue m_obj = JS_NewModuleValue(ctx, m); - js_async_module_execution_rejected(ctx, JS_UNDEFINED, 1, &error, 0, - vc(&m_obj)); - JS_FreeValue(ctx, m_obj); - } - if (!JS_IsUndefined(module->promise)) { JSValue ret_val; assert(module->cycle_root == module); @@ -32153,6 +32145,14 @@ static JSValue js_async_module_execution_rejected(JSContext *ctx, JSValueConst t 1, &error); JS_FreeValue(ctx, ret_val); } + + for(i = 0; i < module->async_parent_modules_count; i++) { + JSModuleDef *m = module->async_parent_modules[i]; + JSValue m_obj = JS_NewModuleValue(ctx, m); + js_async_module_execution_rejected(ctx, JS_UNDEFINED, 1, &error, 0, + vc(&m_obj)); + JS_FreeValue(ctx, m_obj); + } return JS_UNDEFINED; } diff --git a/test262_errors.txt b/test262_errors.txt index 082fb016a..1c2cefbae 100644 --- a/test262_errors.txt +++ b/test262_errors.txt @@ -36,7 +36,6 @@ test262/test/language/module-code/ambiguous-export-bindings/namespace-unambiguou 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 test262/test/language/statements/class/elements/syntax/valid/grammar-field-named-get-followed-by-generator-asi.js:40: SyntaxError: invalid property name diff --git a/tests.conf b/tests.conf index 192656e0b..e5d17b07c 100644 --- a/tests.conf +++ b/tests.conf @@ -16,3 +16,9 @@ tests/fixture_throwing_module.js tests/fixture_reexport_source.js tests/fixture_reexport_missing.js tests/fixture_reexport_direct.js +tests/fixture_reject_leaf.js +tests/fixture_reject_mid.js +tests/fixture_reject_top.js +tests/fixture_reject_sib_leaf.js +tests/fixture_reject_sib_a.js +tests/fixture_reject_sib_b.js diff --git a/tests/fixture_reject_leaf.js b/tests/fixture_reject_leaf.js new file mode 100644 index 000000000..a6df1b2db --- /dev/null +++ b/tests/fixture_reject_leaf.js @@ -0,0 +1,3 @@ +/* the async module that actually fails */ +await Promise.resolve(); +throw new Error("leaf rejected"); diff --git a/tests/fixture_reject_mid.js b/tests/fixture_reject_mid.js new file mode 100644 index 000000000..413867076 --- /dev/null +++ b/tests/fixture_reject_mid.js @@ -0,0 +1,2 @@ +import "./fixture_reject_leaf.js"; +export const mid = 1; diff --git a/tests/fixture_reject_sib_a.js b/tests/fixture_reject_sib_a.js new file mode 100644 index 000000000..f80a641a0 --- /dev/null +++ b/tests/fixture_reject_sib_a.js @@ -0,0 +1,2 @@ +import "./fixture_reject_sib_leaf.js"; +export const a = 1; diff --git a/tests/fixture_reject_sib_b.js b/tests/fixture_reject_sib_b.js new file mode 100644 index 000000000..c5a49bef2 --- /dev/null +++ b/tests/fixture_reject_sib_b.js @@ -0,0 +1,2 @@ +import "./fixture_reject_sib_leaf.js"; +export const b = 1; diff --git a/tests/fixture_reject_sib_leaf.js b/tests/fixture_reject_sib_leaf.js new file mode 100644 index 000000000..c2d043d74 --- /dev/null +++ b/tests/fixture_reject_sib_leaf.js @@ -0,0 +1,2 @@ +await Promise.resolve(); +throw new Error("sibling leaf rejected"); diff --git a/tests/fixture_reject_top.js b/tests/fixture_reject_top.js new file mode 100644 index 000000000..57306af56 --- /dev/null +++ b/tests/fixture_reject_top.js @@ -0,0 +1,2 @@ +import "./fixture_reject_mid.js"; +export const top = 1; diff --git a/tests/module-async-rejection-order.js b/tests/module-async-rejection-order.js new file mode 100644 index 000000000..d6dd50229 --- /dev/null +++ b/tests/module-async-rejection-order.js @@ -0,0 +1,61 @@ +import { assert } from "./assert.js"; + +/* AsyncModuleExecutionRejected rejects the module's own [[TopLevelCapability]] + before recursing into [[AsyncParentModules]], so a failing async module + settles leaf first and root last. Doing it the other way around settles the + whole chain in reverse. */ + +/* Importing the leaf first, then each ancestor in turn, gives every module in + the chain its own top level capability, which is what makes the order + observable at all. */ +const order = []; +const errors = []; + +function record(name) { + return e => { order.push(name); errors.push(e); }; +} + +const leaf = import("./fixture_reject_leaf.js").then(record("leaf.ok"), + record("leaf")); +const mid = import("./fixture_reject_mid.js").then(record("mid.ok"), + record("mid")); +const top = import("./fixture_reject_top.js").then(record("top.ok"), + record("top")); + +await Promise.all([leaf, mid, top]); + +assert(order.join(" -> "), "leaf -> mid -> top"); + +/* every module in the chain reports the leaf's error, and it is the very + same object, not a copy */ +assert(errors.length, 3); +for (const e of errors) { + assert(e instanceof Error, true); + assert(e.message, "leaf rejected"); + assert(e === errors[0], true); +} + +/* re-importing the failed modules keeps reporting the same error and does + not re-run anything */ +{ + const again = []; + await Promise.all([ + import("./fixture_reject_leaf.js").then(() => again.push("ok"), + e => again.push(e.message)), + import("./fixture_reject_top.js").then(() => again.push("ok"), + e => again.push(e.message)), + ]); + assert(again.length, 2); + assert(again[0], "leaf rejected"); + assert(again[1], "leaf rejected"); +} + +/* two independent ancestors of the same failing leaf are both notified */ +{ + const seen = []; + const a = import("./fixture_reject_sib_a.js").catch(e => seen.push("a")); + const b = import("./fixture_reject_sib_b.js").catch(e => seen.push("b")); + await Promise.all([a, b]); + seen.sort(); + assert(seen.join(","), "a,b"); +}