[Lldb-commits] [lldb] [lldb-dap] Improve support for variables with anonymous fields and types (PR #186482)

John Harrison via lldb-commits lldb-commits at lists.llvm.org
Fri Mar 13 14:52:53 PDT 2026


https://github.com/ashgti updated https://github.com/llvm/llvm-project/pull/186482

>From 9a001983a1ed6b626dbd42e1bc84c8db09e21e1a Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Fri, 13 Mar 2026 10:53:21 -0700
Subject: [PATCH 1/2] [lldb-dap] Improve support for variables with anonymous
 fields and types.

This improves support for variables with anonymous fields and anonymous types.

* Changed the name of anonymous fields from '<null>' to '<anonymous>', which I think is more descriptive.
* Adjusts variables to not return an 'evaluateName' for anonymous fields.
* Adjusted '[raw]' values to be marked as 'internal' which deemphasizes them in the UI.

While working in this area, I also consolidated some helpers that are only used within Variables.cpp.
---
 .../lldb-dap/variables/TestDAP_variables.py   | 120 ++++++--
 .../API/tools/lldb-dap/variables/main.cpp     |  30 +-
 lldb/tools/lldb-dap/DAP.cpp                   |   2 +-
 .../Handler/EvaluateRequestHandler.cpp        |   4 +-
 .../Handler/SetVariableRequestHandler.cpp     |   4 +-
 .../Handler/VariablesRequestHandler.cpp       |   3 +-
 lldb/tools/lldb-dap/JSONUtils.cpp             |  10 +-
 lldb/tools/lldb-dap/ProtocolUtils.cpp         |  72 -----
 lldb/tools/lldb-dap/ProtocolUtils.h           |  42 ---
 lldb/tools/lldb-dap/Variables.cpp             | 268 +++++++++++-------
 lldb/tools/lldb-dap/Variables.h               |  43 ++-
 11 files changed, 323 insertions(+), 275 deletions(-)

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 6e0514a0019a4..ad7b33c1ed96d 100644
--- a/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py
+++ b/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py
@@ -351,9 +351,9 @@ def do_test_scopes_variables_setVariable_evaluate(
 
         verify_locals["argc"]["equals"]["value"] = "123"
         verify_locals["pt"]["children"]["x"]["equals"]["value"] = "111"
-        verify_locals["x @ main.cpp:23"] = {"equals": {"type": "int", "value": "89"}}
-        verify_locals["x @ main.cpp:25"] = {"equals": {"type": "int", "value": "42"}}
-        verify_locals["x @ main.cpp:27"] = {"equals": {"type": "int", "value": "72"}}
+        verify_locals["x @ main.cpp:25"] = {"equals": {"type": "int", "value": "89"}}
+        verify_locals["x @ main.cpp:27"] = {"equals": {"type": "int", "value": "42"}}
+        verify_locals["x @ main.cpp:29"] = {"equals": {"type": "int", "value": "72"}}
 
         self.verify_variables(verify_locals, self.dap_server.get_local_variables())
 
@@ -361,22 +361,22 @@ def do_test_scopes_variables_setVariable_evaluate(
         self.assertFalse(self.set_local("x2", 9)["success"])
         self.assertFalse(self.set_local("x @ main.cpp:0", 9)["success"])
 
-        self.assertTrue(self.set_local("x @ main.cpp:23", 19)["success"])
-        self.assertTrue(self.set_local("x @ main.cpp:25", 21)["success"])
-        self.assertTrue(self.set_local("x @ main.cpp:27", 23)["success"])
+        self.assertTrue(self.set_local("x @ main.cpp:25", 19)["success"])
+        self.assertTrue(self.set_local("x @ main.cpp:27", 21)["success"])
+        self.assertTrue(self.set_local("x @ main.cpp:29", 23)["success"])
 
         # The following should have no effect
-        self.assertFalse(self.set_local("x @ main.cpp:27", "invalid")["success"])
+        self.assertFalse(self.set_local("x @ main.cpp:29", "invalid")["success"])
 
-        verify_locals["x @ main.cpp:23"]["equals"]["value"] = "19"
-        verify_locals["x @ main.cpp:25"]["equals"]["value"] = "21"
-        verify_locals["x @ main.cpp:27"]["equals"]["value"] = "23"
+        verify_locals["x @ main.cpp:25"]["equals"]["value"] = "19"
+        verify_locals["x @ main.cpp:27"]["equals"]["value"] = "21"
+        verify_locals["x @ main.cpp:29"]["equals"]["value"] = "23"
 
         self.verify_variables(verify_locals, self.dap_server.get_local_variables())
 
         # The plain x variable shold refer to the innermost x
         self.assertTrue(self.set_local("x", 22)["success"])
-        verify_locals["x @ main.cpp:27"]["equals"]["value"] = "22"
+        verify_locals["x @ main.cpp:29"]["equals"]["value"] = "22"
 
         self.verify_variables(verify_locals, self.dap_server.get_local_variables())
 
@@ -393,9 +393,9 @@ def do_test_scopes_variables_setVariable_evaluate(
         names = [var["name"] for var in locals]
         # The first shadowed x shouldn't have a suffix anymore
         verify_locals["x"] = {"equals": {"type": "int", "value": "19"}}
-        self.assertNotIn("x @ main.cpp:23", names)
         self.assertNotIn("x @ main.cpp:25", names)
         self.assertNotIn("x @ main.cpp:27", names)
+        self.assertNotIn("x @ main.cpp:29", names)
 
         self.verify_variables(verify_locals, locals)
 
@@ -422,14 +422,18 @@ def do_test_scopes_and_evaluate_expansion(self, enableAutoVariableSummaries: boo
             program, enableAutoVariableSummaries=enableAutoVariableSummaries
         )
         source = "main.cpp"
-        breakpoint1_line = line_number(source, "// breakpoint 1")
-        lines = [breakpoint1_line]
-        # Set breakpoint in the thread function so we can step the threads
+        lines = [
+            line_number(source, "// breakpoint 1"),
+            line_number(source, "// breakpoint 3"),
+            line_number(source, "// breakpoint 6"),
+            line_number(source, "// breakpoint 7"),
+        ]
         breakpoint_ids = self.set_source_breakpoints(source, lines)
         self.assertEqual(
             len(breakpoint_ids), len(lines), "expect correct number of breakpoints"
         )
-        self.continue_to_breakpoints(breakpoint_ids)
+        [bp1, bp3, bp6, bp7] = breakpoint_ids
+        self.continue_to_breakpoint(bp1)
 
         # Verify locals
         locals = self.dap_server.get_local_variables()
@@ -602,15 +606,85 @@ def do_test_scopes_and_evaluate_expansion(self, enableAutoVariableSummaries: boo
             expandable_expression["children"], response["body"]["variables"]
         )
 
+        self.continue_to_breakpoint(bp6)
+
+        locals = self.dap_server.get_local_variables()
+        self.verify_variables(
+            {
+                "my_var": {
+                    "equals": {
+                        "type": "(unnamed struct)",
+                        "value": "(unnamed struct)",
+                        "evaluateName": "my_var",
+                    },
+                    "readOnly": True,
+                },
+            },
+            locals,
+        )
+
+        self.continue_to_breakpoint(bp7)
+
+        expr_varref_dict = {}
+        locals = self.dap_server.get_local_variables()
+        self.verify_variables(
+            {
+                "home": {
+                    "equals": {
+                        "type": "MySock",
+                        "value": "MySock",
+                        "evaluateName": "home",
+                    },
+                    "readOnly": True,
+                },
+            },
+            locals,
+            expr_varref_dict,
+        )
+
+        inner_var_ref = expr_varref_dict["home"]
+        anon_vars = self.dap_server.request_variables(inner_var_ref)["body"][
+            "variables"
+        ]
+        self.verify_variables(
+            {
+                "<anonymous>": {
+                    "equals": {
+                        "type": "MySock::(anonymous union)",
+                        "value": "MySock::(anonymous union)",
+                    },
+                    "readOnly": True,
+                    "missing": ["evaluateName"],
+                }
+            },
+            anon_vars,
+        )
+        inner_union_vars = self.dap_server.request_variables(
+            anon_vars[0]["variablesReference"]
+        )["body"]["variables"]
+        self.verify_variables(
+            {
+                "ipv4": {
+                    "equals": {
+                        "type": "unsigned char[4]",
+                        "evaluateName": "home.ipv4",
+                    },
+                    "readOnly": True,
+                },
+                "ipv6": {
+                    "equals": {
+                        "type": "unsigned char[6]",
+                        "evaluateName": "home.ipv6",
+                    },
+                    "readOnly": True,
+                },
+            },
+            inner_union_vars,
+        )
+
         # Continue to breakpoint 3, permanent variable should still exist
         # after resume.
-        breakpoint3_line = line_number(source, "// breakpoint 3")
-        lines = [breakpoint3_line]
-        breakpoint_ids = self.set_source_breakpoints(source, lines)
-        self.assertEqual(
-            len(breakpoint_ids), len(lines), "expect correct number of breakpoints"
-        )
-        self.continue_to_breakpoints(breakpoint_ids)
+        self.continue_to_breakpoint(bp3)
 
         var_ref = permanent_expandable_ref
         response = self.dap_server.request_variables(var_ref)
diff --git a/lldb/test/API/tools/lldb-dap/variables/main.cpp b/lldb/test/API/tools/lldb-dap/variables/main.cpp
index 04fc62f02c22f..42b15af00665a 100644
--- a/lldb/test/API/tools/lldb-dap/variables/main.cpp
+++ b/lldb/test/API/tools/lldb-dap/variables/main.cpp
@@ -11,6 +11,8 @@ int g_global = 123;
 static int s_global = 234;
 int test_indexedVariables();
 int test_return_variable();
+int test_anonymous_types();
+int test_anonymous_fields();
 
 int main(int argc, char const *argv[]) {
   static float s_local = 2.25;
@@ -31,6 +33,8 @@ int main(int argc, char const *argv[]) {
   {
     int return_result = test_return_variable();
   }
+  test_anonymous_types();
+  test_anonymous_fields();
   return test_indexedVariables(); // breakpoint 3
 }
 
@@ -46,4 +50,28 @@ int test_indexedVariables() {
 
 int test_return_variable() {
   return 300; // breakpoint 5
-}
\ No newline at end of file
+}
+
+int test_anonymous_types() {
+  struct {
+    char name[16];
+    int x, y;
+  } my_var = {"hello world!", 42, 7};
+  return my_var.x + my_var.y; // breakpoint 6
+}
+
+struct MySock {
+  union {
+    unsigned char ipv4[4];
+    unsigned char ipv6[6];
+  };
+};
+
+int test_anonymous_fields() {
+  MySock home = {{{0}}};
+  home.ipv4[0] = 127;
+  home.ipv4[1] = 0;
+  home.ipv4[2] = 0;
+  home.ipv4[1] = 1;
+  return 1; // breakpoint 7
+}
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index f482fe5e7aa96..f1ebe9b316496 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -126,7 +126,7 @@ llvm::StringRef DAP::debug_adapter_path = "";
 DAP::DAP(Log &log, const ReplMode default_repl_mode,
          const std::vector<String> &pre_init_commands, bool no_lldbinit,
          llvm::StringRef client_name, DAPTransport &transport, MainLoop &loop)
-    : log(log), transport(transport), reference_storage(log),
+    : log(log), transport(transport), reference_storage(log, configuration),
       broadcaster("lldb-dap"),
       progress_event_reporter(
           [&](const ProgressEvent &event) { SendJSON(event.ToJSON()); }),
diff --git a/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp
index f2117d81f315c..906f262779e1d 100644
--- a/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp
@@ -133,8 +133,8 @@ EvaluateRequestHandler::Run(const EvaluateArguments &arguments) const {
   body.type = desc.display_type_name;
 
   if (value.MightHaveChildren() || ValuePointsToCode(value))
-    body.variablesReference =
-        dap.reference_storage.Insert(value, /*is_permanent=*/is_repl_context);
+    body.variablesReference = dap.reference_storage.Insert(
+        value, /*is_permanent=*/is_repl_context, /*is_internal=*/false);
 
   if (lldb::addr_t addr = value.GetLoadAddress(); addr != LLDB_INVALID_ADDRESS)
     body.memoryReference = EncodeMemoryReference(addr);
diff --git a/lldb/tools/lldb-dap/Handler/SetVariableRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/SetVariableRequestHandler.cpp
index a9c5fae8b18d3..352e84f868bd6 100644
--- a/lldb/tools/lldb-dap/Handler/SetVariableRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/SetVariableRequestHandler.cpp
@@ -66,8 +66,8 @@ SetVariableRequestHandler::Run(const SetVariableArguments &args) const {
   // so always insert a new one to get its variablesReference.
   // is_permanent is false because debug console does not support
   // setVariable request.
-  const var_ref_t new_var_ref =
-      dap.reference_storage.Insert(variable, /*is_permanent=*/false);
+  const var_ref_t new_var_ref = dap.reference_storage.Insert(
+      variable, /*is_permanent=*/false, /*is_internal=*/false);
   if (variable.MightHaveChildren()) {
     body.variablesReference = new_var_ref;
     if (desc.type_obj.IsArrayType())
diff --git a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
index 7918be4983cc0..841e0ec2e497e 100644
--- a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
@@ -37,8 +37,7 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
         llvm::formatv("invalid variablesReference: {}.", var_ref.AsUInt32()),
         /*error_code=*/llvm::inconvertibleErrorCode(), /*show_user=*/false);
 
-  Expected<std::vector<Variable>> variables =
-      store->GetVariables(dap.reference_storage, dap.configuration, arguments);
+  Expected<std::vector<Variable>> variables = store->GetVariables(arguments);
   if (llvm::Error err = variables.takeError())
     return err;
 
diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp
index d75b4e23d0e46..1fed48cbc1e57 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -244,7 +244,7 @@ llvm::json::Object CreateEventObject(const llvm::StringRef event_name) {
 
 llvm::StringRef GetNonNullVariableName(lldb::SBValue &v) {
   const llvm::StringRef name = v.GetName();
-  return !name.empty() ? name : "<null>";
+  return !name.empty() ? name : "<anonymous>";
 }
 
 std::string CreateUniqueVariableNameForDisplay(lldb::SBValue &v,
@@ -311,9 +311,11 @@ VariableDescription::VariableDescription(
     }
   }
 
-  lldb::SBStream evaluateStream;
-  val.GetExpressionPath(evaluateStream);
-  evaluate_name = llvm::StringRef(evaluateStream.GetData()).str();
+  if (!llvm::StringRef(val.GetName()).empty()) {
+    lldb::SBStream evaluateStream;
+    val.GetExpressionPath(evaluateStream);
+    evaluate_name = llvm::StringRef(evaluateStream.GetData()).str();
+  }
 }
 
 std::string VariableDescription::GetResult(protocol::EvaluateContext context) {
diff --git a/lldb/tools/lldb-dap/ProtocolUtils.cpp b/lldb/tools/lldb-dap/ProtocolUtils.cpp
index 99bd5abf0b756..d94a31f554e25 100644
--- a/lldb/tools/lldb-dap/ProtocolUtils.cpp
+++ b/lldb/tools/lldb-dap/ProtocolUtils.cpp
@@ -238,76 +238,4 @@ CreateExceptionBreakpointFilter(const ExceptionBreakpoint &bp) {
   return filter;
 }
 
-Variable CreateVariable(lldb::SBValue v, var_ref_t var_ref, bool format_hex,
-                        bool auto_variable_summaries,
-                        bool synthetic_child_debugging, bool is_name_duplicated,
-                        std::optional<llvm::StringRef> 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.AsUInt32(), false);
-
-  if (ValuePointsToCode(v))
-    var.valueLocationReference = PackLocation(var_ref.AsUInt32(), true);
-
-  if (lldb::addr_t addr = v.GetLoadAddress(); addr != LLDB_INVALID_ADDRESS)
-    var.memoryReference = addr;
-
-  bool is_readonly = v.GetType().IsAggregateType() ||
-                     v.GetValueType() == lldb::eValueTypeRegisterSet;
-  if (is_readonly) {
-    if (!var.presentationHint)
-      var.presentationHint = {VariablePresentationHint()};
-    var.presentationHint->attributes.push_back("readOnly");
-  }
-
-  return var;
-}
-
 } // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/ProtocolUtils.h b/lldb/tools/lldb-dap/ProtocolUtils.h
index 376a8d6b7c023..f3ce2f22ac1c2 100644
--- a/lldb/tools/lldb-dap/ProtocolUtils.h
+++ b/lldb/tools/lldb-dap/ProtocolUtils.h
@@ -107,48 +107,6 @@ 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, var_ref_t var_ref, bool format_hex,
-               bool auto_variable_summaries, bool synthetic_child_debugging,
-               bool is_name_duplicated,
-               std::optional<llvm::StringRef> custom_name = {});
-
 } // namespace lldb_dap
 
 #endif
diff --git a/lldb/tools/lldb-dap/Variables.cpp b/lldb/tools/lldb-dap/Variables.cpp
index 0053926aeeeae..2c32ac7f9853e 100644
--- a/lldb/tools/lldb-dap/Variables.cpp
+++ b/lldb/tools/lldb-dap/Variables.cpp
@@ -7,13 +7,11 @@
 //===----------------------------------------------------------------------===//
 
 #include "Variables.h"
-#include "DAPLog.h"
 #include "JSONUtils.h"
 #include "LLDBUtils.h"
 #include "Protocol/DAPTypes.h"
 #include "Protocol/ProtocolRequests.h"
 #include "Protocol/ProtocolTypes.h"
-#include "ProtocolUtils.h"
 #include "SBAPIExtras.h"
 #include "lldb/API/SBDeclaration.h"
 #include "lldb/API/SBFrame.h"
@@ -47,73 +45,167 @@ template <typename T> StringMap<uint32_t> DistinctNames(T &container) {
   return variable_name_counts;
 }
 
-template <typename T>
-std::vector<Variable> MakeVariables(
-    VariableReferenceStorage &storage, const Configuration &config,
-    const VariablesArguments &args, T &container, bool is_permanent,
-    const std::map<lldb::user_id_t, std::string> &name_overrides = {}) {
-  std::vector<Variable> variables;
+protocol::Scope CreateScope(ScopeKind kind, var_ref_t variablesReference,
+                            bool expensive) {
+  protocol::Scope scope;
+  scope.variablesReference = variablesReference;
+  scope.expensive = expensive;
 
-  // We first find out which variable names are duplicated.
-  StringMap<uint32_t> variable_name_counts = DistinctNames(container);
+  switch (kind) {
+  case eScopeKindLocals:
+    scope.presentationHint = protocol::Scope::eScopePresentationHintLocals;
+    scope.name = "Locals";
+    break;
+  case eScopeKindGlobals:
+    scope.name = "Globals";
+    break;
+  case eScopeKindRegisters:
+    scope.presentationHint = protocol::Scope::eScopePresentationHintRegisters;
+    scope.name = "Registers";
+    break;
+  }
 
-  const bool format_hex = args.format ? args.format->hex : false;
-  auto start_it = begin(container) + args.start;
-  auto end_it = args.count == 0 ? end(container) : start_it + args.count;
+  return scope;
+}
 
-  // Now we construct the result with unique display variable names.
-  for (; start_it != end_it; start_it++) {
-    lldb::SBValue variable = *start_it;
-    if (!variable.IsValid())
-      break;
+class VariableStoreImpl : public VariableStore {
+public:
+  using VariableStore::VariableStore;
+  Variable CreateVariable(lldb::SBValue v, bool format_hex,
+                          bool is_name_duplicated,
+                          std::optional<llvm::StringRef> custom_name) {
+    VariableDescription desc(v, m_storage.config.enableAutoVariableSummaries,
+                             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 (m_storage.config.enableSyntheticChildDebugging)
+            ++num_children;
+          var.indexedVariables = num_children;
+        }
+      }
+    }
+
+    const bool is_internal = var.name == "[raw]" || m_is_internal;
 
     const var_ref_t var_ref =
-        HasInnerVarref(variable)
-            ? storage.Insert(variable, /*is_permanent=*/is_permanent)
+        HasInnerVarref(v)
+            ? m_storage.Insert(v, /*is_permanent=*/m_is_permanent, is_internal)
             : var_ref_t(var_ref_t::k_no_child);
-    if (LLVM_UNLIKELY(var_ref.AsUInt32() >=
-                      var_ref_t::k_variables_reference_threshold)) {
-      DAP_LOG(storage.log,
-              "warning: variablesReference threshold reached. "
-              "current: {} threshold: {}, maximum {}.",
-              var_ref.AsUInt32(), var_ref_t::k_variables_reference_threshold,
-              var_ref_t::k_max_variables_references);
-      break;
-    }
 
-    if (LLVM_UNLIKELY(var_ref.Kind() == eReferenceKindInvalid))
-      break;
+    if (var.indexedVariables || v.MightHaveChildren())
+      var.variablesReference = var_ref;
+
+    if (v.GetDeclaration().IsValid())
+      var.declarationLocationReference =
+          PackLocation(var_ref.AsUInt32(), false);
 
-    std::optional<std::string> custom_name;
-    auto name_it = name_overrides.find(variable.GetID());
-    if (name_it != name_overrides.end())
-      custom_name = name_it->second;
+    if (ValuePointsToCode(v))
+      var.valueLocationReference = PackLocation(var_ref.AsUInt32(), true);
 
-    variables.emplace_back(CreateVariable(
-        variable, var_ref, format_hex, config.enableAutoVariableSummaries,
-        config.enableSyntheticChildDebugging,
-        variable_name_counts[GetNonNullVariableName(variable)] > 1,
-        custom_name));
+    if (lldb::addr_t addr = v.GetLoadAddress(); addr != LLDB_INVALID_ADDRESS)
+      var.memoryReference = addr;
+
+    bool is_readonly = is_internal || v.GetType().IsAggregateType() ||
+                       v.GetValueType() == lldb::eValueTypeRegisterSet;
+    if (is_readonly) {
+      if (!var.presentationHint)
+        var.presentationHint = {VariablePresentationHint()};
+      var.presentationHint->attributes.push_back("readOnly");
+    }
+
+    if (is_internal) {
+      if (!var.presentationHint)
+        var.presentationHint = {VariablePresentationHint()};
+      var.presentationHint->visibility = "internal";
+    }
+
+    return var;
   }
 
-  return variables;
-}
+  template <typename T>
+  std::vector<Variable> MakeVariables(
+      const VariablesArguments &args, T &container,
+      const std::map<lldb::user_id_t, std::string> &name_overrides = {}) {
+    std::vector<Variable> variables;
+
+    // We first find out which variable names are duplicated.
+    StringMap<uint32_t> variable_name_counts = DistinctNames(container);
+
+    const bool format_hex = args.format ? args.format->hex : false;
+    auto start_it = begin(container) + args.start;
+    auto end_it = args.count == 0 ? end(container) : start_it + args.count;
+
+    // Now we construct the result with unique display variable names.
+    for (; start_it != end_it; start_it++) {
+      lldb::SBValue variable = *start_it;
+      if (!variable.IsValid())
+        break;
+
+      std::optional<std::string> custom_name;
+      auto name_it = name_overrides.find(variable.GetID());
+      if (name_it != name_overrides.end())
+        custom_name = name_it->second;
+
+      variables.emplace_back(CreateVariable(
+          variable, format_hex,
+          variable_name_counts[GetNonNullVariableName(variable)] > 1,
+          custom_name));
+    }
+
+    return variables;
+  }
+};
 
 /// A Variable store for fetching variables within a specific scope (locals,
 /// globals, or registers) for a given stack frame.
-class ScopeStore final : public VariableStore {
+class ScopeStore final : public VariableStoreImpl {
 public:
-  explicit ScopeStore(ScopeKind kind, const lldb::SBFrame &frame)
-      : m_frame(frame), m_kind(kind) {}
+  explicit ScopeStore(VariableReferenceStorage &storage, ScopeKind kind,
+                      const lldb::SBFrame &frame)
+      : VariableStoreImpl(storage, /*is_permanent=*/false,
+                          /*is_internal=*/frame.IsArtificial()),
+        m_frame(frame), m_kind(kind) {}
 
   Expected<std::vector<Variable>>
-  GetVariables(VariableReferenceStorage &storage, const Configuration &config,
-               const VariablesArguments &args) override {
+  GetVariables(const VariablesArguments &args) override {
     LoadVariables();
     if (m_error.Fail())
       return ToError(m_error);
-    return MakeVariables(storage, config, args, m_children,
-                         /*is_permanent=*/false, m_names);
+    return MakeVariables(args, m_children, m_names);
   }
 
   lldb::SBValue FindVariable(llvm::StringRef name) override {
@@ -138,8 +230,6 @@ class ScopeStore final : public VariableStore {
     return variable;
   }
 
-  lldb::SBValue GetVariable() const override { return lldb::SBValue(); }
-
 private:
   void LoadVariables() {
     if (m_variables_loaded)
@@ -213,15 +303,16 @@ class ScopeStore final : public VariableStore {
 ///
 /// Manages children variables of complex types (structs, arrays, pointers,
 /// etc.) that can be expanded in the debugger UI.
-class ExpandableValueStore final : public VariableStore {
+class ExpandableValueStore final : public VariableStoreImpl {
 
 public:
-  explicit ExpandableValueStore(const lldb::SBValue &value) : m_value(value) {}
+  explicit ExpandableValueStore(VariableReferenceStorage &storage,
+                                bool is_permanent, bool is_internal,
+                                const lldb::SBValue &value)
+      : VariableStoreImpl(storage, is_permanent, is_internal), m_value(value) {}
 
   llvm::Expected<std::vector<protocol::Variable>>
-  GetVariables(VariableReferenceStorage &storage,
-               const protocol::Configuration &config,
-               const protocol::VariablesArguments &args) override {
+  GetVariables(const protocol::VariablesArguments &args) override {
     std::map<lldb::user_id_t, std::string> name_overrides;
     lldb::SBValueList list;
     for (auto inner : m_value)
@@ -230,7 +321,8 @@ class ExpandableValueStore final : public VariableStore {
     // We insert a new "[raw]" child that can be used to inspect the raw version
     // of a synthetic member. That eliminates the need for the user to go to the
     // debug console and type `frame var <variable> to get these values.
-    if (config.enableSyntheticChildDebugging && m_value.IsSynthetic()) {
+    if (m_storage.config.enableSyntheticChildDebugging &&
+        m_value.IsSynthetic()) {
       lldb::SBValue synthetic_value = m_value.GetNonSyntheticValue();
       name_overrides[synthetic_value.GetID()] = "[raw]";
       // FIXME: Cloning the value seems to affect the type summary, see
@@ -239,10 +331,7 @@ class ExpandableValueStore final : public VariableStore {
       list.Append(synthetic_value);
     }
 
-    const bool is_permanent =
-        args.variablesReference.Kind() == eReferenceKindPermanent;
-    return MakeVariables(storage, config, args, list, is_permanent,
-                         name_overrides);
+    return MakeVariables(args, list, name_overrides);
   }
 
   lldb::SBValue FindVariable(llvm::StringRef name) override {
@@ -269,18 +358,18 @@ class ExpandableValueStore final : public VariableStore {
   lldb::SBValue m_value;
 };
 
-class ExpandableValueListStore final : public VariableStore {
+class ExpandableValueListStore final : public VariableStoreImpl {
 
 public:
-  explicit ExpandableValueListStore(const lldb::SBValueList &list)
-      : m_value_list(list) {}
+  explicit ExpandableValueListStore(VariableReferenceStorage &storage,
+                                    bool is_permanent, bool is_internal,
+                                    const lldb::SBValueList &list)
+      : VariableStoreImpl(storage, is_permanent, is_internal),
+        m_value_list(list) {}
 
   llvm::Expected<std::vector<protocol::Variable>>
-  GetVariables(VariableReferenceStorage &storage,
-               const protocol::Configuration &config,
-               const protocol::VariablesArguments &args) override {
-    return MakeVariables(storage, config, args, m_value_list,
-                         /*is_permanent=*/true);
+  GetVariables(const protocol::VariablesArguments &args) override {
+    return MakeVariables(args, m_value_list);
   }
 
   lldb::SBValue FindVariable(llvm::StringRef name) override {
@@ -291,10 +380,6 @@ class ExpandableValueListStore final : public VariableStore {
     return lldb::SBValue();
   }
 
-  [[nodiscard]] lldb::SBValue GetVariable() const override {
-    return lldb::SBValue();
-  }
-
 private:
   lldb::SBValueList m_value_list;
 };
@@ -303,29 +388,6 @@ class ExpandableValueListStore final : public VariableStore {
 
 namespace lldb_dap {
 
-protocol::Scope CreateScope(ScopeKind kind, var_ref_t variablesReference,
-                            bool expensive) {
-  protocol::Scope scope;
-  scope.variablesReference = variablesReference;
-  scope.expensive = expensive;
-
-  switch (kind) {
-  case eScopeKindLocals:
-    scope.presentationHint = protocol::Scope::eScopePresentationHintLocals;
-    scope.name = "Locals";
-    break;
-  case eScopeKindGlobals:
-    scope.name = "Globals";
-    break;
-  case eScopeKindRegisters:
-    scope.presentationHint = protocol::Scope::eScopePresentationHintRegisters;
-    scope.name = "Registers";
-    break;
-  }
-
-  return scope;
-}
-
 lldb::SBValue VariableReferenceStorage::GetVariable(var_ref_t var_ref) {
   const ReferenceKind kind = var_ref.Kind();
 
@@ -343,22 +405,26 @@ lldb::SBValue VariableReferenceStorage::GetVariable(var_ref_t var_ref) {
 }
 
 var_ref_t VariableReferenceStorage::Insert(const lldb::SBValue &variable,
-                                           bool is_permanent) {
+                                           bool is_permanent,
+                                           bool is_internal) {
   if (is_permanent)
-    return m_permanent_kind_pool.Add<ExpandableValueStore>(variable);
+    return m_permanent_kind_pool.Add<ExpandableValueStore>(
+        *this, is_permanent, is_internal, variable);
 
-  return m_temporary_kind_pool.Add<ExpandableValueStore>(variable);
+  return m_temporary_kind_pool.Add<ExpandableValueStore>(*this, is_permanent,
+                                                         is_internal, variable);
 }
 
 var_ref_t VariableReferenceStorage::Insert(const lldb::SBValueList &values) {
-  return m_permanent_kind_pool.Add<ExpandableValueListStore>(values);
+  return m_permanent_kind_pool.Add<ExpandableValueListStore>(
+      *this, /*is_permanent=*/true, /*is_internal=*/false, values);
 }
 
 std::vector<protocol::Scope>
 VariableReferenceStorage::Insert(const lldb::SBFrame &frame) {
   auto create_scope = [&](ScopeKind kind) {
     const var_ref_t var_ref =
-        m_temporary_kind_pool.Add<ScopeStore>(kind, frame);
+        m_temporary_kind_pool.Add<ScopeStore>(*this, kind, frame);
     const bool is_expensive = kind != eScopeKindLocals;
     return CreateScope(kind, var_ref, is_expensive);
   };
diff --git a/lldb/tools/lldb-dap/Variables.h b/lldb/tools/lldb-dap/Variables.h
index bc6c4637fac80..eda7103aaa781 100644
--- a/lldb/tools/lldb-dap/Variables.h
+++ b/lldb/tools/lldb-dap/Variables.h
@@ -28,44 +28,35 @@ enum ScopeKind : unsigned {
   eScopeKindRegisters
 };
 
-/// Creates a `protocol::Scope` struct.
-///
-/// \param[in] kind
-///     The kind of scope to create
-///
-/// \param[in] variablesReference
-///     The value to place into the "variablesReference" key
-///
-/// \param[in] expensive
-///     The value to place into the "expensive" key
-///
-/// \return
-///     A `protocol::Scope`
-protocol::Scope CreateScope(ScopeKind kind, var_ref_t variablesReference,
-                            bool expensive);
-
 /// An Interface to get or find specific variables by name.
 class VariableStore {
 public:
-  explicit VariableStore() = default;
+  explicit VariableStore(VariableReferenceStorage &storage, bool is_permanent,
+                         bool is_internal)
+      : m_storage(storage), m_is_permanent(is_permanent),
+        m_is_internal(is_internal) {}
   virtual ~VariableStore() = default;
 
   virtual llvm::Expected<std::vector<protocol::Variable>>
-  GetVariables(VariableReferenceStorage &storage,
-               const protocol::Configuration &config,
-               const protocol::VariablesArguments &args) = 0;
+  GetVariables(const protocol::VariablesArguments &args) = 0;
   virtual lldb::SBValue FindVariable(llvm::StringRef name) = 0;
-  virtual lldb::SBValue GetVariable() const = 0;
+  virtual lldb::SBValue GetVariable() const { return {}; }
 
   // Not copyable.
   VariableStore(const VariableStore &) = delete;
   VariableStore &operator=(const VariableStore &) = delete;
-  VariableStore(VariableStore &&) = default;
-  VariableStore &operator=(VariableStore &&) = default;
+  VariableStore(VariableStore &&) = delete;
+  VariableStore &operator=(VariableStore &&) = delete;
+
+protected:
+  VariableReferenceStorage &m_storage;
+  bool m_is_permanent;
+  bool m_is_internal;
 };
 
 struct VariableReferenceStorage {
-  explicit VariableReferenceStorage(Log &log) : log(log) {}
+  explicit VariableReferenceStorage(Log &log, protocol::Configuration &config)
+      : log(log), config(config) {}
   /// \return a new variableReference.
   /// Specify is_permanent as true for variable that should persist entire
   /// debug session.
@@ -78,7 +69,8 @@ struct VariableReferenceStorage {
 
   /// Insert a new \p variable.
   /// \return variableReference assigned to this expandable variable.
-  var_ref_t Insert(const lldb::SBValue &variable, bool is_permanent);
+  var_ref_t Insert(const lldb::SBValue &variable, bool is_permanent,
+                   bool is_internal);
 
   /// Insert a value list. Used to store references to lldb repl command
   /// outputs.
@@ -93,6 +85,7 @@ struct VariableReferenceStorage {
 
   VariableStore *GetVariableStore(var_ref_t var_ref);
   Log &log;
+  protocol::Configuration &config;
 
 private:
   /// Template class for managing pools of variable stores.

>From b8952a370c54db2554589842263d1a1d94aaeccf Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Fri, 13 Mar 2026 14:52:37 -0700
Subject: [PATCH 2/2] Correcting DAP unit tests

---
 lldb/unittests/DAP/VariablesTest.cpp | 29 +++++++++++++++-------------
 1 file changed, 16 insertions(+), 13 deletions(-)

diff --git a/lldb/unittests/DAP/VariablesTest.cpp b/lldb/unittests/DAP/VariablesTest.cpp
index fe85e92a7fee2..7a27bf0445f3a 100644
--- a/lldb/unittests/DAP/VariablesTest.cpp
+++ b/lldb/unittests/DAP/VariablesTest.cpp
@@ -27,7 +27,7 @@ using namespace lldb_dap::protocol;
 class VariablesTest : public ::testing::Test {
 
 public:
-  VariablesTest() : log(llvm::nulls(), mutex), vars(log) {}
+  VariablesTest() : log(llvm::nulls(), mutex), vars(log, config) {}
 
   static void SetUpTestSuite() {
     lldb::SBError error = SBDebugger::InitializeWithErrorHandling();
@@ -48,6 +48,7 @@ class VariablesTest : public ::testing::Test {
   enum : bool { Permanent = true, Temporary = false };
   Log::Mutex mutex;
   Log log;
+  protocol::Configuration config;
   VariableReferenceStorage vars;
   lldb::SBDebugger debugger;
   lldb::SBTarget target;
@@ -104,10 +105,10 @@ TEST_F(VariablesTest, GetNewVariableReference_UniqueAndRanges) {
   auto y42 = target.CreateValueFromExpression("y", "42");
   auto gzero = target.CreateValueFromExpression("$0", "42");
   auto gone = target.CreateValueFromExpression("$1", "7");
-  const var_ref_t temp1 = vars.Insert(x15, Temporary);
-  const var_ref_t temp2 = vars.Insert(y42, Temporary);
-  const var_ref_t perm1 = vars.Insert(gzero, Permanent);
-  const var_ref_t perm2 = vars.Insert(gone, Permanent);
+  const var_ref_t temp1 = vars.Insert(x15, Temporary, /*is_internal=*/false);
+  const var_ref_t temp2 = vars.Insert(y42, Temporary, /*is_internal=*/false);
+  const var_ref_t perm1 = vars.Insert(gzero, Permanent, /*is_internal=*/false);
+  const var_ref_t perm2 = vars.Insert(gone, Permanent, /*is_internal=*/false);
   EXPECT_NE(temp1.AsUInt32(), temp2.AsUInt32());
   EXPECT_NE(perm1.AsUInt32(), perm2.AsUInt32());
   EXPECT_LT(temp1.AsUInt32(), perm1.AsUInt32());
@@ -116,7 +117,7 @@ TEST_F(VariablesTest, GetNewVariableReference_UniqueAndRanges) {
 
 TEST_F(VariablesTest, InsertAndGetVariable_Temporary) {
   lldb::SBValue dummy;
-  const var_ref_t ref = vars.Insert(dummy, Temporary);
+  const var_ref_t ref = vars.Insert(dummy, Temporary, /*is_internal=*/false);
   lldb::SBValue out = vars.GetVariable(ref);
 
   EXPECT_EQ(out.IsValid(), dummy.IsValid());
@@ -124,15 +125,17 @@ TEST_F(VariablesTest, InsertAndGetVariable_Temporary) {
 
 TEST_F(VariablesTest, InsertAndGetVariable_Permanent) {
   lldb::SBValue dummy;
-  const var_ref_t ref = vars.Insert(dummy, Permanent);
+  const var_ref_t ref = vars.Insert(dummy, Permanent, /*is_internal=*/false);
   lldb::SBValue out = vars.GetVariable(ref);
 
   EXPECT_EQ(out.IsValid(), dummy.IsValid());
 }
 
 TEST_F(VariablesTest, IsPermanentVariableReference) {
-  const var_ref_t perm = vars.Insert(lldb::SBValue(), Permanent);
-  const var_ref_t temp = vars.Insert(lldb::SBValue(), Temporary);
+  const var_ref_t perm =
+      vars.Insert(lldb::SBValue(), Permanent, /*is_internal=*/false);
+  const var_ref_t temp =
+      vars.Insert(lldb::SBValue(), Temporary, /*is_internal=*/false);
 
   EXPECT_EQ(perm.Kind(), eReferenceKindPermanent);
   EXPECT_EQ(temp.Kind(), eReferenceKindTemporary);
@@ -140,8 +143,8 @@ TEST_F(VariablesTest, IsPermanentVariableReference) {
 
 TEST_F(VariablesTest, Clear_RemovesTemporaryKeepsPermanent) {
   lldb::SBValue dummy;
-  const var_ref_t temp = vars.Insert(dummy, Temporary);
-  const var_ref_t perm = vars.Insert(dummy, Permanent);
+  const var_ref_t temp = vars.Insert(dummy, Temporary, /*is_internal=*/false);
+  const var_ref_t perm = vars.Insert(dummy, Permanent, /*is_internal=*/false);
   vars.Clear();
 
   EXPECT_FALSE(vars.GetVariable(temp).IsValid());
@@ -184,7 +187,7 @@ TEST_F(VariablesTest, VariablesStore) {
 
   ASSERT_TRUE(vars.FindVariable(local_ref, "rect").IsValid());
 
-  auto variables = locals_store->GetVariables(vars, {}, {});
+  auto variables = locals_store->GetVariables({});
   ASSERT_THAT_EXPECTED(variables, Succeeded());
   ASSERT_EQ(variables->size(), 1u);
   auto rect = variables->at(0);
@@ -196,7 +199,7 @@ TEST_F(VariablesTest, VariablesStore) {
   auto *store = vars.GetVariableStore(args.variablesReference);
   ASSERT_NE(store, nullptr);
 
-  variables = store->GetVariables(vars, {}, args);
+  variables = store->GetVariables(args);
   ASSERT_THAT_EXPECTED(variables, Succeeded());
   ASSERT_EQ(variables->size(), 4u);
   EXPECT_EQ(variables->at(0).name, "x");



More information about the lldb-commits mailing list