blob: f4c23ae5c423ceb9de06c578ed1f6b8890228409 [file] [log] [blame]
// 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.
#include "src/objects/module.h"
#include <unordered_map>
#include <unordered_set>
#include "src/api/api-inl.h"
#include "src/ast/modules.h"
#include "src/builtins/accessors.h"
#include "src/heap/heap-inl.h"
#include "src/objects/cell-inl.h"
#include "src/objects/hash-table-inl.h"
#include "src/objects/js-generator-inl.h"
#include "src/objects/module-inl.h"
#include "src/objects/objects-inl.h"
#include "src/objects/synthetic-module-inl.h"
#include "src/utils/ostreams.h"
namespace v8 {
namespace internal {
namespace {
#ifdef DEBUG
void PrintModuleName(Module module, std::ostream& os) {
if (module.IsSourceTextModule()) {
SourceTextModule::cast(module).script().GetNameOrSourceURL().Print(os);
} else {
SyntheticModule::cast(module).name().Print(os);
}
#ifndef OBJECT_PRINT
os << "\n";
#endif // OBJECT_PRINT
}
void PrintStatusTransition(Module module, Module::Status new_status) {
if (!FLAG_trace_module_status) return;
StdoutStream os;
os << "Changing module status from " << module.status() << " to "
<< new_status << " for ";
PrintModuleName(module, os);
}
void PrintStatusMessage(Module module, const char* message) {
if (!FLAG_trace_module_status) return;
StdoutStream os;
os << "Instantiating module ";
PrintModuleName(module, os);
}
#endif // DEBUG
void SetStatusInternal(Module module, Module::Status new_status) {
DisallowHeapAllocation no_alloc;
#ifdef DEBUG
PrintStatusTransition(module, new_status);
#endif // DEBUG
module.set_status(new_status);
}
} // end namespace
void Module::SetStatus(Status new_status) {
DisallowHeapAllocation no_alloc;
DCHECK_LE(status(), new_status);
DCHECK_NE(new_status, Module::kErrored);
SetStatusInternal(*this, new_status);
}
// static
void Module::RecordErrorUsingPendingException(Isolate* isolate,
Handle<Module> module) {
Handle<Object> the_exception(isolate->pending_exception(), isolate);
RecordError(isolate, module, the_exception);
}
// static
void Module::RecordError(Isolate* isolate, Handle<Module> module,
Handle<Object> error) {
DCHECK(module->exception().IsTheHole(isolate));
DCHECK(!error->IsTheHole(isolate));
if (module->IsSourceTextModule()) {
Handle<SourceTextModule> self(SourceTextModule::cast(*module), isolate);
self->set_code(self->info());
}
SetStatusInternal(*module, Module::kErrored);
if (isolate->is_catchable_by_javascript(*error)) {
module->set_exception(*error);
} else {
// v8::TryCatch uses `null` for termination exceptions.
module->set_exception(*isolate->factory()->null_value());
}
}
void Module::ResetGraph(Isolate* isolate, Handle<Module> module) {
DCHECK_NE(module->status(), kEvaluating);
if (module->status() != kPreInstantiating &&
module->status() != kInstantiating) {
return;
}
Handle<FixedArray> requested_modules =
module->IsSourceTextModule()
? Handle<FixedArray>(
SourceTextModule::cast(*module).requested_modules(), isolate)
: Handle<FixedArray>();
Reset(isolate, module);
if (!module->IsSourceTextModule()) {
DCHECK(module->IsSyntheticModule());
return;
}
for (int i = 0; i < requested_modules->length(); ++i) {
Handle<Object> descendant(requested_modules->get(i), isolate);
if (descendant->IsModule()) {
ResetGraph(isolate, Handle<Module>::cast(descendant));
} else {
DCHECK(descendant->IsUndefined(isolate));
}
}
}
void Module::Reset(Isolate* isolate, Handle<Module> module) {
DCHECK(module->status() == kPreInstantiating ||
module->status() == kInstantiating);
DCHECK(module->exception().IsTheHole(isolate));
// The namespace object cannot exist, because it would have been created
// by RunInitializationCode, which is called only after this module's SCC
// succeeds instantiation.
DCHECK(!module->module_namespace().IsJSModuleNamespace());
const int export_count =
module->IsSourceTextModule()
? SourceTextModule::cast(*module).regular_exports().length()
: SyntheticModule::cast(*module).export_names().length();
Handle<ObjectHashTable> exports = ObjectHashTable::New(isolate, export_count);
if (module->IsSourceTextModule()) {
SourceTextModule::Reset(isolate, Handle<SourceTextModule>::cast(module));
}
module->set_exports(*exports);
SetStatusInternal(*module, kUninstantiated);
}
Object Module::GetException() {
DisallowHeapAllocation no_gc;
DCHECK_EQ(status(), Module::kErrored);
DCHECK(!exception().IsTheHole());
return exception();
}
MaybeHandle<Cell> Module::ResolveExport(Isolate* isolate, Handle<Module> module,
Handle<String> module_specifier,
Handle<String> export_name,
MessageLocation loc, bool must_resolve,
Module::ResolveSet* resolve_set) {
DCHECK_GE(module->status(), kPreInstantiating);
DCHECK_NE(module->status(), kEvaluating);
if (module->IsSourceTextModule()) {
return SourceTextModule::ResolveExport(
isolate, Handle<SourceTextModule>::cast(module), module_specifier,
export_name, loc, must_resolve, resolve_set);
} else {
return SyntheticModule::ResolveExport(
isolate, Handle<SyntheticModule>::cast(module), module_specifier,
export_name, loc, must_resolve);
}
}
bool Module::Instantiate(Isolate* isolate, Handle<Module> module,
v8::Local<v8::Context> context,
v8::Module::ResolveCallback callback) {
#ifdef DEBUG
PrintStatusMessage(*module, "Instantiating module ");
#endif // DEBUG
if (!PrepareInstantiate(isolate, module, context, callback)) {
ResetGraph(isolate, module);
DCHECK_EQ(module->status(), kUninstantiated);
return false;
}
Zone zone(isolate->allocator(), ZONE_NAME);
ZoneForwardList<Handle<SourceTextModule>> stack(&zone);
unsigned dfs_index = 0;
if (!FinishInstantiate(isolate, module, &stack, &dfs_index, &zone)) {
ResetGraph(isolate, module);
DCHECK_EQ(module->status(), kUninstantiated);
return false;
}
DCHECK(module->status() == kInstantiated || module->status() == kEvaluated ||
module->status() == kErrored);
DCHECK(stack.empty());
return true;
}
bool Module::PrepareInstantiate(Isolate* isolate, Handle<Module> module,
v8::Local<v8::Context> context,
v8::Module::ResolveCallback callback) {
DCHECK_NE(module->status(), kEvaluating);
DCHECK_NE(module->status(), kInstantiating);
if (module->status() >= kPreInstantiating) return true;
module->SetStatus(kPreInstantiating);
STACK_CHECK(isolate, false);
if (module->IsSourceTextModule()) {
return SourceTextModule::PrepareInstantiate(
isolate, Handle<SourceTextModule>::cast(module), context, callback);
} else {
return SyntheticModule::PrepareInstantiate(
isolate, Handle<SyntheticModule>::cast(module), context, callback);
}
}
bool Module::FinishInstantiate(Isolate* isolate, Handle<Module> module,
ZoneForwardList<Handle<SourceTextModule>>* stack,
unsigned* dfs_index, Zone* zone) {
DCHECK_NE(module->status(), kEvaluating);
if (module->status() >= kInstantiating) return true;
DCHECK_EQ(module->status(), kPreInstantiating);
STACK_CHECK(isolate, false);
if (module->IsSourceTextModule()) {
return SourceTextModule::FinishInstantiate(
isolate, Handle<SourceTextModule>::cast(module), stack, dfs_index,
zone);
} else {
return SyntheticModule::FinishInstantiate(
isolate, Handle<SyntheticModule>::cast(module));
}
}
MaybeHandle<Object> Module::Evaluate(Isolate* isolate, Handle<Module> module) {
#ifdef DEBUG
PrintStatusMessage(*module, "Evaluating module ");
#endif // DEBUG
STACK_CHECK(isolate, MaybeHandle<Object>());
if (FLAG_harmony_top_level_await && module->IsSourceTextModule()) {
return SourceTextModule::EvaluateMaybeAsync(
isolate, Handle<SourceTextModule>::cast(module));
} else {
return Module::InnerEvaluate(isolate, module);
}
}
MaybeHandle<Object> Module::InnerEvaluate(Isolate* isolate,
Handle<Module> module) {
if (module->status() == kErrored) {
isolate->Throw(module->GetException());
return MaybeHandle<Object>();
} else if (module->status() == kEvaluated) {
return isolate->factory()->undefined_value();
}
// InnerEvaluate can be called both to evaluate top level modules without
// the harmony_top_level_await flag and recursively to evaluate
// SyntheticModules in the dependency graphs of SourceTextModules.
//
// However, SyntheticModules transition directly to 'Evaluated,' so we should
// never see an 'Evaluating' module at this point.
CHECK_EQ(module->status(), kInstantiated);
if (module->IsSourceTextModule()) {
return SourceTextModule::Evaluate(isolate,
Handle<SourceTextModule>::cast(module));
} else {
return SyntheticModule::Evaluate(isolate,
Handle<SyntheticModule>::cast(module));
}
}
Handle<JSModuleNamespace> Module::GetModuleNamespace(Isolate* isolate,
Handle<Module> module) {
Handle<HeapObject> object(module->module_namespace(), isolate);
ReadOnlyRoots roots(isolate);
if (!object->IsUndefined(roots)) {
// Namespace object already exists.
return Handle<JSModuleNamespace>::cast(object);
}
// Collect the export names.
Zone zone(isolate->allocator(), ZONE_NAME);
UnorderedModuleSet visited(&zone);
if (module->IsSourceTextModule()) {
SourceTextModule::FetchStarExports(
isolate, Handle<SourceTextModule>::cast(module), &zone, &visited);
}
Handle<ObjectHashTable> exports(module->exports(), isolate);
ZoneVector<Handle<String>> names(&zone);
names.reserve(exports->NumberOfElements());
for (InternalIndex i : exports->IterateEntries()) {
Object key;
if (!exports->ToKey(roots, i, &key)) continue;
names.push_back(handle(String::cast(key), isolate));
}
DCHECK_EQ(static_cast<int>(names.size()), exports->NumberOfElements());
// Sort them alphabetically.
std::sort(names.begin(), names.end(),
[&isolate](Handle<String> a, Handle<String> b) {
return String::Compare(isolate, a, b) ==
ComparisonResult::kLessThan;
});
// Create the namespace object (initially empty).
Handle<JSModuleNamespace> ns = isolate->factory()->NewJSModuleNamespace();
ns->set_module(*module);
module->set_module_namespace(*ns);
// Create the properties in the namespace object. Transition the object
// to dictionary mode so that property addition is faster.
PropertyAttributes attr = DONT_DELETE;
JSObject::NormalizeProperties(isolate, ns, CLEAR_INOBJECT_PROPERTIES,
static_cast<int>(names.size()),
"JSModuleNamespace");
for (const auto& name : names) {
JSObject::SetNormalizedProperty(
ns, name, Accessors::MakeModuleNamespaceEntryInfo(isolate, name),
PropertyDetails(kAccessor, attr, PropertyCellType::kMutable));
}
JSObject::PreventExtensions(ns, kThrowOnError).ToChecked();
// Optimize the namespace object as a prototype, for two reasons:
// - The object's map is guaranteed not to be shared. ICs rely on this.
// - We can store a pointer from the map back to the namespace object.
// Turbofan can use this for inlining the access.
JSObject::OptimizeAsPrototype(ns);
Handle<PrototypeInfo> proto_info =
Map::GetOrCreatePrototypeInfo(Handle<JSObject>::cast(ns), isolate);
proto_info->set_module_namespace(*ns);
return ns;
}
MaybeHandle<Object> JSModuleNamespace::GetExport(Isolate* isolate,
Handle<String> name) {
Handle<Object> object(module().exports().Lookup(name), isolate);
if (object->IsTheHole(isolate)) {
return isolate->factory()->undefined_value();
}
Handle<Object> value(Cell::cast(*object).value(), isolate);
if (value->IsTheHole(isolate)) {
THROW_NEW_ERROR(
isolate, NewReferenceError(MessageTemplate::kNotDefined, name), Object);
}
return value;
}
Maybe<PropertyAttributes> JSModuleNamespace::GetPropertyAttributes(
LookupIterator* it) {
Handle<JSModuleNamespace> object = it->GetHolder<JSModuleNamespace>();
Handle<String> name = Handle<String>::cast(it->GetName());
DCHECK_EQ(it->state(), LookupIterator::ACCESSOR);
Isolate* isolate = it->isolate();
Handle<Object> lookup(object->module().exports().Lookup(name), isolate);
if (lookup->IsTheHole(isolate)) return Just(ABSENT);
Handle<Object> value(Handle<Cell>::cast(lookup)->value(), isolate);
if (value->IsTheHole(isolate)) {
isolate->Throw(*isolate->factory()->NewReferenceError(
MessageTemplate::kNotDefined, name));
return Nothing<PropertyAttributes>();
}
return Just(it->property_attributes());
}
bool Module::IsGraphAsync(Isolate* isolate) const {
DisallowGarbageCollection no_gc;
// Only SourceTextModules may be async.
if (!IsSourceTextModule()) return false;
SourceTextModule root = SourceTextModule::cast(*this);
Zone zone(isolate->allocator(), ZONE_NAME);
const size_t bucket_count = 2;
ZoneUnorderedSet<Module, Module::Hash> visited(&zone, bucket_count);
ZoneVector<SourceTextModule> worklist(&zone);
visited.insert(root);
worklist.push_back(root);
do {
SourceTextModule current = worklist.back();
worklist.pop_back();
DCHECK_GE(current.status(), kInstantiated);
if (current.async()) return true;
FixedArray requested_modules = current.requested_modules();
for (int i = 0, length = requested_modules.length(); i < length; ++i) {
Module descendant = Module::cast(requested_modules.get(i));
if (descendant.IsSourceTextModule()) {
const bool cycle = !visited.insert(descendant).second;
if (!cycle) worklist.push_back(SourceTextModule::cast(descendant));
}
}
} while (!worklist.empty());
return false;
}
} // namespace internal
} // namespace v8