[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