Files
react-native/ReactCommon/hermes/inspector/chrome/cli/main.cpp
T
Andres Suarez 0f4f917663 Apply clang-format update fixes
Reviewed By: igorsugak

Differential Revision: D25861683

fbshipit-source-id: 616afca13ae64c76421053ce49286035e0687e36
2021-01-09 22:11:00 -08:00

273 lines
6.7 KiB
C++

/*
* Copyright (c) Facebook, Inc. and its 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());
auto adapter = std::make_unique<fbhermes::inspector::SharedRuntimeAdapter>(
runtime, runtime->getDebugger());
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;
}