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/4] [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/4] 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));
 }

>From 979010770b5a747a0d29a4003e407e15f47df35c Mon Sep 17 00:00:00 2001
From: John Harrison <a...@greaterthaninfinity.com>
Date: Wed, 4 Jun 2025 09:08:41 -0700
Subject: [PATCH 3/4] Update lldb/tools/lldb-dap/EventHelper.cpp

Co-authored-by: Ebuka Ezike <yerimy...@gmail.com>
---
 lldb/tools/lldb-dap/EventHelper.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lldb/tools/lldb-dap/EventHelper.cpp 
b/lldb/tools/lldb-dap/EventHelper.cpp
index dae2206b28781..4e43af3561266 100644
--- a/lldb/tools/lldb-dap/EventHelper.cpp
+++ b/lldb/tools/lldb-dap/EventHelper.cpp
@@ -8,7 +8,7 @@
 
 #include "EventHelper.h"
 #include "DAP.h"
-#include "DAPERror.h"
+#include "DAPError.h"
 #include "JSONUtils.h"
 #include "LLDBUtils.h"
 #include "lldb/API/SBFileSpec.h"

>From ef281bf89431c240b0f603ec3cfc3d81bb2b88be Mon Sep 17 00:00:00 2001
From: John Harrison <harj...@google.com>
Date: Wed, 4 Jun 2025 09:49:21 -0700
Subject: [PATCH 4/4] Reverting some of the changes to the stopped event, I'll
 follow up with a new PR for that and improving the error reporting for the
 'DisassembledInstruction' type.

---
 .../tools/lldb-dap/threads/TestDAP_threads.py    |  2 +-
 lldb/tools/lldb-dap/EventHelper.cpp              |  2 +-
 lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp   | 16 +++++++---------
 lldb/tools/lldb-dap/ProtocolUtils.cpp            |  2 ++
 lldb/unittests/DAP/ProtocolTypesTest.cpp         | 15 +++++++++++----
 5 files changed, 22 insertions(+), 15 deletions(-)

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 15bae3cc83daf..acd6108853787 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.
+        # preserveFocusHint is False and threadCausedFocus is True
         self.assertTrue(
             stopped_event[0]["body"]["description"].startswith(
                 "breakpoint %s." % breakpoint_ids[0]
diff --git a/lldb/tools/lldb-dap/EventHelper.cpp 
b/lldb/tools/lldb-dap/EventHelper.cpp
index 4e43af3561266..ae6fc6ec73ae3 100644
--- a/lldb/tools/lldb-dap/EventHelper.cpp
+++ b/lldb/tools/lldb-dap/EventHelper.cpp
@@ -133,7 +133,7 @@ llvm::Error SendThreadStoppedEvent(DAP &dap, bool on_entry) 
{
 
   llvm::DenseSet<lldb::tid_t> old_thread_ids;
   old_thread_ids.swap(dap.thread_ids);
-  uint32_t stop_id = on_entry ? 0 : process.GetStopID();
+  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
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp 
b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp
index e1e57dcf1b0b5..085d53bb006ef 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp
@@ -830,22 +830,20 @@ llvm::json::Value toJSON(const 
DisassembledInstruction::PresentationHint &PH) {
 
 bool fromJSON(const llvm::json::Value &Params, DisassembledInstruction &DI,
               llvm::json::Path P) {
-  std::optional<llvm::StringRef> raw_address =
-      Params.getAsObject()->getString("address");
-  if (!raw_address) {
-    P.report("missing 'address' field");
+  llvm::json::ObjectMapper O(Params, P);
+  std::string raw_address;
+  if (!O || !O.map("address", raw_address))
     return false;
-  }
 
-  std::optional<lldb::addr_t> address = DecodeMemoryReference(*raw_address);
+  std::optional<lldb::addr_t> address = DecodeMemoryReference(raw_address);
   if (!address) {
-    P.report("invalid 'address'");
+    P.field("address").report("expected string encoded uint64_t");
     return false;
   }
 
   DI.address = *address;
-  llvm::json::ObjectMapper O(Params, P);
-  return O && O.map("instruction", DI.instruction) &&
+
+  return O.map("instruction", DI.instruction) &&
          O.mapOptional("instructionBytes", DI.instructionBytes) &&
          O.mapOptional("symbol", DI.symbol) &&
          O.mapOptional("location", DI.location) &&
diff --git a/lldb/tools/lldb-dap/ProtocolUtils.cpp 
b/lldb/tools/lldb-dap/ProtocolUtils.cpp
index 9c446ace61c42..5ee61e0c82cfa 100644
--- a/lldb/tools/lldb-dap/ProtocolUtils.cpp
+++ b/lldb/tools/lldb-dap/ProtocolUtils.cpp
@@ -152,7 +152,9 @@ std::vector<protocol::Thread> GetThreads(lldb::SBProcess 
process,
   std::lock_guard<lldb::SBMutex> guard(lock);
 
   std::vector<protocol::Thread> threads;
+
   const uint32_t num_threads = process.GetNumThreads();
+  threads.reserve(num_threads);
   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));
diff --git a/lldb/unittests/DAP/ProtocolTypesTest.cpp 
b/lldb/unittests/DAP/ProtocolTypesTest.cpp
index ed737df00c15b..68a7b036975cc 100644
--- a/lldb/unittests/DAP/ProtocolTypesTest.cpp
+++ b/lldb/unittests/DAP/ProtocolTypesTest.cpp
@@ -618,8 +618,16 @@ TEST(ProtocolTypesTest, DisassembledInstruction) {
   EXPECT_THAT_EXPECTED(
       parse<DisassembledInstruction>(R"({"address":1})",
                                      "disassemblyInstruction"),
-      FailedWithMessage(
-          "missing 'address' field when parsing disassemblyInstruction"));
+      FailedWithMessage("expected string at disassemblyInstruction.address"));
+  EXPECT_THAT_EXPECTED(parse<DisassembledInstruction>(R"({"address":"-1"})",
+                                                      
"disassemblyInstruction"),
+                       FailedWithMessage("expected string encoded uint64_t at "
+                                         "disassemblyInstruction.address"));
+  EXPECT_THAT_EXPECTED(parse<DisassembledInstruction>(
+                           R"({"address":"0xfffffffffffffffffffffffffff"})",
+                           "disassemblyInstruction"),
+                       FailedWithMessage("expected string encoded uint64_t at "
+                                         "disassemblyInstruction.address"));
 }
 
 TEST(ProtocolTypesTest, Thread) {
@@ -643,8 +651,7 @@ TEST(ProtocolTypesTest, Thread) {
 
 TEST(ProtocolTypesTest, ThreadResponseBody) {
   const ThreadsResponseBody body{{{1, "thr1"}, {2, "thr2"}}};
-  const StringRef json =
-      R"({
+  const StringRef json = R"({
   "threads": [
     {
       "id": 1,

_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to