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 auto column = line_entry.GetColumn();
752 if (column && column != LLDB_INVALID_COLUMN_NUMBER)
753 object.try_emplace(K: "column", Args&: column);
754 } else {
755 object.try_emplace(K: "line", Args: 0);
756 object.try_emplace(K: "column", Args: 0);
757 object.try_emplace(K: "presentationHint", Args: "subtle");
758 }
759
760 const auto pc = frame.GetPC();
761 if (pc != LLDB_INVALID_ADDRESS) {
762 std::string formatted_addr = "0x" + llvm::utohexstr(X: pc);
763 object.try_emplace(K: "instructionPointerReference", Args&: formatted_addr);
764 }
765
766 return llvm::json::Value(std::move(object));
767}
768
769// "Thread": {
770// "type": "object",
771// "description": "A Thread",
772// "properties": {
773// "id": {
774// "type": "integer",
775// "description": "Unique identifier for the thread."
776// },
777// "name": {
778// "type": "string",
779// "description": "A name of the thread."
780// }
781// },
782// "required": [ "id", "name" ]
783// }
784llvm::json::Value CreateThread(lldb::SBThread &thread) {
785 llvm::json::Object object;
786 object.try_emplace(K: "id", Args: (int64_t)thread.GetThreadID());
787 std::string thread_str;
788 lldb::SBStream stream;
789 if (g_dap.thread_format &&
790 thread.GetDescriptionWithFormat(format: g_dap.thread_format, output&: stream).Success()) {
791 thread_str = stream.GetData();
792 } else {
793 const char *thread_name = thread.GetName();
794 const char *queue_name = thread.GetQueueName();
795
796 if (thread_name) {
797 thread_str = std::string(thread_name);
798 } else if (queue_name) {
799 auto kind = thread.GetQueue().GetKind();
800 std::string queue_kind_label = "";
801 if (kind == lldb::eQueueKindSerial) {
802 queue_kind_label = " (serial)";
803 } else if (kind == lldb::eQueueKindConcurrent) {
804 queue_kind_label = " (concurrent)";
805 }
806
807 thread_str =
808 llvm::formatv(Fmt: "Thread {0} Queue: {1}{2}", Vals: thread.GetIndexID(),
809 Vals&: queue_name, Vals&: queue_kind_label)
810 .str();
811 } else {
812 thread_str = llvm::formatv(Fmt: "Thread {0}", Vals: thread.GetIndexID()).str();
813 }
814 }
815
816 EmplaceSafeString(obj&: object, key: "name", str: thread_str);
817
818 return llvm::json::Value(std::move(object));
819}
820
821// "StoppedEvent": {
822// "allOf": [ { "$ref": "#/definitions/Event" }, {
823// "type": "object",
824// "description": "Event message for 'stopped' event type. The event
825// indicates that the execution of the debuggee has stopped
826// due to some condition. This can be caused by a break
827// point previously set, a stepping action has completed,
828// by executing a debugger statement etc.",
829// "properties": {
830// "event": {
831// "type": "string",
832// "enum": [ "stopped" ]
833// },
834// "body": {
835// "type": "object",
836// "properties": {
837// "reason": {
838// "type": "string",
839// "description": "The reason for the event. For backward
840// compatibility this string is shown in the UI if
841// the 'description' attribute is missing (but it
842// must not be translated).",
843// "_enum": [ "step", "breakpoint", "exception", "pause", "entry" ]
844// },
845// "description": {
846// "type": "string",
847// "description": "The full reason for the event, e.g. 'Paused
848// on exception'. This string is shown in the UI
849// as is."
850// },
851// "threadId": {
852// "type": "integer",
853// "description": "The thread which was stopped."
854// },
855// "text": {
856// "type": "string",
857// "description": "Additional information. E.g. if reason is
858// 'exception', text contains the exception name.
859// This string is shown in the UI."
860// },
861// "allThreadsStopped": {
862// "type": "boolean",
863// "description": "If allThreadsStopped is true, a debug adapter
864// can announce that all threads have stopped.
865// The client should use this information to
866// enable that all threads can be expanded to
867// access their stacktraces. If the attribute
868// is missing or false, only the thread with the
869// given threadId can be expanded."
870// }
871// },
872// "required": [ "reason" ]
873// }
874// },
875// "required": [ "event", "body" ]
876// }]
877// }
878llvm::json::Value CreateThreadStopped(lldb::SBThread &thread,
879 uint32_t stop_id) {
880 llvm::json::Object event(CreateEventObject(event_name: "stopped"));
881 llvm::json::Object body;
882 switch (thread.GetStopReason()) {
883 case lldb::eStopReasonTrace:
884 case lldb::eStopReasonPlanComplete:
885 body.try_emplace(K: "reason", Args: "step");
886 break;
887 case lldb::eStopReasonBreakpoint: {
888 ExceptionBreakpoint *exc_bp = g_dap.GetExceptionBPFromStopReason(thread);
889 if (exc_bp) {
890 body.try_emplace(K: "reason", Args: "exception");
891 EmplaceSafeString(obj&: body, key: "description", str: exc_bp->label);
892 } else {
893 body.try_emplace(K: "reason", Args: "breakpoint");
894 lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(idx: 0);
895 lldb::break_id_t bp_loc_id = thread.GetStopReasonDataAtIndex(idx: 1);
896 std::string desc_str =
897 llvm::formatv(Fmt: "breakpoint {0}.{1}", Vals&: bp_id, Vals&: bp_loc_id);
898 body.try_emplace(K: "hitBreakpointIds",
899 Args: llvm::json::Array{llvm::json::Value(bp_id)});
900 EmplaceSafeString(obj&: body, key: "description", str: desc_str);
901 }
902 } break;
903 case lldb::eStopReasonWatchpoint:
904 case lldb::eStopReasonInstrumentation:
905 body.try_emplace(K: "reason", Args: "breakpoint");
906 break;
907 case lldb::eStopReasonProcessorTrace:
908 body.try_emplace(K: "reason", Args: "processor trace");
909 break;
910 case lldb::eStopReasonSignal:
911 case lldb::eStopReasonException:
912 body.try_emplace(K: "reason", Args: "exception");
913 break;
914 case lldb::eStopReasonExec:
915 body.try_emplace(K: "reason", Args: "entry");
916 break;
917 case lldb::eStopReasonFork:
918 body.try_emplace(K: "reason", Args: "fork");
919 break;
920 case lldb::eStopReasonVFork:
921 body.try_emplace(K: "reason", Args: "vfork");
922 break;
923 case lldb::eStopReasonVForkDone:
924 body.try_emplace(K: "reason", Args: "vforkdone");
925 break;
926 case lldb::eStopReasonThreadExiting:
927 case lldb::eStopReasonInvalid:
928 case lldb::eStopReasonNone:
929 break;
930 }
931 if (stop_id == 0)
932 body.try_emplace(K: "reason", Args: "entry");
933 const lldb::tid_t tid = thread.GetThreadID();
934 body.try_emplace(K: "threadId", Args: (int64_t)tid);
935 // If no description has been set, then set it to the default thread stopped
936 // description. If we have breakpoints that get hit and shouldn't be reported
937 // as breakpoints, then they will set the description above.
938 if (!ObjectContainsKey(obj: body, key: "description")) {
939 char description[1024];
940 if (thread.GetStopDescription(dst_or_null: description, dst_len: sizeof(description))) {
941 EmplaceSafeString(obj&: body, key: "description", str: std::string(description));
942 }
943 }
944 // "threadCausedFocus" is used in tests to validate breaking behavior.
945 if (tid == g_dap.focus_tid) {
946 body.try_emplace(K: "threadCausedFocus", Args: true);
947 }
948 body.try_emplace(K: "preserveFocusHint", Args: tid != g_dap.focus_tid);
949 body.try_emplace(K: "allThreadsStopped", Args: true);
950 event.try_emplace(K: "body", Args: std::move(body));
951 return llvm::json::Value(std::move(event));
952}
953
954const char *GetNonNullVariableName(lldb::SBValue v) {
955 const char *name = v.GetName();
956 return name ? name : "<null>";
957}
958
959std::string CreateUniqueVariableNameForDisplay(lldb::SBValue v,
960 bool is_name_duplicated) {
961 lldb::SBStream name_builder;
962 name_builder.Print(str: GetNonNullVariableName(v));
963 if (is_name_duplicated) {
964 lldb::SBDeclaration declaration = v.GetDeclaration();
965 const char *file_name = declaration.GetFileSpec().GetFilename();
966 const uint32_t line = declaration.GetLine();
967
968 if (file_name != nullptr && line > 0)
969 name_builder.Printf(format: " @ %s:%u", file_name, line);
970 else if (const char *location = v.GetLocation())
971 name_builder.Printf(format: " @ %s", location);
972 }
973 return name_builder.GetData();
974}
975
976VariableDescription::VariableDescription(lldb::SBValue v, bool format_hex,
977 bool is_name_duplicated,
978 std::optional<std::string> custom_name)
979 : v(v) {
980 name = custom_name
981 ? *custom_name
982 : CreateUniqueVariableNameForDisplay(v, is_name_duplicated);
983
984 type_obj = v.GetType();
985 std::string raw_display_type_name =
986 llvm::StringRef(type_obj.GetDisplayTypeName()).str();
987 display_type_name =
988 !raw_display_type_name.empty() ? raw_display_type_name : NO_TYPENAME;
989
990 if (format_hex)
991 v.SetFormat(lldb::eFormatHex);
992
993 llvm::raw_string_ostream os_display_value(display_value);
994
995 if (lldb::SBError sb_error = v.GetError(); sb_error.Fail()) {
996 error = sb_error.GetCString();
997 os_display_value << "<error: " << error << ">";
998 } else {
999 value = llvm::StringRef(v.GetValue()).str();
1000 summary = llvm::StringRef(v.GetSummary()).str();
1001 if (summary.empty())
1002 auto_summary = TryCreateAutoSummary(value: v);
1003
1004 std::optional<std::string> effective_summary =
1005 !summary.empty() ? summary : auto_summary;
1006
1007 if (!value.empty()) {
1008 os_display_value << value;
1009 if (effective_summary)
1010 os_display_value << " " << *effective_summary;
1011 } else if (effective_summary) {
1012 os_display_value << *effective_summary;
1013
1014 // As last resort, we print its type and address if available.
1015 } else {
1016 if (!raw_display_type_name.empty()) {
1017 os_display_value << raw_display_type_name;
1018 lldb::addr_t address = v.GetLoadAddress();
1019 if (address != LLDB_INVALID_ADDRESS)
1020 os_display_value << " @ " << llvm::format_hex(N: address, Width: 0);
1021 }
1022 }
1023 }
1024
1025 lldb::SBStream evaluateStream;
1026 v.GetExpressionPath(description&: evaluateStream);
1027 evaluate_name = llvm::StringRef(evaluateStream.GetData()).str();
1028}
1029
1030llvm::json::Object VariableDescription::GetVariableExtensionsJSON() {
1031 llvm::json::Object extensions;
1032 if (error)
1033 EmplaceSafeString(obj&: extensions, key: "error", str: *error);
1034 if (!value.empty())
1035 EmplaceSafeString(obj&: extensions, key: "value", str: value);
1036 if (!summary.empty())
1037 EmplaceSafeString(obj&: extensions, key: "summary", str: summary);
1038 if (auto_summary)
1039 EmplaceSafeString(obj&: extensions, key: "autoSummary", str: *auto_summary);
1040
1041 if (lldb::SBDeclaration decl = v.GetDeclaration(); decl.IsValid()) {
1042 llvm::json::Object decl_obj;
1043 if (lldb::SBFileSpec file = decl.GetFileSpec(); file.IsValid()) {
1044 char path[PATH_MAX] = "";
1045 if (file.GetPath(dst_path: path, dst_len: sizeof(path)) &&
1046 lldb::SBFileSpec::ResolvePath(src_path: path, dst_path: path, PATH_MAX)) {
1047 decl_obj.try_emplace(K: "path", Args: std::string(path));
1048 }
1049 }
1050
1051 if (int line = decl.GetLine())
1052 decl_obj.try_emplace(K: "line", Args&: line);
1053 if (int column = decl.GetColumn())
1054 decl_obj.try_emplace(K: "column", Args&: column);
1055
1056 if (!decl_obj.empty())
1057 extensions.try_emplace(K: "declaration", Args: std::move(decl_obj));
1058 }
1059 return extensions;
1060}
1061
1062std::string VariableDescription::GetResult(llvm::StringRef context) {
1063 // In repl and hover context, the results can be displayed as multiple lines
1064 // so more detailed descriptions can be returned.
1065 if (context != "repl" && context != "hover")
1066 return display_value;
1067
1068 if (!v.IsValid())
1069 return display_value;
1070
1071 // Try the SBValue::GetDescription(), which may call into language runtime
1072 // specific formatters (see ValueObjectPrinter).
1073 lldb::SBStream stream;
1074 v.GetDescription(description&: stream);
1075 llvm::StringRef description = stream.GetData();
1076 return description.trim().str();
1077}
1078
1079// "Variable": {
1080// "type": "object",
1081// "description": "A Variable is a name/value pair. Optionally a variable
1082// can have a 'type' that is shown if space permits or when
1083// hovering over the variable's name. An optional 'kind' is
1084// used to render additional properties of the variable,
1085// e.g. different icons can be used to indicate that a
1086// variable is public or private. If the value is
1087// structured (has children), a handle is provided to
1088// retrieve the children with the VariablesRequest. If
1089// the number of named or indexed children is large, the
1090// numbers should be returned via the optional
1091// 'namedVariables' and 'indexedVariables' attributes. The
1092// client can use this optional information to present the
1093// children in a paged UI and fetch them in chunks.",
1094// "properties": {
1095// "name": {
1096// "type": "string",
1097// "description": "The variable's name."
1098// },
1099// "value": {
1100// "type": "string",
1101// "description": "The variable's value. This can be a multi-line text,
1102// e.g. for a function the body of a function."
1103// },
1104// "type": {
1105// "type": "string",
1106// "description": "The type of the variable's value. Typically shown in
1107// the UI when hovering over the value."
1108// },
1109// "presentationHint": {
1110// "$ref": "#/definitions/VariablePresentationHint",
1111// "description": "Properties of a variable that can be used to determine
1112// how to render the variable in the UI."
1113// },
1114// "evaluateName": {
1115// "type": "string",
1116// "description": "Optional evaluatable name of this variable which can
1117// be passed to the 'EvaluateRequest' to fetch the
1118// variable's value."
1119// },
1120// "variablesReference": {
1121// "type": "integer",
1122// "description": "If variablesReference is > 0, the variable is
1123// structured and its children can be retrieved by
1124// passing variablesReference to the VariablesRequest."
1125// },
1126// "namedVariables": {
1127// "type": "integer",
1128// "description": "The number of named child variables. The client can
1129// use this optional information to present the children
1130// in a paged UI and fetch them in chunks."
1131// },
1132// "indexedVariables": {
1133// "type": "integer",
1134// "description": "The number of indexed child variables. The client
1135// can use this optional information to present the
1136// children in a paged UI and fetch them in chunks."
1137// }
1138//
1139//
1140// "$__lldb_extensions": {
1141// "description": "Unofficial extensions to the protocol",
1142// "properties": {
1143// "declaration": {
1144// "type": "object",
1145// "description": "The source location where the variable was declared.
1146// This value won't be present if no declaration is
1147// available.",
1148// "properties": {
1149// "path": {
1150// "type": "string",
1151// "description": "The source file path where the variable was
1152// declared."
1153// },
1154// "line": {
1155// "type": "number",
1156// "description": "The 1-indexed source line where the variable was
1157// declared."
1158// },
1159// "column": {
1160// "type": "number",
1161// "description": "The 1-indexed source column where the variable
1162// was declared."
1163// }
1164// }
1165// },
1166// "value":
1167// "type": "string",
1168// "description": "The internal value of the variable as returned by
1169// This is effectively SBValue.GetValue(). The other
1170// `value` entry in the top-level variable response is,
1171// on the other hand, just a display string for the
1172// variable."
1173// },
1174// "summary":
1175// "type": "string",
1176// "description": "The summary string of the variable. This is
1177// effectively SBValue.GetSummary()."
1178// },
1179// "autoSummary":
1180// "type": "string",
1181// "description": "The auto generated summary if using
1182// `enableAutoVariableSummaries`."
1183// },
1184// "error":
1185// "type": "string",
1186// "description": "An error message generated if LLDB couldn't inspect
1187// the variable."
1188// }
1189// }
1190// },
1191// "required": [ "name", "value", "variablesReference" ]
1192// }
1193llvm::json::Value CreateVariable(lldb::SBValue v, int64_t variablesReference,
1194 int64_t varID, bool format_hex,
1195 bool is_name_duplicated,
1196 std::optional<std::string> custom_name) {
1197 VariableDescription desc(v, format_hex, is_name_duplicated, custom_name);
1198 llvm::json::Object object;
1199 EmplaceSafeString(obj&: object, key: "name", str: desc.name);
1200 EmplaceSafeString(obj&: object, key: "value", str: desc.display_value);
1201
1202 if (!desc.evaluate_name.empty())
1203 EmplaceSafeString(obj&: object, key: "evaluateName", str: desc.evaluate_name);
1204
1205 // If we have a type with many children, we would like to be able to
1206 // give a hint to the IDE that the type has indexed children so that the
1207 // request can be broken up in grabbing only a few children at a time. We
1208 // want to be careful and only call "v.GetNumChildren()" if we have an array
1209 // type or if we have a synthetic child provider. We don't want to call
1210 // "v.GetNumChildren()" on all objects as class, struct and union types
1211 // don't need to be completed if they are never expanded. So we want to
1212 // avoid calling this to only cases where we it makes sense to keep
1213 // performance high during normal debugging.
1214
1215 // If we have an array type, say that it is indexed and provide the number
1216 // of children in case we have a huge array. If we don't do this, then we
1217 // might take a while to produce all children at onces which can delay your
1218 // debug session.
1219 const bool is_array = desc.type_obj.IsArrayType();
1220 const bool is_synthetic = v.IsSynthetic();
1221 if (is_array || is_synthetic) {
1222 const auto num_children = v.GetNumChildren();
1223 // We create a "[raw]" fake child for each synthetic type, so we have to
1224 // account for it when returning indexed variables. We don't need to do
1225 // this for non-indexed ones.
1226 bool has_raw_child = is_synthetic && g_dap.enable_synthetic_child_debugging;
1227 int actual_num_children = num_children + (has_raw_child ? 1 : 0);
1228 if (is_array) {
1229 object.try_emplace(K: "indexedVariables", Args&: actual_num_children);
1230 } else if (num_children > 0) {
1231 // If a type has a synthetic child provider, then the SBType of "v"
1232 // won't tell us anything about what might be displayed. So we can check
1233 // if the first child's name is "[0]" and then we can say it is indexed.
1234 const char *first_child_name = v.GetChildAtIndex(idx: 0).GetName();
1235 if (first_child_name && strcmp(s1: first_child_name, s2: "[0]") == 0)
1236 object.try_emplace(K: "indexedVariables", Args&: actual_num_children);
1237 }
1238 }
1239 EmplaceSafeString(obj&: object, key: "type", str: desc.display_type_name);
1240 if (varID != INT64_MAX)
1241 object.try_emplace(K: "id", Args&: varID);
1242 if (v.MightHaveChildren())
1243 object.try_emplace(K: "variablesReference", Args&: variablesReference);
1244 else
1245 object.try_emplace(K: "variablesReference", Args: (int64_t)0);
1246
1247 object.try_emplace(K: "$__lldb_extensions", Args: desc.GetVariableExtensionsJSON());
1248 return llvm::json::Value(std::move(object));
1249}
1250
1251llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit unit) {
1252 llvm::json::Object object;
1253 char unit_path_arr[PATH_MAX];
1254 unit.GetFileSpec().GetPath(dst_path: unit_path_arr, dst_len: sizeof(unit_path_arr));
1255 std::string unit_path(unit_path_arr);
1256 object.try_emplace(K: "compileUnitPath", Args&: unit_path);
1257 return llvm::json::Value(std::move(object));
1258}
1259
1260/// See
1261/// https://microsoft.github.io/debug-adapter-protocol/specification#Reverse_Requests_RunInTerminal
1262llvm::json::Object
1263CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request,
1264 llvm::StringRef debug_adaptor_path,
1265 llvm::StringRef comm_file,
1266 lldb::pid_t debugger_pid) {
1267 llvm::json::Object run_in_terminal_args;
1268 // This indicates the IDE to open an embedded terminal, instead of opening the
1269 // terminal in a new window.
1270 run_in_terminal_args.try_emplace(K: "kind", Args: "integrated");
1271
1272 auto launch_request_arguments = launch_request.getObject(K: "arguments");
1273 // The program path must be the first entry in the "args" field
1274 std::vector<std::string> args = {debug_adaptor_path.str(), "--comm-file",
1275 comm_file.str()};
1276 if (debugger_pid != LLDB_INVALID_PROCESS_ID) {
1277 args.push_back(x: "--debugger-pid");
1278 args.push_back(x: std::to_string(val: debugger_pid));
1279 }
1280 args.push_back(x: "--launch-target");
1281 args.push_back(x: GetString(obj: launch_request_arguments, key: "program").str());
1282 std::vector<std::string> target_args =
1283 GetStrings(obj: launch_request_arguments, key: "args");
1284 args.insert(position: args.end(), first: target_args.begin(), last: target_args.end());
1285 run_in_terminal_args.try_emplace(K: "args", Args&: args);
1286
1287 const auto cwd = GetString(obj: launch_request_arguments, key: "cwd");
1288 if (!cwd.empty())
1289 run_in_terminal_args.try_emplace(K: "cwd", Args: cwd);
1290
1291 // We need to convert the input list of environments variables into a
1292 // dictionary
1293 std::vector<std::string> envs = GetStrings(obj: launch_request_arguments, key: "env");
1294 llvm::json::Object environment;
1295 for (const std::string &env : envs) {
1296 size_t index = env.find(c: '=');
1297 environment.try_emplace(K: env.substr(pos: 0, n: index), Args: env.substr(pos: index + 1));
1298 }
1299 run_in_terminal_args.try_emplace(K: "env",
1300 Args: llvm::json::Value(std::move(environment)));
1301
1302 return run_in_terminal_args;
1303}
1304
1305// Keep all the top level items from the statistics dump, except for the
1306// "modules" array. It can be huge and cause delay
1307// Array and dictionary value will return as <key, JSON string> pairs
1308void FilterAndGetValueForKey(const lldb::SBStructuredData data, const char *key,
1309 llvm::json::Object &out) {
1310 lldb::SBStructuredData value = data.GetValueForKey(key);
1311 std::string key_utf8 = llvm::json::fixUTF8(S: key);
1312 if (strcmp(s1: key, s2: "modules") == 0)
1313 return;
1314 switch (value.GetType()) {
1315 case lldb::eStructuredDataTypeFloat:
1316 out.try_emplace(K: key_utf8, Args: value.GetFloatValue());
1317 break;
1318 case lldb::eStructuredDataTypeUnsignedInteger:
1319 out.try_emplace(K: key_utf8, Args: value.GetIntegerValue(fail_value: (uint64_t)0));
1320 break;
1321 case lldb::eStructuredDataTypeSignedInteger:
1322 out.try_emplace(K: key_utf8, Args: value.GetIntegerValue(fail_value: (int64_t)0));
1323 break;
1324 case lldb::eStructuredDataTypeArray: {
1325 lldb::SBStream contents;
1326 value.GetAsJSON(stream&: contents);
1327 out.try_emplace(K: key_utf8, Args: llvm::json::fixUTF8(S: contents.GetData()));
1328 } break;
1329 case lldb::eStructuredDataTypeBoolean:
1330 out.try_emplace(K: key_utf8, Args: value.GetBooleanValue());
1331 break;
1332 case lldb::eStructuredDataTypeString: {
1333 // Get the string size before reading
1334 const size_t str_length = value.GetStringValue(dst: nullptr, dst_len: 0);
1335 std::string str(str_length + 1, 0);
1336 value.GetStringValue(dst: &str[0], dst_len: str_length);
1337 out.try_emplace(K: key_utf8, Args: llvm::json::fixUTF8(S: str));
1338 } break;
1339 case lldb::eStructuredDataTypeDictionary: {
1340 lldb::SBStream contents;
1341 value.GetAsJSON(stream&: contents);
1342 out.try_emplace(K: key_utf8, Args: llvm::json::fixUTF8(S: contents.GetData()));
1343 } break;
1344 case lldb::eStructuredDataTypeNull:
1345 case lldb::eStructuredDataTypeGeneric:
1346 case lldb::eStructuredDataTypeInvalid:
1347 break;
1348 }
1349}
1350
1351void addStatistic(llvm::json::Object &event) {
1352 lldb::SBStructuredData statistics = g_dap.target.GetStatistics();
1353 bool is_dictionary =
1354 statistics.GetType() == lldb::eStructuredDataTypeDictionary;
1355 if (!is_dictionary)
1356 return;
1357 llvm::json::Object stats_body;
1358
1359 lldb::SBStringList keys;
1360 if (!statistics.GetKeys(keys))
1361 return;
1362 for (size_t i = 0; i < keys.GetSize(); i++) {
1363 const char *key = keys.GetStringAtIndex(idx: i);
1364 FilterAndGetValueForKey(data: statistics, key, out&: stats_body);
1365 }
1366 event.try_emplace(K: "statistics", Args: std::move(stats_body));
1367}
1368
1369llvm::json::Object CreateTerminatedEventObject() {
1370 llvm::json::Object event(CreateEventObject(event_name: "terminated"));
1371 addStatistic(event);
1372 return event;
1373}
1374
1375std::string JSONToString(const llvm::json::Value &json) {
1376 std::string data;
1377 llvm::raw_string_ostream os(data);
1378 os << json;
1379 os.flush();
1380 return data;
1381}
1382
1383} // namespace lldb_dap
1384

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