Author: John Harrison
Date: 2025-06-05T15:58:30-07:00
New Revision: 52075f01a70990ce5e4b89f825d417a903a8dbe6

URL: 
https://github.com/llvm/llvm-project/commit/52075f01a70990ce5e4b89f825d417a903a8dbe6
DIFF: 
https://github.com/llvm/llvm-project/commit/52075f01a70990ce5e4b89f825d417a903a8dbe6.diff

LOG: [lldb-dap] Migrating 'threads' request to structured types. (#142510)

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

---------

Co-authored-by: Ebuka Ezike <yerimy...@gmail.com>
Co-authored-by: Jonas Devlieghere <jo...@devlieghere.com>

Added: 
    

Modified: 
    lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
    lldb/tools/lldb-dap/DAP.cpp
    lldb/tools/lldb-dap/DAP.h
    lldb/tools/lldb-dap/EventHelper.cpp
    lldb/tools/lldb-dap/EventHelper.h
    lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp
    lldb/tools/lldb-dap/Handler/RequestHandler.h
    lldb/tools/lldb-dap/Handler/RestartRequestHandler.cpp
    lldb/tools/lldb-dap/Handler/ThreadsRequestHandler.cpp
    lldb/tools/lldb-dap/JSONUtils.cpp
    lldb/tools/lldb-dap/JSONUtils.h
    lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
    lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
    lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp
    lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
    lldb/tools/lldb-dap/ProtocolUtils.cpp
    lldb/tools/lldb-dap/ProtocolUtils.h
    lldb/unittests/DAP/ProtocolTypesTest.cpp

Removed: 
    


################################################################################
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/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index 2537e007d691b..b034c967594ba 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/DAP.h b/lldb/tools/lldb-dap/DAP.h
index 1bd94fab402ca..89bc827c1141f 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::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..ae6fc6ec73ae3 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,78 +119,78 @@ 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) {
+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()) {
-    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())
+    return make_error<DAPError>("invalid process");
+
+  lldb::StateType state = process.GetState();
+  if (!lldb::SBDebugger::StateIsStoppedState(state))
+    return make_error<NotStoppedError>();
+
+  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;
+    }
+  }
 
-      // 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();
+  // 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));
-      } 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));
-          }
-        }
       }
-
-      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));
     }
-  } else {
-    DAP_LOG(dap.log, "error: SendThreadStoppedEvent() invalid process");
   }
+
+  for (const 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();
+  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 90b009c73089e..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);
+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 1281857ef4b60..a85d2dedba871 100644
--- a/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp
@@ -8,8 +8,9 @@
 
 #include "DAP.h"
 #include "EventHelper.h"
-#include "JSONUtils.h"
+#include "LLDBUtils.h"
 #include "Protocol/ProtocolRequests.h"
+#include "ProtocolUtils.h"
 #include "RequestHandler.h"
 #include "lldb/API/SBDebugger.h"
 
@@ -51,11 +52,9 @@ ConfigurationDoneRequestHandler::Run(const 
ConfigurationDoneArguments &) const {
   SendProcessEvent(dap, dap.is_attach ? Attach : Launch);
 
   if (dap.stop_at_entry)
-    SendThreadStoppedEvent(dap);
-  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/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..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);
+    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 16d797c2ab327..8b328d8348e3c 100644
--- a/lldb/tools/lldb-dap/Handler/ThreadsRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/ThreadsRequestHandler.cpp
@@ -8,72 +8,45 @@
 
 #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.
   // If no thread has reported to the client, it prevents something
   // 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();
-    dap.initial_thread_list.reset();
+  if (!dap.initial_thread_list.empty()) {
+    threads = dap.initial_thread_list;
+    dap.initial_thread_list.clear();
   } else {
-    threads = GetThreads(dap.target.GetProcess(), dap.thread_format);
-  }
+    if (!lldb::SBDebugger::StateIsStoppedState(process.GetState()))
+      return make_error<NotStoppedError>();
 
-  if (threads.size() == 0) {
-    response["success"] = llvm::json::Value(false);
+    threads = GetThreads(process, dap.thread_format);
   }
-  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)));
+
+  if (threads.size() == 0)
+    return make_error<DAPError>("failed to retrieve threads from process");
+
+  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..085d53bb006ef 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);
@@ -821,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/Protocol/ProtocolTypes.h 
b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
index f5e21c96fe17f..c7acfc482987b 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 = LLDB_INVALID_THREAD_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.
@@ -637,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.
@@ -677,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/tools/lldb-dap/ProtocolUtils.cpp 
b/lldb/tools/lldb-dap/ProtocolUtils.cpp
index 4e47c87b73592..6e0adf5bc8b59 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,49 @@ 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();
+  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));
+  }
+  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

diff  --git a/lldb/unittests/DAP/ProtocolTypesTest.cpp 
b/lldb/unittests/DAP/ProtocolTypesTest.cpp
index 41703f4a071fb..68a7b036975cc 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,79 @@ 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("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) {
+  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

Reply via email to