Skip to content

Commit 22698d2

Browse files
legendecasV8 LUCI CQ
authored andcommitted
[module] Fix aborts in terminated async module evaluation
SourceTextModule::ExecuteAsyncModule asserts the execution of the module's async function to succeed without exception. However, the problem is that TerminateExecution initiated by embedders is breaking that assumption. The execution can be terminated with an exception and the exception is not catchable by JavaScript. The uncatchable exceptions during the async module evaluation need to be raised to the embedder and not crash the process if possible. Refs: nodejs/node#43182 Change-Id: Ifc152428b95945b6b49a2f70ba35018cfc0ce40b Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3696493 Reviewed-by: Camillo Bruni <[email protected]> Commit-Queue: Chengzhong Wu <[email protected]> Cr-Commit-Position: refs/heads/main@{#81307}
1 parent 96e939d commit 22698d2

4 files changed

Lines changed: 153 additions & 14 deletions

File tree

src/builtins/builtins-async-module.cc

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@ BUILTIN(CallAsyncModuleFulfilled) {
1515
SourceTextModule::cast(isolate->context().get(
1616
SourceTextModule::ExecuteAsyncModuleContextSlots::kModule)),
1717
isolate);
18-
SourceTextModule::AsyncModuleExecutionFulfilled(isolate, module);
18+
if (SourceTextModule::AsyncModuleExecutionFulfilled(isolate, module)
19+
.IsNothing()) {
20+
// The evaluation of async module can not throwing a JavaScript observable
21+
// exception.
22+
DCHECK(isolate->is_execution_termination_pending());
23+
return ReadOnlyRoots(isolate).exception();
24+
}
1925
return ReadOnlyRoots(isolate).undefined_value();
2026
}
2127

src/objects/source-text-module.cc

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -749,14 +749,14 @@ MaybeHandle<Object> SourceTextModule::Evaluate(
749749
return capability;
750750
}
751751

752-
void SourceTextModule::AsyncModuleExecutionFulfilled(
752+
Maybe<bool> SourceTextModule::AsyncModuleExecutionFulfilled(
753753
Isolate* isolate, Handle<SourceTextModule> module) {
754754
// 1. If module.[[Status]] is evaluated, then
755755
if (module->status() == kErrored) {
756756
// a. Assert: module.[[EvaluationError]] is not empty.
757757
DCHECK(!module->exception().IsTheHole(isolate));
758758
// b. Return.
759-
return;
759+
return Just(true);
760760
}
761761
// 3. Assert: module.[[AsyncEvaluating]] is true.
762762
DCHECK(module->IsAsyncEvaluating());
@@ -812,7 +812,9 @@ void SourceTextModule::AsyncModuleExecutionFulfilled(
812812
} else if (m->async()) {
813813
// ii. Otherwise, if m.[[Async]] is *true*, then
814814
// a. Perform ! ExecuteAsyncModule(m).
815-
ExecuteAsyncModule(isolate, m);
815+
// The execution may have been terminated and can not be resumed, so just
816+
// raise the exception.
817+
MAYBE_RETURN(ExecuteAsyncModule(isolate, m), Nothing<bool>());
816818
} else {
817819
// iii. Otherwise,
818820
// a. Let _result_ be m.ExecuteModule().
@@ -846,6 +848,7 @@ void SourceTextModule::AsyncModuleExecutionFulfilled(
846848
}
847849

848850
// 10. Return undefined.
851+
return Just(true);
849852
}
850853

851854
void SourceTextModule::AsyncModuleExecutionRejected(
@@ -905,8 +908,9 @@ void SourceTextModule::AsyncModuleExecutionRejected(
905908
}
906909
}
907910

908-
void SourceTextModule::ExecuteAsyncModule(Isolate* isolate,
909-
Handle<SourceTextModule> module) {
911+
// static
912+
Maybe<bool> SourceTextModule::ExecuteAsyncModule(
913+
Isolate* isolate, Handle<SourceTextModule> module) {
910914
// 1. Assert: module.[[Status]] is "evaluating" or "evaluated".
911915
CHECK(module->status() == kEvaluating || module->status() == kEvaluated);
912916

@@ -961,9 +965,17 @@ void SourceTextModule::ExecuteAsyncModule(Isolate* isolate,
961965
// Note: In V8 we have broken module.ExecuteModule into
962966
// ExecuteModule for synchronous module execution and
963967
// InnerExecuteAsyncModule for asynchronous execution.
964-
InnerExecuteAsyncModule(isolate, module, capability).ToHandleChecked();
968+
MaybeHandle<Object> ret =
969+
InnerExecuteAsyncModule(isolate, module, capability);
970+
if (ret.is_null()) {
971+
// The evaluation of async module can not throwing a JavaScript observable
972+
// exception.
973+
DCHECK(isolate->is_execution_termination_pending());
974+
return Nothing<bool>();
975+
}
965976

966977
// 13. Return.
978+
return Just<bool>(true);
967979
}
968980

969981
MaybeHandle<Object> SourceTextModule::InnerExecuteAsyncModule(
@@ -1150,8 +1162,11 @@ MaybeHandle<Object> SourceTextModule::InnerModuleEvaluation(
11501162

11511163
// c. If module.[[PendingAsyncDependencies]] is 0,
11521164
// perform ! ExecuteAsyncModule(_module_).
1165+
// The execution may have been terminated and can not be resumed, so just
1166+
// raise the exception.
11531167
if (!module->HasPendingAsyncDependencies()) {
1154-
SourceTextModule::ExecuteAsyncModule(isolate, module);
1168+
MAYBE_RETURN(SourceTextModule::ExecuteAsyncModule(isolate, module),
1169+
MaybeHandle<Object>());
11551170
}
11561171
} else {
11571172
// 15. Otherwise, perform ? module.ExecuteModule().

src/objects/source-text-module.h

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,10 @@ class SourceTextModule
5555
static int ExportIndex(int cell_index);
5656

5757
// Used by builtins to fulfill or reject the promise associated
58-
// with async SourceTextModules.
59-
static void AsyncModuleExecutionFulfilled(Isolate* isolate,
60-
Handle<SourceTextModule> module);
58+
// with async SourceTextModules. Return Nothing if the execution is
59+
// terminated.
60+
static Maybe<bool> AsyncModuleExecutionFulfilled(
61+
Isolate* isolate, Handle<SourceTextModule> module);
6162
static void AsyncModuleExecutionRejected(Isolate* isolate,
6263
Handle<SourceTextModule> module,
6364
Handle<Object> exception);
@@ -211,9 +212,10 @@ class SourceTextModule
211212
static V8_WARN_UNUSED_RESULT MaybeHandle<Object> ExecuteModule(
212213
Isolate* isolate, Handle<SourceTextModule> module);
213214

214-
// Implementation of spec ExecuteAsyncModule.
215-
static void ExecuteAsyncModule(Isolate* isolate,
216-
Handle<SourceTextModule> module);
215+
// Implementation of spec ExecuteAsyncModule. Return Nothing if the execution
216+
// is been terminated.
217+
static V8_WARN_UNUSED_RESULT Maybe<bool> ExecuteAsyncModule(
218+
Isolate* isolate, Handle<SourceTextModule> module);
217219

218220
static void Reset(Isolate* isolate, Handle<SourceTextModule> module);
219221

test/cctest/test-api.cc

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24368,6 +24368,122 @@ TEST(ImportFromSyntheticModuleThrow) {
2436824368
CHECK(try_catch.HasCaught());
2436924369
}
2437024370

24371+
namespace {
24372+
24373+
v8::MaybeLocal<Module> ModuleEvaluateTerminateExecutionResolveCallback(
24374+
Local<Context> context, Local<String> specifier,
24375+
Local<FixedArray> import_assertions, Local<Module> referrer) {
24376+
v8::Isolate* isolate = context->GetIsolate();
24377+
24378+
Local<String> url = v8_str("www.test.com");
24379+
Local<String> source_text = v8_str("await Promise.resolve();");
24380+
v8::ScriptOrigin origin(isolate, url, 0, 0, false, -1, Local<v8::Value>(),
24381+
false, false, true);
24382+
v8::ScriptCompiler::Source source(source_text, origin);
24383+
Local<Module> module =
24384+
v8::ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
24385+
module
24386+
->InstantiateModule(context,
24387+
ModuleEvaluateTerminateExecutionResolveCallback)
24388+
.ToChecked();
24389+
24390+
CHECK_EQ(module->GetStatus(), Module::kInstantiated);
24391+
return module;
24392+
}
24393+
24394+
void ModuleEvaluateTerminateExecution(
24395+
const v8::FunctionCallbackInfo<v8::Value>& args) {
24396+
v8::Isolate::GetCurrent()->TerminateExecution();
24397+
}
24398+
} // namespace
24399+
24400+
TEST(ModuleEvaluateTerminateExecution) {
24401+
LocalContext env;
24402+
v8::Isolate* isolate = env->GetIsolate();
24403+
v8::Isolate::Scope iscope(isolate);
24404+
v8::HandleScope scope(isolate);
24405+
v8::Local<v8::Context> context = v8::Context::New(isolate);
24406+
v8::Context::Scope cscope(context);
24407+
24408+
v8::Local<v8::Function> terminate_execution =
24409+
v8::Function::New(context, ModuleEvaluateTerminateExecution,
24410+
v8_str("terminate_execution"))
24411+
.ToLocalChecked();
24412+
context->Global()
24413+
->Set(context, v8_str("terminate_execution"), terminate_execution)
24414+
.FromJust();
24415+
24416+
Local<String> url = v8_str("www.test.com");
24417+
Local<String> source_text = v8_str(
24418+
"terminate_execution();"
24419+
"await Promise.resolve();");
24420+
v8::ScriptOrigin origin(isolate, url, 0, 0, false, -1, Local<v8::Value>(),
24421+
false, false, true);
24422+
v8::ScriptCompiler::Source source(source_text, origin);
24423+
Local<Module> module =
24424+
v8::ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
24425+
module
24426+
->InstantiateModule(context,
24427+
ModuleEvaluateTerminateExecutionResolveCallback)
24428+
.ToChecked();
24429+
24430+
CHECK_EQ(module->GetStatus(), Module::kInstantiated);
24431+
TryCatch try_catch(isolate);
24432+
v8::MaybeLocal<Value> completion_value = module->Evaluate(context);
24433+
CHECK(completion_value.IsEmpty());
24434+
24435+
CHECK_EQ(module->GetStatus(), Module::kErrored);
24436+
CHECK(try_catch.HasCaught());
24437+
CHECK(try_catch.HasTerminated());
24438+
}
24439+
24440+
TEST(ModuleEvaluateImportTerminateExecution) {
24441+
LocalContext env;
24442+
v8::Isolate* isolate = env->GetIsolate();
24443+
v8::Isolate::Scope iscope(isolate);
24444+
v8::HandleScope scope(isolate);
24445+
v8::Local<v8::Context> context = v8::Context::New(isolate);
24446+
v8::Context::Scope cscope(context);
24447+
24448+
v8::Local<v8::Function> terminate_execution =
24449+
v8::Function::New(context, ModuleEvaluateTerminateExecution,
24450+
v8_str("terminate_execution"))
24451+
.ToLocalChecked();
24452+
context->Global()
24453+
->Set(context, v8_str("terminate_execution"), terminate_execution)
24454+
.FromJust();
24455+
24456+
Local<String> url = v8_str("www.test.com");
24457+
Local<String> source_text = v8_str(
24458+
"import './synthetic.module';"
24459+
"terminate_execution();"
24460+
"await Promise.resolve();");
24461+
v8::ScriptOrigin origin(isolate, url, 0, 0, false, -1, Local<v8::Value>(),
24462+
false, false, true);
24463+
v8::ScriptCompiler::Source source(source_text, origin);
24464+
Local<Module> module =
24465+
v8::ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
24466+
module
24467+
->InstantiateModule(context,
24468+
ModuleEvaluateTerminateExecutionResolveCallback)
24469+
.ToChecked();
24470+
24471+
CHECK_EQ(module->GetStatus(), Module::kInstantiated);
24472+
TryCatch try_catch(isolate);
24473+
v8::MaybeLocal<Value> completion_value = module->Evaluate(context);
24474+
Local<v8::Promise> promise(
24475+
Local<v8::Promise>::Cast(completion_value.ToLocalChecked()));
24476+
CHECK_EQ(promise->State(), v8::Promise::kPending);
24477+
isolate->PerformMicrotaskCheckpoint();
24478+
24479+
// The exception thrown by terminate execution is not catchable by JavaScript
24480+
// so the promise can not be settled.
24481+
CHECK_EQ(promise->State(), v8::Promise::kPending);
24482+
CHECK_EQ(module->GetStatus(), Module::kEvaluated);
24483+
CHECK(try_catch.HasCaught());
24484+
CHECK(try_catch.HasTerminated());
24485+
}
24486+
2437124487
// Tests that the code cache does not confuse the same source code compiled as a
2437224488
// script and as a module.
2437324489
TEST(CodeCacheModuleScriptMismatch) {

0 commit comments

Comments
 (0)