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

John Harrison via lldb-commits lldb-commits at lists.llvm.org
Tue Jul 8 15:48:01 PDT 2025


https://github.com/ashgti created https://github.com/llvm/llvm-project/pull/147611

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

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

>From 43ca23b7dd715863eb5e2dc7e205a4164cd38647 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] [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 e35b9830ab60f..c858912c0d812 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.h
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h
@@ -537,11 +537,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 e72d93ee34571..36a8fc40ed618 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -863,38 +863,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.
@@ -931,226 +899,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 fd9a06931ebff..f9093081fa2d6 100644
--- a/lldb/tools/lldb-dap/JSONUtils.h
+++ b/lldb/tools/lldb-dap/JSONUtils.h
@@ -344,10 +344,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);
 };
@@ -362,61 +358,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 9bd84a6c898f9..0703b050fe3c2 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
@@ -517,4 +517,39 @@ json::Value toJSON(const ReadMemoryResponseBody &RMR) {
   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 d4b816c72679b..77777fa0794ce 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
@@ -875,6 +875,54 @@ struct ReadMemoryResponseBody {
 };
 llvm::json::Value toJSON(const ReadMemoryResponseBody &);
 
+/// 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 f3635202175a7..c0ee522cc6fc7 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp
@@ -926,4 +926,69 @@ llvm::json::Value toJSON(const DisassembledInstruction &DI) {
   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 6adfe3b7211b1..974893deb4e48 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
@@ -743,6 +743,137 @@ bool fromJSON(const llvm::json::Value &, DisassembledInstruction &,
               llvm::json::Path);
 llvm::json::Value toJSON(const DisassembledInstruction &);
 
+/// 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 724d851107928..80beafd69a07d 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"
@@ -124,9 +126,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();
@@ -135,4 +137,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 f36bf0fb60a87..caef190cd51dd 100644
--- a/lldb/tools/lldb-dap/ProtocolUtils.h
+++ b/lldb/tools/lldb-dap/ProtocolUtils.h
@@ -76,6 +76,48 @@ std::vector<protocol::Thread> GetThreads(lldb::SBProcess process,
 protocol::ExceptionBreakpointsFilter
 CreateExceptionBreakpointFilter(const ExceptionBreakpoint &bp);
 
+/// 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



More information about the lldb-commits mailing list