[Lldb-commits] [lldb] 52075f0 - [lldb-dap] Migrating 'threads' request to structured types. (#142510)
via lldb-commits
lldb-commits at lists.llvm.org
Thu Jun 5 15:58:35 PDT 2025
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 <yerimyah1 at gmail.com>
Co-authored-by: Jonas Devlieghere <jonas at 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));
}
More information about the lldb-commits
mailing list