1007 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8 9#include "JSONUtils.h"10#include "DAP.h"11#include "ExceptionBreakpoint.h"12#include "LLDBUtils.h"13#include "Protocol/ProtocolBase.h"14#include "Protocol/ProtocolRequests.h"15#include "ProtocolUtils.h"16#include "lldb/API/SBAddress.h"17#include "lldb/API/SBCompileUnit.h"18#include "lldb/API/SBDeclaration.h"19#include "lldb/API/SBEnvironment.h"20#include "lldb/API/SBError.h"21#include "lldb/API/SBFileSpec.h"22#include "lldb/API/SBFrame.h"23#include "lldb/API/SBFunction.h"24#include "lldb/API/SBInstructionList.h"25#include "lldb/API/SBLineEntry.h"26#include "lldb/API/SBModule.h"27#include "lldb/API/SBQueue.h"28#include "lldb/API/SBSection.h"29#include "lldb/API/SBStream.h"30#include "lldb/API/SBStringList.h"31#include "lldb/API/SBStructuredData.h"32#include "lldb/API/SBTarget.h"33#include "lldb/API/SBThread.h"34#include "lldb/API/SBType.h"35#include "lldb/API/SBValue.h"36#include "lldb/Host/PosixApi.h" // IWYU pragma: keep37#include "lldb/lldb-defines.h"38#include "lldb/lldb-enumerations.h"39#include "lldb/lldb-types.h"40#include "llvm/ADT/DenseMap.h"41#include "llvm/ADT/StringExtras.h"42#include "llvm/ADT/StringRef.h"43#include "llvm/Support/Compiler.h"44#include "llvm/Support/Format.h"45#include "llvm/Support/FormatVariadic.h"46#include "llvm/Support/JSON.h"47#include "llvm/Support/Path.h"48#include "llvm/Support/ScopedPrinter.h"49#include "llvm/Support/raw_ostream.h"50#include <chrono>51#include <cstddef>52#include <iomanip>53#include <optional>54#include <sstream>55#include <string>56#include <utility>57#include <vector>58 59namespace lldb_dap {60 61void EmplaceSafeString(llvm::json::Object &obj, llvm::StringRef key,62 llvm::StringRef str) {63 if (LLVM_LIKELY(llvm::json::isUTF8(str)))64 obj.try_emplace(key, str.str());65 else66 obj.try_emplace(key, llvm::json::fixUTF8(str));67}68 69llvm::StringRef GetAsString(const llvm::json::Value &value) {70 if (auto s = value.getAsString())71 return *s;72 return llvm::StringRef();73}74 75// Gets a string from a JSON object using the key, or returns an empty string.76std::optional<llvm::StringRef> GetString(const llvm::json::Object &obj,77 llvm::StringRef key) {78 return obj.getString(key);79}80 81std::optional<llvm::StringRef> GetString(const llvm::json::Object *obj,82 llvm::StringRef key) {83 if (obj == nullptr)84 return std::nullopt;85 86 return GetString(*obj, key);87}88 89std::optional<bool> GetBoolean(const llvm::json::Object &obj,90 llvm::StringRef key) {91 if (auto value = obj.getBoolean(key))92 return *value;93 if (auto value = obj.getInteger(key))94 return *value != 0;95 return std::nullopt;96}97 98std::optional<bool> GetBoolean(const llvm::json::Object *obj,99 llvm::StringRef key) {100 if (obj != nullptr)101 return GetBoolean(*obj, key);102 return std::nullopt;103}104 105bool ObjectContainsKey(const llvm::json::Object &obj, llvm::StringRef key) {106 return obj.find(key) != obj.end();107}108 109std::string EncodeMemoryReference(lldb::addr_t addr) {110 return "0x" + llvm::utohexstr(addr);111}112 113std::optional<lldb::addr_t>114DecodeMemoryReference(llvm::StringRef memoryReference) {115 if (!memoryReference.starts_with("0x"))116 return std::nullopt;117 118 lldb::addr_t addr;119 if (memoryReference.consumeInteger(0, addr))120 return std::nullopt;121 122 return addr;123}124 125bool DecodeMemoryReference(const llvm::json::Value &v, llvm::StringLiteral key,126 lldb::addr_t &out, llvm::json::Path path,127 bool required) {128 const llvm::json::Object *v_obj = v.getAsObject();129 if (!v_obj) {130 path.report("expected object");131 return false;132 }133 134 const llvm::json::Value *mem_ref_value = v_obj->get(key);135 if (!mem_ref_value) {136 if (!required)137 return true;138 139 path.field(key).report("missing value");140 return false;141 }142 143 const std::optional<llvm::StringRef> mem_ref_str =144 mem_ref_value->getAsString();145 if (!mem_ref_str) {146 path.field(key).report("expected string");147 return false;148 }149 150 const std::optional<lldb::addr_t> addr_opt =151 DecodeMemoryReference(*mem_ref_str);152 if (!addr_opt) {153 path.field(key).report("malformed memory reference");154 return false;155 }156 157 out = *addr_opt;158 return true;159}160 161std::vector<std::string> GetStrings(const llvm::json::Object *obj,162 llvm::StringRef key) {163 std::vector<std::string> strs;164 const auto *json_array = obj->getArray(key);165 if (!json_array)166 return strs;167 for (const auto &value : *json_array) {168 switch (value.kind()) {169 case llvm::json::Value::String:170 strs.push_back(value.getAsString()->str());171 break;172 case llvm::json::Value::Number:173 case llvm::json::Value::Boolean:174 strs.push_back(llvm::to_string(value));175 break;176 case llvm::json::Value::Null:177 case llvm::json::Value::Object:178 case llvm::json::Value::Array:179 break;180 }181 }182 return strs;183}184 185std::unordered_map<std::string, std::string>186GetStringMap(const llvm::json::Object &obj, llvm::StringRef key) {187 std::unordered_map<std::string, std::string> strs;188 const auto *const json_object = obj.getObject(key);189 if (!json_object)190 return strs;191 192 for (const auto &[key, value] : *json_object) {193 switch (value.kind()) {194 case llvm::json::Value::String:195 strs.emplace(key.str(), value.getAsString()->str());196 break;197 case llvm::json::Value::Number:198 case llvm::json::Value::Boolean:199 strs.emplace(key.str(), llvm::to_string(value));200 break;201 case llvm::json::Value::Null:202 case llvm::json::Value::Object:203 case llvm::json::Value::Array:204 break;205 }206 }207 return strs;208}209 210static bool IsClassStructOrUnionType(lldb::SBType t) {211 return (t.GetTypeClass() & (lldb::eTypeClassUnion | lldb::eTypeClassStruct |212 lldb::eTypeClassArray)) != 0;213}214 215/// Create a short summary for a container that contains the summary of its216/// first children, so that the user can get a glimpse of its contents at a217/// glance.218static std::optional<std::string>219TryCreateAutoSummaryForContainer(lldb::SBValue &v) {220 if (!v.MightHaveChildren())221 return std::nullopt;222 /// As this operation can be potentially slow, we limit the total time spent223 /// fetching children to a few ms.224 const auto max_evaluation_time = std::chrono::milliseconds(10);225 /// We don't want to generate a extremely long summary string, so we limit its226 /// length.227 const size_t max_length = 32;228 229 auto start = std::chrono::steady_clock::now();230 std::string summary;231 llvm::raw_string_ostream os(summary);232 os << "{";233 234 llvm::StringRef separator = "";235 236 for (size_t i = 0, e = v.GetNumChildren(); i < e; ++i) {237 // If we reached the time limit or exceeded the number of characters, we238 // dump `...` to signal that there are more elements in the collection.239 if (summary.size() > max_length ||240 (std::chrono::steady_clock::now() - start) > max_evaluation_time) {241 os << separator << "...";242 break;243 }244 lldb::SBValue child = v.GetChildAtIndex(i);245 246 if (llvm::StringRef name = child.GetName(); !name.empty()) {247 llvm::StringRef desc;248 if (llvm::StringRef summary = child.GetSummary(); !summary.empty())249 desc = summary;250 else if (llvm::StringRef value = child.GetValue(); !value.empty())251 desc = value;252 else if (IsClassStructOrUnionType(child.GetType()))253 desc = "{...}";254 else255 continue;256 257 // If the child is an indexed entry, we don't show its index to save258 // characters.259 if (name.starts_with("["))260 os << separator << desc;261 else262 os << separator << name << ":" << desc;263 separator = ", ";264 }265 }266 os << "}";267 268 if (summary == "{...}" || summary == "{}")269 return std::nullopt;270 return summary;271}272 273/// Try to create a summary string for the given value that doesn't have a274/// summary of its own.275static std::optional<std::string> TryCreateAutoSummary(lldb::SBValue &value) {276 // We use the dereferenced value for generating the summary.277 if (value.GetType().IsPointerType() || value.GetType().IsReferenceType())278 value = value.Dereference();279 280 // We only support auto summaries for containers.281 return TryCreateAutoSummaryForContainer(value);282}283 284void FillResponse(const llvm::json::Object &request,285 llvm::json::Object &response) {286 // Fill in all of the needed response fields to a "request" and set "success"287 // to true by default.288 response.try_emplace("type", "response");289 response.try_emplace("seq", protocol::kCalculateSeq);290 EmplaceSafeString(response, "command",291 GetString(request, "command").value_or(""));292 const uint64_t seq = GetInteger<uint64_t>(request, "seq").value_or(0);293 response.try_emplace("request_seq", seq);294 response.try_emplace("success", true);295}296 297// "Scope": {298// "type": "object",299// "description": "A Scope is a named container for variables. Optionally300// a scope can map to a source or a range within a source.",301// "properties": {302// "name": {303// "type": "string",304// "description": "Name of the scope such as 'Arguments', 'Locals'."305// },306// "presentationHint": {307// "type": "string",308// "description": "An optional hint for how to present this scope in the309// UI. If this attribute is missing, the scope is shown310// with a generic UI.",311// "_enum": [ "arguments", "locals", "registers" ],312// },313// "variablesReference": {314// "type": "integer",315// "description": "The variables of this scope can be retrieved by316// passing the value of variablesReference to the317// VariablesRequest."318// },319// "namedVariables": {320// "type": "integer",321// "description": "The number of named variables in this scope. The322// client can use this optional information to present323// the variables in a paged UI and fetch them in chunks."324// },325// "indexedVariables": {326// "type": "integer",327// "description": "The number of indexed variables in this scope. The328// client can use this optional information to present329// the variables in a paged UI and fetch them in chunks."330// },331// "expensive": {332// "type": "boolean",333// "description": "If true, the number of variables in this scope is334// large or expensive to retrieve."335// },336// "source": {337// "$ref": "#/definitions/Source",338// "description": "Optional source for this scope."339// },340// "line": {341// "type": "integer",342// "description": "Optional start line of the range covered by this343// scope."344// },345// "column": {346// "type": "integer",347// "description": "Optional start column of the range covered by this348// scope."349// },350// "endLine": {351// "type": "integer",352// "description": "Optional end line of the range covered by this scope."353// },354// "endColumn": {355// "type": "integer",356// "description": "Optional end column of the range covered by this357// scope."358// }359// },360// "required": [ "name", "variablesReference", "expensive" ]361// }362llvm::json::Value CreateScope(const llvm::StringRef name,363 int64_t variablesReference,364 int64_t namedVariables, bool expensive) {365 llvm::json::Object object;366 EmplaceSafeString(object, "name", name.str());367 368 // TODO: Support "arguments" scope. At the moment lldb-dap includes the369 // arguments into the "locals" scope.370 if (variablesReference == VARREF_LOCALS) {371 object.try_emplace("presentationHint", "locals");372 } else if (variablesReference == VARREF_REGS) {373 object.try_emplace("presentationHint", "registers");374 }375 376 object.try_emplace("variablesReference", variablesReference);377 object.try_emplace("expensive", expensive);378 object.try_emplace("namedVariables", namedVariables);379 return llvm::json::Value(std::move(object));380}381 382// "Event": {383// "allOf": [ { "$ref": "#/definitions/ProtocolMessage" }, {384// "type": "object",385// "description": "Server-initiated event.",386// "properties": {387// "type": {388// "type": "string",389// "enum": [ "event" ]390// },391// "event": {392// "type": "string",393// "description": "Type of event."394// },395// "body": {396// "type": [ "array", "boolean", "integer", "null", "number" ,397// "object", "string" ],398// "description": "Event-specific information."399// }400// },401// "required": [ "type", "event" ]402// }]403// },404// "ProtocolMessage": {405// "type": "object",406// "description": "Base class of requests, responses, and events.",407// "properties": {408// "seq": {409// "type": "integer",410// "description": "Sequence number."411// },412// "type": {413// "type": "string",414// "description": "Message type.",415// "_enum": [ "request", "response", "event" ]416// }417// },418// "required": [ "seq", "type" ]419// }420llvm::json::Object CreateEventObject(const llvm::StringRef event_name) {421 llvm::json::Object event;422 event.try_emplace("seq", protocol::kCalculateSeq);423 event.try_emplace("type", "event");424 EmplaceSafeString(event, "event", event_name);425 return event;426}427 428// "StackFrame": {429// "type": "object",430// "description": "A Stackframe contains the source location.",431// "properties": {432// "id": {433// "type": "integer",434// "description": "An identifier for the stack frame. It must be unique435// across all threads. This id can be used to retrieve436// the scopes of the frame with the 'scopesRequest' or437// to restart the execution of a stackframe."438// },439// "name": {440// "type": "string",441// "description": "The name of the stack frame, typically a method name."442// },443// "source": {444// "$ref": "#/definitions/Source",445// "description": "The optional source of the frame."446// },447// "line": {448// "type": "integer",449// "description": "The line within the file of the frame. If source is450// null or doesn't exist, line is 0 and must be ignored."451// },452// "column": {453// "type": "integer",454// "description": "The column within the line. If source is null or455// doesn't exist, column is 0 and must be ignored."456// },457// "endLine": {458// "type": "integer",459// "description": "An optional end line of the range covered by the460// stack frame."461// },462// "endColumn": {463// "type": "integer",464// "description": "An optional end column of the range covered by the465// stack frame."466// },467// "instructionPointerReference": {468// "type": "string",469// "description": "A memory reference for the current instruction470// pointer in this frame."471// },472// "moduleId": {473// "type": ["integer", "string"],474// "description": "The module associated with this frame, if any."475// },476// "presentationHint": {477// "type": "string",478// "enum": [ "normal", "label", "subtle" ],479// "description": "An optional hint for how to present this frame in480// the UI. A value of 'label' can be used to indicate481// that the frame is an artificial frame that is used482// as a visual label or separator. A value of 'subtle'483// can be used to change the appearance of a frame in484// a 'subtle' way."485// }486// },487// "required": [ "id", "name", "line", "column" ]488// }489llvm::json::Value CreateStackFrame(DAP &dap, lldb::SBFrame &frame,490 lldb::SBFormat &format) {491 llvm::json::Object object;492 int64_t frame_id = MakeDAPFrameID(frame);493 object.try_emplace("id", frame_id);494 495 std::string frame_name;496 lldb::SBStream stream;497 if (format && frame.GetDescriptionWithFormat(format, stream).Success()) {498 frame_name = stream.GetData();499 500 // `function_name` can be a nullptr, which throws an error when assigned to501 // an `std::string`.502 } else if (const char *name = frame.GetDisplayFunctionName()) {503 frame_name = name;504 }505 506 if (frame_name.empty()) {507 // If the function name is unavailable, display the pc address as a 16-digit508 // hex string, e.g. "0x0000000000012345"509 frame_name = GetLoadAddressString(frame.GetPC());510 }511 512 // We only include `[opt]` if a custom frame format is not specified.513 if (!format && frame.GetFunction().GetIsOptimized())514 frame_name += " [opt]";515 516 EmplaceSafeString(object, "name", frame_name);517 518 std::optional<protocol::Source> source = dap.ResolveSource(frame);519 520 if (source && !IsAssemblySource(*source)) {521 // This is a normal source with a valid line entry.522 auto line_entry = frame.GetLineEntry();523 object.try_emplace("line", line_entry.GetLine());524 auto column = line_entry.GetColumn();525 object.try_emplace("column", column);526 } else if (frame.GetSymbol().IsValid()) {527 // This is a source where the disassembly is used, but there is a valid528 // symbol. Calculate the line of the current PC from the start of the529 // current symbol.530 lldb::SBInstructionList inst_list = dap.target.ReadInstructions(531 frame.GetSymbol().GetStartAddress(), frame.GetPCAddress(), nullptr);532 size_t inst_line = inst_list.GetSize();533 534 // Line numbers are 1-based.535 object.try_emplace("line", inst_line + 1);536 object.try_emplace("column", 1);537 } else {538 // No valid line entry or symbol.539 object.try_emplace("line", 1);540 object.try_emplace("column", 1);541 }542 543 if (source)544 object.try_emplace("source", std::move(source).value());545 546 const auto pc = frame.GetPC();547 if (pc != LLDB_INVALID_ADDRESS) {548 std::string formatted_addr = "0x" + llvm::utohexstr(pc);549 object.try_emplace("instructionPointerReference", formatted_addr);550 }551 552 if (frame.IsArtificial() || frame.IsHidden())553 object.try_emplace("presentationHint", "subtle");554 555 lldb::SBModule module = frame.GetModule();556 if (module.IsValid()) {557 if (const llvm::StringRef uuid = module.GetUUIDString(); !uuid.empty())558 object.try_emplace("moduleId", uuid.str());559 }560 561 return llvm::json::Value(std::move(object));562}563 564llvm::json::Value CreateExtendedStackFrameLabel(lldb::SBThread &thread,565 lldb::SBFormat &format) {566 std::string name;567 lldb::SBStream stream;568 if (format && thread.GetDescriptionWithFormat(format, stream).Success()) {569 name = stream.GetData();570 } else {571 const uint32_t thread_idx = thread.GetExtendedBacktraceOriginatingIndexID();572 const char *queue_name = thread.GetQueueName();573 if (queue_name != nullptr) {574 name = llvm::formatv("Enqueued from {0} (Thread {1})", queue_name,575 thread_idx);576 } else {577 name = llvm::formatv("Thread {0}", thread_idx);578 }579 }580 581 return llvm::json::Value(llvm::json::Object{{"id", thread.GetThreadID() + 1},582 {"name", name},583 {"presentationHint", "label"}});584}585 586// "StoppedEvent": {587// "allOf": [ { "$ref": "#/definitions/Event" }, {588// "type": "object",589// "description": "Event message for 'stopped' event type. The event590// indicates that the execution of the debuggee has stopped591// due to some condition. This can be caused by a break592// point previously set, a stepping action has completed,593// by executing a debugger statement etc.",594// "properties": {595// "event": {596// "type": "string",597// "enum": [ "stopped" ]598// },599// "body": {600// "type": "object",601// "properties": {602// "reason": {603// "type": "string",604// "description": "The reason for the event. For backward605// compatibility this string is shown in the UI if606// the 'description' attribute is missing (but it607// must not be translated).",608// "_enum": [ "step", "breakpoint", "exception", "pause", "entry" ]609// },610// "description": {611// "type": "string",612// "description": "The full reason for the event, e.g. 'Paused613// on exception'. This string is shown in the UI614// as is."615// },616// "threadId": {617// "type": "integer",618// "description": "The thread which was stopped."619// },620// "text": {621// "type": "string",622// "description": "Additional information. E.g. if reason is623// 'exception', text contains the exception name.624// This string is shown in the UI."625// },626// "allThreadsStopped": {627// "type": "boolean",628// "description": "If allThreadsStopped is true, a debug adapter629// can announce that all threads have stopped.630// The client should use this information to631// enable that all threads can be expanded to632// access their stacktraces. If the attribute633// is missing or false, only the thread with the634// given threadId can be expanded."635// }636// },637// "required": [ "reason" ]638// }639// },640// "required": [ "event", "body" ]641// }]642// }643llvm::json::Value CreateThreadStopped(DAP &dap, lldb::SBThread &thread,644 uint32_t stop_id) {645 llvm::json::Object event(CreateEventObject("stopped"));646 llvm::json::Object body;647 switch (thread.GetStopReason()) {648 case lldb::eStopReasonTrace:649 case lldb::eStopReasonPlanComplete:650 body.try_emplace("reason", "step");651 break;652 case lldb::eStopReasonBreakpoint: {653 ExceptionBreakpoint *exc_bp = dap.GetExceptionBPFromStopReason(thread);654 if (exc_bp) {655 body.try_emplace("reason", "exception");656 EmplaceSafeString(body, "description", exc_bp->GetLabel());657 } else {658 InstructionBreakpoint *inst_bp =659 dap.GetInstructionBPFromStopReason(thread);660 if (inst_bp) {661 body.try_emplace("reason", "instruction breakpoint");662 } else {663 body.try_emplace("reason", "breakpoint");664 }665 std::vector<lldb::break_id_t> bp_ids;666 std::ostringstream desc_sstream;667 desc_sstream << "breakpoint";668 for (size_t idx = 0; idx < thread.GetStopReasonDataCount(); idx += 2) {669 lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(idx);670 lldb::break_id_t bp_loc_id = thread.GetStopReasonDataAtIndex(idx + 1);671 bp_ids.push_back(bp_id);672 desc_sstream << " " << bp_id << "." << bp_loc_id;673 }674 std::string desc_str = desc_sstream.str();675 body.try_emplace("hitBreakpointIds", llvm::json::Array(bp_ids));676 EmplaceSafeString(body, "description", desc_str);677 }678 } break;679 case lldb::eStopReasonWatchpoint: {680 body.try_emplace("reason", "data breakpoint");681 lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(0);682 body.try_emplace("hitBreakpointIds",683 llvm::json::Array{llvm::json::Value(bp_id)});684 EmplaceSafeString(body, "description",685 llvm::formatv("data breakpoint {0}", bp_id).str());686 } break;687 case lldb::eStopReasonInstrumentation:688 body.try_emplace("reason", "breakpoint");689 break;690 case lldb::eStopReasonProcessorTrace:691 body.try_emplace("reason", "processor trace");692 break;693 case lldb::eStopReasonHistoryBoundary:694 body.try_emplace("reason", "history boundary");695 break;696 case lldb::eStopReasonSignal:697 case lldb::eStopReasonException:698 body.try_emplace("reason", "exception");699 break;700 case lldb::eStopReasonExec:701 body.try_emplace("reason", "entry");702 break;703 case lldb::eStopReasonFork:704 body.try_emplace("reason", "fork");705 break;706 case lldb::eStopReasonVFork:707 body.try_emplace("reason", "vfork");708 break;709 case lldb::eStopReasonVForkDone:710 body.try_emplace("reason", "vforkdone");711 break;712 case lldb::eStopReasonInterrupt:713 body.try_emplace("reason", "async interrupt");714 break;715 case lldb::eStopReasonThreadExiting:716 case lldb::eStopReasonInvalid:717 case lldb::eStopReasonNone:718 break;719 }720 if (stop_id == 0)721 body["reason"] = "entry";722 const lldb::tid_t tid = thread.GetThreadID();723 body.try_emplace("threadId", (int64_t)tid);724 // If no description has been set, then set it to the default thread stopped725 // description. If we have breakpoints that get hit and shouldn't be reported726 // as breakpoints, then they will set the description above.727 if (!ObjectContainsKey(body, "description")) {728 char description[1024];729 if (thread.GetStopDescription(description, sizeof(description))) {730 EmplaceSafeString(body, "description", description);731 }732 }733 // "threadCausedFocus" is used in tests to validate breaking behavior.734 if (tid == dap.focus_tid) {735 body.try_emplace("threadCausedFocus", true);736 }737 body.try_emplace("preserveFocusHint", tid != dap.focus_tid);738 body.try_emplace("allThreadsStopped", true);739 event.try_emplace("body", std::move(body));740 return llvm::json::Value(std::move(event));741}742 743const char *GetNonNullVariableName(lldb::SBValue &v) {744 const char *name = v.GetName();745 return name ? name : "<null>";746}747 748std::string CreateUniqueVariableNameForDisplay(lldb::SBValue &v,749 bool is_name_duplicated) {750 lldb::SBStream name_builder;751 name_builder.Print(GetNonNullVariableName(v));752 if (is_name_duplicated) {753 lldb::SBDeclaration declaration = v.GetDeclaration();754 const char *file_name = declaration.GetFileSpec().GetFilename();755 const uint32_t line = declaration.GetLine();756 757 if (file_name != nullptr && line > 0)758 name_builder.Printf(" @ %s:%u", file_name, line);759 else if (const char *location = v.GetLocation())760 name_builder.Printf(" @ %s", location);761 }762 return name_builder.GetData();763}764 765VariableDescription::VariableDescription(lldb::SBValue v,766 bool auto_variable_summaries,767 bool format_hex,768 bool is_name_duplicated,769 std::optional<std::string> custom_name)770 : v(v) {771 name = custom_name772 ? *custom_name773 : CreateUniqueVariableNameForDisplay(v, is_name_duplicated);774 775 type_obj = v.GetType();776 std::string raw_display_type_name =777 llvm::StringRef(type_obj.GetDisplayTypeName()).str();778 display_type_name =779 !raw_display_type_name.empty() ? raw_display_type_name : NO_TYPENAME;780 781 // Only format hex/default if there is no existing special format.782 if (v.GetFormat() == lldb::eFormatDefault ||783 v.GetFormat() == lldb::eFormatHex) {784 if (format_hex)785 v.SetFormat(lldb::eFormatHex);786 else787 v.SetFormat(lldb::eFormatDefault);788 }789 790 llvm::raw_string_ostream os_display_value(display_value);791 792 if (lldb::SBError sb_error = v.GetError(); sb_error.Fail()) {793 error = sb_error.GetCString();794 os_display_value << "<error: " << error << ">";795 } else {796 value = llvm::StringRef(v.GetValue()).str();797 summary = llvm::StringRef(v.GetSummary()).str();798 if (summary.empty() && auto_variable_summaries)799 auto_summary = TryCreateAutoSummary(v);800 801 std::optional<std::string> effective_summary =802 !summary.empty() ? summary : auto_summary;803 804 if (!value.empty()) {805 os_display_value << value;806 if (effective_summary)807 os_display_value << " " << *effective_summary;808 } else if (effective_summary) {809 os_display_value << *effective_summary;810 811 // As last resort, we print its type and address if available.812 } else {813 if (!raw_display_type_name.empty()) {814 os_display_value << raw_display_type_name;815 lldb::addr_t address = v.GetLoadAddress();816 if (address != LLDB_INVALID_ADDRESS)817 os_display_value << " @ " << llvm::format_hex(address, 0);818 }819 }820 }821 822 lldb::SBStream evaluateStream;823 v.GetExpressionPath(evaluateStream);824 evaluate_name = llvm::StringRef(evaluateStream.GetData()).str();825}826 827std::string VariableDescription::GetResult(protocol::EvaluateContext context) {828 // In repl context, the results can be displayed as multiple lines so more829 // detailed descriptions can be returned.830 if (context != protocol::eEvaluateContextRepl)831 return display_value;832 833 if (!v.IsValid())834 return display_value;835 836 // Try the SBValue::GetDescription(), which may call into language runtime837 // specific formatters (see ValueObjectPrinter).838 lldb::SBStream stream;839 v.GetDescription(stream);840 llvm::StringRef description = stream.GetData();841 return description.trim().str();842}843 844bool ValuePointsToCode(lldb::SBValue v) {845 if (!v.GetType().GetPointeeType().IsFunctionType())846 return false;847 848 lldb::addr_t addr = v.GetValueAsAddress();849 lldb::SBLineEntry line_entry =850 v.GetTarget().ResolveLoadAddress(addr).GetLineEntry();851 852 return line_entry.IsValid();853}854 855int64_t PackLocation(int64_t var_ref, bool is_value_location) {856 return var_ref << 1 | is_value_location;857}858 859std::pair<int64_t, bool> UnpackLocation(int64_t location_id) {860 return std::pair{location_id >> 1, location_id & 1};861}862 863llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit &unit) {864 llvm::json::Object object;865 char unit_path_arr[PATH_MAX];866 unit.GetFileSpec().GetPath(unit_path_arr, sizeof(unit_path_arr));867 std::string unit_path(unit_path_arr);868 object.try_emplace("compileUnitPath", unit_path);869 return llvm::json::Value(std::move(object));870}871 872/// See873/// https://microsoft.github.io/debug-adapter-protocol/specification#Reverse_Requests_RunInTerminal874llvm::json::Object CreateRunInTerminalReverseRequest(875 llvm::StringRef program, const std::vector<std::string> &args,876 const llvm::StringMap<std::string> &env, llvm::StringRef cwd,877 llvm::StringRef comm_file, lldb::pid_t debugger_pid,878 const std::vector<std::optional<std::string>> &stdio, bool external) {879 llvm::json::Object run_in_terminal_args;880 if (external) {881 // This indicates the IDE to open an external terminal window.882 run_in_terminal_args.try_emplace("kind", "external");883 } else {884 // This indicates the IDE to open an embedded terminal, instead of opening885 // the terminal in a new window.886 run_in_terminal_args.try_emplace("kind", "integrated");887 }888 // The program path must be the first entry in the "args" field889 std::vector<std::string> req_args = {DAP::debug_adapter_path.str(),890 "--comm-file", comm_file.str()};891 if (debugger_pid != LLDB_INVALID_PROCESS_ID) {892 req_args.push_back("--debugger-pid");893 req_args.push_back(std::to_string(debugger_pid));894 }895 req_args.push_back("--launch-target");896 req_args.push_back(program.str());897 if (!stdio.empty()) {898 req_args.push_back("--stdio");899 std::stringstream ss;900 for (const std::optional<std::string> &file : stdio) {901 if (file)902 ss << *file;903 ss << ":";904 }905 std::string files = ss.str();906 files.pop_back();907 req_args.push_back(std::move(files));908 }909 req_args.insert(req_args.end(), args.begin(), args.end());910 run_in_terminal_args.try_emplace("args", req_args);911 912 if (!cwd.empty())913 run_in_terminal_args.try_emplace("cwd", cwd);914 915 if (!env.empty()) {916 llvm::json::Object env_json;917 for (const auto &kv : env) {918 if (!kv.first().empty())919 env_json.try_emplace(kv.first(), kv.second);920 }921 run_in_terminal_args.try_emplace("env",922 llvm::json::Value(std::move(env_json)));923 }924 925 return run_in_terminal_args;926}927 928// Keep all the top level items from the statistics dump, except for the929// "modules" array. It can be huge and cause delay930// Array and dictionary value will return as <key, JSON string> pairs931static void FilterAndGetValueForKey(const lldb::SBStructuredData data,932 const char *key, llvm::json::Object &out) {933 lldb::SBStructuredData value = data.GetValueForKey(key);934 std::string key_utf8 = llvm::json::fixUTF8(key);935 if (llvm::StringRef(key) == "modules")936 return;937 switch (value.GetType()) {938 case lldb::eStructuredDataTypeFloat:939 out.try_emplace(key_utf8, value.GetFloatValue());940 break;941 case lldb::eStructuredDataTypeUnsignedInteger:942 out.try_emplace(key_utf8, value.GetIntegerValue((uint64_t)0));943 break;944 case lldb::eStructuredDataTypeSignedInteger:945 out.try_emplace(key_utf8, value.GetIntegerValue((int64_t)0));946 break;947 case lldb::eStructuredDataTypeArray: {948 lldb::SBStream contents;949 value.GetAsJSON(contents);950 out.try_emplace(key_utf8, llvm::json::fixUTF8(contents.GetData()));951 } break;952 case lldb::eStructuredDataTypeBoolean:953 out.try_emplace(key_utf8, value.GetBooleanValue());954 break;955 case lldb::eStructuredDataTypeString: {956 // Get the string size before reading957 const size_t str_length = value.GetStringValue(nullptr, 0);958 std::string str(str_length + 1, 0);959 value.GetStringValue(&str[0], str_length);960 out.try_emplace(key_utf8, llvm::json::fixUTF8(str));961 } break;962 case lldb::eStructuredDataTypeDictionary: {963 lldb::SBStream contents;964 value.GetAsJSON(contents);965 out.try_emplace(key_utf8, llvm::json::fixUTF8(contents.GetData()));966 } break;967 case lldb::eStructuredDataTypeNull:968 case lldb::eStructuredDataTypeGeneric:969 case lldb::eStructuredDataTypeInvalid:970 break;971 }972}973 974static void addStatistic(lldb::SBTarget &target, llvm::json::Object &event) {975 lldb::SBStructuredData statistics = target.GetStatistics();976 bool is_dictionary =977 statistics.GetType() == lldb::eStructuredDataTypeDictionary;978 if (!is_dictionary)979 return;980 llvm::json::Object stats_body;981 982 lldb::SBStringList keys;983 if (!statistics.GetKeys(keys))984 return;985 for (size_t i = 0; i < keys.GetSize(); i++) {986 const char *key = keys.GetStringAtIndex(i);987 FilterAndGetValueForKey(statistics, key, stats_body);988 }989 llvm::json::Object body{{"$__lldb_statistics", std::move(stats_body)}};990 event.try_emplace("body", std::move(body));991}992 993llvm::json::Object CreateTerminatedEventObject(lldb::SBTarget &target) {994 llvm::json::Object event(CreateEventObject("terminated"));995 addStatistic(target, event);996 return event;997}998 999std::string JSONToString(const llvm::json::Value &json) {1000 std::string data;1001 llvm::raw_string_ostream os(data);1002 os << json;1003 return data;1004}1005 1006} // namespace lldb_dap1007