diff --git a/ReactCommon/hermes/inspector/chrome/Connection.cpp b/ReactCommon/hermes/inspector/chrome/Connection.cpp index 00cb888156e..572a07cc282 100644 --- a/ReactCommon/hermes/inspector/chrome/Connection.cpp +++ b/ReactCommon/hermes/inspector/chrome/Connection.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -96,6 +97,9 @@ class Connection::Impl : public inspector::InspectorObserver, void handle(const m::heapProfiler::StartSamplingRequest &req) override; void handle(const m::heapProfiler::StopSamplingRequest &req) override; void handle(const m::heapProfiler::CollectGarbageRequest &req) override; + void handle( + const m::heapProfiler::GetObjectByHeapObjectIdRequest &req) override; + void handle(const m::heapProfiler::GetHeapObjectIdRequest &req) override; void handle(const m::runtime::EvaluateRequest &req) override; void handle(const m::runtime::GetPropertiesRequest &req) override; void handle(const m::runtime::RunIfWaitingForDebuggerRequest &req) override; @@ -637,6 +641,76 @@ void Connection::Impl::handle( .thenError(sendErrorToClient(req.id)); } +void Connection::Impl::handle( + const m::heapProfiler::GetObjectByHeapObjectIdRequest &req) { + uint64_t objID = atoi(req.objectId.c_str()); + folly::Optional group = req.objectGroup; + auto remoteObjPtr = std::make_shared(); + + inspector_ + ->executeIfEnabled( + "HeapProfiler.getObjectByHeapObjectId", + [this, remoteObjPtr, objID, group](const debugger::ProgramState &) { + jsi::Runtime *rt = &getRuntime(); + if (auto *hermesRT = dynamic_cast(rt)) { + jsi::Value val = hermesRT->getObjectForID(objID); + if (val.isNull()) { + return; + } + *remoteObjPtr = m::runtime::makeRemoteObject( + getRuntime(), val, objTable_, group.value_or("")); + } + }) + .via(executor_.get()) + .thenValue([this, id = req.id, remoteObjPtr](auto &&) { + if (!remoteObjPtr->type.empty()) { + m::heapProfiler::GetObjectByHeapObjectIdResponse resp; + resp.id = id; + resp.result = *remoteObjPtr; + sendResponseToClient(resp); + } else { + sendResponseToClient(m::makeErrorResponse( + id, m::ErrorCode::ServerError, "Object is not available")); + } + }) + .thenError(sendErrorToClient(req.id)); +} + +void Connection::Impl::handle( + const m::heapProfiler::GetHeapObjectIdRequest &req) { + // Use a shared_ptr because the stack frame will go away. + std::shared_ptr snapshotID = std::make_shared(0); + + inspector_ + ->executeIfEnabled( + "HeapProfiler.getHeapObjectId", + [this, req, snapshotID](const debugger::ProgramState &) { + if (const jsi::Value *valuePtr = objTable_.getValue(req.objectId)) { + jsi::Runtime *rt = &getRuntime(); + if (auto *hermesRT = dynamic_cast(rt)) { + *snapshotID = hermesRT->getUniqueID(*valuePtr); + } + } + }) + .via(executor_.get()) + .thenValue([this, id = req.id, snapshotID](auto &&) { + if (*snapshotID) { + m::heapProfiler::GetHeapObjectIdResponse resp; + resp.id = id; + // std::to_string is not available on Android, use a std::ostream + // instead. + std::ostringstream stream; + stream << *snapshotID; + resp.heapSnapshotObjectId = stream.str(); + sendResponseToClient(resp); + } else { + sendResponseToClient(m::makeErrorResponse( + id, m::ErrorCode::ServerError, "Object is not available")); + } + }) + .thenError(sendErrorToClient(req.id)); +} + void Connection::Impl::handle(const m::runtime::EvaluateRequest &req) { auto remoteObjPtr = std::make_shared(); diff --git a/ReactCommon/hermes/inspector/chrome/MessageTypes.cpp b/ReactCommon/hermes/inspector/chrome/MessageTypes.cpp index c8aea1ba453..484719e130e 100644 --- a/ReactCommon/hermes/inspector/chrome/MessageTypes.cpp +++ b/ReactCommon/hermes/inspector/chrome/MessageTypes.cpp @@ -1,5 +1,5 @@ // Copyright 2004-present Facebook. All Rights Reserved. -// @generated SignedSource<> +// @generated SignedSource<<522f29c54f207a4f7b5c33af07cf64d0>> #include "MessageTypes.h" @@ -46,6 +46,10 @@ std::unique_ptr Request::fromJsonThrowOnError(const std::string &str) { {"Debugger.stepOver", makeUnique}, {"HeapProfiler.collectGarbage", makeUnique}, + {"HeapProfiler.getHeapObjectId", + makeUnique}, + {"HeapProfiler.getObjectByHeapObjectId", + makeUnique}, {"HeapProfiler.startSampling", makeUnique}, {"HeapProfiler.startTrackingHeapObjects", @@ -730,6 +734,65 @@ void heapProfiler::CollectGarbageRequest::accept( handler.handle(*this); } +heapProfiler::GetHeapObjectIdRequest::GetHeapObjectIdRequest() + : Request("HeapProfiler.getHeapObjectId") {} + +heapProfiler::GetHeapObjectIdRequest::GetHeapObjectIdRequest(const dynamic &obj) + : Request("HeapProfiler.getHeapObjectId") { + assign(id, obj, "id"); + assign(method, obj, "method"); + + dynamic params = obj.at("params"); + assign(objectId, params, "objectId"); +} + +dynamic heapProfiler::GetHeapObjectIdRequest::toDynamic() const { + dynamic params = dynamic::object; + put(params, "objectId", objectId); + + dynamic obj = dynamic::object; + put(obj, "id", id); + put(obj, "method", method); + put(obj, "params", std::move(params)); + return obj; +} + +void heapProfiler::GetHeapObjectIdRequest::accept( + RequestHandler &handler) const { + handler.handle(*this); +} + +heapProfiler::GetObjectByHeapObjectIdRequest::GetObjectByHeapObjectIdRequest() + : Request("HeapProfiler.getObjectByHeapObjectId") {} + +heapProfiler::GetObjectByHeapObjectIdRequest::GetObjectByHeapObjectIdRequest( + const dynamic &obj) + : Request("HeapProfiler.getObjectByHeapObjectId") { + assign(id, obj, "id"); + assign(method, obj, "method"); + + dynamic params = obj.at("params"); + assign(objectId, params, "objectId"); + assign(objectGroup, params, "objectGroup"); +} + +dynamic heapProfiler::GetObjectByHeapObjectIdRequest::toDynamic() const { + dynamic params = dynamic::object; + put(params, "objectId", objectId); + put(params, "objectGroup", objectGroup); + + dynamic obj = dynamic::object; + put(obj, "id", id); + put(obj, "method", method); + put(obj, "params", std::move(params)); + return obj; +} + +void heapProfiler::GetObjectByHeapObjectIdRequest::accept( + RequestHandler &handler) const { + handler.handle(*this); +} + heapProfiler::StartSamplingRequest::StartSamplingRequest() : Request("HeapProfiler.startSampling") {} @@ -1071,6 +1134,42 @@ dynamic debugger::SetInstrumentationBreakpointResponse::toDynamic() const { return obj; } +heapProfiler::GetHeapObjectIdResponse::GetHeapObjectIdResponse( + const dynamic &obj) { + assign(id, obj, "id"); + + dynamic res = obj.at("result"); + assign(heapSnapshotObjectId, res, "heapSnapshotObjectId"); +} + +dynamic heapProfiler::GetHeapObjectIdResponse::toDynamic() const { + dynamic res = dynamic::object; + put(res, "heapSnapshotObjectId", heapSnapshotObjectId); + + dynamic obj = dynamic::object; + put(obj, "id", id); + put(obj, "result", std::move(res)); + return obj; +} + +heapProfiler::GetObjectByHeapObjectIdResponse::GetObjectByHeapObjectIdResponse( + const dynamic &obj) { + assign(id, obj, "id"); + + dynamic res = obj.at("result"); + assign(result, res, "result"); +} + +dynamic heapProfiler::GetObjectByHeapObjectIdResponse::toDynamic() const { + dynamic res = dynamic::object; + put(res, "result", result); + + dynamic obj = dynamic::object; + put(obj, "id", id); + put(obj, "result", std::move(res)); + return obj; +} + heapProfiler::StopSamplingResponse::StopSamplingResponse(const dynamic &obj) { assign(id, obj, "id"); diff --git a/ReactCommon/hermes/inspector/chrome/MessageTypes.h b/ReactCommon/hermes/inspector/chrome/MessageTypes.h index c7092f920de..2184a223588 100644 --- a/ReactCommon/hermes/inspector/chrome/MessageTypes.h +++ b/ReactCommon/hermes/inspector/chrome/MessageTypes.h @@ -1,5 +1,5 @@ // Copyright 2004-present Facebook. All Rights Reserved. -// @generated SignedSource<<0961e921eb7c5201466836c8ce82de73>> +// @generated SignedSource<> #pragma once @@ -72,6 +72,11 @@ using UnserializableValue = std::string; namespace heapProfiler { struct AddHeapSnapshotChunkNotification; struct CollectGarbageRequest; +struct GetHeapObjectIdRequest; +struct GetHeapObjectIdResponse; +struct GetObjectByHeapObjectIdRequest; +struct GetObjectByHeapObjectIdResponse; +using HeapSnapshotObjectId = std::string; struct HeapStatsUpdateNotification; struct LastSeenObjectIdNotification; struct ReportHeapSnapshotProgressNotification; @@ -107,6 +112,9 @@ struct RequestHandler { virtual void handle(const debugger::StepOutRequest &req) = 0; virtual void handle(const debugger::StepOverRequest &req) = 0; virtual void handle(const heapProfiler::CollectGarbageRequest &req) = 0; + virtual void handle(const heapProfiler::GetHeapObjectIdRequest &req) = 0; + virtual void handle( + const heapProfiler::GetObjectByHeapObjectIdRequest &req) = 0; virtual void handle(const heapProfiler::StartSamplingRequest &req) = 0; virtual void handle( const heapProfiler::StartTrackingHeapObjectsRequest &req) = 0; @@ -138,6 +146,9 @@ struct NoopRequestHandler : public RequestHandler { void handle(const debugger::StepOutRequest &req) override {} void handle(const debugger::StepOverRequest &req) override {} void handle(const heapProfiler::CollectGarbageRequest &req) override {} + void handle(const heapProfiler::GetHeapObjectIdRequest &req) override {} + void handle( + const heapProfiler::GetObjectByHeapObjectIdRequest &req) override {} void handle(const heapProfiler::StartSamplingRequest &req) override {} void handle( const heapProfiler::StartTrackingHeapObjectsRequest &req) override {} @@ -464,6 +475,27 @@ struct heapProfiler::CollectGarbageRequest : public Request { void accept(RequestHandler &handler) const override; }; +struct heapProfiler::GetHeapObjectIdRequest : public Request { + GetHeapObjectIdRequest(); + explicit GetHeapObjectIdRequest(const folly::dynamic &obj); + + folly::dynamic toDynamic() const override; + void accept(RequestHandler &handler) const override; + + runtime::RemoteObjectId objectId{}; +}; + +struct heapProfiler::GetObjectByHeapObjectIdRequest : public Request { + GetObjectByHeapObjectIdRequest(); + explicit GetObjectByHeapObjectIdRequest(const folly::dynamic &obj); + + folly::dynamic toDynamic() const override; + void accept(RequestHandler &handler) const override; + + heapProfiler::HeapSnapshotObjectId objectId{}; + folly::Optional objectGroup; +}; + struct heapProfiler::StartSamplingRequest : public Request { StartSamplingRequest(); explicit StartSamplingRequest(const folly::dynamic &obj); @@ -602,6 +634,22 @@ struct debugger::SetInstrumentationBreakpointResponse : public Response { debugger::BreakpointId breakpointId{}; }; +struct heapProfiler::GetHeapObjectIdResponse : public Response { + GetHeapObjectIdResponse() = default; + explicit GetHeapObjectIdResponse(const folly::dynamic &obj); + folly::dynamic toDynamic() const override; + + heapProfiler::HeapSnapshotObjectId heapSnapshotObjectId{}; +}; + +struct heapProfiler::GetObjectByHeapObjectIdResponse : public Response { + GetObjectByHeapObjectIdResponse() = default; + explicit GetObjectByHeapObjectIdResponse(const folly::dynamic &obj); + folly::dynamic toDynamic() const override; + + runtime::RemoteObject result{}; +}; + struct heapProfiler::StopSamplingResponse : public Response { StopSamplingResponse() = default; explicit StopSamplingResponse(const folly::dynamic &obj); diff --git a/ReactCommon/hermes/inspector/chrome/tests/ConnectionTests.cpp b/ReactCommon/hermes/inspector/chrome/tests/ConnectionTests.cpp index 952e84aac46..f579799bbae 100644 --- a/ReactCommon/hermes/inspector/chrome/tests/ConnectionTests.cpp +++ b/ReactCommon/hermes/inspector/chrome/tests/ConnectionTests.cpp @@ -22,6 +22,7 @@ #include #include #include +#include namespace facebook { namespace hermes { @@ -2512,6 +2513,86 @@ TEST(ConnectionTests, heapProfilerSampling) { expectNotification(conn); } +TEST(ConnectionTests, heapSnapshotRemoteObject) { + TestContext context; + AsyncHermesRuntime &asyncRuntime = context.runtime(); + std::shared_ptr runtime = asyncRuntime.runtime(); + SyncConnection &conn = context.conn(); + int msgId = 1; + + send(conn, msgId++); + expectExecutionContextCreated(conn); + + asyncRuntime.executeScriptAsync(R"( + storeValue([1, 2, 3]); + debugger; + )"); + expectNotification(conn); + + // We should get a pause before the first statement. + expectNotification(conn); + + { + // Take a heap snapshot first to assign IDs. + m::heapProfiler::TakeHeapSnapshotRequest req; + req.id = msgId++; + req.reportProgress = false; + // We don't need the response because we can directly query for object IDs + // from the runtime. + send(conn, req); + } + + const uint64_t globalObjID = runtime->getUniqueID(runtime->global()); + jsi::Value storedValue = asyncRuntime.awaitStoredValue(); + const uint64_t storedObjID = + runtime->getUniqueID(storedValue.asObject(*runtime)); + + auto testObject = [&msgId, &conn]( + uint64_t objID, + const char *type, + const char *className, + const char *description, + const char *subtype) { + // Get the object by its snapshot ID. + m::heapProfiler::GetObjectByHeapObjectIdRequest req; + req.id = msgId++; + req.objectId = std::to_string(objID); + auto resp = send< + m::heapProfiler::GetObjectByHeapObjectIdRequest, + m::heapProfiler::GetObjectByHeapObjectIdResponse>(conn, req); + EXPECT_EQ(resp.result.type, type); + EXPECT_EQ(resp.result.className, className); + EXPECT_EQ(resp.result.description, description); + if (subtype) { + EXPECT_EQ(resp.result.subtype, subtype); + } + + // Check that fetching the object by heap snapshot ID works. + m::heapProfiler::GetHeapObjectIdRequest idReq; + idReq.id = msgId++; + idReq.objectId = resp.result.objectId.value(); + auto idResp = send< + m::heapProfiler::GetHeapObjectIdRequest, + m::heapProfiler::GetHeapObjectIdResponse>(conn, idReq); + EXPECT_EQ(atoi(idResp.heapSnapshotObjectId.c_str()), objID); + }; + + // Test once before a collection. + testObject(globalObjID, "object", "Object", "Object", nullptr); + testObject(storedObjID, "object", "Array", "Array(3)", "array"); + // Force a collection to move the heap. + runtime->instrumentation().collectGarbage("test"); + // A collection should not disturb the unique ID lookup, and it should be the + // same object as before. Note that it won't have the same remote ID, because + // Hermes doesn't do uniquing. + testObject(globalObjID, "object", "Object", "Object", nullptr); + testObject(storedObjID, "object", "Array", "Array(3)", "array"); + + // Resume and exit. + send(conn, msgId++); + expectNotification(conn); +} + } // namespace chrome } // namespace inspector } // namespace hermes diff --git a/ReactCommon/hermes/inspector/tools/message_types.txt b/ReactCommon/hermes/inspector/tools/message_types.txt index 155c78ae776..6267e82ee0e 100644 --- a/ReactCommon/hermes/inspector/tools/message_types.txt +++ b/ReactCommon/hermes/inspector/tools/message_types.txt @@ -26,6 +26,8 @@ HeapProfiler.startSampling HeapProfiler.stopSampling HeapProfiler.heapStatsUpdate HeapProfiler.lastSeenObjectId +HeapProfiler.getObjectByHeapObjectId +HeapProfiler.getHeapObjectId Runtime.consoleAPICalled Runtime.evaluate Runtime.executionContextCreated