Commit dd6d96c8 authored by Joyee Cheung's avatar Joyee Cheung Committed by V8 LUCI CQ

[api] add v8::Module::GetStalledTopLevelAwaitMessage()

Adds Module::GetStalledTopLevelAwaitMessage() API which searches for
modules that have no pending async dependencies but have not yet
resolved. An embedder may call this API when they are about to exit
to check if TLA evaluation has stalled and provide a better error
message.

Change-Id: I3b88802f70cc84c973551f13d73ef3e3d06f4027
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2341765
Commit-Queue: Joyee Cheung <joyee@igalia.com>
Commit-Queue: Camillo Bruni <cbruni@chromium.org>
Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/main@{#81080}
parent 693db0a3
......@@ -20,6 +20,7 @@
namespace v8 {
class Function;
class Message;
class Object;
class PrimitiveArray;
class Script;
......@@ -292,6 +293,16 @@ class V8_EXPORT Module : public Data {
V8_WARN_UNUSED_RESULT Maybe<bool> SetSyntheticModuleExport(
Isolate* isolate, Local<String> export_name, Local<Value> export_value);
/**
* Search the modules requested directly or indirectly by the module for
* any top-level await that has not yet resolved. If there is any, the
* returned vector contains a tuple of the unresolved module and a message
* with the pending top-level await.
* An embedder may call this before exiting to improve error messages.
*/
std::vector<std::tuple<Local<Module>, Local<Message>>>
GetStalledTopLevelAwaitMessage(Isolate* isolate);
V8_INLINE static Module* Cast(Data* data);
private:
......
......@@ -2467,6 +2467,33 @@ Maybe<bool> Module::SetSyntheticModuleExport(Isolate* v8_isolate,
return Just(true);
}
std::vector<std::tuple<Local<Module>, Local<Message>>>
Module::GetStalledTopLevelAwaitMessage(Isolate* isolate) {
auto i_isolate = reinterpret_cast<i::Isolate*>(isolate);
i::Handle<i::Module> self = Utils::OpenHandle(this);
Utils::ApiCheck(self->IsSourceTextModule(),
"v8::Module::GetStalledTopLevelAwaitMessage",
"v8::Module::GetStalledTopLevelAwaitMessage must only be "
"called on a SourceTextModule");
std::vector<
std::tuple<i::Handle<i::SourceTextModule>, i::Handle<i::JSMessageObject>>>
stalled_awaits = i::Handle<i::SourceTextModule>::cast(self)
->GetStalledTopLevelAwaitMessage(i_isolate);
std::vector<std::tuple<Local<Module>, Local<Message>>> result;
size_t stalled_awaits_count = stalled_awaits.size();
if (stalled_awaits_count == 0) {
return result;
}
result.reserve(stalled_awaits_count);
for (size_t i = 0; i < stalled_awaits_count; ++i) {
auto [module, message] = stalled_awaits[i];
result.push_back(std::make_tuple(ToApiHandle<Module>(module),
ToApiHandle<Message>(message)));
}
return result;
}
namespace {
i::ScriptDetails GetScriptDetails(
......
......@@ -325,6 +325,7 @@ namespace internal {
T(SymbolToNumber, "Cannot convert a Symbol value to a number") \
T(SymbolToString, "Cannot convert a Symbol value to a string") \
T(ThrowMethodMissing, "The iterator does not provide a 'throw' method.") \
T(TopLevelAwaitStalled, "Top-level await promise never resolved") \
T(UndefinedOrNullToObject, "Cannot convert undefined or null to object") \
T(ValueAndAccessor, \
"Invalid property descriptor. Cannot both specify accessors and a value " \
......
......@@ -46,6 +46,7 @@
#include "src/debug/debug-interface.h"
#include "src/deoptimizer/deoptimizer.h"
#include "src/diagnostics/basic-block-profiler.h"
#include "src/execution/microtask-queue.h"
#include "src/execution/v8threads.h"
#include "src/execution/vm-state-inl.h"
#include "src/flags/flags.h"
......@@ -1455,7 +1456,10 @@ bool Shell::ExecuteModule(Isolate* isolate, const char* file_name) {
// Loop until module execution finishes
Local<Promise> result_promise(result.As<Promise>());
while (result_promise->State() == Promise::kPending) {
while (result_promise->State() == Promise::kPending &&
reinterpret_cast<i::Isolate*>(isolate)
->default_microtask_queue()
->size() > 0) {
Shell::CompleteMessageLoop(isolate);
}
......@@ -1473,6 +1477,14 @@ bool Shell::ExecuteModule(Isolate* isolate, const char* file_name) {
return false;
}
std::vector<std::tuple<Local<Module>, Local<Message>>> stalled =
root_module->GetStalledTopLevelAwaitMessage(isolate);
if (stalled.size() > 0) {
Local<Message> message = std::get<1>(stalled[0]);
ReportException(isolate, message, v8::Exception::Error(message->Get()));
return false;
}
DCHECK(!try_catch.HasCaught());
return true;
}
......
......@@ -32,6 +32,7 @@ class JSGeneratorObject
// For suspended generators: the source position at which the generator
// is suspended.
int source_position() const;
int code_offset() const;
// Dispatched behavior.
DECL_PRINTER(JSGeneratorObject)
......
......@@ -6768,21 +6768,25 @@ bool PropertyCell::CanTransitionTo(PropertyDetails new_details,
}
#endif // DEBUG
int JSGeneratorObject::code_offset() const {
DCHECK(input_or_debug_pos().IsSmi());
int code_offset = Smi::ToInt(input_or_debug_pos());
// The stored bytecode offset is relative to a different base than what
// is used in the source position table, hence the subtraction.
code_offset -= BytecodeArray::kHeaderSize - kHeapObjectTag;
return code_offset;
}
int JSGeneratorObject::source_position() const {
CHECK(is_suspended());
DCHECK(function().shared().HasBytecodeArray());
Isolate* isolate = GetIsolate();
DCHECK(
function().shared().GetBytecodeArray(isolate).HasSourcePositionTable());
int code_offset = Smi::ToInt(input_or_debug_pos());
// The stored bytecode offset is relative to a different base than what
// is used in the source position table, hence the subtraction.
code_offset -= BytecodeArray::kHeaderSize - kHeapObjectTag;
AbstractCode code =
AbstractCode::cast(function().shared().GetBytecodeArray(isolate));
return code.SourcePosition(code_offset);
return code.SourcePosition(code_offset());
}
// static
......
......@@ -1186,5 +1186,59 @@ void SourceTextModule::Reset(Isolate* isolate,
module->set_dfs_ancestor_index(-1);
}
std::vector<std::tuple<Handle<SourceTextModule>, Handle<JSMessageObject>>>
SourceTextModule::GetStalledTopLevelAwaitMessage(Isolate* isolate) {
Zone zone(isolate->allocator(), ZONE_NAME);
UnorderedModuleSet visited(&zone);
std::vector<std::tuple<Handle<SourceTextModule>, Handle<JSMessageObject>>>
result;
std::vector<Handle<SourceTextModule>> stalled_modules;
InnerGetStalledTopLevelAwaitModule(isolate, &visited, &stalled_modules);
size_t stalled_modules_size = stalled_modules.size();
if (stalled_modules_size == 0) return result;
result.reserve(stalled_modules_size);
for (size_t i = 0; i < stalled_modules_size; ++i) {
Handle<SourceTextModule> found = stalled_modules[i];
CHECK(found->code().IsJSGeneratorObject());
Handle<JSGeneratorObject> code(JSGeneratorObject::cast(found->code()),
isolate);
Handle<SharedFunctionInfo> shared(found->GetSharedFunctionInfo(), isolate);
Handle<Object> script(shared->script(), isolate);
MessageLocation location = MessageLocation(Handle<Script>::cast(script),
shared, code->code_offset());
Handle<JSMessageObject> message = MessageHandler::MakeMessageObject(
isolate, MessageTemplate::kTopLevelAwaitStalled, &location,
isolate->factory()->null_value(), Handle<FixedArray>());
result.push_back(std::make_tuple(found, message));
}
return result;
}
void SourceTextModule::InnerGetStalledTopLevelAwaitModule(
Isolate* isolate, UnorderedModuleSet* visited,
std::vector<Handle<SourceTextModule>>* result) {
DisallowGarbageCollection no_gc;
// If it's a module that is waiting for no other modules but itself,
// it's what we are looking for. Add it to the results.
if (!HasPendingAsyncDependencies() && IsAsyncEvaluating()) {
result->push_back(handle(*this, isolate));
return;
}
// The module isn't what we are looking for, continue looking in the graph.
FixedArray requested = requested_modules();
int length = requested.length();
for (int i = 0; i < length; ++i) {
Module requested_module = Module::cast(requested.get(i));
if (requested_module.IsSourceTextModule() &&
visited->insert(handle(requested_module, isolate)).second) {
SourceTextModule source_text_module =
SourceTextModule::cast(requested_module);
source_text_module.InnerGetStalledTopLevelAwaitModule(isolate, visited,
result);
}
}
}
} // namespace internal
} // namespace v8
......@@ -83,6 +83,10 @@ class SourceTextModule
kContextLength,
};
V8_EXPORT_PRIVATE
std::vector<std::tuple<Handle<SourceTextModule>, Handle<JSMessageObject>>>
GetStalledTopLevelAwaitMessage(Isolate* isolate);
private:
friend class Factory;
friend class Module;
......@@ -213,6 +217,10 @@ class SourceTextModule
static void Reset(Isolate* isolate, Handle<SourceTextModule> module);
V8_EXPORT_PRIVATE void InnerGetStalledTopLevelAwaitModule(
Isolate* isolate, UnorderedModuleSet* visited,
std::vector<Handle<SourceTextModule>>* result);
TQ_OBJECT_CONSTRUCTORS(SourceTextModule)
};
......
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
await new Promise(() => {});
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'modules-skip-stalled-top-level-await.mjs';
# Copyright 2022 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
*:5: Error: Top-level await promise never resolved
await new Promise(() => {});
^
Error: Top-level await promise never resolved
......@@ -46,6 +46,121 @@ TEST_F(ScriptTest, UnboundScriptPosition) {
EXPECT_EQ(0, column_number);
}
namespace {
v8::Local<v8::String> v8_str(const char* x) {
return v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), x).ToLocalChecked();
}
std::string from_v8_string(Isolate* isolate, Local<String> str) {
String::Utf8Value utf8(isolate, str);
return *utf8;
}
v8::MaybeLocal<Module> ResolveToTopLevelAwait(Local<Context> context,
Local<String> specifier,
Local<FixedArray> assertions,
Local<Module> referrer) {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
v8::ScriptOrigin origin(isolate, specifier, 0, 0, false, -1, Local<Value>(),
false, false, true);
String::Utf8Value specifier_string(isolate, specifier);
std::string source_string =
"const promise = new Promise((resolve, reject) => {\n"
" if (should_resolve) {\n"
" resolve();\n"
" }\n"
"});\n"
"await promise;\n";
if (strncmp(*specifier_string, "stall", strlen("stall")) == 0) {
source_string = "const should_resolve = false;\n" + source_string;
} else if (strncmp(*specifier_string, "resolve", strlen("resolve")) == 0) {
source_string = "const should_resolve = true;\n" + source_string;
} else {
UNREACHABLE();
}
v8::ScriptCompiler::Source source(v8_str(source_string.c_str()), origin);
auto res = v8::ScriptCompiler::CompileModule(isolate, &source);
return res;
}
void TestGetStalledTopLevelAwaitMessage(
v8::Isolate* isolate, const char* source_str,
std::vector<std::string> expected_stalled) {
v8::Isolate::Scope iscope(isolate);
v8::HandleScope scope(isolate);
v8::Local<v8::Context> context = v8::Context::New(isolate);
v8::Context::Scope cscope(context);
v8::ScriptOrigin origin(isolate, v8_str("root.mjs"), 0, 0, false, -1,
Local<Value>(), false, false, true);
v8::ScriptCompiler::Source source(v8_str(source_str), origin);
Local<Module> root =
v8::ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
CHECK(root->InstantiateModule(context, ResolveToTopLevelAwait)
.FromMaybe(false));
Local<v8::Promise> promise =
root->Evaluate(context).ToLocalChecked().As<v8::Promise>();
isolate->PerformMicrotaskCheckpoint();
CHECK_EQ(expected_stalled.size() > 0 ? v8::Promise::PromiseState::kPending
: v8::Promise::PromiseState::kFulfilled,
promise->State());
std::vector<std::tuple<Local<Module>, Local<Message>>> stalled =
root->GetStalledTopLevelAwaitMessage(isolate);
CHECK_EQ(expected_stalled.size(), stalled.size());
for (size_t i = 0; i < stalled.size(); ++i) {
Local<Message> message = std::get<1>(stalled[i]);
CHECK_EQ("Top-level await promise never resolved",
from_v8_string(isolate, message->Get()));
CHECK_EQ(
expected_stalled[i],
from_v8_string(isolate, message->GetScriptResourceName().As<String>()));
CHECK_EQ("await promise;",
from_v8_string(isolate,
message->GetSourceLine(context).ToLocalChecked()));
CHECK_EQ(7, message->GetLineNumber(context).ToChecked());
CHECK_EQ(0, message->GetStartColumn(context).ToChecked());
CHECK_EQ(1, message->GetEndColumn(context).ToChecked());
}
}
} // namespace
TEST_F(ScriptTest, GetSingleStalledTopLevelAwaitMessage) {
TestGetStalledTopLevelAwaitMessage(isolate(), "import 'stall.mjs';",
{"stall.mjs"});
}
TEST_F(ScriptTest, GetMultipleStalledTopLevelAwaitMessage) {
TestGetStalledTopLevelAwaitMessage(
isolate(),
"import 'stall.mjs';\n"
"import 'stall_2.mjs';\n"
"import 'stall_3.mjs';\n"
"import 'stall_4.mjs';\n",
{"stall.mjs", "stall_2.mjs", "stall_3.mjs", "stall_4.mjs"});
}
TEST_F(ScriptTest, GetMixedStalledTopLevelAwaitMessage) {
TestGetStalledTopLevelAwaitMessage(isolate(),
"import 'stall.mjs';\n"
"import 'resolve.mjs';\n"
"import 'stall_2.mjs';\n"
"import 'resolve.mjs';\n",
{"stall.mjs", "stall_2.mjs"});
}
TEST_F(ScriptTest, GetEmptyStalledTopLevelAwaitMessage) {
TestGetStalledTopLevelAwaitMessage(isolate(),
"import 'resolve.mjs';\n"
"import 'resolve_2.mjs';\n"
"import 'resolve_3.mjs';\n",
{});
}
} // namespace
} // namespace v8
......@@ -83,7 +83,7 @@ bytecodes: [
/* 48 E> */ B(DefineKeyedOwnProperty), R(this), R(0), U8(0),
/* 53 S> */ B(LdaImmutableCurrentContextSlot), U8(3),
/* 58 E> */ B(GetKeyedProperty), R(this), U8(2),
B(Wide), B(LdaSmi), I16(300),
B(Wide), B(LdaSmi), I16(301),
B(Star2),
B(LdaConstant), U8(0),
B(Star3),
......@@ -115,7 +115,7 @@ bytecodes: [
/* 41 E> */ B(DefineKeyedOwnProperty), R(this), R(0), U8(0),
/* 46 S> */ B(LdaImmutableCurrentContextSlot), U8(3),
/* 51 E> */ B(GetKeyedProperty), R(this), U8(2),
B(Wide), B(LdaSmi), I16(299),
B(Wide), B(LdaSmi), I16(300),
B(Star2),
B(LdaConstant), U8(0),
B(Star3),
......@@ -149,7 +149,7 @@ bytecodes: [
B(Star2),
B(LdaImmutableCurrentContextSlot), U8(3),
/* 58 E> */ B(GetKeyedProperty), R(this), U8(2),
B(Wide), B(LdaSmi), I16(300),
B(Wide), B(LdaSmi), I16(301),
B(Star3),
B(LdaConstant), U8(0),
B(Star4),
......@@ -181,7 +181,7 @@ bytecodes: [
/* 41 E> */ B(DefineKeyedOwnProperty), R(this), R(0), U8(0),
/* 46 S> */ B(LdaImmutableCurrentContextSlot), U8(3),
/* 51 E> */ B(GetKeyedProperty), R(this), U8(2),
B(Wide), B(LdaSmi), I16(299),
B(Wide), B(LdaSmi), I16(300),
B(Star2),
B(LdaConstant), U8(0),
B(Star3),
......
......@@ -58,7 +58,7 @@ bytecodes: [
B(Star2),
B(LdaImmutableCurrentContextSlot), U8(3),
/* 54 E> */ B(GetKeyedProperty), R(this), U8(2),
B(Wide), B(LdaSmi), I16(298),
B(Wide), B(LdaSmi), I16(299),
B(Star3),
B(LdaConstant), U8(0),
B(Star4),
......@@ -91,7 +91,7 @@ bytecodes: [
/* 44 E> */ B(DefineKeyedOwnProperty), R(this), R(0), U8(0),
/* 49 S> */ B(LdaImmutableCurrentContextSlot), U8(3),
/* 54 E> */ B(GetKeyedProperty), R(this), U8(2),
B(Wide), B(LdaSmi), I16(298),
B(Wide), B(LdaSmi), I16(299),
B(Star2),
B(LdaConstant), U8(0),
B(Star3),
......
......@@ -24,7 +24,7 @@ bytecodes: [
B(TestReferenceEqual), R(this),
B(Mov), R(this), R(1),
B(JumpIfTrue), U8(16),
B(Wide), B(LdaSmi), I16(292),
B(Wide), B(LdaSmi), I16(293),
B(Star2),
B(LdaConstant), U8(0),
B(Star3),
......@@ -61,13 +61,13 @@ bytecodes: [
B(TestReferenceEqual), R(this),
B(Mov), R(this), R(0),
B(JumpIfTrue), U8(16),
B(Wide), B(LdaSmi), I16(292),
B(Wide), B(LdaSmi), I16(293),
B(Star2),
B(LdaConstant), U8(0),
B(Star3),
/* 61 E> */ B(CallRuntime), U16(Runtime::kNewTypeError), R(2), U8(2),
B(Throw),
B(Wide), B(LdaSmi), I16(298),
B(Wide), B(LdaSmi), I16(299),
B(Star2),
B(LdaConstant), U8(1),
B(Star3),
......@@ -99,13 +99,13 @@ bytecodes: [
B(TestReferenceEqual), R(this),
B(Mov), R(this), R(0),
B(JumpIfTrue), U8(16),
B(Wide), B(LdaSmi), I16(292),
B(Wide), B(LdaSmi), I16(293),
B(Star1),
B(LdaConstant), U8(0),
B(Star2),
/* 61 E> */ B(CallRuntime), U16(Runtime::kNewTypeError), R(1), U8(2),
B(Throw),
B(Wide), B(LdaSmi), I16(298),
B(Wide), B(LdaSmi), I16(299),
B(Star1),
B(LdaConstant), U8(1),
B(Star2),
......@@ -145,7 +145,7 @@ bytecodes: [
B(TestReferenceEqual), R(this),
B(Mov), R(this), R(0),
B(JumpIfTrue), U8(16),
B(Wide), B(LdaSmi), I16(292),
B(Wide), B(LdaSmi), I16(293),
B(Star2),
B(LdaConstant), U8(0),
B(Star3),
......@@ -167,7 +167,7 @@ bytecodes: [
B(TestReferenceEqual), R(this),
B(Mov), R(this), R(0),
B(JumpIfTrue), U8(16),
B(Wide), B(LdaSmi), I16(292),
B(Wide), B(LdaSmi), I16(293),
B(Star3),
B(LdaConstant), U8(0),
B(Star4),
......@@ -182,7 +182,7 @@ bytecodes: [
B(TestReferenceEqual), R(this),
B(Mov), R(this), R(0),
B(JumpIfTrue), U8(16),
B(Wide), B(LdaSmi), I16(292),
B(Wide), B(LdaSmi), I16(293),
B(Star2),
B(LdaConstant), U8(0),
B(Star3),
......@@ -216,13 +216,13 @@ bytecodes: [
B(TestReferenceEqual), R(this),
B(Mov), R(this), R(0),
B(JumpIfTrue), U8(16),
B(Wide), B(LdaSmi), I16(292),
B(Wide), B(LdaSmi), I16(293),
B(Star1),
B(LdaConstant), U8(0),
B(Star2),
/* 65 E> */ B(CallRuntime), U16(Runtime::kNewTypeError), R(1), U8(2),
B(Throw),
B(Wide), B(LdaSmi), I16(300),
B(Wide), B(LdaSmi), I16(301),
B(Star1),
B(LdaConstant), U8(1),
B(Star2),
......@@ -253,13 +253,13 @@ bytecodes: [
B(TestReferenceEqual), R(this),
B(Mov), R(this), R(0),
B(JumpIfTrue), U8(16),
B(Wide), B(LdaSmi), I16(292),
B(Wide), B(LdaSmi), I16(293),
B(Star1),
B(LdaConstant), U8(0),
B(Star2),
/* 58 E> */ B(CallRuntime), U16(Runtime::kNewTypeError), R(1), U8(2),
B(Throw),
B(Wide), B(LdaSmi), I16(299),
B(Wide), B(LdaSmi), I16(300),
B(Star1),
B(LdaConstant), U8(1),
B(Star2),
......@@ -292,13 +292,13 @@ bytecodes: [
B(TestReferenceEqual), R(this),
B(Mov), R(this), R(0),
B(JumpIfTrue), U8(16),
B(Wide), B(LdaSmi), I16(292),
B(Wide), B(LdaSmi), I16(293),
B(Star2),
B(LdaConstant), U8(0),
B(Star3),
/* 65 E> */ B(CallRuntime), U16(Runtime::kNewTypeError), R(2), U8(2),
B(Throw),
B(Wide), B(LdaSmi), I16(300),
B(Wide), B(LdaSmi), I16(301),
B(Star2),
B(LdaConstant), U8(1),
B(Star3),
......@@ -327,7 +327,7 @@ bytecode array length: 19
bytecodes: [
/* 46 S> */ B(LdaImmutableCurrentContextSlot), U8(3),
/* 51 E> */ B(GetKeyedProperty), R(this), U8(0),
B(Wide), B(LdaSmi), I16(299),
B(Wide), B(LdaSmi), I16(300),
B(Star1),
B(LdaConstant), U8(0),
B(Star2),
......
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