/* * 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. */ // using include guards instead of #pragma once due to compile issues // with MSVC and BUCK #ifndef HERMES_INSPECTOR_INSPECTOR_STATE_H #define HERMES_INSPECTOR_INSPECTOR_STATE_H #include #include #include #include #include #include #include #include #include namespace facebook { namespace hermes { namespace inspector { using NextStatePtr = std::unique_ptr; using CommandPtr = std::unique_ptr; using MonitorLock = std::unique_lock; /** * InspectorState encapsulates a single state in the Inspector FSM. Events in * the FSM are modeled as methods in InspectorState. * * Some events may cause state transitions. The next state is returned via a * pointer to the next InspectorState. * * We assume that the Inspector's mutex is held across all calls to * InspectorState methods. For more threading notes, see the Inspector * implementation. */ class InspectorState { public: InspectorState(Inspector &inspector) : inspector_(inspector) {} virtual ~InspectorState() = default; /** * onEnter is called when entering the state. prevState may be null when * transitioning into an initial state. */ virtual void onEnter(InspectorState *prevState) {} /* * Events that may cause a state transition. */ /** * detach clears all debugger state and transitions to RunningDetached. */ virtual void detach(std::shared_ptr> promise) { // As we're not attached we'd like for the operation to be idempotent promise->setValue(); } /** * didPause handles the didPause callback from the debugger. It takes the lock * associated with the Inspector's mutex by reference in case we need to * temporarily relinquish the lock (e.g. via condition_variable::wait). */ virtual std::pair didPause(MonitorLock &lock) = 0; /** * enable handles the enable event from the client. */ virtual std::pair enable() { return std::make_pair(nullptr, false); } /* * Events that don't cause a state transition. */ /** * pushPendingFunc appends a function to run the next time the debugger * pauses, either explicitly while paused or implicitly while running. * Returns false if it's not possible to push a func in this state. */ virtual bool pushPendingFunc(folly::Func func) { return false; } /** * pushPendingEval appends an eval request to run the next time the debugger * pauses, either explicitly while paused or implicitly while running. * resultTransformer function will be called with EvalResult before returning * result so that we can manipulate EvalResult while the VM is paused. */ virtual void pushPendingEval( uint32_t frameIndex, const std::string &src, std::shared_ptr> promise, folly::Function resultTransformer) { promise->setException( InvalidStateException("eval", description(), "paused or running")); } /** * setPendingCommand sets a command to break the debugger out of the didPause * run loop. If it's not possible to set a pending command in this state, the * promise fails with InvalidStateException. Otherwise, the promise resolves * to true when the command actually executes. */ virtual void setPendingCommand( debugger::Command command, std::shared_ptr> promise) { promise->setException( InvalidStateException("cmd", description(), "paused")); } /** * pause requests an async pause from the VM. */ virtual bool pause() { return false; } /* * Convenience functions for determining the concrete type and description * for a state instance without RTTI. */ virtual bool isRunningDetached() const { return false; } virtual bool isRunningWaitEnable() const { return false; } virtual bool isRunningWaitPause() const { return false; } virtual bool isPausedWaitEnable() const { return false; } virtual bool isRunning() const { return false; } virtual bool isPaused() const { return false; } virtual const char *description() const = 0; friend std::ostream &operator<<( std::ostream &os, const InspectorState &state); class RunningDetached; class RunningWaitEnable; class RunningWaitPause; class PausedWaitEnable; class Running; class Paused; protected: debugger::PauseReason getPauseReason() { return inspector_.debugger_.getProgramState().getPauseReason(); } private: Inspector &inspector_; }; extern std::ostream &operator<<(std::ostream &os, const InspectorState &state); /** * RunningDetached is the initial state when we're associated with a VM that * initially has no breakpoints. */ class InspectorState::RunningDetached : public InspectorState { public: static std::unique_ptr make(Inspector &inspector) { return std::make_unique(inspector); } RunningDetached(Inspector &inspector) : InspectorState(inspector) {} ~RunningDetached() override {} std::pair didPause(MonitorLock &lock) override; std::pair enable() override; void onEnter(InspectorState *prevState) override; bool isRunningDetached() const override { return true; } const char *description() const override { return "RunningDetached"; } }; /** * RunningWaitEnable is the initial state when we're associated with a VM that * has a breakpoint on the first statement. */ class InspectorState::RunningWaitEnable : public InspectorState { public: static std::unique_ptr make(Inspector &inspector) { return std::make_unique(inspector); } RunningWaitEnable(Inspector &inspector) : InspectorState(inspector) {} ~RunningWaitEnable() override {} std::pair didPause(MonitorLock &lock) override; std::pair enable() override; bool isRunningWaitEnable() const override { return true; } const char *description() const override { return "RunningWaitEnable"; } }; /** * RunningWaitPause is the state when we've received enable call, but * waiting for didPause because we need to pause on the first statement. */ class InspectorState::RunningWaitPause : public InspectorState { public: static std::unique_ptr make(Inspector &inspector) { return std::make_unique(inspector); } std::pair didPause(MonitorLock &lock) override; RunningWaitPause(Inspector &inspector) : InspectorState(inspector) {} ~RunningWaitPause() {} bool isRunningWaitPause() const override { return true; } const char *description() const override { return "RunningWaitPause"; } }; /** * PausedWaitEnable is the state when we're in a didPause callback and we're * waiting for the client to call enable. */ class InspectorState::PausedWaitEnable : public InspectorState { public: static std::unique_ptr make(Inspector &inspector) { return std::make_unique(inspector); } PausedWaitEnable(Inspector &inspector) : InspectorState(inspector) {} ~PausedWaitEnable() override {} std::pair didPause(MonitorLock &lock) override; std::pair enable() override; bool isPausedWaitEnable() const override { return true; } const char *description() const override { return "PausedWaitEnable"; } private: bool enabled_ = false; std::condition_variable enabledCondition_; }; /** * PendingEval holds an eval command and a promise that is fulfilled with the * eval result. */ struct PendingEval { debugger::Command command; std::shared_ptr> promise; folly::Function resultTransformer; }; /** * Running is the state when we're enabled and not currently paused, e.g. when * we're actively executing JS. * * Note that we can be in the running state even if we're not actively running * JS. For instance, React Native could be blocked in a native message queue * waiting for the next message to process outside of the call in to Hermes. * That still counts as Running in this FSM. */ class InspectorState::Running : public InspectorState { public: static std::unique_ptr make(Inspector &inspector) { return std::make_unique(inspector); } Running(Inspector &inspector) : InspectorState(inspector) {} ~Running() override {} void onEnter(InspectorState *prevState) override; void detach(std::shared_ptr> promise) override; std::pair didPause(MonitorLock &lock) override; bool pushPendingFunc(folly::Func func) override; void pushPendingEval( uint32_t frameIndex, const std::string &src, std::shared_ptr> promise, folly::Function resultTransformer) override; bool pause() override; bool isRunning() const override { return true; } const char *description() const override { return "Running"; } private: std::vector pendingFuncs_; std::queue pendingEvals_; std::shared_ptr> pendingEvalPromise_; folly::Function pendingEvalResultTransformer_; std::shared_ptr> pendingDetach_; }; /** * PendingCommand holds a resume or step command and a promise that is fulfilled * just before the debugger resumes or steps. */ struct PendingCommand { PendingCommand( debugger::Command command, std::shared_ptr> promise) : command(std::move(command)), promise(promise) {} debugger::Command command; std::shared_ptr> promise; }; /** * Paused is the state when we're enabled and and currently in a didPause * callback. */ class InspectorState::Paused : public InspectorState { public: static std::unique_ptr make(Inspector &inspector) { return std::make_unique(inspector); } Paused(Inspector &inspector) : InspectorState(inspector) {} ~Paused() override {} void onEnter(InspectorState *prevState) override; void detach(std::shared_ptr> promise) override; std::pair didPause(MonitorLock &lock) override; bool pushPendingFunc(folly::Func func) override; void pushPendingEval( uint32_t frameIndex, const std::string &src, std::shared_ptr> promise, folly::Function resultTransformer) override; void setPendingCommand( debugger::Command command, std::shared_ptr> promise) override; bool isPaused() const override { return true; } const char *description() const override { return "Paused"; } private: std::condition_variable hasPendingWork_; std::vector pendingFuncs_; std::queue pendingEvals_; std::shared_ptr> pendingEvalPromise_; folly::Function pendingEvalResultTransformer_; std::unique_ptr pendingCommand_; std::shared_ptr> pendingDetach_; }; } // namespace inspector } // namespace hermes } // namespace facebook #endif // HERMES_INSPECTOR_INSPECTOR_STATE_H