[Lldb-commits] [lldb] [lldb-dap] Migrate variables request protocol types. (PR #147611)
John Harrison via lldb-commits
lldb-commits at lists.llvm.org
Thu Jul 10 09:58:34 PDT 2025
https://github.com/ashgti updated https://github.com/llvm/llvm-project/pull/147611
>From f39b3a47a8b5abadad3709f78ac4aaafc9d9878d Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Tue, 8 Jul 2025 15:44:57 -0700
Subject: [PATCH 1/6] [lldb-dap] Migrate variables request protocol types.
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.
---
.../lldb-dap/optimized/TestDAP_optimized.py | 7 +-
.../lldb-dap/variables/TestDAP_variables.py | 17 +-
lldb/tools/lldb-dap/Handler/RequestHandler.h | 9 +-
.../Handler/VariablesRequestHandler.cpp | 136 +++-------
lldb/tools/lldb-dap/JSONUtils.cpp | 252 ------------------
lldb/tools/lldb-dap/JSONUtils.h | 59 ----
.../lldb-dap/Protocol/ProtocolRequests.cpp | 35 +++
.../lldb-dap/Protocol/ProtocolRequests.h | 48 ++++
.../tools/lldb-dap/Protocol/ProtocolTypes.cpp | 65 +++++
lldb/tools/lldb-dap/Protocol/ProtocolTypes.h | 131 +++++++++
lldb/tools/lldb-dap/ProtocolUtils.cpp | 70 ++++-
lldb/tools/lldb-dap/ProtocolUtils.h | 42 +++
12 files changed, 430 insertions(+), 441 deletions(-)
diff --git a/lldb/test/API/tools/lldb-dap/optimized/TestDAP_optimized.py b/lldb/test/API/tools/lldb-dap/optimized/TestDAP_optimized.py
index 9cfa9b20f6051..3b769d2dd89ce 100644
--- a/lldb/test/API/tools/lldb-dap/optimized/TestDAP_optimized.py
+++ b/lldb/test/API/tools/lldb-dap/optimized/TestDAP_optimized.py
@@ -28,7 +28,7 @@ def test_stack_frame_name(self):
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 @@ def test_optimized_variable(self):
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()
diff --git a/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py b/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py
index 340be0b39010d..69d4eab0b6fcd 100644
--- a/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py
+++ b/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py
@@ -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 @@ def do_test_scopes_variables_setVariable_evaluate(
"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 @@ def do_test_scopes_variables_setVariable_evaluate(
},
"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"}},
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h
index 3b910abe81992..2d141a2704112 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.h
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h
@@ -540,11 +540,14 @@ class ThreadsRequestHandler
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 {
diff --git a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
index 19bcca2b22b9b..f80a69a5f5ee5 100644
--- a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
@@ -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 {
+ uint64_t var_ref = arguments.variablesReference;
+ uint64_t count = arguments.count;
+ 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.value_or(false);
+
+ std::vector<Variable> variables;
- if (lldb::SBValueList *top_scope =
- dap.variables.GetTopLevelScope(variablesReference)) {
+ 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();
@@ -204,14 +133,13 @@ void VariablesRequestHandler::operator()(
} 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()) {
auto addChild = [&](lldb::SBValue child,
std::optional<std::string> custom_name = {}) {
if (!child.IsValid())
return;
- bool is_permanent =
- dap.variables.IsPermanentVariableReference(variablesReference);
+ bool is_permanent = dap.variables.IsPermanentVariableReference(var_ref);
int64_t var_ref = dap.variables.InsertVariable(child, is_permanent);
variables.emplace_back(CreateVariable(
child, var_ref, hex, dap.configuration.enableAutoVariableSummaries,
@@ -233,10 +161,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
diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp
index 553c52605c998..311e3fb9198ae 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -768,38 +768,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 +804,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];
diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h
index 0424438ad5b72..1556872c6ae62 100644
--- a/lldb/tools/lldb-dap/JSONUtils.h
+++ b/lldb/tools/lldb-dap/JSONUtils.h
@@ -326,10 +326,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 +340,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
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
index 124dbace97dd6..008cfcebd58d4 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
@@ -531,4 +531,39 @@ 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}};
+}
+
} // namespace lldb_dap::protocol
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
index 26eb3cb396ad1..8ca453d263de9 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
@@ -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 &);
+
} // namespace lldb_dap::protocol
#endif
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp
index 9b5c9ef348ca4..8922d4b3e537e 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp
@@ -953,4 +953,69 @@ 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.empty())
+ result.insert({"memoryReference", 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("memoryReference", V.memoryReference) &&
+ O.mapOptional("declarationLocationReference",
+ V.declarationLocationReference) &&
+ O.mapOptional("valueLocationReference", V.valueLocationReference);
+}
+
} // namespace lldb_dap::protocol
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
index 2bb765e956256..b494646f9c9b9 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
@@ -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.
+ std::string memoryReference;
+
+ /// 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
diff --git a/lldb/tools/lldb-dap/ProtocolUtils.cpp b/lldb/tools/lldb-dap/ProtocolUtils.cpp
index f9e373db74618..e2e8f3c2df160 100644
--- a/lldb/tools/lldb-dap/ProtocolUtils.cpp
+++ b/lldb/tools/lldb-dap/ProtocolUtils.cpp
@@ -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 = EncodeMemoryReference(addr);
+
+ return var;
+}
+
} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/ProtocolUtils.h b/lldb/tools/lldb-dap/ProtocolUtils.h
index d906d8e881158..a1f7ae0661914 100644
--- a/lldb/tools/lldb-dap/ProtocolUtils.h
+++ b/lldb/tools/lldb-dap/ProtocolUtils.h
@@ -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
>From 058f0bb2d0eb611dd344126f84bac08271becdb5 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Tue, 8 Jul 2025 17:22:24 -0700
Subject: [PATCH 2/6] Adding unit tests for new protocol types.
---
.../Handler/VariablesRequestHandler.cpp | 2 +-
lldb/tools/lldb-dap/Protocol/ProtocolTypes.h | 2 +-
lldb/unittests/DAP/ProtocolTypesTest.cpp | 117 ++++++++++++++++++
3 files changed, 119 insertions(+), 2 deletions(-)
diff --git a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
index f80a69a5f5ee5..78c66a2e0fb57 100644
--- a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
@@ -28,7 +28,7 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
uint64_t start = arguments.start;
bool hex = false;
if (arguments.format)
- hex = arguments.format->hex.value_or(false);
+ hex = arguments.format->hex;
std::vector<Variable> variables;
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
index b494646f9c9b9..2213b3e7cbee3 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
@@ -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);
diff --git a/lldb/unittests/DAP/ProtocolTypesTest.cpp b/lldb/unittests/DAP/ProtocolTypesTest.cpp
index b5cf06bd6f0b6..055e3c08d9577 100644
--- a/lldb/unittests/DAP/ProtocolTypesTest.cpp
+++ b/lldb/unittests/DAP/ProtocolTypesTest.cpp
@@ -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 = "0x123";
+ 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));
+}
>From 79ce05e6bcc015831334a88d4a8b7cc78d74fad9 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Wed, 9 Jul 2025 17:21:02 -0700
Subject: [PATCH 3/6] Adjusting a variable name to not shadow and making a few
variables 'const'.
---
.../Handler/VariablesRequestHandler.cpp | 20 ++++++++++---------
1 file changed, 11 insertions(+), 9 deletions(-)
diff --git a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
index 78c66a2e0fb57..eba1da827257b 100644
--- a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
@@ -23,9 +23,9 @@ namespace lldb_dap {
/// indexed children.
Expected<VariablesResponseBody>
VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
- uint64_t var_ref = arguments.variablesReference;
- uint64_t count = arguments.count;
- uint64_t start = arguments.start;
+ const uint64_t var_ref = arguments.variablesReference;
+ const uint64_t count = arguments.count;
+ const uint64_t start = arguments.start;
bool hex = false;
if (arguments.format)
hex = arguments.format->hex;
@@ -135,16 +135,18 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
// children.
lldb::SBValue variable = dap.variables.GetVariable(var_ref);
if (variable.IsValid()) {
+ 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(var_ref);
- 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));
+ 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);
>From 26d5c6fca63d062ffc504c6552fd5b5f35109d5d Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Thu, 10 Jul 2025 09:31:31 -0700
Subject: [PATCH 4/6] Updating Variable::memoryReference to be an lldb::addr_t
and creating a new helper for decoding memory references from JSON objects.
---
.../Handler/VariablesRequestHandler.cpp | 5 +-
lldb/tools/lldb-dap/JSONUtils.cpp | 36 ++++++++++++
lldb/tools/lldb-dap/JSONUtils.h | 24 ++++++++
.../tools/lldb-dap/Protocol/ProtocolTypes.cpp | 11 ++--
lldb/tools/lldb-dap/Protocol/ProtocolTypes.h | 2 +-
lldb/tools/lldb-dap/ProtocolUtils.cpp | 2 +-
lldb/unittests/DAP/JSONUtilsTest.cpp | 58 ++++++++++++++++++-
lldb/unittests/DAP/ProtocolTypesTest.cpp | 2 +-
8 files changed, 129 insertions(+), 11 deletions(-)
diff --git a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
index eba1da827257b..13cdc4e02cc92 100644
--- a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
@@ -123,10 +123,11 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
if (!variable.IsValid())
break;
- int64_t var_ref =
+ 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));
}
diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp
index 311e3fb9198ae..52679c9f0ab8c 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -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;
diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h
index 1556872c6ae62..14e6166dac05b 100644
--- a/lldb/tools/lldb-dap/JSONUtils.h
+++ b/lldb/tools/lldb-dap/JSONUtils.h
@@ -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
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp
index 8922d4b3e537e..785830c693104 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp
@@ -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"
@@ -992,8 +993,9 @@ json::Value toJSON(const Variable &V) {
result.insert({"namedVariables", V.namedVariables});
if (V.indexedVariables)
result.insert({"indexedVariables", V.indexedVariables});
- if (!V.memoryReference.empty())
- result.insert({"memoryReference", V.memoryReference});
+ if (V.memoryReference != LLDB_INVALID_ADDRESS)
+ result.insert(
+ {"memoryReference", EncodeMemoryReference(V.memoryReference)});
if (V.declarationLocationReference)
result.insert(
{"declarationLocationReference", V.declarationLocationReference});
@@ -1012,10 +1014,11 @@ bool fromJSON(const json::Value &Param, Variable &V, json::Path Path) {
O.mapOptional("evaluateName", V.evaluateName) &&
O.mapOptional("namedVariables", V.namedVariables) &&
O.mapOptional("indexedVariables", V.indexedVariables) &&
- O.mapOptional("memoryReference", V.memoryReference) &&
O.mapOptional("declarationLocationReference",
V.declarationLocationReference) &&
- O.mapOptional("valueLocationReference", V.valueLocationReference);
+ O.mapOptional("valueLocationReference", V.valueLocationReference) &&
+ DecodeMemoryReference(Param, "memoryReference", V.memoryReference,
+ Path, /*required=*/false);
}
} // namespace lldb_dap::protocol
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
index 2213b3e7cbee3..89122c8f66307 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
@@ -897,7 +897,7 @@ struct Variable {
///
/// This attribute may be returned by a debug adapter if corresponding
/// capability `supportsMemoryReferences` is true.
- std::string memoryReference;
+ 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
diff --git a/lldb/tools/lldb-dap/ProtocolUtils.cpp b/lldb/tools/lldb-dap/ProtocolUtils.cpp
index e2e8f3c2df160..775c82fbb7716 100644
--- a/lldb/tools/lldb-dap/ProtocolUtils.cpp
+++ b/lldb/tools/lldb-dap/ProtocolUtils.cpp
@@ -299,7 +299,7 @@ Variable CreateVariable(lldb::SBValue v, int64_t var_ref, bool format_hex,
var.valueLocationReference = PackLocation(var_ref, true);
if (lldb::addr_t addr = v.GetLoadAddress(); addr != LLDB_INVALID_ADDRESS)
- var.memoryReference = EncodeMemoryReference(addr);
+ var.memoryReference = addr;
return var;
}
diff --git a/lldb/unittests/DAP/JSONUtilsTest.cpp b/lldb/unittests/DAP/JSONUtilsTest.cpp
index 876980eb4bf4a..bb44b60dc025e 100644
--- a/lldb/unittests/DAP/JSONUtilsTest.cpp
+++ b/lldb/unittests/DAP/JSONUtilsTest.cpp
@@ -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,57 @@ 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::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"));
+ }
+}
diff --git a/lldb/unittests/DAP/ProtocolTypesTest.cpp b/lldb/unittests/DAP/ProtocolTypesTest.cpp
index 055e3c08d9577..8add315f47036 100644
--- a/lldb/unittests/DAP/ProtocolTypesTest.cpp
+++ b/lldb/unittests/DAP/ProtocolTypesTest.cpp
@@ -919,7 +919,7 @@ TEST(ProtocolTypesTest, Variable) {
var.evaluateName = "my_name";
var.namedVariables = 7;
var.indexedVariables = 7;
- var.memoryReference = "0x123";
+ var.memoryReference = 291u;
var.declarationLocationReference = 24;
var.valueLocationReference = 100;
>From d50143160e31dde83ad8c1f55bf6799b502dcbc3 Mon Sep 17 00:00:00 2001
From: John Harrison <ash at greaterthaninfinity.com>
Date: Thu, 10 Jul 2025 09:51:00 -0700
Subject: [PATCH 5/6] Update
lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
Co-authored-by: Jonas Devlieghere <jonas at devlieghere.com>
---
lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
index 13cdc4e02cc92..02ecc3eb48620 100644
--- a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
@@ -141,7 +141,7 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
std::optional<std::string> custom_name = {}) {
if (!child.IsValid())
return;
- int64_t child_var_ref =
+ const int64_t child_var_ref =
dap.variables.InsertVariable(child, is_permanent);
variables.emplace_back(
CreateVariable(child, child_var_ref, hex,
>From 7e72ce3b68a37d319568a8545671097efeb90f7d Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Thu, 10 Jul 2025 09:58:05 -0700
Subject: [PATCH 6/6] Adding one more unit test and marking a few variables as
'const'.
---
lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp | 7 ++++---
lldb/unittests/DAP/JSONUtilsTest.cpp | 9 +++++++++
2 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
index 02ecc3eb48620..5fa2b1ef5e20d 100644
--- a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
@@ -123,7 +123,7 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
if (!variable.IsValid())
break;
- int64_t frame_var_ref =
+ const int64_t frame_var_ref =
dap.variables.InsertVariable(variable, /*is_permanent=*/false);
variables.emplace_back(CreateVariable(
variable, frame_var_ref, hex,
@@ -136,7 +136,8 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
// children.
lldb::SBValue variable = dap.variables.GetVariable(var_ref);
if (variable.IsValid()) {
- bool is_permanent = dap.variables.IsPermanentVariableReference(var_ref);
+ const bool is_permanent =
+ dap.variables.IsPermanentVariableReference(var_ref);
auto addChild = [&](lldb::SBValue child,
std::optional<std::string> custom_name = {}) {
if (!child.IsValid())
@@ -150,7 +151,7 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
/*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));
diff --git a/lldb/unittests/DAP/JSONUtilsTest.cpp b/lldb/unittests/DAP/JSONUtilsTest.cpp
index bb44b60dc025e..86ba2d171a2c0 100644
--- a/lldb/unittests/DAP/JSONUtilsTest.cpp
+++ b/lldb/unittests/DAP/JSONUtilsTest.cpp
@@ -206,6 +206,15 @@ TEST(JSONUtilsTest, DecodeMemoryReference) {
/*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;
More information about the lldb-commits
mailing list