Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions API/hermes/DebuggerAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,16 @@ std::vector<BreakpointID> Debugger::getBreakpoints() {
return impl_->getBreakpoints();
}

std::vector<BreakLocation> Debugger::getPossibleBreakpoints(
ScriptID scriptId,
uint32_t startLine,
uint32_t startColumn,
uint32_t endLine,
uint32_t endColumn) {
return impl_->getPossibleBreakpoints(
scriptId, startLine, startColumn, endLine, endColumn);
}

void Debugger::setShouldPauseOnScriptLoad(bool flag) {
return impl_->setShouldPauseOnScriptLoad(flag);
}
Expand Down
22 changes: 22 additions & 0 deletions API/hermes/DebuggerAPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,20 @@ class HERMES_EXPORT Debugger {
/// \return a list of extant breakpoints.
std::vector<BreakpointID> getBreakpoints();

/// \return the possible breakpoint locations within the source range
/// [\p startLine:\p startColumn, \p endLine:\p endColumn) of the script
/// identified by \p scriptId. Lines and columns are 1-based. A \p startColumn
/// of 1 includes the whole start line. If \p endLine is kInvalidLocation, the
/// range extends to the end of the script; otherwise \p endColumn is the
/// exclusive end column on \p endLine. Returns an empty vector if the script
/// is unknown or has no debug info.
std::vector<BreakLocation> getPossibleBreakpoints(
ScriptID scriptId,
uint32_t startLine,
uint32_t startColumn,
uint32_t endLine,
uint32_t endColumn);

/// Set whether the debugger should pause when an exception is thrown.
void setPauseOnThrowMode(PauseOnThrowMode mode);

Expand Down Expand Up @@ -465,6 +479,14 @@ class Debugger {
std::vector<BreakpointID> getBreakpoints() {
return std::vector<BreakpointID>();
}
std::vector<BreakLocation> getPossibleBreakpoints(
ScriptID scriptId,
uint32_t startLine,
uint32_t startColumn,
uint32_t endLine,
uint32_t endColumn) {
return {};
}
void setPauseOnThrowMode(PauseOnThrowMode mode) {}
PauseOnThrowMode getPauseOnThrowMode() const {
return PauseOnThrowMode::None;
Expand Down
3 changes: 3 additions & 0 deletions API/hermes/cdp/CDPAgent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,9 @@ void CDPAgentImpl::DomainAgentsImpl::handleCommand(
} else if (method == "setBreakpointByUrl") {
debuggerAgent_->setBreakpointByUrl(
static_cast<m::debugger::SetBreakpointByUrlRequest &>(*command));
} else if (method == "getPossibleBreakpoints") {
debuggerAgent_->getPossibleBreakpoints(
static_cast<m::debugger::GetPossibleBreakpointsRequest &>(*command));
} else if (method == "removeBreakpoint") {
debuggerAgent_->removeBreakpoint(
static_cast<m::debugger::RemoveBreakpointRequest &>(*command));
Expand Down
111 changes: 111 additions & 0 deletions API/hermes/cdp/DebuggerDomainAgent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,117 @@ void DebuggerDomainAgent::setBreakpointByUrl(
sendResponseToClient(resp);
}

void DebuggerDomainAgent::getPossibleBreakpoints(
const m::debugger::GetPossibleBreakpointsRequest &req) {
if (!checkDebuggerEnabled(req)) {
return;
}

// The start and end locations must refer to the same script.
if (req.end.has_value() && req.end->scriptId != req.start.scriptId) {
sendResponseToClient(
m::makeErrorResponse(
req.id, m::ErrorCode::ServerError, "Invalid range"));
return;
}

// The start must not be positioned after the end.
if (req.end.has_value()) {
long long startColumn = req.start.columnNumber.value_or(0);
long long endColumn = req.end->columnNumber.value_or(0);
if (req.start.lineNumber > req.end->lineNumber ||
(req.start.lineNumber == req.end->lineNumber &&
startColumn > endColumn)) {
sendResponseToClient(
m::makeErrorResponse(
req.id, m::ErrorCode::ServerError, "Invalid range"));
return;
}
}

// Parse and validate the script ID. Reject anything that isn't a plain
// number to avoid throwing in std::stoull (the digit-count guard keeps the
// value within uint64_t range).
const std::string &scriptIdStr = req.start.scriptId;
if (scriptIdStr.empty() || scriptIdStr.size() > 19 ||
scriptIdStr.find_first_not_of("0123456789") != std::string::npos) {
sendResponseToClient(
m::makeErrorResponse(
req.id, m::ErrorCode::ServerError, "No script with id"));
return;
}
auto scriptID = static_cast<debugger::ScriptID>(std::stoull(scriptIdStr));

bool scriptFound = false;
for (const auto &srcLoc : runtime_.getDebugger().getLoadedScripts()) {
if (srcLoc.fileId == scriptID) {
scriptFound = true;
break;
}
}
if (!scriptFound) {
sendResponseToClient(
m::makeErrorResponse(
req.id, m::ErrorCode::ServerError, "No script with id"));
return;
}

// Convert the CDP 0-based range to Hermes' 1-based, half-open range.
uint32_t startLine = static_cast<uint32_t>(req.start.lineNumber) + 1;
uint32_t startColumn = req.start.columnNumber.has_value()
? static_cast<uint32_t>(req.start.columnNumber.value()) + 1
: 1;
uint32_t endLine = debugger::kInvalidLocation;
uint32_t endColumn = debugger::kInvalidLocation;
if (req.end.has_value()) {
endLine = static_cast<uint32_t>(req.end->lineNumber) + 1;
// endColumn is an exclusive bound; an omitted column means the whole end
// line, not column 1.
endColumn = req.end->columnNumber.has_value()
? static_cast<uint32_t>(req.end->columnNumber.value()) + 1
: debugger::kInvalidLocation;
}

std::vector<debugger::BreakLocation> breakLocations =
runtime_.getDebugger().getPossibleBreakpoints(
scriptID, startLine, startColumn, endLine, endColumn);

// Match V8's cap on the number of returned locations to bound the response
// size (see kMaxNumBreakpoints in V8's v8-debugger-agent-impl.cc). With
// statement-level granularity this is rarely hit.
constexpr size_t kMaxNumBreakLocations = 1000;
size_t count = breakLocations.size();
if (count > kMaxNumBreakLocations) {
count = kMaxNumBreakLocations;
}

m::debugger::GetPossibleBreakpointsResponse resp;
resp.id = req.id;
resp.locations.reserve(count);
for (size_t i = 0; i < count; ++i) {
const debugger::BreakLocation &breakLocation = breakLocations[i];
m::debugger::BreakLocation cdpLocation;
cdpLocation.scriptId = std::to_string(breakLocation.location.fileId);
m::setChromeLocation(cdpLocation, breakLocation.location);
switch (breakLocation.type) {
case debugger::BreakLocationType::DebuggerStatement:
cdpLocation.type = "debuggerStatement";
break;
case debugger::BreakLocationType::Call:
cdpLocation.type = "call";
break;
case debugger::BreakLocationType::Return:
cdpLocation.type = "return";
break;
case debugger::BreakLocationType::None:
break;
}
resp.locations.push_back(std::move(cdpLocation));
}

sendResponseToClient(resp);
}

void DebuggerDomainAgent::removeBreakpoint(
const m::debugger::RemoveBreakpointRequest &req) {
if (!checkDebuggerEnabled(req)) {
Expand Down
6 changes: 6 additions & 0 deletions API/hermes/cdp/DebuggerDomainAgent.h
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,12 @@ class DebuggerDomainAgent : public DomainAgent {
/// even in the same CDP session. For comparison, V8 allows it from different
/// sessions but disallows it within a single session.
void setBreakpointByUrl(const m::debugger::SetBreakpointByUrlRequest &req);
/// @cdp Debugger.getPossibleBreakpoints returns the valid breakpoint
/// locations within a source range. The `start` and (if provided) `end`
/// locations must refer to the same script. `restrictToFunction` is not yet
/// supported and is ignored.
void getPossibleBreakpoints(
const m::debugger::GetPossibleBreakpointsRequest &req);
/// Handles Debugger.removeBreakpoint
void removeBreakpoint(const m::debugger::RemoveBreakpointRequest &req);
/// Handles Debugger.setBreakpointsActive
Expand Down
105 changes: 104 additions & 1 deletion API/hermes/cdp/MessageTypes.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved.
// @generated SignedSource<<b6f23d499b731c057a44576d724cde32>>
// @generated SignedSource<<330a0049c8eb8453a49b0b402e63aa47>>

#include "MessageTypes.h"

Expand Down Expand Up @@ -94,6 +94,8 @@ std::unique_ptr<Request> Request::fromJson(const std::string &str) {
{"Debugger.enable", tryMake<debugger::EnableRequest>},
{"Debugger.evaluateOnCallFrame",
tryMake<debugger::EvaluateOnCallFrameRequest>},
{"Debugger.getPossibleBreakpoints",
tryMake<debugger::GetPossibleBreakpointsRequest>},
{"Debugger.pause", tryMake<debugger::PauseRequest>},
{"Debugger.removeBreakpoint", tryMake<debugger::RemoveBreakpointRequest>},
{"Debugger.resume", tryMake<debugger::ResumeRequest>},
Expand Down Expand Up @@ -375,6 +377,27 @@ JSONValue *runtime::ExceptionDetails::toJsonVal(JSONFactory &factory) const {
return factory.newObject(props.begin(), props.end());
}

std::unique_ptr<debugger::BreakLocation> debugger::BreakLocation::tryMake(
const JSONObject *obj) {
std::unique_ptr<debugger::BreakLocation> type =
std::make_unique<debugger::BreakLocation>();
TRY_ASSIGN(type->scriptId, obj, "scriptId");
TRY_ASSIGN(type->lineNumber, obj, "lineNumber");
TRY_ASSIGN(type->columnNumber, obj, "columnNumber");
TRY_ASSIGN(type->type, obj, "type");
return type;
}

JSONValue *debugger::BreakLocation::toJsonVal(JSONFactory &factory) const {
llvh::SmallVector<JSONFactory::Prop, 4> props;

put(props, "scriptId", scriptId, factory);
put(props, "lineNumber", lineNumber, factory);
put(props, "columnNumber", columnNumber, factory);
put(props, "type", type, factory);
return factory.newObject(props.begin(), props.end());
}

std::unique_ptr<debugger::Scope> debugger::Scope::tryMake(
const JSONObject *obj) {
std::unique_ptr<debugger::Scope> type = std::make_unique<debugger::Scope>();
Expand Down Expand Up @@ -786,6 +809,53 @@ void debugger::EvaluateOnCallFrameRequest::accept(
handler.handle(*this);
}

debugger::GetPossibleBreakpointsRequest::GetPossibleBreakpointsRequest()
: Request("Debugger.getPossibleBreakpoints") {}

std::unique_ptr<debugger::GetPossibleBreakpointsRequest>
debugger::GetPossibleBreakpointsRequest::tryMake(const JSONObject *obj) {
std::unique_ptr<debugger::GetPossibleBreakpointsRequest> req =
std::make_unique<debugger::GetPossibleBreakpointsRequest>();
TRY_ASSIGN(req->id, obj, "id");
TRY_ASSIGN(req->method, obj, "method");

JSONValue *v = obj->get("params");
if (v == nullptr) {
return nullptr;
}
auto convertResult = valueFromJson<JSONObject *>(v);
if (!convertResult) {
return nullptr;
}
auto *params = *convertResult;
TRY_ASSIGN(req->start, params, "start");
TRY_ASSIGN(req->end, params, "end");
TRY_ASSIGN(req->restrictToFunction, params, "restrictToFunction");
return req;
}

JSONValue *debugger::GetPossibleBreakpointsRequest::toJsonVal(
JSONFactory &factory) const {
llvh::SmallVector<JSONFactory::Prop, 3> paramsProps;
put(paramsProps, "start", start, factory);
put(paramsProps, "end", end, factory);
put(paramsProps, "restrictToFunction", restrictToFunction, factory);

llvh::SmallVector<JSONFactory::Prop, 1> props;
put(props, "id", id, factory);
put(props, "method", method, factory);
put(props,
"params",
factory.newObject(paramsProps.begin(), paramsProps.end()),
factory);
return factory.newObject(props.begin(), props.end());
}

void debugger::GetPossibleBreakpointsRequest::accept(
RequestHandler &handler) const {
handler.handle(*this);
}

debugger::PauseRequest::PauseRequest() : Request("Debugger.pause") {}

std::unique_ptr<debugger::PauseRequest> debugger::PauseRequest::tryMake(
Expand Down Expand Up @@ -2210,6 +2280,39 @@ JSONValue *debugger::EvaluateOnCallFrameResponse::toJsonVal(
return factory.newObject(props.begin(), props.end());
}

std::unique_ptr<debugger::GetPossibleBreakpointsResponse>
debugger::GetPossibleBreakpointsResponse::tryMake(const JSONObject *obj) {
std::unique_ptr<debugger::GetPossibleBreakpointsResponse> resp =
std::make_unique<debugger::GetPossibleBreakpointsResponse>();
TRY_ASSIGN(resp->id, obj, "id");

JSONValue *v = obj->get("result");
if (v == nullptr) {
return nullptr;
}
auto convertResult = valueFromJson<JSONObject *>(v);
if (!convertResult) {
return nullptr;
}
auto *res = *convertResult;
TRY_ASSIGN(resp->locations, res, "locations");
return resp;
}

JSONValue *debugger::GetPossibleBreakpointsResponse::toJsonVal(
JSONFactory &factory) const {
llvh::SmallVector<JSONFactory::Prop, 1> resProps;
put(resProps, "locations", locations, factory);

llvh::SmallVector<JSONFactory::Prop, 2> props;
put(props, "id", id, factory);
put(props,
"result",
factory.newObject(resProps.begin(), resProps.end()),
factory);
return factory.newObject(props.begin(), props.end());
}

std::unique_ptr<debugger::SetBreakpointResponse>
debugger::SetBreakpointResponse::tryMake(const JSONObject *obj) {
std::unique_ptr<debugger::SetBreakpointResponse> resp =
Expand Down
Loading
Loading