[lldb-dap] Migrate variables request protocol types. (#147611)

This adds new protocol types for the 'variables' request.

While implementing this, I removed the '$__lldb_extension' field we
returned on the 'variables' request, since I think all the data can be
retrieved from other DAP requests.

---------

Co-authored-by: Jonas Devlieghere <jonas@devlieghere.com>
This commit is contained in:
John Harrison 2025-07-10 10:10:00 -07:00 committed by GitHub
parent 8d3f497eb8
commit 4b6e54a8cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 688 additions and 452 deletions

View File

@ -28,7 +28,7 @@ class TestDAP_optimized(lldbdap_testcase.DAPTestCaseBase):
parent_frame = self.dap_server.get_stackFrame(frameIndex=1)
self.assertTrue(parent_frame["name"].endswith(" [opt]"))
@skipIfAsan # On ASAN builds this test intermittently fails https://github.com/llvm/llvm-project/issues/111061
@skipIfAsan # On ASAN builds this test intermittently fails https://github.com/llvm/llvm-project/issues/111061
@skipIfWindows
def test_optimized_variable(self):
"""Test optimized variable value contains error."""
@ -50,9 +50,8 @@ class TestDAP_optimized(lldbdap_testcase.DAPTestCaseBase):
value.startswith("<error:"),
f"expect error for value: '{value}'",
)
error_msg = optimized_variable["$__lldb_extensions"]["error"]
self.assertTrue(
("could not evaluate DW_OP_entry_value: no parent function" in error_msg)
or ("variable not available" in error_msg)
("could not evaluate DW_OP_entry_value: no parent function" in value)
or ("variable not available" in value)
)
self.continue_to_exit()

View File

@ -1,12 +1,10 @@
"""
Test lldb-dap setBreakpoints request
Test lldb-dap variables request
"""
import os
import dap_server
import lldbdap_testcase
from lldbsuite.test import lldbutil
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
@ -168,15 +166,6 @@ class TestDAP_variables(lldbdap_testcase.DAPTestCaseBase):
"type": "int",
"value": "1",
},
"$__lldb_extensions": {
"equals": {
"value": "1",
},
"declaration": {
"equals": {"line": 12, "column": 14},
"contains": {"path": ["lldb-dap", "variables", "main.cpp"]},
},
},
},
"argv": {
"equals": {"type": "const char **"},
@ -196,10 +185,6 @@ class TestDAP_variables(lldbdap_testcase.DAPTestCaseBase):
},
"x": {"equals": {"type": "int"}},
}
if enableAutoVariableSummaries:
verify_locals["pt"]["$__lldb_extensions"] = {
"equals": {"autoSummary": "{x:11, y:22}"}
}
verify_globals = {
"s_local": {"equals": {"type": "float", "value": "2.25"}},

View File

@ -540,11 +540,14 @@ public:
Run(const protocol::ThreadsArguments &) const override;
};
class VariablesRequestHandler : public LegacyRequestHandler {
class VariablesRequestHandler
: public RequestHandler<protocol::VariablesArguments,
llvm::Expected<protocol::VariablesResponseBody>> {
public:
using LegacyRequestHandler::LegacyRequestHandler;
using RequestHandler::RequestHandler;
static llvm::StringLiteral GetCommand() { return "variables"; }
void operator()(const llvm::json::Object &request) const override;
llvm::Expected<protocol::VariablesResponseBody>
Run(const protocol::VariablesArguments &) const override;
};
class LocationsRequestHandler : public LegacyRequestHandler {

View File

@ -8,107 +8,37 @@
#include "DAP.h"
#include "EventHelper.h"
#include "Handler/RequestHandler.h"
#include "JSONUtils.h"
#include "RequestHandler.h"
#include "ProtocolUtils.h"
using namespace llvm;
using namespace lldb_dap::protocol;
namespace lldb_dap {
// "VariablesRequest": {
// "allOf": [ { "$ref": "#/definitions/Request" }, {
// "type": "object",
// "description": "Variables request; value of command field is 'variables'.
// Retrieves all child variables for the given variable reference. An
// optional filter can be used to limit the fetched children to either named
// or indexed children.", "properties": {
// "command": {
// "type": "string",
// "enum": [ "variables" ]
// },
// "arguments": {
// "$ref": "#/definitions/VariablesArguments"
// }
// },
// "required": [ "command", "arguments" ]
// }]
// },
// "VariablesArguments": {
// "type": "object",
// "description": "Arguments for 'variables' request.",
// "properties": {
// "variablesReference": {
// "type": "integer",
// "description": "The Variable reference."
// },
// "filter": {
// "type": "string",
// "enum": [ "indexed", "named" ],
// "description": "Optional filter to limit the child variables to either
// named or indexed. If ommited, both types are fetched."
// },
// "start": {
// "type": "integer",
// "description": "The index of the first variable to return; if omitted
// children start at 0."
// },
// "count": {
// "type": "integer",
// "description": "The number of variables to return. If count is missing
// or 0, all variables are returned."
// },
// "format": {
// "$ref": "#/definitions/ValueFormat",
// "description": "Specifies details on how to format the Variable
// values."
// }
// },
// "required": [ "variablesReference" ]
// },
// "VariablesResponse": {
// "allOf": [ { "$ref": "#/definitions/Response" }, {
// "type": "object",
// "description": "Response to 'variables' request.",
// "properties": {
// "body": {
// "type": "object",
// "properties": {
// "variables": {
// "type": "array",
// "items": {
// "$ref": "#/definitions/Variable"
// },
// "description": "All (or a range) of variables for the given
// variable reference."
// }
// },
// "required": [ "variables" ]
// }
// },
// "required": [ "body" ]
// }]
// }
void VariablesRequestHandler::operator()(
const llvm::json::Object &request) const {
llvm::json::Object response;
FillResponse(request, response);
llvm::json::Array variables;
const auto *arguments = request.getObject("arguments");
const auto variablesReference =
GetInteger<uint64_t>(arguments, "variablesReference").value_or(0);
const auto start = GetInteger<int64_t>(arguments, "start").value_or(0);
const auto count = GetInteger<int64_t>(arguments, "count").value_or(0);
/// Retrieves all child variables for the given variable reference.
///
/// A filter can be used to limit the fetched children to either named or
/// indexed children.
Expected<VariablesResponseBody>
VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
const uint64_t var_ref = arguments.variablesReference;
const uint64_t count = arguments.count;
const uint64_t start = arguments.start;
bool hex = false;
const auto *format = arguments->getObject("format");
if (format)
hex = GetBoolean(format, "hex").value_or(false);
if (arguments.format)
hex = arguments.format->hex;
if (lldb::SBValueList *top_scope =
dap.variables.GetTopLevelScope(variablesReference)) {
std::vector<Variable> variables;
if (lldb::SBValueList *top_scope = dap.variables.GetTopLevelScope(var_ref)) {
// variablesReference is one of our scopes, not an actual variable it is
// asking for the list of args, locals or globals.
int64_t start_idx = 0;
int64_t num_children = 0;
if (variablesReference == VARREF_REGS) {
if (var_ref == VARREF_REGS) {
// Change the default format of any pointer sized registers in the first
// register set to be the lldb::eFormatAddressInfo so we show the pointer
// and resolve what the pointer resolves to. Only change the format if the
@ -128,7 +58,7 @@ void VariablesRequestHandler::operator()(
}
num_children = top_scope->GetSize();
if (num_children == 0 && variablesReference == VARREF_LOCALS) {
if (num_children == 0 && var_ref == VARREF_LOCALS) {
// Check for an error in the SBValueList that might explain why we don't
// have locals. If we have an error display it as the sole value in the
// the locals.
@ -145,12 +75,11 @@ void VariablesRequestHandler::operator()(
// errors are only set when there is a problem that the user could
// fix, so no error will show up when you have no debug info, only when
// we do have debug info and something that is fixable can be done.
llvm::json::Object object;
EmplaceSafeString(object, "name", "<error>");
EmplaceSafeString(object, "type", "const char *");
EmplaceSafeString(object, "value", var_err);
object.try_emplace("variablesReference", (int64_t)0);
variables.emplace_back(std::move(object));
Variable var;
var.name = "<error>";
var.type = "const char *";
var.value = var_err;
variables.emplace_back(var);
}
}
const int64_t end_idx = start_idx + ((count == 0) ? num_children : count);
@ -165,7 +94,7 @@ void VariablesRequestHandler::operator()(
}
// Show return value if there is any ( in the local top frame )
if (variablesReference == VARREF_LOCALS) {
if (var_ref == VARREF_LOCALS) {
auto process = dap.target.GetProcess();
auto selected_thread = process.GetSelectedThread();
lldb::SBValue stop_return_value = selected_thread.GetStopReturnValue();
@ -194,32 +123,35 @@ void VariablesRequestHandler::operator()(
if (!variable.IsValid())
break;
int64_t var_ref =
const int64_t frame_var_ref =
dap.variables.InsertVariable(variable, /*is_permanent=*/false);
variables.emplace_back(CreateVariable(
variable, var_ref, hex, dap.configuration.enableAutoVariableSummaries,
variable, frame_var_ref, hex,
dap.configuration.enableAutoVariableSummaries,
dap.configuration.enableSyntheticChildDebugging,
variable_name_counts[GetNonNullVariableName(variable)] > 1));
}
} else {
// We are expanding a variable that has children, so we will return its
// children.
lldb::SBValue variable = dap.variables.GetVariable(variablesReference);
lldb::SBValue variable = dap.variables.GetVariable(var_ref);
if (variable.IsValid()) {
const bool is_permanent =
dap.variables.IsPermanentVariableReference(var_ref);
auto addChild = [&](lldb::SBValue child,
std::optional<std::string> custom_name = {}) {
if (!child.IsValid())
return;
bool is_permanent =
dap.variables.IsPermanentVariableReference(variablesReference);
int64_t var_ref = dap.variables.InsertVariable(child, is_permanent);
variables.emplace_back(CreateVariable(
child, var_ref, hex, dap.configuration.enableAutoVariableSummaries,
dap.configuration.enableSyntheticChildDebugging,
/*is_name_duplicated=*/false, custom_name));
const int64_t child_var_ref =
dap.variables.InsertVariable(child, is_permanent);
variables.emplace_back(
CreateVariable(child, child_var_ref, hex,
dap.configuration.enableAutoVariableSummaries,
dap.configuration.enableSyntheticChildDebugging,
/*is_name_duplicated=*/false, custom_name));
};
const int64_t num_children = variable.GetNumChildren();
int64_t end_idx = start + ((count == 0) ? num_children : count);
const int64_t end_idx = start + ((count == 0) ? num_children : count);
int64_t i = start;
for (; i < end_idx && i < num_children; ++i)
addChild(variable.GetChildAtIndex(i));
@ -233,10 +165,8 @@ void VariablesRequestHandler::operator()(
addChild(variable.GetNonSyntheticValue(), "[raw]");
}
}
llvm::json::Object body;
body.try_emplace("variables", std::move(variables));
response.try_emplace("body", std::move(body));
dap.SendJSON(llvm::json::Value(std::move(response)));
return VariablesResponseBody{variables};
}
} // namespace lldb_dap

View File

@ -120,6 +120,42 @@ DecodeMemoryReference(llvm::StringRef memoryReference) {
return addr;
}
bool DecodeMemoryReference(const llvm::json::Value &v, llvm::StringLiteral key,
lldb::addr_t &out, llvm::json::Path path,
bool required) {
const llvm::json::Object *v_obj = v.getAsObject();
if (!v_obj) {
path.report("expected object");
return false;
}
const llvm::json::Value *mem_ref_value = v_obj->get(key);
if (!mem_ref_value) {
if (!required)
return true;
path.field(key).report("missing value");
return false;
}
const std::optional<llvm::StringRef> mem_ref_str =
mem_ref_value->getAsString();
if (!mem_ref_str) {
path.field(key).report("expected string");
return false;
}
const std::optional<lldb::addr_t> addr_opt =
DecodeMemoryReference(*mem_ref_str);
if (!addr_opt) {
path.field(key).report("malformed memory reference");
return false;
}
out = *addr_opt;
return true;
}
std::vector<std::string> GetStrings(const llvm::json::Object *obj,
llvm::StringRef key) {
std::vector<std::string> strs;
@ -768,38 +804,6 @@ VariableDescription::VariableDescription(lldb::SBValue v,
evaluate_name = llvm::StringRef(evaluateStream.GetData()).str();
}
llvm::json::Object VariableDescription::GetVariableExtensionsJSON() {
llvm::json::Object extensions;
if (error)
EmplaceSafeString(extensions, "error", *error);
if (!value.empty())
EmplaceSafeString(extensions, "value", value);
if (!summary.empty())
EmplaceSafeString(extensions, "summary", summary);
if (auto_summary)
EmplaceSafeString(extensions, "autoSummary", *auto_summary);
if (lldb::SBDeclaration decl = v.GetDeclaration(); decl.IsValid()) {
llvm::json::Object decl_obj;
if (lldb::SBFileSpec file = decl.GetFileSpec(); file.IsValid()) {
char path[PATH_MAX] = "";
if (file.GetPath(path, sizeof(path)) &&
lldb::SBFileSpec::ResolvePath(path, path, PATH_MAX)) {
decl_obj.try_emplace("path", std::string(path));
}
}
if (int line = decl.GetLine())
decl_obj.try_emplace("line", line);
if (int column = decl.GetColumn())
decl_obj.try_emplace("column", column);
if (!decl_obj.empty())
extensions.try_emplace("declaration", std::move(decl_obj));
}
return extensions;
}
std::string VariableDescription::GetResult(llvm::StringRef context) {
// In repl context, the results can be displayed as multiple lines so more
// detailed descriptions can be returned.
@ -836,226 +840,6 @@ std::pair<int64_t, bool> UnpackLocation(int64_t location_id) {
return std::pair{location_id >> 1, location_id & 1};
}
// "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."
// },
// "memoryReference": {
// "type": "string",
// "description": "A memory reference associated with this variable.
// For pointer type variables, this is generally a
// reference to the memory address contained in the
// pointer. For executable data, this reference may later
// be used in a `disassemble` request. This attribute may
// be returned by a debug adapter if corresponding
// capability `supportsMemoryReferences` is true."
// },
// "declarationLocationReference": {
// "type": "integer",
// "description": "A reference that allows the client to request the
// location where the variable is declared. This should be
// present only if the adapter is likely to be able to
// resolve the location.\n\nThis reference shares the same
// lifetime as the `variablesReference`. See 'Lifetime of
// Object References' in the Overview section for
// details."
// },
// "valueLocationReference": {
// "type": "integer",
// "description": "A reference that allows the client to request the
// location where the variable's value is declared. For
// example, if the variable contains a function pointer,
// the adapter may be able to look up the function's
// location. This should be present only if the adapter
// is likely to be able to resolve the location.\n\nThis
// reference shares the same lifetime as the
// `variablesReference`. See 'Lifetime of Object
// References' in the Overview section for details."
// },
//
// "$__lldb_extensions": {
// "description": "Unofficial extensions to the protocol",
// "properties": {
// "declaration": {
// "type": "object",
// "description": "The source location where the variable was
// declared. This value won't be present if no
// declaration is available.
// Superseded by `declarationLocationReference`",
// "properties": {
// "path": {
// "type": "string",
// "description": "The source file path where the variable was
// declared."
// },
// "line": {
// "type": "number",
// "description": "The 1-indexed source line where the variable
// was declared."
// },
// "column": {
// "type": "number",
// "description": "The 1-indexed source column where the variable
// was declared."
// }
// }
// },
// "value": {
// "type": "string",
// "description": "The internal value of the variable as returned by
// This is effectively SBValue.GetValue(). The other
// `value` entry in the top-level variable response
// is, on the other hand, just a display string for
// the variable."
// },
// "summary": {
// "type": "string",
// "description": "The summary string of the variable. This is
// effectively SBValue.GetSummary()."
// },
// "autoSummary": {
// "type": "string",
// "description": "The auto generated summary if using
// `enableAutoVariableSummaries`."
// },
// "error": {
// "type": "string",
// "description": "An error message generated if LLDB couldn't inspect
// the variable."
// }
// }
// }
// },
// "required": [ "name", "value", "variablesReference" ]
// }
llvm::json::Value CreateVariable(lldb::SBValue v, int64_t var_ref,
bool format_hex, bool auto_variable_summaries,
bool synthetic_child_debugging,
bool is_name_duplicated,
std::optional<std::string> custom_name) {
VariableDescription desc(v, auto_variable_summaries, format_hex,
is_name_duplicated, custom_name);
llvm::json::Object object;
EmplaceSafeString(object, "name", desc.name);
EmplaceSafeString(object, "value", desc.display_value);
if (!desc.evaluate_name.empty())
EmplaceSafeString(object, "evaluateName", desc.evaluate_name);
// If we have a type with many children, we would like to be able to
// give a hint to the IDE that the type has indexed children so that the
// request can be broken up in grabbing only a few children at a time. We
// want to be careful and only call "v.GetNumChildren()" if we have an array
// type or if we have a synthetic child provider producing indexed children.
// We don't want to call "v.GetNumChildren()" on all objects as class, struct
// and union types don't need to be completed if they are never expanded. So
// we want to avoid calling this to only cases where we it makes sense to keep
// performance high during normal debugging.
// If we have an array type, say that it is indexed and provide the number
// of children in case we have a huge array. If we don't do this, then we
// might take a while to produce all children at onces which can delay your
// debug session.
if (desc.type_obj.IsArrayType()) {
object.try_emplace("indexedVariables", v.GetNumChildren());
} else if (v.IsSynthetic()) {
// For a type with a synthetic child provider, the SBType of "v" won't tell
// us anything about what might be displayed. Instead, we check if the first
// child's name is "[0]" and then say it is indexed. We call
// GetNumChildren() only if the child name matches to avoid a potentially
// expensive operation.
if (lldb::SBValue first_child = v.GetChildAtIndex(0)) {
llvm::StringRef first_child_name = first_child.GetName();
if (first_child_name == "[0]") {
size_t num_children = v.GetNumChildren();
// If we are creating a "[raw]" fake child for each synthetic type, we
// have to account for it when returning indexed variables.
if (synthetic_child_debugging)
++num_children;
object.try_emplace("indexedVariables", num_children);
}
}
}
EmplaceSafeString(object, "type", desc.display_type_name);
// A unique variable identifier to help in properly identifying variables with
// the same name. This is an extension to the VS protocol.
object.try_emplace("id", var_ref);
if (v.MightHaveChildren())
object.try_emplace("variablesReference", var_ref);
else
object.try_emplace("variablesReference", 0);
if (v.GetDeclaration().IsValid())
object.try_emplace("declarationLocationReference",
PackLocation(var_ref, false));
if (ValuePointsToCode(v))
object.try_emplace("valueLocationReference", PackLocation(var_ref, true));
if (lldb::addr_t addr = v.GetLoadAddress(); addr != LLDB_INVALID_ADDRESS)
object.try_emplace("memoryReference", EncodeMemoryReference(addr));
object.try_emplace("$__lldb_extensions", desc.GetVariableExtensionsJSON());
return llvm::json::Value(std::move(object));
}
llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit &unit) {
llvm::json::Object object;
char unit_path_arr[PATH_MAX];

View File

@ -138,6 +138,30 @@ std::string EncodeMemoryReference(lldb::addr_t addr);
std::optional<lldb::addr_t>
DecodeMemoryReference(llvm::StringRef memoryReference);
/// Decodes a memory reference from the given json value.
///
/// \param[in] v
/// A JSON value that we expected to contain the memory reference.
///
/// \param[in] key
/// The key of the memory reference.
///
/// \param[out] out
/// The memory address, if successfully decoded.
///
/// \param[in] path
/// The path for reporting errors.
///
/// \param[in] required
/// Indicates if the key is required to be present, otherwise report an error
/// if the key is missing.
///
/// \return
/// Returns \b true if the address was decoded successfully.
bool DecodeMemoryReference(const llvm::json::Value &v, llvm::StringLiteral key,
lldb::addr_t &out, llvm::json::Path path,
bool required);
/// Extract an array of strings for the specified key from an object.
///
/// String values in the array will be extracted without any quotes
@ -326,10 +350,6 @@ struct VariableDescription {
bool format_hex = false, bool is_name_duplicated = false,
std::optional<std::string> custom_name = {});
/// Create a JSON object that represents these extensions to the DAP variable
/// response.
llvm::json::Object GetVariableExtensionsJSON();
/// Returns a description of the value appropriate for the specified context.
std::string GetResult(llvm::StringRef context);
};
@ -344,61 +364,6 @@ int64_t PackLocation(int64_t var_ref, bool is_value_location);
/// Reverse of `PackLocation`
std::pair<int64_t, bool> UnpackLocation(int64_t location_id);
/// Create a "Variable" object for a LLDB thread object.
///
/// This function will fill in the following keys in the returned
/// object:
/// "name" - the name of the variable
/// "value" - the value of the variable as a string
/// "type" - the typename of the variable as a string
/// "id" - a unique identifier for a value in case there are multiple
/// variables with the same name. Other parts of the DAP
/// protocol refer to values by name so this can help
/// disambiguate such cases if a IDE passes this "id" value
/// back down.
/// "variablesReference" - Zero if the variable has no children,
/// non-zero integer otherwise which can be used to expand
/// the variable.
/// "evaluateName" - The name of the variable to use in expressions
/// as a string.
///
/// \param[in] v
/// The LLDB value to use when populating out the "Variable"
/// object.
///
/// \param[in] var_ref
/// The variable reference. Used to identify the value, e.g.
/// in the `variablesReference` or `declarationLocationReference`
/// properties.
///
/// \param[in] format_hex
/// If set to true the variable will be formatted as hex in
/// the "value" key value pair for the value of the variable.
///
/// \param[in] auto_variable_summaries
/// IF set to true the variable will create an automatic variable summary.
///
/// \param[in] is_name_duplicated
/// Whether the same variable name appears multiple times within the same
/// context (e.g. locals). This can happen due to shadowed variables in
/// nested blocks.
///
/// As VSCode doesn't render two of more variables with the same name, we
/// apply a suffix to distinguish duplicated variables.
///
/// \param[in] custom_name
/// A provided custom name that is used instead of the SBValue's when
/// creating the JSON representation.
///
/// \return
/// A "Variable" JSON object with that follows the formal JSON
/// definition outlined by Microsoft.
llvm::json::Value CreateVariable(lldb::SBValue v, int64_t var_ref,
bool format_hex, bool auto_variable_summaries,
bool synthetic_child_debugging,
bool is_name_duplicated = false,
std::optional<std::string> custom_name = {});
llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit &unit);
/// Create a runInTerminal reverse request object

View File

@ -531,6 +531,41 @@ json::Value toJSON(const ModulesResponseBody &MR) {
return result;
}
bool fromJSON(const json::Value &Param, VariablesArguments::VariablesFilter &VA,
json::Path Path) {
auto rawFilter = Param.getAsString();
if (!rawFilter) {
Path.report("expected a string");
return false;
}
std::optional<VariablesArguments::VariablesFilter> filter =
StringSwitch<std::optional<VariablesArguments::VariablesFilter>>(
*rawFilter)
.Case("indexed", VariablesArguments::eVariablesFilterIndexed)
.Case("named", VariablesArguments::eVariablesFilterNamed)
.Default(std::nullopt);
if (!filter) {
Path.report("unexpected value, expected 'named' or 'indexed'");
return false;
}
VA = *filter;
return true;
}
bool fromJSON(const json::Value &Param, VariablesArguments &VA,
json::Path Path) {
json::ObjectMapper O(Param, Path);
return O && O.map("variablesReference", VA.variablesReference) &&
O.mapOptional("filter", VA.filter) &&
O.mapOptional("start", VA.start) && O.mapOptional("count", VA.count) &&
O.mapOptional("format", VA.format);
}
json::Value toJSON(const VariablesResponseBody &VRB) {
return json::Object{{"variables", VRB.variables}};
}
bool fromJSON(const json::Value &Params, WriteMemoryArguments &WMA,
json::Path P) {
json::ObjectMapper O(Params, P);

View File

@ -896,6 +896,54 @@ struct ModulesResponseBody {
};
llvm::json::Value toJSON(const ModulesResponseBody &);
/// Arguments for `variables` request.
struct VariablesArguments {
/// The variable for which to retrieve its children. The `variablesReference`
/// must have been obtained in the current suspended state. See 'Lifetime of
/// Object References' in the Overview section for details.
uint64_t variablesReference;
enum VariablesFilter : unsigned {
eVariablesFilterBoth = 0,
eVariablesFilterIndexed = 1 << 0,
eVariablesFilterNamed = 1 << 1,
};
/// Filter to limit the child variables to either named or indexed. If
/// omitted, both types are fetched.
VariablesFilter filter = eVariablesFilterBoth;
/// The index of the first variable to return; if omitted children start at 0.
///
/// The attribute is only honored by a debug adapter if the corresponding
/// capability `supportsVariablePaging` is true.
uint64_t start = 0;
/// The number of variables to return. If count is missing or 0, all variables
/// are returned.
///
/// The attribute is only honored by a debug adapter if the corresponding
/// capability `supportsVariablePaging` is true.
uint64_t count = 0;
/// Specifies details on how to format the Variable values.
///
/// The attribute is only honored by a debug adapter if the corresponding
/// capability `supportsValueFormattingOptions` is true.
std::optional<ValueFormat> format;
};
bool fromJSON(const llvm::json::Value &Param,
VariablesArguments::VariablesFilter &VA, llvm::json::Path Path);
bool fromJSON(const llvm::json::Value &, VariablesArguments &,
llvm::json::Path);
/// Response to `variables` request.
struct VariablesResponseBody {
/// All (or a range) of variables for the given variable reference.
std::vector<Variable> variables;
};
llvm::json::Value toJSON(const VariablesResponseBody &);
/// Arguments for `writeMemory` request.
struct WriteMemoryArguments {
/// Memory reference to the base location to which data should be written.

View File

@ -9,6 +9,7 @@
#include "Protocol/ProtocolTypes.h"
#include "JSONUtils.h"
#include "ProtocolUtils.h"
#include "lldb/lldb-defines.h"
#include "lldb/lldb-types.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
@ -953,4 +954,71 @@ json::Value toJSON(const Module &M) {
return result;
}
json::Value toJSON(const VariablePresentationHint &VPH) {
json::Object result{};
if (!VPH.kind.empty())
result.insert({"kind", VPH.kind});
if (!VPH.attributes.empty())
result.insert({"attributes", VPH.attributes});
if (!VPH.visibility.empty())
result.insert({"visibility", VPH.visibility});
if (VPH.lazy)
result.insert({"lazy", VPH.lazy});
return result;
}
bool fromJSON(const json::Value &Param, VariablePresentationHint &VPH,
json::Path Path) {
json::ObjectMapper O(Param, Path);
return O && O.mapOptional("kind", VPH.kind) &&
O.mapOptional("attributes", VPH.attributes) &&
O.mapOptional("visibility", VPH.visibility) &&
O.mapOptional("lazy", VPH.lazy);
}
json::Value toJSON(const Variable &V) {
json::Object result{{"name", V.name},
{"variablesReference", V.variablesReference},
{"value", V.value}};
if (!V.type.empty())
result.insert({"type", V.type});
if (V.presentationHint)
result.insert({"presentationHint", *V.presentationHint});
if (!V.evaluateName.empty())
result.insert({"evaluateName", V.evaluateName});
if (V.namedVariables)
result.insert({"namedVariables", V.namedVariables});
if (V.indexedVariables)
result.insert({"indexedVariables", V.indexedVariables});
if (V.memoryReference != LLDB_INVALID_ADDRESS)
result.insert(
{"memoryReference", EncodeMemoryReference(V.memoryReference)});
if (V.declarationLocationReference)
result.insert(
{"declarationLocationReference", V.declarationLocationReference});
if (V.valueLocationReference)
result.insert({"valueLocationReference", V.valueLocationReference});
return result;
}
bool fromJSON(const json::Value &Param, Variable &V, json::Path Path) {
json::ObjectMapper O(Param, Path);
return O && O.map("name", V.name) &&
O.map("variablesReference", V.variablesReference) &&
O.map("value", V.value) && O.mapOptional("type", V.type) &&
O.mapOptional("presentationHint", *V.presentationHint) &&
O.mapOptional("evaluateName", V.evaluateName) &&
O.mapOptional("namedVariables", V.namedVariables) &&
O.mapOptional("indexedVariables", V.indexedVariables) &&
O.mapOptional("declarationLocationReference",
V.declarationLocationReference) &&
O.mapOptional("valueLocationReference", V.valueLocationReference) &&
DecodeMemoryReference(Param, "memoryReference", V.memoryReference,
Path, /*required=*/false);
}
} // namespace lldb_dap::protocol

View File

@ -475,7 +475,7 @@ llvm::json::Value toJSON(const Thread &);
/// Provides formatting information for a value.
struct ValueFormat {
/// Display the value in hex.
std::optional<bool> hex;
bool hex = false;
};
bool fromJSON(const llvm::json::Value &, ValueFormat &, llvm::json::Path);
@ -789,6 +789,137 @@ struct Module {
};
llvm::json::Value toJSON(const Module &);
/// Properties of a variable that can be used to determine how to render the
/// variable in the UI.
struct VariablePresentationHint {
/// The kind of variable. Before introducing additional values, try to use the
/// listed values.
std::string kind;
/// Set of attributes represented as an array of strings. Before introducing
/// additional values, try to use the listed values.
std::vector<std::string> attributes;
/// Visibility of variable. Before introducing additional values, try to use
/// the listed values.
std::string visibility;
/// If true, clients can present the variable with a UI that supports a
/// specific gesture to trigger its evaluation.
///
/// This mechanism can be used for properties that require executing code when
/// retrieving their value and where the code execution can be expensive
/// and/or produce side-effects. A typical example are properties based on a
/// getter function.
///
/// Please note that in addition to the `lazy` flag, the variable's
/// `variablesReference` is expected to refer to a variable that will provide
/// the value through another `variable` request.
bool lazy = false;
};
llvm::json::Value toJSON(const VariablePresentationHint &);
bool fromJSON(const llvm::json::Value &, VariablePresentationHint &,
llvm::json::Path);
/// A Variable is a name/value pair.
///
/// The `type` attribute is shown if space permits or when hovering over the
/// variable's name.
///
/// The `kind` attribute 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 `variables` request.
///
/// If the number of named or indexed children is large, the numbers should be
/// returned via the `namedVariables` and `indexedVariables` attributes.
///
/// The client can use this information to present the children in a paged UI
/// and fetch them in chunks.
struct Variable {
/// The variable's name.
std::string name;
/// The variable's value.
///
/// This can be a multi-line text, e.g. for a function the body of a function.
///
/// For structured variables (which do not have a simple value), it is
/// recommended to provide a one-line representation of the structured object.
/// This helps to identify the structured object in the collapsed state when
/// its children are not yet visible.
///
/// An empty string can be used if no value should be shown in the UI.
std::string value;
/// The type of the variable's value. Typically shown in the UI when hovering
/// over the value.
///
/// This attribute should only be returned by a debug adapter if the
/// corresponding capability `supportsVariableType` is true.
std::string type;
/// Properties of a variable that can be used to determine how to render the
/// variable in the UI.
std::optional<VariablePresentationHint> presentationHint;
/// The evaluatable name of this variable which can be passed to the
/// `evaluate` request to fetch the variable's value.
std::string evaluateName;
/// If `variablesReference` is > 0, the variable is structured and its
/// children can be retrieved by passing `variablesReference` to the
/// `variables` request as long as execution remains suspended. See 'Lifetime
/// of Object References' in the Overview section for details.
uint64_t variablesReference = 0;
/// The number of named child variables.
///
/// The client can use this information to present the children in a paged UI
/// and fetch them in chunks.
uint64_t namedVariables = 0;
/// The number of indexed child variables.
///
/// The client can use this information to present the children in a paged UI
/// and fetch them in chunks.
uint64_t indexedVariables = 0;
/// A memory reference associated with this variable.
///
/// For pointer type variables, this is generally a reference to the memory
/// address contained in the pointer.
///
/// For executable data, this reference may later be used in a `disassemble`
/// request.
///
/// This attribute may be returned by a debug adapter if corresponding
/// capability `supportsMemoryReferences` is true.
lldb::addr_t memoryReference = LLDB_INVALID_ADDRESS;
/// A reference that allows the client to request the location where the
/// variable is declared. This should be present only if the adapter is likely
/// to be able to resolve the location.
///
/// This reference shares the same lifetime as the `variablesReference`. See
/// 'Lifetime of Object References' in the Overview section for details.
uint64_t declarationLocationReference = 0;
/// A reference that allows the client to request the location where the
/// variable's value is declared. For example, if the variable contains a
/// function pointer, the adapter may be able to look up the function's
/// location. This should be present only if the adapter is likely to be able
/// to resolve the location.
///
/// This reference shares the same lifetime as the `variablesReference`. See
/// 'Lifetime of Object References' in the Overview section for details.
uint64_t valueLocationReference = 0;
};
llvm::json::Value toJSON(const Variable &);
bool fromJSON(const llvm::json::Value &, Variable &, llvm::json::Path);
} // namespace lldb_dap::protocol
#endif

View File

@ -7,9 +7,11 @@
//===----------------------------------------------------------------------===//
#include "ProtocolUtils.h"
#include "JSONUtils.h"
#include "LLDBUtils.h"
#include "lldb/API/SBDebugger.h"
#include "lldb/API/SBDeclaration.h"
#include "lldb/API/SBFormat.h"
#include "lldb/API/SBMutex.h"
#include "lldb/API/SBStream.h"
@ -227,9 +229,9 @@ std::vector<protocol::Thread> GetThreads(lldb::SBProcess process,
return threads;
}
protocol::ExceptionBreakpointsFilter
ExceptionBreakpointsFilter
CreateExceptionBreakpointFilter(const ExceptionBreakpoint &bp) {
protocol::ExceptionBreakpointsFilter filter;
ExceptionBreakpointsFilter filter;
filter.filter = bp.GetFilter();
filter.label = bp.GetLabel();
filter.description = bp.GetLabel();
@ -238,4 +240,68 @@ CreateExceptionBreakpointFilter(const ExceptionBreakpoint &bp) {
return filter;
}
Variable CreateVariable(lldb::SBValue v, int64_t var_ref, bool format_hex,
bool auto_variable_summaries,
bool synthetic_child_debugging, bool is_name_duplicated,
std::optional<std::string> custom_name) {
VariableDescription desc(v, auto_variable_summaries, format_hex,
is_name_duplicated, custom_name);
Variable var;
var.name = desc.name;
var.value = desc.display_value;
var.type = desc.display_type_name;
if (!desc.evaluate_name.empty())
var.evaluateName = desc.evaluate_name;
// If we have a type with many children, we would like to be able to
// give a hint to the IDE that the type has indexed children so that the
// request can be broken up in grabbing only a few children at a time. We
// want to be careful and only call "v.GetNumChildren()" if we have an array
// type or if we have a synthetic child provider producing indexed children.
// We don't want to call "v.GetNumChildren()" on all objects as class, struct
// and union types don't need to be completed if they are never expanded. So
// we want to avoid calling this to only cases where we it makes sense to keep
// performance high during normal debugging.
// If we have an array type, say that it is indexed and provide the number
// of children in case we have a huge array. If we don't do this, then we
// might take a while to produce all children at onces which can delay your
// debug session.
if (desc.type_obj.IsArrayType()) {
var.indexedVariables = v.GetNumChildren();
} else if (v.IsSynthetic()) {
// For a type with a synthetic child provider, the SBType of "v" won't tell
// us anything about what might be displayed. Instead, we check if the first
// child's name is "[0]" and then say it is indexed. We call
// GetNumChildren() only if the child name matches to avoid a potentially
// expensive operation.
if (lldb::SBValue first_child = v.GetChildAtIndex(0)) {
llvm::StringRef first_child_name = first_child.GetName();
if (first_child_name == "[0]") {
size_t num_children = v.GetNumChildren();
// If we are creating a "[raw]" fake child for each synthetic type, we
// have to account for it when returning indexed variables.
if (synthetic_child_debugging)
++num_children;
var.indexedVariables = num_children;
}
}
}
if (v.MightHaveChildren())
var.variablesReference = var_ref;
if (v.GetDeclaration().IsValid())
var.declarationLocationReference = PackLocation(var_ref, false);
if (ValuePointsToCode(v))
var.valueLocationReference = PackLocation(var_ref, true);
if (lldb::addr_t addr = v.GetLoadAddress(); addr != LLDB_INVALID_ADDRESS)
var.memoryReference = addr;
return var;
}
} // namespace lldb_dap

View File

@ -106,6 +106,48 @@ CreateExceptionBreakpointFilter(const ExceptionBreakpoint &bp);
/// "2 MB").
std::string ConvertDebugInfoSizeToString(uint64_t debug_size);
/// Create a protocol Variable for the given value.
///
/// \param[in] v
/// The LLDB value to use when populating out the "Variable"
/// object.
///
/// \param[in] var_ref
/// The variable reference. Used to identify the value, e.g.
/// in the `variablesReference` or `declarationLocationReference`
/// properties.
///
/// \param[in] format_hex
/// If set to true the variable will be formatted as hex in
/// the "value" key value pair for the value of the variable.
///
/// \param[in] auto_variable_summaries
/// If set to true the variable will create an automatic variable summary.
///
/// \param[in] synthetic_child_debugging
/// Whether to include synthetic children when listing properties of the
/// value.
///
/// \param[in] is_name_duplicated
/// Whether the same variable name appears multiple times within the same
/// context (e.g. locals). This can happen due to shadowed variables in
/// nested blocks.
///
/// As VSCode doesn't render two of more variables with the same name, we
/// apply a suffix to distinguish duplicated variables.
///
/// \param[in] custom_name
/// A provided custom name that is used instead of the SBValue's when
/// creating the JSON representation.
///
/// \return
/// A Variable representing the given value.
protocol::Variable CreateVariable(lldb::SBValue v, int64_t var_ref,
bool format_hex, bool auto_variable_summaries,
bool synthetic_child_debugging,
bool is_name_duplicated,
std::optional<std::string> custom_name = {});
} // namespace lldb_dap
#endif

View File

@ -7,9 +7,9 @@
//===----------------------------------------------------------------------===//
#include "JSONUtils.h"
#include "lldb/API/SBModule.h"
#include "lldb/API/SBTarget.h"
#include "lldb/lldb-defines.h"
#include "llvm/Support/JSON.h"
#include "llvm/Testing/Support/Error.h"
#include "gtest/gtest.h"
#include <optional>
@ -182,3 +182,66 @@ TEST(JSONUtilsTest, GetStrings_NestedArray) {
ASSERT_EQ(result.size(), 1UL);
EXPECT_EQ(result[0], "string");
}
TEST(JSONUtilsTest, DecodeMemoryReference) {
EXPECT_EQ(DecodeMemoryReference(""), std::nullopt);
EXPECT_EQ(DecodeMemoryReference("123"), std::nullopt);
EXPECT_EQ(DecodeMemoryReference("0o123"), std::nullopt);
EXPECT_EQ(DecodeMemoryReference("0b1010101"), std::nullopt);
EXPECT_EQ(DecodeMemoryReference("0x123"), 291u);
{
addr_t addr = LLDB_INVALID_ADDRESS;
json::Path::Root root;
EXPECT_TRUE(DecodeMemoryReference(json::Object{{"mem_ref", "0x123"}},
"mem_ref", addr, root,
/*required=*/true));
EXPECT_EQ(addr, 291u);
}
{
addr_t addr = LLDB_INVALID_ADDRESS;
json::Path::Root root;
EXPECT_TRUE(DecodeMemoryReference(json::Object{}, "mem_ref", addr, root,
/*required=*/false));
}
{
addr_t addr = LLDB_INVALID_ADDRESS;
json::Path::Root root;
EXPECT_FALSE(DecodeMemoryReference(json::Value{"string"}, "mem_ref", addr,
root,
/*required=*/true));
EXPECT_THAT_ERROR(root.getError(), FailedWithMessage("expected object"));
}
{
addr_t addr = LLDB_INVALID_ADDRESS;
json::Path::Root root;
EXPECT_FALSE(DecodeMemoryReference(json::Object{}, "mem_ref", addr, root,
/*required=*/true));
EXPECT_THAT_ERROR(root.getError(),
FailedWithMessage("missing value at (root).mem_ref"));
}
{
addr_t addr = LLDB_INVALID_ADDRESS;
json::Path::Root root;
EXPECT_FALSE(DecodeMemoryReference(json::Object{{"mem_ref", 123}},
"mem_ref", addr, root,
/*required=*/true));
EXPECT_THAT_ERROR(root.getError(),
FailedWithMessage("expected string at (root).mem_ref"));
}
{
addr_t addr = LLDB_INVALID_ADDRESS;
json::Path::Root root;
EXPECT_FALSE(DecodeMemoryReference(json::Object{{"mem_ref", "123"}},
"mem_ref", addr, root,
/*required=*/true));
EXPECT_THAT_ERROR(
root.getError(),
FailedWithMessage("malformed memory reference at (root).mem_ref"));
}
}

View File

@ -883,3 +883,120 @@ TEST(ProtocolTypesTest, ModulesResponseBody) {
ASSERT_THAT_EXPECTED(expected, llvm::Succeeded());
EXPECT_EQ(pp(*expected), pp(response));
}
TEST(ProtocolTypesTest, VariablePresentationHint) {
VariablePresentationHint hint;
hint.kind = "kind";
hint.attributes = {"a", "b", "c"};
hint.visibility = "public";
hint.lazy = true;
const StringRef json = R"({
"attributes": [
"a",
"b",
"c"
],
"kind": "kind",
"lazy": true,
"visibility": "public"
})";
EXPECT_EQ(pp(Value(hint)), json);
EXPECT_THAT_EXPECTED(json::parse(json), HasValue(Value(hint)));
}
TEST(ProtocolTypesTest, Variable) {
Variable var;
var.name = "var1";
var.variablesReference = 42;
var.value = "value";
var.type = "type";
VariablePresentationHint hint;
hint.kind = "kind";
var.presentationHint = std::move(hint);
var.evaluateName = "my_name";
var.namedVariables = 7;
var.indexedVariables = 7;
var.memoryReference = 291u;
var.declarationLocationReference = 24;
var.valueLocationReference = 100;
const StringRef json = R"({
"declarationLocationReference": 24,
"evaluateName": "my_name",
"indexedVariables": 7,
"memoryReference": "0x123",
"name": "var1",
"namedVariables": 7,
"presentationHint": {
"kind": "kind"
},
"type": "type",
"value": "value",
"valueLocationReference": 100,
"variablesReference": 42
})";
EXPECT_EQ(pp(Value(var)), json);
EXPECT_THAT_EXPECTED(json::parse(json), HasValue(Value(var)));
}
TEST(ProtocolTypesTest, VariablesArguments) {
llvm::Expected<VariablesArguments> expected = parse<VariablesArguments>(R"({
"variablesReference": 42,
"filter": "indexed",
"start": 10,
"count": 5,
"format": {
"hex": true
}
})");
ASSERT_THAT_EXPECTED(expected, llvm::Succeeded());
EXPECT_EQ(expected->variablesReference, 42u);
EXPECT_EQ(expected->filter, VariablesArguments::eVariablesFilterIndexed);
EXPECT_EQ(expected->start, 10u);
EXPECT_EQ(expected->count, 5u);
EXPECT_EQ(expected->format->hex, true);
EXPECT_THAT_EXPECTED(
parse<VariablesArguments>(R"({})"),
FailedWithMessage("missing value at (root).variablesReference"));
EXPECT_THAT_EXPECTED(
parse<VariablesArguments>(
R"({"variablesReference": 42, "filter": "my-filter"})"),
FailedWithMessage(
"unexpected value, expected 'named' or 'indexed' at (root).filter"));
}
TEST(ProtocolTypesTest, VariablesResponseBody) {
Variable var1;
var1.name = "var1";
var1.variablesReference = 42;
var1.value = "<var1-value>";
Variable var2;
var2.name = "var2";
var2.variablesReference = 3;
var2.value = "<var2-value>";
VariablesResponseBody response{{var1, var2}};
Expected<json::Value> expected = json::parse(R"({
"variables": [
{
"name": "var1",
"value": "<var1-value>",
"variablesReference": 42
},
{
"name": "var2",
"value": "<var2-value>",
"variablesReference": 3
}
]
})");
ASSERT_THAT_EXPECTED(expected, llvm::Succeeded());
EXPECT_EQ(pp(*expected), pp(response));
}