diff --git a/API/hermes/cdp/ConsoleMessage.h b/API/hermes/cdp/ConsoleMessage.h index 0a2154f912c..906dbb9a8c5 100644 --- a/API/hermes/cdp/ConsoleMessage.h +++ b/API/hermes/cdp/ConsoleMessage.h @@ -48,13 +48,17 @@ struct ConsoleMessage { double timestamp; ConsoleAPIType type; std::vector args; - debugger::StackTrace stackTrace{}; + debugger::StackTrace stackTrace; ConsoleMessage( double timestamp, ConsoleAPIType type, - std::vector args) - : timestamp(timestamp), type(type), args(std::move(args)) {} + std::vector args, + debugger::StackTrace stackTrace = {}) + : timestamp(timestamp), + type(type), + args(std::move(args)), + stackTrace(stackTrace) {} }; class ConsoleMessageStorage { diff --git a/tools/hcdp/CMakeLists.txt b/tools/hcdp/CMakeLists.txt index 91718452a4b..7747e60430c 100644 --- a/tools/hcdp/CMakeLists.txt +++ b/tools/hcdp/CMakeLists.txt @@ -3,14 +3,32 @@ # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. +set(NO_EH_RTTI_SOURCES + JSONHelpers.cpp + ../../API/hermes/cdp/JSONValueInterfaces.cpp + ../../API/hermes/cdp/MessageInterfaces.cpp + ../../API/hermes/cdp/MessageTypes.cpp + ) + +if (NOT HERMES_ENABLE_EH_RTTI) + if (GCC_COMPATIBLE) + set_property(SOURCE ${NO_EH_RTTI_SOURCES} APPEND_STRING + PROPERTY COMPILE_FLAGS "-fno-exceptions -fno-rtti") + elseif (MSVC) + set_property(SOURCE ${NO_EH_RTTI_SOURCES} APPEND_STRING + PROPERTY COMPILE_FLAGS "/EHs-c- /GR-") + endif () +endif () + set(HERMES_ENABLE_EH_RTTI ON) add_hermes_tool(hcdp hcdp.cpp IPC.cpp + ${NO_EH_RTTI_SOURCES} ${ALL_HEADER_FILES} ) -target_link_libraries(hcdp hermesSerialExecutor libhermes) +target_link_libraries(hcdp hermesSerialExecutor libhermes hermesParser) install(TARGETS hcdp RUNTIME DESTINATION bin diff --git a/tools/hcdp/JSONHelpers.cpp b/tools/hcdp/JSONHelpers.cpp new file mode 100644 index 00000000000..8d29a72506c --- /dev/null +++ b/tools/hcdp/JSONHelpers.cpp @@ -0,0 +1,40 @@ +/* + * 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 "JSONHelpers.h" + +#include +#include +#include + +namespace hermes { + +using namespace ::hermes::parser; +using namespace ::facebook::hermes::cdp; +using namespace ::facebook::hermes::cdp::message; + +std::optional getResponseId(const std::string &message) { + JSLexer::Allocator allocator; + JSONFactory factory(allocator); + std::optional obj = parseStrAsJsonObj(message, factory); + if (!obj.has_value()) { + return std::nullopt; + } + + JSONValue *v = obj.value()->get("id"); + if (v == nullptr) { + return std::nullopt; + } + + std::unique_ptr id = valueFromJson(v); + if (id == nullptr) { + return std::nullopt; + } + return *id; +} + +} // namespace hermes diff --git a/tools/hcdp/JSONHelpers.h b/tools/hcdp/JSONHelpers.h new file mode 100644 index 00000000000..01ebad19b03 --- /dev/null +++ b/tools/hcdp/JSONHelpers.h @@ -0,0 +1,22 @@ +/* + * 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. + */ + +#ifndef HERMES_TOOLS_HCDP_JSONHELPERS_H +#define HERMES_TOOLS_HCDP_JSONHELPERS_H + +#include +#include + +namespace hermes { + +/// Parses a JSON string and extract the message ID out from it. +/// \param message JSON string +std::optional getResponseId(const std::string &message); + +} // namespace hermes + +#endif // HERMES_TOOLS_HCDP_JSONHELPERS_H diff --git a/tools/hcdp/hcdp.cpp b/tools/hcdp/hcdp.cpp index c9ec5a35e33..cc06362b6e4 100644 --- a/tools/hcdp/hcdp.cpp +++ b/tools/hcdp/hcdp.cpp @@ -19,23 +19,32 @@ int main(void) { #include #include #include +#include +#include #include +#include #include #include +#include #include #include "IPC.h" +#include "JSONHelpers.h" namespace hermes { namespace fbhermes = ::facebook::hermes; namespace cdp = fbhermes::cdp; +namespace message = fbhermes::cdp::message; using namespace facebook::hermes; - -constexpr uint32_t kExecutionContextId = 1; +using namespace facebook; +using namespace ::hermes::parser; namespace { + +static uint32_t nextExecutionContextId = 1; + /// Manages a runtime executing a script on a thread. class RuntimeInstance { public: @@ -49,6 +58,40 @@ class RuntimeInstance { .build())) { cdpDebugAPI_ = cdp::CDPDebugAPI::create(*runtime_); + // Install console.log handler + HermesRuntime &hermesRt = *runtime_.get(); + auto console = jsi::Object(hermesRt); + auto nameID = jsi::PropNameID::forUtf8(hermesRt, "log"); + console.setProperty( + hermesRt, + nameID, + jsi::Function::createFromHostFunction( + hermesRt, + nameID, + 1, + [cdpDebugAPI = cdpDebugAPI_.get(), &hermesRt]( + jsi::Runtime &runtime, + const jsi::Value &, + const jsi::Value *argsPtr, + size_t count) { + std::vector args; + for (size_t i = 0; i < count; ++i) { + args.emplace_back(runtime, argsPtr[i]); + } + const auto now = std::chrono::system_clock::now(); + double timestamp = + std::chrono::duration_cast( + now.time_since_epoch()) + .count(); + cdpDebugAPI->addConsoleMessage( + {timestamp, + cdp::ConsoleAPIType::kLog, + std::move(args), + hermesRt.getDebugger().captureStackTrace()}); + return jsi::Value::undefined(); + })); + runtime_->global().setProperty(hermesRt, "console", console); + executor_->add([this, source = std::move(scriptSource), url = std::move(scriptUrl)]() mutable { @@ -86,6 +129,29 @@ class RuntimeInstance { std::unique_ptr runtime_; std::unique_ptr cdpDebugAPI_; }; + +/// Thread-safe class for storing and later retrieving a message ID +class SynchronizedMessageId { + public: + void setId(long long id) { + std::lock_guard lock(mutex_); + id_ = id; + } + + void clearId() { + std::lock_guard lock(mutex_); + id_ = std::nullopt; + } + + std::optional getId() { + std::lock_guard lock(mutex_); + return id_; + } + + private: + std::mutex mutex_{}; + std::optional id_{}; +}; } // namespace /// Read a script from the specified path. @@ -106,6 +172,11 @@ static void debugScript(std::string scriptSource, std::string scriptUrl) { std::make_unique( std::move(scriptSource), std::move(scriptUrl)); + uint32_t executionContextId = nextExecutionContextId++; + + std::shared_ptr runtimeEnableMessageId = + std::make_shared(); + // Process IPC messages std::unordered_map> agents; while (std::optional ipc = receiveIPC()) { @@ -116,7 +187,7 @@ static void debugScript(std::string scriptSource, std::string scriptUrl) { agents.emplace( clientID, cdp::CDPAgent::create( - kExecutionContextId, + executionContextId, runtimeInstance->cdpDebugAPI(), [&runtimeInstance]( std::function task) { @@ -125,7 +196,24 @@ static void debugScript(std::string scriptSource, std::string scriptUrl) { runtimeInstance->addTask( [&runtime, task = std::move(task)]() { task(runtime); }); }, - [clientID](const std::string &message) { + [clientID, executionContextId, runtimeEnableMessageId]( + const std::string &message) { + // Emit executionContextCreated notification, which is + // required for the Console tab to work. + if (runtimeEnableMessageId->getId().has_value()) { + auto id = getResponseId(message); + if (id.has_value() && + id.value() == runtimeEnableMessageId->getId().value()) { + message::runtime::ExecutionContextCreatedNotification + note; + message::runtime::ExecutionContextDescription desc; + desc.id = executionContextId; + desc.name = "main"; + note.context = std::move(desc); + sendIPC(kMessageIPCType, clientID, note.toJsonStr()); + runtimeEnableMessageId->clearId(); + } + } // Forward message to the client, via an IPC message. sendIPC(kMessageIPCType, clientID, message); })); @@ -139,6 +227,13 @@ static void debugScript(std::string scriptSource, std::string scriptUrl) { // or one that never existed. throw std::runtime_error("No such agent"); } + std::unique_ptr command = + message::Request::fromJson(ipc.value().message); + // Save Runtime.enable's message ID so that we could intercept the + // response and emit executionContextCreated notification. + if (command->method == "Runtime.enable") { + runtimeEnableMessageId->setId(command->id); + } agent->second->handleCommand(ipc.value().message); } break;