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

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