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 <yerimy...@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 <yerimy...@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 <yerimy...@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 <yerimy...@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 _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits