[Lldb-commits] [lldb] [lldb][lldb-dap] Migrate ScopesRequest to structured types (PR #138116)
Ebuka Ezike via lldb-commits
lldb-commits at lists.llvm.org
Thu May 1 04:18:00 PDT 2025
https://github.com/da-viper created https://github.com/llvm/llvm-project/pull/138116
None
>From 437c69ffb1f86733649a82aa2a991360dd40fd7c Mon Sep 17 00:00:00 2001
From: Ebuka Ezike <yerimyah1 at gmail.com>
Date: Tue, 29 Apr 2025 18:19:18 +0100
Subject: [PATCH 1/4] [lldb][lldb-dap] Migrate 'Scopes' to structured types.
---
lldb/tools/lldb-dap/Handler/RequestHandler.h | 11 +++
.../lldb-dap/Handler/ScopesRequestHandler.cpp | 6 ++
.../lldb-dap/Protocol/ProtocolRequests.cpp | 13 +++
.../lldb-dap/Protocol/ProtocolRequests.h | 13 +++
.../tools/lldb-dap/Protocol/ProtocolTypes.cpp | 88 +++++++++++++++++--
lldb/tools/lldb-dap/Protocol/ProtocolTypes.h | 80 +++++++++++++++--
6 files changed, 199 insertions(+), 12 deletions(-)
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h
index fa3d76ed4a125..49ad8dd80c68e 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.h
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h
@@ -423,6 +423,17 @@ class PauseRequestHandler : public LegacyRequestHandler {
void operator()(const llvm::json::Object &request) const override;
};
+class ScopesRequestHandler2 final
+ : public RequestHandler<protocol::ScopesArguments,
+ llvm::Expected<protocol::ScopesResponseBody>> {
+public:
+ using RequestHandler::RequestHandler;
+ static llvm::StringLiteral GetCommand() { return "scopes"; }
+
+ llvm::Expected<protocol::ScopesResponseBody>
+ Run(const protocol::ScopesArguments &args) const override;
+};
+
class ScopesRequestHandler : public LegacyRequestHandler {
public:
using LegacyRequestHandler::LegacyRequestHandler;
diff --git a/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
index 7d1608f59f9a4..721230e2525b2 100644
--- a/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
@@ -64,6 +64,12 @@ namespace lldb_dap {
// "required": [ "body" ]
// }]
// }
+
+llvm::Expected<protocol::ScopesResponseBody>
+ScopesRequestHandler2::Run(const protocol::ScopesArguments &args) const {
+ // lldb::SBFrame frame = dap.GetLLDBFrame()
+ return llvm::createStringError("something");
+};
void ScopesRequestHandler::operator()(const llvm::json::Object &request) const {
llvm::json::Object response;
FillResponse(request, response);
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
index 61fea66490c30..d5630424ebdf7 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
@@ -260,6 +260,19 @@ bool fromJSON(const json::Value &Params, LaunchRequestArguments &LRA,
O.mapOptional("runInTerminal", LRA.runInTerminal) &&
parseEnv(Params, LRA.env, P) && parseTimeout(Params, LRA.timeout, P);
}
+bool fromJSON(const llvm::json::Value &Params, ScopesArguments &SCA,
+ llvm::json::Path P) {
+ json::ObjectMapper O(Params, P);
+ return O && O.map("frameId", SCA.frameId);
+}
+
+llvm::json::Value toJSON(const ScopesResponseBody &SCR) {
+ llvm::json::Array body;
+ for (const Scope &scope : SCR.scopes) {
+ body.emplace_back(toJSON(scope));
+ }
+ return body;
+}
bool fromJSON(const json::Value &Params, SourceArguments &SA, json::Path P) {
json::ObjectMapper O(Params, P);
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
index 33f93cc38799a..9ac5468608e1e 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
@@ -294,6 +294,19 @@ bool fromJSON(const llvm::json::Value &, LaunchRequestArguments &,
/// field is required.
using LaunchResponseBody = VoidResponse;
+struct ScopesArguments {
+ /// Retrieve the scopes for the stack frame identified by `frameId`. The
+ /// `frameId` must have been obtained in the current suspended state. See
+ /// 'Lifetime of Object References' in the Overview section for details.
+ uint64_t frameId;
+};
+bool fromJSON(const llvm::json::Value &, ScopesArguments &, llvm::json::Path);
+
+struct ScopesResponseBody {
+ std::vector<Scope> scopes;
+};
+llvm::json::Value toJSON(const ScopesResponseBody &);
+
/// Arguments for `source` request.
struct SourceArguments {
/// Specifies the source content to load. Either `source.path` or
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp
index e64998c4ca488..0f382a8dc66c3 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp
@@ -16,17 +16,18 @@ using namespace llvm;
namespace lldb_dap::protocol {
-bool fromJSON(const json::Value &Params, PresentationHint &PH, json::Path P) {
+bool fromJSON(const json::Value &Params, Source::PresentationHint &PH,
+ json::Path P) {
auto rawHint = Params.getAsString();
if (!rawHint) {
P.report("expected a string");
return false;
}
- std::optional<PresentationHint> hint =
- StringSwitch<std::optional<PresentationHint>>(*rawHint)
- .Case("normal", ePresentationHintNormal)
- .Case("emphasize", ePresentationHintEmphasize)
- .Case("deemphasize", ePresentationHintDeemphasize)
+ std::optional<Source::PresentationHint> hint =
+ StringSwitch<std::optional<Source::PresentationHint>>(*rawHint)
+ .Case("normal", Source::ePresentationHintNormal)
+ .Case("emphasize", Source::ePresentationHintEmphasize)
+ .Case("deemphasize", Source::ePresentationHintDeemphasize)
.Default(std::nullopt);
if (!hint) {
P.report("unexpected value");
@@ -36,12 +37,38 @@ bool fromJSON(const json::Value &Params, PresentationHint &PH, json::Path P) {
return true;
}
+llvm::json::Value toJSON(const Source::PresentationHint &PH) {
+ switch (PH) {
+ case Source::ePresentationHintNormal:
+ return "normal";
+ case Source ::ePresentationHintEmphasize:
+ return "emphasize";
+ case Source::ePresentationHintDeemphasize:
+ return "deemphasize";
+ }
+ llvm_unreachable("Fully covered switch above");
+}
+
bool fromJSON(const json::Value &Params, Source &S, json::Path P) {
json::ObjectMapper O(Params, P);
return O && O.map("name", S.name) && O.map("path", S.path) &&
O.map("presentationHint", S.presentationHint) &&
O.map("sourceReference", S.sourceReference);
}
+llvm::json::Value toJSON(const Source &S) {
+ json::Object result;
+
+ if (S.name.has_value())
+ result.insert({"name", S.name});
+ if (S.path.has_value())
+ result.insert({"path", S.path});
+ if (S.sourceReference.has_value())
+ result.insert({"sourceReference", S.sourceReference});
+ if (S.presentationHint.has_value())
+ result.insert({"presentationHint", S.presentationHint});
+
+ return result;
+}
json::Value toJSON(const ExceptionBreakpointsFilter &EBF) {
json::Object result{{"filter", EBF.filter}, {"label", EBF.label}};
@@ -233,6 +260,55 @@ json::Value toJSON(const Capabilities &C) {
return result;
}
+llvm::json::Value toJSON(const Scope &SC) {
+ llvm::json::Object result{{"name", SC.name},
+ {"variablesReference", SC.variablesReference},
+ {"expensive", SC.expensive}};
+
+ if (SC.presentationHint.has_value()) {
+ llvm::StringRef presentationHint;
+ switch (*SC.presentationHint) {
+ case Scope::ePresentationHintArguments:
+ presentationHint = "arguments";
+ break;
+ case Scope::ePresentationHintLocals:
+ presentationHint = "locals";
+ break;
+ case Scope::ePresentationHintRegisters:
+ presentationHint = "registers";
+ break;
+ case Scope::ePresentationHintReturnValue:
+ presentationHint = "returnValue";
+ break;
+ }
+
+ result.insert({"presentationHint", presentationHint});
+ }
+
+ if (SC.namedVariables.has_value())
+ result.insert({"namedVariables", SC.namedVariables});
+
+ if (SC.indexedVariables.has_value())
+ result.insert({"indexedVariables", SC.indexedVariables});
+
+ if (SC.source.has_value())
+ result.insert({"source", SC.source});
+
+ if (SC.line.has_value())
+ result.insert({"line", SC.line});
+
+ if (SC.column.has_value())
+ result.insert({"column", SC.column});
+
+ if (SC.endLine.has_value())
+ result.insert({"endLine", SC.endLine});
+
+ if (SC.endColumn.has_value())
+ result.insert({"endColumn", SC.endColumn});
+
+ return result;
+}
+
bool fromJSON(const llvm::json::Value &Params, SteppingGranularity &SG,
llvm::json::Path P) {
auto raw_granularity = Params.getAsString();
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
index 54941f24efbd9..a7cac858d112c 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
@@ -23,6 +23,7 @@
#include "llvm/ADT/DenseSet.h"
#include "llvm/Support/JSON.h"
#include <cstdint>
+#include <lldb/lldb-enumerations.h>
#include <optional>
#include <string>
@@ -268,16 +269,15 @@ struct Capabilities {
};
llvm::json::Value toJSON(const Capabilities &);
-enum PresentationHint : unsigned {
- ePresentationHintNormal,
- ePresentationHintEmphasize,
- ePresentationHintDeemphasize,
-};
-
/// A `Source` is a descriptor for source code. It is returned from the debug
/// adapter as part of a `StackFrame` and it is used by clients when specifying
/// breakpoints.
struct Source {
+ enum PresentationHint : unsigned {
+ ePresentationHintNormal,
+ ePresentationHintEmphasize,
+ ePresentationHintDeemphasize,
+ };
/// The short name of the source. Every source returned from the debug adapter
/// has a name. When sending a source to the debug adapter this name is
/// optional.
@@ -302,6 +302,74 @@ struct Source {
// unsupported keys: origin, sources, adapterData, checksums
};
bool fromJSON(const llvm::json::Value &, Source &, llvm::json::Path);
+llvm::json::Value toJSON(const Source &);
+
+/// A `Scope` is a named container for variables. Optionally a scope can map to
+/// a source or a range within a source.
+struct Scope {
+ enum PresentationHint : unsigned {
+ ePresentationHintArguments,
+ ePresentationHintLocals,
+ ePresentationHintRegisters,
+ ePresentationHintReturnValue
+ };
+ /// Name of the scope such as 'Arguments', 'Locals', or 'Registers'. This
+ /// string is shown in the UI as is and can be translated.
+ ////
+ std::string name;
+
+ /// A hint for how to present this scope in the UI. If this attribute is
+ /// missing, the scope is shown with a generic UI.
+ /// Values:
+ /// 'arguments': Scope contains method arguments.
+ /// 'locals': Scope contains local variables.
+ /// 'registers': Scope contains registers. Only a single `registers` scope
+ /// should be returned from a `scopes` request.
+ /// 'returnValue': Scope contains one or more return values.
+ /// etc.
+ std::optional<PresentationHint> presentationHint;
+
+ /// The variables of this scope can be retrieved by passing the value of
+ /// `variablesReference` to the `variables` request as long as execution
+ /// remains suspended. See 'Lifetime of Object References' in the Overview
+ /// section for details.
+ ////
+ uint64_t variablesReference;
+
+ /// The number of named variables in this scope.
+ /// The client can use this information to present the variables in a paged UI
+ /// and fetch them in chunks.
+ std::optional<uint64_t> namedVariables;
+
+ /// The number of indexed variables in this scope.
+ /// The client can use this information to present the variables in a paged UI
+ /// and fetch them in chunks.
+ std::optional<uint64_t> indexedVariables;
+
+ /// The source for this scope.
+ std::optional<Source> source;
+
+ /// If true, the number of variables in this scope is large or expensive to
+ /// retrieve.
+ bool expensive;
+
+ /// The start line of the range covered by this scope.
+ std::optional<uint64_t> line;
+
+ /// Start position of the range covered by the scope. It is measured in UTF-16
+ /// code units and the client capability `columnsStartAt1` determines whether
+ /// it is 0- or 1-based.
+ std::optional<uint64_t> column;
+
+ /// The end line of the range covered by this scope.
+ std::optional<uint64_t> endLine;
+
+ /// End position of the range covered by the scope. It is measured in UTF-16
+ /// code units and the client capability `columnsStartAt1` determines whether
+ /// it is 0- or 1-based.
+ std::optional<uint64_t> endColumn;
+};
+llvm::json::Value toJSON(const Scope &);
/// The granularity of one `step` in the stepping requests `next`, `stepIn`,
/// `stepOut` and `stepBack`.
>From b71343fbdef882f136888a2037fd9265994345b8 Mon Sep 17 00:00:00 2001
From: Ebuka Ezike <yerimyah1 at gmail.com>
Date: Wed, 30 Apr 2025 10:08:01 +0100
Subject: [PATCH 2/4] [lldb][lldb-dap] replicate existing functionality
verbatim
---
lldb/tools/lldb-dap/DAP.cpp | 4 +-
lldb/tools/lldb-dap/DAP.h | 2 +-
.../lldb-dap/Handler/CompletionsHandler.cpp | 4 +-
.../DataBreakpointInfoRequestHandler.cpp | 5 +-
.../Handler/EvaluateRequestHandler.cpp | 5 +-
lldb/tools/lldb-dap/Handler/RequestHandler.h | 6 +-
.../lldb-dap/Handler/ScopesRequestHandler.cpp | 78 +++++++++++++++++--
.../Handler/StepInTargetsRequestHandler.cpp | 5 +-
.../lldb-dap/Protocol/ProtocolRequests.cpp | 7 +-
9 files changed, 97 insertions(+), 19 deletions(-)
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index 4cb0d8e49004c..5d041f2c25da7 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -514,9 +514,7 @@ lldb::SBThread DAP::GetLLDBThread(const llvm::json::Object &arguments) {
return target.GetProcess().GetThreadByID(tid);
}
-lldb::SBFrame DAP::GetLLDBFrame(const llvm::json::Object &arguments) {
- const uint64_t frame_id =
- GetInteger<uint64_t>(arguments, "frameId").value_or(UINT64_MAX);
+lldb::SBFrame DAP::GetLLDBFrame(uint64_t frame_id) {
lldb::SBProcess process = target.GetProcess();
// Upper 32 bits is the thread index ID
lldb::SBThread thread =
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index 88eedb0860cf1..a59591e680435 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -275,7 +275,7 @@ struct DAP {
lldb::SBThread GetLLDBThread(lldb::tid_t id);
lldb::SBThread GetLLDBThread(const llvm::json::Object &arguments);
- lldb::SBFrame GetLLDBFrame(const llvm::json::Object &arguments);
+ lldb::SBFrame GetLLDBFrame(uint64_t frame_id);
llvm::json::Value CreateTopLevelScopes();
diff --git a/lldb/tools/lldb-dap/Handler/CompletionsHandler.cpp b/lldb/tools/lldb-dap/Handler/CompletionsHandler.cpp
index c72fc5686cd5b..65686cd23b243 100644
--- a/lldb/tools/lldb-dap/Handler/CompletionsHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/CompletionsHandler.cpp
@@ -136,7 +136,9 @@ void CompletionsRequestHandler::operator()(
const auto *arguments = request.getObject("arguments");
// If we have a frame, try to set the context for variable completions.
- lldb::SBFrame frame = dap.GetLLDBFrame(*arguments);
+ const uint64_t frame_id =
+ GetInteger<uint64_t>(*arguments, "frameId").value_or(UINT64_MAX);
+ lldb::SBFrame frame = dap.GetLLDBFrame(frame_id);
if (frame.IsValid()) {
frame.GetThread().GetProcess().SetSelectedThread(frame.GetThread());
frame.GetThread().SetSelectedFrame(frame.GetFrameID());
diff --git a/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp
index 4d920f8556254..76407d230438d 100644
--- a/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp
@@ -118,7 +118,10 @@ void DataBreakpointInfoRequestHandler::operator()(
const auto variablesReference =
GetInteger<uint64_t>(arguments, "variablesReference").value_or(0);
llvm::StringRef name = GetString(arguments, "name").value_or("");
- lldb::SBFrame frame = dap.GetLLDBFrame(*arguments);
+
+ const uint64_t frame_id =
+ GetInteger<uint64_t>(arguments, "frameId").value_or(UINT64_MAX);
+ lldb::SBFrame frame = dap.GetLLDBFrame(frame_id);
lldb::SBValue variable = dap.variables.FindVariable(variablesReference, name);
std::string addr, size;
diff --git a/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp
index 5ce133c33b7e1..01b5f956ba953 100644
--- a/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp
@@ -144,7 +144,10 @@ void EvaluateRequestHandler::operator()(
FillResponse(request, response);
llvm::json::Object body;
const auto *arguments = request.getObject("arguments");
- lldb::SBFrame frame = dap.GetLLDBFrame(*arguments);
+
+ const uint64_t frame_id =
+ GetInteger<uint64_t>(arguments, "frameId").value_or(UINT64_MAX);
+ lldb::SBFrame frame = dap.GetLLDBFrame(frame_id);
std::string expression =
GetString(arguments, "expression").value_or("").str();
const llvm::StringRef context = GetString(arguments, "context").value_or("");
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h
index 49ad8dd80c68e..531bee96badb1 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.h
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h
@@ -423,7 +423,7 @@ class PauseRequestHandler : public LegacyRequestHandler {
void operator()(const llvm::json::Object &request) const override;
};
-class ScopesRequestHandler2 final
+class ScopesRequestHandler final
: public RequestHandler<protocol::ScopesArguments,
llvm::Expected<protocol::ScopesResponseBody>> {
public:
@@ -434,10 +434,10 @@ class ScopesRequestHandler2 final
Run(const protocol::ScopesArguments &args) const override;
};
-class ScopesRequestHandler : public LegacyRequestHandler {
+class ScopesRequestHandler2 : public LegacyRequestHandler {
public:
using LegacyRequestHandler::LegacyRequestHandler;
- static llvm::StringLiteral GetCommand() { return "scopes"; }
+ static llvm::StringLiteral GetCommand() { return "scopesr"; }
void operator()(const llvm::json::Object &request) const override;
};
diff --git a/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
index 721230e2525b2..157ec713f4d28 100644
--- a/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
@@ -65,17 +65,85 @@ namespace lldb_dap {
// }]
// }
+protocol::Scope CreateScope2(const llvm::StringRef name,
+ int64_t variablesReference, int64_t namedVariables,
+ bool expensive) {
+ protocol::Scope scope;
+
+ scope.name = name;
+
+ // TODO: Support "arguments" scope. At the moment lldb-dap includes the
+ // arguments into the "locals" scope.
+ // add presentation hint;
+ if (variablesReference == VARREF_LOCALS)
+ scope.presentationHint = protocol::Scope::ePresentationHintLocals;
+ else if (variablesReference == VARREF_REGS)
+ scope.presentationHint = protocol::Scope::ePresentationHintRegisters;
+
+ scope.variablesReference = variablesReference;
+ scope.namedVariables = namedVariables;
+ scope.expensive = expensive;
+
+ return scope;
+}
+
+static std::vector<protocol::Scope> CreateTopLevelScopes(DAP &dap) {
+ std::vector<protocol::Scope> scopes;
+ scopes.reserve(3);
+ scopes.emplace_back(CreateScope2("Locals", VARREF_LOCALS,
+ dap.variables.locals.GetSize(), false));
+ scopes.emplace_back(CreateScope2("Globals", VARREF_GLOBALS,
+ dap.variables.globals.GetSize(), false));
+ scopes.emplace_back(CreateScope2("Registers", VARREF_REGS,
+ dap.variables.registers.GetSize(), false));
+
+ return scopes;
+}
+
llvm::Expected<protocol::ScopesResponseBody>
-ScopesRequestHandler2::Run(const protocol::ScopesArguments &args) const {
- // lldb::SBFrame frame = dap.GetLLDBFrame()
- return llvm::createStringError("something");
+ScopesRequestHandler::Run(const protocol::ScopesArguments &args) const {
+ lldb::SBFrame frame = dap.GetLLDBFrame(args.frameId);
+
+ // As the user selects different stack frames in the GUI, a "scopes" request
+ // will be sent to the DAP. This is the only way we know that the user has
+ // selected a frame in a thread. There are no other notifications that are
+ // sent and VS code doesn't allow multiple frames to show variables
+ // concurrently. If we select the thread and frame as the "scopes" requests
+ // are sent, this allows users to type commands in the debugger console
+ // with a backtick character to run lldb commands and these lldb commands
+ // will now have the right context selected as they are run. If the user
+ // types "`bt" into the debugger console and we had another thread selected
+ // in the LLDB library, we would show the wrong thing to the user. If the
+ // users switches threads with a lldb command like "`thread select 14", the
+ // GUI will not update as there are no "event" notification packets that
+ // allow us to change the currently selected thread or frame in the GUI that
+ // I am aware of.
+ if (frame.IsValid()) {
+ frame.GetThread().GetProcess().SetSelectedThread(frame.GetThread());
+ frame.GetThread().SetSelectedFrame(frame.GetFrameID());
+ }
+ dap.variables.locals = frame.GetVariables(/*arguments=*/true,
+ /*locals=*/true,
+ /*statics=*/false,
+ /*in_scope_only=*/true);
+ dap.variables.globals = frame.GetVariables(/*arguments=*/false,
+ /*locals=*/false,
+ /*statics=*/true,
+ /*in_scope_only=*/true);
+ dap.variables.registers = frame.GetRegisters();
+
+ return protocol::ScopesResponseBody{CreateTopLevelScopes(dap)};
};
-void ScopesRequestHandler::operator()(const llvm::json::Object &request) const {
+void ScopesRequestHandler2::operator()(
+ const llvm::json::Object &request) const {
llvm::json::Object response;
FillResponse(request, response);
llvm::json::Object body;
const auto *arguments = request.getObject("arguments");
- lldb::SBFrame frame = dap.GetLLDBFrame(*arguments);
+
+ const uint64_t frame_id =
+ GetInteger<uint64_t>(arguments, "frameId").value_or(UINT64_MAX);
+ lldb::SBFrame frame = dap.GetLLDBFrame(frame_id);
// As the user selects different stack frames in the GUI, a "scopes" request
// will be sent to the DAP. This is the only way we know that the user has
// selected a frame in a thread. There are no other notifications that are
diff --git a/lldb/tools/lldb-dap/Handler/StepInTargetsRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/StepInTargetsRequestHandler.cpp
index 9b99791599f82..e7c568e9e9e60 100644
--- a/lldb/tools/lldb-dap/Handler/StepInTargetsRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/StepInTargetsRequestHandler.cpp
@@ -74,7 +74,10 @@ void StepInTargetsRequestHandler::operator()(
const auto *arguments = request.getObject("arguments");
dap.step_in_targets.clear();
- lldb::SBFrame frame = dap.GetLLDBFrame(*arguments);
+
+ const uint64_t frame_id =
+ GetInteger<uint64_t>(arguments, "frameId").value_or(UINT64_MAX);
+ lldb::SBFrame frame = dap.GetLLDBFrame(frame_id);
if (frame.IsValid()) {
lldb::SBAddress pc_addr = frame.GetPCAddress();
lldb::SBAddress line_end_addr =
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
index d5630424ebdf7..aa8f193140d7a 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
@@ -267,11 +267,12 @@ bool fromJSON(const llvm::json::Value &Params, ScopesArguments &SCA,
}
llvm::json::Value toJSON(const ScopesResponseBody &SCR) {
- llvm::json::Array body;
+ llvm::json::Array scopes;
for (const Scope &scope : SCR.scopes) {
- body.emplace_back(toJSON(scope));
+ scopes.emplace_back(toJSON(scope));
}
- return body;
+
+ return llvm::json::Object{{"scopes", std::move(scopes)}};
}
bool fromJSON(const json::Value &Params, SourceArguments &SA, json::Path P) {
>From a29da42d9bf12195da70cbe8183d71367c41c0ba Mon Sep 17 00:00:00 2001
From: Ebuka Ezike <yerimyah1 at gmail.com>
Date: Wed, 30 Apr 2025 10:33:28 +0100
Subject: [PATCH 3/4] [lldb][lldb-dap] Remove old functionality
---
lldb/tools/lldb-dap/DAP.cpp | 11 ---
lldb/tools/lldb-dap/DAP.h | 2 -
lldb/tools/lldb-dap/Handler/RequestHandler.h | 7 --
.../lldb-dap/Handler/ScopesRequestHandler.cpp | 59 ++++---------
lldb/tools/lldb-dap/JSONUtils.cpp | 85 -------------------
lldb/tools/lldb-dap/JSONUtils.h | 21 -----
6 files changed, 17 insertions(+), 168 deletions(-)
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index 5d041f2c25da7..cad120ddd0621 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -523,17 +523,6 @@ lldb::SBFrame DAP::GetLLDBFrame(uint64_t frame_id) {
return thread.GetFrameAtIndex(GetLLDBFrameID(frame_id));
}
-llvm::json::Value DAP::CreateTopLevelScopes() {
- llvm::json::Array scopes;
- scopes.emplace_back(
- CreateScope("Locals", VARREF_LOCALS, variables.locals.GetSize(), false));
- scopes.emplace_back(CreateScope("Globals", VARREF_GLOBALS,
- variables.globals.GetSize(), false));
- scopes.emplace_back(CreateScope("Registers", VARREF_REGS,
- variables.registers.GetSize(), false));
- return llvm::json::Value(std::move(scopes));
-}
-
ReplMode DAP::DetectReplMode(lldb::SBFrame frame, std::string &expression,
bool partial_expression) {
// Check for the escape hatch prefix.
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index a59591e680435..d0b4e3987b88c 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -277,8 +277,6 @@ struct DAP {
lldb::SBFrame GetLLDBFrame(uint64_t frame_id);
- llvm::json::Value CreateTopLevelScopes();
-
void PopulateExceptionBreakpoints();
/// Attempt to determine if an expression is a variable expression or
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h
index 531bee96badb1..00eeb66f10670 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.h
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h
@@ -434,13 +434,6 @@ class ScopesRequestHandler final
Run(const protocol::ScopesArguments &args) const override;
};
-class ScopesRequestHandler2 : public LegacyRequestHandler {
-public:
- using LegacyRequestHandler::LegacyRequestHandler;
- static llvm::StringLiteral GetCommand() { return "scopesr"; }
- void operator()(const llvm::json::Object &request) const override;
-};
-
class SetVariableRequestHandler : public LegacyRequestHandler {
public:
using LegacyRequestHandler::LegacyRequestHandler;
diff --git a/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
index 157ec713f4d28..6f6fa18dcaa2c 100644
--- a/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
@@ -65,6 +65,23 @@ namespace lldb_dap {
// }]
// }
+/// Create a "Scope" JSON object as described in the debug adapter definition.
+///
+/// \param[in] name
+/// The value to place into the "name" key
+//
+/// \param[in] variablesReference
+/// The value to place into the "variablesReference" key
+//
+/// \param[in] namedVariables
+/// The value to place into the "namedVariables" key
+//
+/// \param[in] expensive
+/// The value to place into the "expensive" key
+///
+/// \return
+/// A "Scope" JSON object with that follows the formal JSON
+/// definition outlined by Microsoft.
protocol::Scope CreateScope2(const llvm::StringRef name,
int64_t variablesReference, int64_t namedVariables,
bool expensive) {
@@ -133,48 +150,6 @@ ScopesRequestHandler::Run(const protocol::ScopesArguments &args) const {
dap.variables.registers = frame.GetRegisters();
return protocol::ScopesResponseBody{CreateTopLevelScopes(dap)};
-};
-void ScopesRequestHandler2::operator()(
- const llvm::json::Object &request) const {
- llvm::json::Object response;
- FillResponse(request, response);
- llvm::json::Object body;
- const auto *arguments = request.getObject("arguments");
-
- const uint64_t frame_id =
- GetInteger<uint64_t>(arguments, "frameId").value_or(UINT64_MAX);
- lldb::SBFrame frame = dap.GetLLDBFrame(frame_id);
- // As the user selects different stack frames in the GUI, a "scopes" request
- // will be sent to the DAP. This is the only way we know that the user has
- // selected a frame in a thread. There are no other notifications that are
- // sent and VS code doesn't allow multiple frames to show variables
- // concurrently. If we select the thread and frame as the "scopes" requests
- // are sent, this allows users to type commands in the debugger console
- // with a backtick character to run lldb commands and these lldb commands
- // will now have the right context selected as they are run. If the user
- // types "`bt" into the debugger console and we had another thread selected
- // in the LLDB library, we would show the wrong thing to the user. If the
- // users switches threads with a lldb command like "`thread select 14", the
- // GUI will not update as there are no "event" notification packets that
- // allow us to change the currently selected thread or frame in the GUI that
- // I am aware of.
- if (frame.IsValid()) {
- frame.GetThread().GetProcess().SetSelectedThread(frame.GetThread());
- frame.GetThread().SetSelectedFrame(frame.GetFrameID());
- }
-
- dap.variables.locals = frame.GetVariables(/*arguments=*/true,
- /*locals=*/true,
- /*statics=*/false,
- /*in_scope_only=*/true);
- dap.variables.globals = frame.GetVariables(/*arguments=*/false,
- /*locals=*/false,
- /*statics=*/true,
- /*in_scope_only=*/true);
- dap.variables.registers = frame.GetRegisters();
- body.try_emplace("scopes", dap.CreateTopLevelScopes());
- response.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(response)));
}
} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp
index 4409cf5b27e5b..5e6f1f3942dc5 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -269,91 +269,6 @@ void FillResponse(const llvm::json::Object &request,
response.try_emplace("success", true);
}
-// "Scope": {
-// "type": "object",
-// "description": "A Scope is a named container for variables. Optionally
-// a scope can map to a source or a range within a source.",
-// "properties": {
-// "name": {
-// "type": "string",
-// "description": "Name of the scope such as 'Arguments', 'Locals'."
-// },
-// "presentationHint": {
-// "type": "string",
-// "description": "An optional hint for how to present this scope in the
-// UI. If this attribute is missing, the scope is shown
-// with a generic UI.",
-// "_enum": [ "arguments", "locals", "registers" ],
-// },
-// "variablesReference": {
-// "type": "integer",
-// "description": "The variables of this scope can be retrieved by
-// passing the value of variablesReference to the
-// VariablesRequest."
-// },
-// "namedVariables": {
-// "type": "integer",
-// "description": "The number of named variables in this scope. The
-// client can use this optional information to present
-// the variables in a paged UI and fetch them in chunks."
-// },
-// "indexedVariables": {
-// "type": "integer",
-// "description": "The number of indexed variables in this scope. The
-// client can use this optional information to present
-// the variables in a paged UI and fetch them in chunks."
-// },
-// "expensive": {
-// "type": "boolean",
-// "description": "If true, the number of variables in this scope is
-// large or expensive to retrieve."
-// },
-// "source": {
-// "$ref": "#/definitions/Source",
-// "description": "Optional source for this scope."
-// },
-// "line": {
-// "type": "integer",
-// "description": "Optional start line of the range covered by this
-// scope."
-// },
-// "column": {
-// "type": "integer",
-// "description": "Optional start column of the range covered by this
-// scope."
-// },
-// "endLine": {
-// "type": "integer",
-// "description": "Optional end line of the range covered by this scope."
-// },
-// "endColumn": {
-// "type": "integer",
-// "description": "Optional end column of the range covered by this
-// scope."
-// }
-// },
-// "required": [ "name", "variablesReference", "expensive" ]
-// }
-llvm::json::Value CreateScope(const llvm::StringRef name,
- int64_t variablesReference,
- int64_t namedVariables, bool expensive) {
- llvm::json::Object object;
- EmplaceSafeString(object, "name", name.str());
-
- // TODO: Support "arguments" scope. At the moment lldb-dap includes the
- // arguments into the "locals" scope.
- if (variablesReference == VARREF_LOCALS) {
- object.try_emplace("presentationHint", "locals");
- } else if (variablesReference == VARREF_REGS) {
- object.try_emplace("presentationHint", "registers");
- }
-
- object.try_emplace("variablesReference", variablesReference);
- object.try_emplace("expensive", expensive);
- object.try_emplace("namedVariables", namedVariables);
- return llvm::json::Value(std::move(object));
-}
-
// "Breakpoint": {
// "type": "object",
// "description": "Information about a Breakpoint created in setBreakpoints
diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h
index d0e20729f4ed9..1d9f72c3763c9 100644
--- a/lldb/tools/lldb-dap/JSONUtils.h
+++ b/lldb/tools/lldb-dap/JSONUtils.h
@@ -294,27 +294,6 @@ llvm::json::Object CreateEventObject(const llvm::StringRef event_name);
protocol::ExceptionBreakpointsFilter
CreateExceptionBreakpointFilter(const ExceptionBreakpoint &bp);
-/// Create a "Scope" JSON object as described in the debug adapter definition.
-///
-/// \param[in] name
-/// The value to place into the "name" key
-//
-/// \param[in] variablesReference
-/// The value to place into the "variablesReference" key
-//
-/// \param[in] namedVariables
-/// The value to place into the "namedVariables" key
-//
-/// \param[in] expensive
-/// The value to place into the "expensive" key
-///
-/// \return
-/// A "Scope" JSON object with that follows the formal JSON
-/// definition outlined by Microsoft.
-llvm::json::Value CreateScope(const llvm::StringRef name,
- int64_t variablesReference,
- int64_t namedVariables, bool expensive);
-
/// Create a "Source" JSON object as described in the debug adapter definition.
///
/// \param[in] file
>From d539169524a91cb51aca42393ee4ebebd699913d Mon Sep 17 00:00:00 2001
From: Ebuka Ezike <yerimyah1 at gmail.com>
Date: Thu, 1 May 2025 01:44:47 +0100
Subject: [PATCH 4/4] [lldb][lldb-dap] Cleanup
---
.../lldb-dap/Handler/ScopesRequestHandler.cpp | 118 +++++-------------
1 file changed, 30 insertions(+), 88 deletions(-)
diff --git a/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
index 6f6fa18dcaa2c..d9dd29f7269f2 100644
--- a/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
@@ -7,95 +7,43 @@
//===----------------------------------------------------------------------===//
#include "DAP.h"
-#include "EventHelper.h"
-#include "JSONUtils.h"
#include "RequestHandler.h"
+using namespace lldb_dap::protocol;
namespace lldb_dap {
-// "ScopesRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "Scopes request; value of command field is 'scopes'. The
-// request returns the variable scopes for a given stackframe ID.",
-// "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "scopes" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/ScopesArguments"
-// }
-// },
-// "required": [ "command", "arguments" ]
-// }]
-// },
-// "ScopesArguments": {
-// "type": "object",
-// "description": "Arguments for 'scopes' request.",
-// "properties": {
-// "frameId": {
-// "type": "integer",
-// "description": "Retrieve the scopes for this stackframe."
-// }
-// },
-// "required": [ "frameId" ]
-// },
-// "ScopesResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to 'scopes' request.",
-// "properties": {
-// "body": {
-// "type": "object",
-// "properties": {
-// "scopes": {
-// "type": "array",
-// "items": {
-// "$ref": "#/definitions/Scope"
-// },
-// "description": "The scopes of the stackframe. If the array has
-// length zero, there are no scopes available."
-// }
-// },
-// "required": [ "scopes" ]
-// }
-// },
-// "required": [ "body" ]
-// }]
-// }
-
-/// Create a "Scope" JSON object as described in the debug adapter definition.
+/// Creates a `protocol::Scope` struct.
+///
///
/// \param[in] name
/// The value to place into the "name" key
-//
+///
/// \param[in] variablesReference
/// The value to place into the "variablesReference" key
-//
+///
/// \param[in] namedVariables
/// The value to place into the "namedVariables" key
-//
+///
/// \param[in] expensive
/// The value to place into the "expensive" key
///
/// \return
-/// A "Scope" JSON object with that follows the formal JSON
-/// definition outlined by Microsoft.
-protocol::Scope CreateScope2(const llvm::StringRef name,
- int64_t variablesReference, int64_t namedVariables,
- bool expensive) {
- protocol::Scope scope;
-
+/// A `protocol::Scope`
+static Scope CreateScope(const llvm::StringRef name, int64_t variablesReference,
+ int64_t namedVariables, bool expensive) {
+ Scope scope;
scope.name = name;
- // TODO: Support "arguments" scope. At the moment lldb-dap includes the
- // arguments into the "locals" scope.
- // add presentation hint;
+ // TODO: Support "arguments" and "return value" scope.
+ // At the moment lldb-dap includes the arguments and return_value into the
+ // "locals" scope. add presentation hint;
+ // vscode only expands the first non-expensive scope, this causes friction
+ // as the locals scope will not be expanded. It becomes more annoying when
+ // the scope has arguments, return_value and locals.
if (variablesReference == VARREF_LOCALS)
- scope.presentationHint = protocol::Scope::ePresentationHintLocals;
+ scope.presentationHint = Scope::ePresentationHintLocals;
else if (variablesReference == VARREF_REGS)
- scope.presentationHint = protocol::Scope::ePresentationHintRegisters;
+ scope.presentationHint = Scope::ePresentationHintRegisters;
scope.variablesReference = variablesReference;
scope.namedVariables = namedVariables;
@@ -104,21 +52,8 @@ protocol::Scope CreateScope2(const llvm::StringRef name,
return scope;
}
-static std::vector<protocol::Scope> CreateTopLevelScopes(DAP &dap) {
- std::vector<protocol::Scope> scopes;
- scopes.reserve(3);
- scopes.emplace_back(CreateScope2("Locals", VARREF_LOCALS,
- dap.variables.locals.GetSize(), false));
- scopes.emplace_back(CreateScope2("Globals", VARREF_GLOBALS,
- dap.variables.globals.GetSize(), false));
- scopes.emplace_back(CreateScope2("Registers", VARREF_REGS,
- dap.variables.registers.GetSize(), false));
-
- return scopes;
-}
-
-llvm::Expected<protocol::ScopesResponseBody>
-ScopesRequestHandler::Run(const protocol::ScopesArguments &args) const {
+llvm::Expected<ScopesResponseBody>
+ScopesRequestHandler::Run(const ScopesArguments &args) const {
lldb::SBFrame frame = dap.GetLLDBFrame(args.frameId);
// As the user selects different stack frames in the GUI, a "scopes" request
@@ -129,9 +64,9 @@ ScopesRequestHandler::Run(const protocol::ScopesArguments &args) const {
// are sent, this allows users to type commands in the debugger console
// with a backtick character to run lldb commands and these lldb commands
// will now have the right context selected as they are run. If the user
- // types "`bt" into the debugger console and we had another thread selected
+ // types "`bt" into the debugger console, and we had another thread selected
// in the LLDB library, we would show the wrong thing to the user. If the
- // users switches threads with a lldb command like "`thread select 14", the
+ // users switch threads with a lldb command like "`thread select 14", the
// GUI will not update as there are no "event" notification packets that
// allow us to change the currently selected thread or frame in the GUI that
// I am aware of.
@@ -149,7 +84,14 @@ ScopesRequestHandler::Run(const protocol::ScopesArguments &args) const {
/*in_scope_only=*/true);
dap.variables.registers = frame.GetRegisters();
- return protocol::ScopesResponseBody{CreateTopLevelScopes(dap)};
+ std::vector scopes = {CreateScope("Locals", VARREF_LOCALS,
+ dap.variables.locals.GetSize(), false),
+ CreateScope("Globals", VARREF_GLOBALS,
+ dap.variables.globals.GetSize(), false),
+ CreateScope("Registers", VARREF_REGS,
+ dap.variables.registers.GetSize(), false)};
+
+ return ScopesResponseBody{std::move(scopes)};
}
} // namespace lldb_dap
More information about the lldb-commits
mailing list