1//===-- JSONUtils.cpp -------------------------------------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "JSONUtils.h"
10#include "DAP.h"
11#include "ExceptionBreakpoint.h"
12#include "LLDBUtils.h"
13#include "ProtocolUtils.h"
14#include "lldb/API/SBAddress.h"
15#include "lldb/API/SBCompileUnit.h"
16#include "lldb/API/SBDeclaration.h"
17#include "lldb/API/SBEnvironment.h"
18#include "lldb/API/SBError.h"
19#include "lldb/API/SBFileSpec.h"
20#include "lldb/API/SBFrame.h"
21#include "lldb/API/SBFunction.h"
22#include "lldb/API/SBInstructionList.h"
23#include "lldb/API/SBLineEntry.h"
24#include "lldb/API/SBModule.h"
25#include "lldb/API/SBQueue.h"
26#include "lldb/API/SBSection.h"
27#include "lldb/API/SBStream.h"
28#include "lldb/API/SBStringList.h"
29#include "lldb/API/SBStructuredData.h"
30#include "lldb/API/SBTarget.h"
31#include "lldb/API/SBThread.h"
32#include "lldb/API/SBType.h"
33#include "lldb/API/SBValue.h"
34#include "lldb/Host/PosixApi.h" // IWYU pragma: keep
35#include "lldb/lldb-defines.h"
36#include "lldb/lldb-enumerations.h"
37#include "lldb/lldb-types.h"
38#include "llvm/ADT/DenseMap.h"
39#include "llvm/ADT/StringExtras.h"
40#include "llvm/ADT/StringRef.h"
41#include "llvm/Support/Compiler.h"
42#include "llvm/Support/Format.h"
43#include "llvm/Support/FormatVariadic.h"
44#include "llvm/Support/JSON.h"
45#include "llvm/Support/Path.h"
46#include "llvm/Support/ScopedPrinter.h"
47#include "llvm/Support/raw_ostream.h"
48#include <chrono>
49#include <cstddef>
50#include <iomanip>
51#include <optional>
52#include <sstream>
53#include <string>
54#include <utility>
55#include <vector>
56
57namespace lldb_dap {
58
59void EmplaceSafeString(llvm::json::Object &obj, llvm::StringRef key,
60 llvm::StringRef str) {
61 if (LLVM_LIKELY(llvm::json::isUTF8(str)))
62 obj.try_emplace(K: key, Args: str.str());
63 else
64 obj.try_emplace(K: key, Args: llvm::json::fixUTF8(S: str));
65}
66
67llvm::StringRef GetAsString(const llvm::json::Value &value) {
68 if (auto s = value.getAsString())
69 return *s;
70 return llvm::StringRef();
71}
72
73// Gets a string from a JSON object using the key, or returns an empty string.
74std::optional<llvm::StringRef> GetString(const llvm::json::Object &obj,
75 llvm::StringRef key) {
76 return obj.getString(K: key);
77}
78
79std::optional<llvm::StringRef> GetString(const llvm::json::Object *obj,
80 llvm::StringRef key) {
81 if (obj == nullptr)
82 return std::nullopt;
83
84 return GetString(obj: *obj, key);
85}
86
87std::optional<bool> GetBoolean(const llvm::json::Object &obj,
88 llvm::StringRef key) {
89 if (auto value = obj.getBoolean(K: key))
90 return *value;
91 if (auto value = obj.getInteger(K: key))
92 return *value != 0;
93 return std::nullopt;
94}
95
96std::optional<bool> GetBoolean(const llvm::json::Object *obj,
97 llvm::StringRef key) {
98 if (obj != nullptr)
99 return GetBoolean(obj: *obj, key);
100 return std::nullopt;
101}
102
103bool ObjectContainsKey(const llvm::json::Object &obj, llvm::StringRef key) {
104 return obj.find(K: key) != obj.end();
105}
106
107std::string EncodeMemoryReference(lldb::addr_t addr) {
108 return "0x" + llvm::utohexstr(X: addr);
109}
110
111std::optional<lldb::addr_t>
112DecodeMemoryReference(llvm::StringRef memoryReference) {
113 if (!memoryReference.starts_with(Prefix: "0x"))
114 return std::nullopt;
115
116 lldb::addr_t addr;
117 if (memoryReference.consumeInteger(Radix: 0, Result&: addr))
118 return std::nullopt;
119
120 return addr;
121}
122
123std::vector<std::string> GetStrings(const llvm::json::Object *obj,
124 llvm::StringRef key) {
125 std::vector<std::string> strs;
126 const auto *json_array = obj->getArray(K: key);
127 if (!json_array)
128 return strs;
129 for (const auto &value : *json_array) {
130 switch (value.kind()) {
131 case llvm::json::Value::String:
132 strs.push_back(x: value.getAsString()->str());
133 break;
134 case llvm::json::Value::Number:
135 case llvm::json::Value::Boolean:
136 strs.push_back(x: llvm::to_string(Value: value));
137 break;
138 case llvm::json::Value::Null:
139 case llvm::json::Value::Object:
140 case llvm::json::Value::Array:
141 break;
142 }
143 }
144 return strs;
145}
146
147std::unordered_map<std::string, std::string>
148GetStringMap(const llvm::json::Object &obj, llvm::StringRef key) {
149 std::unordered_map<std::string, std::string> strs;
150 const auto *const json_object = obj.getObject(K: key);
151 if (!json_object)
152 return strs;
153
154 for (const auto &[key, value] : *json_object) {
155 switch (value.kind()) {
156 case llvm::json::Value::String:
157 strs.emplace(args: key.str(), args: value.getAsString()->str());
158 break;
159 case llvm::json::Value::Number:
160 case llvm::json::Value::Boolean:
161 strs.emplace(args: key.str(), args: llvm::to_string(Value: value));
162 break;
163 case llvm::json::Value::Null:
164 case llvm::json::Value::Object:
165 case llvm::json::Value::Array:
166 break;
167 }
168 }
169 return strs;
170}
171
172static bool IsClassStructOrUnionType(lldb::SBType t) {
173 return (t.GetTypeClass() & (lldb::eTypeClassUnion | lldb::eTypeClassStruct |
174 lldb::eTypeClassArray)) != 0;
175}
176
177/// Create a short summary for a container that contains the summary of its
178/// first children, so that the user can get a glimpse of its contents at a
179/// glance.
180static std::optional<std::string>
181TryCreateAutoSummaryForContainer(lldb::SBValue &v) {
182 if (!v.MightHaveChildren())
183 return std::nullopt;
184 /// As this operation can be potentially slow, we limit the total time spent
185 /// fetching children to a few ms.
186 const auto max_evaluation_time = std::chrono::milliseconds(10);
187 /// We don't want to generate a extremely long summary string, so we limit its
188 /// length.
189 const size_t max_length = 32;
190
191 auto start = std::chrono::steady_clock::now();
192 std::string summary;
193 llvm::raw_string_ostream os(summary);
194 os << "{";
195
196 llvm::StringRef separator = "";
197
198 for (size_t i = 0, e = v.GetNumChildren(); i < e; ++i) {
199 // If we reached the time limit or exceeded the number of characters, we
200 // dump `...` to signal that there are more elements in the collection.
201 if (summary.size() > max_length ||
202 (std::chrono::steady_clock::now() - start) > max_evaluation_time) {
203 os << separator << "...";
204 break;
205 }
206 lldb::SBValue child = v.GetChildAtIndex(idx: i);
207
208 if (llvm::StringRef name = child.GetName(); !name.empty()) {
209 llvm::StringRef desc;
210 if (llvm::StringRef summary = child.GetSummary(); !summary.empty())
211 desc = summary;
212 else if (llvm::StringRef value = child.GetValue(); !value.empty())
213 desc = value;
214 else if (IsClassStructOrUnionType(t: child.GetType()))
215 desc = "{...}";
216 else
217 continue;
218
219 // If the child is an indexed entry, we don't show its index to save
220 // characters.
221 if (name.starts_with(Prefix: "["))
222 os << separator << desc;
223 else
224 os << separator << name << ":" << desc;
225 separator = ", ";
226 }
227 }
228 os << "}";
229
230 if (summary == "{...}" || summary == "{}")
231 return std::nullopt;
232 return summary;
233}
234
235/// Try to create a summary string for the given value that doesn't have a
236/// summary of its own.
237static std::optional<std::string> TryCreateAutoSummary(lldb::SBValue &value) {
238 // We use the dereferenced value for generating the summary.
239 if (value.GetType().IsPointerType() || value.GetType().IsReferenceType())
240 value = value.Dereference();
241
242 // We only support auto summaries for containers.
243 return TryCreateAutoSummaryForContainer(v&: value);
244}
245
246void FillResponse(const llvm::json::Object &request,
247 llvm::json::Object &response) {
248 // Fill in all of the needed response fields to a "request" and set "success"
249 // to true by default.
250 response.try_emplace(K: "type", Args: "response");
251 response.try_emplace(K: "seq", Args: (int64_t)0);
252 EmplaceSafeString(obj&: response, key: "command",
253 str: GetString(obj: request, key: "command").value_or(u: ""));
254 const uint64_t seq = GetInteger<uint64_t>(obj: request, key: "seq").value_or(u: 0);
255 response.try_emplace(K: "request_seq", Args: seq);
256 response.try_emplace(K: "success", Args: true);
257}
258
259// "Scope": {
260// "type": "object",
261// "description": "A Scope is a named container for variables. Optionally
262// a scope can map to a source or a range within a source.",
263// "properties": {
264// "name": {
265// "type": "string",
266// "description": "Name of the scope such as 'Arguments', 'Locals'."
267// },
268// "presentationHint": {
269// "type": "string",
270// "description": "An optional hint for how to present this scope in the
271// UI. If this attribute is missing, the scope is shown
272// with a generic UI.",
273// "_enum": [ "arguments", "locals", "registers" ],
274// },
275// "variablesReference": {
276// "type": "integer",
277// "description": "The variables of this scope can be retrieved by
278// passing the value of variablesReference to the
279// VariablesRequest."
280// },
281// "namedVariables": {
282// "type": "integer",
283// "description": "The number of named variables in this scope. The
284// client can use this optional information to present
285// the variables in a paged UI and fetch them in chunks."
286// },
287// "indexedVariables": {
288// "type": "integer",
289// "description": "The number of indexed variables in this scope. The
290// client can use this optional information to present
291// the variables in a paged UI and fetch them in chunks."
292// },
293// "expensive": {
294// "type": "boolean",
295// "description": "If true, the number of variables in this scope is
296// large or expensive to retrieve."
297// },
298// "source": {
299// "$ref": "#/definitions/Source",
300// "description": "Optional source for this scope."
301// },
302// "line": {
303// "type": "integer",
304// "description": "Optional start line of the range covered by this
305// scope."
306// },
307// "column": {
308// "type": "integer",
309// "description": "Optional start column of the range covered by this
310// scope."
311// },
312// "endLine": {
313// "type": "integer",
314// "description": "Optional end line of the range covered by this scope."
315// },
316// "endColumn": {
317// "type": "integer",
318// "description": "Optional end column of the range covered by this
319// scope."
320// }
321// },
322// "required": [ "name", "variablesReference", "expensive" ]
323// }
324llvm::json::Value CreateScope(const llvm::StringRef name,
325 int64_t variablesReference,
326 int64_t namedVariables, bool expensive) {
327 llvm::json::Object object;
328 EmplaceSafeString(obj&: object, key: "name", str: name.str());
329
330 // TODO: Support "arguments" scope. At the moment lldb-dap includes the
331 // arguments into the "locals" scope.
332 if (variablesReference == VARREF_LOCALS) {
333 object.try_emplace(K: "presentationHint", Args: "locals");
334 } else if (variablesReference == VARREF_REGS) {
335 object.try_emplace(K: "presentationHint", Args: "registers");
336 }
337
338 object.try_emplace(K: "variablesReference", Args&: variablesReference);
339 object.try_emplace(K: "expensive", Args&: expensive);
340 object.try_emplace(K: "namedVariables", Args&: namedVariables);
341 return llvm::json::Value(std::move(object));
342}
343
344static uint64_t GetDebugInfoSizeInSection(lldb::SBSection section) {
345 uint64_t debug_info_size = 0;
346 llvm::StringRef section_name(section.GetName());
347 if (section_name.starts_with(Prefix: ".debug") ||
348 section_name.starts_with(Prefix: "__debug") ||
349 section_name.starts_with(Prefix: ".apple") || section_name.starts_with(Prefix: "__apple"))
350 debug_info_size += section.GetFileByteSize();
351 size_t num_sub_sections = section.GetNumSubSections();
352 for (size_t i = 0; i < num_sub_sections; i++) {
353 debug_info_size +=
354 GetDebugInfoSizeInSection(section: section.GetSubSectionAtIndex(idx: i));
355 }
356 return debug_info_size;
357}
358
359static uint64_t GetDebugInfoSize(lldb::SBModule module) {
360 uint64_t debug_info_size = 0;
361 size_t num_sections = module.GetNumSections();
362 for (size_t i = 0; i < num_sections; i++) {
363 debug_info_size += GetDebugInfoSizeInSection(section: module.GetSectionAtIndex(idx: i));
364 }
365 return debug_info_size;
366}
367
368static std::string ConvertDebugInfoSizeToString(uint64_t debug_info) {
369 std::ostringstream oss;
370 oss << std::fixed << std::setprecision(1);
371 if (debug_info < 1024) {
372 oss << debug_info << "B";
373 } else if (debug_info < 1024 * 1024) {
374 double kb = double(debug_info) / 1024.0;
375 oss << kb << "KB";
376 } else if (debug_info < 1024 * 1024 * 1024) {
377 double mb = double(debug_info) / (1024.0 * 1024.0);
378 oss << mb << "MB";
379 } else {
380 double gb = double(debug_info) / (1024.0 * 1024.0 * 1024.0);
381 oss << gb << "GB";
382 }
383 return oss.str();
384}
385
386llvm::json::Value CreateModule(lldb::SBTarget &target, lldb::SBModule &module,
387 bool id_only) {
388 llvm::json::Object object;
389 if (!target.IsValid() || !module.IsValid())
390 return llvm::json::Value(std::move(object));
391
392 const char *uuid = module.GetUUIDString();
393 object.try_emplace(K: "id", Args: uuid ? std::string(uuid) : std::string(""));
394
395 if (id_only)
396 return llvm::json::Value(std::move(object));
397
398 object.try_emplace(K: "name", Args: std::string(module.GetFileSpec().GetFilename()));
399 char module_path_arr[PATH_MAX];
400 module.GetFileSpec().GetPath(dst_path: module_path_arr, dst_len: sizeof(module_path_arr));
401 std::string module_path(module_path_arr);
402 object.try_emplace(K: "path", Args&: module_path);
403 if (module.GetNumCompileUnits() > 0) {
404 std::string symbol_str = "Symbols loaded.";
405 std::string debug_info_size;
406 uint64_t debug_info = GetDebugInfoSize(module);
407 if (debug_info > 0) {
408 debug_info_size = ConvertDebugInfoSizeToString(debug_info);
409 }
410 object.try_emplace(K: "symbolStatus", Args&: symbol_str);
411 object.try_emplace(K: "debugInfoSize", Args&: debug_info_size);
412 char symbol_path_arr[PATH_MAX];
413 module.GetSymbolFileSpec().GetPath(dst_path: symbol_path_arr,
414 dst_len: sizeof(symbol_path_arr));
415 std::string symbol_path(symbol_path_arr);
416 object.try_emplace(K: "symbolFilePath", Args&: symbol_path);
417 } else {
418 object.try_emplace(K: "symbolStatus", Args: "Symbols not found.");
419 }
420 std::string load_address =
421 llvm::formatv(Fmt: "{0:x}",
422 Vals: module.GetObjectFileHeaderAddress().GetLoadAddress(target))
423 .str();
424 object.try_emplace(K: "addressRange", Args&: load_address);
425 std::string version_str;
426 uint32_t version_nums[3];
427 uint32_t num_versions =
428 module.GetVersion(versions: version_nums, num_versions: sizeof(version_nums) / sizeof(uint32_t));
429 for (uint32_t i = 0; i < num_versions; ++i) {
430 if (!version_str.empty())
431 version_str += ".";
432 version_str += std::to_string(val: version_nums[i]);
433 }
434 if (!version_str.empty())
435 object.try_emplace(K: "version", Args&: version_str);
436 return llvm::json::Value(std::move(object));
437}
438
439// "Event": {
440// "allOf": [ { "$ref": "#/definitions/ProtocolMessage" }, {
441// "type": "object",
442// "description": "Server-initiated event.",
443// "properties": {
444// "type": {
445// "type": "string",
446// "enum": [ "event" ]
447// },
448// "event": {
449// "type": "string",
450// "description": "Type of event."
451// },
452// "body": {
453// "type": [ "array", "boolean", "integer", "null", "number" ,
454// "object", "string" ],
455// "description": "Event-specific information."
456// }
457// },
458// "required": [ "type", "event" ]
459// }]
460// },
461// "ProtocolMessage": {
462// "type": "object",
463// "description": "Base class of requests, responses, and events.",
464// "properties": {
465// "seq": {
466// "type": "integer",
467// "description": "Sequence number."
468// },
469// "type": {
470// "type": "string",
471// "description": "Message type.",
472// "_enum": [ "request", "response", "event" ]
473// }
474// },
475// "required": [ "seq", "type" ]
476// }
477llvm::json::Object CreateEventObject(const llvm::StringRef event_name) {
478 llvm::json::Object event;
479 event.try_emplace(K: "seq", Args: 0);
480 event.try_emplace(K: "type", Args: "event");
481 EmplaceSafeString(obj&: event, key: "event", str: event_name);
482 return event;
483}
484
485protocol::ExceptionBreakpointsFilter
486CreateExceptionBreakpointFilter(const ExceptionBreakpoint &bp) {
487 protocol::ExceptionBreakpointsFilter filter;
488 filter.filter = bp.GetFilter();
489 filter.label = bp.GetLabel();
490 filter.defaultState = ExceptionBreakpoint::kDefaultValue;
491 return filter;
492}
493
494// "StackFrame": {
495// "type": "object",
496// "description": "A Stackframe contains the source location.",
497// "properties": {
498// "id": {
499// "type": "integer",
500// "description": "An identifier for the stack frame. It must be unique
501// across all threads. This id can be used to retrieve
502// the scopes of the frame with the 'scopesRequest' or
503// to restart the execution of a stackframe."
504// },
505// "name": {
506// "type": "string",
507// "description": "The name of the stack frame, typically a method name."
508// },
509// "source": {
510// "$ref": "#/definitions/Source",
511// "description": "The optional source of the frame."
512// },
513// "line": {
514// "type": "integer",
515// "description": "The line within the file of the frame. If source is
516// null or doesn't exist, line is 0 and must be ignored."
517// },
518// "column": {
519// "type": "integer",
520// "description": "The column within the line. If source is null or
521// doesn't exist, column is 0 and must be ignored."
522// },
523// "endLine": {
524// "type": "integer",
525// "description": "An optional end line of the range covered by the
526// stack frame."
527// },
528// "endColumn": {
529// "type": "integer",
530// "description": "An optional end column of the range covered by the
531// stack frame."
532// },
533// "instructionPointerReference": {
534// "type": "string",
535// "description": "A memory reference for the current instruction
536// pointer in this frame."
537// },
538// "moduleId": {
539// "type": ["integer", "string"],
540// "description": "The module associated with this frame, if any."
541// },
542// "presentationHint": {
543// "type": "string",
544// "enum": [ "normal", "label", "subtle" ],
545// "description": "An optional hint for how to present this frame in
546// the UI. A value of 'label' can be used to indicate
547// that the frame is an artificial frame that is used
548// as a visual label or separator. A value of 'subtle'
549// can be used to change the appearance of a frame in
550// a 'subtle' way."
551// }
552// },
553// "required": [ "id", "name", "line", "column" ]
554// }
555llvm::json::Value CreateStackFrame(lldb::SBFrame &frame,
556 lldb::SBFormat &format) {
557 llvm::json::Object object;
558 int64_t frame_id = MakeDAPFrameID(frame);
559 object.try_emplace(K: "id", Args&: frame_id);
560
561 std::string frame_name;
562 lldb::SBStream stream;
563 if (format && frame.GetDescriptionWithFormat(format, output&: stream).Success()) {
564 frame_name = stream.GetData();
565
566 // `function_name` can be a nullptr, which throws an error when assigned to
567 // an `std::string`.
568 } else if (const char *name = frame.GetDisplayFunctionName()) {
569 frame_name = name;
570 }
571
572 if (frame_name.empty()) {
573 // If the function name is unavailable, display the pc address as a 16-digit
574 // hex string, e.g. "0x0000000000012345"
575 frame_name = GetLoadAddressString(addr: frame.GetPC());
576 }
577
578 // We only include `[opt]` if a custom frame format is not specified.
579 if (!format && frame.GetFunction().GetIsOptimized())
580 frame_name += " [opt]";
581
582 EmplaceSafeString(obj&: object, key: "name", str: frame_name);
583
584 auto target = frame.GetThread().GetProcess().GetTarget();
585 auto source = CreateSource(address: frame.GetPCAddress(), target);
586 if (!IsAssemblySource(source)) {
587 // This is a normal source with a valid line entry.
588 auto line_entry = frame.GetLineEntry();
589 object.try_emplace(K: "line", Args: line_entry.GetLine());
590 auto column = line_entry.GetColumn();
591 object.try_emplace(K: "column", Args&: column);
592 } else if (frame.GetSymbol().IsValid()) {
593 // This is a source where the disassembly is used, but there is a valid
594 // symbol. Calculate the line of the current PC from the start of the
595 // current symbol.
596 lldb::SBTarget target = frame.GetThread().GetProcess().GetTarget();
597 lldb::SBInstructionList inst_list = target.ReadInstructions(
598 start_addr: frame.GetSymbol().GetStartAddress(), end_addr: frame.GetPCAddress(), flavor_string: nullptr);
599 size_t inst_line = inst_list.GetSize();
600
601 // Line numbers are 1-based.
602 object.try_emplace(K: "line", Args: inst_line + 1);
603 object.try_emplace(K: "column", Args: 1);
604 } else {
605 // No valid line entry or symbol.
606 object.try_emplace(K: "line", Args: 1);
607 object.try_emplace(K: "column", Args: 1);
608 }
609
610 object.try_emplace(K: "source", Args: std::move(source));
611
612 const auto pc = frame.GetPC();
613 if (pc != LLDB_INVALID_ADDRESS) {
614 std::string formatted_addr = "0x" + llvm::utohexstr(X: pc);
615 object.try_emplace(K: "instructionPointerReference", Args&: formatted_addr);
616 }
617
618 if (frame.IsArtificial() || frame.IsHidden())
619 object.try_emplace(K: "presentationHint", Args: "subtle");
620
621 return llvm::json::Value(std::move(object));
622}
623
624llvm::json::Value CreateExtendedStackFrameLabel(lldb::SBThread &thread,
625 lldb::SBFormat &format) {
626 std::string name;
627 lldb::SBStream stream;
628 if (format && thread.GetDescriptionWithFormat(format, output&: stream).Success()) {
629 name = stream.GetData();
630 } else {
631 const uint32_t thread_idx = thread.GetExtendedBacktraceOriginatingIndexID();
632 const char *queue_name = thread.GetQueueName();
633 if (queue_name != nullptr) {
634 name = llvm::formatv(Fmt: "Enqueued from {0} (Thread {1})", Vals&: queue_name,
635 Vals: thread_idx);
636 } else {
637 name = llvm::formatv(Fmt: "Thread {0}", Vals: thread_idx);
638 }
639 }
640
641 return llvm::json::Value(llvm::json::Object{{.K: "id", .V: thread.GetThreadID() + 1},
642 {.K: "name", .V: name},
643 {.K: "presentationHint", .V: "label"}});
644}
645
646// "StoppedEvent": {
647// "allOf": [ { "$ref": "#/definitions/Event" }, {
648// "type": "object",
649// "description": "Event message for 'stopped' event type. The event
650// indicates that the execution of the debuggee has stopped
651// due to some condition. This can be caused by a break
652// point previously set, a stepping action has completed,
653// by executing a debugger statement etc.",
654// "properties": {
655// "event": {
656// "type": "string",
657// "enum": [ "stopped" ]
658// },
659// "body": {
660// "type": "object",
661// "properties": {
662// "reason": {
663// "type": "string",
664// "description": "The reason for the event. For backward
665// compatibility this string is shown in the UI if
666// the 'description' attribute is missing (but it
667// must not be translated).",
668// "_enum": [ "step", "breakpoint", "exception", "pause", "entry" ]
669// },
670// "description": {
671// "type": "string",
672// "description": "The full reason for the event, e.g. 'Paused
673// on exception'. This string is shown in the UI
674// as is."
675// },
676// "threadId": {
677// "type": "integer",
678// "description": "The thread which was stopped."
679// },
680// "text": {
681// "type": "string",
682// "description": "Additional information. E.g. if reason is
683// 'exception', text contains the exception name.
684// This string is shown in the UI."
685// },
686// "allThreadsStopped": {
687// "type": "boolean",
688// "description": "If allThreadsStopped is true, a debug adapter
689// can announce that all threads have stopped.
690// The client should use this information to
691// enable that all threads can be expanded to
692// access their stacktraces. If the attribute
693// is missing or false, only the thread with the
694// given threadId can be expanded."
695// }
696// },
697// "required": [ "reason" ]
698// }
699// },
700// "required": [ "event", "body" ]
701// }]
702// }
703llvm::json::Value CreateThreadStopped(DAP &dap, lldb::SBThread &thread,
704 uint32_t stop_id) {
705 llvm::json::Object event(CreateEventObject(event_name: "stopped"));
706 llvm::json::Object body;
707 switch (thread.GetStopReason()) {
708 case lldb::eStopReasonTrace:
709 case lldb::eStopReasonPlanComplete:
710 body.try_emplace(K: "reason", Args: "step");
711 break;
712 case lldb::eStopReasonBreakpoint: {
713 ExceptionBreakpoint *exc_bp = dap.GetExceptionBPFromStopReason(thread);
714 if (exc_bp) {
715 body.try_emplace(K: "reason", Args: "exception");
716 EmplaceSafeString(obj&: body, key: "description", str: exc_bp->GetLabel());
717 } else {
718 InstructionBreakpoint *inst_bp =
719 dap.GetInstructionBPFromStopReason(thread);
720 if (inst_bp) {
721 body.try_emplace(K: "reason", Args: "instruction breakpoint");
722 } else {
723 body.try_emplace(K: "reason", Args: "breakpoint");
724 }
725 lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(idx: 0);
726 lldb::break_id_t bp_loc_id = thread.GetStopReasonDataAtIndex(idx: 1);
727 std::string desc_str =
728 llvm::formatv(Fmt: "breakpoint {0}.{1}", Vals&: bp_id, Vals&: bp_loc_id);
729 body.try_emplace(K: "hitBreakpointIds",
730 Args: llvm::json::Array{llvm::json::Value(bp_id)});
731 EmplaceSafeString(obj&: body, key: "description", str: desc_str);
732 }
733 } break;
734 case lldb::eStopReasonWatchpoint:
735 case lldb::eStopReasonInstrumentation:
736 body.try_emplace(K: "reason", Args: "breakpoint");
737 break;
738 case lldb::eStopReasonProcessorTrace:
739 body.try_emplace(K: "reason", Args: "processor trace");
740 break;
741 case lldb::eStopReasonHistoryBoundary:
742 body.try_emplace(K: "reason", Args: "history boundary");
743 break;
744 case lldb::eStopReasonSignal:
745 case lldb::eStopReasonException:
746 body.try_emplace(K: "reason", Args: "exception");
747 break;
748 case lldb::eStopReasonExec:
749 body.try_emplace(K: "reason", Args: "entry");
750 break;
751 case lldb::eStopReasonFork:
752 body.try_emplace(K: "reason", Args: "fork");
753 break;
754 case lldb::eStopReasonVFork:
755 body.try_emplace(K: "reason", Args: "vfork");
756 break;
757 case lldb::eStopReasonVForkDone:
758 body.try_emplace(K: "reason", Args: "vforkdone");
759 break;
760 case lldb::eStopReasonInterrupt:
761 body.try_emplace(K: "reason", Args: "async interrupt");
762 break;
763 case lldb::eStopReasonThreadExiting:
764 case lldb::eStopReasonInvalid:
765 case lldb::eStopReasonNone:
766 break;
767 }
768 if (stop_id == 0)
769 body.try_emplace(K: "reason", Args: "entry");
770 const lldb::tid_t tid = thread.GetThreadID();
771 body.try_emplace(K: "threadId", Args: (int64_t)tid);
772 // If no description has been set, then set it to the default thread stopped
773 // description. If we have breakpoints that get hit and shouldn't be reported
774 // as breakpoints, then they will set the description above.
775 if (!ObjectContainsKey(obj: body, key: "description")) {
776 char description[1024];
777 if (thread.GetStopDescription(dst_or_null: description, dst_len: sizeof(description))) {
778 EmplaceSafeString(obj&: body, key: "description", str: description);
779 }
780 }
781 // "threadCausedFocus" is used in tests to validate breaking behavior.
782 if (tid == dap.focus_tid) {
783 body.try_emplace(K: "threadCausedFocus", Args: true);
784 }
785 body.try_emplace(K: "preserveFocusHint", Args: tid != dap.focus_tid);
786 body.try_emplace(K: "allThreadsStopped", Args: true);
787 event.try_emplace(K: "body", Args: std::move(body));
788 return llvm::json::Value(std::move(event));
789}
790
791const char *GetNonNullVariableName(lldb::SBValue &v) {
792 const char *name = v.GetName();
793 return name ? name : "<null>";
794}
795
796std::string CreateUniqueVariableNameForDisplay(lldb::SBValue &v,
797 bool is_name_duplicated) {
798 lldb::SBStream name_builder;
799 name_builder.Print(str: GetNonNullVariableName(v));
800 if (is_name_duplicated) {
801 lldb::SBDeclaration declaration = v.GetDeclaration();
802 const char *file_name = declaration.GetFileSpec().GetFilename();
803 const uint32_t line = declaration.GetLine();
804
805 if (file_name != nullptr && line > 0)
806 name_builder.Printf(format: " @ %s:%u", file_name, line);
807 else if (const char *location = v.GetLocation())
808 name_builder.Printf(format: " @ %s", location);
809 }
810 return name_builder.GetData();
811}
812
813VariableDescription::VariableDescription(lldb::SBValue v,
814 bool auto_variable_summaries,
815 bool format_hex,
816 bool is_name_duplicated,
817 std::optional<std::string> custom_name)
818 : v(v) {
819 name = custom_name
820 ? *custom_name
821 : CreateUniqueVariableNameForDisplay(v, is_name_duplicated);
822
823 type_obj = v.GetType();
824 std::string raw_display_type_name =
825 llvm::StringRef(type_obj.GetDisplayTypeName()).str();
826 display_type_name =
827 !raw_display_type_name.empty() ? raw_display_type_name : NO_TYPENAME;
828
829 // Only format hex/default if there is no existing special format.
830 if (v.GetFormat() == lldb::eFormatDefault ||
831 v.GetFormat() == lldb::eFormatHex) {
832 if (format_hex)
833 v.SetFormat(lldb::eFormatHex);
834 else
835 v.SetFormat(lldb::eFormatDefault);
836 }
837
838 llvm::raw_string_ostream os_display_value(display_value);
839
840 if (lldb::SBError sb_error = v.GetError(); sb_error.Fail()) {
841 error = sb_error.GetCString();
842 os_display_value << "<error: " << error << ">";
843 } else {
844 value = llvm::StringRef(v.GetValue()).str();
845 summary = llvm::StringRef(v.GetSummary()).str();
846 if (summary.empty() && auto_variable_summaries)
847 auto_summary = TryCreateAutoSummary(value&: v);
848
849 std::optional<std::string> effective_summary =
850 !summary.empty() ? summary : auto_summary;
851
852 if (!value.empty()) {
853 os_display_value << value;
854 if (effective_summary)
855 os_display_value << " " << *effective_summary;
856 } else if (effective_summary) {
857 os_display_value << *effective_summary;
858
859 // As last resort, we print its type and address if available.
860 } else {
861 if (!raw_display_type_name.empty()) {
862 os_display_value << raw_display_type_name;
863 lldb::addr_t address = v.GetLoadAddress();
864 if (address != LLDB_INVALID_ADDRESS)
865 os_display_value << " @ " << llvm::format_hex(N: address, Width: 0);
866 }
867 }
868 }
869
870 lldb::SBStream evaluateStream;
871 v.GetExpressionPath(description&: evaluateStream);
872 evaluate_name = llvm::StringRef(evaluateStream.GetData()).str();
873}
874
875llvm::json::Object VariableDescription::GetVariableExtensionsJSON() {
876 llvm::json::Object extensions;
877 if (error)
878 EmplaceSafeString(obj&: extensions, key: "error", str: *error);
879 if (!value.empty())
880 EmplaceSafeString(obj&: extensions, key: "value", str: value);
881 if (!summary.empty())
882 EmplaceSafeString(obj&: extensions, key: "summary", str: summary);
883 if (auto_summary)
884 EmplaceSafeString(obj&: extensions, key: "autoSummary", str: *auto_summary);
885
886 if (lldb::SBDeclaration decl = v.GetDeclaration(); decl.IsValid()) {
887 llvm::json::Object decl_obj;
888 if (lldb::SBFileSpec file = decl.GetFileSpec(); file.IsValid()) {
889 char path[PATH_MAX] = "";
890 if (file.GetPath(dst_path: path, dst_len: sizeof(path)) &&
891 lldb::SBFileSpec::ResolvePath(src_path: path, dst_path: path, PATH_MAX)) {
892 decl_obj.try_emplace(K: "path", Args: std::string(path));
893 }
894 }
895
896 if (int line = decl.GetLine())
897 decl_obj.try_emplace(K: "line", Args&: line);
898 if (int column = decl.GetColumn())
899 decl_obj.try_emplace(K: "column", Args&: column);
900
901 if (!decl_obj.empty())
902 extensions.try_emplace(K: "declaration", Args: std::move(decl_obj));
903 }
904 return extensions;
905}
906
907std::string VariableDescription::GetResult(llvm::StringRef context) {
908 // In repl context, the results can be displayed as multiple lines so more
909 // detailed descriptions can be returned.
910 if (context != "repl")
911 return display_value;
912
913 if (!v.IsValid())
914 return display_value;
915
916 // Try the SBValue::GetDescription(), which may call into language runtime
917 // specific formatters (see ValueObjectPrinter).
918 lldb::SBStream stream;
919 v.GetDescription(description&: stream);
920 llvm::StringRef description = stream.GetData();
921 return description.trim().str();
922}
923
924bool ValuePointsToCode(lldb::SBValue v) {
925 if (!v.GetType().GetPointeeType().IsFunctionType())
926 return false;
927
928 lldb::addr_t addr = v.GetValueAsAddress();
929 lldb::SBLineEntry line_entry =
930 v.GetTarget().ResolveLoadAddress(vm_addr: addr).GetLineEntry();
931
932 return line_entry.IsValid();
933}
934
935int64_t PackLocation(int64_t var_ref, bool is_value_location) {
936 return var_ref << 1 | is_value_location;
937}
938
939std::pair<int64_t, bool> UnpackLocation(int64_t location_id) {
940 return std::pair{location_id >> 1, location_id & 1};
941}
942
943// "Variable": {
944// "type": "object",
945// "description": "A Variable is a name/value pair. Optionally a variable
946// can have a 'type' that is shown if space permits or when
947// hovering over the variable's name. An optional 'kind' is
948// used to render additional properties of the variable,
949// e.g. different icons can be used to indicate that a
950// variable is public or private. If the value is
951// structured (has children), a handle is provided to
952// retrieve the children with the VariablesRequest. If
953// the number of named or indexed children is large, the
954// numbers should be returned via the optional
955// 'namedVariables' and 'indexedVariables' attributes. The
956// client can use this optional information to present the
957// children in a paged UI and fetch them in chunks.",
958// "properties": {
959// "name": {
960// "type": "string",
961// "description": "The variable's name."
962// },
963// "value": {
964// "type": "string",
965// "description": "The variable's value. This can be a multi-line text,
966// e.g. for a function the body of a function."
967// },
968// "type": {
969// "type": "string",
970// "description": "The type of the variable's value. Typically shown in
971// the UI when hovering over the value."
972// },
973// "presentationHint": {
974// "$ref": "#/definitions/VariablePresentationHint",
975// "description": "Properties of a variable that can be used to determine
976// how to render the variable in the UI."
977// },
978// "evaluateName": {
979// "type": "string",
980// "description": "Optional evaluatable name of this variable which can
981// be passed to the 'EvaluateRequest' to fetch the
982// variable's value."
983// },
984// "variablesReference": {
985// "type": "integer",
986// "description": "If variablesReference is > 0, the variable is
987// structured and its children can be retrieved by
988// passing variablesReference to the VariablesRequest."
989// },
990// "namedVariables": {
991// "type": "integer",
992// "description": "The number of named child variables. The client can
993// use this optional information to present the children
994// in a paged UI and fetch them in chunks."
995// },
996// "indexedVariables": {
997// "type": "integer",
998// "description": "The number of indexed child variables. The client
999// can use this optional information to present the
1000// children in a paged UI and fetch them in chunks."
1001// },
1002// "memoryReference": {
1003// "type": "string",
1004// "description": "A memory reference associated with this variable.
1005// For pointer type variables, this is generally a
1006// reference to the memory address contained in the
1007// pointer. For executable data, this reference may later
1008// be used in a `disassemble` request. This attribute may
1009// be returned by a debug adapter if corresponding
1010// capability `supportsMemoryReferences` is true."
1011// },
1012// "declarationLocationReference": {
1013// "type": "integer",
1014// "description": "A reference that allows the client to request the
1015// location where the variable is declared. This should be
1016// present only if the adapter is likely to be able to
1017// resolve the location.\n\nThis reference shares the same
1018// lifetime as the `variablesReference`. See 'Lifetime of
1019// Object References' in the Overview section for
1020// details."
1021// },
1022// "valueLocationReference": {
1023// "type": "integer",
1024// "description": "A reference that allows the client to request the
1025// location where the variable's value is declared. For
1026// example, if the variable contains a function pointer,
1027// the adapter may be able to look up the function's
1028// location. This should be present only if the adapter
1029// is likely to be able to resolve the location.\n\nThis
1030// reference shares the same lifetime as the
1031// `variablesReference`. See 'Lifetime of Object
1032// References' in the Overview section for details."
1033// },
1034//
1035// "$__lldb_extensions": {
1036// "description": "Unofficial extensions to the protocol",
1037// "properties": {
1038// "declaration": {
1039// "type": "object",
1040// "description": "The source location where the variable was
1041// declared. This value won't be present if no
1042// declaration is available.
1043// Superseded by `declarationLocationReference`",
1044// "properties": {
1045// "path": {
1046// "type": "string",
1047// "description": "The source file path where the variable was
1048// declared."
1049// },
1050// "line": {
1051// "type": "number",
1052// "description": "The 1-indexed source line where the variable
1053// was declared."
1054// },
1055// "column": {
1056// "type": "number",
1057// "description": "The 1-indexed source column where the variable
1058// was declared."
1059// }
1060// }
1061// },
1062// "value": {
1063// "type": "string",
1064// "description": "The internal value of the variable as returned by
1065// This is effectively SBValue.GetValue(). The other
1066// `value` entry in the top-level variable response
1067// is, on the other hand, just a display string for
1068// the variable."
1069// },
1070// "summary": {
1071// "type": "string",
1072// "description": "The summary string of the variable. This is
1073// effectively SBValue.GetSummary()."
1074// },
1075// "autoSummary": {
1076// "type": "string",
1077// "description": "The auto generated summary if using
1078// `enableAutoVariableSummaries`."
1079// },
1080// "error": {
1081// "type": "string",
1082// "description": "An error message generated if LLDB couldn't inspect
1083// the variable."
1084// }
1085// }
1086// }
1087// },
1088// "required": [ "name", "value", "variablesReference" ]
1089// }
1090llvm::json::Value CreateVariable(lldb::SBValue v, int64_t var_ref,
1091 bool format_hex, bool auto_variable_summaries,
1092 bool synthetic_child_debugging,
1093 bool is_name_duplicated,
1094 std::optional<std::string> custom_name) {
1095 VariableDescription desc(v, auto_variable_summaries, format_hex,
1096 is_name_duplicated, custom_name);
1097 llvm::json::Object object;
1098 EmplaceSafeString(obj&: object, key: "name", str: desc.name);
1099 EmplaceSafeString(obj&: object, key: "value", str: desc.display_value);
1100
1101 if (!desc.evaluate_name.empty())
1102 EmplaceSafeString(obj&: object, key: "evaluateName", str: desc.evaluate_name);
1103
1104 // If we have a type with many children, we would like to be able to
1105 // give a hint to the IDE that the type has indexed children so that the
1106 // request can be broken up in grabbing only a few children at a time. We
1107 // want to be careful and only call "v.GetNumChildren()" if we have an array
1108 // type or if we have a synthetic child provider producing indexed children.
1109 // We don't want to call "v.GetNumChildren()" on all objects as class, struct
1110 // and union types don't need to be completed if they are never expanded. So
1111 // we want to avoid calling this to only cases where we it makes sense to keep
1112 // performance high during normal debugging.
1113
1114 // If we have an array type, say that it is indexed and provide the number
1115 // of children in case we have a huge array. If we don't do this, then we
1116 // might take a while to produce all children at onces which can delay your
1117 // debug session.
1118 if (desc.type_obj.IsArrayType()) {
1119 object.try_emplace(K: "indexedVariables", Args: v.GetNumChildren());
1120 } else if (v.IsSynthetic()) {
1121 // For a type with a synthetic child provider, the SBType of "v" won't tell
1122 // us anything about what might be displayed. Instead, we check if the first
1123 // child's name is "[0]" and then say it is indexed. We call
1124 // GetNumChildren() only if the child name matches to avoid a potentially
1125 // expensive operation.
1126 if (lldb::SBValue first_child = v.GetChildAtIndex(idx: 0)) {
1127 llvm::StringRef first_child_name = first_child.GetName();
1128 if (first_child_name == "[0]") {
1129 size_t num_children = v.GetNumChildren();
1130 // If we are creating a "[raw]" fake child for each synthetic type, we
1131 // have to account for it when returning indexed variables.
1132 if (synthetic_child_debugging)
1133 ++num_children;
1134 object.try_emplace(K: "indexedVariables", Args&: num_children);
1135 }
1136 }
1137 }
1138 EmplaceSafeString(obj&: object, key: "type", str: desc.display_type_name);
1139
1140 // A unique variable identifier to help in properly identifying variables with
1141 // the same name. This is an extension to the VS protocol.
1142 object.try_emplace(K: "id", Args&: var_ref);
1143
1144 if (v.MightHaveChildren())
1145 object.try_emplace(K: "variablesReference", Args&: var_ref);
1146 else
1147 object.try_emplace(K: "variablesReference", Args: 0);
1148
1149 if (v.GetDeclaration().IsValid())
1150 object.try_emplace(K: "declarationLocationReference",
1151 Args: PackLocation(var_ref, is_value_location: false));
1152
1153 if (ValuePointsToCode(v))
1154 object.try_emplace(K: "valueLocationReference", Args: PackLocation(var_ref, is_value_location: true));
1155
1156 if (lldb::addr_t addr = v.GetLoadAddress(); addr != LLDB_INVALID_ADDRESS)
1157 object.try_emplace(K: "memoryReference", Args: EncodeMemoryReference(addr));
1158
1159 object.try_emplace(K: "$__lldb_extensions", Args: desc.GetVariableExtensionsJSON());
1160 return llvm::json::Value(std::move(object));
1161}
1162
1163llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit &unit) {
1164 llvm::json::Object object;
1165 char unit_path_arr[PATH_MAX];
1166 unit.GetFileSpec().GetPath(dst_path: unit_path_arr, dst_len: sizeof(unit_path_arr));
1167 std::string unit_path(unit_path_arr);
1168 object.try_emplace(K: "compileUnitPath", Args&: unit_path);
1169 return llvm::json::Value(std::move(object));
1170}
1171
1172/// See
1173/// https://microsoft.github.io/debug-adapter-protocol/specification#Reverse_Requests_RunInTerminal
1174llvm::json::Object CreateRunInTerminalReverseRequest(
1175 llvm::StringRef program, const std::vector<std::string> &args,
1176 const llvm::StringMap<std::string> &env, llvm::StringRef cwd,
1177 llvm::StringRef comm_file, lldb::pid_t debugger_pid) {
1178 llvm::json::Object run_in_terminal_args;
1179 // This indicates the IDE to open an embedded terminal, instead of opening
1180 // the terminal in a new window.
1181 run_in_terminal_args.try_emplace(K: "kind", Args: "integrated");
1182
1183 // The program path must be the first entry in the "args" field
1184 std::vector<std::string> req_args = {DAP::debug_adapter_path.str(),
1185 "--comm-file", comm_file.str()};
1186 if (debugger_pid != LLDB_INVALID_PROCESS_ID) {
1187 req_args.push_back(x: "--debugger-pid");
1188 req_args.push_back(x: std::to_string(val: debugger_pid));
1189 }
1190 req_args.push_back(x: "--launch-target");
1191 req_args.push_back(x: program.str());
1192 req_args.insert(position: req_args.end(), first: args.begin(), last: args.end());
1193 run_in_terminal_args.try_emplace(K: "args", Args&: req_args);
1194
1195 if (!cwd.empty())
1196 run_in_terminal_args.try_emplace(K: "cwd", Args&: cwd);
1197
1198 if (!env.empty()) {
1199 llvm::json::Object env_json;
1200 for (const auto &kv : env) {
1201 if (!kv.first().empty())
1202 env_json.try_emplace(K: kv.first(), Args: kv.second);
1203 }
1204 run_in_terminal_args.try_emplace(K: "env",
1205 Args: llvm::json::Value(std::move(env_json)));
1206 }
1207
1208 return run_in_terminal_args;
1209}
1210
1211// Keep all the top level items from the statistics dump, except for the
1212// "modules" array. It can be huge and cause delay
1213// Array and dictionary value will return as <key, JSON string> pairs
1214static void FilterAndGetValueForKey(const lldb::SBStructuredData data,
1215 const char *key, llvm::json::Object &out) {
1216 lldb::SBStructuredData value = data.GetValueForKey(key);
1217 std::string key_utf8 = llvm::json::fixUTF8(S: key);
1218 if (llvm::StringRef(key) == "modules")
1219 return;
1220 switch (value.GetType()) {
1221 case lldb::eStructuredDataTypeFloat:
1222 out.try_emplace(K: key_utf8, Args: value.GetFloatValue());
1223 break;
1224 case lldb::eStructuredDataTypeUnsignedInteger:
1225 out.try_emplace(K: key_utf8, Args: value.GetIntegerValue(fail_value: (uint64_t)0));
1226 break;
1227 case lldb::eStructuredDataTypeSignedInteger:
1228 out.try_emplace(K: key_utf8, Args: value.GetIntegerValue(fail_value: (int64_t)0));
1229 break;
1230 case lldb::eStructuredDataTypeArray: {
1231 lldb::SBStream contents;
1232 value.GetAsJSON(stream&: contents);
1233 out.try_emplace(K: key_utf8, Args: llvm::json::fixUTF8(S: contents.GetData()));
1234 } break;
1235 case lldb::eStructuredDataTypeBoolean:
1236 out.try_emplace(K: key_utf8, Args: value.GetBooleanValue());
1237 break;
1238 case lldb::eStructuredDataTypeString: {
1239 // Get the string size before reading
1240 const size_t str_length = value.GetStringValue(dst: nullptr, dst_len: 0);
1241 std::string str(str_length + 1, 0);
1242 value.GetStringValue(dst: &str[0], dst_len: str_length);
1243 out.try_emplace(K: key_utf8, Args: llvm::json::fixUTF8(S: str));
1244 } break;
1245 case lldb::eStructuredDataTypeDictionary: {
1246 lldb::SBStream contents;
1247 value.GetAsJSON(stream&: contents);
1248 out.try_emplace(K: key_utf8, Args: llvm::json::fixUTF8(S: contents.GetData()));
1249 } break;
1250 case lldb::eStructuredDataTypeNull:
1251 case lldb::eStructuredDataTypeGeneric:
1252 case lldb::eStructuredDataTypeInvalid:
1253 break;
1254 }
1255}
1256
1257static void addStatistic(lldb::SBTarget &target, llvm::json::Object &event) {
1258 lldb::SBStructuredData statistics = target.GetStatistics();
1259 bool is_dictionary =
1260 statistics.GetType() == lldb::eStructuredDataTypeDictionary;
1261 if (!is_dictionary)
1262 return;
1263 llvm::json::Object stats_body;
1264
1265 lldb::SBStringList keys;
1266 if (!statistics.GetKeys(keys))
1267 return;
1268 for (size_t i = 0; i < keys.GetSize(); i++) {
1269 const char *key = keys.GetStringAtIndex(idx: i);
1270 FilterAndGetValueForKey(data: statistics, key, out&: stats_body);
1271 }
1272 llvm::json::Object body{{.K: "$__lldb_statistics", .V: std::move(stats_body)}};
1273 event.try_emplace(K: "body", Args: std::move(body));
1274}
1275
1276llvm::json::Object CreateTerminatedEventObject(lldb::SBTarget &target) {
1277 llvm::json::Object event(CreateEventObject(event_name: "terminated"));
1278 addStatistic(target, event);
1279 return event;
1280}
1281
1282std::string JSONToString(const llvm::json::Value &json) {
1283 std::string data;
1284 llvm::raw_string_ostream os(data);
1285 os << json;
1286 return data;
1287}
1288
1289} // namespace lldb_dap
1290

source code of lldb/tools/lldb-dap/JSONUtils.cpp