https://github.com/ashgti updated https://github.com/llvm/llvm-project/pull/142510
>From 2fd86de03cbb207420ee45334639f82d19dbbc28 Mon Sep 17 00:00:00 2001 From: John Harrison <harj...@google.com> Date: Mon, 2 Jun 2025 18:07:52 -0700 Subject: [PATCH 1/2] [lldb-dap] Migrating 'threads' request to structured types. Moving `threads` request to structured types. Adding helper types for this and moving helpers from JSONUtils to ProtocolUtils. --- .../test/tools/lldb-dap/dap_server.py | 4 +- .../tools/lldb-dap/threads/TestDAP_threads.py | 2 +- lldb/tools/lldb-dap/DAP.h | 2 +- lldb/tools/lldb-dap/EventHelper.cpp | 132 +++++++++--------- lldb/tools/lldb-dap/EventHelper.h | 2 +- .../ConfigurationDoneRequestHandler.cpp | 3 +- lldb/tools/lldb-dap/Handler/RequestHandler.h | 9 +- .../Handler/RestartRequestHandler.cpp | 2 +- .../Handler/ThreadsRequestHandler.cpp | 75 +++------- lldb/tools/lldb-dap/JSONUtils.cpp | 64 --------- lldb/tools/lldb-dap/JSONUtils.h | 24 ---- .../lldb-dap/Protocol/ProtocolRequests.cpp | 118 +++++++--------- .../lldb-dap/Protocol/ProtocolRequests.h | 10 ++ .../tools/lldb-dap/Protocol/ProtocolTypes.cpp | 9 ++ lldb/tools/lldb-dap/Protocol/ProtocolTypes.h | 10 ++ lldb/tools/lldb-dap/ProtocolUtils.cpp | 50 +++++++ lldb/tools/lldb-dap/ProtocolUtils.h | 26 ++++ 17 files changed, 258 insertions(+), 284 deletions(-) diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py index 6b41aef2bb5b8..71bae5c4ea035 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py @@ -308,7 +308,6 @@ def _handle_recv_packet(self, packet: Optional[ProtocolMessage]) -> bool: return keepGoing def _process_continued(self, all_threads_continued: bool): - self.threads = None self.frame_scopes = {} if all_threads_continued: self.thread_stop_reasons = {} @@ -1180,6 +1179,9 @@ def request_threads(self): with information about all threads""" command_dict = {"command": "threads", "type": "request", "arguments": {}} response = self.send_recv(command_dict) + if not response["success"]: + self.threads = None + return response body = response["body"] # Fill in "self.threads" correctly so that clients that call # self.get_threads() or self.get_thread_id(...) can get information diff --git a/lldb/test/API/tools/lldb-dap/threads/TestDAP_threads.py b/lldb/test/API/tools/lldb-dap/threads/TestDAP_threads.py index acd6108853787..15bae3cc83daf 100644 --- a/lldb/test/API/tools/lldb-dap/threads/TestDAP_threads.py +++ b/lldb/test/API/tools/lldb-dap/threads/TestDAP_threads.py @@ -33,7 +33,7 @@ def test_correct_thread(self): self.dap_server.request_continue() stopped_event = self.dap_server.wait_for_stopped() # Verify that the description is the relevant breakpoint, - # preserveFocusHint is False and threadCausedFocus is True + # preserveFocusHint is False. self.assertTrue( stopped_event[0]["body"]["description"].startswith( "breakpoint %s." % breakpoint_ids[0] diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h index 1bd94fab402ca..0bde0ba0c9830 100644 --- a/lldb/tools/lldb-dap/DAP.h +++ b/lldb/tools/lldb-dap/DAP.h @@ -152,7 +152,7 @@ struct DAP { llvm::DenseSet<ClientFeature> clientFeatures; /// The initial thread list upon attaching. - std::optional<llvm::json::Array> initial_thread_list; + std::optional<std::vector<protocol::Thread>> initial_thread_list; /// Keep track of all the modules our client knows about: either through the /// modules request or the module events. diff --git a/lldb/tools/lldb-dap/EventHelper.cpp b/lldb/tools/lldb-dap/EventHelper.cpp index c698084836e2f..6b5274ea8997b 100644 --- a/lldb/tools/lldb-dap/EventHelper.cpp +++ b/lldb/tools/lldb-dap/EventHelper.cpp @@ -116,77 +116,79 @@ void SendProcessEvent(DAP &dap, LaunchMethod launch_method) { // Send a thread stopped event for all threads as long as the process // is stopped. -void SendThreadStoppedEvent(DAP &dap) { +void SendThreadStoppedEvent(DAP &dap, bool on_entry) { lldb::SBProcess process = dap.target.GetProcess(); - if (process.IsValid()) { - auto state = process.GetState(); - if (state == lldb::eStateStopped) { - llvm::DenseSet<lldb::tid_t> old_thread_ids; - old_thread_ids.swap(dap.thread_ids); - uint32_t stop_id = process.GetStopID(); - const uint32_t num_threads = process.GetNumThreads(); - - // First make a pass through the threads to see if the focused thread - // has a stop reason. In case the focus thread doesn't have a stop - // reason, remember the first thread that has a stop reason so we can - // set it as the focus thread if below if needed. - lldb::tid_t first_tid_with_reason = LLDB_INVALID_THREAD_ID; - uint32_t num_threads_with_reason = 0; - bool focus_thread_exists = false; - for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx) { - lldb::SBThread thread = process.GetThreadAtIndex(thread_idx); - const lldb::tid_t tid = thread.GetThreadID(); - const bool has_reason = ThreadHasStopReason(thread); - // If the focus thread doesn't have a stop reason, clear the thread ID - if (tid == dap.focus_tid) { - focus_thread_exists = true; - if (!has_reason) - dap.focus_tid = LLDB_INVALID_THREAD_ID; - } - if (has_reason) { - ++num_threads_with_reason; - if (first_tid_with_reason == LLDB_INVALID_THREAD_ID) - first_tid_with_reason = tid; - } - } + if (!process.IsValid()) { + DAP_LOG(dap.log, "error: SendThreadStoppedEvent() invalid process"); + return; + } - // We will have cleared dap.focus_tid if the focus thread doesn't have - // a stop reason, so if it was cleared, or wasn't set, or doesn't exist, - // then set the focus thread to the first thread with a stop reason. - if (!focus_thread_exists || dap.focus_tid == LLDB_INVALID_THREAD_ID) - dap.focus_tid = first_tid_with_reason; - - // If no threads stopped with a reason, then report the first one so - // we at least let the UI know we stopped. - if (num_threads_with_reason == 0) { - lldb::SBThread thread = process.GetThreadAtIndex(0); - dap.focus_tid = thread.GetThreadID(); - dap.SendJSON(CreateThreadStopped(dap, thread, stop_id)); - } else { - for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx) { - lldb::SBThread thread = process.GetThreadAtIndex(thread_idx); - dap.thread_ids.insert(thread.GetThreadID()); - if (ThreadHasStopReason(thread)) { - dap.SendJSON(CreateThreadStopped(dap, thread, stop_id)); - } - } - } + lldb::StateType state = process.GetState(); + if (!lldb::SBDebugger::StateIsStoppedState(state)) { + DAP_LOG(dap.log, + "error: SendThreadStoppedEvent() when process isn't stopped ({0})", + lldb::SBDebugger::StateAsCString(state)); + return; + } - for (auto tid : old_thread_ids) { - auto end = dap.thread_ids.end(); - auto pos = dap.thread_ids.find(tid); - if (pos == end) - SendThreadExitedEvent(dap, tid); - } - } else { - DAP_LOG( - dap.log, - "error: SendThreadStoppedEvent() when process isn't stopped ({0})", - lldb::SBDebugger::StateAsCString(state)); + llvm::DenseSet<lldb::tid_t> old_thread_ids; + old_thread_ids.swap(dap.thread_ids); + uint32_t stop_id = on_entry ? 0 : process.GetStopID(); + const uint32_t num_threads = process.GetNumThreads(); + + // First make a pass through the threads to see if the focused thread + // has a stop reason. In case the focus thread doesn't have a stop + // reason, remember the first thread that has a stop reason so we can + // set it as the focus thread if below if needed. + lldb::tid_t first_tid_with_reason = LLDB_INVALID_THREAD_ID; + uint32_t num_threads_with_reason = 0; + bool focus_thread_exists = false; + for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx) { + lldb::SBThread thread = process.GetThreadAtIndex(thread_idx); + const lldb::tid_t tid = thread.GetThreadID(); + const bool has_reason = ThreadHasStopReason(thread); + // If the focus thread doesn't have a stop reason, clear the thread ID + if (tid == dap.focus_tid) { + focus_thread_exists = true; + if (!has_reason) + dap.focus_tid = LLDB_INVALID_THREAD_ID; } + if (has_reason) { + ++num_threads_with_reason; + if (first_tid_with_reason == LLDB_INVALID_THREAD_ID) + first_tid_with_reason = tid; + } + } + + // We will have cleared dap.focus_tid if the focus thread doesn't have + // a stop reason, so if it was cleared, or wasn't set, or doesn't exist, + // then set the focus thread to the first thread with a stop reason. + if (!focus_thread_exists || dap.focus_tid == LLDB_INVALID_THREAD_ID) + dap.focus_tid = first_tid_with_reason; + + // If no threads stopped with a reason, then report the first one so + // we at least let the UI know we stopped. + if (num_threads_with_reason == 0) { + lldb::SBThread thread = process.GetThreadAtIndex(0); + dap.focus_tid = thread.GetThreadID(); + dap.SendJSON(CreateThreadStopped(dap, thread, stop_id)); } else { - DAP_LOG(dap.log, "error: SendThreadStoppedEvent() invalid process"); + for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx) { + lldb::SBThread thread = process.GetThreadAtIndex(thread_idx); + dap.thread_ids.insert(thread.GetThreadID()); + if (ThreadHasStopReason(thread)) { + dap.SendJSON(CreateThreadStopped(dap, thread, stop_id)); + } + } } + + for (auto tid : old_thread_ids) { + auto end = dap.thread_ids.end(); + auto pos = dap.thread_ids.find(tid); + if (pos == end) + SendThreadExitedEvent(dap, tid); + } + dap.RunStopCommands(); } diff --git a/lldb/tools/lldb-dap/EventHelper.h b/lldb/tools/lldb-dap/EventHelper.h index 90b009c73089e..d77a7879aae28 100644 --- a/lldb/tools/lldb-dap/EventHelper.h +++ b/lldb/tools/lldb-dap/EventHelper.h @@ -18,7 +18,7 @@ enum LaunchMethod { Launch, Attach, AttachForSuspendedLaunch }; void SendProcessEvent(DAP &dap, LaunchMethod launch_method); -void SendThreadStoppedEvent(DAP &dap); +void SendThreadStoppedEvent(DAP &dap, bool on_entry = false); void SendTerminatedEvent(DAP &dap); diff --git a/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp index 1281857ef4b60..a262cf32a9d47 100644 --- a/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp @@ -10,6 +10,7 @@ #include "EventHelper.h" #include "JSONUtils.h" #include "Protocol/ProtocolRequests.h" +#include "ProtocolUtils.h" #include "RequestHandler.h" #include "lldb/API/SBDebugger.h" @@ -51,7 +52,7 @@ ConfigurationDoneRequestHandler::Run(const ConfigurationDoneArguments &) const { SendProcessEvent(dap, dap.is_attach ? Attach : Launch); if (dap.stop_at_entry) - SendThreadStoppedEvent(dap); + SendThreadStoppedEvent(dap, /*on_entry=*/true); else process.Continue(); diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h index 3a965bcc87a5e..d68dcc2b89be7 100644 --- a/lldb/tools/lldb-dap/Handler/RequestHandler.h +++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h @@ -522,11 +522,14 @@ class StackTraceRequestHandler : public LegacyRequestHandler { } }; -class ThreadsRequestHandler : public LegacyRequestHandler { +class ThreadsRequestHandler + : public RequestHandler<protocol::ThreadsArguments, + llvm::Expected<protocol::ThreadsResponseBody>> { public: - using LegacyRequestHandler::LegacyRequestHandler; + using RequestHandler::RequestHandler; static llvm::StringLiteral GetCommand() { return "threads"; } - void operator()(const llvm::json::Object &request) const override; + llvm::Expected<protocol::ThreadsResponseBody> + Run(const protocol::ThreadsArguments &) const override; }; class VariablesRequestHandler : public LegacyRequestHandler { diff --git a/lldb/tools/lldb-dap/Handler/RestartRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/RestartRequestHandler.cpp index 58091b622f7e5..b7f06a7ba1b80 100644 --- a/lldb/tools/lldb-dap/Handler/RestartRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/RestartRequestHandler.cpp @@ -145,7 +145,7 @@ void RestartRequestHandler::operator()( // Because we're restarting, configuration has already happened so we can // continue the process right away. if (dap.stop_at_entry) { - SendThreadStoppedEvent(dap); + SendThreadStoppedEvent(dap, /*on_entry=*/true); } else { dap.target.GetProcess().Continue(); } diff --git a/lldb/tools/lldb-dap/Handler/ThreadsRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ThreadsRequestHandler.cpp index 16d797c2ab327..dd6178567e918 100644 --- a/lldb/tools/lldb-dap/Handler/ThreadsRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/ThreadsRequestHandler.cpp @@ -8,52 +8,25 @@ #include "DAP.h" #include "EventHelper.h" -#include "JSONUtils.h" +#include "Protocol/ProtocolRequests.h" +#include "ProtocolUtils.h" #include "RequestHandler.h" +#include "lldb/API/SBDebugger.h" +#include "lldb/API/SBDefines.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/raw_ostream.h" + +using namespace llvm; +using namespace lldb_dap::protocol; namespace lldb_dap { -// "ThreadsRequest": { -// "allOf": [ { "$ref": "#/definitions/Request" }, { -// "type": "object", -// "description": "Thread request; value of command field is 'threads'. The -// request retrieves a list of all threads.", "properties": { -// "command": { -// "type": "string", -// "enum": [ "threads" ] -// } -// }, -// "required": [ "command" ] -// }] -// }, -// "ThreadsResponse": { -// "allOf": [ { "$ref": "#/definitions/Response" }, { -// "type": "object", -// "description": "Response to 'threads' request.", -// "properties": { -// "body": { -// "type": "object", -// "properties": { -// "threads": { -// "type": "array", -// "items": { -// "$ref": "#/definitions/Thread" -// }, -// "description": "All threads." -// } -// }, -// "required": [ "threads" ] -// } -// }, -// "required": [ "body" ] -// }] -// } -void ThreadsRequestHandler::operator()( - const llvm::json::Object &request) const { - llvm::json::Object response; - FillResponse(request, response); +/// The request retrieves a list of all threads. +Expected<ThreadsResponseBody> +ThreadsRequestHandler::Run(const ThreadsArguments &) const { + lldb::SBProcess process = dap.target.GetProcess(); + std::vector<Thread> threads; - llvm::json::Array threads; // Client requests the baseline of currently existing threads after // a successful launch or attach by sending a 'threads' request // right after receiving the configurationDone response. @@ -61,19 +34,17 @@ void ThreadsRequestHandler::operator()( // like the pause request from working in the running state. // Return the cache of initial threads as the process might have resumed if (dap.initial_thread_list) { - threads = dap.initial_thread_list.value(); + threads = *dap.initial_thread_list; dap.initial_thread_list.reset(); - } else { - threads = GetThreads(dap.target.GetProcess(), dap.thread_format); - } + } else if (!lldb::SBDebugger::StateIsStoppedState(process.GetState())) + return make_error<NotStoppedError>(); + else + threads = GetThreads(process, dap.thread_format); + + if (threads.size() == 0) + return make_error<DAPError>("failed to retrieve threads from process"); - if (threads.size() == 0) { - response["success"] = llvm::json::Value(false); - } - llvm::json::Object body; - body.try_emplace("threads", std::move(threads)); - response.try_emplace("body", std::move(body)); - dap.SendJSON(llvm::json::Value(std::move(response))); + return ThreadsResponseBody{threads}; } } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp index 573f3eba00f62..6cdde63e9796e 100644 --- a/lldb/tools/lldb-dap/JSONUtils.cpp +++ b/lldb/tools/lldb-dap/JSONUtils.cpp @@ -643,70 +643,6 @@ llvm::json::Value CreateExtendedStackFrameLabel(lldb::SBThread &thread, {"presentationHint", "label"}}); } -// "Thread": { -// "type": "object", -// "description": "A Thread", -// "properties": { -// "id": { -// "type": "integer", -// "description": "Unique identifier for the thread." -// }, -// "name": { -// "type": "string", -// "description": "A name of the thread." -// } -// }, -// "required": [ "id", "name" ] -// } -llvm::json::Value CreateThread(lldb::SBThread &thread, lldb::SBFormat &format) { - llvm::json::Object object; - object.try_emplace("id", (int64_t)thread.GetThreadID()); - std::string thread_str; - lldb::SBStream stream; - if (format && thread.GetDescriptionWithFormat(format, stream).Success()) { - thread_str = stream.GetData(); - } else { - llvm::StringRef thread_name(thread.GetName()); - llvm::StringRef queue_name(thread.GetQueueName()); - - if (!thread_name.empty()) { - thread_str = thread_name.str(); - } else if (!queue_name.empty()) { - auto kind = thread.GetQueue().GetKind(); - std::string queue_kind_label = ""; - if (kind == lldb::eQueueKindSerial) { - queue_kind_label = " (serial)"; - } else if (kind == lldb::eQueueKindConcurrent) { - queue_kind_label = " (concurrent)"; - } - - thread_str = - llvm::formatv("Thread {0} Queue: {1}{2}", thread.GetIndexID(), - queue_name, queue_kind_label) - .str(); - } else { - thread_str = llvm::formatv("Thread {0}", thread.GetIndexID()).str(); - } - } - - EmplaceSafeString(object, "name", thread_str); - - return llvm::json::Value(std::move(object)); -} - -llvm::json::Array GetThreads(lldb::SBProcess process, lldb::SBFormat &format) { - lldb::SBMutex lock = process.GetTarget().GetAPIMutex(); - std::lock_guard<lldb::SBMutex> guard(lock); - - llvm::json::Array threads; - const uint32_t num_threads = process.GetNumThreads(); - for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx) { - lldb::SBThread thread = process.GetThreadAtIndex(thread_idx); - threads.emplace_back(CreateThread(thread, format)); - } - return threads; -} - // "StoppedEvent": { // "allOf": [ { "$ref": "#/definitions/Event" }, { // "type": "object", diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h index 08699a94bbd87..10dc46b94184f 100644 --- a/lldb/tools/lldb-dap/JSONUtils.h +++ b/lldb/tools/lldb-dap/JSONUtils.h @@ -283,30 +283,6 @@ llvm::json::Value CreateStackFrame(lldb::SBFrame &frame, llvm::json::Value CreateExtendedStackFrameLabel(lldb::SBThread &thread, lldb::SBFormat &format); -/// Create a "Thread" object for a LLDB thread object. -/// -/// This function will fill in the following keys in the returned -/// object: -/// "id" - the thread ID as an integer -/// "name" - the thread name as a string which combines the LLDB -/// thread index ID along with the string name of the thread -/// from the OS if it has a name. -/// -/// \param[in] thread -/// The LLDB thread to use when populating out the "Thread" -/// object. -/// -/// \param[in] format -/// The LLDB format to use when populating out the "Thread" -/// object. -/// -/// \return -/// A "Thread" JSON object with that follows the formal JSON -/// definition outlined by Microsoft. -llvm::json::Value CreateThread(lldb::SBThread &thread, lldb::SBFormat &format); - -llvm::json::Array GetThreads(lldb::SBProcess process, lldb::SBFormat &format); - /// Create a "StoppedEvent" object for a LLDB thread object. /// /// This function will fill in the following keys in the returned diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp index 4160077d419e1..2cb7c47d60203 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp +++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp @@ -140,9 +140,8 @@ parseSourceMap(const json::Value &Params, namespace lldb_dap::protocol { -bool fromJSON(const llvm::json::Value &Params, CancelArguments &CA, - llvm::json::Path P) { - llvm::json::ObjectMapper O(Params, P); +bool fromJSON(const json::Value &Params, CancelArguments &CA, json::Path P) { + json::ObjectMapper O(Params, P); return O && O.map("requestId", CA.requestId) && O.map("progressId", CA.progressId); } @@ -150,9 +149,9 @@ bool fromJSON(const llvm::json::Value &Params, CancelArguments &CA, bool fromJSON(const json::Value &Params, DisconnectArguments &DA, json::Path P) { json::ObjectMapper O(Params, P); - return O && O.map("restart", DA.restart) && - O.map("terminateDebuggee", DA.terminateDebuggee) && - O.map("suspendDebuggee", DA.suspendDebuggee); + return O && O.mapOptional("restart", DA.restart) && + O.mapOptional("terminateDebuggee", DA.terminateDebuggee) && + O.mapOptional("suspendDebuggee", DA.suspendDebuggee); } bool fromJSON(const json::Value &Params, PathFormat &PF, json::Path P) { @@ -257,12 +256,8 @@ bool fromJSON(const json::Value &Params, BreakpointLocationsArguments &BLA, O.mapOptional("endColumn", BLA.endColumn); } -llvm::json::Value toJSON(const BreakpointLocationsResponseBody &BLRB) { - llvm::json::Array breakpoints_json; - for (const auto &breakpoint : BLRB.breakpoints) { - breakpoints_json.push_back(toJSON(breakpoint)); - } - return llvm::json::Object{{"breakpoints", std::move(breakpoints_json)}}; +json::Value toJSON(const BreakpointLocationsResponseBody &BLRB) { + return json::Object{{"breakpoints", BLRB.breakpoints}}; } bool fromJSON(const json::Value &Params, LaunchRequestArguments &LRA, @@ -293,27 +288,26 @@ bool fromJSON(const json::Value &Params, AttachRequestArguments &ARA, O.mapOptional("coreFile", ARA.coreFile); } -bool fromJSON(const llvm::json::Value &Params, ContinueArguments &CA, - llvm::json::Path P) { +bool fromJSON(const json::Value &Params, ContinueArguments &CA, json::Path P) { json::ObjectMapper O(Params, P); return O && O.map("threadId", CA.threadId) && O.mapOptional("singleThread", CA.singleThread); } -llvm::json::Value toJSON(const ContinueResponseBody &CRB) { +json::Value toJSON(const ContinueResponseBody &CRB) { json::Object Body{{"allThreadsContinued", CRB.allThreadsContinued}}; return std::move(Body); } -bool fromJSON(const llvm::json::Value &Params, SetVariableArguments &SVA, - llvm::json::Path P) { +bool fromJSON(const json::Value &Params, SetVariableArguments &SVA, + json::Path P) { json::ObjectMapper O(Params, P); return O && O.map("variablesReference", SVA.variablesReference) && O.map("name", SVA.name) && O.map("value", SVA.value) && O.mapOptional("format", SVA.format); } -llvm::json::Value toJSON(const SetVariableResponseBody &SVR) { +json::Value toJSON(const SetVariableResponseBody &SVR) { json::Object Body{{"value", SVR.value}}; if (SVR.type.has_value()) Body.insert({"type", SVR.type}); @@ -333,21 +327,15 @@ llvm::json::Value toJSON(const SetVariableResponseBody &SVR) { if (SVR.valueLocationReference.has_value()) Body.insert({"valueLocationReference", SVR.valueLocationReference}); - return llvm::json::Value(std::move(Body)); + return json::Value(std::move(Body)); } -bool fromJSON(const llvm::json::Value &Params, ScopesArguments &SCA, - llvm::json::Path P) { +bool fromJSON(const json::Value &Params, ScopesArguments &SCA, 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 scopes; - for (const Scope &scope : SCR.scopes) { - scopes.emplace_back(toJSON(scope)); - } - - return llvm::json::Object{{"scopes", std::move(scopes)}}; +json::Value toJSON(const ScopesResponseBody &SCR) { + return json::Object{{"scopes", SCR.scopes}}; } bool fromJSON(const json::Value &Params, SourceArguments &SA, json::Path P) { @@ -365,16 +353,14 @@ json::Value toJSON(const SourceResponseBody &SA) { return std::move(Result); } -bool fromJSON(const llvm::json::Value &Params, NextArguments &NA, - llvm::json::Path P) { +bool fromJSON(const json::Value &Params, NextArguments &NA, json::Path P) { json::ObjectMapper OM(Params, P); return OM && OM.map("threadId", NA.threadId) && OM.mapOptional("singleThread", NA.singleThread) && OM.mapOptional("granularity", NA.granularity); } -bool fromJSON(const llvm::json::Value &Params, StepInArguments &SIA, - llvm::json::Path P) { +bool fromJSON(const json::Value &Params, StepInArguments &SIA, json::Path P) { json::ObjectMapper OM(Params, P); return OM && OM.map("threadId", SIA.threadId) && OM.map("targetId", SIA.targetId) && @@ -382,54 +368,47 @@ bool fromJSON(const llvm::json::Value &Params, StepInArguments &SIA, OM.mapOptional("granularity", SIA.granularity); } -bool fromJSON(const llvm::json::Value &Params, StepOutArguments &SOA, - llvm::json::Path P) { +bool fromJSON(const json::Value &Params, StepOutArguments &SOA, json::Path P) { json::ObjectMapper OM(Params, P); return OM && OM.map("threadId", SOA.threadId) && OM.mapOptional("singleThread", SOA.singleThread) && OM.mapOptional("granularity", SOA.granularity); } -bool fromJSON(const llvm::json::Value &Params, SetBreakpointsArguments &SBA, - llvm::json::Path P) { +bool fromJSON(const json::Value &Params, SetBreakpointsArguments &SBA, + json::Path P) { json::ObjectMapper O(Params, P); return O && O.map("source", SBA.source) && O.map("breakpoints", SBA.breakpoints) && O.map("lines", SBA.lines) && O.map("sourceModified", SBA.sourceModified); } -llvm::json::Value toJSON(const SetBreakpointsResponseBody &SBR) { - json::Object result; - result["breakpoints"] = SBR.breakpoints; - return result; +json::Value toJSON(const SetBreakpointsResponseBody &SBR) { + return json::Object{{"breakpoints", SBR.breakpoints}}; } -bool fromJSON(const llvm::json::Value &Params, - SetFunctionBreakpointsArguments &SFBA, llvm::json::Path P) { +bool fromJSON(const json::Value &Params, SetFunctionBreakpointsArguments &SFBA, + json::Path P) { json::ObjectMapper O(Params, P); return O && O.map("breakpoints", SFBA.breakpoints); } -llvm::json::Value toJSON(const SetFunctionBreakpointsResponseBody &SFBR) { - json::Object result; - result["breakpoints"] = SFBR.breakpoints; - return result; +json::Value toJSON(const SetFunctionBreakpointsResponseBody &SFBR) { + return json::Object{{"breakpoints", SFBR.breakpoints}}; } -bool fromJSON(const llvm::json::Value &Params, - SetInstructionBreakpointsArguments &SIBA, llvm::json::Path P) { +bool fromJSON(const json::Value &Params, + SetInstructionBreakpointsArguments &SIBA, json::Path P) { json::ObjectMapper O(Params, P); return O && O.map("breakpoints", SIBA.breakpoints); } -llvm::json::Value toJSON(const SetInstructionBreakpointsResponseBody &SIBR) { - json::Object result; - result["breakpoints"] = SIBR.breakpoints; - return result; +json::Value toJSON(const SetInstructionBreakpointsResponseBody &SIBR) { + return json::Object{{"breakpoints", SIBR.breakpoints}}; } -bool fromJSON(const llvm::json::Value &Params, - DataBreakpointInfoArguments &DBIA, llvm::json::Path P) { +bool fromJSON(const json::Value &Params, DataBreakpointInfoArguments &DBIA, + json::Path P) { json::ObjectMapper O(Params, P); return O && O.map("variablesReference", DBIA.variablesReference) && O.map("name", DBIA.name) && O.map("frameId", DBIA.frameId) && @@ -437,27 +416,30 @@ bool fromJSON(const llvm::json::Value &Params, O.map("mode", DBIA.mode); } -llvm::json::Value toJSON(const DataBreakpointInfoResponseBody &DBIRB) { - json::Object result; - result["dataId"] = DBIRB.dataId ? *DBIRB.dataId : llvm::json::Value(nullptr); - result["description"] = DBIRB.description; +json::Value toJSON(const DataBreakpointInfoResponseBody &DBIRB) { + json::Object result{{"dataId", DBIRB.dataId}, + {"description", DBIRB.description}}; + if (DBIRB.accessTypes) result["accessTypes"] = *DBIRB.accessTypes; if (DBIRB.canPersist) result["canPersist"] = *DBIRB.canPersist; + return result; } -bool fromJSON(const llvm::json::Value &Params, - SetDataBreakpointsArguments &SDBA, llvm::json::Path P) { +bool fromJSON(const json::Value &Params, SetDataBreakpointsArguments &SDBA, + json::Path P) { json::ObjectMapper O(Params, P); return O && O.map("breakpoints", SDBA.breakpoints); } -llvm::json::Value toJSON(const SetDataBreakpointsResponseBody &SDBR) { - json::Object result; - result["breakpoints"] = SDBR.breakpoints; - return result; +json::Value toJSON(const SetDataBreakpointsResponseBody &SDBR) { + return json::Object{{"breakpoints", SDBR.breakpoints}}; +} + +json::Value toJSON(const ThreadsResponseBody &TR) { + return json::Object{{"threads", TR.threads}}; } bool fromJSON(const llvm::json::Value &Params, DisassembleArguments &DA, @@ -470,12 +452,8 @@ bool fromJSON(const llvm::json::Value &Params, DisassembleArguments &DA, O.mapOptional("resolveSymbols", DA.resolveSymbols); } -llvm::json::Value toJSON(const DisassembleResponseBody &DRB) { - llvm::json::Array instructions; - for (const auto &instruction : DRB.instructions) { - instructions.push_back(toJSON(instruction)); - } - return llvm::json::Object{{"instructions", std::move(instructions)}}; +json::Value toJSON(const DisassembleResponseBody &DRB) { + return json::Object{{"instructions", DRB.instructions}}; } } // namespace lldb_dap::protocol diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h index 7c774e50d6e56..d199cc886b11c 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h +++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h @@ -482,6 +482,16 @@ struct SourceResponseBody { }; llvm::json::Value toJSON(const SourceResponseBody &); +/// Arguments for the `threads` request, no arguments. +using ThreadsArguments = EmptyArguments; + +/// Response to `threads` request. +struct ThreadsResponseBody { + /// All threads. + std::vector<Thread> threads; +}; +llvm::json::Value toJSON(const ThreadsResponseBody &); + /// Arguments for `next` request. struct NextArguments { /// Specifies the thread for which to resume execution for one step (of the diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp index 3b297a0bd431f..e1e57dcf1b0b5 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp +++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp @@ -582,6 +582,15 @@ llvm::json::Value toJSON(const SteppingGranularity &SG) { llvm_unreachable("unhandled stepping granularity."); } +bool fromJSON(const json::Value &Params, Thread &T, json::Path P) { + json::ObjectMapper O(Params, P); + return O && O.map("id", T.id) && O.map("name", T.name); +} + +json::Value toJSON(const Thread &T) { + return json::Object{{"id", T.id}, {"name", T.name}}; +} + bool fromJSON(const llvm::json::Value &Params, ValueFormat &VF, llvm::json::Path P) { json::ObjectMapper O(Params, P); diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h index f5e21c96fe17f..bb3ee0a68eace 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h +++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h @@ -414,6 +414,16 @@ bool fromJSON(const llvm::json::Value &, SteppingGranularity &, llvm::json::Path); llvm::json::Value toJSON(const SteppingGranularity &); +/// A Thread. +struct Thread { + /// Unique identifier for the thread. + lldb::tid_t id; + /// The name of the thread. + std::string name; +}; +bool fromJSON(const llvm::json::Value &, Thread &, llvm::json::Path); +llvm::json::Value toJSON(const Thread &); + /// Provides formatting information for a value. struct ValueFormat { /// Display the value in hex. diff --git a/lldb/tools/lldb-dap/ProtocolUtils.cpp b/lldb/tools/lldb-dap/ProtocolUtils.cpp index 4e47c87b73592..9c446ace61c42 100644 --- a/lldb/tools/lldb-dap/ProtocolUtils.cpp +++ b/lldb/tools/lldb-dap/ProtocolUtils.cpp @@ -10,9 +10,15 @@ #include "LLDBUtils.h" #include "lldb/API/SBDebugger.h" +#include "lldb/API/SBFormat.h" +#include "lldb/API/SBMutex.h" +#include "lldb/API/SBStream.h" #include "lldb/API/SBTarget.h" +#include "lldb/API/SBThread.h" #include "lldb/Host/PosixApi.h" // Adds PATH_MAX for windows +#include <optional> +using namespace lldb_dap::protocol; namespace lldb_dap { static bool ShouldDisplayAssemblySource( @@ -110,4 +116,48 @@ std::string GetLoadAddressString(const lldb::addr_t addr) { return "0x" + llvm::utohexstr(addr, false, 16); } +protocol::Thread CreateThread(lldb::SBThread &thread, lldb::SBFormat &format) { + std::string name; + lldb::SBStream stream; + if (format && thread.GetDescriptionWithFormat(format, stream).Success()) { + name = stream.GetData(); + } else { + llvm::StringRef thread_name(thread.GetName()); + llvm::StringRef queue_name(thread.GetQueueName()); + + if (!thread_name.empty()) { + name = thread_name.str(); + } else if (!queue_name.empty()) { + auto kind = thread.GetQueue().GetKind(); + std::string queue_kind_label = ""; + if (kind == lldb::eQueueKindSerial) { + queue_kind_label = " (serial)"; + } else if (kind == lldb::eQueueKindConcurrent) { + queue_kind_label = " (concurrent)"; + } + + name = llvm::formatv("Thread {0} Queue: {1}{2}", thread.GetIndexID(), + queue_name, queue_kind_label) + .str(); + } else { + name = llvm::formatv("Thread {0}", thread.GetIndexID()).str(); + } + } + return protocol::Thread{thread.GetThreadID(), name}; +} + +std::vector<protocol::Thread> GetThreads(lldb::SBProcess process, + lldb::SBFormat &format) { + lldb::SBMutex lock = process.GetTarget().GetAPIMutex(); + std::lock_guard<lldb::SBMutex> guard(lock); + + std::vector<protocol::Thread> threads; + const uint32_t num_threads = process.GetNumThreads(); + for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx) { + lldb::SBThread thread = process.GetThreadAtIndex(thread_idx); + threads.emplace_back(CreateThread(thread, format)); + } + return threads; +} + } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/ProtocolUtils.h b/lldb/tools/lldb-dap/ProtocolUtils.h index 6e4f07d6e3470..2b2ac9e8e35fd 100644 --- a/lldb/tools/lldb-dap/ProtocolUtils.h +++ b/lldb/tools/lldb-dap/ProtocolUtils.h @@ -48,6 +48,32 @@ bool IsAssemblySource(const protocol::Source &source); /// Get the address as a 16-digit hex string, e.g. "0x0000000000012345" std::string GetLoadAddressString(const lldb::addr_t addr); +/// Create a "Thread" object for a LLDB thread object. +/// +/// This function will fill in the following keys in the returned +/// object: +/// "id" - the thread ID as an integer +/// "name" - the thread name as a string which combines the LLDB +/// thread index ID along with the string name of the thread +/// from the OS if it has a name. +/// +/// \param[in] thread +/// The LLDB thread to use when populating out the "Thread" +/// object. +/// +/// \param[in] format +/// The LLDB format to use when populating out the "Thread" +/// object. +/// +/// \return +/// A "Thread" JSON object with that follows the formal JSON +/// definition outlined by Microsoft. +protocol::Thread CreateThread(lldb::SBThread &thread, lldb::SBFormat &format); + +/// Returns the set of threads associated with the process. +std::vector<protocol::Thread> GetThreads(lldb::SBProcess process, + lldb::SBFormat &format); + } // namespace lldb_dap #endif >From 5eefb03283a21e69854922933d2465a6cfe77c82 Mon Sep 17 00:00:00 2001 From: John Harrison <harj...@google.com> Date: Tue, 3 Jun 2025 17:42:47 -0700 Subject: [PATCH 2/2] Addressing feedback and adding unit tests. --- lldb/tools/lldb-dap/DAP.cpp | 5 +- lldb/tools/lldb-dap/EventHelper.cpp | 27 ++--- lldb/tools/lldb-dap/EventHelper.h | 3 +- .../ConfigurationDoneRequestHandler.cpp | 8 +- .../Handler/RestartRequestHandler.cpp | 6 +- .../Handler/ThreadsRequestHandler.cpp | 8 +- lldb/tools/lldb-dap/Protocol/ProtocolTypes.h | 4 +- lldb/unittests/DAP/ProtocolTypesTest.cpp | 103 ++++++++++++++---- 8 files changed, 114 insertions(+), 50 deletions(-) diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index 394e8f0e6c851..a14eef559ffb4 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -1240,7 +1240,10 @@ void DAP::EventThread() { // automatically restarted. if (!lldb::SBProcess::GetRestartedFromEvent(event)) { SendStdOutStdErr(*this, process); - SendThreadStoppedEvent(*this); + if (llvm::Error err = SendThreadStoppedEvent(*this)) + DAP_LOG_ERROR(log, std::move(err), + "({1}) reporting thread stopped: {0}", + transport.GetClientName()); } break; case lldb::eStateRunning: diff --git a/lldb/tools/lldb-dap/EventHelper.cpp b/lldb/tools/lldb-dap/EventHelper.cpp index 6b5274ea8997b..dae2206b28781 100644 --- a/lldb/tools/lldb-dap/EventHelper.cpp +++ b/lldb/tools/lldb-dap/EventHelper.cpp @@ -8,10 +8,11 @@ #include "EventHelper.h" #include "DAP.h" -#include "DAPLog.h" +#include "DAPERror.h" #include "JSONUtils.h" #include "LLDBUtils.h" #include "lldb/API/SBFileSpec.h" +#include "llvm/Support/Error.h" #if defined(_WIN32) #define NOMINMAX @@ -22,6 +23,8 @@ #endif #endif +using namespace llvm; + namespace lldb_dap { static void SendThreadExitedEvent(DAP &dap, lldb::tid_t tid) { @@ -116,20 +119,17 @@ void SendProcessEvent(DAP &dap, LaunchMethod launch_method) { // Send a thread stopped event for all threads as long as the process // is stopped. -void SendThreadStoppedEvent(DAP &dap, bool on_entry) { +llvm::Error SendThreadStoppedEvent(DAP &dap, bool on_entry) { + lldb::SBMutex lock = dap.GetAPIMutex(); + std::lock_guard<lldb::SBMutex> guard(lock); + lldb::SBProcess process = dap.target.GetProcess(); - if (!process.IsValid()) { - DAP_LOG(dap.log, "error: SendThreadStoppedEvent() invalid process"); - return; - } + if (!process.IsValid()) + return make_error<DAPError>("invalid process"); lldb::StateType state = process.GetState(); - if (!lldb::SBDebugger::StateIsStoppedState(state)) { - DAP_LOG(dap.log, - "error: SendThreadStoppedEvent() when process isn't stopped ({0})", - lldb::SBDebugger::StateAsCString(state)); - return; - } + if (!lldb::SBDebugger::StateIsStoppedState(state)) + return make_error<NotStoppedError>(); llvm::DenseSet<lldb::tid_t> old_thread_ids; old_thread_ids.swap(dap.thread_ids); @@ -182,7 +182,7 @@ void SendThreadStoppedEvent(DAP &dap, bool on_entry) { } } - for (auto tid : old_thread_ids) { + for (const auto &tid : old_thread_ids) { auto end = dap.thread_ids.end(); auto pos = dap.thread_ids.find(tid); if (pos == end) @@ -190,6 +190,7 @@ void SendThreadStoppedEvent(DAP &dap, bool on_entry) { } dap.RunStopCommands(); + return Error::success(); } // Send a "terminated" event to indicate the process is done being diff --git a/lldb/tools/lldb-dap/EventHelper.h b/lldb/tools/lldb-dap/EventHelper.h index d77a7879aae28..6a9e3102384c7 100644 --- a/lldb/tools/lldb-dap/EventHelper.h +++ b/lldb/tools/lldb-dap/EventHelper.h @@ -10,6 +10,7 @@ #define LLDB_TOOLS_LLDB_DAP_EVENTHELPER_H #include "DAPForward.h" +#include "llvm/Support/Error.h" namespace lldb_dap { struct DAP; @@ -18,7 +19,7 @@ enum LaunchMethod { Launch, Attach, AttachForSuspendedLaunch }; void SendProcessEvent(DAP &dap, LaunchMethod launch_method); -void SendThreadStoppedEvent(DAP &dap, bool on_entry = false); +llvm::Error SendThreadStoppedEvent(DAP &dap, bool on_entry = false); void SendTerminatedEvent(DAP &dap); diff --git a/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp index a262cf32a9d47..a85d2dedba871 100644 --- a/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp @@ -8,7 +8,7 @@ #include "DAP.h" #include "EventHelper.h" -#include "JSONUtils.h" +#include "LLDBUtils.h" #include "Protocol/ProtocolRequests.h" #include "ProtocolUtils.h" #include "RequestHandler.h" @@ -52,11 +52,9 @@ ConfigurationDoneRequestHandler::Run(const ConfigurationDoneArguments &) const { SendProcessEvent(dap, dap.is_attach ? Attach : Launch); if (dap.stop_at_entry) - SendThreadStoppedEvent(dap, /*on_entry=*/true); - else - process.Continue(); + return SendThreadStoppedEvent(dap, /*on_entry=*/true); - return Error::success(); + return ToError(process.Continue()); } } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Handler/RestartRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/RestartRequestHandler.cpp index b7f06a7ba1b80..705089fba2127 100644 --- a/lldb/tools/lldb-dap/Handler/RestartRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/RestartRequestHandler.cpp @@ -145,7 +145,11 @@ void RestartRequestHandler::operator()( // Because we're restarting, configuration has already happened so we can // continue the process right away. if (dap.stop_at_entry) { - SendThreadStoppedEvent(dap, /*on_entry=*/true); + if (llvm::Error err = SendThreadStoppedEvent(dap, /*on_entry=*/true)) { + EmplaceSafeString(response, "message", llvm::toString(std::move(err))); + dap.SendJSON(llvm::json::Value(std::move(response))); + return; + } } else { dap.target.GetProcess().Continue(); } diff --git a/lldb/tools/lldb-dap/Handler/ThreadsRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ThreadsRequestHandler.cpp index dd6178567e918..aced2222225ea 100644 --- a/lldb/tools/lldb-dap/Handler/ThreadsRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/ThreadsRequestHandler.cpp @@ -36,10 +36,12 @@ ThreadsRequestHandler::Run(const ThreadsArguments &) const { if (dap.initial_thread_list) { threads = *dap.initial_thread_list; dap.initial_thread_list.reset(); - } else if (!lldb::SBDebugger::StateIsStoppedState(process.GetState())) - return make_error<NotStoppedError>(); - else + } else { + if (!lldb::SBDebugger::StateIsStoppedState(process.GetState())) + return make_error<NotStoppedError>(); + threads = GetThreads(process, dap.thread_format); + } if (threads.size() == 0) return make_error<DAPError>("failed to retrieve threads from process"); diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h index bb3ee0a68eace..8edbaf69b68cd 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h +++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h @@ -647,7 +647,7 @@ struct DisassembledInstruction { /// The address of the instruction. Treated as a hex value if prefixed with /// `0x`, or as a decimal value otherwise. - lldb::addr_t address; + lldb::addr_t address = LLDB_INVALID_ADDRESS; /// Raw bytes representing the instruction and its operands, in an /// implementation-defined format. @@ -687,8 +687,6 @@ struct DisassembledInstruction { /// addresses may be presented is 'invalid.' /// Values: 'normal', 'invalid' std::optional<PresentationHint> presentationHint; - - DisassembledInstruction() : address(0) {} }; bool fromJSON(const llvm::json::Value &, DisassembledInstruction::PresentationHint &, llvm::json::Path); diff --git a/lldb/unittests/DAP/ProtocolTypesTest.cpp b/lldb/unittests/DAP/ProtocolTypesTest.cpp index 41703f4a071fb..ed737df00c15b 100644 --- a/lldb/unittests/DAP/ProtocolTypesTest.cpp +++ b/lldb/unittests/DAP/ProtocolTypesTest.cpp @@ -7,12 +7,24 @@ //===----------------------------------------------------------------------===// #include "Protocol/ProtocolTypes.h" +#include "Protocol/ProtocolRequests.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/JSON.h" #include "llvm/Testing/Support/Error.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" +using namespace llvm; using namespace lldb; using namespace lldb_dap; using namespace lldb_dap::protocol; +using llvm::json::parse; +using llvm::json::Value; + +/// Returns a pretty printed json string of a `llvm::json::Value`. +static std::string pp(const json::Value &E) { + return formatv("{0:2}", E).str(); +} template <typename T> static llvm::Expected<T> roundtrip(const T &input) { llvm::json::Value value = toJSON(input); @@ -578,27 +590,72 @@ TEST(ProtocolTypesTest, DisassembledInstruction) { instruction.presentationHint = DisassembledInstruction::eDisassembledInstructionPresentationHintNormal; - llvm::Expected<DisassembledInstruction> deserialized_instruction = - roundtrip(instruction); - ASSERT_THAT_EXPECTED(deserialized_instruction, llvm::Succeeded()); - - EXPECT_EQ(instruction.address, deserialized_instruction->address); - EXPECT_EQ(instruction.instructionBytes, - deserialized_instruction->instructionBytes); - EXPECT_EQ(instruction.instruction, deserialized_instruction->instruction); - EXPECT_EQ(instruction.symbol, deserialized_instruction->symbol); - EXPECT_EQ(instruction.location->name, - deserialized_instruction->location->name); - EXPECT_EQ(instruction.location->path, - deserialized_instruction->location->path); - EXPECT_EQ(instruction.location->sourceReference, - deserialized_instruction->location->sourceReference); - EXPECT_EQ(instruction.location->presentationHint, - deserialized_instruction->location->presentationHint); - EXPECT_EQ(instruction.line, deserialized_instruction->line); - EXPECT_EQ(instruction.column, deserialized_instruction->column); - EXPECT_EQ(instruction.endLine, deserialized_instruction->endLine); - EXPECT_EQ(instruction.endColumn, deserialized_instruction->endColumn); - EXPECT_EQ(instruction.presentationHint, - deserialized_instruction->presentationHint); + StringLiteral json = R"({ + "address": "0x12345678", + "column": 5, + "endColumn": 10, + "endLine": 15, + "instruction": "mov eax, ebx", + "instructionBytes": "0F 1F 00", + "line": 10, + "location": { + "name": "test.cpp", + "path": "/path/to/test.cpp", + "presentationHint": "normal", + "sourceReference": 123 + }, + "presentationHint": "normal", + "symbol": "main" +})"; + + // Validate toJSON + EXPECT_EQ(json, pp(instruction)); + + // Validate fromJSON + EXPECT_THAT_EXPECTED(parse<DisassembledInstruction>(json), + HasValue(Value(instruction))); + // Validate parsing errors + EXPECT_THAT_EXPECTED( + parse<DisassembledInstruction>(R"({"address":1})", + "disassemblyInstruction"), + FailedWithMessage( + "missing 'address' field when parsing disassemblyInstruction")); +} + +TEST(ProtocolTypesTest, Thread) { + const Thread thread{1, "thr1"}; + const StringRef json = R"({ + "id": 1, + "name": "thr1" +})"; + // Validate toJSON + EXPECT_EQ(json, pp(thread)); + // Validate fromJSON + EXPECT_THAT_EXPECTED(parse<Thread>(json), HasValue(Value(thread))); + // Validate parsing errors + EXPECT_THAT_EXPECTED(parse<Thread>(R"({"id":1})", "thread"), + FailedWithMessage("missing value at thread.name")); + EXPECT_THAT_EXPECTED(parse<Thread>(R"({"id":"one"})", "thread"), + FailedWithMessage("expected uint64_t at thread.id")); + EXPECT_THAT_EXPECTED(parse<Thread>(R"({"id":1,"name":false})", "thread"), + FailedWithMessage("expected string at thread.name")); +} + +TEST(ProtocolTypesTest, ThreadResponseBody) { + const ThreadsResponseBody body{{{1, "thr1"}, {2, "thr2"}}}; + const StringRef json = + R"({ + "threads": [ + { + "id": 1, + "name": "thr1" + }, + { + "id": 2, + "name": "thr2" + } + ] +})"; + // Validate toJSON + EXPECT_EQ(json, pp(body)); } _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits