amis-rpc-design/node_modules/react-native/ReactCommon/hermes/inspector/InspectorState.h
2023-10-07 19:42:30 +08:00

413 lines
12 KiB
C++

/*
* 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 <condition_variable>
#include <iostream>
#include <memory>
#include <mutex>
#include <queue>
#include <utility>
#include <folly/Unit.h>
#include <hermes/inspector/Exceptions.h>
#include <hermes/inspector/Inspector.h>
namespace facebook {
namespace hermes {
namespace inspector {
using NextStatePtr = std::unique_ptr<InspectorState>;
using CommandPtr = std::unique_ptr<facebook::hermes::debugger::Command>;
using MonitorLock = std::unique_lock<std::mutex>;
/**
* 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<folly::Promise<folly::Unit>> 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<NextStatePtr, CommandPtr> didPause(MonitorLock &lock) = 0;
/**
* enable handles the enable event from the client.
*/
virtual std::pair<NextStatePtr, bool> enable() {
return std::make_pair<NextStatePtr, bool>(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<folly::Promise<facebook::hermes::debugger::EvalResult>>
promise,
folly::Function<void(const facebook::hermes::debugger::EvalResult &)>
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<folly::Promise<folly::Unit>> 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<InspectorState> make(Inspector &inspector) {
return std::make_unique<RunningDetached>(inspector);
}
RunningDetached(Inspector &inspector) : InspectorState(inspector) {}
~RunningDetached() override {}
std::pair<NextStatePtr, CommandPtr> didPause(MonitorLock &lock) override;
std::pair<NextStatePtr, bool> 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<InspectorState> make(Inspector &inspector) {
return std::make_unique<RunningWaitEnable>(inspector);
}
RunningWaitEnable(Inspector &inspector) : InspectorState(inspector) {}
~RunningWaitEnable() override {}
std::pair<NextStatePtr, CommandPtr> didPause(MonitorLock &lock) override;
std::pair<NextStatePtr, bool> 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<InspectorState> make(Inspector &inspector) {
return std::make_unique<RunningWaitPause>(inspector);
}
std::pair<NextStatePtr, CommandPtr> 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<InspectorState> make(Inspector &inspector) {
return std::make_unique<PausedWaitEnable>(inspector);
}
PausedWaitEnable(Inspector &inspector) : InspectorState(inspector) {}
~PausedWaitEnable() override {}
std::pair<NextStatePtr, CommandPtr> didPause(MonitorLock &lock) override;
std::pair<NextStatePtr, bool> 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<folly::Promise<facebook::hermes::debugger::EvalResult>>
promise;
folly::Function<void(const facebook::hermes::debugger::EvalResult &)>
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<InspectorState> make(Inspector &inspector) {
return std::make_unique<Running>(inspector);
}
Running(Inspector &inspector) : InspectorState(inspector) {}
~Running() override {}
void onEnter(InspectorState *prevState) override;
void detach(std::shared_ptr<folly::Promise<folly::Unit>> promise) override;
std::pair<NextStatePtr, CommandPtr> didPause(MonitorLock &lock) override;
bool pushPendingFunc(folly::Func func) override;
void pushPendingEval(
uint32_t frameIndex,
const std::string &src,
std::shared_ptr<folly::Promise<facebook::hermes::debugger::EvalResult>>
promise,
folly::Function<void(const facebook::hermes::debugger::EvalResult &)>
resultTransformer) override;
bool pause() override;
bool isRunning() const override {
return true;
}
const char *description() const override {
return "Running";
}
private:
std::vector<folly::Func> pendingFuncs_;
std::queue<PendingEval> pendingEvals_;
std::shared_ptr<folly::Promise<facebook::hermes::debugger::EvalResult>>
pendingEvalPromise_;
folly::Function<void(const facebook::hermes::debugger::EvalResult &)>
pendingEvalResultTransformer_;
std::shared_ptr<folly::Promise<folly::Unit>> 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<folly::Promise<folly::Unit>> promise)
: command(std::move(command)), promise(promise) {}
debugger::Command command;
std::shared_ptr<folly::Promise<folly::Unit>> 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<InspectorState> make(Inspector &inspector) {
return std::make_unique<Paused>(inspector);
}
Paused(Inspector &inspector) : InspectorState(inspector) {}
~Paused() override {}
void onEnter(InspectorState *prevState) override;
void detach(std::shared_ptr<folly::Promise<folly::Unit>> promise) override;
std::pair<NextStatePtr, CommandPtr> didPause(MonitorLock &lock) override;
bool pushPendingFunc(folly::Func func) override;
void pushPendingEval(
uint32_t frameIndex,
const std::string &src,
std::shared_ptr<folly::Promise<facebook::hermes::debugger::EvalResult>>
promise,
folly::Function<void(const facebook::hermes::debugger::EvalResult &)>
resultTransformer) override;
void setPendingCommand(
debugger::Command command,
std::shared_ptr<folly::Promise<folly::Unit>> promise) override;
bool isPaused() const override {
return true;
}
const char *description() const override {
return "Paused";
}
private:
std::condition_variable hasPendingWork_;
std::vector<folly::Func> pendingFuncs_;
std::queue<PendingEval> pendingEvals_;
std::shared_ptr<folly::Promise<facebook::hermes::debugger::EvalResult>>
pendingEvalPromise_;
folly::Function<void(const facebook::hermes::debugger::EvalResult &)>
pendingEvalResultTransformer_;
std::unique_ptr<PendingCommand> pendingCommand_;
std::shared_ptr<folly::Promise<folly::Unit>> pendingDetach_;
};
} // namespace inspector
} // namespace hermes
} // namespace facebook
#endif // HERMES_INSPECTOR_INSPECTOR_STATE_H