Remove deprecated inspector and jsinspector (#39300)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/39300

Changelog: [Internal]

`ReactCommon/hermes/inspector` and `ReactCommon/jsinspector` are unused in the React Native repo as of D48897203 and D48966244, respectively. Now that we've removed the last remaining references to them from internal Meta code, we can safely delete them from React Native.

Reviewed By: christophpurrer

Differential Revision: D48983212

fbshipit-source-id: 9a70178b19fb461c00a2304697b647b7bebe74c3
This commit is contained in:
Danny Su
2023-09-05 21:00:22 -07:00
committed by Facebook GitHub Bot
parent 5258f3a7b4
commit cc059bf6aa
50 changed files with 0 additions and 15275 deletions
@@ -1,87 +0,0 @@
---
AccessModifierOffset: -1
AlignAfterOpenBracket: AlwaysBreak
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlinesLeft: true
AlignOperands: false
AlignTrailingComments: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: true
BinPackArguments: false
BinPackParameters: false
BraceWrapping:
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: false
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
ForEachMacros: [ FOR_EACH_RANGE, FOR_EACH, ]
IncludeCategories:
- Regex: '^<.*\.h(pp)?>'
Priority: 1
- Regex: '^<.*'
Priority: 2
- Regex: '.*'
Priority: 3
IndentCaseLabels: true
IndentWidth: 2
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: false
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Right
ReflowComments: true
SortIncludes: true
SpaceAfterCStyleCast: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp11
TabWidth: 8
UseTab: Never
...
@@ -1,34 +0,0 @@
/*
* 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.
*/
#pragma once
namespace facebook {
namespace hermes {
namespace inspector {
/**
* AsyncPauseState is used to track whether we requested an async pause from a
* running VM, and whether the pause was initiated by us or by the client.
*/
enum class AsyncPauseState {
/// None means there is no pending async pause in the VM.
None,
/// Implicit means we requested an async pause from the VM to service an op
/// that can only be performed while paused, like setting a breakpoint. An
/// impliict pause can be upgraded to an explicit pause if the client later
/// explicitly requests a pause.
Implicit,
/// Explicit means that the client requested the pause by calling pause().
Explicit
};
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -1,60 +0,0 @@
/*
* 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_EXCEPTIONS_H
#define HERMES_INSPECTOR_EXCEPTIONS_H
#include <stdexcept>
namespace facebook {
namespace hermes {
namespace inspector {
class AlreadyEnabledException : public std::runtime_error {
public:
AlreadyEnabledException()
: std::runtime_error("can't enable: debugger already enabled") {}
};
class NotEnabledException : public std::runtime_error {
public:
NotEnabledException(const std::string &cmd)
: std::runtime_error("debugger can't perform " + cmd + ": not enabled") {}
};
class InvalidStateException : public std::runtime_error {
public:
InvalidStateException(
const std::string &cmd,
const std::string &curState,
const std::string &expectedState)
: std::runtime_error(
"debugger can't perform " + cmd + ": in " + curState +
", expected " + expectedState) {}
};
class MultipleCommandsPendingException : public std::runtime_error {
public:
MultipleCommandsPendingException(const std::string &cmd)
: std::runtime_error(
"debugger can't perform " + cmd +
": a step or resume is already pending") {}
};
class UserCallbackException : public std::runtime_error {
public:
UserCallbackException(const std::exception &e)
: std::runtime_error(std::string("callback exception: ") + e.what()) {}
};
} // namespace inspector
} // namespace hermes
} // namespace facebook
#endif // HERMES_INSPECTOR_EXCEPTIONS_H
@@ -1,745 +0,0 @@
/*
* 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->getRuntime().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::scoped_lock 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();
auto fileId = info.fileId;
if (!loadedScripts_.count(fileId)) {
loadedScriptIdByName_[info.fileName] = fileId;
loadedScripts_[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,
std::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::scoped_lock lock(mutex_);
debugger_.setIsDebuggerAttached(false);
state_->detach(promise);
}
void Inspector::enableOnExecutor(
std::shared_ptr<folly::Promise<Unit>> promise) {
std::scoped_lock 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::scoped_lock 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,
std::optional<std::string> condition,
std::shared_ptr<folly::Promise<debugger::BreakpointInfo>> promise) {
std::scoped_lock 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::scoped_lock 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::scoped_lock 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::scoped_lock lock(mutex_);
state_->setPendingCommand(std::move(command), promise);
}
void Inspector::pauseOnExecutor(std::shared_ptr<folly::Promise<Unit>> promise) {
std::scoped_lock 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::scoped_lock 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::scoped_lock 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
@@ -1,384 +0,0 @@
/*
* 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_H
#define HERMES_INSPECTOR_INSPECTOR_H
#include <memory>
#include <queue>
#include <unordered_map>
#include <folly/Executor.h>
#include <folly/Unit.h>
#include <folly/futures/Future.h>
#include <hermes/DebuggerAPI.h>
#include <hermes/hermes.h>
#include <hermes/inspector/AsyncPauseState.h>
#include <hermes/inspector/Exceptions.h>
#include <hermes/inspector/RuntimeAdapter.h>
#include <optional>
namespace facebook {
namespace hermes {
namespace inspector {
class Inspector;
class InspectorState;
/**
* ScriptInfo contains info about loaded scripts.
*/
struct ScriptInfo {
uint32_t fileId{};
std::string fileName;
std::string sourceMappingUrl;
};
struct ConsoleMessageInfo {
std::string source;
std::string level;
std::string url;
int line;
int column;
jsi::Array args;
ConsoleMessageInfo(std::string level, jsi::Array args)
: source("console-api"),
level(level),
url(""),
line(-1),
column(-1),
args(std::move(args)) {}
};
enum PauseOnLoadMode { None, Smart, All };
/**
* InspectorObserver notifies the observer of events that occur in the VM.
*/
class InspectorObserver {
public:
virtual ~InspectorObserver() = default;
/// onContextCreated fires when the VM is created.
virtual void onContextCreated(Inspector &inspector) = 0;
/// onBreakpointResolve fires when a lazy breakpoint is resolved.
virtual void onBreakpointResolved(
Inspector &inspector,
const facebook::hermes::debugger::BreakpointInfo &info) = 0;
/// onPause fires when VM transitions from running to paused state. This is
/// called directly on the JS thread while the VM is paused, so the receiver
/// can call debugger::ProgramState methods safely.
virtual void onPause(
Inspector &inspector,
const facebook::hermes::debugger::ProgramState &state) = 0;
/// onResume fires when VM transitions from paused to running state.
virtual void onResume(Inspector &inspector) = 0;
/// onScriptParsed fires when after the VM parses a script.
virtual void onScriptParsed(Inspector &inspector, const ScriptInfo &info) = 0;
// onMessageAdded fires when new console message is added.
virtual void onMessageAdded(
Inspector &inspector,
const ConsoleMessageInfo &info) = 0;
};
/**
* Inspector implements a future-based interface over the low-level Hermes
* debugging API.
*/
class Inspector : public facebook::hermes::debugger::EventObserver,
public std::enable_shared_from_this<Inspector> {
public:
/**
* Inspector's constructor should be used to install the inspector on the
* provided runtime before any JS executes in the runtime.
*/
Inspector(
std::shared_ptr<RuntimeAdapter> adapter,
InspectorObserver &observer,
bool pauseOnFirstStatement);
~Inspector() override;
/**
* disable turns off the inspector. All of the subsequent methods will not do
* anything unless the inspector is enabled.
*/
folly::Future<folly::Unit> disable();
/**
* enable turns on the inspector. All of the subsequent methods will not do
* anything unless the inspector is enabled. The returned future succeeds when
* the debugger is enabled, or fails with AlreadyEnabledException if the
* debugger was already enabled.
*/
folly::Future<folly::Unit> enable();
/**
* installs console log handler. Ideally this should be done inside
* constructor, but because it uses shared_from_this we can't do this
* in constructor.
*/
void installLogHandler();
/**
* executeIfEnabled executes the provided callback *on the JS thread with the
* inspector lock held*. Execution can be implicitly requested while running.
* The inspector lock:
*
* 1) Protects VM state transitions. This means that the VM is guaranteed to
* stay in the paused or running state for the duration of the callback.
* 2) Protects InspectorObserver callbacks. This means that if some shared
* data is accessed only in InspectorObserver and executeIfEnabled
* callbacks, it does not need to be locked, since it's already protected
* by the inspector lock.
*
* The returned future resolves to true in the VM can be paused, or
* fails with IllegalStateException otherwise. The description is only used
* to populate the IllegalStateException with more useful info on failure.
*/
folly::Future<folly::Unit> executeIfEnabled(
const std::string &description,
folly::Function<void(const facebook::hermes::debugger::ProgramState &)>
func);
/**
* setBreakpoint can be called at any time after the debugger is enabled to
* set a breakpoint in the VM. The future is fulfilled with the resolved
* breakpoint info.
*
* Resolving a breakpoint takes an indeterminate amount of time since Hermes
* only resolves breakpoints when the debugger is able to actively pause JS
* execution.
*/
folly::Future<facebook::hermes::debugger::BreakpointInfo> setBreakpoint(
facebook::hermes::debugger::SourceLocation loc,
std::optional<std::string> condition = std::nullopt);
folly::Future<folly::Unit> removeBreakpoint(
facebook::hermes::debugger::BreakpointID loc);
/**
* logs console message.
*/
folly::Future<folly::Unit> logMessage(ConsoleMessageInfo info);
/**
* resume and step methods are only valid when the VM is currently paused. The
* returned future succeeds when the VM resumes execution, or fails with an
* InvalidStateException otherwise.
*/
folly::Future<folly::Unit> resume();
folly::Future<folly::Unit> stepIn();
folly::Future<folly::Unit> stepOver();
folly::Future<folly::Unit> stepOut();
/**
* pause can be issued at any time while the inspector is enabled. It requests
* the VM to asynchronously break execution. The returned future succeeds if
* the VM can be paused in this state and fails with InvalidStateException if
* otherwise.
*/
folly::Future<folly::Unit> pause();
/**
* evaluate runs JavaScript code within the context of a call frame. The
* returned promise is fulfilled with an eval result if it's possible to
* evaluate code in the current state or fails with InvalidStateException
* otherwise.
*/
folly::Future<facebook::hermes::debugger::EvalResult> evaluate(
uint32_t frameIndex,
const std::string &src,
folly::Function<void(const facebook::hermes::debugger::EvalResult &)>
resultTransformer);
folly::Future<folly::Unit> setPauseOnExceptions(
const facebook::hermes::debugger::PauseOnThrowMode &mode);
/**
* Set whether to pause on loads. This does not require runtime modifications,
* but returns a future for consistency.
*/
folly::Future<folly::Unit> setPauseOnLoads(const PauseOnLoadMode mode);
/**
* Set whether breakpoints are active (pause when hit). This does not require
* runtime modifications, but returns a future for consistency.
*/
folly::Future<folly::Unit> setBreakpointsActive(bool active);
/**
* If called during a script load event, return true if we should pause.
* Assumed to be called from a script load event where we already hold
* `mutex_`.
*/
bool shouldPauseOnThisScriptLoad();
/**
* didPause implements the pause callback from Hermes. This callback arrives
* on the JS thread.
*/
facebook::hermes::debugger::Command didPause(
facebook::hermes::debugger::Debugger &debugger) override;
/**
* breakpointResolved implements the breakpointResolved callback from Hermes.
*/
void breakpointResolved(
facebook::hermes::debugger::Debugger &debugger,
facebook::hermes::debugger::BreakpointID breakpointId) override;
/**
* Get whether we started with pauseOnFirstStatement, and have not yet had a
* debugger attach and ask to resume from that point. This matches the
* semantics of when CDP Debugger.runIfWaitingForDebugger should resume.
*
* It's not named "isPausedOnStart" because the VM and inspector is not
* necessarily paused; we could be in a RunningWaitPause state.
*/
bool isAwaitingDebuggerOnStart();
private:
friend class InspectorState;
void triggerAsyncPause(bool andTickle);
void notifyContextCreated();
ScriptInfo getScriptInfoFromTopCallFrame();
void addCurrentScriptToLoadedScripts();
void removeAllBreakpoints();
void resetScriptsLoaded();
void notifyScriptsLoaded();
folly::Future<folly::Unit> setPendingCommand(debugger::Command command);
void transition(std::unique_ptr<InspectorState> nextState);
// All methods that end with OnExecutor run on executor_.
void disableOnExecutor(std::shared_ptr<folly::Promise<folly::Unit>> promise);
void enableOnExecutor(std::shared_ptr<folly::Promise<folly::Unit>> promise);
void executeIfEnabledOnExecutor(
const std::string &description,
folly::Function<void(const facebook::hermes::debugger::ProgramState &)>
func,
std::shared_ptr<folly::Promise<folly::Unit>> promise);
void setBreakpointOnExecutor(
debugger::SourceLocation loc,
std::optional<std::string> condition,
std::shared_ptr<
folly::Promise<facebook::hermes::debugger::BreakpointInfo>> promise);
void removeBreakpointOnExecutor(
debugger::BreakpointID breakpointId,
std::shared_ptr<folly::Promise<folly::Unit>> promise);
void logOnExecutor(
ConsoleMessageInfo info,
std::shared_ptr<folly::Promise<folly::Unit>> promise);
void setPendingCommandOnExecutor(
facebook::hermes::debugger::Command command,
std::shared_ptr<folly::Promise<folly::Unit>> promise);
void pauseOnExecutor(std::shared_ptr<folly::Promise<folly::Unit>> promise);
void evaluateOnExecutor(
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);
void setPauseOnExceptionsOnExecutor(
const facebook::hermes::debugger::PauseOnThrowMode &mode,
std::shared_ptr<folly::Promise<folly::Unit>> promise);
void installConsoleFunction(
jsi::Object &console,
std::shared_ptr<jsi::Object> &originalConsole,
const std::string &name,
const std::string &chromeType);
std::shared_ptr<RuntimeAdapter> adapter_;
facebook::hermes::debugger::Debugger &debugger_;
InspectorObserver &observer_;
// All of the following member variables are guarded by mutex_.
std::mutex mutex_;
std::unique_ptr<InspectorState> state_;
// See the InspectorState::Running implementation for an explanation for why
// this state is here rather than in the Running class.
AsyncPauseState pendingPauseState_ = AsyncPauseState::None;
// Whether we should enter a paused state when a script loads.
PauseOnLoadMode pauseOnLoadMode_ = PauseOnLoadMode::None;
// Whether or not we should pause on breakpoints.
bool breakpointsActive_ = true;
// All scripts loaded in to the VM, along with whether we've notified the
// client about the script yet.
struct LoadedScriptInfo {
ScriptInfo info;
bool notifiedClient;
};
std::unordered_map<int, LoadedScriptInfo> loadedScripts_;
std::unordered_map<std::string, int> loadedScriptIdByName_;
// Returns true if we are executing a file instance that has since been
// reloaded. I.e. we are running an old version of the file.
bool isExecutingSupersededFile();
// Allow the user to suppress warnings about superseded files.
bool shouldSuppressAlertAboutSupersededFiles();
// Trigger a fake console.log if we're currently in a superseded file.
void alertIfPausedInSupersededFile();
// Are we currently waiting for a debugger to attach, because we
// requested 'pauseOnFirstStatement'?
bool awaitingDebuggerOnStart_;
// All client methods (e.g. enable, setBreakpoint, resume, etc.) are executed
// on executor_ to prevent deadlocking on mutex_. See the implementation for
// more comments on the threading invariants used in this class.
// NOTE: This needs to be declared LAST because it should be destroyed FIRST.
std::unique_ptr<folly::Executor> executor_;
};
/// Helper function that guards user code execution in a try-catch block.
template <typename C, typename... A>
std::optional<UserCallbackException> runUserCallback(C &cb, A &&...arg) {
try {
cb(std::forward<A>(arg)...);
} catch (const std::exception &e) {
return UserCallbackException(e);
}
return {};
}
} // namespace inspector
} // namespace hermes
} // namespace facebook
#endif // HERMES_INSPECTOR_INSPECTOR_H
@@ -1,511 +0,0 @@
/*
* 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 "InspectorState.h"
#include <glog/logging.h>
namespace facebook {
namespace hermes {
namespace inspector {
using folly::Unit;
namespace debugger = ::facebook::hermes::debugger;
namespace {
std::unique_ptr<debugger::Command> makeContinueCommand() {
return std::make_unique<debugger::Command>(
debugger::Command::continueExecution());
}
} // namespace
std::ostream &operator<<(std::ostream &os, const InspectorState &state) {
return os << state.description();
}
/*
* InspectorState::RunningDetached
*/
std::pair<NextStatePtr, CommandPtr> InspectorState::RunningDetached::didPause(
MonitorLock &lock) {
debugger::PauseReason reason = getPauseReason();
if (reason == debugger::PauseReason::DebuggerStatement) {
return std::make_pair<NextStatePtr, CommandPtr>(
InspectorState::PausedWaitEnable::make(inspector_), nullptr);
}
if (reason == debugger::PauseReason::ScriptLoaded) {
inspector_.addCurrentScriptToLoadedScripts();
}
return std::make_pair<NextStatePtr, CommandPtr>(
nullptr, makeContinueCommand());
}
void InspectorState::RunningDetached::onEnter(InspectorState *previous) {
inspector_.awaitingDebuggerOnStart_ = false;
}
std::pair<NextStatePtr, bool> InspectorState::RunningDetached::enable() {
return std::make_pair<NextStatePtr, bool>(
InspectorState::Running::make(inspector_), true);
}
/*
* InspectorState::RunningWaitEnable
*/
std::pair<NextStatePtr, CommandPtr> InspectorState::RunningWaitEnable::didPause(
MonitorLock &lock) {
// If we started in RWE, then we asked for the VM to break on the first
// statement, and the first pause should be because of a script load.
assert(getPauseReason() == debugger::PauseReason::ScriptLoaded);
inspector_.addCurrentScriptToLoadedScripts();
return std::make_pair<NextStatePtr, CommandPtr>(
InspectorState::PausedWaitEnable::make(inspector_), nullptr);
}
std::pair<NextStatePtr, bool> InspectorState::RunningWaitEnable::enable() {
return std::make_pair<NextStatePtr, bool>(
InspectorState::RunningWaitPause::make(inspector_), true);
}
/*
* InspectorState::RunningWaitPause
*/
std::pair<NextStatePtr, CommandPtr> InspectorState::RunningWaitPause::didPause(
MonitorLock &lock) {
// If we are in RWP, then we asked for the VM to break on the first
// statement, and the first pause should be because of a script load.
assert(getPauseReason() == debugger::PauseReason::ScriptLoaded);
inspector_.addCurrentScriptToLoadedScripts();
return std::make_pair<NextStatePtr, CommandPtr>(
InspectorState::Paused::make(inspector_), nullptr);
}
/*
* InspectorState::PausedWaitEnable
*/
std::pair<NextStatePtr, CommandPtr> InspectorState::PausedWaitEnable::didPause(
MonitorLock &lock) {
if (getPauseReason() == debugger::PauseReason::ScriptLoaded) {
inspector_.addCurrentScriptToLoadedScripts();
}
while (!enabled_) {
/*
* The call to wait temporarily relinquishes the inspector mutex. This is
* safe because no other PausedWaitEnable event handler directly transitions
* out of PausedWaitEnable. So we know that our state is the active state
* both before and after the call to wait. This preserves the invariant that
* the inspector state is not modified during the execution of this method.
*
* Instead, PausedWaitEnable::enable indirectly induces the state transition
* out of PausedWaitEnable by signaling us via enabledCondition_.
*/
enabledCondition_.wait(lock);
assert(inspector_.state_.get() == this);
}
return std::make_pair<NextStatePtr, CommandPtr>(
InspectorState::Paused::make(inspector_), nullptr);
}
std::pair<NextStatePtr, bool> InspectorState::PausedWaitEnable::enable() {
if (enabled_) {
// Someone already called enable before and we're just waiting for the
// condition variable to wake up didPause.
return std::make_pair<NextStatePtr, bool>(nullptr, false);
}
enabled_ = true;
enabledCondition_.notify_one();
return std::make_pair<NextStatePtr, bool>(nullptr, true);
}
/*
* InspectorState::Running
*
* # Async Pauses
*
* We distinguish between implicit and explicit async pauses. An implicit async
* pause is requested by the inspector itself to service a request that requires
* the VM to be paused (e.g. to set a breakpoint). This is different from an
* explicit async pause requested by the user by hitting the pause button in the
* debugger UI.
*
* The async pause state must live in the Inspector class instead of the Running
* class because of potential races between when the implicit pause is requested
* and when it's serviced. Consider:
*
* 1. We request an implicit pause (e.g. to set a breakpoint).
* 2. An existing breakpoint fires, moving us from Running => Paused.
* 3. Client resumes execution, moving us from Paused => Running.
* 4. Now the debugger notices the async pause flag we set in (1), which pauses
* us again, causing Running::didPause to run.
*
* In this case, the Running state instance from (1) is no longer the same as
* the Running state instance in (4). But the running state instance in (4)
* needs to know that we requested the async break sometime in the past so it
* knows to automatically continue in the didPause callback. Therefore the async
* break state has to be stored in the long-lived Inspector class rather than in
* the short-lived Running class.
*/
void InspectorState::Running::onEnter(InspectorState *prevState) {
if (prevState) {
if (prevState->isPaused()) {
inspector_.observer_.onResume(inspector_);
} else {
// send context created and script load notifications if we just enabled
// the debugger
inspector_.notifyContextCreated();
inspector_.notifyScriptsLoaded();
}
}
inspector_.awaitingDebuggerOnStart_ = false;
}
void InspectorState::Running::detach(
std::shared_ptr<folly::Promise<Unit>> promise) {
pushPendingFunc([this, promise] {
pendingDetach_ = promise;
inspector_.removeAllBreakpoints();
inspector_.resetScriptsLoaded();
});
}
std::pair<NextStatePtr, CommandPtr> InspectorState::Running::didPause(
MonitorLock &lock) {
debugger::PauseReason reason = getPauseReason();
for (auto &func : pendingFuncs_) {
func();
}
pendingFuncs_.clear();
if (pendingDetach_) {
// Clear any pending pause state back to no requests for the next attach
inspector_.pendingPauseState_ = AsyncPauseState::None;
// Ensure we fulfill any pending ScriptLoaded requests
if (reason == debugger::PauseReason::ScriptLoaded) {
inspector_.addCurrentScriptToLoadedScripts();
}
// Fail any in-flight Eval requests
if (pendingEvalPromise_) {
pendingEvalPromise_->setException(NotEnabledException("eval"));
}
// if we requested the break implicitly to clear state and detach,
// transition to RunningDetached
pendingDetach_->setValue();
return std::make_pair<NextStatePtr, CommandPtr>(
InspectorState::RunningDetached::make(inspector_),
makeContinueCommand());
}
if (reason == debugger::PauseReason::AsyncTrigger) {
AsyncPauseState &pendingPauseState = inspector_.pendingPauseState_;
switch (pendingPauseState) {
case AsyncPauseState::None:
// shouldn't ever async break without us asking first
assert(false);
break;
case AsyncPauseState::Implicit:
pendingPauseState = AsyncPauseState::None;
break;
case AsyncPauseState::Explicit:
// explicit break was requested by user, so go to Paused state
pendingPauseState = AsyncPauseState::None;
return std::make_pair<NextStatePtr, CommandPtr>(
InspectorState::Paused::make(inspector_), nullptr);
}
} else if (reason == debugger::PauseReason::ScriptLoaded) {
inspector_.addCurrentScriptToLoadedScripts();
inspector_.notifyScriptsLoaded();
if (inspector_.shouldPauseOnThisScriptLoad()) {
return std::make_pair<NextStatePtr, CommandPtr>(
InspectorState::Paused::make(inspector_), nullptr);
}
} else if (reason == debugger::PauseReason::EvalComplete) {
assert(pendingEvalPromise_);
if (auto userCallbackException = runUserCallback(
pendingEvalResultTransformer_,
inspector_.debugger_.getProgramState().getEvalResult())) {
pendingEvalPromise_->setException(*userCallbackException);
} else {
pendingEvalPromise_->setValue(
inspector_.debugger_.getProgramState().getEvalResult());
}
pendingEvalPromise_.reset();
} else if (
reason == debugger::PauseReason::Breakpoint &&
!inspector_.breakpointsActive_) {
// We hit a user defined breakpoint, but breakpoints have been deactivated.
return std::make_pair<NextStatePtr, CommandPtr>(
nullptr, makeContinueCommand());
} else /* other cases imply a transition to Pause */ {
return std::make_pair<NextStatePtr, CommandPtr>(
InspectorState::Paused::make(inspector_), nullptr);
}
if (!pendingEvals_.empty()) {
assert(!pendingEvalPromise_);
auto eval = std::make_unique<PendingEval>(std::move(pendingEvals_.front()));
pendingEvals_.pop();
pendingEvalPromise_ = eval->promise;
pendingEvalResultTransformer_ = std::move(eval->resultTransformer);
return std::make_pair<NextStatePtr, CommandPtr>(
nullptr, std::make_unique<debugger::Command>(std::move(eval->command)));
}
return std::make_pair<NextStatePtr, CommandPtr>(
nullptr, makeContinueCommand());
}
bool InspectorState::Running::pushPendingFunc(folly::Func func) {
pendingFuncs_.emplace_back(std::move(func));
if (inspector_.pendingPauseState_ == AsyncPauseState::None) {
inspector_.pendingPauseState_ = AsyncPauseState::Implicit;
inspector_.triggerAsyncPause(true);
}
return true;
}
void InspectorState::Running::pushPendingEval(
uint32_t frameIndex,
const std::string &src,
std::shared_ptr<folly::Promise<debugger::EvalResult>> promise,
folly::Function<void(const facebook::hermes::debugger::EvalResult &)>
resultTransformer) {
PendingEval pendingEval{
debugger::Command::eval(src, frameIndex),
promise,
std::move(resultTransformer)};
pendingEvals_.emplace(std::move(pendingEval));
if (inspector_.pendingPauseState_ == AsyncPauseState::None) {
inspector_.pendingPauseState_ = AsyncPauseState::Implicit;
}
inspector_.triggerAsyncPause(true);
}
bool InspectorState::Running::pause() {
AsyncPauseState &pendingPauseState = inspector_.pendingPauseState_;
bool canPause = false;
switch (pendingPauseState) {
case AsyncPauseState::None:
// haven't yet requested a pause, so do it now
pendingPauseState = AsyncPauseState::Explicit;
inspector_.triggerAsyncPause(false);
canPause = true;
break;
case AsyncPauseState::Implicit:
// already requested an implicit pause on our own, upgrade it to an
// explicit pause
pendingPauseState = AsyncPauseState::Explicit;
inspector_.triggerAsyncPause(false);
canPause = true;
break;
case AsyncPauseState::Explicit:
// client already requested a pause that hasn't occurred yet
canPause = false;
break;
}
return canPause;
}
/*
* InspectorState::Paused
*/
void InspectorState::Paused::onEnter(InspectorState *prevState) {
// send script load notifications if we just enabled the debugger
if (prevState && !prevState->isRunning()) {
inspector_.notifyContextCreated();
inspector_.notifyScriptsLoaded();
}
const debugger::ProgramState &state = inspector_.debugger_.getProgramState();
inspector_.alertIfPausedInSupersededFile();
inspector_.observer_.onPause(inspector_, state);
}
std::pair<NextStatePtr, CommandPtr> InspectorState::Paused::didPause(
std::unique_lock<std::mutex> &lock) {
switch (getPauseReason()) {
case debugger::PauseReason::AsyncTrigger:
inspector_.pendingPauseState_ = AsyncPauseState::None;
break;
case debugger::PauseReason::EvalComplete: {
assert(pendingEvalPromise_);
if (auto userCallbackException = runUserCallback(
pendingEvalResultTransformer_,
inspector_.debugger_.getProgramState().getEvalResult())) {
pendingEvalPromise_->setException(*userCallbackException);
} else {
pendingEvalPromise_->setValue(
inspector_.debugger_.getProgramState().getEvalResult());
}
pendingEvalPromise_.reset();
} break;
case debugger::PauseReason::ScriptLoaded:
inspector_.addCurrentScriptToLoadedScripts();
inspector_.notifyScriptsLoaded();
break;
default:
break;
}
std::unique_ptr<PendingEval> eval;
std::unique_ptr<PendingCommand> resumeOrStep;
while (!eval && !resumeOrStep && !pendingDetach_) {
{
while (!pendingCommand_ && pendingEvals_.empty() &&
pendingFuncs_.empty()) {
/*
* The call to wait temporarily relinquishes the inspector mutex. This
* is safe because no other Paused event handler directly transitions
* out of Paused. So we know that our state is the active state both
* before and after the call to wait. This preserves the invariant that
* the inspector state is not modified during the execution of this
* method.
*/
hasPendingWork_.wait(lock);
}
assert(inspector_.state_.get() == this);
}
if (!pendingEvals_.empty()) {
eval = std::make_unique<PendingEval>(std::move(pendingEvals_.front()));
pendingEvals_.pop();
} else if (pendingCommand_) {
resumeOrStep.swap(pendingCommand_);
}
for (auto &func : pendingFuncs_) {
func();
}
pendingFuncs_.clear();
}
if (pendingDetach_) {
if (pendingEvalPromise_) {
pendingEvalPromise_->setException(NotEnabledException("eval"));
}
if (resumeOrStep) {
resumeOrStep->promise->setValue();
}
pendingDetach_->setValue();
// Send resume so client-side UI doesn't stay stuck at the breakpoint UI
inspector_.observer_.onResume(inspector_);
return std::make_pair<NextStatePtr, CommandPtr>(
InspectorState::RunningDetached::make(inspector_),
makeContinueCommand());
}
if (eval) {
assert(!pendingEvalPromise_);
pendingEvalPromise_ = eval->promise;
pendingEvalResultTransformer_ = std::move(eval->resultTransformer);
return std::make_pair<NextStatePtr, CommandPtr>(
nullptr, std::make_unique<debugger::Command>(std::move(eval->command)));
}
assert(resumeOrStep);
resumeOrStep->promise->setValue();
return std::make_pair<NextStatePtr, CommandPtr>(
InspectorState::Running::make(inspector_),
std::make_unique<debugger::Command>(std::move(resumeOrStep->command)));
}
void InspectorState::Paused::detach(
std::shared_ptr<folly::Promise<Unit>> promise) {
pushPendingFunc([this, promise] {
pendingDetach_ = promise;
inspector_.removeAllBreakpoints();
inspector_.resetScriptsLoaded();
});
}
bool InspectorState::Paused::pushPendingFunc(folly::Func func) {
pendingFuncs_.emplace_back(std::move(func));
hasPendingWork_.notify_one();
return true;
}
void InspectorState::Paused::pushPendingEval(
uint32_t frameIndex,
const std::string &src,
std::shared_ptr<folly::Promise<debugger::EvalResult>> promise,
folly::Function<void(const facebook::hermes::debugger::EvalResult &)>
resultTransformer) {
// Shouldn't allow the client to eval if there's already a pending resume/step
if (pendingCommand_) {
promise->setException(MultipleCommandsPendingException("eval"));
return;
}
PendingEval pendingEval{
debugger::Command::eval(src, frameIndex),
promise,
std::move(resultTransformer)};
pendingEvals_.emplace(std::move(pendingEval));
hasPendingWork_.notify_one();
}
void InspectorState::Paused::setPendingCommand(
debugger::Command command,
std::shared_ptr<folly::Promise<Unit>> promise) {
if (pendingCommand_) {
promise->setException(MultipleCommandsPendingException("cmd"));
return;
}
pendingCommand_ =
std::make_unique<PendingCommand>(std::move(command), promise);
hasPendingWork_.notify_one();
}
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -1,412 +0,0 @@
/*
* 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
@@ -1,119 +0,0 @@
# DEPRECATED!
This is a **deprecated, frozen version of the Hermes inspector support code in React Native**. See `ReactCommon/hermes/inspector-modern` instead.
---
hermes-inspector provides a bridge between the low-level debugging API exposed
by Hermes and higher-level debugging protocols such as the Chrome DevTools
protocol.
# Targets
- chrome: classes that implement the Chrome DevTools Protocol adapter. Sits on
top of classes provided by the inspector target.
- detail: utility classes and functions
- inspector: protocol-independent classes that sit on top of the low-level
Hermes debugging API.
# Testing
Tests are implemented using gtest. Debug logging is enabled for tests, and you
can get debug logs to show even when tests are passing by running the test
executable directly:
```
$ buck build //xplat/js/react-native-github/packages/react-native/ReactCommon/hermes/inspector:chrome-tests
$ buck-out/gen/xplat/js/react-native-github/packages/react-native/ReactCommon/hermes/inspector/chrome-tests
[...]
```
You can use standard gtest filters to only execute a particular set of tests:
```
$ buck-out/gen/xplat/js/react-native-github/packages/react-native/ReactCommon/hermes/inspector/chrome-tests \
--gtest_filter='ConnectionTests.testSetBreakpoint'
```
You can debug the tests using lldb or gdb:
```
$ lldb buck-out/gen/xplat/js/react-native-github/packages/react-native/ReactCommon/hermes/inspector/chrome-tests
$ gdb buck-out/gen/xplat/js/react-native-github/packages/react-native/ReactCommon/hermes/inspector/chrome-tests
```
# Formatting
Make sure the code is formatted using the hermes clang-format rules before
committing:
```
$ xplat/js/react-native-github/packages/react-native/ReactCommon/hermes/inspector/tools/format
```
We follow the clang format rules used by the rest of the Hermes project.
# Adding Support For New Message Types
To add support for a new Chrome DevTools protocol message, add the message you
want to add to tools/message_types.txt, and re-run the message types generator:
```
$ xplat/js/react-native-github/packages/react-native/ReactCommon/hermes/inspector/tools/run_msggen
```
This will generate C++ structs for the new message type in
`chrome/MessageTypes.{h,cpp}`.
You'll then need to:
1. Implement a message handler for the new message type in `chrome::Connection`.
2. Implement a public API for the new message type in `Inspector`. This will
most likely return a `folly::Future` that the message handler in (1) can use
for chaining.
3. Implement a private API for the new message type in `Inspector` that performs
the logic in Inspector's executor. (Inspector.cpp contains a comment
explaining why the executor is necessary.)
4. Optionally, implement a method for the new message type in `InspectorState`.
In most cases this is probably not necessary--one of the existing methods in
`InspectorState` will work.
For a diff that illustrates these steps, take a look at D6601459.
# Testing Integration With Nuclide and Apps
For now, the quickest way to use hermes-inspector in an app is with Eats. First,
make sure the packager is running:
```
$ js1 run
```
Then, on Android, build the fbeats target:
```
$ buck install --run fbeats
```
On iOS, build the `//Apps/Internal/Eats:Eats` target:
```
$ buck install --run //Apps/Internal/Eats:Eats
```
You can also build `Eats` in Xcode using `arc focus` if you prefer an
IDE:
```
$ arc focus --force-build \
-b //Apps/Internal/Eats:Eats \
cxxreact //xplat/hermes/API:HermesAPI //xplat/hermes/lib/VM:VM jsi \
jsinspector hermes-inspector FBReactKit FBReactModule FBCatalystWrapper \
//xplat/js:React //xplat/js/react-native-github:ReactInternal
```
For all the above commands, if you want to build the inspector `-O0` for better
debug info, add the argument `--config hermes.build_mode=dbg`.
You should then be able to launch the app and see it listed in the list of
Mobile JS contexts in the Nuclide debugger.
@@ -1,30 +0,0 @@
/*
* 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 "RuntimeAdapter.h"
namespace facebook {
namespace hermes {
namespace inspector {
RuntimeAdapter::~RuntimeAdapter() = default;
void RuntimeAdapter::tickleJs() {}
SharedRuntimeAdapter::SharedRuntimeAdapter(
std::shared_ptr<HermesRuntime> runtime)
: runtime_(std::move(runtime)) {}
SharedRuntimeAdapter::~SharedRuntimeAdapter() = default;
HermesRuntime &SharedRuntimeAdapter::getRuntime() {
return *runtime_;
}
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -1,76 +0,0 @@
/*
* 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.
*/
#pragma once
#include <memory>
#include <hermes/hermes.h>
#ifndef INSPECTOR_EXPORT
#ifdef _MSC_VER
#ifdef CREATE_SHARED_LIBRARY
#define INSPECTOR_EXPORT __declspec(dllexport)
#else
#define INSPECTOR_EXPORT
#endif // CREATE_SHARED_LIBRARY
#else // _MSC_VER
#define INSPECTOR_EXPORT __attribute__((visibility("default")))
#endif // _MSC_VER
#endif // !defined(INSPECTOR_EXPORT)
namespace facebook {
namespace hermes {
namespace inspector {
/**
* RuntimeAdapter encapsulates a HermesRuntime object. The underlying Hermes
* runtime object should stay alive for at least as long as the RuntimeAdapter
* is alive.
*/
class INSPECTOR_EXPORT RuntimeAdapter {
public:
virtual ~RuntimeAdapter() = 0;
/// getRuntime should return the runtime encapsulated by this adapter.
virtual HermesRuntime &getRuntime() = 0;
/// tickleJs is a method that subclasses can choose to override to make the
/// inspector more responsive. If overridden, it should call the "__tickleJs"
/// function. The call should occur with appropriate locking (e.g. via a
/// thread-safe runtime instance, or by enqueuing the call on to a dedicated
/// JS thread).
///
/// This makes the inspector more responsive because it gives the inspector
/// the ability to force the process to enter the Hermes interpreter loop
/// soon. This is important because the inspector can only do a number of
/// important operations (like manipulating breakpoints) within the context of
/// a Hermes interpreter loop.
///
/// The default implementation does nothing.
virtual void tickleJs();
};
/**
* SharedRuntimeAdapter is a simple implementation of RuntimeAdapter that
* uses shared_ptr to hold on to the runtime. It's generally only used in tests,
* since it does not implement tickleJs.
*/
class INSPECTOR_EXPORT SharedRuntimeAdapter : public RuntimeAdapter {
public:
SharedRuntimeAdapter(std::shared_ptr<HermesRuntime> runtime);
~SharedRuntimeAdapter() override;
HermesRuntime &getRuntime() override;
private:
std::shared_ptr<HermesRuntime> runtime_;
};
} // namespace inspector
} // namespace hermes
} // namespace facebook
File diff suppressed because it is too large Load Diff
@@ -1,67 +0,0 @@
/*
* 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_CONNECTION_H
#define HERMES_INSPECTOR_CONNECTION_H
#include <functional>
#include <memory>
#include <string>
#include <hermes/hermes.h>
#include <hermes/inspector/RuntimeAdapter.h>
#include <jsinspector/InspectorInterfaces.h>
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
/// Connection is a duplex connection between the client and the debugger.
class INSPECTOR_EXPORT Connection {
public:
/// Connection constructor enables the debugger on the provided runtime. This
/// should generally called before you start running any JS in the runtime.
Connection(
std::unique_ptr<RuntimeAdapter> adapter,
const std::string &title,
bool waitForDebugger = false);
~Connection();
/// getRuntime returns the underlying runtime being debugged.
HermesRuntime &getRuntime();
/// getTitle returns the name of the friendly name of the runtime that's shown
/// to users in Nuclide.
std::string getTitle() const;
/// connect attaches this connection to the runtime's debugger. Requests to
/// the debugger sent via send(). Replies and notifications from the debugger
/// are sent back to the client via IRemoteConnection::onMessage.
bool connect(
std::unique_ptr<::facebook::react::IRemoteConnection> remoteConn);
/// disconnect disconnects this connection from the runtime's debugger
bool disconnect();
/// sendMessage delivers a JSON-encoded Chrome DevTools Protocol request to
/// the debugger.
void sendMessage(std::string str);
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
#endif // HERMES_INSPECTOR_CONNECTION_H
@@ -1,131 +0,0 @@
/*
* 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 "ConnectionDemux.h"
#include "Connection.h"
#include <jsinspector/InspectorInterfaces.h>
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
using ::facebook::react::IInspector;
using ::facebook::react::ILocalConnection;
using ::facebook::react::IRemoteConnection;
namespace {
class LocalConnection : public ILocalConnection {
public:
LocalConnection(
std::shared_ptr<Connection> conn,
std::shared_ptr<std::unordered_set<std::string>> inspectedContexts);
~LocalConnection();
void sendMessage(std::string message) override;
void disconnect() override;
private:
std::shared_ptr<Connection> conn_;
std::shared_ptr<std::unordered_set<std::string>> inspectedContexts_;
};
LocalConnection::LocalConnection(
std::shared_ptr<Connection> conn,
std::shared_ptr<std::unordered_set<std::string>> inspectedContexts)
: conn_(conn), inspectedContexts_(inspectedContexts) {
inspectedContexts_->insert(conn->getTitle());
}
LocalConnection::~LocalConnection() = default;
void LocalConnection::sendMessage(std::string str) {
conn_->sendMessage(std::move(str));
}
void LocalConnection::disconnect() {
inspectedContexts_->erase(conn_->getTitle());
conn_->disconnect();
}
} // namespace
ConnectionDemux::ConnectionDemux(facebook::react::IInspector &inspector)
: globalInspector_(inspector),
inspectedContexts_(std::make_shared<std::unordered_set<std::string>>()) {}
ConnectionDemux::~ConnectionDemux() = default;
DebugSessionToken ConnectionDemux::enableDebugging(
std::unique_ptr<RuntimeAdapter> adapter,
const std::string &title) {
std::scoped_lock lock(mutex_);
// TODO(#22976087): workaround for ComponentScript contexts never being
// destroyed.
//
// After a reload, the old ComponentScript VM instance stays alive. When we
// register the new CS VM instance, check for any previous CS VM (via strcmp
// of title) and remove them.
std::vector<int> pagesToDelete;
for (auto it = conns_.begin(); it != conns_.end(); ++it) {
if (it->second->getTitle() == title) {
pagesToDelete.push_back(it->first);
}
}
for (auto pageId : pagesToDelete) {
removePage(pageId);
}
auto waitForDebugger =
(inspectedContexts_->find(title) != inspectedContexts_->end());
return addPage(
std::make_shared<Connection>(std::move(adapter), title, waitForDebugger));
}
void ConnectionDemux::disableDebugging(DebugSessionToken session) {
std::scoped_lock lock(mutex_);
if (conns_.find(session) == conns_.end()) {
return;
}
removePage(session);
}
int ConnectionDemux::addPage(std::shared_ptr<Connection> conn) {
auto connectFunc = [conn, this](std::unique_ptr<IRemoteConnection> remoteConn)
-> std::unique_ptr<ILocalConnection> {
if (!conn->connect(std::move(remoteConn))) {
return nullptr;
}
return std::make_unique<LocalConnection>(conn, inspectedContexts_);
};
int pageId = globalInspector_.addPage(
conn->getTitle(), "Hermes", std::move(connectFunc));
conns_[pageId] = std::move(conn);
return pageId;
}
void ConnectionDemux::removePage(int pageId) {
globalInspector_.removePage(pageId);
auto conn = conns_.at(pageId);
std::string title = conn->getTitle();
inspectedContexts_->erase(title);
conn->disconnect();
conns_.erase(pageId);
}
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -1,59 +0,0 @@
/*
* 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.
*/
#pragma once
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <hermes/hermes.h>
#include <hermes/inspector/RuntimeAdapter.h>
#include <hermes/inspector/chrome/Connection.h>
#include <hermes/inspector/chrome/Registration.h>
#include <jsinspector/InspectorInterfaces.h>
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
/*
* ConnectionDemux keeps track of all debuggable Hermes runtimes (called
* "pages" in the higher-level React Native API) in this process. See
* Registration.h for documentation of the public API.
*/
class ConnectionDemux {
public:
explicit ConnectionDemux(facebook::react::IInspector &inspector);
~ConnectionDemux();
ConnectionDemux(const ConnectionDemux &) = delete;
ConnectionDemux &operator=(const ConnectionDemux &) = delete;
DebugSessionToken enableDebugging(
std::unique_ptr<RuntimeAdapter> adapter,
const std::string &title);
void disableDebugging(DebugSessionToken session);
private:
int addPage(std::shared_ptr<Connection> conn);
void removePage(int pageId);
facebook::react::IInspector &globalInspector_;
std::mutex mutex_;
std::unordered_map<int, std::shared_ptr<Connection>> conns_;
std::shared_ptr<std::unordered_set<std::string>> inspectedContexts_;
};
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -1,378 +0,0 @@
/*
* 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 "MessageConverters.h"
#include <cmath>
#include <limits>
#include <folly/Conv.h>
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
namespace h = ::facebook::hermes;
namespace m = ::facebook::hermes::inspector::chrome::message;
constexpr size_t kMaxPreviewProperties = 10;
m::ErrorResponse
m::makeErrorResponse(int id, m::ErrorCode code, const std::string &message) {
m::ErrorResponse resp;
resp.id = id;
resp.code = static_cast<int>(code);
resp.message = message;
return resp;
}
m::OkResponse m::makeOkResponse(int id) {
m::OkResponse resp;
resp.id = id;
return resp;
}
std::string m::stripCachePrevention(const std::string &url) {
std::regex regex("&?cachePrevention=[0-9]*");
return std::regex_replace(url, regex, "");
}
/*
* debugger message conversion helpers
*/
m::debugger::Location m::debugger::makeLocation(
const h::debugger::SourceLocation &loc) {
m::debugger::Location result;
result.scriptId = folly::to<std::string>(loc.fileId);
m::setChromeLocation(result, loc);
return result;
}
m::debugger::CallFrame m::debugger::makeCallFrame(
uint32_t callFrameIndex,
const h::debugger::CallFrameInfo &callFrameInfo,
const h::debugger::LexicalInfo &lexicalInfo,
RemoteObjectsTable &objTable,
jsi::Runtime &runtime,
const facebook::hermes::debugger::ProgramState &state) {
m::debugger::CallFrame result;
result.callFrameId = folly::to<std::string>(callFrameIndex);
result.functionName = callFrameInfo.functionName;
result.location = makeLocation(callFrameInfo.location);
uint32_t scopeCount = lexicalInfo.getScopesCount();
// First we have our local scope (unless we're in the global function)
if (scopeCount > 1) {
m::debugger::Scope scope;
scope.type = "local";
scope.object.objectId = objTable.addScope(
std::make_pair(callFrameIndex, 0), BacktraceObjectGroup);
scope.object.type = "object";
scope.object.className = "Object";
result.scopeChain.emplace_back(std::move(scope));
}
// Then we have zero or more parent closure scopes
for (uint32_t scopeIndex = 1; scopeIndex < scopeCount - 1; scopeIndex++) {
m::debugger::Scope scope;
scope.type = "closure";
// TODO: Get the parent closure's name
scope.name = folly::to<std::string>(scopeIndex);
scope.object.objectId = objTable.addScope(
std::make_pair(callFrameIndex, scopeIndex), BacktraceObjectGroup);
scope.object.type = "object";
scope.object.className = "Object";
result.scopeChain.emplace_back(std::move(scope));
}
// Finally, we always have the global scope
{
m::debugger::Scope scope;
scope.type = "global";
scope.object.objectId =
objTable.addValue(runtime.global(), BacktraceObjectGroup);
scope.object.type = "object";
scope.object.className = "Object";
result.scopeChain.emplace_back(std::move(scope));
}
result.thisObj.type = "object";
result.thisObj.objectId = objTable.addValue(
state.getVariableInfoForThis(callFrameIndex).value, BacktraceObjectGroup);
return result;
}
std::vector<m::debugger::CallFrame> m::debugger::makeCallFrames(
const h::debugger::ProgramState &state,
RemoteObjectsTable &objTable,
jsi::Runtime &runtime) {
const h::debugger::StackTrace &stackTrace = state.getStackTrace();
uint32_t count = stackTrace.callFrameCount();
std::vector<m::debugger::CallFrame> result;
result.reserve(count);
for (uint32_t i = 0; i < count; i++) {
h::debugger::CallFrameInfo callFrameInfo = stackTrace.callFrameForIndex(i);
h::debugger::LexicalInfo lexicalInfo = state.getLexicalInfo(i);
result.emplace_back(
makeCallFrame(i, callFrameInfo, lexicalInfo, objTable, runtime, state));
}
return result;
}
/*
* runtime message conversion helpers
*/
m::runtime::CallFrame m::runtime::makeCallFrame(
const h::debugger::CallFrameInfo &info) {
m::runtime::CallFrame result;
result.functionName = info.functionName;
result.scriptId = folly::to<std::string>(info.location.fileId);
result.url = info.location.fileName;
m::setChromeLocation(result, info.location);
return result;
}
std::vector<m::runtime::CallFrame> m::runtime::makeCallFrames(
const facebook::hermes::debugger::StackTrace &stackTrace) {
std::vector<m::runtime::CallFrame> result;
result.reserve(stackTrace.callFrameCount());
for (size_t i = 0; i < stackTrace.callFrameCount(); i++) {
h::debugger::CallFrameInfo info = stackTrace.callFrameForIndex(i);
result.emplace_back(makeCallFrame(info));
}
return result;
}
m::runtime::ExceptionDetails m::runtime::makeExceptionDetails(
const h::debugger::ExceptionDetails &details) {
m::runtime::ExceptionDetails result;
result.text = details.text;
result.scriptId = folly::to<std::string>(details.location.fileId);
result.url = details.location.fileName;
result.stackTrace = m::runtime::StackTrace();
result.stackTrace->callFrames = makeCallFrames(details.getStackTrace());
m::setChromeLocation(result, details.location);
return result;
}
static m::runtime::PropertyPreview generatePropertyPreview(
facebook::jsi::Runtime &runtime,
const std::string &name,
const facebook::jsi::Value &value) {
m::runtime::PropertyPreview preview;
preview.name = name;
if (value.isUndefined()) {
preview.type = "undefined";
preview.value = "undefined";
} else if (value.isNull()) {
preview.type = "object";
preview.subtype = "null";
preview.value = "null";
} else if (value.isBool()) {
preview.type = "boolean";
preview.value = value.toString(runtime).utf8(runtime);
} else if (value.isNumber()) {
preview.type = "number";
preview.value = value.toString(runtime).utf8(runtime);
} else if (value.isSymbol()) {
preview.type = "symbol";
preview.value = value.toString(runtime).utf8(runtime);
} else if (value.isBigInt()) {
preview.type = "bigint";
preview.value =
value.getBigInt(runtime).toString(runtime).utf8(runtime) + 'n';
} else if (value.isString()) {
preview.type = "string";
preview.value = value.toString(runtime).utf8(runtime);
} else if (value.isObject()) {
jsi::Object obj = value.asObject(runtime);
if (obj.isFunction(runtime)) {
preview.type = "function";
} else if (obj.isArray(runtime)) {
preview.type = "object";
preview.subtype = "array";
preview.value = "Array(" +
std::to_string(obj.getArray(runtime).length(runtime)) + ")";
} else {
preview.type = "object";
preview.value = "Object";
}
} else {
preview.type = "string";
preview.value = "<UnknownValueKind>";
}
return preview;
}
static m::runtime::ObjectPreview generateArrayPreview(
facebook::jsi::Runtime &runtime,
const facebook::jsi::Array &obj) {
m::runtime::ObjectPreview preview{};
preview.type = "object";
preview.subtype = "array";
size_t count = obj.length(runtime);
for (size_t i = 0; i < std::min(kMaxPreviewProperties, count); i++) {
m::runtime::PropertyPreview desc;
std::string indexString = std::to_string(i);
try {
desc = generatePropertyPreview(
runtime, indexString, obj.getValueAtIndex(runtime, i));
} catch (const jsi::JSError &err) {
desc.name = indexString;
desc.type = "string";
desc.value = "<Exception>";
}
preview.properties.push_back(std::move(desc));
}
preview.description =
"Array(" + std::to_string(obj.getArray(runtime).length(runtime)) + ")";
preview.overflow = count > kMaxPreviewProperties;
return preview;
}
static m::runtime::ObjectPreview generateObjectPreview(
facebook::jsi::Runtime &runtime,
const facebook::jsi::Object &obj) {
m::runtime::ObjectPreview preview{};
preview.type = "object";
// n.b. own properties only
jsi::Array propNames =
runtime.global()
.getPropertyAsObject(runtime, "Object")
.getPropertyAsFunction(runtime, "getOwnPropertyNames")
.call(runtime, obj)
.getObject(runtime)
.getArray(runtime);
size_t propCount = propNames.length(runtime);
for (size_t i = 0; i < std::min(kMaxPreviewProperties, propCount); i++) {
jsi::String propName =
propNames.getValueAtIndex(runtime, i).getString(runtime);
m::runtime::PropertyPreview desc;
try {
// Currently, we fetch the property even if it runs code.
// Chrome instead detects getters and makes you click to invoke.
desc = generatePropertyPreview(
runtime, propName.utf8(runtime), obj.getProperty(runtime, propName));
} catch (const jsi::JSError &err) {
desc.name = propName.utf8(runtime);
desc.type = "string";
desc.value = "<Exception>";
}
preview.properties.push_back(std::move(desc));
}
preview.description = "Object";
preview.overflow = propCount > kMaxPreviewProperties;
return preview;
}
m::runtime::RemoteObject m::runtime::makeRemoteObject(
facebook::jsi::Runtime &runtime,
const facebook::jsi::Value &value,
RemoteObjectsTable &objTable,
const std::string &objectGroup,
bool byValue,
bool generatePreview) {
m::runtime::RemoteObject result;
if (value.isUndefined()) {
result.type = "undefined";
} else if (value.isNull()) {
result.type = "object";
result.subtype = "null";
result.value = "null";
} else if (value.isBool()) {
result.type = "boolean";
result.value = value.getBool();
} else if (value.isNumber()) {
double numberValue = value.getNumber();
result.type = "number";
if (std::isnan(numberValue)) {
result.description = result.unserializableValue = "NaN";
} else if (numberValue == -std::numeric_limits<double>::infinity()) {
result.description = result.unserializableValue = "-Infinity";
} else if (numberValue == std::numeric_limits<double>::infinity()) {
result.description = result.unserializableValue = "Infinity";
} else if (numberValue == 0.0 && std::signbit(numberValue)) {
result.description = result.unserializableValue = "-0";
} else {
result.value = numberValue;
}
} else if (value.isString()) {
result.type = "string";
result.value = value.getString(runtime).utf8(runtime);
} else if (value.isSymbol()) {
result.type = "symbol";
auto sym = value.getSymbol(runtime);
result.description = sym.toString(runtime);
result.objectId =
objTable.addValue(jsi::Value(std::move(sym)), objectGroup);
} else if (value.isBigInt()) {
auto strRepresentation =
value.getBigInt(runtime).toString(runtime).utf8(runtime) + 'n';
result.description = result.unserializableValue = strRepresentation;
} else if (value.isObject()) {
jsi::Object obj = value.getObject(runtime);
if (obj.isFunction(runtime)) {
result.type = "function";
result.value = "";
} else if (obj.isArray(runtime)) {
auto array = obj.getArray(runtime);
size_t arrayCount = array.length(runtime);
result.type = "object";
result.subtype = "array";
result.className = "Array";
result.description = "Array(" + folly::to<std::string>(arrayCount) + ")";
if (generatePreview) {
result.preview = generateArrayPreview(runtime, array);
}
} else {
result.type = "object";
result.description = result.className = "Object";
if (generatePreview) {
result.preview = generateObjectPreview(runtime, obj);
}
}
if (byValue) {
// FIXME: JSI currently does not handle cycles and functions well here
result.value = jsi::dynamicFromValue(runtime, value);
} else {
result.objectId =
objTable.addValue(jsi::Value(std::move(obj)), objectGroup);
}
}
return result;
}
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -1,136 +0,0 @@
/*
* 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.
*/
#pragma once
#include <regex>
#include <string>
#include <vector>
#include <hermes/DebuggerAPI.h>
#include <hermes/hermes.h>
#include <hermes/inspector/chrome/MessageTypes.h>
#include <hermes/inspector/chrome/RemoteObjectsTable.h>
#include <jsi/JSIDynamic.h>
#include <jsi/jsi.h>
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
namespace message {
std::string stripCachePrevention(const std::string &url);
template <typename T>
void setHermesLocation(
facebook::hermes::debugger::SourceLocation &hermesLoc,
const T &chromeLoc,
const std::vector<std::string> &parsedScripts) {
hermesLoc.line = chromeLoc.lineNumber + 1;
if (chromeLoc.columnNumber.has_value()) {
if (chromeLoc.columnNumber.value() == 0) {
// TODO: When CDTP sends a column number of 0, we send Hermes a column
// number of 1. For some reason, this causes Hermes to not be
// able to resolve breakpoints.
hermesLoc.column = ::facebook::hermes::debugger::kInvalidLocation;
} else {
hermesLoc.column = chromeLoc.columnNumber.value() + 1;
}
}
if (chromeLoc.url.has_value()) {
hermesLoc.fileName = stripCachePrevention(chromeLoc.url.value());
} else if (chromeLoc.urlRegex.has_value()) {
const std::regex regex(stripCachePrevention(chromeLoc.urlRegex.value()));
auto it = parsedScripts.rbegin();
// We currently only support one physical breakpoint per location, so
// search backwards so that we find the latest matching file.
while (it != parsedScripts.rend()) {
if (std::regex_match(*it, regex)) {
hermesLoc.fileName = *it;
break;
}
it++;
}
}
}
template <typename T>
void setChromeLocation(
T &chromeLoc,
const facebook::hermes::debugger::SourceLocation &hermesLoc) {
if (hermesLoc.line != facebook::hermes::debugger::kInvalidLocation) {
chromeLoc.lineNumber = hermesLoc.line - 1;
}
if (hermesLoc.column != facebook::hermes::debugger::kInvalidLocation) {
chromeLoc.columnNumber = hermesLoc.column - 1;
}
}
/// ErrorCode magic numbers match JSC's (see InspectorBackendDispatcher.cpp)
enum class ErrorCode {
ParseError = -32700,
InvalidRequest = -32600,
MethodNotFound = -32601,
InvalidParams = -32602,
InternalError = -32603,
ServerError = -32000
};
ErrorResponse
makeErrorResponse(int id, ErrorCode code, const std::string &message);
OkResponse makeOkResponse(int id);
namespace debugger {
Location makeLocation(const facebook::hermes::debugger::SourceLocation &loc);
CallFrame makeCallFrame(
uint32_t callFrameIndex,
const facebook::hermes::debugger::CallFrameInfo &callFrameInfo,
const facebook::hermes::debugger::LexicalInfo &lexicalInfo,
facebook::hermes::inspector::chrome::RemoteObjectsTable &objTable,
jsi::Runtime &runtime,
const facebook::hermes::debugger::ProgramState &state);
std::vector<CallFrame> makeCallFrames(
const facebook::hermes::debugger::ProgramState &state,
facebook::hermes::inspector::chrome::RemoteObjectsTable &objTable,
jsi::Runtime &runtime);
} // namespace debugger
namespace runtime {
CallFrame makeCallFrame(const facebook::hermes::debugger::CallFrameInfo &info);
std::vector<CallFrame> makeCallFrames(
const facebook::hermes::debugger::StackTrace &stackTrace);
ExceptionDetails makeExceptionDetails(
const facebook::hermes::debugger::ExceptionDetails &details);
RemoteObject makeRemoteObject(
facebook::jsi::Runtime &runtime,
const facebook::jsi::Value &value,
facebook::hermes::inspector::chrome::RemoteObjectsTable &objTable,
const std::string &objectGroup,
bool byValue = false,
bool generatePreview = false);
} // namespace runtime
} // namespace message
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -1,74 +0,0 @@
/*
* 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.
*/
#pragma once
#include <memory>
#include <string>
#include <unordered_map>
#include <folly/Try.h>
#include <folly/dynamic.h>
#include <folly/json.h>
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
namespace message {
struct RequestHandler;
/// Serializable is an interface for objects that can be serialized to and from
/// JSON.
struct Serializable {
virtual ~Serializable() = default;
virtual folly::dynamic toDynamic() const = 0;
std::string toJson() const {
return folly::toJson(toDynamic());
}
};
/// Requests are sent from the debugger to the target.
struct Request : public Serializable {
static std::unique_ptr<Request> fromJsonThrowOnError(const std::string &str);
static folly::Try<std::unique_ptr<Request>> fromJson(const std::string &str);
Request() = default;
explicit Request(std::string method) : method(method) {}
// accept dispatches to the appropriate handler method in RequestHandler based
// on the type of the request.
virtual void accept(RequestHandler &handler) const = 0;
int id = 0;
std::string method;
};
/// Responses are sent from the target to the debugger in response to a Request.
struct Response : public Serializable {
Response() = default;
int id = 0;
};
/// Notifications are sent from the target to the debugger. This is used to
/// notify the debugger about events that occur in the target, e.g. stopping
/// at a breakpoint.
struct Notification : public Serializable {
Notification() = default;
explicit Notification(std::string method) : method(method) {}
std::string method;
};
} // namespace message
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,190 +0,0 @@
/*
* 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.
*/
#pragma once
#include <hermes/inspector/chrome/MessageInterfaces.h>
#include <memory>
#include <optional>
#include <type_traits>
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
namespace message {
using dynamic = folly::dynamic;
template <typename T>
using optional = std::optional<T>;
template <typename>
struct is_vector : std::false_type {};
template <typename T>
struct is_vector<std::vector<T>> : std::true_type {};
/// valueFromDynamic
template <typename T>
typename std::enable_if<std::is_base_of<Serializable, T>::value, T>::type
valueFromDynamic(const dynamic &obj) {
return T(obj);
}
template <typename T>
typename std::enable_if<std::is_integral<T>::value, T>::type valueFromDynamic(
const dynamic &obj) {
return obj.asInt();
}
template <typename T>
typename std::enable_if<std::is_floating_point<T>::value, T>::type
valueFromDynamic(const dynamic &obj) {
return obj.asDouble();
}
template <typename T>
typename std::enable_if<std::is_same<T, std::string>::value, T>::type
valueFromDynamic(const dynamic &obj) {
return obj.asString();
}
template <typename T>
typename std::enable_if<std::is_same<T, dynamic>::value, T>::type
valueFromDynamic(const dynamic &obj) {
return obj;
}
template <typename T>
typename std::enable_if<is_vector<T>::value, T>::type valueFromDynamic(
const dynamic &items) {
T result;
result.reserve(items.size());
for (const auto &item : items) {
result.push_back(valueFromDynamic<typename T::value_type>(item));
}
return result;
}
/// assign(lhs, obj, key) is a wrapper for:
///
/// lhs = obj[key]
///
/// It mainly exists so that we can choose the right version of valueFromDynamic
/// based on the type of lhs.
template <typename T, typename U>
void assign(T &lhs, const dynamic &obj, const U &key) {
lhs = valueFromDynamic<T>(obj.at(key));
}
template <typename T, typename U>
void assign(optional<T> &lhs, const dynamic &obj, const U &key) {
auto it = obj.find(key);
if (it != obj.items().end()) {
lhs = valueFromDynamic<T>(it->second);
} else {
lhs.reset();
}
}
template <typename T, typename U>
void assign(std::unique_ptr<T> &lhs, const dynamic &obj, const U &key) {
auto it = obj.find(key);
if (it != obj.items().end()) {
lhs = std::make_unique<T>(valueFromDynamic<T>(it->second));
} else {
lhs.reset();
}
}
template <typename T, typename U, typename D>
void assign(
std::unique_ptr<T, std::function<void(D *)>> &lhs,
const dynamic &obj,
const U &key) {
auto it = obj.find(key);
if (it != obj.items().end()) {
lhs = std::make_unique<T>(valueFromDynamic<T>(it->second));
} else {
lhs.reset();
}
}
/// valueToDynamic
inline dynamic valueToDynamic(const Serializable &value) {
return value.toDynamic();
}
template <typename T>
typename std::enable_if<!std::is_base_of<Serializable, T>::value, dynamic>::type
valueToDynamic(const T &item) {
return dynamic(item);
}
template <typename T>
dynamic valueToDynamic(const std::vector<T> &items) {
dynamic result = dynamic::array;
for (const auto &item : items) {
result.push_back(valueToDynamic(item));
}
return result;
}
/// put(obj, key, value) is a wrapper for:
///
/// obj[key] = valueToDynamic(value);
template <typename K, typename V>
void put(dynamic &obj, const K &key, const V &value) {
obj[key] = valueToDynamic(value);
}
template <typename K, typename V>
void put(dynamic &obj, const K &key, const optional<V> &optValue) {
if (optValue.has_value()) {
obj[key] = valueToDynamic(optValue.value());
} else {
obj.erase(key);
}
}
template <typename K, typename V>
void put(dynamic &obj, const K &key, const std::unique_ptr<V> &ptr) {
if (ptr.get()) {
obj[key] = valueToDynamic(*ptr);
} else {
obj.erase(key);
}
}
template <typename K, typename V, typename D>
void put(
dynamic &obj,
const K &key,
const std::unique_ptr<V, std::function<void(D *)>> &ptr) {
if (ptr.get()) {
obj[key] = valueToDynamic(*ptr);
} else {
obj.erase(key);
}
}
template <typename T>
void deleter(T *p) {
delete p;
}
} // namespace message
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -1,38 +0,0 @@
/*
* 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 "Registration.h"
#include "ConnectionDemux.h"
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
namespace {
ConnectionDemux &demux() {
static ConnectionDemux instance{facebook::react::getInspectorInstance()};
return instance;
}
} // namespace
DebugSessionToken enableDebugging(
std::unique_ptr<RuntimeAdapter> adapter,
const std::string &title) {
return demux().enableDebugging(std::move(adapter), title);
}
void disableDebugging(DebugSessionToken session) {
demux().disableDebugging(session);
}
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -1,43 +0,0 @@
/*
* 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.
*/
#pragma once
#include <memory>
#include <string>
#include <hermes/hermes.h>
#include <hermes/inspector/RuntimeAdapter.h>
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
using DebugSessionToken = int;
/*
* enableDebugging adds this runtime to the list of debuggable JS targets
* (called "pages" in the higher-level React Native API) in this process. It
* should be called before any JS runs in the runtime. The returned token
* can be used to disable debugging for this runtime.
*/
extern DebugSessionToken enableDebugging(
std::unique_ptr<RuntimeAdapter> adapter,
const std::string &title);
/*
* disableDebugging removes this runtime from the list of debuggable JS targets
* in this process. The runtime to remove is identified by the token returned
* from enableDebugging.
*/
extern void disableDebugging(DebugSessionToken session);
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -1,146 +0,0 @@
/*
* 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 "RemoteObjectsTable.h"
#include <cstdlib>
#include <folly/Conv.h>
namespace {
bool isScopeId(int64_t id) {
return id < 0;
}
bool isValueId(int64_t id) {
return id > 0;
}
std::string toObjId(int64_t id) {
return folly::to<std::string>(id);
}
int64_t toId(const std::string &objId) {
return atoll(objId.c_str());
}
} // namespace
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
const char *BacktraceObjectGroup = "backtrace";
const char *ConsoleObjectGroup = "console";
RemoteObjectsTable::RemoteObjectsTable() = default;
RemoteObjectsTable::~RemoteObjectsTable() = default;
std::string RemoteObjectsTable::addScope(
std::pair<uint32_t, uint32_t> frameAndScopeIndex,
const std::string &objectGroup) {
int64_t id = scopeId_--;
scopes_[id] = frameAndScopeIndex;
if (!objectGroup.empty()) {
idToGroup_[id] = objectGroup;
groupToIds_[objectGroup].push_back(id);
}
return toObjId(id);
}
std::string RemoteObjectsTable::addValue(
::facebook::jsi::Value value,
const std::string &objectGroup) {
int64_t id = valueId_++;
values_[id] = std::move(value);
if (!objectGroup.empty()) {
idToGroup_[id] = objectGroup;
groupToIds_[objectGroup].push_back(id);
}
return toObjId(id);
}
const std::pair<uint32_t, uint32_t> *RemoteObjectsTable::getScope(
const std::string &objId) const {
int64_t id = toId(objId);
if (!isScopeId(id)) {
return nullptr;
}
auto it = scopes_.find(id);
if (it == scopes_.end()) {
return nullptr;
}
return &it->second;
}
const ::facebook::jsi::Value *RemoteObjectsTable::getValue(
const std::string &objId) const {
int64_t id = toId(objId);
if (!isValueId(id)) {
return nullptr;
}
auto it = values_.find(id);
if (it == values_.end()) {
return nullptr;
}
return &it->second;
}
std::string RemoteObjectsTable::getObjectGroup(const std::string &objId) const {
int64_t id = toId(objId);
auto it = idToGroup_.find(id);
if (it == idToGroup_.end()) {
return "";
}
return it->second;
}
void RemoteObjectsTable::releaseObject(int64_t id) {
if (isScopeId(id)) {
scopes_.erase(id);
} else if (isValueId(id)) {
values_.erase(id);
}
}
void RemoteObjectsTable::releaseObject(const std::string &objId) {
int64_t id = toId(objId);
releaseObject(id);
}
void RemoteObjectsTable::releaseObjectGroup(const std::string &objectGroup) {
auto it = groupToIds_.find(objectGroup);
if (it == groupToIds_.end()) {
return;
}
const auto &ids = it->second;
for (int64_t id : ids) {
releaseObject(id);
}
groupToIds_.erase(it);
}
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -1,124 +0,0 @@
/*
* 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.
*/
#pragma once
#include <cstdint>
#include <unordered_map>
#include <utility>
#include <vector>
#include <jsi/jsi.h>
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
/// Well-known object group names
/**
* Objects created as a result of the Debugger.paused notification (e.g. scope
* objects) are placed in the "backtrace" object group. This object group is
* cleared when the VM resumes.
*/
extern const char *BacktraceObjectGroup;
/**
* Objects that are created as a result of a console evaluation are placed in
* the "console" object group. This object group is cleared when the client
* clears the console.
*/
extern const char *ConsoleObjectGroup;
/**
* RemoteObjectsTable manages the mapping of string object ids to scope metadata
* or actual JSI objects. The debugger vends these ids to the client so that the
* client can perform operations on the ids (e.g. enumerate properties on the
* object backed by the id). See Runtime.RemoteObjectId in the CDT docs for
* more details.
*
* Note that object handles are not ref-counted. Suppose an object foo is mapped
* to object id "objId" and is also in object group "objGroup". Then *either* of
* `releaseObject("objId")` or `releaseObjectGroup("objGroup")` will remove foo
* from the table. This matches the behavior of object groups in CDT.
*/
class RemoteObjectsTable {
public:
RemoteObjectsTable();
~RemoteObjectsTable();
RemoteObjectsTable(const RemoteObjectsTable &) = delete;
RemoteObjectsTable &operator=(const RemoteObjectsTable &) = delete;
/**
* addScope adds the provided (frameIndex, scopeIndex) mapping to the table.
* If objectGroup is non-empty, then the scope object is also added to that
* object group for releasing via releaseObjectGroup. Returns an object id.
*/
std::string addScope(
std::pair<uint32_t, uint32_t> frameAndScopeIndex,
const std::string &objectGroup);
/**
* addValue adds the JSI value to the table. If objectGroup is non-empty, then
* the scope object is also added to that object group for releasing via
* releaseObjectGroup. Returns an object id.
*/
std::string addValue(
::facebook::jsi::Value value,
const std::string &objectGroup);
/**
* Retrieves the (frameIndex, scopeIndex) associated with this object id, or
* nullptr if no mapping exists. The pointer stays valid as long as you only
* call const methods on this class.
*/
const std::pair<uint32_t, uint32_t> *getScope(const std::string &objId) const;
/**
* Retrieves the JSI value associated with this object id, or nullptr if no
* mapping exists. The pointer stays valid as long as you only call const
* methods on this class.
*/
const ::facebook::jsi::Value *getValue(const std::string &objId) const;
/**
* Retrieves the object group that this object id is in, or empty string if it
* isn't in an object group. The returned pointer is only guaranteed to be
* valid until the next call to this class.
*/
std::string getObjectGroup(const std::string &objId) const;
/**
* Removes the scope or JSI value backed by the provided object ID from the
* table.
*/
void releaseObject(const std::string &objId);
/**
* Removes all objects that are part of the provided object group from the
* table.
*/
void releaseObjectGroup(const std::string &objectGroup);
private:
void releaseObject(int64_t id);
int64_t scopeId_ = -1;
int64_t valueId_ = 1;
std::unordered_map<int64_t, std::pair<uint32_t, uint32_t>> scopes_;
std::unordered_map<int64_t, ::facebook::jsi::Value> values_;
std::unordered_map<int64_t, std::string> idToGroup_;
std::unordered_map<std::string, std::vector<int64_t>> groupToIds_;
};
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -1,274 +0,0 @@
/*
* 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 <cstdio>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <memory>
#include <thread>
#include <utility>
#include <getopt.h>
#include <folly/json.h>
#include <hermes/hermes.h>
#include <hermes/inspector/RuntimeAdapter.h>
#include <hermes/inspector/chrome/Connection.h>
using ::facebook::react::IRemoteConnection;
namespace fbhermes = ::facebook::hermes;
static const char *usageMessage = R"(hermes-chrome-debug-server script.js
Uses Hermes to evaluate script.js within a debugging session. The process will
wait for Chrome DevTools Protocol requests on stdin and writes responses and
events to stdout.
This can be used with a WebSocket bridge to host a Chrome DevTools Protocol
debug server. For instance, running this:
websocketd --port=9999 hermes-chrome-debug-server script.js
will run a WebSocket server on port 9999 that debugs script.js in Hermes. Chrome
can connect to this debugging session using a URL like this:
devtools://devtools/bundled/inspector.html?experiments=false&v8only=true&ws=127.0.0.1:9999
Options:
-l, --log: path to a file with pretty-printed protocol logs
-h, --help: this message
)";
static void usage() {
fputs(usageMessage, stderr);
exit(1);
}
/// Truncate UTF8 string \p s to be at most \p len bytes long. If a multi-byte
/// sequence crosses the limit (len), it will be wholly omitted from the
/// output. If the output is truncated, it will be suffixed with "...".
///
/// \pre len must be strictly greater than the length of the truncation suffix
/// (three characters), so there is something left after truncation.
static void truncate(std::string &s, size_t len) {
static constexpr char suffix[] = "...";
static const size_t suflen = strlen(suffix);
assert(len > suflen);
if (s.size() <= len) {
return;
}
// Iterate back from the edge to pop off continuation bytes.
ssize_t last = len - suflen;
while (last > 0 && (s[last] & 0xC0) == 0x80)
--last;
// Copy in the suffix.
strncpy(&s[last], suffix, suflen);
// Trim the excess.
s.resize(last + suflen);
}
/// Traverse the structure of \p d, truncating all the strings found,
/// (excluding object keys) to at most \p len bytes long using the definition
/// of truncate above.
static void truncateStringsIn(folly::dynamic &d, size_t len) {
switch (d.type()) {
case folly::dynamic::STRING:
truncate(d.getString(), len);
break;
case folly::dynamic::ARRAY:
for (auto &child : d) {
truncateStringsIn(child, len);
}
break;
case folly::dynamic::OBJECT:
for (auto &kvp : d.items()) {
truncateStringsIn(kvp.second, len);
}
break;
default:
/* nop */
break;
}
}
/// Pretty print the JSON blob contained within \p str, by introducing
/// parsing it and pretty printing it. Large string values (larger than 512
/// characters) are truncated.
///
/// \pre str contains a valid JSON blob.
static std::string prettify(const std::string &str) {
constexpr size_t MAX_LINE_LEN = 512;
try {
folly::dynamic obj = folly::parseJson(str);
truncateStringsIn(obj, MAX_LINE_LEN);
return folly::toPrettyJson(obj);
} catch (...) {
// pass
}
if (str.size() > MAX_LINE_LEN) {
std::string cpy = str;
truncate(cpy, MAX_LINE_LEN);
return cpy;
}
return str;
}
static FILE *logFile = stderr;
static void setLogFilePath(const char *path) {
logFile = fopen(path, "w");
if (logFile == nullptr) {
perror("fopen couldn't open log file");
exit(1);
}
}
static void log(const std::string &str, bool isReq) {
fprintf(logFile, "%s %s\n\n", isReq ? "=>" : "<=", prettify(str).c_str());
}
static void logRequest(const std::string &str) {
log(str, true);
}
static void sendResponse(const std::string &str) {
log(str, false);
printf("%s\n", str.c_str());
}
static std::string readScriptSource(const char *path) {
std::ifstream stream(path);
return std::string{
std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>()};
}
static std::string getUrl(const char *path) {
char absPath[PATH_MAX] = {};
realpath(path, absPath);
return std::string("file://") + absPath;
}
static bool handleScriptSourceRequest(
const std::string &reqStr,
const std::string &scriptSource) {
auto req = folly::parseJson(reqStr);
if (req.at("method") == "Debugger.getScriptSource") {
folly::dynamic result = folly::dynamic::object;
result["scriptSource"] = scriptSource;
folly::dynamic resp = folly::dynamic::object;
resp["id"] = req.at("id");
resp["result"] = std::move(result);
sendResponse(folly::toJson(resp));
return true;
}
return false;
}
class RemoteConnection : public IRemoteConnection {
public:
void onMessage(std::string message) override {
sendResponse(message);
}
void onDisconnect() override {}
};
static void runDebuggerLoop(
fbhermes::inspector::chrome::Connection &conn,
std::string scriptSource) {
conn.connect(std::make_unique<RemoteConnection>());
std::string line;
while (std::getline(std::cin, line)) {
logRequest(line);
if (!handleScriptSourceRequest(line, scriptSource)) {
conn.sendMessage(line);
}
}
}
static void runScript(const std::string &scriptSource, const std::string &url) {
std::shared_ptr<fbhermes::HermesRuntime> runtime(
fbhermes::makeHermesRuntime(::hermes::vm::RuntimeConfig::Builder()
.withEnableSampleProfiling(true)
.build()));
auto adapter =
std::make_unique<fbhermes::inspector::SharedRuntimeAdapter>(runtime);
fbhermes::inspector::chrome::Connection conn(
std::move(adapter), "hermes-chrome-debug-server");
std::thread debuggerLoop(runDebuggerLoop, std::ref(conn), scriptSource);
fbhermes::HermesRuntime::DebugFlags flags{};
runtime->debugJavaScript(scriptSource, url, flags);
debuggerLoop.join();
}
int main(int argc, char **argv) {
const char *shortOpts = "l:h";
const option longOpts[] = {
{"log", 1, nullptr, 'l'},
{"help", 0, nullptr, 'h'},
{nullptr, 0, nullptr, 0}};
while (true) {
int opt = getopt_long(argc, argv, shortOpts, longOpts, nullptr);
if (opt == -1) {
break;
}
switch (opt) {
case 'l':
setLogFilePath(optarg);
break;
case 'h':
usage();
break;
default:
fprintf(stderr, "Unrecognized option: %c\n", opt);
usage();
break;
}
}
setbuf(logFile, nullptr);
setbuf(stdout, nullptr);
if (optind + 1 != argc) {
usage();
}
const char *path = argv[optind];
std::string scriptSource = readScriptSource(path);
std::string url = getUrl(path);
runScript(scriptSource, url);
fclose(logFile);
return 0;
}
@@ -1,166 +0,0 @@
/*
* 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 "AsyncHermesRuntime.h"
#include <functional>
#include <stdexcept>
#include <thread>
#include <glog/logging.h>
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
namespace detail = facebook::hermes::inspector::detail;
AsyncHermesRuntime::AsyncHermesRuntime(bool veryLazy)
: executor_(
std::make_unique<detail::SerialExecutor>("async-hermes-runtime")) {
using namespace std::placeholders;
auto builder = ::hermes::vm::RuntimeConfig::Builder();
if (veryLazy) {
builder.withCompilationMode(::hermes::vm::ForceLazyCompilation);
}
runtime_ = facebook::hermes::makeHermesRuntime(builder.build());
runtime_->global().setProperty(
*runtime_,
"shouldStop",
jsi::Function::createFromHostFunction(
*runtime_,
jsi::PropNameID::forAscii(*runtime_, "shouldStop"),
0,
std::bind(&AsyncHermesRuntime::shouldStop, this, _1, _2, _3, _4)));
runtime_->global().setProperty(
*runtime_,
"storeValue",
jsi::Function::createFromHostFunction(
*runtime_,
jsi::PropNameID::forAscii(*runtime_, "storeValue"),
0,
std::bind(&AsyncHermesRuntime::storeValue, this, _1, _2, _3, _4)));
}
AsyncHermesRuntime::~AsyncHermesRuntime() {
stop();
wait();
}
void AsyncHermesRuntime::executeScriptAsync(
const std::string &script,
const std::string &url,
HermesRuntime::DebugFlags flags) {
int scriptId = rand();
LOG(INFO) << "AsyncHermesRuntime will execute script with id: " << scriptId
<< ", contents: " << script;
executor_->add([this, script, url, flags, scriptId] {
LOG(INFO) << "AsyncHermesRuntime executing script id " << scriptId
<< " in background";
try {
runtime_->debugJavaScript(script, url, flags);
} catch (jsi::JSError &error) {
LOG(INFO) << "AsyncHermesRuntime JSError " << error.getMessage();
thrownExceptions_.push_back(error.getMessage());
}
LOG(INFO) << "AsyncHermesRuntime finished executing script id " << scriptId;
});
}
void AsyncHermesRuntime::start() {
LOG(INFO) << "AsyncHermesRuntime: set stop flag false";
stopFlag_.store(false);
}
void AsyncHermesRuntime::stop() {
LOG(INFO) << "AsyncHermesRuntime: set stop flag true";
stopFlag_.store(true);
}
folly::Future<jsi::Value> AsyncHermesRuntime::getStoredValue() {
return storedValue_.getFuture();
}
bool AsyncHermesRuntime::hasStoredValue() {
return storedValue_.isFulfilled();
}
jsi::Value AsyncHermesRuntime::awaitStoredValue(
std::chrono::milliseconds timeout) {
return getStoredValue().get(timeout);
}
void AsyncHermesRuntime::wait(std::chrono::milliseconds timeout) {
LOG(INFO) << "AsyncHermesRuntime wait requested";
auto promise = std::make_shared<folly::Promise<bool>>();
auto future = promise->getFuture();
executor_->add([promise] {
LOG(INFO) << "AsyncHermesRuntime wait resolved";
promise->setValue(true);
});
std::move(future).get(timeout);
}
jsi::Value AsyncHermesRuntime::shouldStop(
jsi::Runtime &runtime,
const jsi::Value &thisVal,
const jsi::Value *args,
size_t count) {
return stopFlag_.load() ? jsi::Value(true) : jsi::Value(false);
}
jsi::Value AsyncHermesRuntime::storeValue(
jsi::Runtime &runtime,
const jsi::Value &thisVal,
const jsi::Value *args,
size_t count) {
if (count > 0) {
storedValue_.setValue(jsi::Value(runtime, args[0]));
}
return jsi::Value();
}
size_t AsyncHermesRuntime::getNumberOfExceptions() {
return thrownExceptions_.size();
}
std::string AsyncHermesRuntime::getLastThrownExceptionMessage() {
return thrownExceptions_.back();
}
void AsyncHermesRuntime::registerForProfilingInExecutor() {
// Sampling profiler registration needs to happen in the thread where JS runs.
folly::via(executor_.get(), [runtime = runtime_]() {
runtime->registerForProfiling();
});
// Wait until the executor is registered for profiling.
wait();
}
void AsyncHermesRuntime::unregisterForProfilingInExecutor() {
// Sampling profiler deregistration needs to happen in the thread where JS
// runs.
folly::via(executor_.get(), [runtime = runtime_]() {
runtime->unregisterForProfiling();
});
// Wait until the executor is unregistered for profiling.
wait();
}
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -1,148 +0,0 @@
/*
* 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 <atomic>
#include <chrono>
#include <memory>
#include <mutex>
#include <folly/futures/Future.h>
#include <hermes/hermes.h>
#include <hermes/inspector/detail/SerialExecutor.h>
#include <jsi/jsi.h>
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
/**
* AsyncHermesRuntime is a helper class that runs JS scripts in a Hermes VM on
* a separate thread. This is useful for tests that want to test running JS
* in a multithreaded environment.
*/
class AsyncHermesRuntime {
public:
// Create a runtime. If veryLazy, configure the runtime to use completely
// lazy compilation.
AsyncHermesRuntime(bool veryLazy = false);
~AsyncHermesRuntime();
std::shared_ptr<HermesRuntime> runtime() {
return runtime_;
}
/**
* stop sets the stop flag on this instance. JS scripts can get the current
* value of the stop flag by calling the global shouldStop() function.
*/
void stop();
/**
* start unsets the stop flag on this instance. JS scripts can get the current
* value of the stop flag by calling the global shouldStop() function.
*/
void start();
/**
* getStoredValue returns a future that is fulfilled with the value passed in
* to storeValue() by the JS script.
*/
folly::Future<jsi::Value> getStoredValue();
/**
* hasStoredValue returns whether or not a value has been stored yet
*/
bool hasStoredValue();
/**
* awaitStoredValue is a helper for getStoredValue that returns the value
* synchronously rather than in a future.
*/
jsi::Value awaitStoredValue(
std::chrono::milliseconds timeout = std::chrono::milliseconds(2500));
/**
* executeScriptAsync evaluates JS in the underlying Hermes runtime on a
* separate thread.
*
* This method should be called at most once during the lifetime of an
* AsyncHermesRuntime instance.
*/
void executeScriptAsync(
const std::string &str,
const std::string &url = "url",
facebook::hermes::HermesRuntime::DebugFlags flags =
facebook::hermes::HermesRuntime::DebugFlags{});
/**
* wait blocks until all previous executeScriptAsync calls finish.
*/
void wait(
std::chrono::milliseconds timeout = std::chrono::milliseconds(2500));
/**
* returns the number of thrown exceptions.
*/
size_t getNumberOfExceptions();
/**
* returns the message of the last thrown exception.
*/
std::string getLastThrownExceptionMessage();
/**
* registers the runtime for profiling in the executor thread.
*/
void registerForProfilingInExecutor();
/**
* unregisters the runtime for profiling in the executor thread.
*/
void unregisterForProfilingInExecutor();
private:
jsi::Value shouldStop(
jsi::Runtime &runtime,
const jsi::Value &thisVal,
const jsi::Value *args,
size_t count);
jsi::Value storeValue(
jsi::Runtime &runtime,
const jsi::Value &thisVal,
const jsi::Value *args,
size_t count);
std::shared_ptr<HermesRuntime> runtime_;
std::unique_ptr<folly::Executor> executor_;
std::atomic<bool> stopFlag_{};
folly::Promise<jsi::Value> storedValue_;
std::vector<std::string> thrownExceptions_;
};
/// RAII-style class dealing with sampling profiler registration in tests. This
/// is especially important in tests -- if any test failure is caused by an
/// uncaught exception, stack unwinding will destroy a VM registered for
/// profiling in a thread that's not the one where registration happened, which
/// will lead to a hermes fatal error. Using this RAII class ensure that the
/// proper test failure cause is reported.
struct SamplingProfilerRAII {
explicit SamplingProfilerRAII(AsyncHermesRuntime &rt) : runtime_(rt) {
runtime_.registerForProfilingInExecutor();
}
~SamplingProfilerRAII() {
runtime_.unregisterForProfilingInExecutor();
}
AsyncHermesRuntime &runtime_;
};
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -1,147 +0,0 @@
/*
* 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 <chrono>
#include <condition_variable>
#include <mutex>
#include <string>
#include <unordered_map>
#include <vector>
#include <gtest/gtest.h>
#include <hermes/hermes.h>
#include <hermes/inspector/chrome/ConnectionDemux.h>
#include <jsinspector/InspectorInterfaces.h>
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
using ::facebook::react::IInspector;
using ::facebook::react::InspectorPage;
using ::facebook::react::IRemoteConnection;
namespace {
std::unordered_map<int, std::string> makePageMap(
const std::vector<InspectorPage> &pages) {
std::unordered_map<int, std::string> pageMap;
for (auto &page : pages) {
pageMap[page.id] = page.title;
}
return pageMap;
}
void expectPages(
IInspector &inspector,
const std::unordered_map<int, std::string> &expected) {
auto pages = makePageMap(inspector.getPages());
EXPECT_EQ(pages, expected);
}
class TestRemoteConnection : public IRemoteConnection {
public:
class Data {
public:
void expectDisconnected() {
std::unique_lock<std::mutex> lock(mutex_);
cv_.wait_for(
lock, std::chrono::milliseconds(2500), [&] { return !connected_; });
EXPECT_FALSE(connected_);
}
void setDisconnected() {
std::scoped_lock lock(mutex_);
connected_ = false;
cv_.notify_one();
}
private:
std::mutex mutex_;
std::condition_variable cv_;
bool connected_{true};
};
TestRemoteConnection() : data_(std::make_shared<Data>()) {}
~TestRemoteConnection() {}
void onMessage(std::string message) override {}
void onDisconnect() override {
data_->setDisconnected();
}
std::shared_ptr<Data> getData() {
return data_;
}
private:
std::shared_ptr<Data> data_;
};
}; // namespace
TEST(ConnectionDemuxTests, TestEnableDisable) {
std::shared_ptr<HermesRuntime> runtime1(
facebook::hermes::makeHermesRuntime());
std::shared_ptr<HermesRuntime> runtime2(
facebook::hermes::makeHermesRuntime());
auto inspector = facebook::react::makeTestInspectorInstance();
ConnectionDemux demux{*inspector};
int id1 = demux.enableDebugging(
std::make_unique<SharedRuntimeAdapter>(runtime1), "page1");
int id2 = demux.enableDebugging(
std::make_unique<SharedRuntimeAdapter>(runtime2), "page2");
expectPages(*inspector, {{id1, "page1"}, {id2, "page2"}});
auto remoteConn1 = std::make_unique<TestRemoteConnection>();
auto remoteData1 = remoteConn1->getData();
auto localConn1 = inspector->connect(id1, std::move(remoteConn1));
EXPECT_NE(localConn1.get(), nullptr);
{
// If we connect to the same page id again without disconnecting, we should
// get null
auto remoteConn = std::make_unique<TestRemoteConnection>();
auto localConn = inspector->connect(id1, std::move(remoteConn));
EXPECT_EQ(localConn.get(), nullptr);
}
auto remoteConn2 = std::make_unique<TestRemoteConnection>();
auto remoteData2 = remoteConn2->getData();
auto localConn2 = inspector->connect(id2, std::move(remoteConn2));
EXPECT_NE(localConn2.get(), nullptr);
// Disable debugging on runtime2. This should remove its page from the list
// and call onDisconnect on its remoteConn
demux.disableDebugging(id2);
expectPages(*inspector, {{id1, "page1"}});
remoteData2->expectDisconnected();
// Disconnect conn1. Its page should still be in the page list and
// onDisconnect should be called.
localConn1->disconnect();
remoteData1->expectDisconnected();
{
// Should still be able to reconnect after disconnecting
auto remoteConn = std::make_unique<TestRemoteConnection>();
auto localConn = inspector->connect(id1, std::move(remoteConn));
EXPECT_NE(localConn.get(), nullptr);
}
}
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
File diff suppressed because it is too large Load Diff
@@ -1,111 +0,0 @@
/*
* 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/inspector/chrome/RemoteObjectsTable.h>
#include <gtest/gtest.h>
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
namespace {
struct TestContext {
TestContext() {
scope1 = table.addScope(std::make_pair(1, 1), BacktraceObjectGroup);
scope2 = table.addScope(std::make_pair(2, 1), ConsoleObjectGroup);
scope3 = table.addScope(std::make_pair(3, 1), "");
value1 = table.addValue(jsi::Value(1.5), BacktraceObjectGroup);
value2 = table.addValue(jsi::Value(2.5), BacktraceObjectGroup);
value3 = table.addValue(jsi::Value(3.5), "");
}
RemoteObjectsTable table;
std::string scope1;
std::string scope2;
std::string scope3;
std::string value1;
std::string value2;
std::string value3;
};
} // namespace
TEST(RemoteObjectsTableTest, TestGetScope) {
TestContext ctx;
EXPECT_EQ(ctx.table.getScope(ctx.scope1)->first, 1);
EXPECT_EQ(ctx.table.getScope(ctx.scope2)->first, 2);
EXPECT_EQ(ctx.table.getScope(ctx.scope3)->first, 3);
EXPECT_TRUE(ctx.table.getScope(ctx.value1) == nullptr);
EXPECT_TRUE(ctx.table.getScope(ctx.value2) == nullptr);
EXPECT_TRUE(ctx.table.getScope(ctx.value3) == nullptr);
}
TEST(RemoteObjectsTableTest, TestGetValue) {
TestContext ctx;
EXPECT_TRUE(ctx.table.getValue(ctx.scope1) == nullptr);
EXPECT_TRUE(ctx.table.getValue(ctx.scope2) == nullptr);
EXPECT_TRUE(ctx.table.getValue(ctx.scope3) == nullptr);
EXPECT_EQ(ctx.table.getValue(ctx.value1)->asNumber(), 1.5);
EXPECT_EQ(ctx.table.getValue(ctx.value2)->asNumber(), 2.5);
EXPECT_EQ(ctx.table.getValue(ctx.value3)->asNumber(), 3.5);
}
TEST(RemoteObjectsTableTest, TestGetObjectGroup) {
TestContext ctx;
EXPECT_EQ(ctx.table.getObjectGroup(ctx.scope1), BacktraceObjectGroup);
EXPECT_EQ(ctx.table.getObjectGroup(ctx.scope2), ConsoleObjectGroup);
EXPECT_EQ(ctx.table.getObjectGroup(ctx.scope3), "");
EXPECT_EQ(ctx.table.getObjectGroup(ctx.value1), BacktraceObjectGroup);
EXPECT_EQ(ctx.table.getObjectGroup(ctx.value2), BacktraceObjectGroup);
EXPECT_EQ(ctx.table.getObjectGroup(ctx.value3), "");
}
TEST(RemoteObjectsTableTest, TestReleaseObject) {
TestContext ctx;
ctx.table.releaseObject(ctx.scope1);
ctx.table.releaseObject(ctx.value3);
std::string scope4 = ctx.table.addScope(std::make_pair(4, 1), "");
std::string value4 = ctx.table.addValue(jsi::Value(4.5), "");
EXPECT_EQ(ctx.table.getScope(ctx.scope1), nullptr);
EXPECT_EQ(ctx.table.getScope(ctx.scope2)->first, 2);
EXPECT_EQ(ctx.table.getScope(ctx.scope3)->first, 3);
EXPECT_EQ(ctx.table.getScope(scope4)->first, 4);
EXPECT_EQ(ctx.table.getValue(ctx.value1)->asNumber(), 1.5);
EXPECT_EQ(ctx.table.getValue(ctx.value2)->asNumber(), 2.5);
EXPECT_EQ(ctx.table.getValue(ctx.value3), nullptr);
EXPECT_EQ(ctx.table.getValue(value4)->asNumber(), 4.5);
}
TEST(RemoteObjectsTableTest, TestReleaseObjectGroup) {
TestContext ctx;
ctx.table.releaseObjectGroup(BacktraceObjectGroup);
std::string scope4 = ctx.table.addScope(std::make_pair(4, 1), "");
std::string value4 = ctx.table.addValue(jsi::Value(4.5), "");
EXPECT_EQ(ctx.table.getScope(ctx.scope1), nullptr);
EXPECT_EQ(ctx.table.getScope(ctx.scope2)->first, 2);
EXPECT_EQ(ctx.table.getScope(ctx.scope3)->first, 3);
EXPECT_EQ(ctx.table.getScope(scope4)->first, 4);
EXPECT_EQ(ctx.table.getValue(ctx.value1), nullptr);
EXPECT_EQ(ctx.table.getValue(ctx.value2), nullptr);
EXPECT_EQ(ctx.table.getValue(ctx.value3)->asNumber(), 3.5);
EXPECT_EQ(ctx.table.getValue(value4)->asNumber(), 4.5);
}
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -1,133 +0,0 @@
/*
* 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 "SyncConnection.h"
#include <functional>
#include <stdexcept>
#include <folly/json.h>
#include <glog/logging.h>
#include <hermes/inspector/RuntimeAdapter.h>
#include <jsinspector/InspectorInterfaces.h>
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
using namespace std::placeholders;
using ::facebook::react::IRemoteConnection;
namespace {
std::string prettify(const std::string &str) {
try {
folly::dynamic obj = folly::parseJson(str);
return folly::toPrettyJson(obj);
} catch (...) {
// pass
}
return str;
}
} // namespace
class SyncConnection::RemoteConnection : public IRemoteConnection {
public:
RemoteConnection(SyncConnection &conn) : conn_(conn) {}
void onMessage(std::string message) override {
conn_.onReply(message);
}
void onDisconnect() override {}
private:
SyncConnection &conn_;
};
SyncConnection::SyncConnection(
std::shared_ptr<HermesRuntime> runtime,
bool waitForDebugger)
: connection_(
std::make_unique<SharedRuntimeAdapter>(runtime),
"testConn",
waitForDebugger) {
connection_.connect(std::make_unique<RemoteConnection>(*this));
}
void SyncConnection::send(const std::string &str) {
LOG(INFO) << "SyncConnection::send sending " << str;
connection_.sendMessage(str);
}
void SyncConnection::waitForResponse(
folly::Function<void(const std::string &)> handler,
std::chrono::milliseconds timeout) {
std::string reply;
{
std::unique_lock<std::mutex> lock(mutex_);
bool success = hasReply_.wait_for(
lock, timeout, [this]() -> bool { return !replies_.empty(); });
if (!success) {
throw std::runtime_error("timed out waiting for reply");
}
reply = std::move(replies_.front());
replies_.pop();
}
handler(reply);
}
void SyncConnection::waitForNotification(
folly::Function<void(const std::string &)> handler,
std::chrono::milliseconds timeout) {
std::string notification;
{
std::unique_lock<std::mutex> lock(mutex_);
bool success = hasNotification_.wait_for(
lock, timeout, [this]() -> bool { return !notifications_.empty(); });
if (!success) {
throw std::runtime_error("timed out waiting for notification");
}
notification = std::move(notifications_.front());
notifications_.pop();
}
handler(notification);
}
void SyncConnection::onReply(const std::string &message) {
LOG(INFO) << "SyncConnection::onReply got message: " << prettify(message);
std::scoped_lock lock(mutex_);
folly::dynamic obj = folly::parseJson(message);
if (obj.count("id")) {
replies_.push(message);
hasReply_.notify_one();
} else {
notifications_.push(message);
hasNotification_.notify_one();
}
}
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -1,69 +0,0 @@
/*
* 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.
*/
#pragma once
#include <chrono>
#include <condition_variable>
#include <mutex>
#include <queue>
#include <folly/Function.h>
#include <hermes/hermes.h>
#include <hermes/inspector/chrome/Connection.h>
#include <optional>
namespace facebook {
namespace hermes {
namespace inspector {
namespace chrome {
/**
* SyncConnection provides a synchronous interface over Connection that is
* useful in tests.
*/
class SyncConnection {
public:
SyncConnection(
std::shared_ptr<HermesRuntime> runtime,
bool waitForDebugger = false);
~SyncConnection() = default;
/// sends a message to the debugger
void send(const std::string &str);
/// waits for the next response from the debugger. handler is called with the
/// response. throws on timeout.
void waitForResponse(
folly::Function<void(const std::string &)> handler,
std::chrono::milliseconds timeout = std::chrono::milliseconds(2500));
/// waits for the next notification from the debugger. handler is called with
/// the notification. throws on timeout.
void waitForNotification(
folly::Function<void(const std::string &)> handler,
std::chrono::milliseconds timeout = std::chrono::milliseconds(2500));
private:
class RemoteConnection;
friend class RemoteConnection;
void onReply(const std::string &message);
Connection connection_;
std::mutex mutex_;
std::condition_variable hasReply_;
std::queue<std::string> replies_;
std::condition_variable hasNotification_;
std::queue<std::string> notifications_;
};
} // namespace chrome
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -1,77 +0,0 @@
/*
* 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 "CallbackOStream.h"
#include <algorithm>
#include <cassert>
namespace facebook {
namespace hermes {
namespace inspector {
namespace detail {
CallbackOStream::CallbackOStream(size_t sz, Fn cb)
: std::ostream(&sbuf_), sbuf_(sz, std::move(cb)) {}
CallbackOStream::StreamBuf::StreamBuf(size_t sz, Fn cb)
: sz_(sz), buf_(std::make_unique<char[]>(sz)), cb_(std::move(cb)) {
reset();
}
CallbackOStream::StreamBuf::~StreamBuf() {
sync();
}
std::streambuf::int_type CallbackOStream::StreamBuf::overflow(
std::streambuf::int_type ch) {
assert(pptr() <= epptr() && "overflow expects the buffer not to be overfull");
if (!pptr()) {
return traits_type::eof();
}
*pptr() = ch;
pbump(1);
if (sync() == 0) {
return traits_type::not_eof(ch);
}
// Set to nullptr on failure.
setp(nullptr, nullptr);
return traits_type::eof();
}
int CallbackOStream::StreamBuf::sync() {
try {
return pbase() == pptr() || cb_(take()) ? 0 : -1;
} catch (...) {
return -1;
}
}
void CallbackOStream::StreamBuf::reset() {
assert(sz_ > 0 && "Buffer cannot be empty.");
// std::streambuf::overflow accepts the character that caused the overflow as
// a parameter. Part of handling the overflow is adding this character to the
// stream. We choose to do this by stealing a byte at the end of the "put"
// area where the character can be written, even if the area is otherwise
// full, immediately prior to being flushed.
setp(&buf_[0], &buf_[0] + sz_ - 1);
}
std::string CallbackOStream::StreamBuf::take() {
const size_t strsz = pptr() - pbase();
reset();
return std::string(pbase(), strsz);
}
} // namespace detail
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -1,89 +0,0 @@
/*
* 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.
*/
#pragma once
#include <functional>
#include <memory>
#include <ostream>
#include <streambuf>
#include <string>
namespace facebook {
namespace hermes {
namespace inspector {
namespace detail {
/// Subclass of \c std::ostream where flushing is implemented through a
/// callback. Writes are collected in a buffer. When filled, the buffer's
/// contents are emptied out and sent to a callback.
struct CallbackOStream : public std::ostream {
/// Signature of callback called to flush buffer contents. Accepts the buffer
/// as a string. Returns a boolean indicating whether flushing succeeded.
/// Callback failure will be translated to stream failure. If the callback
/// throws an exception it will be swallowed and translated into stream
/// failure.
using Fn = std::function<bool(std::string)>;
/// Construct a new stream.
///
/// \p sz The size of the buffer -- how large it can get before it must be
/// flushed. Must be non-zero.
/// \p cb The callback function.
CallbackOStream(size_t sz, Fn cb);
/// This class is neither movable nor copyable.
CallbackOStream(CallbackOStream &&that) = delete;
CallbackOStream &operator=(CallbackOStream &&that) = delete;
CallbackOStream(const CallbackOStream &that) = delete;
CallbackOStream &operator=(const CallbackOStream &that) = delete;
private:
/// \c std::streambuf sub-class backed by a std::string buffer and
/// implementing overflow by calling a callback.
struct StreamBuf : public std::streambuf {
/// Construct a new streambuf. Parameters are the same as those of
/// \c CallbackOStream .
StreamBuf(size_t sz, Fn cb);
/// Destruction will flush any remaining buffer contents.
~StreamBuf();
/// StreamBufs are not copyable, to avoid the flush callback receiving
/// the contents of multiple streams.
StreamBuf(const StreamBuf &) = delete;
StreamBuf &operator=(const StreamBuf &) = delete;
protected:
/// std::streambuf overrides
int_type overflow(int_type ch) override;
int sync() override;
private:
/// The size of the backing buffer. Fixed for an instance of the streambuf.
size_t sz_;
/// The backing buffer that writes will go to until full.
std::unique_ptr<char[]> buf_;
/// The function called when buf_ has been filled.
Fn cb_;
/// Clears the backing buffer.
void reset();
/// Clears the backing buffer and returns it contents in a string.
std::string take();
};
StreamBuf sbuf_;
};
} // namespace detail
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -1,60 +0,0 @@
/*
* 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 "SerialExecutor.h"
namespace facebook {
namespace hermes {
namespace inspector {
namespace detail {
SerialExecutor::SerialExecutor(const std::string &name)
: finish_(false), thread_(name, [this]() { runLoop(); }) {}
SerialExecutor::~SerialExecutor() {
{
std::scoped_lock lock(mutex_);
finish_ = true;
wakeup_.notify_one();
}
thread_.join();
}
void SerialExecutor::add(folly::Func func) {
std::scoped_lock lock(mutex_);
funcs_.push(std::move(func));
wakeup_.notify_one();
}
void SerialExecutor::runLoop() {
bool shouldExit = false;
while (!shouldExit) {
folly::Func func;
{
std::unique_lock<std::mutex> lock(mutex_);
wakeup_.wait(lock, [this] { return finish_ || !funcs_.empty(); });
if (!funcs_.empty()) {
func = std::move(funcs_.front());
funcs_.pop();
}
shouldExit = funcs_.empty() && finish_;
}
if (func) {
func();
}
}
}
} // namespace detail
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -1,57 +0,0 @@
/*
* 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.
*/
#pragma once
#include <condition_variable>
#include <mutex>
#include <queue>
#include <string>
#include <hermes/inspector/detail/Thread.h>
#include <folly/Executor.h>
namespace facebook {
namespace hermes {
namespace inspector {
namespace detail {
/// SerialExecutor is a simple implementation of folly::Executor that processes
/// work items serially on a worker thread. It exists for two reasons:
///
/// 1. Currently Hermes builds for the host as well as in fbandroid and
/// fbobjc, so we need an implementation of a serial executor that doesn't
/// use the SerialAsyncExecutorFactory from fbandroid or fbobjc.
/// 2. None of folly's Executor factories are included in the stripped-down
/// version of folly in xplat.
///
/// TODO: create a factory that uses SerialAsyncExecutorFactory if we're
/// building for fbandroid or fbobjc, and otherwise creates an instance of this
/// class.
class SerialExecutor : public folly::Executor {
public:
SerialExecutor(const std::string &name);
~SerialExecutor() override;
void add(folly::Func) override;
private:
void runLoop();
std::mutex mutex_;
std::queue<folly::Func> funcs_;
std::condition_variable wakeup_;
bool finish_;
Thread thread_;
};
} // namespace detail
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -1,39 +0,0 @@
/*
* 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.
*/
#ifdef __ANDROID__
#include "Thread.h"
#include <fbjni/JThread.h>
namespace facebook {
namespace hermes {
namespace inspector {
namespace detail {
struct Thread::Impl {
facebook::jni::global_ref<facebook::jni::JThread> thread_;
};
Thread::Thread(std::string, std::function<void()> runnable)
: impl_(std::make_unique<Impl>(Impl{facebook::jni::make_global(
facebook::jni::JThread::create(std::move(runnable)))})) {
impl_->thread_->start();
}
Thread::~Thread() {}
void Thread::join() {
impl_->thread_->join();
}
} // namespace detail
} // namespace inspector
} // namespace hermes
} // namespace facebook
#endif
@@ -1,85 +0,0 @@
/*
* 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.
*/
#pragma once
#include <functional>
#include <memory>
#ifdef _WINDOWS
#include <thread>
#elif !defined(__ANDROID__)
#include <pthread.h>
#include <thread>
#endif
namespace facebook {
namespace hermes {
namespace inspector {
namespace detail {
#ifdef __ANDROID__
/// Android version of Thread that uses JThread, which is a java.lang.Thread.
/// This is desirable because real Java threads have access to the app's
/// classloader, which allows us to call in to Java from C++.
///
/// The implementation is private to the .cpp file to avoid leaking
/// the fbjni dependencies into code which creates Threads.
class Thread {
public:
Thread(std::string name, std::function<void()> runnable);
~Thread();
void detach() {
// Java threads don't need to be explicitly detached
}
void join();
private:
struct Impl;
std::unique_ptr<Impl> impl_;
};
#else
class Thread {
public:
Thread(std::string name, std::function<void()> runnable)
: thread_(run, name, runnable) {}
void detach() {
thread_.detach();
}
void join() {
thread_.join();
}
private:
static void run(std::string name, std::function<void()> runnable) {
#if defined(_GNU_SOURCE)
pthread_setname_np(pthread_self(), name.c_str());
#elif defined(__APPLE__)
pthread_setname_np(name.c_str());
#endif
runnable();
}
std::thread thread_;
};
#endif
}; // namespace detail
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -1,104 +0,0 @@
/*
* 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/inspector/detail/CallbackOStream.h>
#include <memory>
#include <ostream>
#include <type_traits>
#include <vector>
#include <gmock/gmock.h>
namespace {
using namespace ::testing;
using namespace facebook::hermes::inspector::detail;
TEST(CallbackOStreamTests, Chunking) {
std::vector<std::string> recvd;
CallbackOStream cos(/* sz */ 4, [&recvd](std::string s) {
recvd.emplace_back(std::move(s));
return true;
});
cos << "123412341234";
EXPECT_THAT(recvd, ElementsAre("1234", "1234", "1234"));
}
TEST(CallbackOStreamTests, SyncOnDestruction) {
std::vector<std::string> recvd;
{
CallbackOStream cos(/* sz */ 4, [&recvd](std::string s) {
recvd.emplace_back(std::move(s));
return true;
});
cos << "123412341234123";
ASSERT_THAT(recvd, ElementsAre("1234", "1234", "1234"));
}
EXPECT_THAT(recvd, ElementsAre("1234", "1234", "1234", "123"));
}
TEST(CallbackOStreamTests, ExplicitFlush) {
std::vector<std::string> recvd;
CallbackOStream cos(/* sz */ 4, [&recvd](std::string s) {
recvd.emplace_back(std::move(s));
return true;
});
cos << "123412341234123";
EXPECT_THAT(recvd, ElementsAre("1234", "1234", "1234"));
cos << std::flush;
EXPECT_THAT(recvd, ElementsAre("1234", "1234", "1234", "123"));
}
TEST(CallbackOStreamTests, FlushEmpty) {
size_t i = 0;
CallbackOStream cos(/* sz */ 4, [&i](std::string) { return ++i; });
cos << "12341234";
ASSERT_THAT(i, Eq(2));
// If the put area is empty, we will not flush.
cos << std::flush;
EXPECT_THAT(i, Eq(2));
}
TEST(CallbackOStreamTests, FailingCallback) {
size_t i = 0;
std::vector<std::string> recvd;
CallbackOStream cos(/* sz */ 4, [&i, &recvd](std::string s) {
recvd.emplace_back(std::move(s));
return ++i < 2;
});
cos << "123412341234";
EXPECT_THAT(recvd, ElementsAre("1234", "1234"));
EXPECT_THAT(!cos, Eq(true));
}
TEST(CallbackOStreamTests, ThrowingCallback) {
size_t i = 0;
std::vector<std::string> recvd;
CallbackOStream cos(/* sz */ 4, [&i, &recvd](std::string s) {
if (i++ >= 2) {
throw "too big";
}
recvd.emplace_back(std::move(s));
return true;
});
cos << "123412341234";
EXPECT_THAT(recvd, ElementsAre("1234", "1234"));
EXPECT_THAT(!cos, Eq(true));
}
} // namespace
@@ -1,41 +0,0 @@
/*
* 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/inspector/detail/SerialExecutor.h>
#include <array>
#include <iostream>
#include <gtest/gtest.h>
namespace facebook {
namespace hermes {
namespace inspector {
namespace detail {
TEST(SerialExecutorTests, testProcessesItems) {
std::array<int, 1024> values{};
{
SerialExecutor executor("TestExecutor");
for (int i = 0; i < values.size(); i++) {
executor.add([=, &values]() { values[i] = i; });
}
}
// By this time the serial executor destructor should have exited and waited
// for all work items to complete.
for (int i = 0; i < values.size(); i++) {
EXPECT_EQ(values[i], i);
}
}
} // namespace detail
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -1,7 +0,0 @@
#!/bin/sh
# 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.
dot -Tpdf InspectorFSM.gv -o InspectorFSM.pdf
@@ -1,39 +0,0 @@
digraph InspectorFSM {
node [shape = point];
init1;
init2;
node [shape = circle];
init1 -> RWE [ label = " pauseOnFirstStmt " ]
init2 -> RD [ label = " !pauseOnFirstStmt " ]
RD -> RD [ label = " didPause" ];
RD -> PWE [ label = " debuggerStmt " ];
RWE -> PWE [ label = " didPause " ];
RWP -> P [label = " didPause " ];
RD -> R [ label = " enable " ];
PWE -> P [label = " enable " ];
RWE -> RWP [ label = " enable" ];
R -> P [ label = " !implicitPause " ];
R -> R [ label = " implicitPause "];
P -> R [ label = " receivedCommand " ];
P -> RD [ label = " disable "];
R -> RD [ label = " disable "];
{ rank = same; RD RWE }
{ rank = same; R P }
label = < <table border="0">
<!-- hack: empty row for spacing -->
<tr> <td></td><td> </td></tr>
<tr><td>Abbrev</td><td>State</td></tr>
<hr/>
<tr><td>RWE</td><td>RunningWaitEnable</td></tr>
<tr><td>RWP</td><td>RunningWaitPause</td></tr>
<tr><td>RD</td><td>RunningDetached</td></tr>
<tr><td>PWE</td><td>PausedWaitEnable</td></tr>
<tr><td>P</td><td>Paused</td></tr>
<tr><td>R</td><td>Running</td></tr>
</table> >;
labelloc = "b";
}
@@ -1,410 +0,0 @@
/*
* 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/inspector/Inspector.h>
#include <atomic>
#include <chrono>
#include <functional>
#include <iostream>
#include <memory>
#include <folly/futures/Future.h>
#include <gtest/gtest.h>
#include <hermes/inspector/RuntimeAdapter.h>
namespace facebook {
namespace hermes {
namespace inspector {
namespace debugger = facebook::hermes::debugger;
using namespace std::chrono_literals;
using Unit = folly::Unit;
static auto constexpr kDefaultTimeout = 5000ms;
namespace {
int getCurrentLine(const debugger::ProgramState &state) {
return state.getStackTrace().callFrameForIndex(0).location.line;
}
debugger::SourceLocation locationForLine(int line) {
debugger::SourceLocation loc;
loc.line = line;
return loc;
}
} // namespace
/*
* LambdaInspectorObserver is useful for sequencing calls to the debugger based
* on the number of onPause() callbacks.
*/
using OnPauseFunction =
std::function<void(Inspector &, const debugger::ProgramState &, int)>;
class LambdaInspectorObserver : public InspectorObserver {
public:
LambdaInspectorObserver(OnPauseFunction func)
: onPauseFunc_(func), pauseCount_(0) {}
~LambdaInspectorObserver() = default;
void onBreakpointResolved(
Inspector &inspector,
const debugger::BreakpointInfo &info) override {}
void onContextCreated(Inspector &inspector) override {}
void onPause(Inspector &inspector, const debugger::ProgramState &state)
override {
pauseCount_++;
onPauseFunc_(inspector, state, pauseCount_);
}
void onResume(Inspector &inspector) override {}
void onScriptParsed(Inspector &inspector, const ScriptInfo &info) override {}
void onMessageAdded(Inspector &inspector, const ConsoleMessageInfo &info)
override{};
int getPauseCount() {
return pauseCount_;
}
private:
OnPauseFunction onPauseFunc_;
int pauseCount_;
};
/*
* Helpers for running JS in a separate thread.
*/
struct HermesDebugContext {
HermesDebugContext(
InspectorObserver &observer,
folly::Future<Unit> &&finished)
: runtime(makeHermesRuntime()),
inspector(
std::make_shared<SharedRuntimeAdapter>(runtime),
observer,
false),
stopFlag(false),
finished(std::move(finished)) {
runtime->global().setProperty(
*runtime,
"shouldStop",
jsi::Function::createFromHostFunction(
*runtime,
jsi::PropNameID::forAscii(*runtime, "shouldStop"),
0,
[this](
jsi::Runtime &,
const jsi::Value &,
const jsi::Value *args,
size_t count) {
return stopFlag.load() ? jsi::Value(true) : jsi::Value(false);
}));
}
~HermesDebugContext() = default;
void setStopFlag() {
stopFlag.store(true);
}
void wait(std::chrono::milliseconds timeout = kDefaultTimeout) {
std::move(finished).get(timeout);
}
std::shared_ptr<HermesRuntime> runtime;
Inspector inspector;
std::atomic<bool> stopFlag{};
folly::Future<Unit> finished;
};
static std::shared_ptr<HermesDebugContext> runScriptAsync(
InspectorObserver &observer,
const std::string &script) {
auto promise = std::make_shared<folly::Promise<Unit>>();
auto future = promise->getFuture();
auto context =
std::make_shared<HermesDebugContext>(observer, std::move(future));
std::thread t([=]() {
HermesRuntime::DebugFlags flags{};
context->runtime->debugJavaScript(script, "url", flags);
promise->setValue();
});
t.detach();
return context;
}
/*
* Tests
*/
TEST(InspectorTests, testStepOver) {
std::string script = R"(
var a = 1 + 2;
debugger;
var b = a / 2;
var c = a + b;
var d = b - c;
var e = c * d;
var f = 10;
)";
// TODO: move this vector into lambdaInspectorObserver
std::vector<folly::Future<Unit>> futures;
OnPauseFunction onPauseFunc = [&futures](
Inspector &inspector,
const debugger::ProgramState &state,
int pauseCount) {
switch (pauseCount) {
case 1: {
EXPECT_EQ(
state.getPauseReason(), debugger::PauseReason::DebuggerStatement);
EXPECT_EQ(getCurrentLine(state), 3);
futures.emplace_back(inspector.stepOver());
break;
}
case 2: {
EXPECT_EQ(state.getPauseReason(), debugger::PauseReason::StepFinish);
EXPECT_EQ(getCurrentLine(state), 4);
futures.emplace_back(inspector.stepOver());
break;
}
case 3: {
EXPECT_EQ(state.getPauseReason(), debugger::PauseReason::StepFinish);
EXPECT_EQ(getCurrentLine(state), 5);
futures.emplace_back(inspector.resume());
break;
}
}
};
LambdaInspectorObserver observer(onPauseFunc);
std::shared_ptr<HermesDebugContext> context =
runScriptAsync(observer, script);
// TODO: temporarily do this to ensure i hit failure case
std::this_thread::sleep_for(1000ms);
futures.emplace_back(context->inspector.enable());
context->wait();
folly::collectAll(futures).get(kDefaultTimeout);
EXPECT_EQ(observer.getPauseCount(), 3);
}
TEST(InspectorTests, testSetBreakpoint) {
std::string script = R"(
var a = 1 + 2;
debugger;
var b = a / 2;
var c = a + b;
var d = b - c;
var e = c * d;
var f = 10;
)";
std::vector<folly::Future<Unit>> futures;
OnPauseFunction onPauseFunc = [&futures](
Inspector &inspector,
const debugger::ProgramState &state,
int pauseCount) {
switch (pauseCount) {
case 1: {
EXPECT_EQ(
state.getPauseReason(), debugger::PauseReason::DebuggerStatement);
EXPECT_EQ(getCurrentLine(state), 3);
auto stepFuture = inspector.stepOver();
futures.emplace_back(std::move(stepFuture));
break;
}
case 2: {
EXPECT_EQ(state.getPauseReason(), debugger::PauseReason::StepFinish);
EXPECT_EQ(getCurrentLine(state), 4);
auto breakpointFuture = inspector.setBreakpoint(locationForLine(6));
futures.emplace_back(std::move(breakpointFuture)
.thenValue([](debugger::BreakpointInfo info) {
EXPECT_EQ(info.resolvedLocation.line, 6);
}));
auto resumeFuture = inspector.resume();
futures.emplace_back(std::move(resumeFuture));
break;
}
case 3: {
EXPECT_EQ(state.getPauseReason(), debugger::PauseReason::Breakpoint);
EXPECT_EQ(getCurrentLine(state), 6);
auto resumeFuture = inspector.resume();
futures.emplace_back(std::move(resumeFuture));
break;
}
}
};
LambdaInspectorObserver observer(onPauseFunc);
std::shared_ptr<HermesDebugContext> context =
runScriptAsync(observer, script);
auto enablePromise = context->inspector.enable();
futures.emplace_back(std::move(enablePromise));
context->wait();
folly::collectAll(futures).get(kDefaultTimeout);
EXPECT_EQ(observer.getPauseCount(), 3);
}
TEST(InspectorTests, testAsyncSetBreakpoint) {
std::string script = R"(
while (!shouldStop()) {
var a = 1;
var b = 2;
var c = a + b;
var d = 10;
}
)";
std::vector<folly::Future<Unit>> futures;
folly::Func stopFunc;
OnPauseFunction onPauseFunc = [&futures, &stopFunc](
Inspector &inspector,
const debugger::ProgramState &state,
int pauseCount) {
switch (pauseCount) {
case 1: {
EXPECT_EQ(state.getPauseReason(), debugger::PauseReason::Breakpoint);
EXPECT_EQ(getCurrentLine(state), 4);
auto stepFuture = inspector.stepOver();
futures.emplace_back(std::move(stepFuture));
break;
}
case 2: {
EXPECT_EQ(state.getPauseReason(), debugger::PauseReason::StepFinish);
EXPECT_EQ(getCurrentLine(state), 5);
stopFunc();
auto resumeFuture = inspector.resume();
futures.emplace_back(std::move(resumeFuture));
break;
}
}
};
LambdaInspectorObserver observer(onPauseFunc);
std::shared_ptr<HermesDebugContext> context =
runScriptAsync(observer, script);
stopFunc = [context]() { context->setStopFlag(); };
context->inspector.enable();
auto breakpointPromise = context->inspector.setBreakpoint(locationForLine(4))
.thenValue([](debugger::BreakpointInfo info) {
EXPECT_EQ(info.resolvedLocation.line, 4);
});
context->wait();
futures.emplace_back(std::move(breakpointPromise));
folly::collectAll(futures).get(kDefaultTimeout);
EXPECT_EQ(observer.getPauseCount(), 2);
}
TEST(InspectorTests, testDisable) {
std::string script = R"(
var a = 1 + 2;
debugger;
var b = a / 2;
var c = a + b;
var d = b - c;
var e = c * d;
var f = 10;
)";
std::vector<folly::Future<Unit>> futures;
OnPauseFunction onPauseFunc = [&futures](
Inspector &inspector,
const debugger::ProgramState &state,
int pauseCount) {
switch (pauseCount) {
case 1: {
EXPECT_EQ(
state.getPauseReason(), debugger::PauseReason::DebuggerStatement);
EXPECT_EQ(getCurrentLine(state), 3);
auto stepFuture = inspector.stepOver();
futures.emplace_back(std::move(stepFuture));
break;
}
case 2: {
EXPECT_EQ(state.getPauseReason(), debugger::PauseReason::StepFinish);
EXPECT_EQ(getCurrentLine(state), 4);
futures.emplace_back(inspector.setBreakpoint(locationForLine(6))
.thenValue([](debugger::BreakpointInfo info) {
EXPECT_EQ(info.resolvedLocation.line, 6);
}));
futures.emplace_back(inspector.setBreakpoint(locationForLine(7))
.thenValue([](debugger::BreakpointInfo info) {
EXPECT_EQ(info.resolvedLocation.line, 7);
}));
auto detachFuture = inspector.disable();
futures.emplace_back(std::move(detachFuture));
break;
}
}
};
LambdaInspectorObserver observer(onPauseFunc);
std::shared_ptr<HermesDebugContext> context =
runScriptAsync(observer, script);
auto enablePromise = context->inspector.enable();
futures.emplace_back(std::move(enablePromise));
context->wait();
folly::collectAll(futures).get(kDefaultTimeout);
EXPECT_EQ(observer.getPauseCount(), 2);
}
} // namespace inspector
} // namespace hermes
} // namespace facebook
@@ -1,10 +0,0 @@
#!/bin/sh
DIR="$(dirname "${BASH_SOURCE[0]}")"
cd "$DIR"
FBSOURCE="$(hg root)"
CLANG_FORMAT="$FBSOURCE/tools/third-party/clang-format/clang-format"
SRC="$FBSOURCE/xplat/js/react-native-github/packages/react-native/ReactCommon/hermes/inspector"
find "$SRC" '(' -name '*.h' -or -name '*.cpp' ')' -exec "$CLANG_FORMAT" -i -style=file '{}' ';'
@@ -1,41 +0,0 @@
Debugger.breakpointResolved
Debugger.disable
Debugger.enable
Debugger.evaluateOnCallFrame
Debugger.pause
Debugger.paused
Debugger.removeBreakpoint
Debugger.resume
Debugger.resumed
Debugger.scriptParsed
Debugger.setBreakpoint
Debugger.setBreakpointByUrl
Debugger.setBreakpointsActive
Debugger.setInstrumentationBreakpoint
Debugger.setPauseOnExceptions
Debugger.stepInto
Debugger.stepOut
Debugger.stepOver
HeapProfiler.addHeapSnapshotChunk
HeapProfiler.collectGarbage
HeapProfiler.reportHeapSnapshotProgress
HeapProfiler.takeHeapSnapshot
HeapProfiler.startTrackingHeapObjects
HeapProfiler.stopTrackingHeapObjects
HeapProfiler.startSampling
HeapProfiler.stopSampling
HeapProfiler.heapStatsUpdate
HeapProfiler.lastSeenObjectId
HeapProfiler.getObjectByHeapObjectId
HeapProfiler.getHeapObjectId
Profiler.start
Profiler.stop
Runtime.callFunctionOn
Runtime.consoleAPICalled
Runtime.evaluate
Runtime.executionContextCreated
Runtime.getHeapUsage
Runtime.getProperties
Runtime.runIfWaitingForDebugger
Runtime.globalLexicalScopeNames
Runtime.compileScript
@@ -1,32 +0,0 @@
#!/bin/bash
set -e
DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd)
MSGTYPES_PATH="${DIR}/message_types.txt"
HEADER_PATH="${DIR}/../chrome/MessageTypes.h"
CPP_PATH="${DIR}/../chrome/MessageTypes.cpp"
FBSOURCE=$(hg root)
CLANG_FORMAT="${FBSOURCE}/tools/third-party/clang-format/clang-format"
SIGNEDSOURCE="${FBSOURCE}/tools/signedsource"
pushd "../../../../../hermes-inspector-msggen"
yarn install
yarn build
node bin/index.js \
--ignore-experimental \
--include-experimental=Runtime.getProperties.generatePreview,Runtime.evaluate.generatePreview,Runtime.callFunctionOn.generatePreview,Debugger.evaluateOnCallFrame.generatePreview,Runtime.RemoteObject.preview,Runtime.RemoteObject.customPreview,Runtime.CustomPreview,Runtime.EntryPreview,Runtime.ObjectPreview,Runtime.PropertyPreview,Runtime.getHeapUsage \
--roots "${MSGTYPES_PATH}" \
"${HEADER_PATH}" "${CPP_PATH}"
"${CLANG_FORMAT}" -i --style=file "${HEADER_PATH}"
"${CLANG_FORMAT}" -i --style=file "${CPP_PATH}"
"${SIGNEDSOURCE}" sign "${HEADER_PATH}"
"${SIGNEDSOURCE}" sign "${CPP_PATH}"
popd >/dev/null
@@ -1,13 +0,0 @@
#!/bin/bash
# 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.
THIS_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
source "$THIS_DIR/setup.sh"
buck test //xplat/js/react-native-github/packages/react-native/ReactCommon/hermes/inspector:chrome &&
buck test //xplat/js/react-native-github/packages/react-native/ReactCommon/hermes/inspector:detail &&
buck test //xplat/js/react-native-github/packages/react-native/ReactCommon/hermes/inspector:inspectorlib &&
buck build //xplat/js/react-native-github/packages/react-native/ReactCommon/hermes/inspector:hermes-chrome-debug-server
@@ -1,31 +0,0 @@
#!/bin/bash
# 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.
#
# Basic setup for xplat testing in sandcastle. Based on
# xplat/hermes/facebook/sandcastle/setup.sh.
set -x
set -e
set -o pipefail
THIS_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
ROOT_DIR=$(cd "$THIS_DIR" && hg root)
# Enter xplat
cd "$ROOT_DIR"/xplat || exit 1
# Setup env
export TITLE
TITLE=$(hg log -l 1 --template "{desc|strip|firstline}")
export REV
REV=$(hg log -l 1 --template "{node}")
export AUTHOR
AUTHOR=$(hg log -l 1 --template "{author|emailuser}")
if [ -n "$SANDCASTLE" ]; then
source automation/setup_buck.sh
fi