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