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