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