Skip to content

Commit

Permalink
Implement HeapProfiler.getObjectByHeapObjectId
Browse files Browse the repository at this point in the history
Summary:
Implement the API for querying the properties of an object found in a
heap snapshot.

Now when you are debugging and take a heap snapshot, you can hover
over an object and inspect it!

Only works for subclasses of JSObject. Doesn't work for stuff like HiddenClass,
PropertyAccessor, native objects like WeakValueMap, etc. Those internal objects
display "Preview is not available" which matches what Chrome prints for its own
internal stuff.

Changelog: [Internal]

Reviewed By: avp

Differential Revision: D27834672

fbshipit-source-id: 607a8984b5a48b76c5ae57f9bd5bf53168f3ec3f
  • Loading branch information
dulinriley authored and facebook-github-bot committed Apr 30, 2021
1 parent 2b27323 commit 7579ed3
Show file tree
Hide file tree
Showing 5 changed files with 306 additions and 2 deletions.
74 changes: 74 additions & 0 deletions ReactCommon/hermes/inspector/chrome/Connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include <cstdlib>
#include <mutex>
#include <sstream>

#include <folly/Conv.h>
#include <folly/Executor.h>
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -637,6 +641,76 @@ void Connection::Impl::handle(
.thenError<std::exception>(sendErrorToClient(req.id));
}

void Connection::Impl::handle(
const m::heapProfiler::GetObjectByHeapObjectIdRequest &req) {
uint64_t objID = atoi(req.objectId.c_str());
folly::Optional<std::string> group = req.objectGroup;
auto remoteObjPtr = std::make_shared<m::runtime::RemoteObject>();

inspector_
->executeIfEnabled(
"HeapProfiler.getObjectByHeapObjectId",
[this, remoteObjPtr, objID, group](const debugger::ProgramState &) {
jsi::Runtime *rt = &getRuntime();
if (auto *hermesRT = dynamic_cast<HermesRuntime *>(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<std::exception>(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<uint64_t> snapshotID = std::make_shared<uint64_t>(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<HermesRuntime *>(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<std::exception>(sendErrorToClient(req.id));
}

void Connection::Impl::handle(const m::runtime::EvaluateRequest &req) {
auto remoteObjPtr = std::make_shared<m::runtime::RemoteObject>();

Expand Down
101 changes: 100 additions & 1 deletion ReactCommon/hermes/inspector/chrome/MessageTypes.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright 2004-present Facebook. All Rights Reserved.
// @generated SignedSource<<f195ef454dab0ca2be532d6cdb2ebd0a>>
// @generated SignedSource<<522f29c54f207a4f7b5c33af07cf64d0>>

#include "MessageTypes.h"

Expand Down Expand Up @@ -46,6 +46,10 @@ std::unique_ptr<Request> Request::fromJsonThrowOnError(const std::string &str) {
{"Debugger.stepOver", makeUnique<debugger::StepOverRequest>},
{"HeapProfiler.collectGarbage",
makeUnique<heapProfiler::CollectGarbageRequest>},
{"HeapProfiler.getHeapObjectId",
makeUnique<heapProfiler::GetHeapObjectIdRequest>},
{"HeapProfiler.getObjectByHeapObjectId",
makeUnique<heapProfiler::GetObjectByHeapObjectIdRequest>},
{"HeapProfiler.startSampling",
makeUnique<heapProfiler::StartSamplingRequest>},
{"HeapProfiler.startTrackingHeapObjects",
Expand Down Expand Up @@ -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") {}

Expand Down Expand Up @@ -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");

Expand Down
50 changes: 49 additions & 1 deletion ReactCommon/hermes/inspector/chrome/MessageTypes.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright 2004-present Facebook. All Rights Reserved.
// @generated SignedSource<<0961e921eb7c5201466836c8ce82de73>>
// @generated SignedSource<<a541d174394c8959b9fb6a7c575e7040>>

#pragma once

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 {}
Expand Down Expand Up @@ -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<std::string> objectGroup;
};

struct heapProfiler::StartSamplingRequest : public Request {
StartSamplingRequest();
explicit StartSamplingRequest(const folly::dynamic &obj);
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 7579ed3

Please sign in to comment.