[Lldb-commits] [lldb] WIP: Stop using replicated variable ids (PR #124232)

Anthony Eid via lldb-commits lldb-commits at lists.llvm.org
Wed Aug 27 23:26:29 PDT 2025


https://github.com/Anthony-Eid updated https://github.com/llvm/llvm-project/pull/124232

>From 30658e994b18b7c0db114a297036421c8de2dea3 Mon Sep 17 00:00:00 2001
From: Anthony <hello at anthonyeid.me>
Date: Wed, 27 Aug 2025 13:04:26 -0400
Subject: [PATCH 1/8] Fix variable request from reusing variable_ids

---
 .../lldb-dap/Handler/ScopesRequestHandler.cpp | 45 ++++++-----
 lldb/tools/lldb-dap/JSONUtils.h               |  1 +
 lldb/tools/lldb-dap/Variables.cpp             | 80 +++++++++++++++++--
 lldb/tools/lldb-dap/Variables.h               | 18 ++++-
 4 files changed, 119 insertions(+), 25 deletions(-)

diff --git a/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
index aaad0e20f9c21..160d8e264d089 100644
--- a/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
@@ -29,8 +29,8 @@ namespace lldb_dap {
 ///
 /// \return
 ///     A `protocol::Scope`
-static Scope CreateScope(const llvm::StringRef name, int64_t variablesReference,
-                         int64_t namedVariables, bool expensive) {
+Scope CreateScope(const llvm::StringRef name, int64_t variablesReference,
+                  int64_t namedVariables, bool expensive) {
   Scope scope;
   scope.name = name;
 
@@ -75,22 +75,31 @@ ScopesRequestHandler::Run(const ScopesArguments &args) const {
     frame.GetThread().GetProcess().SetSelectedThread(frame.GetThread());
     frame.GetThread().SetSelectedFrame(frame.GetFrameID());
   }
-  dap.variables.locals = frame.GetVariables(/*arguments=*/true,
-                                            /*locals=*/true,
-                                            /*statics=*/false,
-                                            /*in_scope_only=*/true);
-  dap.variables.globals = frame.GetVariables(/*arguments=*/false,
-                                             /*locals=*/false,
-                                             /*statics=*/true,
-                                             /*in_scope_only=*/true);
-  dap.variables.registers = frame.GetRegisters();
-
-  std::vector scopes = {CreateScope("Locals", VARREF_LOCALS,
-                                    dap.variables.locals.GetSize(), false),
-                        CreateScope("Globals", VARREF_GLOBALS,
-                                    dap.variables.globals.GetSize(), false),
-                        CreateScope("Registers", VARREF_REGS,
-                                    dap.variables.registers.GetSize(), false)};
+
+  uint32_t frame_id = frame.GetFrameID();
+
+  dap.variables.ReadyFrame(frame_id, frame);
+  dap.variables.SwitchFrame(frame_id);
+
+  std::vector<Scope> scopes = {};
+
+  int64_t variable_reference = dap.variables.GetNewVariableReference(false);
+  scopes.push_back(CreateScope("Locals", variable_reference,
+                               dap.variables.locals.GetSize(), false));
+
+  dap.variables.AddScopeKind(variable_reference, ScopeKind::Locals, frame_id);
+
+  variable_reference = dap.variables.GetNewVariableReference(false);
+  scopes.push_back(CreateScope("Globals", variable_reference,
+                               dap.variables.globals.GetSize(), false));
+  dap.variables.AddScopeKind(variable_reference, ScopeKind::Globals, frame_id);
+
+  variable_reference = dap.variables.GetNewVariableReference(false);
+  scopes.push_back(CreateScope("Registers", variable_reference,
+                               dap.variables.registers.GetSize(), false));
+
+  dap.variables.AddScopeKind(variable_reference, ScopeKind::Registers,
+                             frame_id);
 
   return ScopesResponseBody{std::move(scopes)};
 }
diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h
index e9094f67b94ec..6575411acd878 100644
--- a/lldb/tools/lldb-dap/JSONUtils.h
+++ b/lldb/tools/lldb-dap/JSONUtils.h
@@ -9,6 +9,7 @@
 #ifndef LLDB_TOOLS_LLDB_DAP_JSONUTILS_H
 #define LLDB_TOOLS_LLDB_DAP_JSONUTILS_H
 
+#include "DAP.h"
 #include "DAPForward.h"
 #include "Protocol/ProtocolTypes.h"
 #include "lldb/API/SBCompileUnit.h"
diff --git a/lldb/tools/lldb-dap/Variables.cpp b/lldb/tools/lldb-dap/Variables.cpp
index 777e3183d8c0d..9ed3773df817d 100644
--- a/lldb/tools/lldb-dap/Variables.cpp
+++ b/lldb/tools/lldb-dap/Variables.cpp
@@ -8,20 +8,33 @@
 
 #include "Variables.h"
 #include "JSONUtils.h"
+#include "lldb/API/SBFrame.h"
 
 using namespace lldb_dap;
 
 lldb::SBValueList *Variables::GetTopLevelScope(int64_t variablesReference) {
-  switch (variablesReference) {
-  case VARREF_LOCALS:
+  auto iter = m_scope_kinds.find(variablesReference);
+  if (iter == m_scope_kinds.end()) {
+    return nullptr;
+  }
+
+  ScopeKind scope_kind = iter->second.first;
+  uint32_t frame_id = iter->second.second;
+
+  if (!SwitchFrame(frame_id)) {
+    return nullptr;
+  }
+
+  switch (scope_kind) {
+  case lldb_dap::ScopeKind::Locals:
     return &locals;
-  case VARREF_GLOBALS:
+  case lldb_dap::ScopeKind::Globals:
     return &globals;
-  case VARREF_REGS:
+  case lldb_dap::ScopeKind::Registers:
     return ®isters;
-  default:
-    return nullptr;
   }
+
+  return nullptr;
 }
 
 void Variables::Clear() {
@@ -29,6 +42,8 @@ void Variables::Clear() {
   globals.Clear();
   registers.Clear();
   m_referencedvariables.clear();
+  m_frames.clear();
+  m_next_temporary_var_ref = VARREF_FIRST_VAR_IDX;
 }
 
 int64_t Variables::GetNewVariableReference(bool is_permanent) {
@@ -103,3 +118,56 @@ lldb::SBValue Variables::FindVariable(uint64_t variablesReference,
   }
   return variable;
 }
+
+std::optional<ScopeKind>
+Variables::GetScopeKind(const int64_t variablesReference) {
+  auto iter = m_scope_kinds.find(variablesReference);
+  if (iter != m_scope_kinds.end()) {
+    return iter->second.first;
+  }
+
+  return std::nullopt;
+}
+
+bool Variables::SwitchFrame(const uint32_t frame_id) {
+  auto iter = m_frames.find(frame_id);
+
+  if (iter == m_frames.end()) {
+    return false;
+  }
+
+  auto [frame_locals, frame_globals, frame_registers] = iter->second;
+
+  locals = frame_locals;
+  globals = frame_globals;
+  registers = frame_registers;
+
+  return true;
+}
+
+void Variables::ReadyFrame(uint32_t frame_id, lldb::SBFrame &frame) {
+  if (m_frames.find(frame_id) == m_frames.end()) {
+
+    auto locals = frame.GetVariables(/*arguments=*/true,
+                                     /*locals=*/true,
+                                     /*statics=*/false,
+                                     /*in_scope_only=*/true);
+
+    auto globals = frame.GetVariables(/*arguments=*/false,
+                                      /*locals=*/false,
+                                      /*statics=*/true,
+                                      /*in_scope_only=*/true);
+
+    auto registers = frame.GetRegisters();
+
+    m_frames.insert(
+        std::make_pair(frame_id, std::make_tuple(locals, globals, registers)));
+  }
+}
+
+void Variables::AddScopeKind(int64_t variable_reference, ScopeKind kind,
+                             uint32_t frame_id) {
+
+  m_scope_kinds[variable_reference] =
+      std::make_pair(ScopeKind::Globals, frame_id);
+}
diff --git a/lldb/tools/lldb-dap/Variables.h b/lldb/tools/lldb-dap/Variables.h
index 0ed84b36aef99..2f40f87e9b4bb 100644
--- a/lldb/tools/lldb-dap/Variables.h
+++ b/lldb/tools/lldb-dap/Variables.h
@@ -12,6 +12,7 @@
 #include "lldb/API/SBValue.h"
 #include "lldb/API/SBValueList.h"
 #include "llvm/ADT/DenseMap.h"
+#include <map>
 
 #define VARREF_FIRST_VAR_IDX (int64_t)4
 #define VARREF_LOCALS (int64_t)1
@@ -20,6 +21,8 @@
 
 namespace lldb_dap {
 
+enum ScopeKind { Locals, Globals, Registers };
+
 struct Variables {
   lldb::SBValueList locals;
   lldb::SBValueList globals;
@@ -47,12 +50,23 @@ struct Variables {
 
   lldb::SBValue FindVariable(uint64_t variablesReference, llvm::StringRef name);
 
+  bool SwitchFrame(uint32_t frame_id);
+  /// Initialize a frame if it hasn't been already, otherwise do nothing
+  void ReadyFrame(uint32_t frame_id, lldb::SBFrame &frame);
+  std::optional<ScopeKind> GetScopeKind(const int64_t variablesReference);
+
   /// Clear all scope variables and non-permanent expandable variables.
   void Clear();
 
+  void AddScopeKind(int64_t variable_reference, ScopeKind kind,
+                    uint32_t frame_id);
+
 private:
   /// Variable_reference start index of permanent expandable variable.
   static constexpr int64_t PermanentVariableStartIndex = (1ll << 32);
+  int64_t m_next_temporary_var_ref{VARREF_FIRST_VAR_IDX};
+
+  std::map<int, std::pair<ScopeKind, uint32_t>> m_scope_kinds;
 
   /// Variables that are alive in this stop state.
   /// Will be cleared when debuggee resumes.
@@ -62,7 +76,9 @@ struct Variables {
   /// These are the variables evaluated from debug console REPL.
   llvm::DenseMap<int64_t, lldb::SBValue> m_referencedpermanent_variables;
 
-  int64_t m_next_temporary_var_ref{VARREF_FIRST_VAR_IDX};
+  std::map<uint32_t,
+           std::tuple<lldb::SBValueList, lldb::SBValueList, lldb::SBValueList>>
+      m_frames;
   int64_t m_next_permanent_var_ref{PermanentVariableStartIndex};
 };
 

>From 9707978154cc43ee6f7e3d54d0159a0c40d5bbf0 Mon Sep 17 00:00:00 2001
From: Anthony <hello at anthonyeid.me>
Date: Wed, 27 Aug 2025 13:12:02 -0400
Subject: [PATCH 2/8] Add createScope changes

---
 lldb/tools/lldb-dap/JSONUtils.cpp | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp
index 4f26599a49bac..7c256481f8c71 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -11,6 +11,7 @@
 #include "ExceptionBreakpoint.h"
 #include "LLDBUtils.h"
 #include "ProtocolUtils.h"
+#include "Variables.h"
 #include "lldb/API/SBAddress.h"
 #include "lldb/API/SBCompileUnit.h"
 #include "lldb/API/SBDeclaration.h"
@@ -358,16 +359,16 @@ void FillResponse(const llvm::json::Object &request,
 //   "required": [ "name", "variablesReference", "expensive" ]
 // }
 llvm::json::Value CreateScope(const llvm::StringRef name,
-                              int64_t variablesReference,
+                              int64_t variablesReference, ScopeKind kind,
                               int64_t namedVariables, bool expensive) {
   llvm::json::Object object;
   EmplaceSafeString(object, "name", name.str());
 
   // TODO: Support "arguments" scope. At the moment lldb-dap includes the
   // arguments into the "locals" scope.
-  if (variablesReference == VARREF_LOCALS) {
+  if (kind == ScopeKind::Locals) {
     object.try_emplace("presentationHint", "locals");
-  } else if (variablesReference == VARREF_REGS) {
+  } else if (kind == ScopeKind::Registers) {
     object.try_emplace("presentationHint", "registers");
   }
 

>From 50230c201673a58236bf5bccfe3036ca5d14cc95 Mon Sep 17 00:00:00 2001
From: Anthony <hello at anthonyeid.me>
Date: Wed, 27 Aug 2025 18:19:44 -0400
Subject: [PATCH 3/8] Fix some bugs

---
 lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp  | 1 +
 lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp    | 4 ++--
 lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp | 7 ++++---
 lldb/tools/lldb-dap/Variables.cpp                       | 4 ++--
 4 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp
index e1556846dff19..e207f830183b6 100644
--- a/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp
@@ -162,6 +162,7 @@ void EvaluateRequestHandler::operator()(
     // focus_tid to the current frame for any thread related events.
     if (frame.IsValid()) {
       dap.focus_tid = frame.GetThread().GetThreadID();
+      dap.variables.SwitchFrame(frame.GetFrameID());
     }
 
     bool required_command_failed = false;
diff --git a/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
index 160d8e264d089..553a7b30adba2 100644
--- a/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
@@ -41,9 +41,9 @@ Scope CreateScope(const llvm::StringRef name, int64_t variablesReference,
   // if we add the arguments above the local scope as the locals scope will not
   // be expanded if we enter a function with arguments. It becomes more
   // annoying when the scope has arguments, return_value and locals.
-  if (variablesReference == VARREF_LOCALS)
+  if (name == "Locals")
     scope.presentationHint = Scope::eScopePresentationHintLocals;
-  else if (variablesReference == VARREF_REGS)
+  else if (name == "Registers")
     scope.presentationHint = Scope::eScopePresentationHintRegisters;
 
   scope.variablesReference = variablesReference;
diff --git a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
index 5fa2b1ef5e20d..3d99983ac0c83 100644
--- a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
@@ -38,7 +38,8 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
     int64_t start_idx = 0;
     int64_t num_children = 0;
 
-    if (var_ref == VARREF_REGS) {
+    std::optional<ScopeKind> scope_kind = dap.variables.GetScopeKind(var_ref);
+    if (scope_kind && *scope_kind == ScopeKind::Registers) {
       // 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
@@ -58,7 +59,7 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
     }
 
     num_children = top_scope->GetSize();
-    if (num_children == 0 && var_ref == VARREF_LOCALS) {
+    if (num_children == 0 && scope_kind && *scope_kind == ScopeKind::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.
@@ -94,7 +95,7 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
     }
 
     // Show return value if there is any ( in the local top frame )
-    if (var_ref == VARREF_LOCALS) {
+    if (scope_kind && *scope_kind == ScopeKind::Locals) {
       auto process = dap.target.GetProcess();
       auto selected_thread = process.GetSelectedThread();
       lldb::SBValue stop_return_value = selected_thread.GetStopReturnValue();
diff --git a/lldb/tools/lldb-dap/Variables.cpp b/lldb/tools/lldb-dap/Variables.cpp
index 9ed3773df817d..b5cd9d69d385f 100644
--- a/lldb/tools/lldb-dap/Variables.cpp
+++ b/lldb/tools/lldb-dap/Variables.cpp
@@ -42,6 +42,7 @@ void Variables::Clear() {
   globals.Clear();
   registers.Clear();
   m_referencedvariables.clear();
+  m_scope_kinds.clear();
   m_frames.clear();
   m_next_temporary_var_ref = VARREF_FIRST_VAR_IDX;
 }
@@ -168,6 +169,5 @@ void Variables::ReadyFrame(uint32_t frame_id, lldb::SBFrame &frame) {
 void Variables::AddScopeKind(int64_t variable_reference, ScopeKind kind,
                              uint32_t frame_id) {
 
-  m_scope_kinds[variable_reference] =
-      std::make_pair(ScopeKind::Globals, frame_id);
+  m_scope_kinds[variable_reference] = std::make_pair(kind, frame_id);
 }

>From c73d4c4f82afbee0399396909a0becf98aac257e Mon Sep 17 00:00:00 2001
From: Anthony <hello at anthonyeid.me>
Date: Wed, 27 Aug 2025 18:31:25 -0400
Subject: [PATCH 4/8] remove unused GetScopedsKind func

---
 lldb/tools/lldb-dap/JSONUtils.cpp | 85 -------------------------------
 lldb/tools/lldb-dap/Variables.h   |  2 +-
 2 files changed, 1 insertion(+), 86 deletions(-)

diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp
index 7c256481f8c71..ad1f035caaf54 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -293,91 +293,6 @@ void FillResponse(const llvm::json::Object &request,
   response.try_emplace("success", true);
 }
 
-// "Scope": {
-//   "type": "object",
-//   "description": "A Scope is a named container for variables. Optionally
-//                   a scope can map to a source or a range within a source.",
-//   "properties": {
-//     "name": {
-//       "type": "string",
-//       "description": "Name of the scope such as 'Arguments', 'Locals'."
-//     },
-//     "presentationHint": {
-//       "type": "string",
-//       "description": "An optional hint for how to present this scope in the
-//                       UI. If this attribute is missing, the scope is shown
-//                       with a generic UI.",
-//       "_enum": [ "arguments", "locals", "registers" ],
-//     },
-//     "variablesReference": {
-//       "type": "integer",
-//       "description": "The variables of this scope can be retrieved by
-//                       passing the value of variablesReference to the
-//                       VariablesRequest."
-//     },
-//     "namedVariables": {
-//       "type": "integer",
-//       "description": "The number of named variables in this scope. The
-//                       client can use this optional information to present
-//                       the variables in a paged UI and fetch them in chunks."
-//     },
-//     "indexedVariables": {
-//       "type": "integer",
-//       "description": "The number of indexed variables in this scope. The
-//                       client can use this optional information to present
-//                       the variables in a paged UI and fetch them in chunks."
-//     },
-//     "expensive": {
-//       "type": "boolean",
-//       "description": "If true, the number of variables in this scope is
-//                       large or expensive to retrieve."
-//     },
-//     "source": {
-//       "$ref": "#/definitions/Source",
-//       "description": "Optional source for this scope."
-//     },
-//     "line": {
-//       "type": "integer",
-//       "description": "Optional start line of the range covered by this
-//                       scope."
-//     },
-//     "column": {
-//       "type": "integer",
-//       "description": "Optional start column of the range covered by this
-//                       scope."
-//     },
-//     "endLine": {
-//       "type": "integer",
-//       "description": "Optional end line of the range covered by this scope."
-//     },
-//     "endColumn": {
-//       "type": "integer",
-//       "description": "Optional end column of the range covered by this
-//                       scope."
-//     }
-//   },
-//   "required": [ "name", "variablesReference", "expensive" ]
-// }
-llvm::json::Value CreateScope(const llvm::StringRef name,
-                              int64_t variablesReference, ScopeKind kind,
-                              int64_t namedVariables, bool expensive) {
-  llvm::json::Object object;
-  EmplaceSafeString(object, "name", name.str());
-
-  // TODO: Support "arguments" scope. At the moment lldb-dap includes the
-  // arguments into the "locals" scope.
-  if (kind == ScopeKind::Locals) {
-    object.try_emplace("presentationHint", "locals");
-  } else if (kind == ScopeKind::Registers) {
-    object.try_emplace("presentationHint", "registers");
-  }
-
-  object.try_emplace("variablesReference", variablesReference);
-  object.try_emplace("expensive", expensive);
-  object.try_emplace("namedVariables", namedVariables);
-  return llvm::json::Value(std::move(object));
-}
-
 // "Event": {
 //   "allOf": [ { "$ref": "#/definitions/ProtocolMessage" }, {
 //     "type": "object",
diff --git a/lldb/tools/lldb-dap/Variables.h b/lldb/tools/lldb-dap/Variables.h
index 2f40f87e9b4bb..a8fad707e2e08 100644
--- a/lldb/tools/lldb-dap/Variables.h
+++ b/lldb/tools/lldb-dap/Variables.h
@@ -66,7 +66,7 @@ struct Variables {
   static constexpr int64_t PermanentVariableStartIndex = (1ll << 32);
   int64_t m_next_temporary_var_ref{VARREF_FIRST_VAR_IDX};
 
-  std::map<int, std::pair<ScopeKind, uint32_t>> m_scope_kinds;
+  std::map<int64_t, std::pair<ScopeKind, uint32_t>> m_scope_kinds;
 
   /// Variables that are alive in this stop state.
   /// Will be cleared when debuggee resumes.

>From 2712abdb5a21e466a4ee8a289c52c8bc9e537c63 Mon Sep 17 00:00:00 2001
From: Anthony <hello at anthonyeid.me>
Date: Thu, 28 Aug 2025 00:25:00 -0400
Subject: [PATCH 5/8] Fix hardcoded scope references in DAP variable handling

The test and API changes now get scope references dynamically instead of
using hardcoded values.

This fixes the below tests:
- testDAP_memory
- TestDAP_variables
- GetTopLevelScope_ReturnsCorrectScope
---
 .../test/tools/lldb-dap/lldbdap_testcase.py   | 33 ++++++++++++++++--
 .../tools/lldb-dap/memory/TestDAP_memory.py   |  4 ++-
 .../lldb-dap/variables/TestDAP_variables.py   | 17 +++++-----
 lldb/unittests/DAP/VariablesTest.cpp          | 34 ++++++++++++++++---
 4 files changed, 73 insertions(+), 15 deletions(-)

diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
index b28a78792c70f..a26be6e5d1bfb 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
@@ -351,11 +351,40 @@ def get_local_as_int(self, name, threadId=None):
 
     def set_local(self, name, value, id=None):
         """Set a top level local variable only."""
-        return self.dap_server.request_setVariable(1, name, str(value), id=id)
+        # Get the locals scope reference dynamically
+        locals_ref = self.get_locals_scope_reference()
+        if locals_ref is None:
+            return None
+        return self.dap_server.request_setVariable(locals_ref, name, str(value), id=id)
 
     def set_global(self, name, value, id=None):
         """Set a top level global variable only."""
-        return self.dap_server.request_setVariable(2, name, str(value), id=id)
+        # Get the globals scope reference dynamically
+        stackFrame = self.dap_server.get_stackFrame()
+        if stackFrame is None:
+            return None
+        frameId = stackFrame["id"]
+        scopes_response = self.dap_server.request_scopes(frameId)
+        frame_scopes = scopes_response["body"]["scopes"]
+        for scope in frame_scopes:
+            if scope["name"] == "Globals":
+                varRef = scope["variablesReference"]
+                return self.dap_server.request_setVariable(varRef, name, str(value), id=id)
+        return None
+
+
+    def get_locals_scope_reference(self):
+        """Get the variablesReference for the locals scope."""
+        stackFrame = self.dap_server.get_stackFrame()
+        if stackFrame is None:
+            return None
+        frameId = stackFrame["id"]
+        scopes_response = self.dap_server.request_scopes(frameId)
+        frame_scopes = scopes_response["body"]["scopes"]
+        for scope in frame_scopes:
+            if scope["name"] == "Locals":
+                return scope["variablesReference"]
+        return None
 
     def stepIn(
         self,
diff --git a/lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py b/lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py
index f51056d7020c6..5e1dc8d30ff8d 100644
--- a/lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py
+++ b/lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py
@@ -70,9 +70,11 @@ def test_memory_refs_set_variable(self):
         self.continue_to_next_stop()
 
         ptr_value = self.get_local_as_int("rawptr")
+        locals_ref = self.get_locals_scope_reference()
+        self.assertIsNotNone(locals_ref, "Failed to get locals scope reference")
         self.assertIn(
             "memoryReference",
-            self.dap_server.request_setVariable(1, "rawptr", ptr_value + 2)[
+            self.dap_server.request_setVariable(locals_ref, "rawptr", ptr_value + 2)[
                 "body"
             ].keys(),
         )
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 a3a4bdaaf40a6..a51d1f0ee179c 100644
--- a/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py
+++ b/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py
@@ -341,24 +341,25 @@ def do_test_scopes_variables_setVariable_evaluate(
         self.verify_variables(verify_locals, self.dap_server.get_local_variables())
 
         # Now we verify that we correctly change the name of a variable with and without differentiator suffix
-        self.assertFalse(self.dap_server.request_setVariable(1, "x2", 9)["success"])
+        local_scope_ref = self.get_locals_scope_reference()
+        self.assertFalse(self.dap_server.request_setVariable(local_scope_ref, "x2", 9)["success"])
         self.assertFalse(
-            self.dap_server.request_setVariable(1, "x @ main.cpp:0", 9)["success"]
+            self.dap_server.request_setVariable(local_scope_ref, "x @ main.cpp:0", 9)["success"]
         )
 
         self.assertTrue(
-            self.dap_server.request_setVariable(1, "x @ main.cpp:19", 19)["success"]
+            self.dap_server.request_setVariable(local_scope_ref, "x @ main.cpp:19", 19)["success"]
         )
         self.assertTrue(
-            self.dap_server.request_setVariable(1, "x @ main.cpp:21", 21)["success"]
+            self.dap_server.request_setVariable(local_scope_ref, "x @ main.cpp:21", 21)["success"]
         )
         self.assertTrue(
-            self.dap_server.request_setVariable(1, "x @ main.cpp:23", 23)["success"]
+            self.dap_server.request_setVariable(local_scope_ref, "x @ main.cpp:23", 23)["success"]
         )
 
         # The following should have no effect
         self.assertFalse(
-            self.dap_server.request_setVariable(1, "x @ main.cpp:23", "invalid")[
+            self.dap_server.request_setVariable(local_scope_ref, "x @ main.cpp:23", "invalid")[
                 "success"
             ]
         )
@@ -370,7 +371,7 @@ def do_test_scopes_variables_setVariable_evaluate(
         self.verify_variables(verify_locals, self.dap_server.get_local_variables())
 
         # The plain x variable shold refer to the innermost x
-        self.assertTrue(self.dap_server.request_setVariable(1, "x", 22)["success"])
+        self.assertTrue(self.dap_server.request_setVariable(local_scope_ref, "x", 22)["success"])
         verify_locals["x @ main.cpp:23"]["equals"]["value"] = "22"
 
         self.verify_variables(verify_locals, self.dap_server.get_local_variables())
@@ -709,7 +710,7 @@ def test_return_variables(self):
                 break
 
         self.assertFalse(
-            self.dap_server.request_setVariable(1, "(Return Value)", 20)["success"]
+            self.dap_server.request_setVariable(self.get_locals_scope_reference(), "(Return Value)", 20)["success"]
         )
 
     @skipIfWindows
diff --git a/lldb/unittests/DAP/VariablesTest.cpp b/lldb/unittests/DAP/VariablesTest.cpp
index 6b14fc6c3945d..40884e89e0c54 100644
--- a/lldb/unittests/DAP/VariablesTest.cpp
+++ b/lldb/unittests/DAP/VariablesTest.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "Variables.h"
+#include "lldb/API/SBFrame.h"
 #include "lldb/API/SBValue.h"
 #include "lldb/API/SBValueList.h"
 #include "gtest/gtest.h"
@@ -66,20 +67,45 @@ TEST_F(VariablesTest, Clear_RemovesTemporaryKeepsPermanent) {
 }
 
 TEST_F(VariablesTest, GetTopLevelScope_ReturnsCorrectScope) {
+  lldb::SBFrame frame;
+  uint32_t frame_id = 0;
+
+  vars.ReadyFrame(frame_id, frame);
+  vars.SwitchFrame(frame_id);
+
   vars.locals.Append(lldb::SBValue());
   vars.globals.Append(lldb::SBValue());
   vars.registers.Append(lldb::SBValue());
 
-  EXPECT_EQ(vars.GetTopLevelScope(VARREF_LOCALS), &vars.locals);
-  EXPECT_EQ(vars.GetTopLevelScope(VARREF_GLOBALS), &vars.globals);
-  EXPECT_EQ(vars.GetTopLevelScope(VARREF_REGS), &vars.registers);
+  int64_t locals_ref = vars.GetNewVariableReference(false);
+  vars.AddScopeKind(locals_ref, ScopeKind::Locals, frame_id);
+
+  int64_t globals_ref = vars.GetNewVariableReference(false);
+  vars.AddScopeKind(globals_ref, ScopeKind::Globals, frame_id);
+
+  int64_t registers_ref = vars.GetNewVariableReference(false);
+  vars.AddScopeKind(registers_ref, ScopeKind::Registers, frame_id);
+
+  EXPECT_EQ(vars.GetTopLevelScope(locals_ref), &vars.locals);
+  EXPECT_EQ(vars.GetTopLevelScope(globals_ref), &vars.globals);
+  EXPECT_EQ(vars.GetTopLevelScope(registers_ref), &vars.registers);
   EXPECT_EQ(vars.GetTopLevelScope(9999), nullptr);
 }
 
 TEST_F(VariablesTest, FindVariable_LocalsByName) {
+  lldb::SBFrame frame;
+  uint32_t frame_id = 0;
+
+  vars.ReadyFrame(frame_id, frame);
+  vars.SwitchFrame(frame_id);
+
   lldb::SBValue dummy;
   vars.locals.Append(dummy);
-  lldb::SBValue found = vars.FindVariable(VARREF_LOCALS, "");
+
+  int64_t locals_ref = vars.GetNewVariableReference(false);
+  vars.AddScopeKind(locals_ref, ScopeKind::Locals, frame_id);
+
+  lldb::SBValue found = vars.FindVariable(locals_ref, "");
 
   EXPECT_EQ(found.IsValid(), dummy.IsValid());
 }

>From d0f1e86dd1cd081427329c5b53b262274653556c Mon Sep 17 00:00:00 2001
From: Anthony <hello at anthonyeid.me>
Date: Thu, 28 Aug 2025 00:54:37 -0400
Subject: [PATCH 6/8] Fix set data breakpoint test by using the correct
 variable reference

Variable reference 1 was hard coded to always be the local scope but
since the var_ref of local scope is dynamic now it uses a helper
function in the test
---
 .../lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py     | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py b/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py
index a542a318050dd..211d71d43d155 100644
--- a/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py
+++ b/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py
@@ -106,8 +106,10 @@ def test_functionality(self):
         self.set_source_breakpoints(source, [first_loop_break_line])
         self.continue_to_next_stop()
         self.dap_server.get_local_variables()
+        locals_ref = self.get_locals_scope_reference()
+        self.assertIsNotNone(locals_ref, "Failed to get locals scope reference")
         # Test write watchpoints on x, arr[2]
-        response_x = self.dap_server.request_dataBreakpointInfo(1, "x")
+        response_x = self.dap_server.request_dataBreakpointInfo(locals_ref, "x")
         arr = self.dap_server.get_local_variable("arr")
         response_arr_2 = self.dap_server.request_dataBreakpointInfo(
             arr["variablesReference"], "[2]"

>From 875b926e8b04b072d56dbf2d75ce9f5732341382 Mon Sep 17 00:00:00 2001
From: Anthony <hello at anthonyeid.me>
Date: Thu, 28 Aug 2025 02:16:27 -0400
Subject: [PATCH 7/8] Have llm (Claude Opus 4) Generate a regression test

---
 .../lldb-dap/variables/TestDAP_variables.py   | 41 +++++++++++++++++++
 1 file changed, 41 insertions(+)

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 a51d1f0ee179c..342dc4de7b895 100644
--- a/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py
+++ b/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py
@@ -832,3 +832,44 @@ def test_value_format(self):
         self.assertEqual(var_pt_x["value"], "11")
         var_pt_y = self.dap_server.get_local_variable_child("pt", "y", is_hex=is_hex)
         self.assertEqual(var_pt_y["value"], "22")
+
+    @skipIfWindows
+    def test_variable_id_uniqueness_simple(self):
+        """
+        Simple regression test for variable ID uniqueness across frames.
+        Ensures variable IDs are not reused between different scopes/frames.
+        """
+        program = self.getBuildArtifact("a.out")
+        self.build_and_launch(program)
+        source = "main.cpp"
+
+        # Set breakpoint at test_indexedVariables call to get multiple frames
+        bp_line = line_number(source, "// breakpoint 3")
+        self.set_source_breakpoints(source, [bp_line])
+        self.continue_to_next_stop()
+
+        # Get stack frames
+        frames = self.get_stackFrames()
+        self.assertGreaterEqual(len(frames), 2, "Need at least 2 frames")
+
+        # Track all variable references for uniqueness check
+        all_refs = set()
+
+        # Check first 2-3 frames
+        for i in range(min(3, len(frames))):
+            frame_id = frames[i]['id']
+            scopes = self.dap_server.request_scopes(frame_id)["body"]["scopes"]
+
+            for scope in scopes:
+                ref = scope['variablesReference']
+                if ref != 0:  # 0 means no variables
+                    # Ensure this reference hasn't been used before
+                    self.assertNotIn(ref, all_refs,
+                        f"Variable reference {ref} was reused!")
+                    all_refs.add(ref)
+
+        # Verify we collected references and they're still accessible
+        self.assertGreater(len(all_refs), 0, "Should have found variable references")
+        for ref in all_refs:
+            response = self.dap_server.request_variables(ref)
+            self.assertTrue(response['success'], f"Failed to access reference {ref}")

>From 4dae801003ed014378cfd95bfea1b6ddac0664b2 Mon Sep 17 00:00:00 2001
From: Anthony <hello at anthonyeid.me>
Date: Thu, 28 Aug 2025 02:26:15 -0400
Subject: [PATCH 8/8] Format test files with darker

---
 .../lldb-dap/variables/TestDAP_variables.py   | 19 +++++++------------
 1 file changed, 7 insertions(+), 12 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 342dc4de7b895..705863edbc861 100644
--- a/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py
+++ b/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py
@@ -843,33 +843,28 @@ def test_variable_id_uniqueness_simple(self):
         self.build_and_launch(program)
         source = "main.cpp"
 
-        # Set breakpoint at test_indexedVariables call to get multiple frames
         bp_line = line_number(source, "// breakpoint 3")
         self.set_source_breakpoints(source, [bp_line])
         self.continue_to_next_stop()
 
-        # Get stack frames
         frames = self.get_stackFrames()
         self.assertGreaterEqual(len(frames), 2, "Need at least 2 frames")
 
-        # Track all variable references for uniqueness check
         all_refs = set()
 
-        # Check first 2-3 frames
         for i in range(min(3, len(frames))):
-            frame_id = frames[i]['id']
+            frame_id = frames[i]["id"]
             scopes = self.dap_server.request_scopes(frame_id)["body"]["scopes"]
 
             for scope in scopes:
-                ref = scope['variablesReference']
-                if ref != 0:  # 0 means no variables
-                    # Ensure this reference hasn't been used before
-                    self.assertNotIn(ref, all_refs,
-                        f"Variable reference {ref} was reused!")
+                ref = scope["variablesReference"]
+                if ref != 0:
+                    self.assertNotIn(
+                        ref, all_refs, f"Variable reference {ref} was reused!"
+                    )
                     all_refs.add(ref)
 
-        # Verify we collected references and they're still accessible
         self.assertGreater(len(all_refs), 0, "Should have found variable references")
         for ref in all_refs:
             response = self.dap_server.request_variables(ref)
-            self.assertTrue(response['success'], f"Failed to access reference {ref}")
+            self.assertTrue(response["success"], f"Failed to access reference {ref}")



More information about the lldb-commits mailing list