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