#include "module_handle.h" #include "context_handle.h" #include "reference_handle.h" #include "transferable.h" #include "isolate/class_handle.h" #include "isolate/run_with_timeout.h" #include "isolate/three_phase_task.h" #include using namespace v8; using std::shared_ptr; namespace ivm { namespace { auto LookupModuleInfo(Local module) { auto& module_map = IsolateEnvironment::GetCurrent().module_handles; auto range = module_map.equal_range(module->GetIdentityHash()); auto it = std::find_if(range.first, range.second, [&](decltype(*module_map.begin()) data) { return data.second->handle.Deref() == module; }); return it == range.second ? nullptr : it->second; } } // anonymous namespace ModuleInfo::ModuleInfo(Local handle) : identity_hash{handle->GetIdentityHash()}, handle{handle} { // Add to isolate's list of modules IsolateEnvironment::GetCurrent().module_handles.emplace(identity_hash, this); // Grab all dependency specifiers Isolate* isolate = Isolate::GetCurrent(); auto context = isolate->GetCurrentContext(); auto& requests = **handle->GetModuleRequests(); dependency_specifiers.reserve(requests.Length()); for (int ii = 0; ii < requests.Length(); ii++) { auto request = requests.Get(context, ii).As(); dependency_specifiers.emplace_back(*String::Utf8Value{isolate, request->GetSpecifier()}); } } ModuleInfo::~ModuleInfo() { // Remove from isolate's list of modules auto environment = handle.GetIsolateHolder()->GetIsolate(); if (environment) { auto& module_map = environment->module_handles; auto range = module_map.equal_range(identity_hash); auto it = std::find_if(range.first, range.second, [&](decltype(*module_map.begin()) data) { return this == data.second; }); assert(it != range.second); module_map.erase(it); } } ModuleHandle::ModuleHandleTransferable::ModuleHandleTransferable(shared_ptr info) : info(std::move(info)) {} auto ModuleHandle::ModuleHandleTransferable::TransferIn() -> Local { return ClassHandle::NewInstance(info); }; ModuleHandle::ModuleHandle(shared_ptr info) : info(std::move(info)) {} auto ModuleHandle::Definition() -> Local { return Inherit(MakeClass( "Module", nullptr, "dependencySpecifiers", MemberAccessor{}, "instantiate", MemberFunction{}, "instantiateSync", MemberFunction{}, "evaluate", MemberFunction), &ModuleHandle::Evaluate<1>>{}, "evaluateSync", MemberFunction), &ModuleHandle::Evaluate<0>>{}, "namespace", MemberAccessor{}, "release", MemberFunction{} )); } auto ModuleHandle::TransferOut() -> std::unique_ptr { return std::make_unique(info); } auto ModuleHandle::GetDependencySpecifiers() -> Local { Isolate* isolate = Isolate::GetCurrent(); size_t length = info->dependency_specifiers.size(); Local deps = Array::New(isolate, length); for (size_t ii = 0; ii < length; ++ii) { Unmaybe(deps->Set(isolate->GetCurrentContext(), ii, v8_string(info->dependency_specifiers[ii].c_str()))); } return deps; } auto ModuleHandle::GetInfo() const -> std::shared_ptr { if (!info) { throw RuntimeGenericError("Module has been released"); } return info; } auto ModuleHandle::Release() -> Local { info.reset(); return Undefined(Isolate::GetCurrent()); } void ModuleHandle::InitializeImportMeta(Local context, Local module, Local meta) { ModuleInfo* found = LookupModuleInfo(module); if (found != nullptr) { if (found->meta_callback) { detail::RunBarrier([&]() { Local argv[1]; argv[0] = meta; Unmaybe(found->meta_callback.Deref()->Call(context, Undefined(context->GetIsolate()), 1, argv)); }); } } } /** * Implements the module linking logic used by `instantiate`. This is implemented as a class handle * so v8 can manage the lifetime of the linker. If a promise fails to resolve then v8 will be * responsible for calling the destructor. */ class ModuleLinker : public ClassHandle { public: /** * These methods are split out from the main class so I don't have to recreate the class * inheritance in v8 */ struct Implementation { RemoteHandle linker; explicit Implementation(Local linker) : linker(linker) {} virtual ~Implementation() = default; virtual void HandleCallbackReturn(ModuleHandle* module, size_t ii, Local value) = 0; virtual auto Begin(ModuleHandle& module, RemoteHandle context) -> Local = 0; auto GetLinker() const -> ModuleLinker& { auto* ptr = ClassHandle::Unwrap(linker.Deref()); assert(ptr); return *ptr; } }; private: RemoteHandle callback; std::unique_ptr impl; std::vector> modules; public: static auto Definition() -> v8::Local { return MakeClass("Linker", nullptr); } explicit ModuleLinker(Local callback) : callback(callback) {} ModuleLinker(const ModuleLinker&) = delete; auto operator=(const ModuleLinker&) = delete; ~ModuleLinker() override { Reset(); } template void SetImplementation() { impl = std::make_unique(This()); } template auto GetImplementation() -> T* { return dynamic_cast(impl.get()); } auto Begin(ModuleHandle& module, RemoteHandle context) -> Local { return impl->Begin(module, std::move(context)); } void ResolveDependency(size_t ii, ModuleInfo& module, ModuleHandle* dependency) { { // I don't think the lock is actually needed here because this linker has already claimed // the whole module, and this code will only be running in a single thread.. but putting // up the lock is probably good practice or something. std::lock_guard lock(module.mutex); module.resolutions[module.dependency_specifiers[ii]] = dependency->GetInfo(); } Link(dependency); } void Link(ModuleHandle* module) { // Check current link status auto info = module->GetInfo(); { std::lock_guard lock(info->mutex); switch (info->link_status) { case ModuleInfo::LinkStatus::None: info->link_status = ModuleInfo::LinkStatus::Linking; info->linker = this; break; case ModuleInfo::LinkStatus::Linking: if (info->linker != this) { throw RuntimeGenericError("Module is currently being linked by another linker"); } return; case ModuleInfo::LinkStatus::Linked: return; } } // Recursively link modules.emplace_back(info); Isolate* isolate = Isolate::GetCurrent(); Local context = isolate->GetCurrentContext(); Local recv = Undefined(isolate); Local argv[2]; argv[1] = module->This(); Local fn = callback.Deref(); for (size_t ii = 0; ii < info->dependency_specifiers.size(); ++ii) { argv[0] = v8_string(info->dependency_specifiers[ii].c_str()); impl->HandleCallbackReturn(module, ii, Unmaybe(fn->Call(context, recv, 2, argv))); } } void Reset(ModuleInfo::LinkStatus status = ModuleInfo::LinkStatus::None) { // Clears out dependency info. If the module wasn't instantiated this resets them back to // their original state. If it was instantiated then we don't need the dependencies anymore // anyway. for (auto& module : modules) { std::lock_guard lock(module->mutex); module->linker = nullptr; module->link_status = status; module->resolutions.clear(); } modules.clear(); impl.reset(); } }; /** * Runner for `instantiate`. By the time this is invoked the module will already have all its * dependencies resolved by the linker. */ struct InstantiateRunner : public ThreePhaseTask { RemoteHandle context; shared_ptr info; RemoteHandle linker; static auto ResolveCallback(Local /*context*/, Local specifier, Local /*import_assertions*/, Local referrer) -> MaybeLocal { MaybeLocal ret; detail::RunBarrier([&]() { // Lookup ModuleInfo* instance from `referrer` ModuleInfo* found = LookupModuleInfo(referrer); if (found != nullptr) { // nb: lock is already acquired in `Instantiate` auto& resolutions = found->resolutions; auto it = resolutions.find(*String::Utf8Value{Isolate::GetCurrent(), specifier}); if (it != resolutions.end()) { ret = it->second->handle.Deref(); return; } } throw RuntimeGenericError("Dependency was left unresolved. Please report this error on github."); }); return ret; } InstantiateRunner( RemoteHandle context, shared_ptr info, Local linker ) : context(std::move(context)), info(std::move(info)), linker(linker) { // Sanity check if (this->info->handle.GetIsolateHolder() != this->context.GetIsolateHolder()) { throw RuntimeGenericError("Invalid context"); } } void Phase2() final { Local mod = info->handle.Deref(); Local context_local = context.Deref(); info->context_handle = std::move(context); std::lock_guard lock{info->mutex}; TryCatch try_catch{Isolate::GetCurrent()}; try { Unmaybe(mod->InstantiateModule(context_local, ResolveCallback)); } catch (...) { try_catch.ReThrow(); throw; } // `InstantiateModule` will return Maybe{true} even when there are exceptions pending. // This condition is checked here and a C++ is thrown which will propagate out as a JS // exception. if (try_catch.HasCaught()) { try_catch.ReThrow(); throw RuntimeError(); } } auto Phase3() -> Local final { ClassHandle::Unwrap(linker.Deref())->Reset(ModuleInfo::LinkStatus::Linked); return Undefined(Isolate::GetCurrent()); } }; /** * Async / sync implementations of the linker */ class ModuleLinkerSync : public ModuleLinker::Implementation { private: void HandleCallbackReturn(ModuleHandle* module, size_t ii, Local value) final { ModuleHandle* resolved = value->IsObject() ? ClassHandle::Unwrap(value.As()) : nullptr; if (resolved == nullptr) { throw RuntimeTypeError("Resolved dependency was not `Module`"); } GetLinker().ResolveDependency(ii, *module->GetInfo(), resolved); } public: using ModuleLinker::Implementation::Implementation; auto Begin(ModuleHandle& module, RemoteHandle context) -> Local final { try { GetLinker().Link(&module); } catch (const RuntimeError& err) { GetLinker().Reset(); throw; } auto info = module.GetInfo(); return ThreePhaseTask::Run<0, InstantiateRunner>(*info->handle.GetIsolateHolder(), context, info, linker.Deref()); } }; class ModuleLinkerAsync : public ModuleLinker::Implementation { private: RemoteTuple async_handles; RemoteHandle context; shared_ptr info; uint32_t pending = 0; static auto ModuleResolved(Local holder, Local value) -> Local { detail::RunBarrier([&]() { ModuleHandle* resolved = value->IsObject() ? ClassHandle::Unwrap(value.As()) : nullptr; if (resolved == nullptr) { throw RuntimeTypeError("Resolved dependency was not `Module`"); } Local context = Isolate::GetCurrent()->GetCurrentContext(); auto* linker = ClassHandle::Unwrap(Unmaybe(holder->Get(context, 0)).As()); auto* impl = linker->GetImplementation(); if (impl == nullptr) { return; } auto* module = ClassHandle::Unwrap(Unmaybe(holder->Get(context, 1)).As()); auto ii = Unmaybe(holder->Get(context, 2)).As()->Value(); linker->ResolveDependency(ii, *module->GetInfo(), resolved); if (--impl->pending == 0) { impl->Instantiate(); } }); return Undefined(Isolate::GetCurrent()); } static auto ModuleRejected(ModuleLinker& linker, Local error) -> Local { detail::RunBarrier([&]() { auto* impl = linker.GetImplementation(); if (impl != nullptr) { Unmaybe(impl->async_handles.Deref<0>()->Reject(Isolate::GetCurrent()->GetCurrentContext(), error)); linker.Reset(); } }); return Undefined(Isolate::GetCurrent()); } void HandleCallbackReturn(ModuleHandle* module, size_t ii, Local value) final { // Resolve via Promise.resolve() so thenables will work ++pending; Isolate* isolate = Isolate::GetCurrent(); Local context = isolate->GetCurrentContext(); Local resolver = Unmaybe(Promise::Resolver::New(context)); Local promise = resolver->GetPromise(); Local holder = Array::New(isolate, 3); Unmaybe(holder->Set(context, 0, linker.Deref())); Unmaybe(holder->Set(context, 1, module->This())); Unmaybe(holder->Set(context, 2, Uint32::New(isolate, ii))); promise = Unmaybe(promise->Then(context, Unmaybe( Function::New(context, FreeFunctionWithData{}.callback, holder) ))); Unmaybe(promise->Catch(context, async_handles.Deref<1>())); Unmaybe(resolver->Resolve(context, value)); } void Instantiate() { Unmaybe(async_handles.Deref<0>()->Resolve( Isolate::GetCurrent()->GetCurrentContext(), ThreePhaseTask::Run<1, InstantiateRunner>(*info->handle.GetIsolateHolder(), context, info, linker.Deref()) )); } public: explicit ModuleLinkerAsync(Local linker) : Implementation(linker), async_handles( Unmaybe(Promise::Resolver::New(Isolate::GetCurrent()->GetCurrentContext())), Unmaybe(Function::New( Isolate::GetCurrent()->GetCurrentContext(), FreeFunctionWithData{}.callback, linker) ) ) {} using ModuleLinker::Implementation::Implementation; auto Begin(ModuleHandle& module, RemoteHandle context) -> Local final { GetLinker().Link(&module); info = module.GetInfo(); this->context = std::move(context); if (pending == 0) { Instantiate(); } return async_handles.Deref<0>()->GetPromise(); } }; auto ModuleHandle::Instantiate(ContextHandle& context_handle, Local callback) -> Local { auto context = context_handle.GetContext(); Local linker_handle = ClassHandle::NewInstance(callback); auto* linker = ClassHandle::Unwrap(linker_handle); linker->SetImplementation(); return linker->Begin(*this, context); } auto ModuleHandle::InstantiateSync(ContextHandle& context_handle, Local callback) -> Local { auto context = context_handle.GetContext(); Local linker_handle = ClassHandle::NewInstance(callback); auto* linker = ClassHandle::Unwrap(linker_handle); linker->SetImplementation(); return linker->Begin(*this, context); } struct EvaluateRunner : public ThreePhaseTask { shared_ptr info; std::unique_ptr result; uint32_t timeout; EvaluateRunner(shared_ptr info, uint32_t ms) : info(std::move(info)), timeout(ms) {} void Phase2() final { Local mod = info->handle.Deref(); if (mod->GetStatus() == Module::Status::kUninstantiated) { throw RuntimeGenericError("Module is uninstantiated"); } Local context_local = Deref(info->context_handle); Context::Scope context_scope(context_local); result = OptionalTransferOut(RunWithTimeout(timeout, [&]() { return mod->Evaluate(context_local); })); std::lock_guard lock(info->mutex); info->global_namespace = RemoteHandle(mod->GetModuleNamespace()); } auto Phase3() -> Local final { if (result) { return result->TransferIn(); } else { return Undefined(Isolate::GetCurrent()).As(); } } }; template auto ModuleHandle::Evaluate(MaybeLocal maybe_options) -> Local { auto info = GetInfo(); int32_t timeout_ms = ReadOption(maybe_options, StringTable::Get().timeout, 0); return ThreePhaseTask::Run(*info->handle.GetIsolateHolder(), info, timeout_ms); } auto ModuleHandle::GetNamespace() -> Local { std::lock_guard lock(info->mutex); if (!info->global_namespace) { throw RuntimeGenericError("Module has not been instantiated."); } return ClassHandle::NewInstance(info->handle.GetSharedIsolateHolder(), info->global_namespace, info->context_handle, ReferenceHandle::TypeOf::Object, true, false); } } // namespace ivm