Files
react-native/ReactCommon/hermes/inspector/Inspector.cpp
T
Kudo Chien 962d9c9c1a Fix compile error for native debug build (#26248)
Summary:
Fix Android gradle build error for native debug build.
`NATIVE_BUILD_TYPE=Debug ./gradlew :ReactAndroid:installArchives`
The root cause is `folly::Future<bool>` not declared.
As we don't include true folly::Future implementation in OSS build but to use a forward declaration,
the fix is pretty much like to original declaration as `folly::Future<folly::Unit>`.

## Changelog

[Android] [Fixed] - Fix compile error for native debug build
This change could be ignored from changelog which is only for internal development.
Pull Request resolved: https://github.com/facebook/react-native/pull/26248

Test Plan: Makes sure `NATIVE_BUILD_TYPE=Debug ./gradlew :ReactAndroid:installArchives` build without errors.

Differential Revision: D17169696

Pulled By: mdvacca

fbshipit-source-id: 42e8b84b7ee0d1bd99d913702df98bc030965f63
2019-09-03 18:27:55 -07:00

584 lines
18 KiB
C++

// Copyright 2004-present Facebook. All Rights Reserved.
#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>
// <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.
template class folly::Future<folly::Unit>;
template class folly::Future<bool>;
namespace folly {
namespace futures {
Future<Unit> sleep(Duration dur, Timekeeper *tk) {
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>
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 = true;
// 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->getRuntime().getDebugger()),
observer_(observer),
executor_(std::make_unique<detail::SerialExecutor>("hermes-inspector")) {
// TODO (t26491391): make tickleJs a real Hermes runtime API
const char *src = "function __tickleJs() { return Math.random(); }";
adapter->getRuntime().debugJavaScript(src, "__tickleJsHackUrl", {});
{
std::lock_guard<std::mutex> lock(mutex_);
if (pauseOnFirstStatement) {
TRANSITION(std::make_unique<InspectorState::RunningWaitEnable>(*this));
} else {
TRANSITION(std::make_unique<InspectorState::RunningDetached>(*this));
}
}
debugger_.setShouldPauseOnScriptLoad(true);
debugger_.setEventObserver(this);
}
Inspector::~Inspector() {
// TODO: think about expected detach flow
debugger_.setEventObserver(nullptr);
}
void Inspector::installConsoleFunction(
jsi::Object &console,
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, chromeType](
jsi::Runtime &runtime,
const jsi::Value &thisVal,
const jsi::Value *args,
size_t count) {
if (auto inspector = weakInspector.lock()) {
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();
}));
}
void Inspector::installLogHandler() {
jsi::Runtime &rt = adapter_->getRuntime();
auto console = jsi::Object(rt);
installConsoleFunction(console, "assert");
installConsoleFunction(console, "clear");
installConsoleFunction(console, "debug");
installConsoleFunction(console, "dir");
installConsoleFunction(console, "dirxml");
installConsoleFunction(console, "error");
installConsoleFunction(console, "group", "startGroup");
installConsoleFunction(console, "groupCollapsed", "startGroupCollapsed");
installConsoleFunction(console, "groupEnd", "endGroup");
installConsoleFunction(console, "info");
installConsoleFunction(console, "log");
installConsoleFunction(console, "profile");
installConsoleFunction(console, "profileEnd");
installConsoleFunction(console, "table");
installConsoleFunction(console, "trace");
installConsoleFunction(console, "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();
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) {
uint32_t i = stackTrace.callFrameCount() - 1;
debugger::SourceLocation loc = stackTrace.callFrameForIndex(i).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)) {
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>>();
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();
};
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 {
wrappedFunc();
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();
});
}
} // namespace inspector
} // namespace hermes
} // namespace facebook