#include "reference_handle.h" #include "external_copy/external_copy.h" #include "isolate/run_with_timeout.h" #include "isolate/three_phase_task.h" #include "transferable.h" #include using namespace v8; using std::shared_ptr; using std::unique_ptr; namespace ivm { namespace { using TypeOf = detail::ReferenceData::TypeOf; auto InferTypeOf(Local value) -> TypeOf { if (value->IsNull()) { return TypeOf::Null; } else if (value->IsUndefined()) { return TypeOf::Undefined; } else if (value->IsNumber()) { return TypeOf::Number; } else if (value->IsString()) { return TypeOf::String; } else if (value->IsBoolean()) { return TypeOf::Boolean; } else if (value->IsFunction()) { return TypeOf::Function; } else { return TypeOf::Object; } } /** * The return value for .derefInto() */ class DereferenceHandleTransferable : public Transferable { public: DereferenceHandleTransferable(shared_ptr isolate, RemoteHandle reference) : isolate{std::move(isolate)}, reference{std::move(reference)} {} auto TransferIn() -> v8::Local final { if (isolate == IsolateEnvironment::GetCurrentHolder()) { return Deref(reference); } else { throw RuntimeTypeError("Cannot dereference this into target isolate"); } } private: shared_ptr isolate; RemoteHandle reference; }; class DereferenceHandle : public TransferableHandle { public: DereferenceHandle(shared_ptr isolate, RemoteHandle reference) : isolate{std::move(isolate)}, reference{std::move(reference)} {} static auto Definition() -> v8::Local { return Inherit(MakeClass("Dereference", nullptr)); } auto TransferOut() -> std::unique_ptr final { if (!reference) { throw RuntimeGenericError("The return value of `derefInto()` should only be used once"); } return std::make_unique(std::move(isolate), std::move(reference)); } private: shared_ptr isolate; RemoteHandle reference; }; } // anonymous namespace namespace detail { ReferenceData::ReferenceData(Local value, bool inherit) : ReferenceData{ IsolateEnvironment::GetCurrentHolder(), RemoteHandle(value), RemoteHandle(Isolate::GetCurrent()->GetCurrentContext()), InferTypeOf(value), false, inherit} {} ReferenceData::ReferenceData( shared_ptr isolate, RemoteHandle reference, RemoteHandle context, TypeOf type_of, bool accessors, bool inherit ) : isolate{std::move(isolate)}, reference{std::move(reference)}, context{std::move(context)}, type_of{type_of}, accessors{accessors}, inherit{inherit} {} } // namespace detail /** * ReferenceHandle implementation */ static std::unique_ptr ReferenceHandle_New_Wrapper(v8::Local value, v8::MaybeLocal options) { return ReferenceHandle::New(value, options); } auto ReferenceHandle::Definition() -> Local { return Inherit(MakeClass( "Reference", ConstructorFunction{}, "deref", MemberFunction{}, "derefInto", MemberFunction{}, "release", MemberFunction{}, "copy", MemberFunction), &ReferenceHandle::Copy<1>>{}, "copySync", MemberFunction), &ReferenceHandle::Copy<0>>{}, "delete", MemberFunction), &ReferenceHandle::Delete<1>>{}, "deleteIgnored", MemberFunction), &ReferenceHandle::Delete<2>>{}, "deleteSync", MemberFunction), &ReferenceHandle::Delete<0>>{}, "get", MemberFunction), &ReferenceHandle::Get<1>>{}, "getSync", MemberFunction), &ReferenceHandle::Get<0>>{}, "set", MemberFunction), &ReferenceHandle::Set<1>>{}, "setIgnored", MemberFunction), &ReferenceHandle::Set<2>>{}, "setSync", MemberFunction), &ReferenceHandle::Set<0>>{}, "apply", MemberFunction), &ReferenceHandle::Apply<1>>{}, "applyIgnored", MemberFunction), &ReferenceHandle::Apply<2>>{}, "applySync", MemberFunction), &ReferenceHandle::Apply<0>>{}, "applySyncPromise", MemberFunction), &ReferenceHandle::Apply<4>>{}, "typeof", MemberAccessor{} )); } auto ReferenceHandle::New(Local value, MaybeLocal options) -> unique_ptr { auto inherit = ReadOption(options, StringTable::Get().unsafeInherit, false); return std::make_unique(value, inherit); } auto ReferenceHandle::TransferOut() -> unique_ptr { return std::make_unique(*this); } /** * Getter for typeof property. */ auto ReferenceHandle::TypeOfGetter() -> Local { CheckDisposed(); switch (type_of) { case TypeOf::Null: return StringTable::Get().null; case TypeOf::Undefined: return StringTable::Get().undefined; case TypeOf::Number: return StringTable::Get().number; case TypeOf::String: return StringTable::Get().string; case TypeOf::Boolean: return StringTable::Get().boolean; case TypeOf::Object: return StringTable::Get().object; case TypeOf::Function: return StringTable::Get().function; } std::terminate(); } /** * Attempt to return this handle to the current context. */ auto ReferenceHandle::Deref(MaybeLocal maybe_options) -> Local { CheckDisposed(); if (isolate.get() != IsolateEnvironment::GetCurrentHolder().get()) { throw RuntimeTypeError("Cannot dereference this from current isolate"); } bool release = ReadOption(maybe_options, StringTable::Get().release, false); Local ret = ivm::Deref(reference); if (release) { Release(); } return ret; } /** * Return a handle which will dereference itself when passing into another isolate. */ auto ReferenceHandle::DerefInto(MaybeLocal maybe_options) -> Local { CheckDisposed(); bool release = ReadOption(maybe_options, StringTable::Get().release, false); Local ret = ClassHandle::NewInstance(isolate, reference); if (release) { Release(); } return ret; } /** * Release this reference. */ auto ReferenceHandle::Release() -> Local { CheckDisposed(); isolate.reset(); reference = {}; context = {}; return Undefined(Isolate::GetCurrent()); } /** * Call a function, like Function.prototype.apply */ class ApplyRunner : public ThreePhaseTask { public: ApplyRunner( ReferenceHandle& that, MaybeLocal recv_handle, Maybe maybe_arguments, MaybeLocal maybe_options ) : context{that.context}, reference{that.reference} { that.CheckDisposed(); // Get receiver, holder, this, whatever Local recv_local; if (recv_handle.ToLocal(&recv_local)) { recv = TransferOut(recv_local); } // Get run options TransferOptions arguments_transfer_options; Local options; if (maybe_options.ToLocal(&options)) { timeout = ReadOption(options, StringTable::Get().timeout, 0); arguments_transfer_options = TransferOptions{ ReadOption>(options, StringTable::Get().arguments, {})}; return_transfer_options = TransferOptions{ ReadOption>(options, StringTable::Get().result, {}), TransferOptions::Type::Reference}; } // Externalize all arguments ArrayRange arguments; if (maybe_arguments.To(&arguments)) { argv.reserve(std::distance(arguments.begin(), arguments.end())); for (auto argument : arguments) { argv.push_back(TransferOut(argument, arguments_transfer_options)); } } } ApplyRunner(const ApplyRunner&) = delete; auto operator=(const ApplyRunner&) = delete; ~ApplyRunner() final { if (did_finish) { *did_finish = 1; } } void Phase2() final { // Invoke in the isolate Local context_handle = Deref(context); Context::Scope context_scope{context_handle}; Local fn = Deref(reference); if (!fn->IsFunction()) { throw RuntimeTypeError("Reference is not a function"); } std::vector> argv_inner = TransferArguments(); Local recv_inner = recv->TransferIn(); Local result = RunWithTimeout(timeout, [&fn, &context_handle, &recv_inner, &argv_inner]() { return fn.As()->Call(context_handle, recv_inner, argv_inner.size(), argv_inner.empty() ? nullptr : &argv_inner[0]); } ); ret = TransferOut(result, return_transfer_options); } auto Phase2Async(Scheduler::AsyncWait& wait) -> bool final { // Same as regular `Phase2()` but if it returns a promise we will wait on it if (!(return_transfer_options == TransferOptions{TransferOptions::Type::Reference})) { throw RuntimeTypeError("`result` options are not available for `applySyncPromise`"); } Local context_handle = Deref(context); Context::Scope context_scope{context_handle}; Local fn = Deref(reference); if (!fn->IsFunction()) { throw RuntimeTypeError("Reference is not a function"); } Local recv_inner = recv->TransferIn(); std::vector> argv_inner = TransferArguments(); Local value = RunWithTimeout( timeout, [&fn, &context_handle, &recv_inner, &argv_inner]() { return fn.As()->Call(context_handle, recv_inner, argv_inner.size(), argv_inner.empty() ? nullptr : &argv_inner[0]); } ); if (value->IsPromise()) { Isolate* isolate = Isolate::GetCurrent(); // This is only called from the default isolate, so we don't need an IsolateSpecific static Persistent callback_persistent{isolate, CompileAsyncWrapper()}; Local callback_fn = Deref(callback_persistent); did_finish = std::make_shared(0); std::array, 3> argv; argv[0] = External::New(isolate, reinterpret_cast(this)); argv[1] = External::New(isolate, new shared_ptr(did_finish)); argv[2] = value; async_wait = &wait; Unmaybe(callback_fn->Call(context_handle, callback_fn, 3, &argv.front())); return true; } else { ret = TransferOut(value, return_transfer_options); return false; } } auto Phase3() -> Local final { if (did_finish && *did_finish == 0) { *did_finish = 1; throw RuntimeGenericError("Script execution timed out."); } else if (async_error) { Isolate::GetCurrent()->ThrowException(async_error->CopyInto()); throw RuntimeError(); } else { return ret->TransferIn(); } } private: /** * This is an internal callback that will be called after a Promise returned from * `applySyncPromise` has resolved */ static void AsyncCallback(const v8::FunctionCallbackInfo& info) { // It's possible the invocation timed out, in which case the ApplyRunner will be dead. The // shared_ptr here will be marked as true and we can exit early. auto* did_finish_ptr = reinterpret_cast*>(info[1].As()->Value()); auto did_finish = std::move(*did_finish_ptr); delete did_finish_ptr; if (*did_finish == 1) { return; } ApplyRunner& self = *reinterpret_cast(info[0].As()->Value()); if (info.Length() == 3) { // Resolved FunctorRunners::RunCatchExternal(IsolateEnvironment::GetCurrent().DefaultContext(), [&self, &info]() { self.ret = TransferOut(info[2]); }, [&self](unique_ptr error) { self.async_error = std::move(error); }); } else { // Rejected self.async_error = ExternalCopy::CopyThrownValue(info[3]); } *self.did_finish = 1; self.async_wait->Done(); } /** * The C++ promise interface is a little clumsy so this does some work in JS for us. This function * is called once and returns a JS function that will be reused. */ static auto CompileAsyncWrapper() -> Local { Isolate* isolate = Isolate::GetCurrent(); Local context = IsolateEnvironment::GetCurrent().DefaultContext(); Local