API/hermes/hermes.cpp (1,802 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include "hermes.h" #include "llvh/Support/Compiler.h" #include "hermes/BCGen/HBC/BytecodeDataProvider.h" #include "hermes/BCGen/HBC/BytecodeFileFormat.h" #include "hermes/BCGen/HBC/BytecodeProviderFromSrc.h" #include "hermes/DebuggerAPI.h" #include "hermes/Platform/Logging.h" #include "hermes/Public/RuntimeConfig.h" #include "hermes/SourceMap/SourceMapParser.h" #include "hermes/Support/SimpleDiagHandler.h" #include "hermes/Support/UTF16Stream.h" #include "hermes/Support/UTF8.h" #include "hermes/VM/CallResult.h" #include "hermes/VM/Debugger/Debugger.h" #include "hermes/VM/GC.h" #include "hermes/VM/HostModel.h" #include "hermes/VM/JSArray.h" #include "hermes/VM/JSArrayBuffer.h" #include "hermes/VM/JSLib.h" #include "hermes/VM/JSLib/RuntimeCommonStorage.h" #include "hermes/VM/JSLib/RuntimeJSONUtils.h" #include "hermes/VM/Operations.h" #include "hermes/VM/Profiler/CodeCoverageProfiler.h" #include "hermes/VM/Profiler/SamplingProfiler.h" #include "hermes/VM/Runtime.h" #include "hermes/VM/StringPrimitive.h" #include "hermes/VM/StringView.h" #include "hermes/VM/SymbolID.h" #include "hermes/VM/TimeLimitMonitor.h" #include "llvh/Support/ErrorHandling.h" #include "llvh/Support/FileSystem.h" #include "llvh/Support/SHA1.h" #include "llvh/Support/raw_os_ostream.h" #include <algorithm> #include <atomic> #include <limits> #include <list> #include <mutex> #include <system_error> #include <unordered_map> #include <jsi/instrumentation.h> #include <jsi/threadsafe.h> #ifdef HERMESVM_LLVM_PROFILE_DUMP extern "C" { int __llvm_profile_dump(void); } #endif // Android OSS has a bug where exception data can get mangled when going via // fbjni. This macro can be used to expose the root cause in adb log. It serves // no purpose other than as a backup. #ifdef __ANDROID__ #define LOG_EXCEPTION_CAUSE(...) hermesLog("HermesVM", __VA_ARGS__) #else #define LOG_EXCEPTION_CAUSE(...) \ do { \ } while (0) #endif // If a function body might throw C++ exceptions other than // jsi::JSError from Hermes, it should be wrapped in this form: // // return maybeRethrow([&] { body }) // // This will execute body; if exceptions are enabled, this execution // will be wrapped in a try/catch that catches those exceptions, and // rethrows them as JSI exceptions. // This function should be used to wrap any JSI APIs that may allocate // memory(for OOM) or may enter interpreter(call a VM API with _RJS postfix). // We convert the hermes exception into a JSINativeException; JSError represents // exceptions mandated by the spec, and JSINativeException covers all // other exceptions. namespace { template <typename F> auto maybeRethrow(const F &f) -> decltype(f()) { #ifdef HERMESVM_EXCEPTION_ON_OOM try { return f(); } catch (const ::hermes::vm::JSOutOfMemoryError &ex) { // We surface this as a JSINativeException -- the out of memory // exception is not part of the spec. throw ::facebook::jsi::JSINativeException(ex.what()); } #else // HERMESVM_EXCEPTION_ON_OOM return f(); #endif } } // namespace namespace vm = hermes::vm; namespace hbc = hermes::hbc; using ::hermes::hermesLog; namespace facebook { namespace hermes { namespace detail { #if !defined(NDEBUG) && !defined(ASSERT_ON_DANGLING_VM_REFS) #define ASSERT_ON_DANGLING_VM_REFS #endif static void (*sApiFatalHandler)(const std::string &) = nullptr; /// Handler called by HermesVM to report unrecoverable errors. /// This is a forward declaration to prevent a compiler warning. void hermesFatalErrorHandler( void *user_data, const std::string &reason, bool gen_crash_diag); void hermesFatalErrorHandler( void * /*user_data*/, const std::string &reason, bool /*gen_crash_diag*/) { // Actually crash and let breakpad handle the reporting. if (sApiFatalHandler) { sApiFatalHandler(reason); } else { *((volatile int *)nullptr) = 42; } } } // namespace detail namespace { // Max size of the runtime's register stack. // The runtime register stack needs to be small enough to be allocated on the // native thread stack in Android (1MiB) and on MacOS's thread stack (512 KiB) // Calculated by: (thread stack size - size of runtime - // 8 memory pages for other stuff in the thread) static constexpr unsigned kMaxNumRegisters = (512 * 1024 - sizeof(::hermes::vm::Runtime) - 4096 * 8) / sizeof(::hermes::vm::PinnedHermesValue); void raw_ostream_append(llvh::raw_ostream &os) {} template <typename Arg0, typename... Args> void raw_ostream_append(llvh::raw_ostream &os, Arg0 &&arg0, Args &&...args) { os << arg0; raw_ostream_append(os, args...); } template <typename... Args> jsi::JSError makeJSError(jsi::Runtime &rt, Args &&...args) { std::string s; llvh::raw_string_ostream os(s); raw_ostream_append(os, std::forward<Args>(args)...); LOG_EXCEPTION_CAUSE("JSError: %s", os.str().c_str()); return jsi::JSError(rt, os.str()); } /// HermesVM uses the LLVM fatal error handle to report fatal errors. This /// wrapper helps us install the handler at construction time, before any /// HermesVM code has been invoked. class InstallHermesFatalErrorHandler { public: InstallHermesFatalErrorHandler() { // The LLVM fatal error handler can only be installed once. Use a Meyer's // singleton to guarantee it - the static "dummy" is guaranteed by the // compiler to be initialized no more than once. static int dummy = ([]() { llvh::install_fatal_error_handler(detail::hermesFatalErrorHandler); return 0; })(); (void)dummy; } }; } // namespace // Recording timing stats for every JS<->C++ transition has some overhead, so // applications where such transitions are extremely frequent may want to define // the HERMESJSI_DISABLE_STATS_TIMER symbol to save this overhead. #ifdef HERMESJSI_DISABLE_STATS_TIMER #define STATS_TIMER(rt, desc, field) #else #define STATS_TIMER(rt, desc, field) \ auto &_stats = (rt).runtime_.getRuntimeStats(); \ const vm::instrumentation::RAIITimer _timer{desc, _stats, _stats.field}; #endif class HermesRuntimeImpl final : public HermesRuntime, private InstallHermesFatalErrorHandler, private jsi::Instrumentation { public: static constexpr uint32_t kSentinelNativeValue = 0x6ef71fe1; HermesRuntimeImpl(const vm::RuntimeConfig &runtimeConfig) : rt_(::hermes::vm::Runtime::create( runtimeConfig.rebuild() .withRegisterStack(nullptr) .withMaxNumRegisters(kMaxNumRegisters) .build())), runtime_(*rt_), vmExperimentFlags_(runtimeConfig.getVMExperimentFlags()) { #ifdef HERMES_ENABLE_DEBUGGER compileFlags_.debug = true; #endif switch (runtimeConfig.getCompilationMode()) { case vm::SmartCompilation: compileFlags_.lazy = true; // (Leaves thresholds at default values) break; case vm::ForceEagerCompilation: compileFlags_.lazy = false; break; case vm::ForceLazyCompilation: compileFlags_.lazy = true; compileFlags_.preemptiveFileCompilationThreshold = 0; compileFlags_.preemptiveFunctionCompilationThreshold = 0; break; } compileFlags_.enableGenerator = runtimeConfig.getEnableGenerator(); compileFlags_.emitAsyncBreakCheck = defaultEmitAsyncBreakCheck_ = runtimeConfig.getAsyncBreakCheckInEval(); runtime_.addCustomRootsFunction( [this](vm::GC *, vm::RootAcceptor &acceptor) { for (auto it = hermesValues_->begin(); it != hermesValues_->end();) { if (it->get() == 0) { it = hermesValues_->erase(it); } else { acceptor.accept(const_cast<vm::PinnedHermesValue &>(it->phv)); ++it; } } }); runtime_.addCustomWeakRootsFunction( [this](vm::GC *, vm::WeakRootAcceptor &acceptor) { for (auto it = weakHermesValues_->begin(); it != weakHermesValues_->end();) { if (it->get() == 0) { it = weakHermesValues_->erase(it); } else { acceptor.acceptWeak(it->wr); ++it; } } }); runtime_.addCustomSnapshotFunction( [this](vm::HeapSnapshot &snap) { snap.beginNode(); snap.endNode( vm::HeapSnapshot::NodeType::Native, "ManagedValues", vm::GCBase::IDTracker::reserved( vm::GCBase::IDTracker::ReservedObjectID::JSIHermesValueList), hermesValues_->size() * sizeof(HermesPointerValue), 0); snap.beginNode(); snap.endNode( vm::HeapSnapshot::NodeType::Native, "ManagedValues", vm::GCBase::IDTracker::reserved( vm::GCBase::IDTracker::ReservedObjectID:: JSIWeakHermesValueList), weakHermesValues_->size() * sizeof(WeakRefPointerValue), 0); }, [](vm::HeapSnapshot &snap) { snap.addNamedEdge( vm::HeapSnapshot::EdgeType::Internal, "hermesValues", vm::GCBase::IDTracker::reserved( vm::GCBase::IDTracker::ReservedObjectID::JSIHermesValueList)); snap.addNamedEdge( vm::HeapSnapshot::EdgeType::Internal, "weakHermesValues", vm::GCBase::IDTracker::reserved( vm::GCBase::IDTracker::ReservedObjectID:: JSIWeakHermesValueList)); }); } public: ~HermesRuntimeImpl() { #ifdef HERMES_ENABLE_DEBUGGER // Deallocate the debugger so it frees any HermesPointerValues it may hold. // This must be done before we check hermesValues_ below. debugger_.reset(); #endif } #ifdef HERMES_ENABLE_DEBUGGER // This should only be called once by the factory. void setDebugger(std::unique_ptr<debugger::Debugger> d) { debugger_ = std::move(d); } #endif struct CountedPointerValue : PointerValue { CountedPointerValue() : refCount(1) {} void invalidate() override { #ifdef ASSERT_ON_DANGLING_VM_REFS assert( ((1 << 31) & refCount) == 0 && "This PointerValue was left dangling after the Runtime was destroyed."); #endif dec(); } void inc() { auto oldCount = refCount.fetch_add(1, std::memory_order_relaxed); assert(oldCount + 1 != 0 && "Ref count overflow"); (void)oldCount; } void dec() { auto oldCount = refCount.fetch_sub(1, std::memory_order_relaxed); assert(oldCount > 0 && "Ref count underflow"); (void)oldCount; } uint32_t get() const { return refCount.load(std::memory_order_relaxed); } #ifdef ASSERT_ON_DANGLING_VM_REFS void markDangling() { // Mark this PointerValue as dangling by setting the top bit AND the // second-top bit. The top bit is used to determine if the pointer is // dangling. Setting the second-top bit ensures that accidental // over-calling the dec() function doesn't clear the top bit without // complicating the implementation of dec(). refCount |= 0b11 << 30; } #endif private: std::atomic<uint32_t> refCount; }; struct HermesPointerValue final : CountedPointerValue { HermesPointerValue(vm::HermesValue hv) : phv(hv) {} // This should only ever be modified by the GC. We const_cast the // reference before passing it to the GC. const vm::PinnedHermesValue phv; }; struct WeakRefPointerValue final : CountedPointerValue { WeakRefPointerValue(vm::WeakRoot<vm::JSObject> _wr) : wr(_wr) {} vm::WeakRoot<vm::JSObject> wr; }; HermesPointerValue *clone(const Runtime::PointerValue *pv) { if (!pv) { return nullptr; } // These are only ever allocated by us, so we can remove their constness auto result = static_cast<HermesPointerValue *>( const_cast<Runtime::PointerValue *>(pv)); result->inc(); return result; } template <typename T> T add(::hermes::vm::HermesValue hv) { static_assert( std::is_base_of<jsi::Pointer, T>::value, "this type cannot be added"); hermesValues_->emplace_front(hv); return make<T>(&(hermesValues_->front())); } jsi::WeakObject addWeak(::hermes::vm::WeakRoot<vm::JSObject> wr) { weakHermesValues_->emplace_front(wr); return make<jsi::WeakObject>(&(weakHermesValues_->front())); } // overriden from jsi::Instrumentation std::string getRecordedGCStats() override { std::string s; llvh::raw_string_ostream os(s); runtime_.printHeapStats(os); return os.str(); } // Overridden from jsi::Instrumentation // See include/hermes/VM/GCBase.h for documentation of the fields std::unordered_map<std::string, int64_t> getHeapInfo( bool includeExpensive) override { vm::GCBase::HeapInfo info; if (includeExpensive) { runtime_.getHeap().getHeapInfoWithMallocSize(info); } else { runtime_.getHeap().getHeapInfo(info); } #ifndef NDEBUG vm::GCBase::DebugHeapInfo debugInfo; runtime_.getHeap().getDebugHeapInfo(debugInfo); #endif std::unordered_map<std::string, int64_t> jsInfo; #define BRIDGE_INFO(TYPE, HOLDER, NAME) \ jsInfo["hermes_" #NAME] = static_cast<TYPE>(HOLDER.NAME); BRIDGE_INFO(int, info, numCollections); BRIDGE_INFO(double, info, totalAllocatedBytes); BRIDGE_INFO(double, info, allocatedBytes); BRIDGE_INFO(double, info, heapSize); BRIDGE_INFO(double, info, va); BRIDGE_INFO(double, info, externalBytes); BRIDGE_INFO(int, info, numMarkStackOverflows); if (includeExpensive) { BRIDGE_INFO(double, info, mallocSizeEstimate); } #ifndef NDEBUG BRIDGE_INFO(int, debugInfo, numAllocatedObjects); BRIDGE_INFO(int, debugInfo, numReachableObjects); BRIDGE_INFO(int, debugInfo, numCollectedObjects); BRIDGE_INFO(int, debugInfo, numFinalizedObjects); BRIDGE_INFO(int, debugInfo, numMarkedSymbols); BRIDGE_INFO(int, debugInfo, numHiddenClasses); BRIDGE_INFO(int, debugInfo, numLeafHiddenClasses); #endif #undef BRIDGE_INFO jsInfo["hermes_peakAllocatedBytes"] = runtime_.getHeap().getPeakAllocatedBytes(); jsInfo["hermes_peakLiveAfterGC"] = runtime_.getHeap().getPeakLiveAfterGC(); #define BRIDGE_GEN_INFO(NAME, STAT_EXPR, FACTOR) \ jsInfo["hermes_full_" #NAME] = info.fullStats.STAT_EXPR * FACTOR; \ jsInfo["hermes_yg_" #NAME] = info.youngGenStats.STAT_EXPR * FACTOR; BRIDGE_GEN_INFO(numCollections, numCollections, 1.0); // Times are converted from seconds to milliseconds for the logging pipeline // ... BRIDGE_GEN_INFO(gcTime, gcWallTime.sum(), 1000); BRIDGE_GEN_INFO(maxPause, gcWallTime.max(), 1000); BRIDGE_GEN_INFO(gcCPUTime, gcCPUTime.sum(), 1000); BRIDGE_GEN_INFO(gcMaxCPUPause, gcCPUTime.max(), 1000); // ... and since this is square seconds, we must square the 1000 too. BRIDGE_GEN_INFO(gcTimeSquares, gcWallTime.sumOfSquares(), 1000 * 1000); BRIDGE_GEN_INFO(gcCPUTimeSquares, gcCPUTime.sumOfSquares(), 1000 * 1000); #undef BRIDGE_GEN_INFO return jsInfo; } // Overridden from jsi::Instrumentation void collectGarbage(std::string cause) override { if ((vmExperimentFlags_ & vm::experiments::IgnoreMemoryWarnings) && cause == "TRIM_MEMORY_RUNNING_CRITICAL") { // Do nothing if the GC is a memory warning. // TODO(T79835917): Remove this after proving this is the cause of OOMs // and finding a better resolution. return; } runtime_.collect(std::move(cause)); } // Overridden from jsi::Instrumentation void startTrackingHeapObjectStackTraces( std::function<void( uint64_t, std::chrono::microseconds, std::vector<HeapStatsUpdate>)> fragmentCallback) override { runtime_.enableAllocationLocationTracker(std::move(fragmentCallback)); } // Overridden from jsi::Instrumentation void stopTrackingHeapObjectStackTraces() override { runtime_.disableAllocationLocationTracker(); } // Overridden from jsi::Instrumentation void startHeapSampling(size_t samplingInterval) override { runtime_.enableSamplingHeapProfiler(samplingInterval); } // Overridden from jsi::Instrumentation void stopHeapSampling(std::ostream &os) override { llvh::raw_os_ostream ros(os); runtime_.disableSamplingHeapProfiler(ros); } // Overridden from jsi::Instrumentation void createSnapshotToFile(const std::string &path) override { std::error_code code; llvh::raw_fd_ostream os(path, code, llvh::sys::fs::FileAccess::FA_Write); if (code) { throw std::system_error(code); } runtime_.getHeap().createSnapshot(os); } // Overridden from jsi::Instrumentation void createSnapshotToStream(std::ostream &os) override { llvh::raw_os_ostream ros(os); runtime_.getHeap().createSnapshot(ros); } // Overridden from jsi::Instrumentation std::string flushAndDisableBridgeTrafficTrace() override { throw std::logic_error( "Bridge traffic trace is only supported by TracingRuntime"); } // Overridden from jsi::Instrumentation void writeBasicBlockProfileTraceToFile( const std::string &fileName) const override { #ifdef HERMESVM_PROFILER_BB std::error_code ec; llvh::raw_fd_ostream os(fileName.c_str(), ec, llvh::sys::fs::F_Text); if (ec) { throw std::system_error(ec); } runtime_.dumpBasicBlockProfileTrace(os); #else throw std::logic_error( "Cannot write the basic block profile trace out if Hermes wasn't built with " "hermes.profiler=BB"); #endif } // Overridden from jsi::Instrumentation void dumpProfilerSymbolsToFile(const std::string &fileName) const override { #ifdef HERMESVM_PROFILER_EXTERN dumpProfilerSymbolMap(&runtime_, fileName); #else throw std::logic_error( "Cannot dump profiler symbols out if Hermes wasn't built with " "hermes.profiler=EXTERN"); #endif } // These are all methods which do pointer type gymnastics and should // mostly inline and optimize away. static const ::hermes::vm::PinnedHermesValue &phv( const jsi::Pointer &pointer) { assert( dynamic_cast<const HermesPointerValue *>(getPointerValue(pointer)) && "Pointer does not contain a HermesPointerValue"); return static_cast<const HermesPointerValue *>(getPointerValue(pointer)) ->phv; } static const ::hermes::vm::PinnedHermesValue &phv(const jsi::Value &value) { assert( dynamic_cast<const HermesPointerValue *>(getPointerValue(value)) && "Pointer does not contain a HermesPointerValue"); return static_cast<const HermesPointerValue *>(getPointerValue(value))->phv; } static ::hermes::vm::Handle<::hermes::vm::HermesValue> stringHandle( const jsi::String &str) { return ::hermes::vm::Handle<::hermes::vm::HermesValue>::vmcast(&phv(str)); } static ::hermes::vm::Handle<::hermes::vm::JSObject> handle( const jsi::Object &obj) { return ::hermes::vm::Handle<::hermes::vm::JSObject>::vmcast(&phv(obj)); } static ::hermes::vm::Handle<::hermes::vm::JSArray> arrayHandle( const jsi::Array &arr) { return ::hermes::vm::Handle<::hermes::vm::JSArray>::vmcast(&phv(arr)); } static ::hermes::vm::Handle<::hermes::vm::JSArrayBuffer> arrayBufferHandle( const jsi::ArrayBuffer &arr) { return ::hermes::vm::Handle<::hermes::vm::JSArrayBuffer>::vmcast(&phv(arr)); } static ::hermes::vm::WeakRoot<vm::JSObject> &weakRoot(jsi::Pointer &pointer) { assert( dynamic_cast<WeakRefPointerValue *>(getPointerValue(pointer)) && "Pointer does not contain a WeakRefPointerValue"); return static_cast<WeakRefPointerValue *>(getPointerValue(pointer))->wr; } // These helpers use public (mostly) interfaces on the runtime and // value types to convert between jsi and vm types. static vm::HermesValue hvFromValue(const jsi::Value &value) { if (value.isUndefined()) { return vm::HermesValue::encodeUndefinedValue(); } else if (value.isNull()) { return vm::HermesValue::encodeNullValue(); } else if (value.isBool()) { return vm::HermesValue::encodeBoolValue(value.getBool()); } else if (value.isNumber()) { return vm::HermesValue::encodeUntrustedDoubleValue(value.getNumber()); } else if (value.isSymbol() || value.isString() || value.isObject()) { return phv(value); } else { llvm_unreachable("unknown value kind"); } } vm::Handle<> vmHandleFromValue(const jsi::Value &value) { if (value.isUndefined()) { return vm::Runtime::getUndefinedValue(); } else if (value.isNull()) { return vm::Runtime::getNullValue(); } else if (value.isBool()) { return vm::Runtime::getBoolValue(value.getBool()); } else if (value.isNumber()) { return runtime_.makeHandle( vm::HermesValue::encodeUntrustedDoubleValue(value.getNumber())); } else if (value.isSymbol() || value.isString() || value.isObject()) { return vm::Handle<vm::HermesValue>(&phv(value)); } else { llvm_unreachable("unknown value kind"); } } jsi::Value valueFromHermesValue(vm::HermesValue hv) { if (hv.isUndefined() || hv.isEmpty()) { return jsi::Value::undefined(); } else if (hv.isNull()) { return nullptr; } else if (hv.isBool()) { return hv.getBool(); } else if (hv.isDouble()) { return hv.getDouble(); } else if (hv.isSymbol()) { return add<jsi::Symbol>(hv); } else if (hv.isString()) { return add<jsi::String>(hv); } else if (hv.isObject()) { return add<jsi::Object>(hv); } else { llvm_unreachable("unknown HermesValue type"); } } /// Same as \c prepareJavaScript but with a source map. std::shared_ptr<const jsi::PreparedJavaScript> prepareJavaScriptWithSourceMap( const std::shared_ptr<const jsi::Buffer> &buffer, const std::shared_ptr<const jsi::Buffer> &sourceMapBuf, std::string sourceURL); // Concrete declarations of jsi::Runtime pure virtual methods std::shared_ptr<const jsi::PreparedJavaScript> prepareJavaScript( const std::shared_ptr<const jsi::Buffer> &buffer, std::string sourceURL) override; jsi::Value evaluatePreparedJavaScript( const std::shared_ptr<const jsi::PreparedJavaScript> &js) override; jsi::Value evaluateJavaScript( const std::shared_ptr<const jsi::Buffer> &buffer, const std::string &sourceURL) override; bool drainMicrotasks(int maxMicrotasksHint = -1) override; jsi::Object global() override; std::string description() override; bool isInspectable() override; jsi::Instrumentation &instrumentation() override; PointerValue *cloneSymbol(const Runtime::PointerValue *pv) override; PointerValue *cloneString(const Runtime::PointerValue *pv) override; PointerValue *cloneObject(const Runtime::PointerValue *pv) override; PointerValue *clonePropNameID(const Runtime::PointerValue *pv) override; jsi::PropNameID createPropNameIDFromAscii(const char *str, size_t length) override; jsi::PropNameID createPropNameIDFromUtf8(const uint8_t *utf8, size_t length) override; jsi::PropNameID createPropNameIDFromString(const jsi::String &str) override; jsi::PropNameID createPropNameIDFromSymbol(const jsi::Symbol &sym) override; std::string utf8(const jsi::PropNameID &) override; bool compare(const jsi::PropNameID &, const jsi::PropNameID &) override; std::string symbolToString(const jsi::Symbol &) override; jsi::String createStringFromAscii(const char *str, size_t length) override; jsi::String createStringFromUtf8(const uint8_t *utf8, size_t length) override; std::string utf8(const jsi::String &) override; jsi::Value createValueFromJsonUtf8(const uint8_t *json, size_t length) override; jsi::Object createObject() override; jsi::Object createObject(std::shared_ptr<jsi::HostObject> ho) override; std::shared_ptr<jsi::HostObject> getHostObject(const jsi::Object &) override; jsi::HostFunctionType &getHostFunction(const jsi::Function &) override; jsi::Value getProperty(const jsi::Object &, const jsi::PropNameID &name) override; jsi::Value getProperty(const jsi::Object &, const jsi::String &name) override; bool hasProperty(const jsi::Object &, const jsi::PropNameID &name) override; bool hasProperty(const jsi::Object &, const jsi::String &name) override; void setPropertyValue( jsi::Object &, const jsi::PropNameID &name, const jsi::Value &value) override; void setPropertyValue( jsi::Object &, const jsi::String &name, const jsi::Value &value) override; bool isArray(const jsi::Object &) const override; bool isArrayBuffer(const jsi::Object &) const override; bool isFunction(const jsi::Object &) const override; bool isHostObject(const jsi::Object &) const override; bool isHostFunction(const jsi::Function &) const override; jsi::Array getPropertyNames(const jsi::Object &) override; jsi::WeakObject createWeakObject(const jsi::Object &) override; jsi::Value lockWeakObject(jsi::WeakObject &) override; jsi::Array createArray(size_t length) override; size_t size(const jsi::Array &) override; size_t size(const jsi::ArrayBuffer &) override; uint8_t *data(const jsi::ArrayBuffer &) override; jsi::Value getValueAtIndex(const jsi::Array &, size_t i) override; void setValueAtIndexImpl(jsi::Array &, size_t i, const jsi::Value &value) override; jsi::Function createFunctionFromHostFunction( const jsi::PropNameID &name, unsigned int paramCount, jsi::HostFunctionType func) override; jsi::Value call( const jsi::Function &, const jsi::Value &jsThis, const jsi::Value *args, size_t count) override; jsi::Value callAsConstructor( const jsi::Function &, const jsi::Value *args, size_t count) override; bool strictEquals(const jsi::Symbol &a, const jsi::Symbol &b) const override; bool strictEquals(const jsi::String &a, const jsi::String &b) const override; bool strictEquals(const jsi::Object &a, const jsi::Object &b) const override; bool instanceOf(const jsi::Object &o, const jsi::Function &ctor) override; ScopeState *pushScope() override; void popScope(ScopeState *prv) override; void checkStatus(vm::ExecutionStatus); vm::HermesValue stringHVFromAscii(const char *ascii, size_t length); vm::HermesValue stringHVFromUtf8(const uint8_t *utf8, size_t length); size_t getLength(vm::Handle<vm::ArrayImpl> arr); size_t getByteLength(vm::Handle<vm::JSArrayBuffer> arr); struct JsiProxy final : public vm::HostObjectProxy { HermesRuntimeImpl &rt_; std::shared_ptr<jsi::HostObject> ho_; JsiProxy(HermesRuntimeImpl &rt, std::shared_ptr<jsi::HostObject> ho) : rt_(rt), ho_(ho) {} vm::CallResult<vm::HermesValue> get(vm::SymbolID id) override { STATS_TIMER(rt_, "HostObject.get", hostFunction); jsi::PropNameID sym = rt_.add<jsi::PropNameID>(vm::HermesValue::encodeSymbolValue(id)); jsi::Value ret; try { ret = ho_->get(rt_, sym); } catch (const jsi::JSError &error) { return rt_.runtime_.setThrownValue(hvFromValue(error.value())); } catch (const std::exception &ex) { return rt_.runtime_.setThrownValue(hvFromValue( rt_.global() .getPropertyAsFunction(rt_, "Error") .call( rt_, "Exception in HostObject::get for prop '" + rt_.runtime_.getIdentifierTable().convertSymbolToUTF8( id) + "': " + ex.what()))); } catch (...) { return rt_.runtime_.setThrownValue(hvFromValue( rt_.global() .getPropertyAsFunction(rt_, "Error") .call( rt_, "Exception in HostObject::get: for prop '" + rt_.runtime_.getIdentifierTable().convertSymbolToUTF8( id) + "': <unknown exception>"))); } return hvFromValue(ret); } vm::CallResult<bool> set(vm::SymbolID id, vm::HermesValue value) override { STATS_TIMER(rt_, "HostObject.set", hostFunction); jsi::PropNameID sym = rt_.add<jsi::PropNameID>(vm::HermesValue::encodeSymbolValue(id)); try { ho_->set(rt_, sym, rt_.valueFromHermesValue(value)); } catch (const jsi::JSError &error) { return rt_.runtime_.setThrownValue(hvFromValue(error.value())); } catch (const std::exception &ex) { return rt_.runtime_.setThrownValue(hvFromValue( rt_.global() .getPropertyAsFunction(rt_, "Error") .call( rt_, "Exception in HostObject::set for prop '" + rt_.runtime_.getIdentifierTable().convertSymbolToUTF8( id) + "': " + ex.what()))); } catch (...) { return rt_.runtime_.setThrownValue(hvFromValue( rt_.global() .getPropertyAsFunction(rt_, "Error") .call( rt_, "Exception in HostObject::set: for prop '" + rt_.runtime_.getIdentifierTable().convertSymbolToUTF8( id) + "': <unknown exception>"))); } return true; } vm::CallResult<vm::Handle<vm::JSArray>> getHostPropertyNames() override { STATS_TIMER(rt_, "HostObject.getHostPropertyNames", hostFunction); try { auto names = ho_->getPropertyNames(rt_); auto arrayRes = vm::JSArray::create(rt_.runtime_, names.size(), names.size()); if (arrayRes == vm::ExecutionStatus::EXCEPTION) { return vm::ExecutionStatus::EXCEPTION; } vm::Handle<vm::JSArray> arrayHandle = *arrayRes; vm::GCScope gcScope{rt_.runtime_}; vm::MutableHandle<vm::SymbolID> tmpHandle{rt_.runtime_}; size_t i = 0; for (auto &name : names) { tmpHandle = phv(name).getSymbol(); vm::JSArray::setElementAt(arrayHandle, rt_.runtime_, i++, tmpHandle); } return arrayHandle; } catch (const jsi::JSError &error) { return rt_.runtime_.setThrownValue(hvFromValue(error.value())); } catch (const std::exception &ex) { return rt_.runtime_.setThrownValue(hvFromValue( rt_.global() .getPropertyAsFunction(rt_, "Error") .call( rt_, std::string("Exception in HostObject::getPropertyNames: ") + ex.what()))); } catch (...) { return rt_.runtime_.setThrownValue(hvFromValue( rt_.global() .getPropertyAsFunction(rt_, "Error") .call( rt_, "Exception in HostObject::getPropertyNames: <unknown>"))); } }; }; struct HFContext final { HFContext(jsi::HostFunctionType hf, HermesRuntimeImpl &hri) : hostFunction(std::move(hf)), hermesRuntimeImpl(hri) {} static vm::CallResult<vm::HermesValue> func(void *context, vm::Runtime &runtime, vm::NativeArgs hvArgs) { HFContext *hfc = reinterpret_cast<HFContext *>(context); HermesRuntimeImpl &rt = hfc->hermesRuntimeImpl; assert(&runtime == &rt.runtime_); STATS_TIMER(rt, "Host Function", hostFunction); llvh::SmallVector<jsi::Value, 8> apiArgs; for (vm::HermesValue hv : hvArgs) { apiArgs.push_back(rt.valueFromHermesValue(hv)); } jsi::Value ret; const jsi::Value *args = apiArgs.empty() ? nullptr : &apiArgs.front(); try { ret = (hfc->hostFunction)( rt, rt.valueFromHermesValue(hvArgs.getThisArg()), args, apiArgs.size()); } catch (const jsi::JSError &error) { return runtime.setThrownValue(hvFromValue(error.value())); } catch (const std::exception &ex) { return rt.runtime_.setThrownValue(hvFromValue( rt.global() .getPropertyAsFunction(rt, "Error") .call( rt, std::string("Exception in HostFunction: ") + ex.what()))); } catch (...) { return rt.runtime_.setThrownValue( hvFromValue(rt.global() .getPropertyAsFunction(rt, "Error") .call(rt, "Exception in HostFunction: <unknown>"))); } return hvFromValue(ret); } static void finalize(void *context) { delete reinterpret_cast<HFContext *>(context); } jsi::HostFunctionType hostFunction; HermesRuntimeImpl &hermesRuntimeImpl; }; template <typename T> struct ManagedValues { #ifdef ASSERT_ON_DANGLING_VM_REFS // If we have active HermesValuePointers when deconstructing, these will // now be dangling. We deliberately allocate and immediately leak heap // memory to hold the internal list. This keeps alive memory holding the // ref-count of the now dangling references, allowing them to detect the // dangling case safely and assert when they are eventually released. By // deferring the assert it's a bit easier to see what's holding the pointers // for too long. ~ManagedValues() { bool anyDangling = false; for (auto it = values.begin(); it != values.end();) { if (it->get() == 0) { it = values.erase(it); } else { anyDangling = true; it->markDangling(); ++it; } } if (anyDangling) { // This is the deliberate memory leak described above. new std::list<T>(std::move(values)); } } #endif std::list<T> *operator->() { return &values; } const std::list<T> *operator->() const { return &values; } std::list<T> values; }; protected: /// Helper function that is parameterized over the type of context being /// created. template <typename ContextType> jsi::Function createFunctionFromHostFunction( ContextType *context, const jsi::PropNameID &name, unsigned int paramCount); public: ManagedValues<HermesPointerValue> hermesValues_; ManagedValues<WeakRefPointerValue> weakHermesValues_; std::shared_ptr<::hermes::vm::Runtime> rt_; ::hermes::vm::Runtime &runtime_; #ifdef HERMES_ENABLE_DEBUGGER friend class debugger::Debugger; std::unique_ptr<debugger::Debugger> debugger_; #endif ::hermes::vm::experiments::VMExperimentFlags vmExperimentFlags_{0}; /// Compilation flags used by prepareJavaScript(). ::hermes::hbc::CompileFlags compileFlags_{}; /// The default setting of "emit async break check" in this runtime. bool defaultEmitAsyncBreakCheck_{false}; }; namespace { inline HermesRuntimeImpl *impl(HermesRuntime *rt) { // This is guaranteed safe because HermesRuntime is abstract so // cannot be constructed, and the only instances created are // HermesRuntimeImpl's created by the factory function. It's kind // of like pimpl, but different. return static_cast<HermesRuntimeImpl *>(rt); } inline const HermesRuntimeImpl *impl(const HermesRuntime *rt) { // See above comment return static_cast<const HermesRuntimeImpl *>(rt); } } // namespace bool HermesRuntime::isHermesBytecode(const uint8_t *data, size_t len) { return hbc::BCProviderFromBuffer::isBytecodeStream( llvh::ArrayRef<uint8_t>(data, len)); } uint32_t HermesRuntime::getBytecodeVersion() { return hbc::BYTECODE_VERSION; } void HermesRuntime::prefetchHermesBytecode(const uint8_t *data, size_t len) { hbc::BCProviderFromBuffer::prefetch(llvh::ArrayRef<uint8_t>(data, len)); } bool HermesRuntime::hermesBytecodeSanityCheck( const uint8_t *data, size_t len, std::string *errorMessage) { return hbc::BCProviderFromBuffer::bytecodeStreamSanityCheck( llvh::ArrayRef<uint8_t>(data, len), errorMessage); } std::pair<const uint8_t *, size_t> HermesRuntime::getBytecodeEpilogue( const uint8_t *data, size_t len) { auto epi = hbc::BCProviderFromBuffer::getEpilogueFromBytecode( llvh::ArrayRef<uint8_t>(data, len)); return std::make_pair(epi.data(), epi.size()); } void HermesRuntime::enableSamplingProfiler() { ::hermes::vm::SamplingProfiler::enable(); } void HermesRuntime::disableSamplingProfiler() { ::hermes::vm::SamplingProfiler::disable(); } void HermesRuntime::dumpSampledTraceToFile(const std::string &fileName) { std::error_code ec; llvh::raw_fd_ostream os(fileName.c_str(), ec, llvh::sys::fs::F_Text); if (ec) { throw std::system_error(ec); } ::hermes::vm::SamplingProfiler::dumpChromeTraceGlobal(os); } void HermesRuntime::dumpSampledTraceToStream(std::ostream &stream) { llvh::raw_os_ostream os(stream); ::hermes::vm::SamplingProfiler::dumpChromeTraceGlobal(os); } void HermesRuntime::sampledTraceToStreamInDevToolsFormat(std::ostream &stream) { llvh::raw_os_ostream os(stream); impl(this)->runtime_.samplingProfiler->serializeInDevToolsFormat(os); } /*static*/ std::unordered_map<std::string, std::vector<std::string>> HermesRuntime::getExecutedFunctions() { std::unordered_map< std::string, std::vector<::hermes::vm::CodeCoverageProfiler::FuncInfo>> executedFunctionsByVM = ::hermes::vm::CodeCoverageProfiler::getExecutedFunctions(); std::unordered_map<std::string, std::vector<std::string>> result; for (auto const &x : executedFunctionsByVM) { std::vector<std::string> res; std::transform( x.second.begin(), x.second.end(), std::back_inserter(res), [](const ::hermes::vm::CodeCoverageProfiler::FuncInfo &entry) { std::stringstream ss; ss << entry.moduleId; ss << ":"; ss << entry.funcVirtualOffset; ss << ":"; ss << entry.debugInfo; return ss.str(); }); result.emplace(x.first, res); } return result; } /*static*/ bool HermesRuntime::isCodeCoverageProfilerEnabled() { return ::hermes::vm::CodeCoverageProfiler::globallyEnabled(); } /*static*/ void HermesRuntime::enableCodeCoverageProfiler() { ::hermes::vm::CodeCoverageProfiler::enableGlobal(); } /*static*/ void HermesRuntime::disableCodeCoverageProfiler() { ::hermes::vm::CodeCoverageProfiler::disableGlobal(); } void HermesRuntime::setFatalHandler(void (*handler)(const std::string &)) { detail::sApiFatalHandler = handler; } namespace { // A class which adapts a jsi buffer to a Hermes buffer. class BufferAdapter final : public ::hermes::Buffer { public: BufferAdapter(std::shared_ptr<const jsi::Buffer> buf) : buf_(std::move(buf)) { data_ = buf_->data(); size_ = buf_->size(); } private: std::shared_ptr<const jsi::Buffer> buf_; }; } // namespace void HermesRuntime::loadSegment( std::unique_ptr<const jsi::Buffer> buffer, const jsi::Value &context) { auto ret = hbc::BCProviderFromBuffer::createBCProviderFromBuffer( std::make_unique<BufferAdapter>(std::move(buffer))); if (!ret.first) { LOG_EXCEPTION_CAUSE("Error evaluating javascript: %s", ret.second.c_str()); throw jsi::JSINativeException("Error evaluating javascript: " + ret.second); } auto requireContext = vm::Handle<vm::RequireContext>::dyn_vmcast( impl(this)->vmHandleFromValue(context)); if (!requireContext) { LOG_EXCEPTION_CAUSE("Error loading segment: Invalid context"); throw jsi::JSINativeException("Error loading segment: Invalid context"); } vm::RuntimeModuleFlags flags; flags.persistent = true; impl(this)->checkStatus(impl(this)->runtime_.loadSegment( std::move(ret.first), requireContext, flags)); } uint64_t HermesRuntime::getUniqueID(const jsi::Object &o) const { return impl(this)->runtime_.getHeap().getObjectID( static_cast<vm::GCCell *>(impl(this)->phv(o).getObject())); } uint64_t HermesRuntime::getUniqueID(const jsi::String &s) const { return impl(this)->runtime_.getHeap().getObjectID( static_cast<vm::GCCell *>(impl(this)->phv(s).getString())); } // TODO(T111638575): PropNameID and Symbol can have the same unique ID. We // should either add a way to distinguish them, or explicitly state that the // unique ID may not be used to distinguish a PropNameID from a Value. uint64_t HermesRuntime::getUniqueID(const jsi::PropNameID &pni) const { return impl(this)->runtime_.getHeap().getObjectID( impl(this)->phv(pni).getSymbol()); } uint64_t HermesRuntime::getUniqueID(const jsi::Symbol &sym) const { return impl(this)->runtime_.getHeap().getObjectID( impl(this)->phv(sym).getSymbol()); } uint64_t HermesRuntime::getUniqueID(const jsi::Value &val) const { vm::HermesValue hv = HermesRuntimeImpl::hvFromValue(val); // 0 is reserved as a non-ID. return impl(this)->runtime_.getHeap().getSnapshotID(hv).getValueOr(0); } jsi::Value HermesRuntime::getObjectForID(uint64_t id) { vm::GCCell *ptr = static_cast<vm::GCCell *>( impl(this)->runtime_.getHeap().getObjectForID(id)); if (ptr && vm::vmisa<vm::JSObject>(ptr)) { return impl(this)->add<jsi::Object>( vm::HermesValue::encodeObjectValue(ptr)); } // If the ID doesn't map to a pointer, or that pointer isn't an object, // return null. // This is because a jsi::Object can't be used to represent something internal // to the VM like a HiddenClass. return jsi::Value::null(); } /// Get a structure representing the enviroment-dependent behavior, so /// it can be written into the trace for later replay. const ::hermes::vm::MockedEnvironment &HermesRuntime::getMockedEnvironment() const { return static_cast<const HermesRuntimeImpl *>(this) ->runtime_.getCommonStorage() ->tracedEnv; } void HermesRuntime::setMockedEnvironment( const ::hermes::vm::MockedEnvironment &env) { static_cast<HermesRuntimeImpl *>(this)->runtime_.setMockedEnvironment(env); } const ::hermes::vm::GCExecTrace &HermesRuntime::getGCExecTrace() const { return static_cast<const HermesRuntimeImpl *>(this) ->runtime_.getGCExecTrace(); } std::string HermesRuntime::getIOTrackingInfoJSON() { std::string buf; llvh::raw_string_ostream strstrm(buf); static_cast<HermesRuntimeImpl *>(this)->runtime_.getIOTrackingInfoJSON( strstrm); strstrm.flush(); return buf; } #ifdef HERMESVM_PROFILER_BB void HermesRuntime::dumpBasicBlockProfileTrace(std::ostream &stream) const { llvh::raw_os_ostream os(stream); static_cast<const HermesRuntimeImpl *>(this) ->runtime_.dumpBasicBlockProfileTrace(os); } #endif #ifdef HERMESVM_PROFILER_OPCODE void HermesRuntime::dumpOpcodeStats(std::ostream &stream) const { llvh::raw_os_ostream os(stream); static_cast<const HermesRuntimeImpl *>(this)->runtime_.dumpOpcodeStats(os); } #endif #ifdef HERMES_ENABLE_DEBUGGER debugger::Debugger &HermesRuntime::getDebugger() { return *(impl(this)->debugger_); } void HermesRuntime::debugJavaScript( const std::string &src, const std::string &sourceURL, const DebugFlags &debugFlags) { vm::Runtime &runtime = impl(this)->runtime_; vm::GCScope gcScope(runtime); vm::ExecutionStatus res = runtime.run(src, sourceURL, impl(this)->compileFlags_).getStatus(); impl(this)->checkStatus(res); } #endif void HermesRuntime::registerForProfiling() { vm::Runtime &runtime = impl(this)->runtime_; if (runtime.samplingProfiler) { ::hermes::hermes_fatal( "re-registering HermesVMs for profiling is not allowed"); } runtime.samplingProfiler = std::make_unique<::hermes::vm::SamplingProfiler>(runtime); } void HermesRuntime::unregisterForProfiling() { if (!impl(this)->runtime_.samplingProfiler) { ::hermes::hermes_fatal( "unregistering HermesVM not registered for profiling is not allowed"); } impl(this)->runtime_.samplingProfiler.reset(); } void HermesRuntime::watchTimeLimit(uint32_t timeoutInMs) { impl(this)->compileFlags_.emitAsyncBreakCheck = true; ::hermes::vm::TimeLimitMonitor::getInstance().watchRuntime( impl(this)->runtime_, timeoutInMs); } void HermesRuntime::unwatchTimeLimit() { // Restore the default state. impl(this)->compileFlags_.emitAsyncBreakCheck = impl(this)->defaultEmitAsyncBreakCheck_; ::hermes::vm::TimeLimitMonitor::getInstance().unwatchRuntime( impl(this)->runtime_); } jsi::Value HermesRuntime::evaluateJavaScriptWithSourceMap( const std::shared_ptr<const jsi::Buffer> &buffer, const std::shared_ptr<const jsi::Buffer> &sourceMapBuf, const std::string &sourceURL) { return impl(this)->evaluatePreparedJavaScript( impl(this)->prepareJavaScriptWithSourceMap( buffer, sourceMapBuf, sourceURL)); } size_t HermesRuntime::rootsListLength() const { return impl(this)->hermesValues_->size(); } namespace { /// An implementation of PreparedJavaScript that wraps a BytecodeProvider. class HermesPreparedJavaScript final : public jsi::PreparedJavaScript { std::shared_ptr<hbc::BCProvider> bcProvider_; vm::RuntimeModuleFlags runtimeFlags_; std::string sourceURL_; public: explicit HermesPreparedJavaScript( std::unique_ptr<hbc::BCProvider> bcProvider, vm::RuntimeModuleFlags runtimeFlags, std::string sourceURL) : bcProvider_(std::move(bcProvider)), runtimeFlags_(runtimeFlags), sourceURL_(std::move(sourceURL)) {} std::shared_ptr<hbc::BCProvider> bytecodeProvider() const { return bcProvider_; } vm::RuntimeModuleFlags runtimeFlags() const { return runtimeFlags_; } const std::string &sourceURL() const { return sourceURL_; } }; } // namespace std::shared_ptr<const jsi::PreparedJavaScript> HermesRuntimeImpl::prepareJavaScriptWithSourceMap( const std::shared_ptr<const jsi::Buffer> &jsiBuffer, const std::shared_ptr<const jsi::Buffer> &sourceMapBuf, std::string sourceURL) { std::pair<std::unique_ptr<hbc::BCProvider>, std::string> bcErr{}; auto buffer = std::make_unique<BufferAdapter>(jsiBuffer); vm::RuntimeModuleFlags runtimeFlags{}; runtimeFlags.persistent = true; bool isBytecode = isHermesBytecode(buffer->data(), buffer->size()); #ifdef HERMESVM_PLATFORM_LOGGING hermesLog( "HermesVM", "Prepare JS on %s.", isBytecode ? "bytecode" : "source"); #endif // Save the first few bytes of the buffer so that we can later append them // to any error message. uint8_t bufPrefix[16]; const size_t bufSize = buffer->size(); memcpy(bufPrefix, buffer->data(), std::min(sizeof(bufPrefix), bufSize)); // Construct the BC provider either from buffer or source. if (isBytecode) { if (sourceMapBuf) { throw std::logic_error("Source map cannot be specified with bytecode"); } bcErr = hbc::BCProviderFromBuffer::createBCProviderFromBuffer( std::move(buffer)); } else { #if defined(HERMESVM_LEAN) bcErr.second = "prepareJavaScript source compilation not supported"; #else std::unique_ptr<::hermes::SourceMap> sourceMap{}; if (sourceMapBuf) { // Convert the buffer into a form the parser needs. llvh::MemoryBufferRef mbref( llvh::StringRef( (const char *)sourceMapBuf->data(), sourceMapBuf->size()), ""); ::hermes::SimpleDiagHandler diag; ::hermes::SourceErrorManager sm; diag.installInto(sm); sourceMap = ::hermes::SourceMapParser::parse(mbref, sm); if (!sourceMap) { auto errorStr = diag.getErrorString(); LOG_EXCEPTION_CAUSE("Error parsing source map: %s", errorStr.c_str()); throw std::runtime_error("Error parsing source map:" + errorStr); } } bcErr = hbc::BCProviderFromSrc::createBCProviderFromSrc( std::move(buffer), sourceURL, std::move(sourceMap), compileFlags_); #endif } if (!bcErr.first) { std::string storage; llvh::raw_string_ostream os(storage); os << " Buffer size " << bufSize << " starts with: "; for (size_t i = 0; i < sizeof(bufPrefix) && i < bufSize; ++i) os << llvh::format_hex_no_prefix(bufPrefix[i], 2); std::string bufferModes = ""; for (const auto &mode : ::hermes::oscompat::get_vm_protect_modes( jsiBuffer->data(), jsiBuffer->size())) { // We only expect one match, but if there are multiple, we want to know. bufferModes += mode; } if (!bufferModes.empty()) { os << " and has protection mode(s): " << bufferModes; } LOG_EXCEPTION_CAUSE( "Compiling JS failed: %s, %s", bcErr.second.c_str(), os.str().c_str()); throw jsi::JSINativeException( "Compiling JS failed: " + std::move(bcErr.second) + os.str()); } return std::make_shared<const HermesPreparedJavaScript>( std::move(bcErr.first), runtimeFlags, std::move(sourceURL)); } std::shared_ptr<const jsi::PreparedJavaScript> HermesRuntimeImpl::prepareJavaScript( const std::shared_ptr<const jsi::Buffer> &jsiBuffer, std::string sourceURL) { return prepareJavaScriptWithSourceMap(jsiBuffer, nullptr, sourceURL); } jsi::Value HermesRuntimeImpl::evaluatePreparedJavaScript( const std::shared_ptr<const jsi::PreparedJavaScript> &js) { return maybeRethrow([&] { assert( dynamic_cast<const HermesPreparedJavaScript *>(js.get()) && "js must be an instance of HermesPreparedJavaScript"); STATS_TIMER(*this, "Evaluate JS", evaluateJS); const auto *hermesPrep = static_cast<const HermesPreparedJavaScript *>(js.get()); vm::GCScope gcScope(runtime_); auto res = runtime_.runBytecode( hermesPrep->bytecodeProvider(), hermesPrep->runtimeFlags(), hermesPrep->sourceURL(), vm::Runtime::makeNullHandle<vm::Environment>()); checkStatus(res.getStatus()); return valueFromHermesValue(*res); }); } jsi::Value HermesRuntimeImpl::evaluateJavaScript( const std::shared_ptr<const jsi::Buffer> &buffer, const std::string &sourceURL) { return evaluateJavaScriptWithSourceMap(buffer, nullptr, sourceURL); } bool HermesRuntimeImpl::drainMicrotasks(int maxMicrotasksHint) { if (runtime_.useJobQueue()) { checkStatus(runtime_.drainJobs()); } // \c drainJobs is currently an unbounded execution, hence no exceptions // implies drained until TODO(T89426441): \c maxMicrotasksHint is supported return true; } jsi::Object HermesRuntimeImpl::global() { return add<jsi::Object>(runtime_.getGlobal().getHermesValue()); } std::string HermesRuntimeImpl::description() { std::string gcName = runtime_.getHeap().getName(); if (gcName.empty()) { return "HermesRuntime"; } else { return "HermesRuntime[" + gcName + "]"; } } bool HermesRuntimeImpl::isInspectable() { #ifdef HERMES_ENABLE_DEBUGGER return true; #else return false; #endif } jsi::Instrumentation &HermesRuntimeImpl::instrumentation() { return *this; } jsi::Runtime::PointerValue *HermesRuntimeImpl::cloneSymbol( const Runtime::PointerValue *pv) { return clone(pv); } jsi::Runtime::PointerValue *HermesRuntimeImpl::cloneString( const Runtime::PointerValue *pv) { return clone(pv); } jsi::Runtime::PointerValue *HermesRuntimeImpl::cloneObject( const Runtime::PointerValue *pv) { return clone(pv); } jsi::Runtime::PointerValue *HermesRuntimeImpl::clonePropNameID( const Runtime::PointerValue *pv) { return clone(pv); } jsi::PropNameID HermesRuntimeImpl::createPropNameIDFromAscii( const char *str, size_t length) { return maybeRethrow([&] { #ifndef NDEBUG for (size_t i = 0; i < length; ++i) { assert( static_cast<unsigned char>(str[i]) < 128 && "non-ASCII character in property name"); } #endif vm::GCScope gcScope(runtime_); auto cr = vm::stringToSymbolID( runtime_, vm::StringPrimitive::createNoThrow( runtime_, llvh::StringRef(str, length))); checkStatus(cr.getStatus()); return add<jsi::PropNameID>(cr->getHermesValue()); }); } jsi::PropNameID HermesRuntimeImpl::createPropNameIDFromUtf8( const uint8_t *utf8, size_t length) { return maybeRethrow([&] { vm::GCScope gcScope(runtime_); auto cr = vm::stringToSymbolID( runtime_, vm::createPseudoHandle(stringHVFromUtf8(utf8, length).getString())); checkStatus(cr.getStatus()); return add<jsi::PropNameID>(cr->getHermesValue()); }); } jsi::PropNameID HermesRuntimeImpl::createPropNameIDFromString( const jsi::String &str) { return maybeRethrow([&] { vm::GCScope gcScope(runtime_); auto cr = vm::stringToSymbolID( runtime_, vm::createPseudoHandle(phv(str).getString())); checkStatus(cr.getStatus()); return add<jsi::PropNameID>(cr->getHermesValue()); }); } jsi::PropNameID HermesRuntimeImpl::createPropNameIDFromSymbol( const jsi::Symbol &sym) { return add<jsi::PropNameID>(phv(sym)); } std::string HermesRuntimeImpl::utf8(const jsi::PropNameID &sym) { vm::GCScope gcScope(runtime_); vm::SymbolID id = phv(sym).getSymbol(); auto view = runtime_.getIdentifierTable().getStringView(runtime_, id); vm::SmallU16String<32> allocator; std::string ret; ::hermes::convertUTF16ToUTF8WithReplacements( ret, view.getUTF16Ref(allocator)); return ret; } bool HermesRuntimeImpl::compare( const jsi::PropNameID &a, const jsi::PropNameID &b) { return phv(a).getSymbol() == phv(b).getSymbol(); } namespace { std::string toStdString( vm::Runtime &runtime, vm::Handle<vm::StringPrimitive> handle) { auto view = vm::StringPrimitive::createStringView(runtime, handle); vm::SmallU16String<32> allocator; std::string ret; ::hermes::convertUTF16ToUTF8WithReplacements( ret, view.getUTF16Ref(allocator)); return ret; } } // namespace std::string HermesRuntimeImpl::symbolToString(const jsi::Symbol &sym) { vm::GCScope gcScope(runtime_); auto res = symbolDescriptiveString( runtime_, ::hermes::vm::Handle<::hermes::vm::SymbolID>::vmcast(&phv(sym))); checkStatus(res.getStatus()); return toStdString(runtime_, res.getValue()); } jsi::String HermesRuntimeImpl::createStringFromAscii( const char *str, size_t length) { return maybeRethrow([&] { #ifndef NDEBUG for (size_t i = 0; i < length; ++i) { assert( static_cast<unsigned char>(str[i]) < 128 && "non-ASCII character in string"); } #endif vm::GCScope gcScope(runtime_); return add<jsi::String>(stringHVFromAscii(str, length)); }); } jsi::String HermesRuntimeImpl::createStringFromUtf8( const uint8_t *utf8, size_t length) { return maybeRethrow([&] { vm::GCScope gcScope(runtime_); return add<jsi::String>(stringHVFromUtf8(utf8, length)); }); } std::string HermesRuntimeImpl::utf8(const jsi::String &str) { vm::GCScope gcScope(runtime_); return maybeRethrow([&] { vm::Handle<vm::StringPrimitive> handle( runtime_, stringHandle(str)->getString()); return toStdString(runtime_, handle); }); } jsi::Value HermesRuntimeImpl::createValueFromJsonUtf8( const uint8_t *json, size_t length) { return maybeRethrow([&] { vm::GCScope gcScope(runtime_); llvh::ArrayRef<uint8_t> ref(json, length); vm::CallResult<vm::HermesValue> res = runtimeJSONParseRef(runtime_, ::hermes::UTF16Stream(ref)); checkStatus(res.getStatus()); return valueFromHermesValue(*res); }); } jsi::Object HermesRuntimeImpl::createObject() { vm::GCScope gcScope(runtime_); return maybeRethrow([&] { return add<jsi::Object>(vm::JSObject::create(runtime_).getHermesValue()); }); } jsi::Object HermesRuntimeImpl::createObject( std::shared_ptr<jsi::HostObject> ho) { return maybeRethrow([&] { vm::GCScope gcScope(runtime_); auto objRes = vm::HostObject::createWithoutPrototype( runtime_, std::make_unique<JsiProxy>(*this, ho)); checkStatus(objRes.getStatus()); return add<jsi::Object>(*objRes); }); } std::shared_ptr<jsi::HostObject> HermesRuntimeImpl::getHostObject( const jsi::Object &obj) { const vm::HostObjectProxy *proxy = vm::vmcast<vm::HostObject>(phv(obj))->getProxy(); return static_cast<const JsiProxy *>(proxy)->ho_; } jsi::Value HermesRuntimeImpl::getProperty( const jsi::Object &obj, const jsi::String &name) { return maybeRethrow([&] { vm::GCScope gcScope(runtime_); auto h = handle(obj); auto res = h->getComputed_RJS(h, runtime_, stringHandle(name)); checkStatus(res.getStatus()); return valueFromHermesValue(res->get()); }); } jsi::Value HermesRuntimeImpl::getProperty( const jsi::Object &obj, const jsi::PropNameID &name) { return maybeRethrow([&] { vm::GCScope gcScope(runtime_); auto h = handle(obj); vm::SymbolID nameID = phv(name).getSymbol(); auto res = h->getNamedOrIndexed(h, runtime_, nameID); checkStatus(res.getStatus()); return valueFromHermesValue(res->get()); }); } bool HermesRuntimeImpl::hasProperty( const jsi::Object &obj, const jsi::String &name) { vm::GCScope gcScope(runtime_); auto h = handle(obj); auto result = h->hasComputed(h, runtime_, stringHandle(name)); checkStatus(result.getStatus()); return result.getValue(); } bool HermesRuntimeImpl::hasProperty( const jsi::Object &obj, const jsi::PropNameID &name) { vm::GCScope gcScope(runtime_); auto h = handle(obj); vm::SymbolID nameID = phv(name).getSymbol(); auto result = h->hasNamedOrIndexed(h, runtime_, nameID); checkStatus(result.getStatus()); return result.getValue(); } void HermesRuntimeImpl::setPropertyValue( jsi::Object &obj, const jsi::String &name, const jsi::Value &value) { return maybeRethrow([&] { vm::GCScope gcScope(runtime_); auto h = handle(obj); checkStatus(h->putComputed_RJS( h, runtime_, stringHandle(name), vmHandleFromValue(value), vm::PropOpFlags().plusThrowOnError()) .getStatus()); }); } void HermesRuntimeImpl::setPropertyValue( jsi::Object &obj, const jsi::PropNameID &name, const jsi::Value &value) { return maybeRethrow([&] { vm::GCScope gcScope(runtime_); auto h = handle(obj); vm::SymbolID nameID = phv(name).getSymbol(); checkStatus(h->putNamedOrIndexed( h, runtime_, nameID, vmHandleFromValue(value), vm::PropOpFlags().plusThrowOnError()) .getStatus()); }); } bool HermesRuntimeImpl::isArray(const jsi::Object &obj) const { return vm::vmisa<vm::JSArray>(phv(obj)); } bool HermesRuntimeImpl::isArrayBuffer(const jsi::Object &obj) const { return vm::vmisa<vm::JSArrayBuffer>(phv(obj)); } bool HermesRuntimeImpl::isFunction(const jsi::Object &obj) const { return vm::vmisa<vm::Callable>(phv(obj)); } bool HermesRuntimeImpl::isHostObject(const jsi::Object &obj) const { return vm::vmisa<vm::HostObject>(phv(obj)); } bool HermesRuntimeImpl::isHostFunction(const jsi::Function &func) const { return vm::vmisa<vm::FinalizableNativeFunction>(phv(func)); } jsi::Array HermesRuntimeImpl::getPropertyNames(const jsi::Object &obj) { return maybeRethrow([&] { vm::GCScope gcScope(runtime_); uint32_t beginIndex; uint32_t endIndex; vm::CallResult<vm::Handle<vm::SegmentedArray>> cr = vm::getForInPropertyNames(runtime_, handle(obj), beginIndex, endIndex); checkStatus(cr.getStatus()); vm::Handle<vm::SegmentedArray> arr = *cr; size_t length = endIndex - beginIndex; auto ret = createArray(length); for (size_t i = 0; i < length; ++i) { vm::HermesValue name = arr->at(beginIndex + i); if (name.isString()) { ret.setValueAtIndex(*this, i, valueFromHermesValue(name)); } else if (name.isNumber()) { std::string s; llvh::raw_string_ostream os(s); os << static_cast<size_t>(name.getNumber()); ret.setValueAtIndex( *this, i, jsi::String::createFromAscii(*this, os.str())); } else { llvm_unreachable("property name is not String or Number"); } } return ret; }); } jsi::WeakObject HermesRuntimeImpl::createWeakObject(const jsi::Object &obj) { return maybeRethrow([&] { return addWeak(vm::WeakRoot<vm::JSObject>( static_cast<vm::JSObject *>(phv(obj).getObject()), runtime_)); }); } jsi::Value HermesRuntimeImpl::lockWeakObject(jsi::WeakObject &wo) { vm::WeakRoot<vm::JSObject> &wr = weakRoot(wo); if (const auto ptr = wr.get(runtime_, &runtime_.getHeap())) return add<jsi::Object>(vm::HermesValue::encodeObjectValue(ptr)); return jsi::Value(); } jsi::Array HermesRuntimeImpl::createArray(size_t length) { return maybeRethrow([&] { vm::GCScope gcScope(runtime_); auto result = vm::JSArray::create(runtime_, length, length); checkStatus(result.getStatus()); return add<jsi::Object>(result->getHermesValue()).getArray(*this); }); } size_t HermesRuntimeImpl::size(const jsi::Array &arr) { vm::GCScope gcScope(runtime_); return getLength(arrayHandle(arr)); } size_t HermesRuntimeImpl::size(const jsi::ArrayBuffer &arr) { vm::GCScope gcScope(runtime_); return getByteLength(arrayBufferHandle(arr)); } uint8_t *HermesRuntimeImpl::data(const jsi::ArrayBuffer &arr) { return vm::vmcast<vm::JSArrayBuffer>(phv(arr))->getDataBlock(); } jsi::Value HermesRuntimeImpl::getValueAtIndex(const jsi::Array &arr, size_t i) { return maybeRethrow([&] { vm::GCScope gcScope(runtime_); if (LLVM_UNLIKELY(i >= size(arr))) { throw makeJSError( *this, "getValueAtIndex: index ", i, " is out of bounds [0, ", size(arr), ")"); } auto res = vm::JSObject::getComputed_RJS( arrayHandle(arr), runtime_, runtime_.makeHandle(vm::HermesValue::encodeNumberValue(i))); checkStatus(res.getStatus()); return valueFromHermesValue(res->get()); }); } void HermesRuntimeImpl::setValueAtIndexImpl( jsi::Array &arr, size_t i, const jsi::Value &value) { return maybeRethrow([&] { vm::GCScope gcScope(runtime_); if (LLVM_UNLIKELY(i >= size(arr))) { throw makeJSError( *this, "setValueAtIndex: index ", i, " is out of bounds [0, ", size(arr), ")"); } auto h = arrayHandle(arr); h->setElementAt(h, runtime_, i, vmHandleFromValue(value)); }); } jsi::Function HermesRuntimeImpl::createFunctionFromHostFunction( const jsi::PropNameID &name, unsigned int paramCount, jsi::HostFunctionType func) { return maybeRethrow([&] { auto context = std::make_unique<HFContext>(std::move(func), *this); auto hostfunc = createFunctionFromHostFunction(context.get(), name, paramCount); context.release(); return hostfunc; }); } template <typename ContextType> jsi::Function HermesRuntimeImpl::createFunctionFromHostFunction( ContextType *context, const jsi::PropNameID &name, unsigned int paramCount) { return maybeRethrow([&] { vm::GCScope gcScope(runtime_); vm::SymbolID nameID = phv(name).getSymbol(); auto funcRes = vm::FinalizableNativeFunction::createWithoutPrototype( runtime_, context, &ContextType::func, &ContextType::finalize, nameID, paramCount); checkStatus(funcRes.getStatus()); jsi::Function ret = add<jsi::Object>(*funcRes).getFunction(*this); return ret; }); } jsi::HostFunctionType &HermesRuntimeImpl::getHostFunction( const jsi::Function &func) { return static_cast<HFContext *>( vm::vmcast<vm::FinalizableNativeFunction>(phv(func))->getContext()) ->hostFunction; } jsi::Value HermesRuntimeImpl::call( const jsi::Function &func, const jsi::Value &jsThis, const jsi::Value *args, size_t count) { return maybeRethrow([&] { vm::GCScope gcScope(runtime_); vm::Handle<vm::Callable> handle = vm::Handle<vm::Callable>::vmcast(&phv(func)); if (count > std::numeric_limits<uint32_t>::max() || !runtime_.checkAvailableStack((uint32_t)count)) { LOG_EXCEPTION_CAUSE( "HermesRuntimeImpl::call: Unable to call function: stack overflow"); throw jsi::JSINativeException( "HermesRuntimeImpl::call: Unable to call function: stack overflow"); } STATS_TIMER(*this, "Incoming Function", incomingFunction); vm::ScopedNativeCallFrame newFrame{ runtime_, static_cast<uint32_t>(count), handle.getHermesValue(), vm::HermesValue::encodeUndefinedValue(), hvFromValue(jsThis)}; if (LLVM_UNLIKELY(newFrame.overflowed())) { checkStatus(runtime_.raiseStackOverflow( ::hermes::vm::Runtime::StackOverflowKind::NativeStack)); } for (uint32_t i = 0; i != count; ++i) { newFrame->getArgRef(i) = hvFromValue(args[i]); } auto callRes = vm::Callable::call(handle, runtime_); checkStatus(callRes.getStatus()); return valueFromHermesValue(callRes->get()); }); } jsi::Value HermesRuntimeImpl::callAsConstructor( const jsi::Function &func, const jsi::Value *args, size_t count) { return maybeRethrow([&] { vm::GCScope gcScope(runtime_); vm::Handle<vm::Callable> funcHandle = vm::Handle<vm::Callable>::vmcast(&phv(func)); if (count > std::numeric_limits<uint32_t>::max() || !runtime_.checkAvailableStack((uint32_t)count)) { LOG_EXCEPTION_CAUSE( "HermesRuntimeImpl::call: Unable to call function: stack overflow"); throw jsi::JSINativeException( "HermesRuntimeImpl::call: Unable to call function: stack overflow"); } STATS_TIMER( *this, "Incoming Function: Call As Constructor", incomingFunction); // We follow es5 13.2.2 [[Construct]] here. Below F == func. // 13.2.2.5: // Let proto be the value of calling the [[Get]] internal property of // F with argument "prototype" // 13.2.2.6: // If Type(proto) is Object, set the [[Prototype]] internal property // of obj to proto // 13.2.2.7: // If Type(proto) is not Object, set the [[Prototype]] internal property // of obj to the standard built-in Object prototype object as described // in 15.2.4 // // Note that 13.2.2.1-4 are also handled by the call to newObject. auto thisRes = vm::Callable::createThisForConstruct(funcHandle, runtime_); // We need to capture this in case the ctor doesn't return an object, // we need to return this object. auto objHandle = runtime_.makeHandle<vm::JSObject>(std::move(*thisRes)); // 13.2.2.8: // Let result be the result of calling the [[Call]] internal property of // F, providing obj as the this value and providing the argument list // passed into [[Construct]] as args. // // For us result == res. vm::ScopedNativeCallFrame newFrame{ runtime_, static_cast<uint32_t>(count), funcHandle.getHermesValue(), funcHandle.getHermesValue(), objHandle.getHermesValue()}; if (newFrame.overflowed()) { checkStatus(runtime_.raiseStackOverflow( ::hermes::vm::Runtime::StackOverflowKind::NativeStack)); } for (uint32_t i = 0; i != count; ++i) { newFrame->getArgRef(i) = hvFromValue(args[i]); } // The last parameter indicates that this call should construct an object. auto callRes = vm::Callable::call(funcHandle, runtime_); checkStatus(callRes.getStatus()); // 13.2.2.9: // If Type(result) is Object then return result // 13.2.2.10: // Return obj auto resultValue = callRes->get(); vm::HermesValue resultHValue = resultValue.isObject() ? resultValue : objHandle.getHermesValue(); return valueFromHermesValue(resultHValue); }); } bool HermesRuntimeImpl::strictEquals(const jsi::Symbol &a, const jsi::Symbol &b) const { return phv(a).getSymbol() == phv(b).getSymbol(); } bool HermesRuntimeImpl::strictEquals(const jsi::String &a, const jsi::String &b) const { return phv(a).getString()->equals(phv(b).getString()); } bool HermesRuntimeImpl::strictEquals(const jsi::Object &a, const jsi::Object &b) const { return phv(a).getRaw() == phv(b).getRaw(); } bool HermesRuntimeImpl::instanceOf( const jsi::Object &o, const jsi::Function &f) { return maybeRethrow([&] { vm::GCScope gcScope(runtime_); auto result = vm::instanceOfOperator_RJS( runtime_, runtime_.makeHandle(phv(o)), runtime_.makeHandle(phv(f))); checkStatus(result.getStatus()); return *result; }); } jsi::Runtime::ScopeState *HermesRuntimeImpl::pushScope() { hermesValues_->emplace_front( vm::HermesValue::encodeNativeUInt32(kSentinelNativeValue)); return reinterpret_cast<ScopeState *>(&hermesValues_->front()); } void HermesRuntimeImpl::popScope(ScopeState *prv) { HermesPointerValue *sentinel = reinterpret_cast<HermesPointerValue *>(prv); assert(sentinel->phv.isNativeValue()); assert(sentinel->phv.getNativeUInt32() == kSentinelNativeValue); for (auto it = hermesValues_->begin(); it != hermesValues_->end();) { auto &value = *it; if (&value == sentinel) { hermesValues_->erase(it); return; } if (value.phv.isNativeValue()) { // We reached another sentinel value or we started added another native // value to the hermesValue_ list. This should not happen. std::terminate(); } if (value.get() == 0) { it = hermesValues_->erase(it); } else { ++it; } } // We did not find a sentinel value. std::terminate(); } void HermesRuntimeImpl::checkStatus(vm::ExecutionStatus status) { if (LLVM_LIKELY(status != vm::ExecutionStatus::EXCEPTION)) { return; } jsi::Value exception = valueFromHermesValue(runtime_.getThrownValue()); runtime_.clearThrownValue(); // Here, we increment the depth to detect recursion in error handling. vm::ScopedNativeDepthTracker depthTracker{runtime_}; if (LLVM_LIKELY(!depthTracker.overflowed())) { auto ex = jsi::JSError(*this, std::move(exception)); LOG_EXCEPTION_CAUSE("JSI rethrowing JS exception: %s", ex.what()); throw ex; } (void)runtime_.raiseStackOverflow( vm::Runtime::StackOverflowKind::NativeStack); exception = valueFromHermesValue(runtime_.getThrownValue()); runtime_.clearThrownValue(); // Here, we give us a little more room so we can call into JS to // populate the JSError members. vm::ScopedNativeDepthReducer reducer(runtime_); throw jsi::JSError(*this, std::move(exception)); } vm::HermesValue HermesRuntimeImpl::stringHVFromAscii( const char *str, size_t length) { auto strRes = vm::StringPrimitive::createEfficient( runtime_, llvh::makeArrayRef(str, length)); checkStatus(strRes.getStatus()); return *strRes; } vm::HermesValue HermesRuntimeImpl::stringHVFromUtf8( const uint8_t *utf8, size_t length) { const bool IgnoreInputErrors = true; auto strRes = vm::StringPrimitive::createEfficient( runtime_, llvh::makeArrayRef(utf8, length), IgnoreInputErrors); checkStatus(strRes.getStatus()); return *strRes; } size_t HermesRuntimeImpl::getLength(vm::Handle<vm::ArrayImpl> arr) { return maybeRethrow([&] { auto res = vm::JSObject::getNamed_RJS( arr, runtime_, vm::Predefined::getSymbolID(vm::Predefined::length)); checkStatus(res.getStatus()); if (!(*res)->isNumber()) { throw jsi::JSError(*this, "getLength: property 'length' is not a number"); } return static_cast<size_t>((*res)->getDouble()); }); } size_t HermesRuntimeImpl::getByteLength(vm::Handle<vm::JSArrayBuffer> arr) { return maybeRethrow([&] { auto res = vm::JSObject::getNamed_RJS( arr, runtime_, vm::Predefined::getSymbolID(vm::Predefined::byteLength)); checkStatus(res.getStatus()); if (!(*res)->isNumber()) { throw jsi::JSError( *this, "getLength: property 'byteLength' is not a number"); } return static_cast<size_t>((*res)->getDouble()); }); } namespace { class HermesMutex : public std::recursive_mutex { public: // ThreadSafeRuntimeImpl expects that the lock ctor takes a // reference to the Runtime. Otherwise, this is a // std::recursive_mutex. HermesMutex(HermesRuntimeImpl &) {} }; } // namespace std::unique_ptr<HermesRuntime> makeHermesRuntime( const vm::RuntimeConfig &runtimeConfig) { // This is insurance against someone adding data members to // HermesRuntime. If on some weird platform it fails, it can be // updated or removed. static_assert( sizeof(HermesRuntime) == sizeof(void *), "HermesRuntime should only include a vtable ptr"); #if defined(HERMESVM_PLATFORM_LOGGING) auto ret = std::make_unique<HermesRuntimeImpl>( runtimeConfig.rebuild() .withGCConfig(runtimeConfig.getGCConfig() .rebuild() .withShouldRecordStats(true) .build()) .build()); #else auto ret = std::make_unique<HermesRuntimeImpl>(runtimeConfig); #endif #ifdef HERMES_ENABLE_DEBUGGER // Only HermesRuntime can create a debugger instance. This requires // the setter and not using make_unique, so the call to new is here // in this function, which is a friend of debugger::Debugger. ret->setDebugger(std::unique_ptr<debugger::Debugger>( new debugger::Debugger(ret.get(), &(ret->runtime_.getDebugger())))); #endif return ret; } std::unique_ptr<jsi::ThreadSafeRuntime> makeThreadSafeHermesRuntime( const vm::RuntimeConfig &runtimeConfig) { #if defined(HERMESVM_PLATFORM_LOGGING) const vm::RuntimeConfig &actualRuntimeConfig = runtimeConfig.rebuild() .withGCConfig(runtimeConfig.getGCConfig() .rebuild() .withShouldRecordStats(true) .build()) .build(); #else const vm::RuntimeConfig &actualRuntimeConfig = runtimeConfig; #endif auto ret = std::make_unique< jsi::detail::ThreadSafeRuntimeImpl<HermesRuntimeImpl, HermesMutex>>( actualRuntimeConfig); #ifdef HERMES_ENABLE_DEBUGGER auto &hermesRt = ret->getUnsafeRuntime(); // Only HermesRuntime can create a debugger instance. This requires // the setter and not using make_unique, so the call to new is here // in this function, which is a friend of debugger::Debugger. hermesRt.setDebugger(std::unique_ptr<debugger::Debugger>( new debugger::Debugger(&hermesRt, &(hermesRt.runtime_.getDebugger())))); #endif return ret; } #ifdef HERMES_ENABLE_DEBUGGER /// Glue code enabling the Debugger to produce a jsi::Value from a HermesValue. jsi::Value debugger::Debugger::jsiValueFromHermesValue(vm::HermesValue hv) { return impl(runtime_)->valueFromHermesValue(hv); } #endif } // namespace hermes } // namespace facebook