llvm-project/lldb/tools/lldb-vscode/JSONUtils.cpp

883 lines
32 KiB
C++
Raw Normal View History

//===-- JSONUtils.cpp -------------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "llvm/Support/FormatAdapters.h"
#include "lldb/API/SBBreakpoint.h"
#include "lldb/API/SBBreakpointLocation.h"
#include "lldb/API/SBValue.h"
#include "ExceptionBreakpoint.h"
#include "JSONUtils.h"
#include "LLDBUtils.h"
#include "VSCode.h"
namespace lldb_vscode {
llvm::StringRef GetAsString(const llvm::json::Value &value) {
if (auto s = value.getAsString())
return *s;
return llvm::StringRef();
}
// Gets a string from a JSON object using the key, or returns an empty string.
llvm::StringRef GetString(const llvm::json::Object &obj, llvm::StringRef key) {
if (auto value = obj.getString(key))
return GetAsString(*value);
return llvm::StringRef();
}
llvm::StringRef GetString(const llvm::json::Object *obj, llvm::StringRef key) {
if (obj == nullptr)
return llvm::StringRef();
return GetString(*obj, key);
}
// Gets an unsigned integer from a JSON object using the key, or returns the
// specified fail value.
uint64_t GetUnsigned(const llvm::json::Object &obj, llvm::StringRef key,
uint64_t fail_value) {
if (auto value = obj.getInteger(key))
return (uint64_t)*value;
return fail_value;
}
uint64_t GetUnsigned(const llvm::json::Object *obj, llvm::StringRef key,
uint64_t fail_value) {
if (obj == nullptr)
return fail_value;
return GetUnsigned(*obj, key, fail_value);
}
bool GetBoolean(const llvm::json::Object &obj, llvm::StringRef key,
bool fail_value) {
if (auto value = obj.getBoolean(key))
return *value;
if (auto value = obj.getInteger(key))
return *value != 0;
return fail_value;
}
bool GetBoolean(const llvm::json::Object *obj, llvm::StringRef key,
bool fail_value) {
if (obj == nullptr)
return fail_value;
return GetBoolean(*obj, key, fail_value);
}
int64_t GetSigned(const llvm::json::Object &obj, llvm::StringRef key,
int64_t fail_value) {
if (auto value = obj.getInteger(key))
return *value;
return fail_value;
}
int64_t GetSigned(const llvm::json::Object *obj, llvm::StringRef key,
int64_t fail_value) {
if (obj == nullptr)
return fail_value;
return GetSigned(*obj, key, fail_value);
}
bool ObjectContainsKey(const llvm::json::Object &obj, llvm::StringRef key) {
if (obj.find(key) != obj.end())
return true;
return false;
}
std::vector<std::string> GetStrings(const llvm::json::Object *obj,
llvm::StringRef key) {
std::vector<std::string> strs;
auto json_array = obj->getArray(key);
if (!json_array)
return strs;
for (const auto &value : *json_array) {
switch (value.kind()) {
case llvm::json::Value::String:
strs.push_back(value.getAsString()->str());
break;
case llvm::json::Value::Number:
case llvm::json::Value::Boolean: {
std::string s;
llvm::raw_string_ostream strm(s);
strm << value;
strs.push_back(strm.str());
break;
}
case llvm::json::Value::Null:
case llvm::json::Value::Object:
case llvm::json::Value::Array:
break;
}
}
return strs;
}
void SetValueForKey(lldb::SBValue &v, llvm::json::Object &object,
llvm::StringRef key) {
llvm::StringRef value = v.GetValue();
llvm::StringRef summary = v.GetSummary();
llvm::StringRef type_name = v.GetType().GetDisplayTypeName();
std::string result;
llvm::raw_string_ostream strm(result);
if (!value.empty()) {
strm << value;
if (!summary.empty())
strm << ' ' << summary;
} else if (!summary.empty()) {
strm << ' ' << summary;
} else if (!type_name.empty()) {
strm << type_name;
lldb::addr_t address = v.GetLoadAddress();
if (address != LLDB_INVALID_ADDRESS)
strm << " @ " << llvm::format_hex(address, 0);
}
strm.flush();
object.try_emplace(key, result);
}
void FillResponse(const llvm::json::Object &request,
llvm::json::Object &response) {
// Fill in all of the needed response fields to a "request" and set "success"
// to true by default.
response.try_emplace("type", "response");
response.try_emplace("seq", (int64_t)0);
response.try_emplace("command", GetString(request, "command"));
const int64_t seq = GetSigned(request, "seq", 0);
response.try_emplace("request_seq", seq);
response.try_emplace("success", true);
}
//----------------------------------------------------------------------
// "Scope": {
// "type": "object",
// "description": "A Scope is a named container for variables. Optionally
// a scope can map to a source or a range within a source.",
// "properties": {
// "name": {
// "type": "string",
// "description": "Name of the scope such as 'Arguments', 'Locals'."
// },
// "variablesReference": {
// "type": "integer",
// "description": "The variables of this scope can be retrieved by
// passing the value of variablesReference to the
// VariablesRequest."
// },
// "namedVariables": {
// "type": "integer",
// "description": "The number of named variables in this scope. The
// client can use this optional information to present
// the variables in a paged UI and fetch them in chunks."
// },
// "indexedVariables": {
// "type": "integer",
// "description": "The number of indexed variables in this scope. The
// client can use this optional information to present
// the variables in a paged UI and fetch them in chunks."
// },
// "expensive": {
// "type": "boolean",
// "description": "If true, the number of variables in this scope is
// large or expensive to retrieve."
// },
// "source": {
// "$ref": "#/definitions/Source",
// "description": "Optional source for this scope."
// },
// "line": {
// "type": "integer",
// "description": "Optional start line of the range covered by this
// scope."
// },
// "column": {
// "type": "integer",
// "description": "Optional start column of the range covered by this
// scope."
// },
// "endLine": {
// "type": "integer",
// "description": "Optional end line of the range covered by this scope."
// },
// "endColumn": {
// "type": "integer",
// "description": "Optional end column of the range covered by this
// scope."
// }
// },
// "required": [ "name", "variablesReference", "expensive" ]
// }
//----------------------------------------------------------------------
llvm::json::Value CreateScope(const llvm::StringRef name,
int64_t variablesReference,
int64_t namedVariables, bool expensive) {
llvm::json::Object object;
object.try_emplace("name", name.str());
object.try_emplace("variablesReference", variablesReference);
object.try_emplace("expensive", expensive);
object.try_emplace("namedVariables", namedVariables);
return llvm::json::Value(std::move(object));
}
//----------------------------------------------------------------------
// "Breakpoint": {
// "type": "object",
// "description": "Information about a Breakpoint created in setBreakpoints
// or setFunctionBreakpoints.",
// "properties": {
// "id": {
// "type": "integer",
// "description": "An optional unique identifier for the breakpoint."
// },
// "verified": {
// "type": "boolean",
// "description": "If true breakpoint could be set (but not necessarily
// at the desired location)."
// },
// "message": {
// "type": "string",
// "description": "An optional message about the state of the breakpoint.
// This is shown to the user and can be used to explain
// why a breakpoint could not be verified."
// },
// "source": {
// "$ref": "#/definitions/Source",
// "description": "The source where the breakpoint is located."
// },
// "line": {
// "type": "integer",
// "description": "The start line of the actual range covered by the
// breakpoint."
// },
// "column": {
// "type": "integer",
// "description": "An optional start column of the actual range covered
// by the breakpoint."
// },
// "endLine": {
// "type": "integer",
// "description": "An optional end line of the actual range covered by
// the breakpoint."
// },
// "endColumn": {
// "type": "integer",
// "description": "An optional end column of the actual range covered by
// the breakpoint. If no end line is given, then the end
// column is assumed to be in the start line."
// }
// },
// "required": [ "verified" ]
// }
//----------------------------------------------------------------------
llvm::json::Value CreateBreakpoint(lldb::SBBreakpointLocation &bp_loc) {
// Each breakpoint location is treated as a separate breakpoint for VS code.
// They don't have the notion of a single breakpoint with multiple locations.
llvm::json::Object object;
if (!bp_loc.IsValid())
return llvm::json::Value(std::move(object));
object.try_emplace("verified", true);
const auto bp_id = bp_loc.GetBreakpoint().GetID();
const auto vs_id = (int64_t)(((int64_t)bp_id << 32) | bp_loc.GetID());
object.try_emplace("id", vs_id);
auto bp_addr = bp_loc.GetAddress();
if (bp_addr.IsValid()) {
auto line_entry = bp_addr.GetLineEntry();
const auto line = line_entry.GetLine();
if (line != UINT32_MAX)
object.try_emplace("line", line);
object.try_emplace("source", CreateSource(line_entry));
}
return llvm::json::Value(std::move(object));
}
void AppendBreakpoint(lldb::SBBreakpoint &bp, llvm::json::Array &breakpoints) {
if (!bp.IsValid())
return;
const auto num_locations = bp.GetNumLocations();
if (num_locations == 0)
return;
for (size_t i = 0; i < num_locations; ++i) {
auto bp_loc = bp.GetLocationAtIndex(i);
breakpoints.emplace_back(CreateBreakpoint(bp_loc));
}
}
//----------------------------------------------------------------------
// "Event": {
// "allOf": [ { "$ref": "#/definitions/ProtocolMessage" }, {
// "type": "object",
// "description": "Server-initiated event.",
// "properties": {
// "type": {
// "type": "string",
// "enum": [ "event" ]
// },
// "event": {
// "type": "string",
// "description": "Type of event."
// },
// "body": {
// "type": [ "array", "boolean", "integer", "null", "number" ,
// "object", "string" ],
// "description": "Event-specific information."
// }
// },
// "required": [ "type", "event" ]
// }]
// },
// "ProtocolMessage": {
// "type": "object",
// "description": "Base class of requests, responses, and events.",
// "properties": {
// "seq": {
// "type": "integer",
// "description": "Sequence number."
// },
// "type": {
// "type": "string",
// "description": "Message type.",
// "_enum": [ "request", "response", "event" ]
// }
// },
// "required": [ "seq", "type" ]
// }
//----------------------------------------------------------------------
llvm::json::Object CreateEvent(const llvm::StringRef event_name) {
llvm::json::Object event;
event.try_emplace("seq", 0);
event.try_emplace("type", "event");
event.try_emplace("event", event_name);
return event;
}
//----------------------------------------------------------------------
// "ExceptionBreakpointsFilter": {
// "type": "object",
// "description": "An ExceptionBreakpointsFilter is shown in the UI as an
// option for configuring how exceptions are dealt with.",
// "properties": {
// "filter": {
// "type": "string",
// "description": "The internal ID of the filter. This value is passed
// to the setExceptionBreakpoints request."
// },
// "label": {
// "type": "string",
// "description": "The name of the filter. This will be shown in the UI."
// },
// "default": {
// "type": "boolean",
// "description": "Initial value of the filter. If not specified a value
// 'false' is assumed."
// }
// },
// "required": [ "filter", "label" ]
// }
//----------------------------------------------------------------------
llvm::json::Value
CreateExceptionBreakpointFilter(const ExceptionBreakpoint &bp) {
llvm::json::Object object;
object.try_emplace("filter", bp.filter);
object.try_emplace("label", bp.label);
object.try_emplace("default", bp.default_value);
return llvm::json::Value(std::move(object));
}
//----------------------------------------------------------------------
// "Source": {
// "type": "object",
// "description": "A Source is a descriptor for source code. It is returned
// from the debug adapter as part of a StackFrame and it is
// used by clients when specifying breakpoints.",
// "properties": {
// "name": {
// "type": "string",
// "description": "The short name of the source. Every source returned
// from the debug adapter has a name. When sending a
// source to the debug adapter this name is optional."
// },
// "path": {
// "type": "string",
// "description": "The path of the source to be shown in the UI. It is
// only used to locate and load the content of the
// source if no sourceReference is specified (or its
// value is 0)."
// },
// "sourceReference": {
// "type": "number",
// "description": "If sourceReference > 0 the contents of the source must
// be retrieved through the SourceRequest (even if a path
// is specified). A sourceReference is only valid for a
// session, so it must not be used to persist a source."
// },
// "presentationHint": {
// "type": "string",
// "description": "An optional hint for how to present the source in the
// UI. A value of 'deemphasize' can be used to indicate
// that the source is not available or that it is
// skipped on stepping.",
// "enum": [ "normal", "emphasize", "deemphasize" ]
// },
// "origin": {
// "type": "string",
// "description": "The (optional) origin of this source: possible values
// 'internal module', 'inlined content from source map',
// etc."
// },
// "sources": {
// "type": "array",
// "items": {
// "$ref": "#/definitions/Source"
// },
// "description": "An optional list of sources that are related to this
// source. These may be the source that generated this
// source."
// },
// "adapterData": {
// "type":["array","boolean","integer","null","number","object","string"],
// "description": "Optional data that a debug adapter might want to loop
// through the client. The client should leave the data
// intact and persist it across sessions. The client
// should not interpret the data."
// },
// "checksums": {
// "type": "array",
// "items": {
// "$ref": "#/definitions/Checksum"
// },
// "description": "The checksums associated with this file."
// }
// }
// }
//----------------------------------------------------------------------
llvm::json::Value CreateSource(lldb::SBLineEntry &line_entry) {
llvm::json::Object object;
lldb::SBFileSpec file = line_entry.GetFileSpec();
if (file.IsValid()) {
const char *name = file.GetFilename();
if (name)
object.try_emplace("name", name);
char path[PATH_MAX] = "";
file.GetPath(path, sizeof(path));
if (path[0]) {
object.try_emplace("path", std::string(path));
}
}
return llvm::json::Value(std::move(object));
}
llvm::json::Value CreateSource(lldb::SBFrame &frame, int64_t &disasm_line) {
disasm_line = 0;
auto line_entry = frame.GetLineEntry();
if (line_entry.GetFileSpec().IsValid())
return CreateSource(line_entry);
llvm::json::Object object;
const auto pc = frame.GetPC();
lldb::SBInstructionList insts;
lldb::SBFunction function = frame.GetFunction();
lldb::addr_t low_pc = LLDB_INVALID_ADDRESS;
if (function.IsValid()) {
low_pc = function.GetStartAddress().GetLoadAddress(g_vsc.target);
auto addr_srcref = g_vsc.addr_to_source_ref.find(low_pc);
if (addr_srcref != g_vsc.addr_to_source_ref.end()) {
// We have this disassembly cached already, return the existing
// sourceReference
object.try_emplace("sourceReference", addr_srcref->second);
disasm_line = g_vsc.GetLineForPC(addr_srcref->second, pc);
} else {
insts = function.GetInstructions(g_vsc.target);
}
} else {
lldb::SBSymbol symbol = frame.GetSymbol();
if (symbol.IsValid()) {
low_pc = symbol.GetStartAddress().GetLoadAddress(g_vsc.target);
auto addr_srcref = g_vsc.addr_to_source_ref.find(low_pc);
if (addr_srcref != g_vsc.addr_to_source_ref.end()) {
// We have this disassembly cached already, return the existing
// sourceReference
object.try_emplace("sourceReference", addr_srcref->second);
disasm_line = g_vsc.GetLineForPC(addr_srcref->second, pc);
} else {
insts = symbol.GetInstructions(g_vsc.target);
}
}
}
const auto num_insts = insts.GetSize();
if (low_pc != LLDB_INVALID_ADDRESS && num_insts > 0) {
object.try_emplace("name", frame.GetFunctionName());
SourceReference source;
llvm::raw_string_ostream src_strm(source.content);
std::string line;
for (size_t i = 0; i < num_insts; ++i) {
lldb::SBInstruction inst = insts.GetInstructionAtIndex(i);
const auto inst_addr = inst.GetAddress().GetLoadAddress(g_vsc.target);
const char *m = inst.GetMnemonic(g_vsc.target);
const char *o = inst.GetOperands(g_vsc.target);
const char *c = inst.GetComment(g_vsc.target);
if (pc == inst_addr)
disasm_line = i + 1;
const auto inst_offset = inst_addr - low_pc;
int spaces = 0;
if (inst_offset < 10)
spaces = 3;
else if (inst_offset < 100)
spaces = 2;
else if (inst_offset < 1000)
spaces = 1;
line.clear();
llvm::raw_string_ostream line_strm(line);
line_strm << llvm::formatv("{0:X+}: <{1}> {2} {3,12} {4}", inst_addr,
inst_offset, llvm::fmt_repeat(' ', spaces),
m, o);
const uint32_t comment_row = 60;
// If there is a comment append it starting at column 60
if (c && c[0]) {
if (line.size() < comment_row)
line_strm.indent(comment_row - line_strm.str().size());
line_strm << " # " << c;
}
src_strm << line_strm.str() << "\n";
source.addr_to_line[inst_addr] = i + 1;
}
// Flush the source stream
src_strm.str();
auto sourceReference = VSCode::GetNextSourceReference();
g_vsc.source_map[sourceReference] = std::move(source);
g_vsc.addr_to_source_ref[low_pc] = sourceReference;
object.try_emplace("sourceReference", sourceReference);
}
return llvm::json::Value(std::move(object));
}
//----------------------------------------------------------------------
// "StackFrame": {
// "type": "object",
// "description": "A Stackframe contains the source location.",
// "properties": {
// "id": {
// "type": "integer",
// "description": "An identifier for the stack frame. It must be unique
// across all threads. This id can be used to retrieve
// the scopes of the frame with the 'scopesRequest' or
// to restart the execution of a stackframe."
// },
// "name": {
// "type": "string",
// "description": "The name of the stack frame, typically a method name."
// },
// "source": {
// "$ref": "#/definitions/Source",
// "description": "The optional source of the frame."
// },
// "line": {
// "type": "integer",
// "description": "The line within the file of the frame. If source is
// null or doesn't exist, line is 0 and must be ignored."
// },
// "column": {
// "type": "integer",
// "description": "The column within the line. If source is null or
// doesn't exist, column is 0 and must be ignored."
// },
// "endLine": {
// "type": "integer",
// "description": "An optional end line of the range covered by the
// stack frame."
// },
// "endColumn": {
// "type": "integer",
// "description": "An optional end column of the range covered by the
// stack frame."
// },
// "moduleId": {
// "type": ["integer", "string"],
// "description": "The module associated with this frame, if any."
// },
// "presentationHint": {
// "type": "string",
// "enum": [ "normal", "label", "subtle" ],
// "description": "An optional hint for how to present this frame in
// the UI. A value of 'label' can be used to indicate
// that the frame is an artificial frame that is used
// as a visual label or separator. A value of 'subtle'
// can be used to change the appearance of a frame in
// a 'subtle' way."
// }
// },
// "required": [ "id", "name", "line", "column" ]
// }
//----------------------------------------------------------------------
llvm::json::Value CreateStackFrame(lldb::SBFrame &frame) {
llvm::json::Object object;
int64_t frame_id = MakeVSCodeFrameID(frame);
object.try_emplace("id", frame_id);
object.try_emplace("name", frame.GetFunctionName());
int64_t disasm_line = 0;
object.try_emplace("source", CreateSource(frame, disasm_line));
auto line_entry = frame.GetLineEntry();
if (disasm_line > 0) {
object.try_emplace("line", disasm_line);
} else {
auto line = line_entry.GetLine();
if (line == UINT32_MAX)
line = 0;
object.try_emplace("line", line);
}
object.try_emplace("column", line_entry.GetColumn());
return llvm::json::Value(std::move(object));
}
//----------------------------------------------------------------------
// "Thread": {
// "type": "object",
// "description": "A Thread",
// "properties": {
// "id": {
// "type": "integer",
// "description": "Unique identifier for the thread."
// },
// "name": {
// "type": "string",
// "description": "A name of the thread."
// }
// },
// "required": [ "id", "name" ]
// }
//----------------------------------------------------------------------
llvm::json::Value CreateThread(lldb::SBThread &thread) {
llvm::json::Object object;
object.try_emplace("id", (int64_t)thread.GetThreadID());
char thread_str[64];
snprintf(thread_str, sizeof(thread_str), "Thread #%u", thread.GetIndexID());
const char *name = thread.GetName();
if (name) {
std::string thread_with_name(thread_str);
thread_with_name += ' ';
thread_with_name += name;
object.try_emplace("name", thread_with_name);
} else {
object.try_emplace("name", std::string(thread_str));
}
return llvm::json::Value(std::move(object));
}
//----------------------------------------------------------------------
// "StoppedEvent": {
// "allOf": [ { "$ref": "#/definitions/Event" }, {
// "type": "object",
// "description": "Event message for 'stopped' event type. The event
// indicates that the execution of the debuggee has stopped
// due to some condition. This can be caused by a break
// point previously set, a stepping action has completed,
// by executing a debugger statement etc.",
// "properties": {
// "event": {
// "type": "string",
// "enum": [ "stopped" ]
// },
// "body": {
// "type": "object",
// "properties": {
// "reason": {
// "type": "string",
// "description": "The reason for the event. For backward
// compatibility this string is shown in the UI if
// the 'description' attribute is missing (but it
// must not be translated).",
// "_enum": [ "step", "breakpoint", "exception", "pause", "entry" ]
// },
// "description": {
// "type": "string",
// "description": "The full reason for the event, e.g. 'Paused
// on exception'. This string is shown in the UI
// as is."
// },
// "threadId": {
// "type": "integer",
// "description": "The thread which was stopped."
// },
// "text": {
// "type": "string",
// "description": "Additional information. E.g. if reason is
// 'exception', text contains the exception name.
// This string is shown in the UI."
// },
// "allThreadsStopped": {
// "type": "boolean",
// "description": "If allThreadsStopped is true, a debug adapter
// can announce that all threads have stopped.
// The client should use this information to
// enable that all threads can be expanded to
// access their stacktraces. If the attribute
// is missing or false, only the thread with the
// given threadId can be expanded."
// }
// },
// "required": [ "reason" ]
// }
// },
// "required": [ "event", "body" ]
// }]
// }
//----------------------------------------------------------------------
llvm::json::Value CreateThreadStopped(lldb::SBThread &thread,
uint32_t stop_id) {
llvm::json::Object event(CreateEvent("stopped"));
llvm::json::Object body;
switch (thread.GetStopReason()) {
case lldb::eStopReasonTrace:
case lldb::eStopReasonPlanComplete:
body.try_emplace("reason", "step");
break;
case lldb::eStopReasonBreakpoint: {
ExceptionBreakpoint *exc_bp = g_vsc.GetExceptionBPFromStopReason(thread);
if (exc_bp) {
body.try_emplace("reason", "exception");
body.try_emplace("description", exc_bp->label);
} else {
body.try_emplace("reason", "breakpoint");
}
} break;
case lldb::eStopReasonWatchpoint:
case lldb::eStopReasonInstrumentation:
body.try_emplace("reason", "breakpoint");
break;
case lldb::eStopReasonSignal:
body.try_emplace("reason", "exception");
break;
case lldb::eStopReasonException:
body.try_emplace("reason", "exception");
break;
case lldb::eStopReasonExec:
body.try_emplace("reason", "entry");
break;
case lldb::eStopReasonThreadExiting:
case lldb::eStopReasonInvalid:
case lldb::eStopReasonNone:
break;
}
if (stop_id == 0)
body.try_emplace("reason", "entry");
const lldb::tid_t tid = thread.GetThreadID();
body.try_emplace("threadId", (int64_t)tid);
// If no description has been set, then set it to the default thread stopped
// description. If we have breakpoints that get hit and shouldn't be reported
// as breakpoints, then they will set the description above.
if (ObjectContainsKey(body, "description")) {
char description[1024];
if (thread.GetStopDescription(description, sizeof(description))) {
body.try_emplace("description", std::string(description));
}
}
if (tid == g_vsc.focus_tid) {
body.try_emplace("threadCausedFocus", true);
}
body.try_emplace("preserveFocusHint", tid != g_vsc.focus_tid);
body.try_emplace("allThreadsStopped", true);
event.try_emplace("body", std::move(body));
return llvm::json::Value(std::move(event));
}
//----------------------------------------------------------------------
// "Variable": {
// "type": "object",
// "description": "A Variable is a name/value pair. Optionally a variable
// can have a 'type' that is shown if space permits or when
// hovering over the variable's name. An optional 'kind' is
// used to render additional properties of the variable,
// e.g. different icons can be used to indicate that a
// variable is public or private. If the value is
// structured (has children), a handle is provided to
// retrieve the children with the VariablesRequest. If
// the number of named or indexed children is large, the
// numbers should be returned via the optional
// 'namedVariables' and 'indexedVariables' attributes. The
// client can use this optional information to present the
// children in a paged UI and fetch them in chunks.",
// "properties": {
// "name": {
// "type": "string",
// "description": "The variable's name."
// },
// "value": {
// "type": "string",
// "description": "The variable's value. This can be a multi-line text,
// e.g. for a function the body of a function."
// },
// "type": {
// "type": "string",
// "description": "The type of the variable's value. Typically shown in
// the UI when hovering over the value."
// },
// "presentationHint": {
// "$ref": "#/definitions/VariablePresentationHint",
// "description": "Properties of a variable that can be used to determine
// how to render the variable in the UI."
// },
// "evaluateName": {
// "type": "string",
// "description": "Optional evaluatable name of this variable which can
// be passed to the 'EvaluateRequest' to fetch the
// variable's value."
// },
// "variablesReference": {
// "type": "integer",
// "description": "If variablesReference is > 0, the variable is
// structured and its children can be retrieved by
// passing variablesReference to the VariablesRequest."
// },
// "namedVariables": {
// "type": "integer",
// "description": "The number of named child variables. The client can
// use this optional information to present the children
// in a paged UI and fetch them in chunks."
// },
// "indexedVariables": {
// "type": "integer",
// "description": "The number of indexed child variables. The client
// can use this optional information to present the
// children in a paged UI and fetch them in chunks."
// }
// },
// "required": [ "name", "value", "variablesReference" ]
// }
//----------------------------------------------------------------------
llvm::json::Value CreateVariable(lldb::SBValue v, int64_t variablesReference,
int64_t varID, bool format_hex) {
llvm::json::Object object;
auto name = v.GetName();
object.try_emplace("name", name ? name : "<null>");
if (format_hex)
v.SetFormat(lldb::eFormatHex);
SetValueForKey(v, object, "value");
auto type_cstr = v.GetType().GetDisplayTypeName();
object.try_emplace("type", type_cstr ? type_cstr : NO_TYPENAME);
if (varID != INT64_MAX)
object.try_emplace("id", varID);
if (v.MightHaveChildren())
object.try_emplace("variablesReference", variablesReference);
else
object.try_emplace("variablesReference", (int64_t)0);
lldb::SBStream evaluateStream;
v.GetExpressionPath(evaluateStream);
const char *evaluateName = evaluateStream.GetData();
if (evaluateName && evaluateName[0])
object.try_emplace("evaluateName", std::string(evaluateName));
return llvm::json::Value(std::move(object));
}
} // namespace lldb_vscode