diff --git a/common.gypi b/common.gypi index ecb5dd907f2b75..851816e084565d 100644 --- a/common.gypi +++ b/common.gypi @@ -36,7 +36,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.8', + 'v8_embedder_string': '-node.9', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/src/builtins/builtins-async-module.cc b/deps/v8/src/builtins/builtins-async-module.cc index 7128ad7e9d35bf..180bbd5df898e7 100644 --- a/deps/v8/src/builtins/builtins-async-module.cc +++ b/deps/v8/src/builtins/builtins-async-module.cc @@ -12,7 +12,15 @@ namespace internal { BUILTIN(CallAsyncModuleFulfilled) { HandleScope handle_scope(isolate); Handle module(args.at(0)); - SourceTextModule::AsyncModuleExecutionFulfilled(isolate, module); + if (SourceTextModule::AsyncModuleExecutionFulfilled(isolate, module) + .IsNothing()) { + // The evaluation of async module can not throwing a JavaScript observable + // exception. + DCHECK(isolate->has_pending_exception()); + DCHECK_EQ(isolate->pending_exception(), + ReadOnlyRoots(isolate).termination_exception()); + return ReadOnlyRoots(isolate).exception(); + } return ReadOnlyRoots(isolate).undefined_value(); } diff --git a/deps/v8/src/objects/source-text-module.cc b/deps/v8/src/objects/source-text-module.cc index 3dfcfac10d7655..dad5641bb2dd8d 100644 --- a/deps/v8/src/objects/source-text-module.cc +++ b/deps/v8/src/objects/source-text-module.cc @@ -749,14 +749,14 @@ MaybeHandle SourceTextModule::Evaluate( return capability; } -void SourceTextModule::AsyncModuleExecutionFulfilled( +Maybe SourceTextModule::AsyncModuleExecutionFulfilled( Isolate* isolate, Handle module) { // 1. If module.[[Status]] is evaluated, then if (module->status() == kErrored) { // a. Assert: module.[[EvaluationError]] is not empty. DCHECK(!module->exception().IsTheHole(isolate)); // b. Return. - return; + return Just(true); } // 3. Assert: module.[[AsyncEvaluating]] is true. DCHECK(module->IsAsyncEvaluating()); @@ -812,7 +812,9 @@ void SourceTextModule::AsyncModuleExecutionFulfilled( } else if (m->async()) { // ii. Otherwise, if m.[[Async]] is *true*, then // a. Perform ! ExecuteAsyncModule(m). - ExecuteAsyncModule(isolate, m); + // The execution may have been terminated and can not be resumed, so just + // raise the exception. + MAYBE_RETURN(ExecuteAsyncModule(isolate, m), Nothing()); } else { // iii. Otherwise, // a. Let _result_ be m.ExecuteModule(). @@ -846,6 +848,7 @@ void SourceTextModule::AsyncModuleExecutionFulfilled( } // 10. Return undefined. + return Just(true); } void SourceTextModule::AsyncModuleExecutionRejected( @@ -905,8 +908,9 @@ void SourceTextModule::AsyncModuleExecutionRejected( } } -void SourceTextModule::ExecuteAsyncModule(Isolate* isolate, - Handle module) { +// static +Maybe SourceTextModule::ExecuteAsyncModule( + Isolate* isolate, Handle module) { // 1. Assert: module.[[Status]] is "evaluating" or "evaluated". CHECK(module->status() == kEvaluating || module->status() == kEvaluated); @@ -956,9 +960,19 @@ void SourceTextModule::ExecuteAsyncModule(Isolate* isolate, // Note: In V8 we have broken module.ExecuteModule into // ExecuteModule for synchronous module execution and // InnerExecuteAsyncModule for asynchronous execution. - InnerExecuteAsyncModule(isolate, module, capability).ToHandleChecked(); + MaybeHandle ret = + InnerExecuteAsyncModule(isolate, module, capability); + if (ret.is_null()) { + // The evaluation of async module can not throwing a JavaScript observable + // exception. + DCHECK(isolate->has_pending_exception()); + DCHECK_EQ(isolate->pending_exception(), + ReadOnlyRoots(isolate).termination_exception()); + return Nothing(); + } // 13. Return. + return Just(true); } MaybeHandle SourceTextModule::InnerExecuteAsyncModule( @@ -1145,8 +1159,11 @@ MaybeHandle SourceTextModule::InnerModuleEvaluation( // c. If module.[[PendingAsyncDependencies]] is 0, // perform ! ExecuteAsyncModule(_module_). + // The execution may have been terminated and can not be resumed, so just + // raise the exception. if (!module->HasPendingAsyncDependencies()) { - SourceTextModule::ExecuteAsyncModule(isolate, module); + MAYBE_RETURN(SourceTextModule::ExecuteAsyncModule(isolate, module), + MaybeHandle()); } } else { // 15. Otherwise, perform ? module.ExecuteModule(). diff --git a/deps/v8/src/objects/source-text-module.h b/deps/v8/src/objects/source-text-module.h index 9894973d9d9a61..63a0f8021aac74 100644 --- a/deps/v8/src/objects/source-text-module.h +++ b/deps/v8/src/objects/source-text-module.h @@ -54,9 +54,10 @@ class SourceTextModule static int ExportIndex(int cell_index); // Used by builtins to fulfill or reject the promise associated - // with async SourceTextModules. - static void AsyncModuleExecutionFulfilled(Isolate* isolate, - Handle module); + // with async SourceTextModules. Return Nothing if the execution is + // terminated. + static Maybe AsyncModuleExecutionFulfilled( + Isolate* isolate, Handle module); static void AsyncModuleExecutionRejected(Isolate* isolate, Handle module, Handle exception); @@ -201,9 +202,10 @@ class SourceTextModule static V8_WARN_UNUSED_RESULT MaybeHandle ExecuteModule( Isolate* isolate, Handle module); - // Implementation of spec ExecuteAsyncModule. - static void ExecuteAsyncModule(Isolate* isolate, - Handle module); + // Implementation of spec ExecuteAsyncModule. Return Nothing if the execution + // is been terminated. + static V8_WARN_UNUSED_RESULT Maybe ExecuteAsyncModule( + Isolate* isolate, Handle module); static void Reset(Isolate* isolate, Handle module); diff --git a/deps/v8/test/cctest/test-api.cc b/deps/v8/test/cctest/test-api.cc index 92f549ad967ad3..01870cda8ded1b 100644 --- a/deps/v8/test/cctest/test-api.cc +++ b/deps/v8/test/cctest/test-api.cc @@ -24649,6 +24649,122 @@ TEST(ImportFromSyntheticModuleThrow) { CHECK(try_catch.HasCaught()); } +namespace { + +v8::MaybeLocal ModuleEvaluateTerminateExecutionResolveCallback( + Local context, Local specifier, + Local import_assertions, Local referrer) { + v8::Isolate* isolate = context->GetIsolate(); + + Local url = v8_str("www.test.com"); + Local source_text = v8_str("await Promise.resolve();"); + v8::ScriptOrigin origin(isolate, url, 0, 0, false, -1, Local(), + false, false, true); + v8::ScriptCompiler::Source source(source_text, origin); + Local module = + v8::ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked(); + module + ->InstantiateModule(context, + ModuleEvaluateTerminateExecutionResolveCallback) + .ToChecked(); + + CHECK_EQ(module->GetStatus(), Module::kInstantiated); + return module; +} + +void ModuleEvaluateTerminateExecution( + const v8::FunctionCallbackInfo& args) { + v8::Isolate::GetCurrent()->TerminateExecution(); +} +} // namespace + +TEST(ModuleEvaluateTerminateExecution) { + LocalContext env; + v8::Isolate* isolate = env->GetIsolate(); + v8::Isolate::Scope iscope(isolate); + v8::HandleScope scope(isolate); + v8::Local context = v8::Context::New(isolate); + v8::Context::Scope cscope(context); + + v8::Local terminate_execution = + v8::Function::New(context, ModuleEvaluateTerminateExecution, + v8_str("terminate_execution")) + .ToLocalChecked(); + context->Global() + ->Set(context, v8_str("terminate_execution"), terminate_execution) + .FromJust(); + + Local url = v8_str("www.test.com"); + Local source_text = v8_str( + "terminate_execution();" + "await Promise.resolve();"); + v8::ScriptOrigin origin(isolate, url, 0, 0, false, -1, Local(), + false, false, true); + v8::ScriptCompiler::Source source(source_text, origin); + Local module = + v8::ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked(); + module + ->InstantiateModule(context, + ModuleEvaluateTerminateExecutionResolveCallback) + .ToChecked(); + + CHECK_EQ(module->GetStatus(), Module::kInstantiated); + TryCatch try_catch(isolate); + v8::MaybeLocal completion_value = module->Evaluate(context); + CHECK(completion_value.IsEmpty()); + + CHECK_EQ(module->GetStatus(), Module::kErrored); + CHECK(try_catch.HasCaught()); + CHECK(try_catch.HasTerminated()); +} + +TEST(ModuleEvaluateImportTerminateExecution) { + LocalContext env; + v8::Isolate* isolate = env->GetIsolate(); + v8::Isolate::Scope iscope(isolate); + v8::HandleScope scope(isolate); + v8::Local context = v8::Context::New(isolate); + v8::Context::Scope cscope(context); + + v8::Local terminate_execution = + v8::Function::New(context, ModuleEvaluateTerminateExecution, + v8_str("terminate_execution")) + .ToLocalChecked(); + context->Global() + ->Set(context, v8_str("terminate_execution"), terminate_execution) + .FromJust(); + + Local url = v8_str("www.test.com"); + Local source_text = v8_str( + "import './synthetic.module';" + "terminate_execution();" + "await Promise.resolve();"); + v8::ScriptOrigin origin(isolate, url, 0, 0, false, -1, Local(), + false, false, true); + v8::ScriptCompiler::Source source(source_text, origin); + Local module = + v8::ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked(); + module + ->InstantiateModule(context, + ModuleEvaluateTerminateExecutionResolveCallback) + .ToChecked(); + + CHECK_EQ(module->GetStatus(), Module::kInstantiated); + TryCatch try_catch(isolate); + v8::MaybeLocal completion_value = module->Evaluate(context); + Local promise( + Local::Cast(completion_value.ToLocalChecked())); + CHECK_EQ(promise->State(), v8::Promise::kPending); + isolate->PerformMicrotaskCheckpoint(); + + // The exception thrown by terminate execution is not catchable by JavaScript + // so the promise can not be settled. + CHECK_EQ(promise->State(), v8::Promise::kPending); + CHECK_EQ(module->GetStatus(), Module::kEvaluated); + CHECK(try_catch.HasCaught()); + CHECK(try_catch.HasTerminated()); +} + // Tests that the code cache does not confuse the same source code compiled as a // script and as a module. TEST(CodeCacheModuleScriptMismatch) { diff --git a/test/parallel/test-worker-process-exit-async-module.js b/test/parallel/test-worker-process-exit-async-module.js new file mode 100644 index 00000000000000..38d4ad74c7bd85 --- /dev/null +++ b/test/parallel/test-worker-process-exit-async-module.js @@ -0,0 +1,11 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { Worker } = require('worker_threads'); + +// Regression for https://github.com/nodejs/node/issues/43182. +const w = new Worker(new URL('data:text/javascript,process.exit(1);await new Promise(()=>{ process.exit(2); })')); +w.on('exit', common.mustCall((code) => { + assert.strictEqual(code, 1); +}));