Commit f588c889 authored by Daniel Clark's avatar Daniel Clark Committed by Commit Bot

[modules] Add refactored API to get ModuleRequests and expose import assertions

This change refactors the v8.h API as discussed in
https://docs.google.com/document/d/1yuXgNHSbTAPubT1Mg0JXp5uTrfirkvO1g5cHHCe-LmY/edit#heading=h.q0c9h4p928mn
such that a v8::Module exposes module requests as a FixedArray of
ModuleRequest objects, which can then be used to obtain their module
specifier and source code offset.  This replaces the old functions that
passed back individual specifier Strings and Locations via repeated
calls to getters that take an index.  These are marked as deprecated.

The new ModuleRequest interface includes a getter for an
ImportAssertions FixedArray, which will contain the import assertions
for the request if --harmony-import-assertions is set, and will be
empty otherwise.

One notable change here is that the APIs now return source code offsets
rather than v8::Locations.  The host must then call the new
Module::SourceOffsetToLocation to convert these offsets into line/column
numbers. This requires a bit more back-and-forth, but allows the host to
defer the cost of converting from source offset to line/column numbers
until an error needs to be reported, potentially skipping the work
altogether.

Bug: v8:10958
Change-Id: I181639737c701e467324e6c781aa4d7bdd87ae8c
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2545577
Commit-Queue: Dan Clark <daniec@microsoft.com>
Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Reviewed-by: 's avatarMarja Hölttä <marja@chromium.org>
Cr-Commit-Position: refs/heads/master@{#71387}
parent 9750bda3
......@@ -1523,6 +1523,43 @@ class V8_EXPORT Location {
int column_number_;
};
/**
* A fixed-sized array with elements of type Data.
*/
class V8_EXPORT FixedArray : public Data {
public:
int Length() const;
Local<Data> Get(Local<Context> context, int i) const;
};
class V8_EXPORT ModuleRequest : public Data {
public:
/**
* Returns the module specifier for this ModuleRequest.
*/
Local<String> GetSpecifier() const;
/**
* Returns the source code offset of this module request.
* Use Module::SourceOffsetToLocation to convert this to line/column numbers.
*/
int GetSourceOffset() const;
/**
* Contains the import assertions for this request in the form:
* [key1, value1, source_offset1, key2, value2, source_offset2, ...].
* The keys and values are of type v8::String, and the source offsets are of
* type Int32. Use Module::SourceOffsetToLocation to convert the source
* offsets to Locations with line/column numbers.
*/
Local<FixedArray> GetImportAssertions() const;
V8_INLINE static ModuleRequest* Cast(Data* data);
private:
static void CheckCast(Data* obj);
};
/**
* A compiled JavaScript module.
*/
......@@ -1557,20 +1594,37 @@ class V8_EXPORT Module : public Data {
/**
* Returns the number of modules requested by this module.
*/
V8_DEPRECATE_SOON("Use Module::GetModuleRequests() and FixedArray::Length().")
int GetModuleRequestsLength() const;
/**
* Returns the ith module specifier in this module.
* i must be < GetModuleRequestsLength() and >= 0.
*/
V8_DEPRECATE_SOON(
"Use Module::GetModuleRequests() and ModuleRequest::GetSpecifier().")
Local<String> GetModuleRequest(int i) const;
/**
* Returns the source location (line number and column number) of the ith
* module specifier's first occurrence in this module.
*/
V8_DEPRECATE_SOON(
"Use Module::GetModuleRequests(), ModuleRequest::GetSourceOffset(), and "
"Module::SourceOffsetToLocation().")
Location GetModuleRequestLocation(int i) const;
/**
* Returns the ModuleRequests for this module.
*/
Local<FixedArray> GetModuleRequests() const;
/**
* For the given source text offset in this module, returns the corresponding
* Location with line and column numbers.
*/
Location SourceOffsetToLocation(int offset) const;
/**
* Returns the identity hash for this object.
*/
......@@ -11746,6 +11800,13 @@ Private* Private::Cast(Data* data) {
return reinterpret_cast<Private*>(data);
}
ModuleRequest* ModuleRequest::Cast(Data* data) {
#ifdef V8_ENABLE_CHECKS
CheckCast(data);
#endif
return reinterpret_cast<ModuleRequest*>(data);
}
Module* Module::Cast(Data* data) {
#ifdef V8_ENABLE_CHECKS
CheckCast(data);
......
......@@ -2205,6 +2205,35 @@ Local<Primitive> PrimitiveArray::Get(Isolate* v8_isolate, int index) {
return ToApiHandle<Primitive>(i_item);
}
int FixedArray::Length() const {
i::Handle<i::FixedArray> self = Utils::OpenHandle(this);
return self->length();
}
Local<Data> FixedArray::Get(Local<Context> context, int i) const {
i::Handle<i::FixedArray> self = Utils::OpenHandle(this);
auto isolate = reinterpret_cast<i::Isolate*>(context->GetIsolate());
CHECK_LT(i, self->length());
i::Handle<i::Object> entry(self->get(i), isolate);
return ToApiHandle<Data>(entry);
}
Local<String> ModuleRequest::GetSpecifier() const {
i::Handle<i::ModuleRequest> self = Utils::OpenHandle(this);
i::Isolate* isolate = self->GetIsolate();
return ToApiHandle<String>(i::handle(self->specifier(), isolate));
}
int ModuleRequest::GetSourceOffset() const {
return Utils::OpenHandle(this)->position();
}
Local<FixedArray> ModuleRequest::GetImportAssertions() const {
i::Handle<i::ModuleRequest> self = Utils::OpenHandle(this);
i::Isolate* isolate = self->GetIsolate();
return ToApiHandle<FixedArray>(i::handle(self->import_assertions(), isolate));
}
Module::Status Module::GetStatus() const {
i::Handle<i::Module> self = Utils::OpenHandle(this);
switch (self->status()) {
......@@ -2283,6 +2312,33 @@ Location Module::GetModuleRequestLocation(int i) const {
return v8::Location(info.line, info.column);
}
Local<FixedArray> Module::GetModuleRequests() const {
i::Handle<i::Module> self = Utils::OpenHandle(this);
Utils::ApiCheck(
self->IsSourceTextModule(), "v8::Module::GetModuleRequests",
"v8::Module::GetModuleRequests must be used on an SourceTextModule");
i::Isolate* isolate = self->GetIsolate();
i::Handle<i::FixedArray> module_requests(
i::Handle<i::SourceTextModule>::cast(self)->info().module_requests(),
isolate);
return ToApiHandle<FixedArray>(module_requests);
}
Location Module::SourceOffsetToLocation(int offset) const {
i::Handle<i::Module> self = Utils::OpenHandle(this);
i::Isolate* isolate = self->GetIsolate();
ENTER_V8_NO_SCRIPT_NO_EXCEPTION(isolate);
i::HandleScope scope(isolate);
Utils::ApiCheck(
self->IsSourceTextModule(), "v8::Module::SourceOffsetToLocation",
"v8::Module::SourceOffsetToLocation must be used on an SourceTextModule");
i::Handle<i::Script> script(
i::Handle<i::SourceTextModule>::cast(self)->GetScript(), isolate);
i::Script::PositionInfo info;
i::Script::GetPositionInfo(script, offset, &info, i::Script::WITH_OFFSET);
return v8::Location(info.line, info.column);
}
Local<Value> Module::GetModuleNamespace() {
Utils::ApiCheck(
GetStatus() >= kInstantiated, "v8::Module::GetModuleNamespace",
......@@ -3774,6 +3830,12 @@ void v8::Private::CheckCast(v8::Data* that) {
"v8::Private::Cast", "Value is not a Private");
}
void v8::ModuleRequest::CheckCast(v8::Data* that) {
i::Handle<i::Object> obj = Utils::OpenHandle(that);
Utils::ApiCheck(obj->IsModuleRequest(), "v8::ModuleRequest::Cast",
"Value is not a ModuleRequest");
}
void v8::Module::CheckCast(v8::Data* that) {
i::Handle<i::Object> obj = Utils::OpenHandle(that);
Utils::ApiCheck(obj->IsModule(), "v8::Module::Cast", "Value is not a Module");
......
......@@ -134,7 +134,9 @@ class RegisteredExtension {
V(Primitive, Object) \
V(PrimitiveArray, FixedArray) \
V(BigInt, BigInt) \
V(ScriptOrModule, Script)
V(ScriptOrModule, Script) \
V(FixedArray, FixedArray) \
V(ModuleRequest, ModuleRequest)
class Utils {
public:
......
......@@ -931,8 +931,11 @@ MaybeLocal<Module> Shell::FetchModuleTree(Local<Module> referrer,
std::string dir_name = DirName(file_name);
for (int i = 0, length = module->GetModuleRequestsLength(); i < length; ++i) {
Local<String> name = module->GetModuleRequest(i);
Local<FixedArray> module_requests = module->GetModuleRequests();
for (int i = 0, length = module_requests->Length(); i < length; ++i) {
Local<ModuleRequest> module_request =
module_requests->Get(context, i).As<ModuleRequest>();
Local<String> name = module_request->GetSpecifier();
std::string absolute_path =
NormalizePath(ToSTLString(isolate, name), dir_name);
if (d->specifier_to_module_map.count(absolute_path)) continue;
......
......@@ -9,11 +9,16 @@
namespace {
using v8::Context;
using v8::Data;
using v8::FixedArray;
using v8::HandleScope;
using v8::Int32;
using v8::Isolate;
using v8::Local;
using v8::Location;
using v8::MaybeLocal;
using v8::Module;
using v8::ModuleRequest;
using v8::Promise;
using v8::ScriptCompiler;
using v8::ScriptOrigin;
......@@ -60,15 +65,27 @@ TEST(ModuleInstantiationFailures1) {
ScriptCompiler::Source source(source_text, origin);
module = ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
CHECK_EQ(Module::kUninstantiated, module->GetStatus());
CHECK_EQ(2, module->GetModuleRequestsLength());
CHECK(v8_str("./foo.js")->StrictEquals(module->GetModuleRequest(0)));
v8::Location loc = module->GetModuleRequestLocation(0);
Local<FixedArray> module_requests = module->GetModuleRequests();
CHECK_EQ(2, module_requests->Length());
Local<ModuleRequest> module_request_0 =
module_requests->Get(env.local(), 0).As<ModuleRequest>();
CHECK(v8_str("./foo.js")->StrictEquals(module_request_0->GetSpecifier()));
int offset = module_request_0->GetSourceOffset();
CHECK_EQ(7, offset);
Location loc = module->SourceOffsetToLocation(offset);
CHECK_EQ(0, loc.GetLineNumber());
CHECK_EQ(7, loc.GetColumnNumber());
CHECK(v8_str("./bar.js")->StrictEquals(module->GetModuleRequest(1)));
loc = module->GetModuleRequestLocation(1);
CHECK_EQ(0, module_request_0->GetImportAssertions()->Length());
Local<ModuleRequest> module_request_1 =
module_requests->Get(env.local(), 1).As<ModuleRequest>();
CHECK(v8_str("./bar.js")->StrictEquals(module_request_1->GetSpecifier()));
offset = module_request_1->GetSourceOffset();
CHECK_EQ(34, offset);
loc = module->SourceOffsetToLocation(offset);
CHECK_EQ(1, loc.GetLineNumber());
CHECK_EQ(15, loc.GetColumnNumber());
CHECK_EQ(0, module_request_1->GetImportAssertions()->Length());
}
// Instantiation should fail.
......@@ -114,6 +131,85 @@ TEST(ModuleInstantiationFailures1) {
i::FLAG_harmony_top_level_await = prev_top_level_await;
}
TEST(ModuleInstantiationFailures1WithImportAssertions) {
bool prev_top_level_await = i::FLAG_harmony_top_level_await;
bool prev_import_assertions = i::FLAG_harmony_import_assertions;
i::FLAG_harmony_import_assertions = true;
for (auto top_level_await : {true, false}) {
i::FLAG_harmony_top_level_await = top_level_await;
Isolate* isolate = CcTest::isolate();
HandleScope scope(isolate);
LocalContext env;
v8::TryCatch try_catch(isolate);
Local<Module> module;
{
Local<String> source_text = v8_str(
"import './foo.js' assert { };\n"
"export {} from './bar.js' assert { a: 'b' };");
ScriptOrigin origin = ModuleOrigin(v8_str("file.js"), CcTest::isolate());
ScriptCompiler::Source source(source_text, origin);
module = ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
CHECK_EQ(Module::kUninstantiated, module->GetStatus());
Local<FixedArray> module_requests = module->GetModuleRequests();
CHECK_EQ(2, module_requests->Length());
Local<ModuleRequest> module_request_0 =
module_requests->Get(env.local(), 0).As<ModuleRequest>();
CHECK(v8_str("./foo.js")->StrictEquals(module_request_0->GetSpecifier()));
int offset = module_request_0->GetSourceOffset();
CHECK_EQ(7, offset);
Location loc = module->SourceOffsetToLocation(offset);
CHECK_EQ(0, loc.GetLineNumber());
CHECK_EQ(7, loc.GetColumnNumber());
CHECK_EQ(0, module_request_0->GetImportAssertions()->Length());
Local<ModuleRequest> module_request_1 =
module_requests->Get(env.local(), 1).As<ModuleRequest>();
CHECK(v8_str("./bar.js")->StrictEquals(module_request_1->GetSpecifier()));
offset = module_request_1->GetSourceOffset();
CHECK_EQ(45, offset);
loc = module->SourceOffsetToLocation(offset);
CHECK_EQ(1, loc.GetLineNumber());
CHECK_EQ(15, loc.GetColumnNumber());
Local<FixedArray> import_assertions_1 =
module_request_1->GetImportAssertions();
CHECK_EQ(3, import_assertions_1->Length());
Local<String> assertion_key =
import_assertions_1->Get(env.local(), 0).As<Value>().As<String>();
CHECK(v8_str("a")->StrictEquals(assertion_key));
Local<String> assertion_value =
import_assertions_1->Get(env.local(), 1).As<Value>().As<String>();
CHECK(v8_str("b")->StrictEquals(assertion_value));
Local<Data> assertion_source_offset_data =
import_assertions_1->Get(env.local(), 2);
Local<Int32> assertion_source_offset_int32 =
assertion_source_offset_data.As<Value>()
->ToInt32(env.local())
.ToLocalChecked();
int32_t assertion_source_offset = assertion_source_offset_int32->Value();
CHECK_EQ(65, assertion_source_offset);
loc = module->SourceOffsetToLocation(assertion_source_offset);
CHECK_EQ(1, loc.GetLineNumber());
CHECK_EQ(35, loc.GetColumnNumber());
}
// Instantiation should fail.
{
v8::TryCatch inner_try_catch(isolate);
CHECK(
module->InstantiateModule(env.local(), ResolveCallback).IsNothing());
CHECK(inner_try_catch.HasCaught());
CHECK(inner_try_catch.Exception()->StrictEquals(v8_str("boom")));
CHECK_EQ(Module::kUninstantiated, module->GetStatus());
}
CHECK(!try_catch.HasCaught());
}
i::FLAG_harmony_top_level_await = prev_top_level_await;
i::FLAG_harmony_import_assertions = prev_import_assertions;
}
TEST(ModuleInstantiationFailures2) {
bool prev_top_level_await = i::FLAG_harmony_top_level_await;
for (auto top_level_await : {true, false}) {
......
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