mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Summary: Calls to Inspector::evaluate() and Inspector::executeIfEnabled() take a user-provided callback which are not try-guarded. This means that, should the user code throw, the inspector's process will likely die due to the unhandled exception. This change makes sure that, if the user provided callback throws, then the promise attached to those methods will be fulfilled by an exception. Change: [internal] Reviewed By: avp Differential Revision: D33852073 fbshipit-source-id: 7fbb6662b28d393a5d5b494c004aa9521e23ebb6
745 lines
24 KiB
C++
745 lines
24 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.
|
|
*/
|
|
|
|
#include "Inspector.h"
|
|
#include "Exceptions.h"
|
|
#include "InspectorState.h"
|
|
|
|
#include <functional>
|
|
#include <string>
|
|
|
|
#include <glog/logging.h>
|
|
#include <hermes/inspector/detail/SerialExecutor.h>
|
|
#include <hermes/inspector/detail/Thread.h>
|
|
|
|
#ifdef HERMES_INSPECTOR_FOLLY_KLUDGE
|
|
// <kludge> This is here, instead of linking against
|
|
// folly/futures/Future.cpp, to avoid pulling in another pile of
|
|
// dependencies, including the separate dependency libevent. This is
|
|
// likely specific to the version of folly RN uses, so may need to be
|
|
// changed. Even better, perhaps folly can be refactored to simplify
|
|
// this. Providing a RN-specific Timekeeper impl may also help.
|
|
|
|
template class folly::Future<folly::Unit>;
|
|
template class folly::Future<bool>;
|
|
|
|
namespace folly {
|
|
namespace futures {
|
|
|
|
SemiFuture<Unit> sleep(Duration, Timekeeper *) {
|
|
LOG(FATAL) << "folly::futures::sleep() not implemented";
|
|
}
|
|
|
|
} // namespace futures
|
|
|
|
namespace detail {
|
|
|
|
std::shared_ptr<Timekeeper> getTimekeeperSingleton() {
|
|
LOG(FATAL) << "folly::detail::getTimekeeperSingleton() not implemented";
|
|
}
|
|
|
|
} // namespace detail
|
|
} // namespace folly
|
|
|
|
// </kludge>
|
|
#endif
|
|
|
|
namespace facebook {
|
|
namespace hermes {
|
|
namespace inspector {
|
|
|
|
using folly::Unit;
|
|
|
|
namespace debugger = ::facebook::hermes::debugger;
|
|
|
|
/**
|
|
* Threading notes:
|
|
*
|
|
* 1. mutex_ must be held before using state_ or any InspectorState methods.
|
|
* 2. Methods that are callable by the client (like enable, resume, etc.) call
|
|
* various InspectorState methods via state_. This implies that they must
|
|
* acquire mutex_.
|
|
* 3. Since some InspectorState methods call back out to the client (e.g. via
|
|
* fulfilling promises, or via the InspectorObserver callbacks), we have to
|
|
* be careful about reentrancy from a callback causing a deadlock when (1)
|
|
* and (2) interact. Consider:
|
|
*
|
|
* 1) Debugger pauses, which causes InspectorObserve::onPause to fire.
|
|
* onPause is called by InspectorState::Paused::onEnter on the JS
|
|
* thread with mutex_ held.
|
|
* 2) Client calls setBreakpoint from the onPause callback.
|
|
* 3) If setBreakpoint directly tried to acquire mutex_ here, we would
|
|
* deadlock since our thread already owns the mutex_ (see 1).
|
|
*
|
|
* For this reason, all client-facing methods are executed on executor_, which
|
|
* runs on its own thread. The pattern is:
|
|
*
|
|
* 1. The client-facing method foo (e.g. enable) enqueues a call to
|
|
* fooOnExecutor (e.g. enableOnExecutor) on executor_.
|
|
* 2. fooOnExecutor is responsible for acquiring mutex_.
|
|
*
|
|
*/
|
|
|
|
// TODO: read this out of an env variable or config
|
|
static constexpr bool kShouldLog = false;
|
|
|
|
// Logging state transitions is done outside of transition() in a macro so that
|
|
// function and line numbers in the log will be accurate.
|
|
#define TRANSITION(nextState) \
|
|
do { \
|
|
if (kShouldLog) { \
|
|
if (state_ == nullptr) { \
|
|
LOG(INFO) << "Inspector::" << __func__ \
|
|
<< " transitioning to initial state " << *(nextState); \
|
|
} else { \
|
|
LOG(INFO) << "Inspector::" << __func__ << " transitioning from " \
|
|
<< *state_ << " to " << *(nextState); \
|
|
} \
|
|
} \
|
|
transition((nextState)); \
|
|
} while (0)
|
|
|
|
Inspector::Inspector(
|
|
std::shared_ptr<RuntimeAdapter> adapter,
|
|
InspectorObserver &observer,
|
|
bool pauseOnFirstStatement)
|
|
: adapter_(adapter),
|
|
debugger_(adapter->getDebugger()),
|
|
observer_(observer),
|
|
executor_(std::make_unique<detail::SerialExecutor>("hermes-inspector")) {
|
|
// TODO (t26491391): make tickleJs a real Hermes runtime API
|
|
std::string src = "function __tickleJs() { return Math.random(); }";
|
|
adapter->getRuntime().evaluateJavaScript(
|
|
std::make_shared<jsi::StringBuffer>(src), "__tickleJsHackUrl");
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
|
|
if (pauseOnFirstStatement) {
|
|
awaitingDebuggerOnStart_ = true;
|
|
TRANSITION(std::make_unique<InspectorState::RunningWaitEnable>(*this));
|
|
} else {
|
|
TRANSITION(std::make_unique<InspectorState::RunningDetached>(*this));
|
|
}
|
|
}
|
|
|
|
debugger_.setShouldPauseOnScriptLoad(true);
|
|
debugger_.setEventObserver(this);
|
|
}
|
|
|
|
Inspector::~Inspector() {
|
|
debugger_.setEventObserver(nullptr);
|
|
}
|
|
|
|
static bool toBoolean(jsi::Runtime &runtime, const jsi::Value &val) {
|
|
// Based on Operations.cpp:toBoolean in the Hermes VM.
|
|
if (val.isUndefined() || val.isNull()) {
|
|
return false;
|
|
}
|
|
if (val.isBool()) {
|
|
return val.getBool();
|
|
}
|
|
if (val.isNumber()) {
|
|
double m = val.getNumber();
|
|
return m != 0 && !std::isnan(m);
|
|
}
|
|
if (val.isSymbol() || val.isObject()) {
|
|
return true;
|
|
}
|
|
if (val.isString()) {
|
|
std::string s = val.getString(runtime).utf8(runtime);
|
|
return !s.empty();
|
|
}
|
|
assert(false && "All cases should be covered");
|
|
return false;
|
|
}
|
|
|
|
void Inspector::installConsoleFunction(
|
|
jsi::Object &console,
|
|
std::shared_ptr<jsi::Object> &originalConsole,
|
|
const std::string &name,
|
|
const std::string &chromeTypeDefault = "") {
|
|
jsi::Runtime &rt = adapter_->getRuntime();
|
|
auto chromeType = chromeTypeDefault == "" ? name : chromeTypeDefault;
|
|
auto nameID = jsi::PropNameID::forUtf8(rt, name);
|
|
auto weakInspector = std::weak_ptr<Inspector>(shared_from_this());
|
|
console.setProperty(
|
|
rt,
|
|
nameID,
|
|
jsi::Function::createFromHostFunction(
|
|
rt,
|
|
nameID,
|
|
1,
|
|
[weakInspector, originalConsole, name, chromeType](
|
|
jsi::Runtime &runtime,
|
|
const jsi::Value &thisVal,
|
|
const jsi::Value *args,
|
|
size_t count) {
|
|
if (originalConsole) {
|
|
auto val = originalConsole->getProperty(runtime, name.c_str());
|
|
if (val.isObject()) {
|
|
auto obj = val.getObject(runtime);
|
|
if (obj.isFunction(runtime)) {
|
|
auto func = obj.getFunction(runtime);
|
|
func.callWithThis(runtime, *originalConsole, args, count);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (auto inspector = weakInspector.lock()) {
|
|
if (name != "assert") {
|
|
// All cases other than assert just log a simple message.
|
|
jsi::Array argsArray(runtime, count);
|
|
for (size_t index = 0; index < count; ++index)
|
|
argsArray.setValueAtIndex(runtime, index, args[index]);
|
|
inspector->logMessage(
|
|
ConsoleMessageInfo{chromeType, std::move(argsArray)});
|
|
return jsi::Value::undefined();
|
|
}
|
|
// console.assert needs to check the first parameter before
|
|
// logging.
|
|
if (count == 0) {
|
|
// No parameters, throw a blank assertion failed message.
|
|
inspector->logMessage(
|
|
ConsoleMessageInfo{chromeType, jsi::Array(runtime, 0)});
|
|
} else if (!toBoolean(runtime, args[0])) {
|
|
// Shift the message array down by one to not include the
|
|
// condition.
|
|
jsi::Array argsArray(runtime, count - 1);
|
|
for (size_t index = 1; index < count; ++index)
|
|
argsArray.setValueAtIndex(runtime, index, args[index]);
|
|
inspector->logMessage(
|
|
ConsoleMessageInfo{chromeType, std::move(argsArray)});
|
|
}
|
|
}
|
|
|
|
return jsi::Value::undefined();
|
|
}));
|
|
}
|
|
|
|
void Inspector::installLogHandler() {
|
|
jsi::Runtime &rt = adapter_->getRuntime();
|
|
auto console = jsi::Object(rt);
|
|
auto val = rt.global().getProperty(rt, "console");
|
|
std::shared_ptr<jsi::Object> originalConsole;
|
|
if (val.isObject()) {
|
|
originalConsole = std::make_shared<jsi::Object>(val.getObject(rt));
|
|
}
|
|
installConsoleFunction(console, originalConsole, "assert");
|
|
installConsoleFunction(console, originalConsole, "clear");
|
|
installConsoleFunction(console, originalConsole, "debug");
|
|
installConsoleFunction(console, originalConsole, "dir");
|
|
installConsoleFunction(console, originalConsole, "dirxml");
|
|
installConsoleFunction(console, originalConsole, "error");
|
|
installConsoleFunction(console, originalConsole, "group", "startGroup");
|
|
installConsoleFunction(
|
|
console, originalConsole, "groupCollapsed", "startGroupCollapsed");
|
|
installConsoleFunction(console, originalConsole, "groupEnd", "endGroup");
|
|
installConsoleFunction(console, originalConsole, "info");
|
|
installConsoleFunction(console, originalConsole, "log");
|
|
installConsoleFunction(console, originalConsole, "profile");
|
|
installConsoleFunction(console, originalConsole, "profileEnd");
|
|
installConsoleFunction(console, originalConsole, "table");
|
|
installConsoleFunction(console, originalConsole, "trace");
|
|
installConsoleFunction(console, originalConsole, "warn", "warning");
|
|
rt.global().setProperty(rt, "console", console);
|
|
}
|
|
|
|
void Inspector::triggerAsyncPause(bool andTickle) {
|
|
// In order to ensure that we pause soon, we both set the async pause flag on
|
|
// the runtime, and we run a bit of dummy JS to ensure we enter the Hermes
|
|
// interpreter loop.
|
|
debugger_.triggerAsyncPause(
|
|
pendingPauseState_ == AsyncPauseState::Implicit
|
|
? debugger::AsyncPauseKind::Implicit
|
|
: debugger::AsyncPauseKind::Explicit);
|
|
|
|
if (andTickle) {
|
|
// We run the dummy JS on a background thread to avoid any reentrancy issues
|
|
// in case this thread is called with the inspector mutex held.
|
|
std::shared_ptr<RuntimeAdapter> adapter = adapter_;
|
|
detail::Thread tickleJsLater(
|
|
"inspectorTickleJs", [adapter]() { adapter->tickleJs(); });
|
|
tickleJsLater.detach();
|
|
}
|
|
}
|
|
|
|
void Inspector::notifyContextCreated() {
|
|
observer_.onContextCreated(*this);
|
|
}
|
|
|
|
ScriptInfo Inspector::getScriptInfoFromTopCallFrame() {
|
|
ScriptInfo info{};
|
|
auto stackTrace = debugger_.getProgramState().getStackTrace();
|
|
|
|
if (stackTrace.callFrameCount() > 0) {
|
|
debugger::SourceLocation loc = stackTrace.callFrameForIndex(0).location;
|
|
|
|
info.fileId = loc.fileId;
|
|
info.fileName = loc.fileName;
|
|
info.sourceMappingUrl = debugger_.getSourceMappingUrl(info.fileId);
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
void Inspector::addCurrentScriptToLoadedScripts() {
|
|
ScriptInfo info = getScriptInfoFromTopCallFrame();
|
|
|
|
if (!loadedScripts_.count(info.fileId)) {
|
|
loadedScriptIdByName_[info.fileName] = info.fileId;
|
|
loadedScripts_[info.fileId] = LoadedScriptInfo{std::move(info), false};
|
|
}
|
|
}
|
|
|
|
void Inspector::removeAllBreakpoints() {
|
|
debugger_.deleteAllBreakpoints();
|
|
}
|
|
|
|
void Inspector::resetScriptsLoaded() {
|
|
for (auto &it : loadedScripts_) {
|
|
it.second.notifiedClient = false;
|
|
}
|
|
}
|
|
|
|
void Inspector::notifyScriptsLoaded() {
|
|
for (auto &it : loadedScripts_) {
|
|
LoadedScriptInfo &loadedScriptInfo = it.second;
|
|
|
|
if (!loadedScriptInfo.notifiedClient) {
|
|
loadedScriptInfo.notifiedClient = true;
|
|
observer_.onScriptParsed(*this, loadedScriptInfo.info);
|
|
}
|
|
}
|
|
}
|
|
|
|
folly::Future<Unit> Inspector::disable() {
|
|
auto promise = std::make_shared<folly::Promise<Unit>>();
|
|
|
|
executor_->add([this, promise] { disableOnExecutor(promise); });
|
|
|
|
return promise->getFuture();
|
|
}
|
|
|
|
folly::Future<Unit> Inspector::enable() {
|
|
auto promise = std::make_shared<folly::Promise<Unit>>();
|
|
|
|
executor_->add([this, promise] { enableOnExecutor(promise); });
|
|
|
|
return promise->getFuture();
|
|
}
|
|
|
|
folly::Future<Unit> Inspector::executeIfEnabled(
|
|
const std::string &description,
|
|
folly::Function<void(const debugger::ProgramState &)> func) {
|
|
auto promise = std::make_shared<folly::Promise<Unit>>();
|
|
|
|
executor_->add(
|
|
[this, description, func = std::move(func), promise]() mutable {
|
|
executeIfEnabledOnExecutor(description, std::move(func), promise);
|
|
});
|
|
|
|
return promise->getFuture();
|
|
}
|
|
|
|
folly::Future<debugger::BreakpointInfo> Inspector::setBreakpoint(
|
|
debugger::SourceLocation loc,
|
|
folly::Optional<std::string> condition) {
|
|
auto promise = std::make_shared<folly::Promise<debugger::BreakpointInfo>>();
|
|
// Automatically re-enable breakpoints since the user presumably wants this
|
|
// to start triggering.
|
|
breakpointsActive_ = true;
|
|
|
|
executor_->add([this, loc, condition, promise] {
|
|
setBreakpointOnExecutor(loc, condition, promise);
|
|
});
|
|
|
|
return promise->getFuture();
|
|
}
|
|
|
|
folly::Future<folly::Unit> Inspector::removeBreakpoint(
|
|
debugger::BreakpointID breakpointId) {
|
|
auto promise = std::make_shared<folly::Promise<folly::Unit>>();
|
|
|
|
executor_->add([this, breakpointId, promise] {
|
|
removeBreakpointOnExecutor(breakpointId, promise);
|
|
});
|
|
|
|
return promise->getFuture();
|
|
}
|
|
|
|
folly::Future<folly::Unit> Inspector::logMessage(ConsoleMessageInfo info) {
|
|
auto promise = std::make_shared<folly::Promise<folly::Unit>>();
|
|
|
|
executor_->add([this,
|
|
pInfo = std::make_unique<ConsoleMessageInfo>(std::move(info)),
|
|
promise] { logOnExecutor(std::move(*pInfo), promise); });
|
|
|
|
return promise->getFuture();
|
|
}
|
|
|
|
folly::Future<Unit> Inspector::setPendingCommand(debugger::Command command) {
|
|
auto promise = std::make_shared<folly::Promise<Unit>>();
|
|
|
|
executor_->add([this, promise, cmd = std::move(command)]() mutable {
|
|
setPendingCommandOnExecutor(std::move(cmd), promise);
|
|
});
|
|
|
|
return promise->getFuture();
|
|
}
|
|
|
|
folly::Future<Unit> Inspector::resume() {
|
|
return setPendingCommand(debugger::Command::continueExecution());
|
|
}
|
|
|
|
folly::Future<Unit> Inspector::stepIn() {
|
|
return setPendingCommand(debugger::Command::step(debugger::StepMode::Into));
|
|
}
|
|
|
|
folly::Future<Unit> Inspector::stepOver() {
|
|
return setPendingCommand(debugger::Command::step(debugger::StepMode::Over));
|
|
}
|
|
|
|
folly::Future<Unit> Inspector::stepOut() {
|
|
return setPendingCommand(debugger::Command::step(debugger::StepMode::Out));
|
|
}
|
|
|
|
folly::Future<Unit> Inspector::pause() {
|
|
auto promise = std::make_shared<folly::Promise<Unit>>();
|
|
|
|
executor_->add([this, promise]() { pauseOnExecutor(promise); });
|
|
|
|
return promise->getFuture();
|
|
}
|
|
|
|
folly::Future<debugger::EvalResult> Inspector::evaluate(
|
|
uint32_t frameIndex,
|
|
const std::string &src,
|
|
folly::Function<void(const facebook::hermes::debugger::EvalResult &)>
|
|
resultTransformer) {
|
|
auto promise = std::make_shared<folly::Promise<debugger::EvalResult>>();
|
|
|
|
executor_->add([this,
|
|
frameIndex,
|
|
src,
|
|
promise,
|
|
resultTransformer = std::move(resultTransformer)]() mutable {
|
|
evaluateOnExecutor(frameIndex, src, promise, std::move(resultTransformer));
|
|
});
|
|
|
|
return promise->getFuture();
|
|
}
|
|
|
|
folly::Future<folly::Unit> Inspector::setPauseOnExceptions(
|
|
const debugger::PauseOnThrowMode &mode) {
|
|
auto promise = std::make_shared<folly::Promise<Unit>>();
|
|
|
|
executor_->add([this, mode, promise]() mutable {
|
|
setPauseOnExceptionsOnExecutor(mode, promise);
|
|
});
|
|
|
|
return promise->getFuture();
|
|
};
|
|
|
|
folly::Future<folly::Unit> Inspector::setPauseOnLoads(
|
|
const PauseOnLoadMode mode) {
|
|
// This flag does not touch the runtime, so it doesn't need the executor.
|
|
// Return a future anyways for consistency.
|
|
auto promise = std::make_shared<folly::Promise<Unit>>();
|
|
pauseOnLoadMode_ = mode;
|
|
promise->setValue();
|
|
return promise->getFuture();
|
|
};
|
|
|
|
folly::Future<folly::Unit> Inspector::setBreakpointsActive(bool active) {
|
|
// Same logic as setPauseOnLoads.
|
|
auto promise = std::make_shared<folly::Promise<Unit>>();
|
|
breakpointsActive_ = active;
|
|
promise->setValue();
|
|
return promise->getFuture();
|
|
};
|
|
|
|
bool Inspector::shouldPauseOnThisScriptLoad() {
|
|
switch (pauseOnLoadMode_) {
|
|
case None:
|
|
return false;
|
|
case All:
|
|
return true;
|
|
case Smart:
|
|
// If we don't have active breakpoints, there's nothing to set or update.
|
|
if (debugger_.getBreakpoints().size() == 0) {
|
|
return false;
|
|
}
|
|
// If there's no source map URL, it's probably not a file we care about.
|
|
if (getScriptInfoFromTopCallFrame().sourceMappingUrl.size() == 0) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
debugger::Command Inspector::didPause(debugger::Debugger &debugger) {
|
|
std::unique_lock<std::mutex> lock(mutex_);
|
|
|
|
if (kShouldLog) {
|
|
LOG(INFO) << "received didPause for reason: "
|
|
<< static_cast<int>(debugger.getProgramState().getPauseReason())
|
|
<< " in state: " << *state_;
|
|
}
|
|
|
|
while (true) {
|
|
/*
|
|
* Keep sending the onPause event to the current state until we get a
|
|
* command to return. For instance, this handles the transition from
|
|
* Running to Paused to Running:
|
|
*
|
|
* 1) (R => P) We're currently in Running, so we call Running::didPause,
|
|
* which returns {nextState: Paused, command: null}. There isn't a
|
|
* command to return yet.
|
|
* 2) (P => R) Now we're in Paused, so we call Paused::didPause, which
|
|
* returns {nextState: Running, command: someCommand} where someCommand
|
|
* is non-null (e.g. continue or step over). This terminates the loop.
|
|
*/
|
|
auto result = state_->didPause(lock);
|
|
|
|
std::unique_ptr<InspectorState> nextState = std::move(result.first);
|
|
if (nextState) {
|
|
TRANSITION(std::move(nextState));
|
|
}
|
|
|
|
std::unique_ptr<debugger::Command> command = std::move(result.second);
|
|
if (command) {
|
|
return std::move(*command);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Inspector::breakpointResolved(
|
|
debugger::Debugger &debugger,
|
|
debugger::BreakpointID breakpointId) {
|
|
std::unique_lock<std::mutex> lock(mutex_);
|
|
|
|
debugger::BreakpointInfo info = debugger.getBreakpointInfo(breakpointId);
|
|
observer_.onBreakpointResolved(*this, info);
|
|
}
|
|
|
|
void Inspector::transition(std::unique_ptr<InspectorState> nextState) {
|
|
assert(nextState);
|
|
assert(state_ != nextState);
|
|
|
|
std::unique_ptr<InspectorState> prevState = std::move(state_);
|
|
state_ = std::move(nextState);
|
|
state_->onEnter(prevState.get());
|
|
}
|
|
|
|
void Inspector::disableOnExecutor(
|
|
std::shared_ptr<folly::Promise<Unit>> promise) {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
|
|
debugger_.setIsDebuggerAttached(false);
|
|
|
|
state_->detach(promise);
|
|
}
|
|
|
|
void Inspector::enableOnExecutor(
|
|
std::shared_ptr<folly::Promise<Unit>> promise) {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
|
|
auto result = state_->enable();
|
|
|
|
/**
|
|
* We fulfill the promise before changing state because fulfilling the promise
|
|
* responds to the Debugger.enable request, and changing state could send a
|
|
* notification (like Debugger.paused). It seems like a good idea to respond
|
|
* to enable before sending out any notifications.
|
|
*/
|
|
bool enabled = result.second;
|
|
if (enabled) {
|
|
debugger_.setIsDebuggerAttached(true);
|
|
promise->setValue();
|
|
} else {
|
|
promise->setException(AlreadyEnabledException());
|
|
}
|
|
|
|
std::unique_ptr<InspectorState> nextState = std::move(result.first);
|
|
if (nextState) {
|
|
TRANSITION(std::move(nextState));
|
|
}
|
|
}
|
|
|
|
void Inspector::executeIfEnabledOnExecutor(
|
|
const std::string &description,
|
|
folly::Function<void(const debugger::ProgramState &)> func,
|
|
std::shared_ptr<folly::Promise<Unit>> promise) {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
|
|
if (!state_->isPaused() && !state_->isRunning()) {
|
|
promise->setException(InvalidStateException(
|
|
description, state_->description(), "paused or running"));
|
|
return;
|
|
}
|
|
|
|
folly::Func wrappedFunc = [this, func = std::move(func)]() mutable {
|
|
func(debugger_.getProgramState());
|
|
};
|
|
|
|
state_->pushPendingFunc(
|
|
[wrappedFunc = std::move(wrappedFunc), promise]() mutable {
|
|
if (auto userCallbackException = runUserCallback(wrappedFunc)) {
|
|
promise->setException(*userCallbackException);
|
|
} else {
|
|
promise->setValue();
|
|
}
|
|
});
|
|
}
|
|
|
|
void Inspector::setBreakpointOnExecutor(
|
|
debugger::SourceLocation loc,
|
|
folly::Optional<std::string> condition,
|
|
std::shared_ptr<folly::Promise<debugger::BreakpointInfo>> promise) {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
|
|
bool pushed = state_->pushPendingFunc([this, loc, condition, promise] {
|
|
debugger::BreakpointID id = debugger_.setBreakpoint(loc);
|
|
debugger::BreakpointInfo info{debugger::kInvalidBreakpoint};
|
|
if (id != debugger::kInvalidBreakpoint) {
|
|
info = debugger_.getBreakpointInfo(id);
|
|
|
|
if (condition) {
|
|
debugger_.setBreakpointCondition(id, condition.value());
|
|
}
|
|
}
|
|
|
|
promise->setValue(std::move(info));
|
|
});
|
|
|
|
if (!pushed) {
|
|
promise->setException(NotEnabledException("setBreakpoint"));
|
|
}
|
|
}
|
|
|
|
void Inspector::removeBreakpointOnExecutor(
|
|
debugger::BreakpointID breakpointId,
|
|
std::shared_ptr<folly::Promise<folly::Unit>> promise) {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
|
|
bool pushed = state_->pushPendingFunc([this, breakpointId, promise] {
|
|
debugger_.deleteBreakpoint(breakpointId);
|
|
promise->setValue();
|
|
});
|
|
|
|
if (!pushed) {
|
|
promise->setException(NotEnabledException("removeBreakpoint"));
|
|
}
|
|
}
|
|
|
|
void Inspector::logOnExecutor(
|
|
ConsoleMessageInfo info,
|
|
std::shared_ptr<folly::Promise<folly::Unit>> promise) {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
|
|
state_->pushPendingFunc([this, info = std::move(info)] {
|
|
observer_.onMessageAdded(*this, info);
|
|
});
|
|
|
|
promise->setValue();
|
|
}
|
|
|
|
void Inspector::setPendingCommandOnExecutor(
|
|
debugger::Command command,
|
|
std::shared_ptr<folly::Promise<Unit>> promise) {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
|
|
state_->setPendingCommand(std::move(command), promise);
|
|
}
|
|
|
|
void Inspector::pauseOnExecutor(std::shared_ptr<folly::Promise<Unit>> promise) {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
|
|
bool canPause = state_->pause();
|
|
|
|
if (canPause) {
|
|
promise->setValue();
|
|
} else {
|
|
promise->setException(NotEnabledException("pause"));
|
|
}
|
|
}
|
|
|
|
void Inspector::evaluateOnExecutor(
|
|
uint32_t frameIndex,
|
|
const std::string &src,
|
|
std::shared_ptr<folly::Promise<debugger::EvalResult>> promise,
|
|
folly::Function<void(const facebook::hermes::debugger::EvalResult &)>
|
|
resultTransformer) {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
|
|
state_->pushPendingEval(
|
|
frameIndex, src, promise, std::move(resultTransformer));
|
|
}
|
|
|
|
void Inspector::setPauseOnExceptionsOnExecutor(
|
|
const debugger::PauseOnThrowMode &mode,
|
|
std::shared_ptr<folly::Promise<folly::Unit>> promise) {
|
|
std::lock_guard<std::mutex> local(mutex_);
|
|
|
|
state_->pushPendingFunc([this, mode, promise] {
|
|
debugger_.setPauseOnThrowMode(mode);
|
|
promise->setValue();
|
|
});
|
|
}
|
|
|
|
static const char *kSuppressionVariable = "_hermes_suppress_superseded_warning";
|
|
void Inspector::alertIfPausedInSupersededFile() {
|
|
if (isExecutingSupersededFile() &&
|
|
!shouldSuppressAlertAboutSupersededFiles()) {
|
|
ScriptInfo info = getScriptInfoFromTopCallFrame();
|
|
std::string warning =
|
|
"You have loaded the current file multiple times, and you are "
|
|
"now paused in one of the previous instances. The source "
|
|
"code you see may not correspond to what's being executed "
|
|
"(set JS variable " +
|
|
std::string(kSuppressionVariable) +
|
|
"=true to "
|
|
"suppress this warning. Filename: " +
|
|
info.fileName + ").";
|
|
jsi::Array jsiArray(adapter_->getRuntime(), 1);
|
|
jsiArray.setValueAtIndex(adapter_->getRuntime(), 0, warning);
|
|
|
|
ConsoleMessageInfo logMessage("warning", std::move(jsiArray));
|
|
observer_.onMessageAdded(*this, logMessage);
|
|
}
|
|
}
|
|
|
|
bool Inspector::shouldSuppressAlertAboutSupersededFiles() {
|
|
jsi::Runtime &rt = adapter_->getRuntime();
|
|
jsi::Value setting = rt.global().getProperty(rt, kSuppressionVariable);
|
|
|
|
if (setting.isUndefined() || !setting.isBool())
|
|
return false;
|
|
return setting.getBool();
|
|
}
|
|
|
|
bool Inspector::isExecutingSupersededFile() {
|
|
ScriptInfo info = getScriptInfoFromTopCallFrame();
|
|
if (info.fileName.empty())
|
|
return false;
|
|
|
|
auto it = loadedScriptIdByName_.find(info.fileName);
|
|
if (it != loadedScriptIdByName_.end()) {
|
|
return it->second > info.fileId;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Inspector::isAwaitingDebuggerOnStart() {
|
|
return awaitingDebuggerOnStart_;
|
|
}
|
|
|
|
} // namespace inspector
|
|
} // namespace hermes
|
|
} // namespace facebook
|