brintos

brintos / llvm-project-archived public Read only

0
0
Text · 34.8 KiB · 40b4f5b Raw
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