Commit 1c552999 authored by Jan Krems's avatar Jan Krems Committed by Commit Bot

Reland "[modules] Implement import.meta proposal"

This is a reland of ed6f00fb
Original change's description:
> [modules] Implement import.meta proposal
> 
> Rewrites references to import.meta to a new GetImportMetaObject runtime
> call. Embedders can define a callback for creating the meta object using
> v8::Isolate::SetHostGetImportMetaObjectCallback. If no callback has been
> provided, an empty object with null prototype is created.
> 
> This adds an example implementation to d8 that sets meta.url.
> 
> Bug: v8:6693
> Cq-Include-Trybots: master.tryserver.chromium.linux:linux_chromium_rel_ng
> Change-Id: I6871eec79da45bba81bbbc84b1ffff48534c368d
> Reviewed-on: https://chromium-review.googlesource.com/707902
> Commit-Queue: Sathya Gunasekaran <gsathya@chromium.org>
> Reviewed-by: Adam Klein <adamk@chromium.org>
> Reviewed-by: Sathya Gunasekaran <gsathya@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#48433}

TBR=adamk@chromium.org

Bug: v8:6693
Change-Id: Ie2d746ad996a56ed6ff50b832f320fe44e02f231
Cq-Include-Trybots: master.tryserver.chromium.linux:linux_chromium_rel_ng
Reviewed-on: https://chromium-review.googlesource.com/712834Reviewed-by: 's avatarSathya Gunasekaran <gsathya@chromium.org>
Commit-Queue: Sathya Gunasekaran <gsathya@chromium.org>
Cr-Commit-Position: refs/heads/master@{#48468}
parent 14bfa18b
......@@ -6291,6 +6291,20 @@ typedef MaybeLocal<Promise> (*HostImportModuleDynamicallyCallback)(
Local<Context> context, Local<ScriptOrModule> referrer,
Local<String> specifier);
/**
* HostInitializeImportMetaObjectCallback is called the first time import.meta
* is accessed for a module. Subsequent acccess will reuse the same value.
*
* The method combines two implementation-defined abstract operations into one:
* HostGetImportMetaProperties and HostFinalizeImportMeta.
*
* The embedder should use v8::Object::CreateDataProperty to add properties on
* the meta object.
*/
typedef void (*HostInitializeImportMetaObjectCallback)(Local<Context> context,
Local<Module> module,
Local<Object> meta);
/**
* PromiseHook with type kInit is called when a new promise is
* created. When a new promise is created as part of the chain in the
......@@ -7111,6 +7125,16 @@ class V8_EXPORT Isolate {
void SetHostImportModuleDynamicallyCallback(
HostImportModuleDynamicallyCallback callback);
/**
* This is an unfinished experimental feature, and is only exposed
* here for internal testing purposes. DO NOT USE.
*
* This specifies the callback called by the upcoming importa.meta
* language feature to retrieve host-defined meta data for a module.
*/
void SetHostInitializeImportMetaObjectCallback(
HostInitializeImportMetaObjectCallback callback);
/**
* Optional notification that the system is running low on memory.
* V8 uses these notifications to guide heuristics.
......
......@@ -8755,6 +8755,12 @@ void Isolate::SetHostImportModuleDynamicallyCallback(
isolate->SetHostImportModuleDynamicallyCallback(callback);
}
void Isolate::SetHostInitializeImportMetaObjectCallback(
HostInitializeImportMetaObjectCallback callback) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);
isolate->SetHostInitializeImportMetaObjectCallback(callback);
}
Isolate::DisallowJavascriptExecutionScope::DisallowJavascriptExecutionScope(
Isolate* isolate,
Isolate::DisallowJavascriptExecutionScope::OnFailure on_failure)
......
......@@ -518,6 +518,8 @@ ScriptCompiler::CachedData* CompileForCachedData(
Isolate* temp_isolate = Isolate::New(create_params);
temp_isolate->SetHostImportModuleDynamicallyCallback(
Shell::HostImportModuleDynamically);
temp_isolate->SetHostInitializeImportMetaObjectCallback(
Shell::HostInitializeImportMetaObject);
ScriptCompiler::CachedData* result = NULL;
{
Isolate::Scope isolate_scope(temp_isolate);
......@@ -712,13 +714,13 @@ class ModuleEmbedderData {
public:
explicit ModuleEmbedderData(Isolate* isolate)
: module_to_directory_map(10, ModuleGlobalHash(isolate)) {}
: module_to_specifier_map(10, ModuleGlobalHash(isolate)) {}
// Map from normalized module specifier to Module.
std::unordered_map<std::string, Global<Module>> specifier_to_module_map;
// Map from Module to the directory that Module was loaded from.
// Map from Module to its URL as defined in the ScriptOrigin
std::unordered_map<Global<Module>, std::string, ModuleGlobalHash>
module_to_directory_map;
module_to_specifier_map;
};
enum {
......@@ -746,11 +748,11 @@ MaybeLocal<Module> ResolveModuleCallback(Local<Context> context,
Local<Module> referrer) {
Isolate* isolate = context->GetIsolate();
ModuleEmbedderData* d = GetModuleDataFromContext(context);
auto dir_name_it =
d->module_to_directory_map.find(Global<Module>(isolate, referrer));
CHECK(dir_name_it != d->module_to_directory_map.end());
std::string absolute_path =
NormalizePath(ToSTLString(isolate, specifier), dir_name_it->second);
auto specifier_it =
d->module_to_specifier_map.find(Global<Module>(isolate, referrer));
CHECK(specifier_it != d->module_to_specifier_map.end());
std::string absolute_path = NormalizePath(ToSTLString(isolate, specifier),
DirName(specifier_it->second));
auto module_it = d->specifier_to_module_map.find(absolute_path);
CHECK(module_it != d->specifier_to_module_map.end());
return module_it->second.Get(isolate);
......@@ -783,11 +785,11 @@ MaybeLocal<Module> Shell::FetchModuleTree(Local<Context> context,
CHECK(d->specifier_to_module_map
.insert(std::make_pair(file_name, Global<Module>(isolate, module)))
.second);
CHECK(d->module_to_specifier_map
.insert(std::make_pair(Global<Module>(isolate, module), file_name))
.second);
std::string dir_name = DirName(file_name);
CHECK(d->module_to_directory_map
.insert(std::make_pair(Global<Module>(isolate, module), dir_name))
.second);
for (int i = 0, length = module->GetModuleRequestsLength(); i < length; ++i) {
Local<String> name = module->GetModuleRequest(i);
......@@ -842,6 +844,26 @@ MaybeLocal<Promise> Shell::HostImportModuleDynamically(
return MaybeLocal<Promise>();
}
void Shell::HostInitializeImportMetaObject(Local<Context> context,
Local<Module> module,
Local<Object> meta) {
Isolate* isolate = context->GetIsolate();
HandleScope handle_scope(isolate);
ModuleEmbedderData* d = GetModuleDataFromContext(context);
auto specifier_it =
d->module_to_specifier_map.find(Global<Module>(isolate, module));
CHECK(specifier_it != d->module_to_specifier_map.end());
Local<String> url_key =
String::NewFromUtf8(isolate, "url", NewStringType::kNormal)
.ToLocalChecked();
Local<String> url = String::NewFromUtf8(isolate, specifier_it->second.c_str(),
NewStringType::kNormal)
.ToLocalChecked();
meta->CreateDataProperty(context, url_key, url).ToChecked();
}
void Shell::DoHostImportModuleDynamically(void* import_data) {
std::unique_ptr<DynamicImportData> import_data_(
static_cast<DynamicImportData*>(import_data));
......@@ -2428,6 +2450,8 @@ void SourceGroup::ExecuteInThread() {
Isolate* isolate = Isolate::New(create_params);
isolate->SetHostImportModuleDynamicallyCallback(
Shell::HostImportModuleDynamically);
isolate->SetHostInitializeImportMetaObjectCallback(
Shell::HostInitializeImportMetaObject);
Shell::EnsureEventLoopInitialized(isolate);
D8Console console(isolate);
......@@ -2573,6 +2597,8 @@ void Worker::ExecuteInThread() {
Isolate* isolate = Isolate::New(create_params);
isolate->SetHostImportModuleDynamicallyCallback(
Shell::HostImportModuleDynamically);
isolate->SetHostInitializeImportMetaObjectCallback(
Shell::HostInitializeImportMetaObject);
D8Console console(isolate);
debug::SetConsoleDelegate(isolate, &console);
{
......@@ -3249,6 +3275,8 @@ int Shell::Main(int argc, char* argv[]) {
Isolate* isolate = Isolate::New(create_params);
isolate->SetHostImportModuleDynamicallyCallback(
Shell::HostImportModuleDynamically);
isolate->SetHostInitializeImportMetaObjectCallback(
Shell::HostInitializeImportMetaObject);
D8Console console(isolate);
{
......
......@@ -450,6 +450,9 @@ class Shell : public i::AllStatic {
static MaybeLocal<Promise> HostImportModuleDynamically(
Local<Context> context, Local<ScriptOrModule> referrer,
Local<String> specifier);
static void HostInitializeImportMetaObject(Local<Context> context,
Local<Module> module,
Local<Object> meta);
// Data is of type DynamicImportData*. We use void* here to be able
// to conform with MicrotaskCallback interface and enqueue this
......
......@@ -2093,6 +2093,7 @@ Handle<Module> Factory::NewModule(Handle<SharedFunctionInfo> code) {
module->set_script(Script::cast(code->script()));
module->set_status(Module::kUninstantiated);
module->set_exception(isolate()->heap()->the_hole_value());
module->set_import_meta(isolate()->heap()->the_hole_value());
module->set_dfs_index(-1);
module->set_dfs_ancestor_index(-1);
return module;
......
......@@ -3418,6 +3418,27 @@ void Isolate::SetHostImportModuleDynamicallyCallback(
host_import_module_dynamically_callback_ = callback;
}
Handle<JSObject> Isolate::RunHostInitializeImportMetaObjectCallback(
Handle<Module> module) {
Handle<Object> host_meta(module->import_meta(), this);
if (host_meta->IsTheHole(this)) {
host_meta = factory()->NewJSObjectWithNullProto();
if (host_initialize_import_meta_object_callback_ != nullptr) {
v8::Local<v8::Context> api_context = v8::Utils::ToLocal(native_context());
host_initialize_import_meta_object_callback_(
api_context, Utils::ToLocal(module),
v8::Local<v8::Object>::Cast(v8::Utils::ToLocal(host_meta)));
}
module->set_import_meta(*host_meta);
}
return Handle<JSObject>::cast(host_meta);
}
void Isolate::SetHostInitializeImportMetaObjectCallback(
HostInitializeImportMetaObjectCallback callback) {
host_initialize_import_meta_object_callback_ = callback;
}
void Isolate::SetPromiseHook(PromiseHook hook) {
promise_hook_ = hook;
DebugStateUpdated();
......
......@@ -1260,6 +1260,11 @@ class Isolate {
MaybeHandle<JSPromise> RunHostImportModuleDynamicallyCallback(
Handle<Script> referrer, Handle<Object> specifier);
void SetHostInitializeImportMetaObjectCallback(
HostInitializeImportMetaObjectCallback callback);
Handle<JSObject> RunHostInitializeImportMetaObjectCallback(
Handle<Module> module);
void SetRAILMode(RAILMode rail_mode);
RAILMode rail_mode() { return rail_mode_.Value(); }
......@@ -1504,6 +1509,8 @@ class Isolate {
bool promise_hook_or_debug_is_active_;
PromiseHook promise_hook_;
HostImportModuleDynamicallyCallback host_import_module_dynamically_callback_;
HostInitializeImportMetaObjectCallback
host_initialize_import_meta_object_callback_;
base::Mutex rail_mutex_;
double load_start_time_ms_;
......
......@@ -1246,6 +1246,7 @@ void Module::ModuleVerify() {
VerifyPointer(module_namespace());
VerifyPointer(requested_modules());
VerifyPointer(script());
VerifyPointer(import_meta());
VerifyPointer(exception());
VerifySmiField(kHashOffset);
VerifySmiField(kStatusOffset);
......@@ -1266,6 +1267,8 @@ void Module::ModuleVerify() {
CHECK_EQ(requested_modules()->length(), info()->module_requests()->length());
CHECK(import_meta()->IsTheHole(GetIsolate()) || import_meta()->IsJSObject());
CHECK_NE(hash(), 0);
}
......
......@@ -1335,6 +1335,7 @@ void Module::ModulePrint(std::ostream& os) { // NOLINT
os << "\n - exports: " << Brief(exports());
os << "\n - requested_modules: " << Brief(requested_modules());
os << "\n - script: " << Brief(script());
os << "\n - import_meta: " << Brief(import_meta());
os << "\n - status: " << status();
os << "\n - exception: " << Brief(exception());
os << "\n";
......
......@@ -22,6 +22,7 @@ ACCESSORS(Module, module_namespace, HeapObject, kModuleNamespaceOffset)
ACCESSORS(Module, requested_modules, FixedArray, kRequestedModulesOffset)
ACCESSORS(Module, script, Script, kScriptOffset)
ACCESSORS(Module, exception, Object, kExceptionOffset)
ACCESSORS(Module, import_meta, Object, kImportMetaOffset)
SMI_ACCESSORS(Module, status, kStatusOffset)
SMI_ACCESSORS(Module, dfs_index, kDfsIndexOffset)
SMI_ACCESSORS(Module, dfs_ancestor_index, kDfsAncestorIndexOffset)
......
......@@ -78,6 +78,11 @@ class Module : public Struct {
// [script]: Script from which the module originates.
DECL_ACCESSORS(script, Script)
// The value of import.meta inside of this module.
// Lazily initialized on first access. It's the hole before first access and
// a JSObject afterwards.
DECL_ACCESSORS(import_meta, Object)
// Get the ModuleInfo associated with the code.
inline ModuleInfo* info() const;
......@@ -119,7 +124,8 @@ class Module : public Struct {
static const int kDfsAncestorIndexOffset = kDfsIndexOffset + kPointerSize;
static const int kExceptionOffset = kDfsAncestorIndexOffset + kPointerSize;
static const int kScriptOffset = kExceptionOffset + kPointerSize;
static const int kSize = kScriptOffset + kPointerSize;
static const int kImportMetaOffset = kScriptOffset + kPointerSize;
static const int kSize = kImportMetaOffset + kPointerSize;
private:
friend class Factory;
......
......@@ -3518,7 +3518,7 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseImportExpressions(
return impl()->NullExpression();
}
return impl()->ExpressionFromLiteral(Token::NULL_LITERAL, pos);
return impl()->ImportMetaExpression(pos);
}
Expect(Token::LPAREN, CHECK_OK);
ExpressionT arg = ParseAssignmentExpression(true, CHECK_OK);
......
......@@ -383,6 +383,12 @@ Expression* Parser::FunctionSentExpression(int pos) {
args, pos);
}
Expression* Parser::ImportMetaExpression(int pos) {
return factory()->NewCallRuntime(
Runtime::kGetImportMetaObject,
new (zone()) ZoneList<Expression*>(0, zone()), pos);
}
Literal* Parser::ExpressionFromLiteral(Token::Value token, int pos) {
switch (token) {
case Token::NULL_LITERAL:
......
......@@ -882,6 +882,7 @@ class V8_EXPORT_PRIVATE Parser : public NON_EXPORTED_BASE(ParserBase<Parser>) {
Expression* NewSuperCallReference(int pos);
Expression* NewTargetExpression(int pos);
Expression* FunctionSentExpression(int pos);
Expression* ImportMetaExpression(int pos);
Literal* ExpressionFromLiteral(Token::Value token, int pos);
......
......@@ -1480,6 +1480,10 @@ class PreParser : public ParserBase<PreParser> {
return PreParserExpression::Default();
}
V8_INLINE PreParserExpression ImportMetaExpression(int pos) {
return PreParserExpression::Default();
}
V8_INLINE PreParserExpression ExpressionFromLiteral(Token::Value token,
int pos) {
return PreParserExpression::Default();
......
......@@ -57,5 +57,12 @@ RUNTIME_FUNCTION(Runtime_StoreModuleVariable) {
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_GetImportMetaObject) {
HandleScope scope(isolate);
DCHECK_EQ(0, args.length());
Handle<Module> module(isolate->context()->module());
return *isolate->RunHostInitializeImportMetaObjectCallback(module);
}
} // namespace internal
} // namespace v8
......@@ -356,6 +356,7 @@ namespace internal {
#define FOR_EACH_INTRINSIC_MODULE(F) \
F(DynamicImportCall, 2, 1) \
F(GetImportMetaObject, 0, 1) \
F(GetModuleNamespace, 1, 1) \
F(LoadModuleVariable, 1, 1) \
F(StoreModuleVariable, 2, 1)
......
......@@ -72,6 +72,7 @@ using ::v8::Local;
using ::v8::Maybe;
using ::v8::Message;
using ::v8::MessageCallback;
using ::v8::Module;
using ::v8::Name;
using ::v8::None;
using ::v8::Object;
......@@ -27199,6 +27200,58 @@ TEST(DynamicImport) {
CHECK(result->Equals(i::String::cast(promise->result())));
}
void HostInitializeImportMetaObjectCallbackStatic(Local<Context> context,
Local<Module> module,
Local<Object> meta) {
CHECK(!module.IsEmpty());
meta->CreateDataProperty(context, v8_str("foo"), v8_str("bar")).ToChecked();
}
v8::MaybeLocal<Module> UnexpectedModuleResolveCallback(Local<Context> context,
Local<String> specifier,
Local<Module> referrer) {
CHECK_WITH_MSG(false, "Unexpected call to resolve callback");
}
TEST(ImportMeta) {
i::FLAG_harmony_dynamic_import = true;
i::FLAG_harmony_import_meta = true;
LocalContext context;
v8::Isolate* isolate = context->GetIsolate();
v8::HandleScope scope(isolate);
isolate->SetHostInitializeImportMetaObjectCallback(
HostInitializeImportMetaObjectCallbackStatic);
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
Local<String> url = v8_str("www.google.com");
Local<String> source_text = v8_str("import.meta;");
v8::ScriptOrigin origin(url, Local<v8::Integer>(), Local<v8::Integer>(),
Local<v8::Boolean>(), Local<v8::Integer>(),
Local<v8::Value>(), Local<v8::Boolean>(),
Local<v8::Boolean>(), True(isolate));
v8::ScriptCompiler::Source source(source_text, origin);
Local<Module> module =
v8::ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
i::Handle<i::Object> meta =
i_isolate->RunHostInitializeImportMetaObjectCallback(
v8::Utils::OpenHandle(*module));
CHECK(meta->IsJSObject());
Local<Object> meta_obj = Local<Object>::Cast(v8::Utils::ToLocal(meta));
CHECK(meta_obj->Get(context.local(), v8_str("foo"))
.ToLocalChecked()
->IsString());
CHECK(meta_obj->Get(context.local(), v8_str("zapp"))
.ToLocalChecked()
->IsUndefined());
module->InstantiateModule(context.local(), UnexpectedModuleResolveCallback)
.ToChecked();
Local<Value> result = module->Evaluate(context.local()).ToLocalChecked();
CHECK(result->StrictEquals(Local<v8::Value>::Cast(v8::Utils::ToLocal(meta))));
}
TEST(GlobalTemplateWithDoubleProperty) {
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope handle_scope(isolate);
......
// Copyright 2017 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.
// MODULE
// Flags: --harmony-import-meta
import foreign, { url as otherUrl } from './modules-skip-export-import-meta.js';
assertEquals("object", typeof import.meta);
assertEquals(null, Object.getPrototypeOf(import.meta));
assertSame(import.meta, import.meta);
// This property isn't part of the spec itself but is mentioned as an example
assertMatches(/\/modules-import-meta\.js$/, import.meta.url);
import.meta.x = 42;
assertEquals(42, import.meta.x);
Object.assign(import.meta, { foo: "bar" })
assertEquals("bar", import.meta.foo);
// PerformEval parses its argument for the goal symbol Script. So the following
// should fail just as it does for every other Script context.
//
// See:
// https://github.com/tc39/proposal-import-meta/issues/7#issuecomment-329363083
assertThrows(() => eval('import.meta'), SyntaxError);
assertThrows(() => new Function('return import.meta;'), SyntaxError);
assertNotEquals(foreign, import.meta);
assertMatches(/\/modules-skip-export-import-meta\.js$/, foreign.url);
assertEquals(foreign.url, otherUrl);
// Copyright 2017 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.
export default import.meta;
const { url } = import.meta;
export { url };
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