Commit 596cf1ee authored by Gus Caplan's avatar Gus Caplan Committed by Commit Bot

[top-level-await] fix handling termination exceptions

Properly handle termination exceptions in TLA modules.

Bug: v8:9978
Change-Id: Ica70a55d1f54ec89d175d7c846e9a405eaffe0a0
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1920750
Commit-Queue: Georg Neis <neis@chromium.org>
Reviewed-by: 's avatarGeorg Neis <neis@chromium.org>
Reviewed-by: 's avatarYang Guo <yangguo@chromium.org>
Reviewed-by: 's avatarJoshua Litt <joshualitt@chromium.org>
Cr-Commit-Position: refs/heads/master@{#65135}
parent 5081bf10
......@@ -67,7 +67,12 @@ void Module::RecordError(Isolate* isolate, Handle<Object> error) {
PrintStatusTransition(Module::kErrored);
#endif // DEBUG
set_status(Module::kErrored);
if (isolate->is_catchable_by_javascript(*error)) {
set_exception(*error);
} else {
// v8::TryCatch uses `null` for termination exceptions.
set_exception(*isolate->factory()->null_value());
}
}
void Module::ResetGraph(Isolate* isolate, Handle<Module> module) {
......
......@@ -624,6 +624,16 @@ MaybeHandle<Object> SourceTextModule::EvaluateMaybeAsync(
// 9. If result is an abrupt completion, then
Handle<Object> unused_result;
if (!Evaluate(isolate, module).ToHandle(&unused_result)) {
// If the exception was a termination exception, rejecting the promise
// would resume execution, and our API contract is to return an empty
// handle. The module's status should be set to kErrored and the
// exception field should be set to `null`.
if (!isolate->is_catchable_by_javascript(isolate->pending_exception())) {
DCHECK_EQ(module->status(), kErrored);
DCHECK_EQ(module->exception(), *isolate->factory()->null_value());
return {};
}
// d. Perform ! Call(capability.[[Reject]], undefined,
// «result.[[Value]]»).
isolate->clear_pending_exception();
......@@ -669,7 +679,14 @@ MaybeHandle<Object> SourceTextModule::Evaluate(
// iii. Set m.[[EvaluationError]] to result.
descendant->RecordErrorUsingPendingException(isolate);
}
DCHECK_EQ(module->exception(), isolate->pending_exception());
#ifdef DEBUG
if (isolate->is_catchable_by_javascript(isolate->pending_exception())) {
CHECK_EQ(module->exception(), isolate->pending_exception());
} else {
CHECK_EQ(module->exception(), *isolate->factory()->null_value());
}
#endif // DEBUG
} else {
// 10. Otherwise,
// c. Assert: stack is empty.
......@@ -770,6 +787,8 @@ void SourceTextModule::AsyncModuleExecutionFulfilled(
void SourceTextModule::AsyncModuleExecutionRejected(
Isolate* isolate, Handle<SourceTextModule> module,
Handle<Object> exception) {
DCHECK(isolate->is_catchable_by_javascript(*exception));
// 1. Assert: module.[[Status]] is "evaluated".
CHECK(module->status() == kEvaluated || module->status() == kErrored);
......
......@@ -886,4 +886,99 @@ TEST(ModuleEvaluationTopLevelAwaitDynamicImportError) {
i::FLAG_harmony_dynamic_import = previous_dynamic_import_flag_value;
}
TEST(TerminateExecutionTopLevelAwaitSync) {
bool previous_top_level_await_flag_value = i::FLAG_harmony_top_level_await;
i::FLAG_harmony_top_level_await = true;
Isolate* isolate = CcTest::isolate();
HandleScope scope(isolate);
LocalContext env;
v8::TryCatch try_catch(isolate);
env.local()
->Global()
->Set(env.local(), v8_str("terminate"),
v8::Function::New(env.local(),
[](const v8::FunctionCallbackInfo<Value>& info) {
info.GetIsolate()->TerminateExecution();
})
.ToLocalChecked())
.ToChecked();
Local<String> source_text = v8_str("terminate(); while (true) {}");
ScriptOrigin origin = ModuleOrigin(v8_str("file.js"), CcTest::isolate());
ScriptCompiler::Source source(source_text, origin);
Local<Module> module =
ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
CHECK(module
->InstantiateModule(env.local(),
CompileSpecifierAsModuleResolveCallback)
.FromJust());
CHECK(module->Evaluate(env.local()).IsEmpty());
CHECK(try_catch.HasCaught());
CHECK(try_catch.HasTerminated());
CHECK_EQ(module->GetStatus(), Module::kErrored);
CHECK_EQ(module->GetException(), v8::Null(isolate));
i::FLAG_harmony_top_level_await = previous_top_level_await_flag_value;
}
TEST(TerminateExecutionTopLevelAwaitAsync) {
bool previous_top_level_await_flag_value = i::FLAG_harmony_top_level_await;
i::FLAG_harmony_top_level_await = true;
Isolate* isolate = CcTest::isolate();
HandleScope scope(isolate);
LocalContext env;
v8::TryCatch try_catch(isolate);
env.local()
->Global()
->Set(env.local(), v8_str("terminate"),
v8::Function::New(env.local(),
[](const v8::FunctionCallbackInfo<Value>& info) {
info.GetIsolate()->TerminateExecution();
})
.ToLocalChecked())
.ToChecked();
Local<Promise::Resolver> eval_promise =
Promise::Resolver::New(env.local()).ToLocalChecked();
env.local()
->Global()
->Set(env.local(), v8_str("evalPromise"), eval_promise)
.ToChecked();
Local<String> source_text =
v8_str("await evalPromise; terminate(); while (true) {}");
ScriptOrigin origin = ModuleOrigin(v8_str("file.js"), CcTest::isolate());
ScriptCompiler::Source source(source_text, origin);
Local<Module> module =
ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
CHECK(module
->InstantiateModule(env.local(),
CompileSpecifierAsModuleResolveCallback)
.FromJust());
Local<Promise> promise =
Local<Promise>::Cast(module->Evaluate(env.local()).ToLocalChecked());
CHECK_EQ(module->GetStatus(), Module::kEvaluated);
CHECK_EQ(promise->State(), Promise::PromiseState::kPending);
CHECK(!try_catch.HasCaught());
CHECK(!try_catch.HasTerminated());
eval_promise->Resolve(env.local(), v8::Undefined(isolate)).ToChecked();
CHECK(try_catch.HasCaught());
CHECK(try_catch.HasTerminated());
CHECK_EQ(promise->State(), Promise::PromiseState::kPending);
// The termination exception doesn't trigger the module's
// catch handler, so the module isn't transitioned to kErrored.
CHECK_EQ(module->GetStatus(), Module::kEvaluated);
i::FLAG_harmony_top_level_await = previous_top_level_await_flag_value;
}
} // anonymous namespace
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment