Skip to content

[lldb-dap] Migrating 'threads' request to structured types. #142510

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 5, 2025

Conversation

ashgti
Copy link
Contributor

@ashgti ashgti commented Jun 3, 2025

Moving threads request to structured types. Adding helper types for this and moving helpers from JSONUtils to ProtocolUtils.

@llvmbot
Copy link
Member

llvmbot commented Jun 3, 2025

@llvm/pr-subscribers-lldb

Author: John Harrison (ashgti)

Changes

Moving threads request to structured types. Adding helper types for this and moving helpers from JSONUtils to ProtocolUtils.


Patch is 35.01 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/142510.diff

17 Files Affected:

  • (modified) lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py (+3-1)
  • (modified) lldb/test/API/tools/lldb-dap/threads/TestDAP_threads.py (+1-1)
  • (modified) lldb/tools/lldb-dap/DAP.h (+1-1)
  • (modified) lldb/tools/lldb-dap/EventHelper.cpp (+67-65)
  • (modified) lldb/tools/lldb-dap/EventHelper.h (+1-1)
  • (modified) lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp (+2-1)
  • (modified) lldb/tools/lldb-dap/Handler/RequestHandler.h (+6-3)
  • (modified) lldb/tools/lldb-dap/Handler/RestartRequestHandler.cpp (+1-1)
  • (modified) lldb/tools/lldb-dap/Handler/ThreadsRequestHandler.cpp (+23-52)
  • (modified) lldb/tools/lldb-dap/JSONUtils.cpp (-64)
  • (modified) lldb/tools/lldb-dap/JSONUtils.h (-24)
  • (modified) lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp (+48-70)
  • (modified) lldb/tools/lldb-dap/Protocol/ProtocolRequests.h (+10)
  • (modified) lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp (+9)
  • (modified) lldb/tools/lldb-dap/Protocol/ProtocolTypes.h (+10)
  • (modified) lldb/tools/lldb-dap/ProtocolUtils.cpp (+50)
  • (modified) lldb/tools/lldb-dap/ProtocolUtils.h (+26)
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) &&
+       ...
[truncated]

Copy link
Member

@JDevlieghere JDevlieghere left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a Thread serialization/deserialization unit test?

@ashgti
Copy link
Contributor Author

ashgti commented Jun 4, 2025

Can you add a Thread serialization/deserialization unit test?

I added some unit tests but looking at this a bit more, I took some time to try a slightly different approach to validating the serialization logic.

Specifically, using roundtrip and the individual asserts on each field felt like a lot of boiler plate.

I have a slightly different approach in my test that uses a few of the llvm::json helpers (parse and Value) to convert our POD types into other types that have == operators for making comparisons more straight forward.

Additionally, I also think we can support just a fromJSON or toJSON case as well with this approach.

I also tweaked the ProtocolTypesTest.DisassembledInstruction test as well, because the Thread type is fairly trivial and I wanted to make sure this worked okay for a more complex type.

Let me know what you think

@JDevlieghere
Copy link
Member

I was trying to avoid putting JSON in the test because I find it hard to maintain (your editor/IDE doesn't understand it's JSON, refactoring tools don't work, no automatic formatting, etc) but if we think this is easier to maintain (and means folks write more tests) then I'm all for it. I also really like the checks for the parsing errors.


llvm::DenseSet<lldb::tid_t> old_thread_ids;
old_thread_ids.swap(dap.thread_ids);
uint32_t stop_id = on_entry ? 0 : process.GetStopID();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
uint32_t stop_id = on_entry ? 0 : process.GetStopID();
uint32_t stop_id = on_entry ? LLDB_INVALID_STOP_ID : process.GetStopID();

I assume the 0 is for an invalid stop id ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In CreateThreadStopped we check if the stop_id is '0' for marking that as the 'entry' stop reason (e.g. the app launched).

I could use LLDB_INVALID_STOP_ID but that feels a little off, since this isn't invalid per-say just.

Maybe to make it more clear, I'll pass on_entry to the CreateThreadStopped helper instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead, I'll follow up with a new PR that migrates the 'stopped' event to a protocol type and update this then.

@ashgti
Copy link
Contributor Author

ashgti commented Jun 4, 2025

I was trying to avoid putting JSON in the test because I find it hard to maintain (your editor/IDE doesn't understand it's JSON, refactoring tools don't work, no automatic formatting, etc) but if we think this is easier to maintain (and means folks write more tests) then I'm all for it. I also really like the checks for the parsing errors.

While updating the test for the 'DisassembledInstruction' type I noticed it handled the 'address' a bit poorly, so I updated that as well with some improved error reporting.

@ashgti ashgti requested a review from JDevlieghere June 4, 2025 19:58
ashgti and others added 6 commits June 4, 2025 16:54
Moving `threads` request to structured types. Adding helper types for this and moving helpers from JSONUtils to ProtocolUtils.
Co-authored-by: Ebuka Ezike <yerimyah1@gmail.com>
…th a new PR for that and improving the error reporting for the 'DisassembledInstruction' type.
Co-authored-by: Jonas Devlieghere <jonas@devlieghere.com>
@ashgti ashgti force-pushed the lldb-dap-threads branch from 0e3075d to 2075f01 Compare June 4, 2025 23:55
@ashgti ashgti merged commit 52075f0 into llvm:main Jun 5, 2025
7 checks passed
@ashgti ashgti deleted the lldb-dap-threads branch June 5, 2025 22:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants