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

Anthony Eid via lldb-commits lldb-commits at lists.llvm.org
Fri Jan 30 02:18:12 PST 2026


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 01/39] 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 02/39] 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 03/39] 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 04/39] 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 05/39] 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 06/39] 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 07/39] 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 08/39] 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}")

>From a52fe0777cc872932f583926aa6c9098962d84aa Mon Sep 17 00:00:00 2001
From: Anthony <hello at anthonyeid.me>
Date: Thu, 28 Aug 2025 02:33:27 -0400
Subject: [PATCH 09/39] Remove unused global defines and set First Var Ref to 1

---
 lldb/tools/lldb-dap/Variables.h | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/lldb/tools/lldb-dap/Variables.h b/lldb/tools/lldb-dap/Variables.h
index a8fad707e2e08..2ea386d8d50c9 100644
--- a/lldb/tools/lldb-dap/Variables.h
+++ b/lldb/tools/lldb-dap/Variables.h
@@ -14,10 +14,7 @@
 #include "llvm/ADT/DenseMap.h"
 #include <map>
 
-#define VARREF_FIRST_VAR_IDX (int64_t)4
-#define VARREF_LOCALS (int64_t)1
-#define VARREF_GLOBALS (int64_t)2
-#define VARREF_REGS (int64_t)3
+#define VARREF_FIRST_VAR_IDX (int64_t)1
 
 namespace lldb_dap {
 

>From 5f47586f82d1df6f7c3a4f8335c46cd191262bfa Mon Sep 17 00:00:00 2001
From: Anthony <hello at anthonyeid.me>
Date: Thu, 28 Aug 2025 18:12:23 -0400
Subject: [PATCH 10/39] Use scopekind instead of hardcoded comparisons in
 CreateScope

---
 .../lldb-dap/Handler/ScopesRequestHandler.cpp | 25 +++++++++++++------
 1 file changed, 18 insertions(+), 7 deletions(-)

diff --git a/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
index 553a7b30adba2..2073eb6f9fc76 100644
--- a/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
@@ -8,6 +8,7 @@
 
 #include "DAP.h"
 #include "RequestHandler.h"
+#include "Variables.h"
 
 using namespace lldb_dap::protocol;
 namespace lldb_dap {
@@ -29,10 +30,9 @@ namespace lldb_dap {
 ///
 /// \return
 ///     A `protocol::Scope`
-Scope CreateScope(const llvm::StringRef name, int64_t variablesReference,
+Scope CreateScope(const ScopeKind kind, int64_t variablesReference,
                   int64_t namedVariables, bool expensive) {
   Scope scope;
-  scope.name = name;
 
   // TODO: Support "arguments" and "return value" scope.
   // At the moment lldb-dap includes the arguments and return_value  into the
@@ -41,10 +41,21 @@ 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 (name == "Locals")
+  switch (kind) {
+  case ScopeKind::Locals:
     scope.presentationHint = Scope::eScopePresentationHintLocals;
-  else if (name == "Registers")
+    scope.name = "Locals";
+    break;
+  case ScopeKind::Globals:
+    scope.name = "Globals";
+    break;
+  case ScopeKind::Registers:
     scope.presentationHint = Scope::eScopePresentationHintRegisters;
+    scope.name = "Registers";
+    break;
+  default:
+    llvm_unreachable("Unhandled ScopeKind");
+  }
 
   scope.variablesReference = variablesReference;
   scope.namedVariables = namedVariables;
@@ -84,18 +95,18 @@ ScopesRequestHandler::Run(const ScopesArguments &args) const {
   std::vector<Scope> scopes = {};
 
   int64_t variable_reference = dap.variables.GetNewVariableReference(false);
-  scopes.push_back(CreateScope("Locals", variable_reference,
+  scopes.push_back(CreateScope(ScopeKind::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,
+  scopes.push_back(CreateScope(ScopeKind::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,
+  scopes.push_back(CreateScope(ScopeKind::Registers, variable_reference,
                                dap.variables.registers.GetSize(), false));
 
   dap.variables.AddScopeKind(variable_reference, ScopeKind::Registers,

>From b7548f1e8d1bff888b3987d78ad694db1a9e310f Mon Sep 17 00:00:00 2001
From: Anthony <hello at anthonyeid.me>
Date: Fri, 29 Aug 2025 01:43:41 -0400
Subject: [PATCH 11/39] Stop storing duplicate scopes in variables

I got rid of a variables locals, globals, and register fields and added
some helper methods to access scopes based on a var_ref or a frame_id
and a ScopeKind variant
---
 .../Handler/EvaluateRequestHandler.cpp        |  1 -
 .../lldb-dap/Handler/ScopesRequestHandler.cpp | 25 ++++----
 .../Handler/VariablesRequestHandler.cpp       | 13 ++--
 lldb/tools/lldb-dap/SourceBreakpoint.cpp      |  1 -
 lldb/tools/lldb-dap/Variables.cpp             | 64 +++++++++++++------
 lldb/tools/lldb-dap/Variables.h               | 21 ++++--
 6 files changed, 79 insertions(+), 46 deletions(-)

diff --git a/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp
index e207f830183b6..e1556846dff19 100644
--- a/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp
@@ -162,7 +162,6 @@ 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 2073eb6f9fc76..1f4641340d234 100644
--- a/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
@@ -30,8 +30,8 @@ namespace lldb_dap {
 ///
 /// \return
 ///     A `protocol::Scope`
-Scope CreateScope(const ScopeKind kind, int64_t variablesReference,
-                  int64_t namedVariables, bool expensive) {
+static Scope CreateScope(const ScopeKind kind, int64_t variablesReference,
+                         int64_t namedVariables, bool expensive) {
   Scope scope;
 
   // TODO: Support "arguments" and "return value" scope.
@@ -53,8 +53,6 @@ Scope CreateScope(const ScopeKind kind, int64_t variablesReference,
     scope.presentationHint = Scope::eScopePresentationHintRegisters;
     scope.name = "Registers";
     break;
-  default:
-    llvm_unreachable("Unhandled ScopeKind");
   }
 
   scope.variablesReference = variablesReference;
@@ -90,24 +88,27 @@ ScopesRequestHandler::Run(const ScopesArguments &args) const {
   uint32_t frame_id = frame.GetFrameID();
 
   dap.variables.ReadyFrame(frame_id, frame);
-  dap.variables.SwitchFrame(frame_id);
 
-  std::vector<Scope> scopes = {};
+  std::vector<protocol::Scope> scopes = {};
 
   int64_t variable_reference = dap.variables.GetNewVariableReference(false);
-  scopes.push_back(CreateScope(ScopeKind::Locals, variable_reference,
-                               dap.variables.locals.GetSize(), false));
+  scopes.push_back(CreateScope(
+      ScopeKind::Locals, variable_reference,
+      dap.variables.GetScope(frame_id, ScopeKind::Locals)->GetSize(), false));
 
   dap.variables.AddScopeKind(variable_reference, ScopeKind::Locals, frame_id);
 
   variable_reference = dap.variables.GetNewVariableReference(false);
-  scopes.push_back(CreateScope(ScopeKind::Globals, variable_reference,
-                               dap.variables.globals.GetSize(), false));
+  scopes.push_back(CreateScope(
+      ScopeKind::Globals, variable_reference,
+      dap.variables.GetScope(frame_id, ScopeKind::Globals)->GetSize(), false));
   dap.variables.AddScopeKind(variable_reference, ScopeKind::Globals, frame_id);
 
   variable_reference = dap.variables.GetNewVariableReference(false);
-  scopes.push_back(CreateScope(ScopeKind::Registers, variable_reference,
-                               dap.variables.registers.GetSize(), false));
+  scopes.push_back(CreateScope(
+      ScopeKind::Registers, variable_reference,
+      dap.variables.GetScope(frame_id, ScopeKind::Registers)->GetSize(),
+      false));
 
   dap.variables.AddScopeKind(variable_reference, ScopeKind::Registers,
                              frame_id);
diff --git a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
index 3d99983ac0c83..ca5da1e78a79f 100644
--- a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
@@ -11,6 +11,7 @@
 #include "Handler/RequestHandler.h"
 #include "JSONUtils.h"
 #include "ProtocolUtils.h"
+#include "Variables.h"
 
 using namespace llvm;
 using namespace lldb_dap::protocol;
@@ -38,15 +39,16 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
     int64_t start_idx = 0;
     int64_t num_children = 0;
 
-    std::optional<ScopeKind> scope_kind = dap.variables.GetScopeKind(var_ref);
-    if (scope_kind && *scope_kind == ScopeKind::Registers) {
+    std::optional<ScopeData> scope_data = dap.variables.GetScopeKind(var_ref);
+    if (scope_data.has_value() && scope_data->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
       // format was set to the default format or if it was hex as some registers
       // have formats set for them.
       const uint32_t addr_size = dap.target.GetProcess().GetAddressByteSize();
-      lldb::SBValue reg_set = dap.variables.registers.GetValueAtIndex(0);
+      lldb::SBValue reg_set = scope_data->scope.GetValueAtIndex(0);
       const uint32_t num_regs = reg_set.GetNumChildren();
       for (uint32_t reg_idx = 0; reg_idx < num_regs; ++reg_idx) {
         lldb::SBValue reg = reg_set.GetChildAtIndex(reg_idx);
@@ -59,7 +61,8 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
     }
 
     num_children = top_scope->GetSize();
-    if (num_children == 0 && scope_kind && *scope_kind == ScopeKind::Locals) {
+    if (num_children == 0 && scope_data &&
+        scope_data->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.
@@ -95,7 +98,7 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
     }
 
     // Show return value if there is any ( in the local top frame )
-    if (scope_kind && *scope_kind == ScopeKind::Locals) {
+    if (scope_data && scope_data->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/SourceBreakpoint.cpp b/lldb/tools/lldb-dap/SourceBreakpoint.cpp
index 843a5eb09c7ae..6f776f4d5f429 100644
--- a/lldb/tools/lldb-dap/SourceBreakpoint.cpp
+++ b/lldb/tools/lldb-dap/SourceBreakpoint.cpp
@@ -10,7 +10,6 @@
 #include "BreakpointBase.h"
 #include "DAP.h"
 #include "JSONUtils.h"
-#include "ProtocolUtils.h"
 #include "lldb/API/SBBreakpoint.h"
 #include "lldb/API/SBFileSpec.h"
 #include "lldb/API/SBFileSpecList.h"
diff --git a/lldb/tools/lldb-dap/Variables.cpp b/lldb/tools/lldb-dap/Variables.cpp
index b5cd9d69d385f..82e8950c2deb9 100644
--- a/lldb/tools/lldb-dap/Variables.cpp
+++ b/lldb/tools/lldb-dap/Variables.cpp
@@ -9,6 +9,9 @@
 #include "Variables.h"
 #include "JSONUtils.h"
 #include "lldb/API/SBFrame.h"
+#include "lldb/API/SBValueList.h"
+#include <cstdint>
+#include <optional>
 
 using namespace lldb_dap;
 
@@ -21,26 +24,24 @@ lldb::SBValueList *Variables::GetTopLevelScope(int64_t variablesReference) {
   ScopeKind scope_kind = iter->second.first;
   uint32_t frame_id = iter->second.second;
 
-  if (!SwitchFrame(frame_id)) {
+  auto frame_iter = m_frames.find(frame_id);
+  if (frame_iter == m_frames.end()) {
     return nullptr;
   }
 
   switch (scope_kind) {
   case lldb_dap::ScopeKind::Locals:
-    return &locals;
+    return &std::get<0>(frame_iter->second);
   case lldb_dap::ScopeKind::Globals:
-    return &globals;
+    return &std::get<1>(frame_iter->second);
   case lldb_dap::ScopeKind::Registers:
-    return ®isters;
+    return &std::get<2>(frame_iter->second);
   }
 
   return nullptr;
 }
 
 void Variables::Clear() {
-  locals.Clear();
-  globals.Clear();
-  registers.Clear();
   m_referencedvariables.clear();
   m_scope_kinds.clear();
   m_frames.clear();
@@ -120,30 +121,51 @@ lldb::SBValue Variables::FindVariable(uint64_t variablesReference,
   return variable;
 }
 
-std::optional<ScopeKind>
+std::optional<ScopeData>
 Variables::GetScopeKind(const int64_t variablesReference) {
-  auto iter = m_scope_kinds.find(variablesReference);
-  if (iter != m_scope_kinds.end()) {
-    return iter->second.first;
+  auto scope_kind_iter = m_scope_kinds.find(variablesReference);
+  if (scope_kind_iter == m_scope_kinds.end()) {
+    return std::nullopt;
+  }
+
+  auto scope_iter = m_frames.find(scope_kind_iter->second.second);
+  if (scope_iter == m_frames.end()) {
+    return std::nullopt;
+  }
+
+  switch (scope_kind_iter->second.first) {
+  case lldb_dap::ScopeKind::Locals:
+    return ScopeData(scope_kind_iter->second.first,
+                     std::get<0>(scope_iter->second));
+  case lldb_dap::ScopeKind::Globals:
+    return ScopeData(scope_kind_iter->second.first,
+                     std::get<1>(scope_iter->second));
+  case lldb_dap::ScopeKind::Registers:
+    return ScopeData(scope_kind_iter->second.first,
+                     std::get<2>(scope_iter->second));
   }
 
   return std::nullopt;
 }
 
-bool Variables::SwitchFrame(const uint32_t frame_id) {
-  auto iter = m_frames.find(frame_id);
+lldb::SBValueList *Variables::GetScope(const uint32_t frame_id,
+                                       const ScopeKind kind) {
 
-  if (iter == m_frames.end()) {
-    return false;
+  auto frame = m_frames.find(frame_id);
+  if (m_frames.find(frame_id) == m_frames.end()) {
+    return nullptr;
   }
 
-  auto [frame_locals, frame_globals, frame_registers] = iter->second;
-
-  locals = frame_locals;
-  globals = frame_globals;
-  registers = frame_registers;
+  switch (kind) {
+  case ScopeKind::Locals:
+    return &std::get<0>(frame->second);
+  case ScopeKind::Globals:
+    return &std::get<1>(frame->second);
+  case ScopeKind::Registers:
+    return &std::get<2>(frame->second);
+  }
 
-  return true;
+  return nullptr;
 }
 
 void Variables::ReadyFrame(uint32_t frame_id, lldb::SBFrame &frame) {
diff --git a/lldb/tools/lldb-dap/Variables.h b/lldb/tools/lldb-dap/Variables.h
index 2ea386d8d50c9..e001d688bd039 100644
--- a/lldb/tools/lldb-dap/Variables.h
+++ b/lldb/tools/lldb-dap/Variables.h
@@ -13,6 +13,7 @@
 #include "lldb/API/SBValueList.h"
 #include "llvm/ADT/DenseMap.h"
 #include <map>
+#include <utility>
 
 #define VARREF_FIRST_VAR_IDX (int64_t)1
 
@@ -20,11 +21,15 @@ namespace lldb_dap {
 
 enum ScopeKind { Locals, Globals, Registers };
 
-struct Variables {
-  lldb::SBValueList locals;
-  lldb::SBValueList globals;
-  lldb::SBValueList registers;
+struct ScopeData {
+  ScopeKind kind;
+  lldb::SBValueList scope;
+
+  ScopeData(ScopeKind kind, lldb::SBValueList scope)
+      : kind(kind), scope(scope) {}
+};
 
+struct Variables {
   /// Check if \p var_ref points to a variable that should persist for the
   /// entire duration of the debug session, e.g. repl expandable variables
   static bool IsPermanentVariableReference(int64_t var_ref);
@@ -39,6 +44,8 @@ struct Variables {
   /// If \p var_ref is invalid an empty SBValue is returned.
   lldb::SBValue GetVariable(int64_t var_ref) const;
 
+  lldb::SBValueList *GetScope(const uint32_t frame_id, const ScopeKind kind);
+
   /// Insert a new \p variable.
   /// \return variableReference assigned to this expandable variable.
   int64_t InsertVariable(lldb::SBValue variable, bool is_permanent);
@@ -47,10 +54,9 @@ 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);
+  std::optional<ScopeData> GetScopeKind(const int64_t variablesReference);
 
   /// Clear all scope variables and non-permanent expandable variables.
   void Clear();
@@ -63,6 +69,7 @@ struct Variables {
   static constexpr int64_t PermanentVariableStartIndex = (1ll << 32);
   int64_t m_next_temporary_var_ref{VARREF_FIRST_VAR_IDX};
 
+  // Variable Reference,                 frame_id
   std::map<int64_t, std::pair<ScopeKind, uint32_t>> m_scope_kinds;
 
   /// Variables that are alive in this stop state.
@@ -73,6 +80,8 @@ struct Variables {
   /// These are the variables evaluated from debug console REPL.
   llvm::DenseMap<int64_t, lldb::SBValue> m_referencedpermanent_variables;
 
+  /// Key = frame_id
+  /// Value = (locals, globals Registers) scopes
   std::map<uint32_t,
            std::tuple<lldb::SBValueList, lldb::SBValueList, lldb::SBValueList>>
       m_frames;

>From c8196a27ea8ff5b89a5d83aa3ee85f227858a4fe Mon Sep 17 00:00:00 2001
From: Anthony <hello at anthonyeid.me>
Date: Fri, 29 Aug 2025 02:01:04 -0400
Subject: [PATCH 12/39] Apply Python formatting to fix CI darker checks

---
 .../test/tools/lldb-dap/lldbdap_testcase.py   |  5 +--
 .../lldb-dap/variables/TestDAP_variables.py   | 34 +++++++++++++------
 2 files changed, 27 insertions(+), 12 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 a26be6e5d1bfb..9c363e54e747c 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
@@ -369,10 +369,11 @@ def set_global(self, name, value, id=None):
         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 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()
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 705863edbc861..6d83661ede922 100644
--- a/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py
+++ b/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py
@@ -342,26 +342,36 @@ def do_test_scopes_variables_setVariable_evaluate(
 
         # Now we verify that we correctly change the name of a variable with and without differentiator suffix
         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(local_scope_ref, "x @ main.cpp:0", 9)["success"]
+            self.dap_server.request_setVariable(local_scope_ref, "x2", 9)["success"]
+        )
+        self.assertFalse(
+            self.dap_server.request_setVariable(local_scope_ref, "x @ main.cpp:0", 9)[
+                "success"
+            ]
         )
 
         self.assertTrue(
-            self.dap_server.request_setVariable(local_scope_ref, "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(local_scope_ref, "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(local_scope_ref, "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(local_scope_ref, "x @ main.cpp:23", "invalid")[
-                "success"
-            ]
+            self.dap_server.request_setVariable(
+                local_scope_ref, "x @ main.cpp:23", "invalid"
+            )["success"]
         )
 
         verify_locals["x @ main.cpp:19"]["equals"]["value"] = "19"
@@ -371,7 +381,9 @@ 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(local_scope_ref, "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())
@@ -710,7 +722,9 @@ def test_return_variables(self):
                 break
 
         self.assertFalse(
-            self.dap_server.request_setVariable(self.get_locals_scope_reference(), "(Return Value)", 20)["success"]
+            self.dap_server.request_setVariable(
+                self.get_locals_scope_reference(), "(Return Value)", 20
+            )["success"]
         )
 
     @skipIfWindows

>From 23879526b6304f21dac5ea4e584e347f6d8fe0a5 Mon Sep 17 00:00:00 2001
From: Anthony <hello at anthonyeid.me>
Date: Fri, 29 Aug 2025 02:30:46 -0400
Subject: [PATCH 13/39] Hopefully fix failing tests (They're not running on my
 end)

---
 lldb/unittests/DAP/VariablesTest.cpp | 18 ++++++------------
 1 file changed, 6 insertions(+), 12 deletions(-)

diff --git a/lldb/unittests/DAP/VariablesTest.cpp b/lldb/unittests/DAP/VariablesTest.cpp
index 40884e89e0c54..46b171a443586 100644
--- a/lldb/unittests/DAP/VariablesTest.cpp
+++ b/lldb/unittests/DAP/VariablesTest.cpp
@@ -71,11 +71,6 @@ TEST_F(VariablesTest, GetTopLevelScope_ReturnsCorrectScope) {
   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());
 
   int64_t locals_ref = vars.GetNewVariableReference(false);
   vars.AddScopeKind(locals_ref, ScopeKind::Locals, frame_id);
@@ -86,9 +81,12 @@ TEST_F(VariablesTest, GetTopLevelScope_ReturnsCorrectScope) {
   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(locals_ref),
+            &vars.GetScope(frame_id, lldb_dap::ScopeKind::Locals));
+  EXPECT_EQ(vars.GetTopLevelScope(globals_ref),
+            &vars.GetScope(frame_id, lldb_dap::ScopeKind::Globals));
+  EXPECT_EQ(vars.GetTopLevelScope(registers_ref),
+            &vars.GetScope(frame_id, lldb_dap::ScopeKind::Registers));
   EXPECT_EQ(vars.GetTopLevelScope(9999), nullptr);
 }
 
@@ -97,10 +95,6 @@ TEST_F(VariablesTest, FindVariable_LocalsByName) {
   uint32_t frame_id = 0;
 
   vars.ReadyFrame(frame_id, frame);
-  vars.SwitchFrame(frame_id);
-
-  lldb::SBValue dummy;
-  vars.locals.Append(dummy);
 
   int64_t locals_ref = vars.GetNewVariableReference(false);
   vars.AddScopeKind(locals_ref, ScopeKind::Locals, frame_id);

>From bc0032595afe685a75c25dab514f7d4100c8b440 Mon Sep 17 00:00:00 2001
From: Anthony <hello at anthonyeid.me>
Date: Fri, 29 Aug 2025 12:20:49 -0400
Subject: [PATCH 14/39] Actually fix the failing tests for real

---
 lldb/unittests/DAP/VariablesTest.cpp | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/lldb/unittests/DAP/VariablesTest.cpp b/lldb/unittests/DAP/VariablesTest.cpp
index 46b171a443586..2c5e608c30675 100644
--- a/lldb/unittests/DAP/VariablesTest.cpp
+++ b/lldb/unittests/DAP/VariablesTest.cpp
@@ -82,11 +82,11 @@ TEST_F(VariablesTest, GetTopLevelScope_ReturnsCorrectScope) {
   vars.AddScopeKind(registers_ref, ScopeKind::Registers, frame_id);
 
   EXPECT_EQ(vars.GetTopLevelScope(locals_ref),
-            &vars.GetScope(frame_id, lldb_dap::ScopeKind::Locals));
+            vars.GetScope(frame_id, lldb_dap::ScopeKind::Locals));
   EXPECT_EQ(vars.GetTopLevelScope(globals_ref),
-            &vars.GetScope(frame_id, lldb_dap::ScopeKind::Globals));
+            vars.GetScope(frame_id, lldb_dap::ScopeKind::Globals));
   EXPECT_EQ(vars.GetTopLevelScope(registers_ref),
-            &vars.GetScope(frame_id, lldb_dap::ScopeKind::Registers));
+            vars.GetScope(frame_id, lldb_dap::ScopeKind::Registers));
   EXPECT_EQ(vars.GetTopLevelScope(9999), nullptr);
 }
 
@@ -100,6 +100,7 @@ TEST_F(VariablesTest, FindVariable_LocalsByName) {
   vars.AddScopeKind(locals_ref, ScopeKind::Locals, frame_id);
 
   lldb::SBValue found = vars.FindVariable(locals_ref, "");
+  lldb::SBValue dummy;
 
   EXPECT_EQ(found.IsValid(), dummy.IsValid());
 }

>From facb48958e9f426e075ebef7d95614ff6d020ff4 Mon Sep 17 00:00:00 2001
From: Anthony <hello at anthonyeid.me>
Date: Tue, 2 Sep 2025 10:47:57 -0400
Subject: [PATCH 15/39] Simplify Scopes request handler

Merge ReadyFrame and Switch Frame functions. Also get rid of
AddScopeKind function in favor of inlining it in ReadyFrame
---
 .../lldb-dap/Handler/ScopesRequestHandler.cpp | 77 +------------------
 lldb/tools/lldb-dap/JSONUtils.cpp             |  1 -
 lldb/tools/lldb-dap/JSONUtils.h               |  1 -
 lldb/tools/lldb-dap/Variables.cpp             | 72 +++++++++++++++--
 lldb/tools/lldb-dap/Variables.h               | 25 +++++-
 5 files changed, 90 insertions(+), 86 deletions(-)

diff --git a/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
index 1f4641340d234..0f0c80383bf2d 100644
--- a/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
@@ -13,55 +13,6 @@
 using namespace lldb_dap::protocol;
 namespace lldb_dap {
 
-/// Creates a `protocol::Scope` struct.
-///
-///
-/// \param[in] name
-///     The value to place into the "name" key
-///
-/// \param[in] variablesReference
-///     The value to place into the "variablesReference" key
-///
-/// \param[in] namedVariables
-///     The value to place into the "namedVariables" key
-///
-/// \param[in] expensive
-///     The value to place into the "expensive" key
-///
-/// \return
-///     A `protocol::Scope`
-static Scope CreateScope(const ScopeKind kind, int64_t variablesReference,
-                         int64_t namedVariables, bool expensive) {
-  Scope scope;
-
-  // TODO: Support "arguments" and "return value" scope.
-  // At the moment lldb-dap includes the arguments and return_value  into the
-  // "locals" scope.
-  // vscode only expands the first non-expensive scope, this causes friction
-  // 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.
-  switch (kind) {
-  case ScopeKind::Locals:
-    scope.presentationHint = Scope::eScopePresentationHintLocals;
-    scope.name = "Locals";
-    break;
-  case ScopeKind::Globals:
-    scope.name = "Globals";
-    break;
-  case ScopeKind::Registers:
-    scope.presentationHint = Scope::eScopePresentationHintRegisters;
-    scope.name = "Registers";
-    break;
-  }
-
-  scope.variablesReference = variablesReference;
-  scope.namedVariables = namedVariables;
-  scope.expensive = expensive;
-
-  return scope;
-}
-
 llvm::Expected<ScopesResponseBody>
 ScopesRequestHandler::Run(const ScopesArguments &args) const {
   lldb::SBFrame frame = dap.GetLLDBFrame(args.frameId);
@@ -86,32 +37,8 @@ ScopesRequestHandler::Run(const ScopesArguments &args) const {
   }
 
   uint32_t frame_id = frame.GetFrameID();
-
-  dap.variables.ReadyFrame(frame_id, frame);
-
-  std::vector<protocol::Scope> scopes = {};
-
-  int64_t variable_reference = dap.variables.GetNewVariableReference(false);
-  scopes.push_back(CreateScope(
-      ScopeKind::Locals, variable_reference,
-      dap.variables.GetScope(frame_id, ScopeKind::Locals)->GetSize(), false));
-
-  dap.variables.AddScopeKind(variable_reference, ScopeKind::Locals, frame_id);
-
-  variable_reference = dap.variables.GetNewVariableReference(false);
-  scopes.push_back(CreateScope(
-      ScopeKind::Globals, variable_reference,
-      dap.variables.GetScope(frame_id, ScopeKind::Globals)->GetSize(), false));
-  dap.variables.AddScopeKind(variable_reference, ScopeKind::Globals, frame_id);
-
-  variable_reference = dap.variables.GetNewVariableReference(false);
-  scopes.push_back(CreateScope(
-      ScopeKind::Registers, variable_reference,
-      dap.variables.GetScope(frame_id, ScopeKind::Registers)->GetSize(),
-      false));
-
-  dap.variables.AddScopeKind(variable_reference, ScopeKind::Registers,
-                             frame_id);
+  std::vector<protocol::Scope> scopes =
+      dap.variables.ReadyFrame(frame_id, frame);
 
   return ScopesResponseBody{std::move(scopes)};
 }
diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp
index ad1f035caaf54..a4c42346d31f8 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -11,7 +11,6 @@
 #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"
diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h
index 6575411acd878..e9094f67b94ec 100644
--- a/lldb/tools/lldb-dap/JSONUtils.h
+++ b/lldb/tools/lldb-dap/JSONUtils.h
@@ -9,7 +9,6 @@
 #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 82e8950c2deb9..4566931d0e7cc 100644
--- a/lldb/tools/lldb-dap/Variables.cpp
+++ b/lldb/tools/lldb-dap/Variables.cpp
@@ -8,13 +8,49 @@
 
 #include "Variables.h"
 #include "JSONUtils.h"
+#include "Protocol/ProtocolTypes.h"
 #include "lldb/API/SBFrame.h"
 #include "lldb/API/SBValueList.h"
 #include <cstdint>
 #include <optional>
+#include <vector>
 
 using namespace lldb_dap;
 
+namespace lldb_dap {
+
+protocol::Scope CreateScope(const ScopeKind kind, int64_t variablesReference,
+                            int64_t namedVariables, bool expensive) {
+  protocol::Scope scope;
+
+  // TODO: Support "arguments" and "return value" scope.
+  // At the moment lldb-he arguments and return_value  into the
+  // "locals" scope.
+  // vscode only expands the first non-expensive scope, this causes friction
+  // 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.
+  switch (kind) {
+  case ScopeKind::Locals:
+    scope.presentationHint = protocol::Scope::eScopePresentationHintLocals;
+    scope.name = "Locals";
+    break;
+  case ScopeKind::Globals:
+    scope.name = "Globals";
+    break;
+  case ScopeKind::Registers:
+    scope.presentationHint = protocol::Scope::eScopePresentationHintRegisters;
+    scope.name = "Registers";
+    break;
+  }
+
+  scope.variablesReference = variablesReference;
+  scope.namedVariables = namedVariables;
+  scope.expensive = expensive;
+
+  return scope;
+}
+
 lldb::SBValueList *Variables::GetTopLevelScope(int64_t variablesReference) {
   auto iter = m_scope_kinds.find(variablesReference);
   if (iter == m_scope_kinds.end()) {
@@ -168,7 +204,9 @@ lldb::SBValueList *Variables::GetScope(const uint32_t frame_id,
   return nullptr;
 }
 
-void Variables::ReadyFrame(uint32_t frame_id, lldb::SBFrame &frame) {
+std::vector<protocol::Scope> Variables::ReadyFrame(uint32_t frame_id,
+                                                   lldb::SBFrame &frame) {
+
   if (m_frames.find(frame_id) == m_frames.end()) {
 
     auto locals = frame.GetVariables(/*arguments=*/true,
@@ -186,10 +224,34 @@ void Variables::ReadyFrame(uint32_t frame_id, lldb::SBFrame &frame) {
     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) {
+  std::vector<protocol::Scope> scopes = {};
+
+  int64_t variable_reference = GetNewVariableReference(false);
+
+  scopes.push_back(CreateScope(ScopeKind::Locals, variable_reference,
+                               GetScope(frame_id, ScopeKind::Locals)->GetSize(),
+                               false));
 
-  m_scope_kinds[variable_reference] = std::make_pair(kind, frame_id);
+  m_scope_kinds[variable_reference] =
+      std::make_pair(ScopeKind::Locals, frame_id);
+
+  variable_reference = GetNewVariableReference(false);
+  scopes.push_back(
+      CreateScope(ScopeKind::Globals, variable_reference,
+                  GetScope(frame_id, ScopeKind::Globals)->GetSize(), false));
+  m_scope_kinds[variable_reference] =
+      std::make_pair(ScopeKind::Globals, frame_id);
+
+  variable_reference = GetNewVariableReference(false);
+  scopes.push_back(
+      CreateScope(ScopeKind::Registers, variable_reference,
+                  GetScope(frame_id, ScopeKind::Registers)->GetSize(), false));
+
+  m_scope_kinds[variable_reference] =
+      std::make_pair(ScopeKind::Registers, frame_id);
+
+  return scopes;
 }
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Variables.h b/lldb/tools/lldb-dap/Variables.h
index e001d688bd039..2470cd030b47d 100644
--- a/lldb/tools/lldb-dap/Variables.h
+++ b/lldb/tools/lldb-dap/Variables.h
@@ -9,6 +9,7 @@
 #ifndef LLDB_TOOLS_LLDB_DAP_VARIABLES_H
 #define LLDB_TOOLS_LLDB_DAP_VARIABLES_H
 
+#include "Protocol/ProtocolTypes.h"
 #include "lldb/API/SBValue.h"
 #include "lldb/API/SBValueList.h"
 #include "llvm/ADT/DenseMap.h"
@@ -20,6 +21,24 @@
 namespace lldb_dap {
 
 enum ScopeKind { Locals, Globals, Registers };
+/// 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] namedVariables
+///     The value to place into the "namedVariables" key
+///
+/// \param[in] expensive
+///     The value to place into the "expensive" key
+///
+/// \return
+///     A `protocol::Scope`
+protocol::Scope CreateScope(const ScopeKind kind, int64_t variablesReference,
+                            int64_t namedVariables, bool expensive);
 
 struct ScopeData {
   ScopeKind kind;
@@ -55,15 +74,13 @@ struct Variables {
   lldb::SBValue FindVariable(uint64_t variablesReference, llvm::StringRef name);
 
   /// Initialize a frame if it hasn't been already, otherwise do nothing
-  void ReadyFrame(uint32_t frame_id, lldb::SBFrame &frame);
+  std::vector<protocol::Scope> ReadyFrame(uint32_t frame_id,
+                                          lldb::SBFrame &frame);
   std::optional<ScopeData> 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);

>From c4df0a8aaca50ef1c473d28643f8788b691e6694 Mon Sep 17 00:00:00 2001
From: Anthony <hello at anthonyeid.me>
Date: Tue, 2 Sep 2025 14:20:09 -0400
Subject: [PATCH 16/39] Fix variables test

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

diff --git a/lldb/unittests/DAP/VariablesTest.cpp b/lldb/unittests/DAP/VariablesTest.cpp
index 2c5e608c30675..8857d420ced20 100644
--- a/lldb/unittests/DAP/VariablesTest.cpp
+++ b/lldb/unittests/DAP/VariablesTest.cpp
@@ -72,20 +72,13 @@ TEST_F(VariablesTest, GetTopLevelScope_ReturnsCorrectScope) {
 
   vars.ReadyFrame(frame_id, frame);
 
-  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);
+  int64_t next_variable_ref = vars.GetNewVariableReference(false);
 
-  EXPECT_EQ(vars.GetTopLevelScope(locals_ref),
+  EXPECT_EQ(vars.GetTopLevelScope(next_variable_ref - 3),
             vars.GetScope(frame_id, lldb_dap::ScopeKind::Locals));
-  EXPECT_EQ(vars.GetTopLevelScope(globals_ref),
+  EXPECT_EQ(vars.GetTopLevelScope(next_variable_ref - 2),
             vars.GetScope(frame_id, lldb_dap::ScopeKind::Globals));
-  EXPECT_EQ(vars.GetTopLevelScope(registers_ref),
+  EXPECT_EQ(vars.GetTopLevelScope(next_variable_ref - 1),
             vars.GetScope(frame_id, lldb_dap::ScopeKind::Registers));
   EXPECT_EQ(vars.GetTopLevelScope(9999), nullptr);
 }
@@ -97,9 +90,8 @@ TEST_F(VariablesTest, FindVariable_LocalsByName) {
   vars.ReadyFrame(frame_id, frame);
 
   int64_t locals_ref = vars.GetNewVariableReference(false);
-  vars.AddScopeKind(locals_ref, ScopeKind::Locals, frame_id);
 
-  lldb::SBValue found = vars.FindVariable(locals_ref, "");
+  lldb::SBValue found = vars.FindVariable(locals_ref - 1, "");
   lldb::SBValue dummy;
 
   EXPECT_EQ(found.IsValid(), dummy.IsValid());

>From 348f21ca0507d08dd30c5ebb4929b5007a098a85 Mon Sep 17 00:00:00 2001
From: Anthony <hello at anthonyeid.me>
Date: Fri, 5 Sep 2025 01:47:01 -0400
Subject: [PATCH 17/39] Simplify ScopeData and improve naming in ready frame
 function

---
 lldb/tools/lldb-dap/Variables.cpp | 36 +++++++++++++++----------------
 lldb/tools/lldb-dap/Variables.h   |  3 ---
 2 files changed, 18 insertions(+), 21 deletions(-)

diff --git a/lldb/tools/lldb-dap/Variables.cpp b/lldb/tools/lldb-dap/Variables.cpp
index 4566931d0e7cc..8f0c48f4e1ca6 100644
--- a/lldb/tools/lldb-dap/Variables.cpp
+++ b/lldb/tools/lldb-dap/Variables.cpp
@@ -169,16 +169,19 @@ Variables::GetScopeKind(const int64_t variablesReference) {
     return std::nullopt;
   }
 
+  ScopeData scope_data = ScopeData();
+  scope_data.kind = scope_kind_iter->second.first;
+
   switch (scope_kind_iter->second.first) {
   case lldb_dap::ScopeKind::Locals:
-    return ScopeData(scope_kind_iter->second.first,
-                     std::get<0>(scope_iter->second));
+    scope_data.scope = std::get<0>(scope_iter->second);
+    return scope_data;
   case lldb_dap::ScopeKind::Globals:
-    return ScopeData(scope_kind_iter->second.first,
-                     std::get<1>(scope_iter->second));
+    scope_data.scope = std::get<1>(scope_iter->second);
+    return scope_data;
   case lldb_dap::ScopeKind::Registers:
-    return ScopeData(scope_kind_iter->second.first,
-                     std::get<2>(scope_iter->second));
+    scope_data.scope = std::get<2>(scope_iter->second);
+    return scope_data;
   }
 
   return std::nullopt;
@@ -227,29 +230,26 @@ std::vector<protocol::Scope> Variables::ReadyFrame(uint32_t frame_id,
 
   std::vector<protocol::Scope> scopes = {};
 
-  int64_t variable_reference = GetNewVariableReference(false);
+  int64_t locals_ref = GetNewVariableReference(false);
 
-  scopes.push_back(CreateScope(ScopeKind::Locals, variable_reference,
+  scopes.push_back(CreateScope(ScopeKind::Locals, locals_ref,
                                GetScope(frame_id, ScopeKind::Locals)->GetSize(),
                                false));
 
-  m_scope_kinds[variable_reference] =
-      std::make_pair(ScopeKind::Locals, frame_id);
+  m_scope_kinds[locals_ref] = std::make_pair(ScopeKind::Locals, frame_id);
 
-  variable_reference = GetNewVariableReference(false);
+  int64_t globals_ref = GetNewVariableReference(false);
   scopes.push_back(
-      CreateScope(ScopeKind::Globals, variable_reference,
+      CreateScope(ScopeKind::Globals, globals_ref,
                   GetScope(frame_id, ScopeKind::Globals)->GetSize(), false));
-  m_scope_kinds[variable_reference] =
-      std::make_pair(ScopeKind::Globals, frame_id);
+  m_scope_kinds[globals_ref] = std::make_pair(ScopeKind::Globals, frame_id);
 
-  variable_reference = GetNewVariableReference(false);
+  int64_t registers_ref = GetNewVariableReference(false);
   scopes.push_back(
-      CreateScope(ScopeKind::Registers, variable_reference,
+      CreateScope(ScopeKind::Registers, registers_ref,
                   GetScope(frame_id, ScopeKind::Registers)->GetSize(), false));
 
-  m_scope_kinds[variable_reference] =
-      std::make_pair(ScopeKind::Registers, frame_id);
+  m_scope_kinds[registers_ref] = std::make_pair(ScopeKind::Registers, frame_id);
 
   return scopes;
 }
diff --git a/lldb/tools/lldb-dap/Variables.h b/lldb/tools/lldb-dap/Variables.h
index 2470cd030b47d..c3ce14c1661fe 100644
--- a/lldb/tools/lldb-dap/Variables.h
+++ b/lldb/tools/lldb-dap/Variables.h
@@ -43,9 +43,6 @@ protocol::Scope CreateScope(const ScopeKind kind, int64_t variablesReference,
 struct ScopeData {
   ScopeKind kind;
   lldb::SBValueList scope;
-
-  ScopeData(ScopeKind kind, lldb::SBValueList scope)
-      : kind(kind), scope(scope) {}
 };
 
 struct Variables {

>From c4155cc279323f5014dba9e4495ec4bef71e0a31 Mon Sep 17 00:00:00 2001
From: Anthony Eid <hello at anthonyeid.me>
Date: Sun, 7 Dec 2025 16:46:39 -0500
Subject: [PATCH 18/39] Switch Variables.m_frames key to use thread_id and
 frame_id as key

This addresses @ashgti feedback by making the frame key unique across
threads.
---
 .../lldb-dap/Handler/ScopesRequestHandler.cpp |  3 +-
 lldb/tools/lldb-dap/Variables.cpp             | 43 ++++++++++---------
 lldb/tools/lldb-dap/Variables.h               | 15 ++++---
 3 files changed, 31 insertions(+), 30 deletions(-)

diff --git a/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
index 0f0c80383bf2d..5a016265f770f 100644
--- a/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
@@ -36,9 +36,8 @@ ScopesRequestHandler::Run(const ScopesArguments &args) const {
     frame.GetThread().SetSelectedFrame(frame.GetFrameID());
   }
 
-  uint32_t frame_id = frame.GetFrameID();
   std::vector<protocol::Scope> scopes =
-      dap.variables.ReadyFrame(frame_id, frame);
+      dap.variables.ReadyFrame(args.frameId, frame);
 
   return ScopesResponseBody{std::move(scopes)};
 }
diff --git a/lldb/tools/lldb-dap/Variables.cpp b/lldb/tools/lldb-dap/Variables.cpp
index 8f0c48f4e1ca6..3096c27267139 100644
--- a/lldb/tools/lldb-dap/Variables.cpp
+++ b/lldb/tools/lldb-dap/Variables.cpp
@@ -58,9 +58,9 @@ lldb::SBValueList *Variables::GetTopLevelScope(int64_t variablesReference) {
   }
 
   ScopeKind scope_kind = iter->second.first;
-  uint32_t frame_id = iter->second.second;
+  uint64_t dap_frame_id = iter->second.second;
 
-  auto frame_iter = m_frames.find(frame_id);
+  auto frame_iter = m_frames.find(dap_frame_id);
   if (frame_iter == m_frames.end()) {
     return nullptr;
   }
@@ -187,11 +187,11 @@ Variables::GetScopeKind(const int64_t variablesReference) {
   return std::nullopt;
 }
 
-lldb::SBValueList *Variables::GetScope(const uint32_t frame_id,
+lldb::SBValueList *Variables::GetScope(const uint64_t dap_frame_id,
                                        const ScopeKind kind) {
 
-  auto frame = m_frames.find(frame_id);
-  if (m_frames.find(frame_id) == m_frames.end()) {
+  auto frame = m_frames.find(dap_frame_id);
+  if (m_frames.find(dap_frame_id) == m_frames.end()) {
     return nullptr;
   }
 
@@ -207,10 +207,10 @@ lldb::SBValueList *Variables::GetScope(const uint32_t frame_id,
   return nullptr;
 }
 
-std::vector<protocol::Scope> Variables::ReadyFrame(uint32_t frame_id,
+std::vector<protocol::Scope> Variables::ReadyFrame(const uint64_t dap_frame_id,
                                                    lldb::SBFrame &frame) {
 
-  if (m_frames.find(frame_id) == m_frames.end()) {
+  if (m_frames.find(dap_frame_id) == m_frames.end()) {
 
     auto locals = frame.GetVariables(/*arguments=*/true,
                                      /*locals=*/true,
@@ -224,32 +224,33 @@ std::vector<protocol::Scope> Variables::ReadyFrame(uint32_t frame_id,
 
     auto registers = frame.GetRegisters();
 
-    m_frames.insert(
-        std::make_pair(frame_id, std::make_tuple(locals, globals, registers)));
+    m_frames.insert(std::make_pair(
+        dap_frame_id, std::make_tuple(locals, globals, registers)));
   }
 
   std::vector<protocol::Scope> scopes = {};
 
   int64_t locals_ref = GetNewVariableReference(false);
 
-  scopes.push_back(CreateScope(ScopeKind::Locals, locals_ref,
-                               GetScope(frame_id, ScopeKind::Locals)->GetSize(),
-                               false));
+  scopes.push_back(
+      CreateScope(ScopeKind::Locals, locals_ref,
+                  GetScope(dap_frame_id, ScopeKind::Locals)->GetSize(), false));
 
-  m_scope_kinds[locals_ref] = std::make_pair(ScopeKind::Locals, frame_id);
+  m_scope_kinds[locals_ref] = std::make_pair(ScopeKind::Locals, dap_frame_id);
 
   int64_t globals_ref = GetNewVariableReference(false);
-  scopes.push_back(
-      CreateScope(ScopeKind::Globals, globals_ref,
-                  GetScope(frame_id, ScopeKind::Globals)->GetSize(), false));
-  m_scope_kinds[globals_ref] = std::make_pair(ScopeKind::Globals, frame_id);
+  scopes.push_back(CreateScope(
+      ScopeKind::Globals, globals_ref,
+      GetScope(dap_frame_id, ScopeKind::Globals)->GetSize(), false));
+  m_scope_kinds[globals_ref] = std::make_pair(ScopeKind::Globals, dap_frame_id);
 
   int64_t registers_ref = GetNewVariableReference(false);
-  scopes.push_back(
-      CreateScope(ScopeKind::Registers, registers_ref,
-                  GetScope(frame_id, ScopeKind::Registers)->GetSize(), false));
+  scopes.push_back(CreateScope(
+      ScopeKind::Registers, registers_ref,
+      GetScope(dap_frame_id, ScopeKind::Registers)->GetSize(), false));
 
-  m_scope_kinds[registers_ref] = std::make_pair(ScopeKind::Registers, frame_id);
+  m_scope_kinds[registers_ref] =
+      std::make_pair(ScopeKind::Registers, dap_frame_id);
 
   return scopes;
 }
diff --git a/lldb/tools/lldb-dap/Variables.h b/lldb/tools/lldb-dap/Variables.h
index c3ce14c1661fe..0e4c8685b6fde 100644
--- a/lldb/tools/lldb-dap/Variables.h
+++ b/lldb/tools/lldb-dap/Variables.h
@@ -60,7 +60,8 @@ struct Variables {
   /// If \p var_ref is invalid an empty SBValue is returned.
   lldb::SBValue GetVariable(int64_t var_ref) const;
 
-  lldb::SBValueList *GetScope(const uint32_t frame_id, const ScopeKind kind);
+  lldb::SBValueList *GetScope(const uint64_t dap_frame_id,
+                              const ScopeKind kind);
 
   /// Insert a new \p variable.
   /// \return variableReference assigned to this expandable variable.
@@ -71,7 +72,7 @@ struct Variables {
   lldb::SBValue FindVariable(uint64_t variablesReference, llvm::StringRef name);
 
   /// Initialize a frame if it hasn't been already, otherwise do nothing
-  std::vector<protocol::Scope> ReadyFrame(uint32_t frame_id,
+  std::vector<protocol::Scope> ReadyFrame(const uint64_t dap_frame_id,
                                           lldb::SBFrame &frame);
   std::optional<ScopeData> GetScopeKind(const int64_t variablesReference);
 
@@ -83,8 +84,8 @@ struct Variables {
   static constexpr int64_t PermanentVariableStartIndex = (1ll << 32);
   int64_t m_next_temporary_var_ref{VARREF_FIRST_VAR_IDX};
 
-  // Variable Reference,                 frame_id
-  std::map<int64_t, std::pair<ScopeKind, uint32_t>> m_scope_kinds;
+  // Variable Reference,                 dap_frame_id
+  std::map<int64_t, std::pair<ScopeKind, uint64_t>> m_scope_kinds;
 
   /// Variables that are alive in this stop state.
   /// Will be cleared when debuggee resumes.
@@ -94,9 +95,9 @@ struct Variables {
   /// These are the variables evaluated from debug console REPL.
   llvm::DenseMap<int64_t, lldb::SBValue> m_referencedpermanent_variables;
 
-  /// Key = frame_id
-  /// Value = (locals, globals Registers) scopes
-  std::map<uint32_t,
+  /// Key = dap_frame_id (encodes both thread index ID and frame ID)
+  /// Value = (locals, globals, registers) scopes
+  std::map<uint64_t,
            std::tuple<lldb::SBValueList, lldb::SBValueList, lldb::SBValueList>>
       m_frames;
   int64_t m_next_permanent_var_ref{PermanentVariableStartIndex};

>From a603d7c91f83a0931c79b64eda1c4b2c0a2a489a Mon Sep 17 00:00:00 2001
From: Anthony Eid <hello at anthonyeid.me>
Date: Sun, 7 Dec 2025 17:23:52 -0500
Subject: [PATCH 19/39] Add test_variable_id_uniqueness_simple regression test

---
 .../lldb-dap/variables/TestDAP_variables.py   | 36 +++++++++++++++++++
 1 file changed, 36 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 977d6ce9dac8a..d0b01b0e565a5 100644
--- a/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py
+++ b/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py
@@ -834,3 +834,39 @@ 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"
+
+        bp_line = line_number(source, "// breakpoint 3")
+        self.set_source_breakpoints(source, [bp_line])
+        self.continue_to_next_stop()
+
+        frames = self.get_stackFrames()
+        self.assertGreaterEqual(len(frames), 2, "Need at least 2 frames")
+
+        all_refs = set()
+
+        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:
+                    self.assertNotIn(
+                        ref, all_refs, f"Variable reference {ref} was reused!"
+                    )
+                    all_refs.add(ref)
+
+        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 153b19019bc5369bd801a85f13968346d135ecb7 Mon Sep 17 00:00:00 2001
From: Anthony Eid <hello at anthonyeid.me>
Date: Sun, 7 Dec 2025 20:46:05 -0500
Subject: [PATCH 20/39] Fix duplicate function definition

---
 .../Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py    | 2 --
 1 file changed, 2 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 394b228359ecd..765c93a24472b 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
@@ -374,8 +374,6 @@ def set_variable(self, varRef, name, value, id=None):
             self.verify_memory_event(response["body"].get("memoryReference"))
         return response
 
-    def set_local(self, name, value, id=None):
-        """Set a top level local variable only."""
     def set_local(self, name, value, id=None):
         """Set a top level local variable only."""
         # Get the locals scope reference dynamically

>From ce02a97a5a56a7a609e0ec1782a4e222a9952217 Mon Sep 17 00:00:00 2001
From: Anthony Eid <hello at anthonyeid.me>
Date: Fri, 9 Jan 2026 03:58:50 -0500
Subject: [PATCH 21/39] Move m_next_temporary_var_ref next to permanent var ref

---
 lldb/tools/lldb-dap/Variables.h | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lldb/tools/lldb-dap/Variables.h b/lldb/tools/lldb-dap/Variables.h
index 0e4c8685b6fde..27604c0193fa7 100644
--- a/lldb/tools/lldb-dap/Variables.h
+++ b/lldb/tools/lldb-dap/Variables.h
@@ -82,6 +82,8 @@ struct Variables {
 private:
   /// Variable_reference start index of permanent expandable variable.
   static constexpr int64_t PermanentVariableStartIndex = (1ll << 32);
+
+  int64_t m_next_permanent_var_ref{PermanentVariableStartIndex};
   int64_t m_next_temporary_var_ref{VARREF_FIRST_VAR_IDX};
 
   // Variable Reference,                 dap_frame_id
@@ -100,7 +102,6 @@ struct Variables {
   std::map<uint64_t,
            std::tuple<lldb::SBValueList, lldb::SBValueList, lldb::SBValueList>>
       m_frames;
-  int64_t m_next_permanent_var_ref{PermanentVariableStartIndex};
 };
 
 } // namespace lldb_dap

>From 02005af223ea9db138a4b041d8d4c25c33d2cd6c Mon Sep 17 00:00:00 2001
From: Anthony Eid <hello at anthonyeid.me>
Date: Fri, 9 Jan 2026 04:01:49 -0500
Subject: [PATCH 22/39] Add e prefix to scopes kind enum

---
 .../Handler/VariablesRequestHandler.cpp       |  6 +--
 lldb/tools/lldb-dap/Variables.cpp             | 51 ++++++++++---------
 lldb/tools/lldb-dap/Variables.h               | 10 ++--
 3 files changed, 34 insertions(+), 33 deletions(-)

diff --git a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
index ca5da1e78a79f..fe84b2df4f895 100644
--- a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
@@ -40,7 +40,7 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
     int64_t num_children = 0;
 
     std::optional<ScopeData> scope_data = dap.variables.GetScopeKind(var_ref);
-    if (scope_data.has_value() && scope_data->kind == ScopeKind::Registers) {
+    if (scope_data.has_value() && scope_data->kind == eScopeKind::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
@@ -62,7 +62,7 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
 
     num_children = top_scope->GetSize();
     if (num_children == 0 && scope_data &&
-        scope_data->kind == ScopeKind::Locals) {
+        scope_data->kind == eScopeKind::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.
@@ -98,7 +98,7 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
     }
 
     // Show return value if there is any ( in the local top frame )
-    if (scope_data && scope_data->kind == ScopeKind::Locals) {
+    if (scope_data && scope_data->kind == eScopeKind::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 3096c27267139..1c041a52cdac1 100644
--- a/lldb/tools/lldb-dap/Variables.cpp
+++ b/lldb/tools/lldb-dap/Variables.cpp
@@ -19,7 +19,7 @@ using namespace lldb_dap;
 
 namespace lldb_dap {
 
-protocol::Scope CreateScope(const ScopeKind kind, int64_t variablesReference,
+protocol::Scope CreateScope(const eScopeKind kind, int64_t variablesReference,
                             int64_t namedVariables, bool expensive) {
   protocol::Scope scope;
 
@@ -31,14 +31,14 @@ protocol::Scope CreateScope(const ScopeKind kind, int64_t variablesReference,
   // be expanded if we enter a function with arguments. It becomes more
   // annoying when the scope has arguments, return_value and locals.
   switch (kind) {
-  case ScopeKind::Locals:
+  case eScopeKind::Locals:
     scope.presentationHint = protocol::Scope::eScopePresentationHintLocals;
     scope.name = "Locals";
     break;
-  case ScopeKind::Globals:
+  case eScopeKind::Globals:
     scope.name = "Globals";
     break;
-  case ScopeKind::Registers:
+  case eScopeKind::Registers:
     scope.presentationHint = protocol::Scope::eScopePresentationHintRegisters;
     scope.name = "Registers";
     break;
@@ -57,7 +57,7 @@ lldb::SBValueList *Variables::GetTopLevelScope(int64_t variablesReference) {
     return nullptr;
   }
 
-  ScopeKind scope_kind = iter->second.first;
+  eScopeKind scope_kind = iter->second.first;
   uint64_t dap_frame_id = iter->second.second;
 
   auto frame_iter = m_frames.find(dap_frame_id);
@@ -66,11 +66,11 @@ lldb::SBValueList *Variables::GetTopLevelScope(int64_t variablesReference) {
   }
 
   switch (scope_kind) {
-  case lldb_dap::ScopeKind::Locals:
+  case lldb_dap::eScopeKind::Locals:
     return &std::get<0>(frame_iter->second);
-  case lldb_dap::ScopeKind::Globals:
+  case lldb_dap::eScopeKind::Globals:
     return &std::get<1>(frame_iter->second);
-  case lldb_dap::ScopeKind::Registers:
+  case lldb_dap::eScopeKind::Registers:
     return &std::get<2>(frame_iter->second);
   }
 
@@ -173,13 +173,13 @@ Variables::GetScopeKind(const int64_t variablesReference) {
   scope_data.kind = scope_kind_iter->second.first;
 
   switch (scope_kind_iter->second.first) {
-  case lldb_dap::ScopeKind::Locals:
+  case lldb_dap::eScopeKind::Locals:
     scope_data.scope = std::get<0>(scope_iter->second);
     return scope_data;
-  case lldb_dap::ScopeKind::Globals:
+  case lldb_dap::eScopeKind::Globals:
     scope_data.scope = std::get<1>(scope_iter->second);
     return scope_data;
-  case lldb_dap::ScopeKind::Registers:
+  case lldb_dap::eScopeKind::Registers:
     scope_data.scope = std::get<2>(scope_iter->second);
     return scope_data;
   }
@@ -188,7 +188,7 @@ Variables::GetScopeKind(const int64_t variablesReference) {
 }
 
 lldb::SBValueList *Variables::GetScope(const uint64_t dap_frame_id,
-                                       const ScopeKind kind) {
+                                       const eScopeKind kind) {
 
   auto frame = m_frames.find(dap_frame_id);
   if (m_frames.find(dap_frame_id) == m_frames.end()) {
@@ -196,11 +196,11 @@ lldb::SBValueList *Variables::GetScope(const uint64_t dap_frame_id,
   }
 
   switch (kind) {
-  case ScopeKind::Locals:
+  case eScopeKind::Locals:
     return &std::get<0>(frame->second);
-  case ScopeKind::Globals:
+  case eScopeKind::Globals:
     return &std::get<1>(frame->second);
-  case ScopeKind::Registers:
+  case eScopeKind::Registers:
     return &std::get<2>(frame->second);
   }
 
@@ -232,25 +232,26 @@ std::vector<protocol::Scope> Variables::ReadyFrame(const uint64_t dap_frame_id,
 
   int64_t locals_ref = GetNewVariableReference(false);
 
-  scopes.push_back(
-      CreateScope(ScopeKind::Locals, locals_ref,
-                  GetScope(dap_frame_id, ScopeKind::Locals)->GetSize(), false));
+  scopes.push_back(CreateScope(
+      eScopeKind::Locals, locals_ref,
+      GetScope(dap_frame_id, eScopeKind::Locals)->GetSize(), false));
 
-  m_scope_kinds[locals_ref] = std::make_pair(ScopeKind::Locals, dap_frame_id);
+  m_scope_kinds[locals_ref] = std::make_pair(eScopeKind::Locals, dap_frame_id);
 
   int64_t globals_ref = GetNewVariableReference(false);
   scopes.push_back(CreateScope(
-      ScopeKind::Globals, globals_ref,
-      GetScope(dap_frame_id, ScopeKind::Globals)->GetSize(), false));
-  m_scope_kinds[globals_ref] = std::make_pair(ScopeKind::Globals, dap_frame_id);
+      eScopeKind::Globals, globals_ref,
+      GetScope(dap_frame_id, eScopeKind::Globals)->GetSize(), false));
+  m_scope_kinds[globals_ref] =
+      std::make_pair(eScopeKind::Globals, dap_frame_id);
 
   int64_t registers_ref = GetNewVariableReference(false);
   scopes.push_back(CreateScope(
-      ScopeKind::Registers, registers_ref,
-      GetScope(dap_frame_id, ScopeKind::Registers)->GetSize(), false));
+      eScopeKind::Registers, registers_ref,
+      GetScope(dap_frame_id, eScopeKind::Registers)->GetSize(), false));
 
   m_scope_kinds[registers_ref] =
-      std::make_pair(ScopeKind::Registers, dap_frame_id);
+      std::make_pair(eScopeKind::Registers, dap_frame_id);
 
   return scopes;
 }
diff --git a/lldb/tools/lldb-dap/Variables.h b/lldb/tools/lldb-dap/Variables.h
index 27604c0193fa7..0d3cdb877cdef 100644
--- a/lldb/tools/lldb-dap/Variables.h
+++ b/lldb/tools/lldb-dap/Variables.h
@@ -20,7 +20,7 @@
 
 namespace lldb_dap {
 
-enum ScopeKind { Locals, Globals, Registers };
+enum eScopeKind { Locals, Globals, Registers };
 /// Creates a `protocol::Scope` struct.
 ///
 /// \param[in] kind
@@ -37,11 +37,11 @@ enum ScopeKind { Locals, Globals, Registers };
 ///
 /// \return
 ///     A `protocol::Scope`
-protocol::Scope CreateScope(const ScopeKind kind, int64_t variablesReference,
+protocol::Scope CreateScope(const eScopeKind kind, int64_t variablesReference,
                             int64_t namedVariables, bool expensive);
 
 struct ScopeData {
-  ScopeKind kind;
+  eScopeKind kind;
   lldb::SBValueList scope;
 };
 
@@ -61,7 +61,7 @@ struct Variables {
   lldb::SBValue GetVariable(int64_t var_ref) const;
 
   lldb::SBValueList *GetScope(const uint64_t dap_frame_id,
-                              const ScopeKind kind);
+                              const eScopeKind kind);
 
   /// Insert a new \p variable.
   /// \return variableReference assigned to this expandable variable.
@@ -87,7 +87,7 @@ struct Variables {
   int64_t m_next_temporary_var_ref{VARREF_FIRST_VAR_IDX};
 
   // Variable Reference,                 dap_frame_id
-  std::map<int64_t, std::pair<ScopeKind, uint64_t>> m_scope_kinds;
+  std::map<int64_t, std::pair<eScopeKind, uint64_t>> m_scope_kinds;
 
   /// Variables that are alive in this stop state.
   /// Will be cleared when debuggee resumes.

>From c5c5baa48729d844342f25380d417fcc1a148ed8 Mon Sep 17 00:00:00 2001
From: Anthony Eid <hello at anthonyeid.me>
Date: Fri, 9 Jan 2026 04:05:19 -0500
Subject: [PATCH 23/39] Fix comment copy and paste error

---
 lldb/tools/lldb-dap/Variables.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lldb/tools/lldb-dap/Variables.cpp b/lldb/tools/lldb-dap/Variables.cpp
index 1c041a52cdac1..1b4024f5bcf71 100644
--- a/lldb/tools/lldb-dap/Variables.cpp
+++ b/lldb/tools/lldb-dap/Variables.cpp
@@ -24,7 +24,7 @@ protocol::Scope CreateScope(const eScopeKind kind, int64_t variablesReference,
   protocol::Scope scope;
 
   // TODO: Support "arguments" and "return value" scope.
-  // At the moment lldb-he arguments and return_value  into the
+  // At the moment lldb-dap includes the arguments and return_value  into the
   // "locals" scope.
   // vscode only expands the first non-expensive scope, this causes friction
   // if we add the arguments above the local scope as the locals scope will not

>From 3f9ed9095aef100ef34675f14b605775083b60f8 Mon Sep 17 00:00:00 2001
From: Anthony Eid <hello at anthonyeid.me>
Date: Fri, 9 Jan 2026 04:23:29 -0500
Subject: [PATCH 24/39] Create a FrameScope object to store a stack frames
 scopes

I also made a helper method to get the scope kind based on the
eScopeKind enum
---
 lldb/tools/lldb-dap/Variables.cpp | 45 ++++++++-----------------------
 lldb/tools/lldb-dap/Variables.h   | 26 +++++++++++++++---
 2 files changed, 33 insertions(+), 38 deletions(-)

diff --git a/lldb/tools/lldb-dap/Variables.cpp b/lldb/tools/lldb-dap/Variables.cpp
index 1b4024f5bcf71..659d1d2eb38dd 100644
--- a/lldb/tools/lldb-dap/Variables.cpp
+++ b/lldb/tools/lldb-dap/Variables.cpp
@@ -10,6 +10,7 @@
 #include "JSONUtils.h"
 #include "Protocol/ProtocolTypes.h"
 #include "lldb/API/SBFrame.h"
+#include "lldb/API/SBValue.h"
 #include "lldb/API/SBValueList.h"
 #include <cstdint>
 #include <optional>
@@ -65,16 +66,7 @@ lldb::SBValueList *Variables::GetTopLevelScope(int64_t variablesReference) {
     return nullptr;
   }
 
-  switch (scope_kind) {
-  case lldb_dap::eScopeKind::Locals:
-    return &std::get<0>(frame_iter->second);
-  case lldb_dap::eScopeKind::Globals:
-    return &std::get<1>(frame_iter->second);
-  case lldb_dap::eScopeKind::Registers:
-    return &std::get<2>(frame_iter->second);
-  }
-
-  return nullptr;
+  return frame_iter->second.GetScope(scope_kind);
 }
 
 void Variables::Clear() {
@@ -171,40 +163,25 @@ Variables::GetScopeKind(const int64_t variablesReference) {
 
   ScopeData scope_data = ScopeData();
   scope_data.kind = scope_kind_iter->second.first;
+  lldb::SBValueList *scope = scope_iter->second.GetScope(scope_data.kind);
 
-  switch (scope_kind_iter->second.first) {
-  case lldb_dap::eScopeKind::Locals:
-    scope_data.scope = std::get<0>(scope_iter->second);
-    return scope_data;
-  case lldb_dap::eScopeKind::Globals:
-    scope_data.scope = std::get<1>(scope_iter->second);
-    return scope_data;
-  case lldb_dap::eScopeKind::Registers:
-    scope_data.scope = std::get<2>(scope_iter->second);
-    return scope_data;
+  if (scope == nullptr) {
+    return std::nullopt;
   }
 
-  return std::nullopt;
+  scope_data.scope = *scope;
+  return scope_data;
 }
 
 lldb::SBValueList *Variables::GetScope(const uint64_t dap_frame_id,
                                        const eScopeKind kind) {
 
   auto frame = m_frames.find(dap_frame_id);
-  if (m_frames.find(dap_frame_id) == m_frames.end()) {
+  if (frame == m_frames.end()) {
     return nullptr;
   }
 
-  switch (kind) {
-  case eScopeKind::Locals:
-    return &std::get<0>(frame->second);
-  case eScopeKind::Globals:
-    return &std::get<1>(frame->second);
-  case eScopeKind::Registers:
-    return &std::get<2>(frame->second);
-  }
-
-  return nullptr;
+  return frame->second.GetScope(kind);
 }
 
 std::vector<protocol::Scope> Variables::ReadyFrame(const uint64_t dap_frame_id,
@@ -224,8 +201,8 @@ std::vector<protocol::Scope> Variables::ReadyFrame(const uint64_t dap_frame_id,
 
     auto registers = frame.GetRegisters();
 
-    m_frames.insert(std::make_pair(
-        dap_frame_id, std::make_tuple(locals, globals, registers)));
+    m_frames.insert(
+        std::make_pair(dap_frame_id, FrameScopes{locals, globals, registers}));
   }
 
   std::vector<protocol::Scope> scopes = {};
diff --git a/lldb/tools/lldb-dap/Variables.h b/lldb/tools/lldb-dap/Variables.h
index 0d3cdb877cdef..790a601acff6e 100644
--- a/lldb/tools/lldb-dap/Variables.h
+++ b/lldb/tools/lldb-dap/Variables.h
@@ -45,6 +45,26 @@ struct ScopeData {
   lldb::SBValueList scope;
 };
 
+/// Stores the three scope variable lists for a single stack frame.
+struct FrameScopes {
+  lldb::SBValueList locals;
+  lldb::SBValueList globals;
+  lldb::SBValueList registers;
+
+  /// Returns a pointer to the scope corresponding to the given kind.
+  lldb::SBValueList *GetScope(eScopeKind kind) {
+    switch (kind) {
+    case eScopeKind::Locals:
+      return &locals;
+    case eScopeKind::Globals:
+      return &globals;
+    case eScopeKind::Registers:
+      return ®isters;
+    }
+    return nullptr;
+  }
+};
+
 struct Variables {
   /// Check if \p var_ref points to a variable that should persist for the
   /// entire duration of the debug session, e.g. repl expandable variables
@@ -98,10 +118,8 @@ struct Variables {
   llvm::DenseMap<int64_t, lldb::SBValue> m_referencedpermanent_variables;
 
   /// Key = dap_frame_id (encodes both thread index ID and frame ID)
-  /// Value = (locals, globals, registers) scopes
-  std::map<uint64_t,
-           std::tuple<lldb::SBValueList, lldb::SBValueList, lldb::SBValueList>>
-      m_frames;
+  /// Value = scopes for the frame (locals, globals, registers)
+  std::map<uint64_t, FrameScopes> m_frames;
 };
 
 } // namespace lldb_dap

>From 1031b10073114d05f46490d16237ffb54101388e Mon Sep 17 00:00:00 2001
From: Anthony Eid <hello at anthonyeid.me>
Date: Fri, 9 Jan 2026 04:34:10 -0500
Subject: [PATCH 25/39] Switch VARREF_FIRST_VAR_IDX from a #define variable to
 a constexpr

This follows what `PermanentVariableStartIndex` does and is more inline
with newer c++ standards.

I renamed the variable to `TemporaryVariableStartIndex` as well
---
 lldb/tools/lldb-dap/Variables.cpp | 2 +-
 lldb/tools/lldb-dap/Variables.h   | 9 +++++----
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/lldb/tools/lldb-dap/Variables.cpp b/lldb/tools/lldb-dap/Variables.cpp
index 659d1d2eb38dd..3faf553000bc1 100644
--- a/lldb/tools/lldb-dap/Variables.cpp
+++ b/lldb/tools/lldb-dap/Variables.cpp
@@ -73,7 +73,7 @@ void Variables::Clear() {
   m_referencedvariables.clear();
   m_scope_kinds.clear();
   m_frames.clear();
-  m_next_temporary_var_ref = VARREF_FIRST_VAR_IDX;
+  m_next_temporary_var_ref = TemporaryVariableStartIndex;
 }
 
 int64_t Variables::GetNewVariableReference(bool is_permanent) {
diff --git a/lldb/tools/lldb-dap/Variables.h b/lldb/tools/lldb-dap/Variables.h
index 790a601acff6e..b8069198733a8 100644
--- a/lldb/tools/lldb-dap/Variables.h
+++ b/lldb/tools/lldb-dap/Variables.h
@@ -16,8 +16,6 @@
 #include <map>
 #include <utility>
 
-#define VARREF_FIRST_VAR_IDX (int64_t)1
-
 namespace lldb_dap {
 
 enum eScopeKind { Locals, Globals, Registers };
@@ -100,11 +98,14 @@ struct Variables {
   void Clear();
 
 private:
-  /// Variable_reference start index of permanent expandable variable.
+  /// Variable reference start index of temporary variables.
+  static constexpr int64_t TemporaryVariableStartIndex = 1;
+
+  /// Variable reference start index of permanent expandable variable.
   static constexpr int64_t PermanentVariableStartIndex = (1ll << 32);
 
   int64_t m_next_permanent_var_ref{PermanentVariableStartIndex};
-  int64_t m_next_temporary_var_ref{VARREF_FIRST_VAR_IDX};
+  int64_t m_next_temporary_var_ref{TemporaryVariableStartIndex};
 
   // Variable Reference,                 dap_frame_id
   std::map<int64_t, std::pair<eScopeKind, uint64_t>> m_scope_kinds;

>From 1ae6545f7114d33226a4aa182b83a7cab7d13dd3 Mon Sep 17 00:00:00 2001
From: Anthony Eid <hello at anthonyeid.me>
Date: Fri, 9 Jan 2026 04:46:05 -0500
Subject: [PATCH 26/39] Add scope lookup helper for DAP VariablesTest

---
 lldb/unittests/DAP/VariablesTest.cpp | 39 ++++++++++++++++++++--------
 1 file changed, 28 insertions(+), 11 deletions(-)

diff --git a/lldb/unittests/DAP/VariablesTest.cpp b/lldb/unittests/DAP/VariablesTest.cpp
index 8857d420ced20..0263586cadc53 100644
--- a/lldb/unittests/DAP/VariablesTest.cpp
+++ b/lldb/unittests/DAP/VariablesTest.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "Variables.h"
+#include "Protocol/ProtocolTypes.h"
 #include "lldb/API/SBFrame.h"
 #include "lldb/API/SBValue.h"
 #include "lldb/API/SBValueList.h"
@@ -18,6 +19,15 @@ class VariablesTest : public ::testing::Test {
 protected:
   enum : bool { Permanent = true, Temporary = false };
   Variables vars;
+
+  static const protocol::Scope *
+  FindScope(const std::vector<protocol::Scope> &scopes, llvm::StringRef name) {
+    for (const auto &scope : scopes) {
+      if (scope.name == name)
+        return &scope;
+    }
+    return nullptr;
+  }
 };
 
 TEST_F(VariablesTest, GetNewVariableReference_UniqueAndRanges) {
@@ -70,16 +80,22 @@ TEST_F(VariablesTest, GetTopLevelScope_ReturnsCorrectScope) {
   lldb::SBFrame frame;
   uint32_t frame_id = 0;
 
-  vars.ReadyFrame(frame_id, frame);
+  std::vector<protocol::Scope> scopes = vars.ReadyFrame(frame_id, frame);
+
+  const protocol::Scope *locals_scope = FindScope(scopes, "Locals");
+  const protocol::Scope *globals_scope = FindScope(scopes, "Globals");
+  const protocol::Scope *registers_scope = FindScope(scopes, "Registers");
 
-  int64_t next_variable_ref = vars.GetNewVariableReference(false);
+  ASSERT_NE(locals_scope, nullptr);
+  ASSERT_NE(globals_scope, nullptr);
+  ASSERT_NE(registers_scope, nullptr);
 
-  EXPECT_EQ(vars.GetTopLevelScope(next_variable_ref - 3),
-            vars.GetScope(frame_id, lldb_dap::ScopeKind::Locals));
-  EXPECT_EQ(vars.GetTopLevelScope(next_variable_ref - 2),
-            vars.GetScope(frame_id, lldb_dap::ScopeKind::Globals));
-  EXPECT_EQ(vars.GetTopLevelScope(next_variable_ref - 1),
-            vars.GetScope(frame_id, lldb_dap::ScopeKind::Registers));
+  EXPECT_EQ(vars.GetTopLevelScope(locals_scope->variablesReference),
+            vars.GetScope(frame_id, eScopeKind::Locals));
+  EXPECT_EQ(vars.GetTopLevelScope(globals_scope->variablesReference),
+            vars.GetScope(frame_id, eScopeKind::Globals));
+  EXPECT_EQ(vars.GetTopLevelScope(registers_scope->variablesReference),
+            vars.GetScope(frame_id, eScopeKind::Registers));
   EXPECT_EQ(vars.GetTopLevelScope(9999), nullptr);
 }
 
@@ -87,11 +103,12 @@ TEST_F(VariablesTest, FindVariable_LocalsByName) {
   lldb::SBFrame frame;
   uint32_t frame_id = 0;
 
-  vars.ReadyFrame(frame_id, frame);
+  std::vector<protocol::Scope> scopes = vars.ReadyFrame(frame_id, frame);
 
-  int64_t locals_ref = vars.GetNewVariableReference(false);
+  const protocol::Scope *locals_scope = FindScope(scopes, "Locals");
+  ASSERT_NE(locals_scope, nullptr);
 
-  lldb::SBValue found = vars.FindVariable(locals_ref - 1, "");
+  lldb::SBValue found = vars.FindVariable(locals_scope->variablesReference, "");
   lldb::SBValue dummy;
 
   EXPECT_EQ(found.IsValid(), dummy.IsValid());

>From 74e8e46c6623e33affc7473ee9b53946d69bc572 Mon Sep 17 00:00:00 2001
From: Anthony Eid <56899983+Anthony-Eid at users.noreply.github.com>
Date: Fri, 9 Jan 2026 16:13:18 -0500
Subject: [PATCH 27/39] Update lldb/tools/lldb-dap/Variables.cpp

Co-authored-by: Jonas Devlieghere <jonas at devlieghere.com>
---
 lldb/tools/lldb-dap/Variables.cpp | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/lldb/tools/lldb-dap/Variables.cpp b/lldb/tools/lldb-dap/Variables.cpp
index 3faf553000bc1..301a4ca9eb64e 100644
--- a/lldb/tools/lldb-dap/Variables.cpp
+++ b/lldb/tools/lldb-dap/Variables.cpp
@@ -62,9 +62,8 @@ lldb::SBValueList *Variables::GetTopLevelScope(int64_t variablesReference) {
   uint64_t dap_frame_id = iter->second.second;
 
   auto frame_iter = m_frames.find(dap_frame_id);
-  if (frame_iter == m_frames.end()) {
-    return nullptr;
-  }
+  if (frame_iter == m_frames.end())
+return nullptr;
 
   return frame_iter->second.GetScope(scope_kind);
 }

>From 70af7984c4fc347fff82c3bd37ba518d0bcb4eab Mon Sep 17 00:00:00 2001
From: Anthony Eid <56899983+Anthony-Eid at users.noreply.github.com>
Date: Fri, 9 Jan 2026 16:13:37 -0500
Subject: [PATCH 28/39] Update lldb/tools/lldb-dap/Variables.cpp

Co-authored-by: Jonas Devlieghere <jonas at devlieghere.com>
---
 lldb/tools/lldb-dap/Variables.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lldb/tools/lldb-dap/Variables.cpp b/lldb/tools/lldb-dap/Variables.cpp
index 301a4ca9eb64e..b71ede58d23d1 100644
--- a/lldb/tools/lldb-dap/Variables.cpp
+++ b/lldb/tools/lldb-dap/Variables.cpp
@@ -28,7 +28,7 @@ protocol::Scope CreateScope(const eScopeKind kind, int64_t variablesReference,
   // At the moment lldb-dap includes the arguments and return_value  into the
   // "locals" scope.
   // vscode only expands the first non-expensive scope, this causes friction
-  // if we add the arguments above the local scope as the locals scope will not
+  // 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.
   switch (kind) {

>From cae84f518b27bb8260d2dc67db5bd77539bdf0c4 Mon Sep 17 00:00:00 2001
From: Anthony Eid <56899983+Anthony-Eid at users.noreply.github.com>
Date: Fri, 9 Jan 2026 16:14:03 -0500
Subject: [PATCH 29/39] Update lldb/tools/lldb-dap/Variables.cpp

Co-authored-by: Jonas Devlieghere <jonas at devlieghere.com>
---
 lldb/tools/lldb-dap/Variables.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/lldb/tools/lldb-dap/Variables.cpp b/lldb/tools/lldb-dap/Variables.cpp
index b71ede58d23d1..db82b4ed19b52 100644
--- a/lldb/tools/lldb-dap/Variables.cpp
+++ b/lldb/tools/lldb-dap/Variables.cpp
@@ -54,9 +54,8 @@ protocol::Scope CreateScope(const eScopeKind kind, int64_t variablesReference,
 
 lldb::SBValueList *Variables::GetTopLevelScope(int64_t variablesReference) {
   auto iter = m_scope_kinds.find(variablesReference);
-  if (iter == m_scope_kinds.end()) {
+  if (iter == m_scope_kinds.end()) 
     return nullptr;
-  }
 
   eScopeKind scope_kind = iter->second.first;
   uint64_t dap_frame_id = iter->second.second;

>From 7ea1f9deedfb3420394941441d18c6635022762f Mon Sep 17 00:00:00 2001
From: Anthony Eid <hello at anthonyeid.me>
Date: Fri, 9 Jan 2026 05:36:22 -0500
Subject: [PATCH 30/39] Explicitly add optional include to variable.h

---
 lldb/tools/lldb-dap/Variables.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lldb/tools/lldb-dap/Variables.h b/lldb/tools/lldb-dap/Variables.h
index b8069198733a8..0d11cc8450779 100644
--- a/lldb/tools/lldb-dap/Variables.h
+++ b/lldb/tools/lldb-dap/Variables.h
@@ -14,6 +14,7 @@
 #include "lldb/API/SBValueList.h"
 #include "llvm/ADT/DenseMap.h"
 #include <map>
+#include <optional>
 #include <utility>
 
 namespace lldb_dap {

>From 3d844d1a7a3fa6dbdd6e56994090917e2f2f3f56 Mon Sep 17 00:00:00 2001
From: Anthony Eid <hello at anthonyeid.me>
Date: Fri, 9 Jan 2026 16:16:13 -0500
Subject: [PATCH 31/39] Add llvm unreachable

---
 lldb/tools/lldb-dap/Variables.h | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lldb/tools/lldb-dap/Variables.h b/lldb/tools/lldb-dap/Variables.h
index 0d11cc8450779..c7c75f0ae2fdc 100644
--- a/lldb/tools/lldb-dap/Variables.h
+++ b/lldb/tools/lldb-dap/Variables.h
@@ -60,7 +60,8 @@ struct FrameScopes {
     case eScopeKind::Registers:
       return ®isters;
     }
-    return nullptr;
+
+    llvm_unreachable("unknown scope kind");
   }
 };
 

>From cd90407e8f276c03eca99d51c37937dcb0c58393 Mon Sep 17 00:00:00 2001
From: Anthony Eid <56899983+Anthony-Eid at users.noreply.github.com>
Date: Fri, 9 Jan 2026 16:16:59 -0500
Subject: [PATCH 32/39] Update lldb/tools/lldb-dap/Variables.cpp

Co-authored-by: Jonas Devlieghere <jonas at devlieghere.com>
---
 lldb/tools/lldb-dap/Variables.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lldb/tools/lldb-dap/Variables.cpp b/lldb/tools/lldb-dap/Variables.cpp
index db82b4ed19b52..bffd0369cd915 100644
--- a/lldb/tools/lldb-dap/Variables.cpp
+++ b/lldb/tools/lldb-dap/Variables.cpp
@@ -27,7 +27,7 @@ protocol::Scope CreateScope(const eScopeKind kind, int64_t variablesReference,
   // TODO: Support "arguments" and "return value" scope.
   // At the moment lldb-dap includes the arguments and return_value  into the
   // "locals" scope.
-  // vscode only expands the first non-expensive scope, this causes friction
+  // VS Code only expands the first non-expensive scope. This causes friction
   // 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.

>From 6d680eecd827daf5fbc593eb9b2e9fac50b836c0 Mon Sep 17 00:00:00 2001
From: Anthony Eid <hello at anthonyeid.me>
Date: Fri, 9 Jan 2026 16:18:09 -0500
Subject: [PATCH 33/39] Move scope field initialization closer to func args

---
 lldb/tools/lldb-dap/Variables.cpp | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/lldb/tools/lldb-dap/Variables.cpp b/lldb/tools/lldb-dap/Variables.cpp
index db82b4ed19b52..1512b29e04274 100644
--- a/lldb/tools/lldb-dap/Variables.cpp
+++ b/lldb/tools/lldb-dap/Variables.cpp
@@ -23,6 +23,9 @@ namespace lldb_dap {
 protocol::Scope CreateScope(const eScopeKind kind, int64_t variablesReference,
                             int64_t namedVariables, bool expensive) {
   protocol::Scope scope;
+  scope.variablesReference = variablesReference;
+  scope.namedVariables = namedVariables;
+  scope.expensive = expensive;
 
   // TODO: Support "arguments" and "return value" scope.
   // At the moment lldb-dap includes the arguments and return_value  into the
@@ -45,16 +48,12 @@ protocol::Scope CreateScope(const eScopeKind kind, int64_t variablesReference,
     break;
   }
 
-  scope.variablesReference = variablesReference;
-  scope.namedVariables = namedVariables;
-  scope.expensive = expensive;
-
   return scope;
 }
 
 lldb::SBValueList *Variables::GetTopLevelScope(int64_t variablesReference) {
   auto iter = m_scope_kinds.find(variablesReference);
-  if (iter == m_scope_kinds.end()) 
+  if (iter == m_scope_kinds.end())
     return nullptr;
 
   eScopeKind scope_kind = iter->second.first;
@@ -62,7 +61,7 @@ lldb::SBValueList *Variables::GetTopLevelScope(int64_t variablesReference) {
 
   auto frame_iter = m_frames.find(dap_frame_id);
   if (frame_iter == m_frames.end())
-return nullptr;
+    return nullptr;
 
   return frame_iter->second.GetScope(scope_kind);
 }

>From 23f21e07ae944aff43784486edc9db580d5ffeea Mon Sep 17 00:00:00 2001
From: Anthony Eid <56899983+Anthony-Eid at users.noreply.github.com>
Date: Tue, 13 Jan 2026 15:11:38 -0500
Subject: [PATCH 34/39] Update lldb/tools/lldb-dap/Variables.cpp

Co-authored-by: Ebuka Ezike <yerimyah1 at gmail.com>
---
 lldb/tools/lldb-dap/Variables.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/lldb/tools/lldb-dap/Variables.cpp b/lldb/tools/lldb-dap/Variables.cpp
index bffd0369cd915..00181aa5c6664 100644
--- a/lldb/tools/lldb-dap/Variables.cpp
+++ b/lldb/tools/lldb-dap/Variables.cpp
@@ -217,8 +217,7 @@ std::vector<protocol::Scope> Variables::ReadyFrame(const uint64_t dap_frame_id,
   scopes.push_back(CreateScope(
       eScopeKind::Globals, globals_ref,
       GetScope(dap_frame_id, eScopeKind::Globals)->GetSize(), false));
-  m_scope_kinds[globals_ref] =
-      std::make_pair(eScopeKind::Globals, dap_frame_id);
+  m_scope_kinds.try_emplace(globals_ref, eScopeKind::Globals, dap_frame_id);
 
   int64_t registers_ref = GetNewVariableReference(false);
   scopes.push_back(CreateScope(

>From aaed972a765d76e3c4e5a8ce7472dd6748c3a729 Mon Sep 17 00:00:00 2001
From: Anthony Eid <56899983+Anthony-Eid at users.noreply.github.com>
Date: Tue, 13 Jan 2026 15:12:19 -0500
Subject: [PATCH 35/39] Update lldb/tools/lldb-dap/Variables.cpp

Co-authored-by: Ebuka Ezike <yerimyah1 at gmail.com>
---
 lldb/tools/lldb-dap/Variables.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lldb/tools/lldb-dap/Variables.cpp b/lldb/tools/lldb-dap/Variables.cpp
index 00181aa5c6664..13a91f2fd4353 100644
--- a/lldb/tools/lldb-dap/Variables.cpp
+++ b/lldb/tools/lldb-dap/Variables.cpp
@@ -154,8 +154,8 @@ Variables::GetScopeKind(const int64_t variablesReference) {
     return std::nullopt;
   }
 
-  auto scope_iter = m_frames.find(scope_kind_iter->second.second);
-  if (scope_iter == m_frames.end()) {
+  auto frames_iter = m_frames.find(scope_kind_iter->second.second);
+  if (frames_iter == m_frames.end()) {
     return std::nullopt;
   }
 

>From c054399a62d711ddaa84d56f936951881842d8ce Mon Sep 17 00:00:00 2001
From: Anthony Eid <hello at anthonyeid.me>
Date: Thu, 29 Jan 2026 23:04:40 -0500
Subject: [PATCH 36/39] Rename ScopeKind and add unsigned typing

---
 .../Handler/VariablesRequestHandler.cpp       |  6 ++--
 lldb/tools/lldb-dap/Variables.cpp             | 35 +++++++++----------
 lldb/tools/lldb-dap/Variables.h               | 22 +++++++-----
 lldb/unittests/DAP/VariablesTest.cpp          |  6 ++--
 4 files changed, 36 insertions(+), 33 deletions(-)

diff --git a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
index 7a6245ff79eed..d31858643f640 100644
--- a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
@@ -38,7 +38,7 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
     int64_t num_children = 0;
 
     std::optional<ScopeData> scope_data = dap.variables.GetScopeKind(var_ref);
-    if (scope_data.has_value() && scope_data->kind == eScopeKind::Registers) {
+    if (scope_data.has_value() && scope_data->kind == eScopeKindRegisters) {
 
       // Change the default format of any pointer sized registers in the first
       // register set to be the lldb::eFormatAddressInfo so we show the pointer
@@ -60,7 +60,7 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
 
     num_children = top_scope->GetSize();
     if (num_children == 0 && scope_data &&
-        scope_data->kind == eScopeKind::Locals) {
+        scope_data->kind == eScopeKindLocals) {
       // 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.
@@ -96,7 +96,7 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
     }
 
     // Show return value if there is any ( in the local top frame )
-    if (scope_data && scope_data->kind == eScopeKind::Locals) {
+    if (scope_data && scope_data->kind == eScopeKindLocals) {
       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 1512b29e04274..d13aebc3c42b6 100644
--- a/lldb/tools/lldb-dap/Variables.cpp
+++ b/lldb/tools/lldb-dap/Variables.cpp
@@ -20,7 +20,7 @@ using namespace lldb_dap;
 
 namespace lldb_dap {
 
-protocol::Scope CreateScope(const eScopeKind kind, int64_t variablesReference,
+protocol::Scope CreateScope(const ScopeKind kind, int64_t variablesReference,
                             int64_t namedVariables, bool expensive) {
   protocol::Scope scope;
   scope.variablesReference = variablesReference;
@@ -35,14 +35,14 @@ protocol::Scope CreateScope(const eScopeKind kind, int64_t variablesReference,
   // be expanded if we enter a function with arguments. It becomes more
   // annoying when the scope has arguments, return_value and locals.
   switch (kind) {
-  case eScopeKind::Locals:
+  case eScopeKindLocals:
     scope.presentationHint = protocol::Scope::eScopePresentationHintLocals;
     scope.name = "Locals";
     break;
-  case eScopeKind::Globals:
+  case eScopeKindGlobals:
     scope.name = "Globals";
     break;
-  case eScopeKind::Registers:
+  case eScopeKindRegisters:
     scope.presentationHint = protocol::Scope::eScopePresentationHintRegisters;
     scope.name = "Registers";
     break;
@@ -56,7 +56,7 @@ lldb::SBValueList *Variables::GetTopLevelScope(int64_t variablesReference) {
   if (iter == m_scope_kinds.end())
     return nullptr;
 
-  eScopeKind scope_kind = iter->second.first;
+  ScopeKind scope_kind = iter->second.first;
   uint64_t dap_frame_id = iter->second.second;
 
   auto frame_iter = m_frames.find(dap_frame_id);
@@ -171,7 +171,7 @@ Variables::GetScopeKind(const int64_t variablesReference) {
 }
 
 lldb::SBValueList *Variables::GetScope(const uint64_t dap_frame_id,
-                                       const eScopeKind kind) {
+                                       const ScopeKind kind) {
 
   auto frame = m_frames.find(dap_frame_id);
   if (frame == m_frames.end()) {
@@ -206,26 +206,25 @@ std::vector<protocol::Scope> Variables::ReadyFrame(const uint64_t dap_frame_id,
 
   int64_t locals_ref = GetNewVariableReference(false);
 
-  scopes.push_back(CreateScope(
-      eScopeKind::Locals, locals_ref,
-      GetScope(dap_frame_id, eScopeKind::Locals)->GetSize(), false));
+  scopes.push_back(
+      CreateScope(eScopeKindLocals, locals_ref,
+                  GetScope(dap_frame_id, eScopeKindLocals)->GetSize(), false));
 
-  m_scope_kinds[locals_ref] = std::make_pair(eScopeKind::Locals, dap_frame_id);
+  m_scope_kinds[locals_ref] = std::make_pair(eScopeKindLocals, dap_frame_id);
 
   int64_t globals_ref = GetNewVariableReference(false);
-  scopes.push_back(CreateScope(
-      eScopeKind::Globals, globals_ref,
-      GetScope(dap_frame_id, eScopeKind::Globals)->GetSize(), false));
-  m_scope_kinds[globals_ref] =
-      std::make_pair(eScopeKind::Globals, dap_frame_id);
+  scopes.push_back(
+      CreateScope(eScopeKindGlobals, globals_ref,
+                  GetScope(dap_frame_id, eScopeKindGlobals)->GetSize(), false));
+  m_scope_kinds[globals_ref] = std::make_pair(eScopeKindGlobals, dap_frame_id);
 
   int64_t registers_ref = GetNewVariableReference(false);
   scopes.push_back(CreateScope(
-      eScopeKind::Registers, registers_ref,
-      GetScope(dap_frame_id, eScopeKind::Registers)->GetSize(), false));
+      eScopeKindRegisters, registers_ref,
+      GetScope(dap_frame_id, eScopeKindRegisters)->GetSize(), false));
 
   m_scope_kinds[registers_ref] =
-      std::make_pair(eScopeKind::Registers, dap_frame_id);
+      std::make_pair(eScopeKindRegisters, dap_frame_id);
 
   return scopes;
 }
diff --git a/lldb/tools/lldb-dap/Variables.h b/lldb/tools/lldb-dap/Variables.h
index c7c75f0ae2fdc..f2c4d1a3de6ec 100644
--- a/lldb/tools/lldb-dap/Variables.h
+++ b/lldb/tools/lldb-dap/Variables.h
@@ -19,7 +19,11 @@
 
 namespace lldb_dap {
 
-enum eScopeKind { Locals, Globals, Registers };
+enum ScopeKind : unsigned {
+  eScopeKindLocals,
+  eScopeKindGlobals,
+  eScopeKindRegisters
+};
 /// Creates a `protocol::Scope` struct.
 ///
 /// \param[in] kind
@@ -36,11 +40,11 @@ enum eScopeKind { Locals, Globals, Registers };
 ///
 /// \return
 ///     A `protocol::Scope`
-protocol::Scope CreateScope(const eScopeKind kind, int64_t variablesReference,
+protocol::Scope CreateScope(const ScopeKind kind, int64_t variablesReference,
                             int64_t namedVariables, bool expensive);
 
 struct ScopeData {
-  eScopeKind kind;
+  ScopeKind kind;
   lldb::SBValueList scope;
 };
 
@@ -51,13 +55,13 @@ struct FrameScopes {
   lldb::SBValueList registers;
 
   /// Returns a pointer to the scope corresponding to the given kind.
-  lldb::SBValueList *GetScope(eScopeKind kind) {
+  lldb::SBValueList *GetScope(ScopeKind kind) {
     switch (kind) {
-    case eScopeKind::Locals:
+    case eScopeKindLocals:
       return &locals;
-    case eScopeKind::Globals:
+    case eScopeKindGlobals:
       return &globals;
-    case eScopeKind::Registers:
+    case eScopeKindRegisters:
       return ®isters;
     }
 
@@ -81,7 +85,7 @@ struct Variables {
   lldb::SBValue GetVariable(int64_t var_ref) const;
 
   lldb::SBValueList *GetScope(const uint64_t dap_frame_id,
-                              const eScopeKind kind);
+                              const ScopeKind kind);
 
   /// Insert a new \p variable.
   /// \return variableReference assigned to this expandable variable.
@@ -110,7 +114,7 @@ struct Variables {
   int64_t m_next_temporary_var_ref{TemporaryVariableStartIndex};
 
   // Variable Reference,                 dap_frame_id
-  std::map<int64_t, std::pair<eScopeKind, uint64_t>> m_scope_kinds;
+  std::map<int64_t, std::pair<ScopeKind, uint64_t>> m_scope_kinds;
 
   /// Variables that are alive in this stop state.
   /// Will be cleared when debuggee resumes.
diff --git a/lldb/unittests/DAP/VariablesTest.cpp b/lldb/unittests/DAP/VariablesTest.cpp
index 0263586cadc53..c6c6b6e02b169 100644
--- a/lldb/unittests/DAP/VariablesTest.cpp
+++ b/lldb/unittests/DAP/VariablesTest.cpp
@@ -91,11 +91,11 @@ TEST_F(VariablesTest, GetTopLevelScope_ReturnsCorrectScope) {
   ASSERT_NE(registers_scope, nullptr);
 
   EXPECT_EQ(vars.GetTopLevelScope(locals_scope->variablesReference),
-            vars.GetScope(frame_id, eScopeKind::Locals));
+            vars.GetScope(frame_id, eScopeKindLocals));
   EXPECT_EQ(vars.GetTopLevelScope(globals_scope->variablesReference),
-            vars.GetScope(frame_id, eScopeKind::Globals));
+            vars.GetScope(frame_id, eScopeKindGlobals));
   EXPECT_EQ(vars.GetTopLevelScope(registers_scope->variablesReference),
-            vars.GetScope(frame_id, eScopeKind::Registers));
+            vars.GetScope(frame_id, eScopeKindRegisters));
   EXPECT_EQ(vars.GetTopLevelScope(9999), nullptr);
 }
 

>From 7924902a53864737407524cc9ce517b611d8c23d Mon Sep 17 00:00:00 2001
From: Anthony Eid <hello at anthonyeid.me>
Date: Fri, 30 Jan 2026 05:08:07 -0500
Subject: [PATCH 37/39] rename ReadyFrame to CreateScopes

---
 lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp | 2 +-
 lldb/tools/lldb-dap/Variables.cpp                    | 4 ++--
 lldb/tools/lldb-dap/Variables.h                      | 4 ++--
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
index 5a016265f770f..251711ca62406 100644
--- a/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
@@ -37,7 +37,7 @@ ScopesRequestHandler::Run(const ScopesArguments &args) const {
   }
 
   std::vector<protocol::Scope> scopes =
-      dap.variables.ReadyFrame(args.frameId, frame);
+      dap.variables.CreateScopes(args.frameId, frame);
 
   return ScopesResponseBody{std::move(scopes)};
 }
diff --git a/lldb/tools/lldb-dap/Variables.cpp b/lldb/tools/lldb-dap/Variables.cpp
index 84e7f5642e4b8..77799b546afd1 100644
--- a/lldb/tools/lldb-dap/Variables.cpp
+++ b/lldb/tools/lldb-dap/Variables.cpp
@@ -181,8 +181,8 @@ lldb::SBValueList *Variables::GetScope(const uint64_t dap_frame_id,
   return frame->second.GetScope(kind);
 }
 
-std::vector<protocol::Scope> Variables::ReadyFrame(const uint64_t dap_frame_id,
-                                                   lldb::SBFrame &frame) {
+std::vector<protocol::Scope>
+Variables::CreateScopes(const uint64_t dap_frame_id, lldb::SBFrame &frame) {
 
   if (m_frames.find(dap_frame_id) == m_frames.end()) {
 
diff --git a/lldb/tools/lldb-dap/Variables.h b/lldb/tools/lldb-dap/Variables.h
index f2c4d1a3de6ec..867c6c82312bf 100644
--- a/lldb/tools/lldb-dap/Variables.h
+++ b/lldb/tools/lldb-dap/Variables.h
@@ -96,8 +96,8 @@ struct Variables {
   lldb::SBValue FindVariable(uint64_t variablesReference, llvm::StringRef name);
 
   /// Initialize a frame if it hasn't been already, otherwise do nothing
-  std::vector<protocol::Scope> ReadyFrame(const uint64_t dap_frame_id,
-                                          lldb::SBFrame &frame);
+  std::vector<protocol::Scope> CreateScopes(const uint64_t dap_frame_id,
+                                            lldb::SBFrame &frame);
   std::optional<ScopeData> GetScopeKind(const int64_t variablesReference);
 
   /// Clear all scope variables and non-permanent expandable variables.

>From 324f7f0beb7d1edc8094181aa8b48045f06c7375 Mon Sep 17 00:00:00 2001
From: Anthony Eid <hello at anthonyeid.me>
Date: Fri, 30 Jan 2026 05:14:05 -0500
Subject: [PATCH 38/39] Make GetTopLevelScope return an optional<ScopeData>

---
 .../Handler/VariablesRequestHandler.cpp       | 17 +++---
 lldb/tools/lldb-dap/Variables.cpp             | 55 +++++++------------
 lldb/tools/lldb-dap/Variables.h               |  3 +-
 3 files changed, 29 insertions(+), 46 deletions(-)

diff --git a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
index d31858643f640..ee7cb525d9c29 100644
--- a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
@@ -31,14 +31,14 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
 
   std::vector<Variable> variables;
 
-  if (lldb::SBValueList *top_scope = dap.variables.GetTopLevelScope(var_ref)) {
+  std::optional<ScopeData> scope_data = dap.variables.GetTopLevelScope(var_ref);
+  if (scope_data) {
     // 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;
 
-    std::optional<ScopeData> scope_data = dap.variables.GetScopeKind(var_ref);
-    if (scope_data.has_value() && scope_data->kind == eScopeKindRegisters) {
+    if (scope_data->kind == eScopeKindRegisters) {
 
       // Change the default format of any pointer sized registers in the first
       // register set to be the lldb::eFormatAddressInfo so we show the pointer
@@ -58,16 +58,15 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
       }
     }
 
-    num_children = top_scope->GetSize();
-    if (num_children == 0 && scope_data &&
-        scope_data->kind == eScopeKindLocals) {
+    num_children = scope_data->scope.GetSize();
+    if (num_children == 0 && scope_data->kind == eScopeKindLocals) {
       // 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.
 
       // "error" owns the error string so we must keep it alive as long as we
       // want to use the returns "const char *"
-      lldb::SBError error = top_scope->GetError();
+      lldb::SBError error = scope_data->scope.GetError();
       const char *var_err = error.GetCString();
       if (var_err) {
         // Create a fake variable named "error" to explain why variables were
@@ -89,7 +88,7 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
     // We first find out which variable names are duplicated
     std::map<llvm::StringRef, int> variable_name_counts;
     for (auto i = start_idx; i < end_idx; ++i) {
-      lldb::SBValue variable = top_scope->GetValueAtIndex(i);
+      lldb::SBValue variable = scope_data->scope.GetValueAtIndex(i);
       if (!variable.IsValid())
         break;
       variable_name_counts[GetNonNullVariableName(variable)]++;
@@ -120,7 +119,7 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
 
     // Now we construct the result with unique display variable names
     for (auto i = start_idx; i < end_idx; ++i) {
-      lldb::SBValue variable = top_scope->GetValueAtIndex(i);
+      lldb::SBValue variable = scope_data->scope.GetValueAtIndex(i);
 
       if (!variable.IsValid())
         break;
diff --git a/lldb/tools/lldb-dap/Variables.cpp b/lldb/tools/lldb-dap/Variables.cpp
index 77799b546afd1..28d0888e3634a 100644
--- a/lldb/tools/lldb-dap/Variables.cpp
+++ b/lldb/tools/lldb-dap/Variables.cpp
@@ -51,19 +51,27 @@ protocol::Scope CreateScope(const ScopeKind kind, int64_t variablesReference,
   return scope;
 }
 
-lldb::SBValueList *Variables::GetTopLevelScope(int64_t variablesReference) {
-  auto iter = m_scope_kinds.find(variablesReference);
-  if (iter == m_scope_kinds.end())
-    return nullptr;
+std::optional<ScopeData>
+Variables::GetTopLevelScope(int64_t variablesReference) {
+  auto scope_kind_iter = m_scope_kinds.find(variablesReference);
+  if (scope_kind_iter == m_scope_kinds.end())
+    return std::nullopt;
 
-  ScopeKind scope_kind = iter->second.first;
-  uint64_t dap_frame_id = iter->second.second;
+  ScopeKind scope_kind = scope_kind_iter->second.first;
+  uint64_t dap_frame_id = scope_kind_iter->second.second;
 
   auto frame_iter = m_frames.find(dap_frame_id);
   if (frame_iter == m_frames.end())
-    return nullptr;
+    return std::nullopt;
 
-  return frame_iter->second.GetScope(scope_kind);
+  lldb::SBValueList *scope = frame_iter->second.GetScope(scope_kind);
+  if (scope == nullptr)
+    return std::nullopt;
+
+  ScopeData scope_data;
+  scope_data.kind = scope_kind;
+  scope_data.scope = *scope;
+  return scope_data;
 }
 
 void Variables::Clear() {
@@ -108,15 +116,16 @@ int64_t Variables::InsertVariable(lldb::SBValue variable, bool is_permanent) {
 lldb::SBValue Variables::FindVariable(uint64_t variablesReference,
                                       llvm::StringRef name) {
   lldb::SBValue variable;
-  if (lldb::SBValueList *top_scope = GetTopLevelScope(variablesReference)) {
+  if (std::optional<ScopeData> scope_data =
+          GetTopLevelScope(variablesReference)) {
     bool is_duplicated_variable_name = name.contains(" @");
     // variablesReference is one of our scopes, not an actual variable it is
     // asking for a variable in locals or globals or registers
-    int64_t end_idx = top_scope->GetSize();
+    int64_t end_idx = scope_data->scope.GetSize();
     // Searching backward so that we choose the variable in closest scope
     // among variables of the same name.
     for (int64_t i = end_idx - 1; i >= 0; --i) {
-      lldb::SBValue curr_variable = top_scope->GetValueAtIndex(i);
+      lldb::SBValue curr_variable = scope_data->scope.GetValueAtIndex(i);
       std::string variable_name = CreateUniqueVariableNameForDisplay(
           curr_variable, is_duplicated_variable_name);
       if (variable_name == name) {
@@ -146,30 +155,6 @@ lldb::SBValue Variables::FindVariable(uint64_t variablesReference,
   return variable;
 }
 
-std::optional<ScopeData>
-Variables::GetScopeKind(const int64_t variablesReference) {
-  auto scope_kind_iter = m_scope_kinds.find(variablesReference);
-  if (scope_kind_iter == m_scope_kinds.end()) {
-    return std::nullopt;
-  }
-
-  auto frames_iter = m_frames.find(scope_kind_iter->second.second);
-  if (frames_iter == m_frames.end()) {
-    return std::nullopt;
-  }
-
-  ScopeData scope_data = ScopeData();
-  scope_data.kind = scope_kind_iter->second.first;
-  lldb::SBValueList *scope = frames_iter->second.GetScope(scope_data.kind);
-
-  if (scope == nullptr) {
-    return std::nullopt;
-  }
-
-  scope_data.scope = *scope;
-  return scope_data;
-}
-
 lldb::SBValueList *Variables::GetScope(const uint64_t dap_frame_id,
                                        const ScopeKind kind) {
 
diff --git a/lldb/tools/lldb-dap/Variables.h b/lldb/tools/lldb-dap/Variables.h
index 867c6c82312bf..9814cabc7036c 100644
--- a/lldb/tools/lldb-dap/Variables.h
+++ b/lldb/tools/lldb-dap/Variables.h
@@ -91,14 +91,13 @@ struct Variables {
   /// \return variableReference assigned to this expandable variable.
   int64_t InsertVariable(lldb::SBValue variable, bool is_permanent);
 
-  lldb::SBValueList *GetTopLevelScope(int64_t variablesReference);
+  std::optional<ScopeData> GetTopLevelScope(int64_t variablesReference);
 
   lldb::SBValue FindVariable(uint64_t variablesReference, llvm::StringRef name);
 
   /// Initialize a frame if it hasn't been already, otherwise do nothing
   std::vector<protocol::Scope> CreateScopes(const uint64_t dap_frame_id,
                                             lldb::SBFrame &frame);
-  std::optional<ScopeData> GetScopeKind(const int64_t variablesReference);
 
   /// Clear all scope variables and non-permanent expandable variables.
   void Clear();

>From cacb4c23ea9fa4ffc70828ff8ee47f905534f657 Mon Sep 17 00:00:00 2001
From: Anthony Eid <hello at anthonyeid.me>
Date: Fri, 30 Jan 2026 05:17:39 -0500
Subject: [PATCH 39/39] Clean up CreateScope function

---
 lldb/tools/lldb-dap/Variables.cpp | 44 +++++++++++--------------------
 1 file changed, 16 insertions(+), 28 deletions(-)

diff --git a/lldb/tools/lldb-dap/Variables.cpp b/lldb/tools/lldb-dap/Variables.cpp
index 28d0888e3634a..6d49113243799 100644
--- a/lldb/tools/lldb-dap/Variables.cpp
+++ b/lldb/tools/lldb-dap/Variables.cpp
@@ -168,9 +168,8 @@ lldb::SBValueList *Variables::GetScope(const uint64_t dap_frame_id,
 
 std::vector<protocol::Scope>
 Variables::CreateScopes(const uint64_t dap_frame_id, lldb::SBFrame &frame) {
-
-  if (m_frames.find(dap_frame_id) == m_frames.end()) {
-
+  auto iter = m_frames.find(dap_frame_id);
+  if (iter == m_frames.end()) {
     auto locals = frame.GetVariables(/*arguments=*/true,
                                      /*locals=*/true,
                                      /*statics=*/false,
@@ -183,35 +182,24 @@ Variables::CreateScopes(const uint64_t dap_frame_id, lldb::SBFrame &frame) {
 
     auto registers = frame.GetRegisters();
 
-    m_frames.insert(
-        std::make_pair(dap_frame_id, FrameScopes{locals, globals, registers}));
+    iter =
+        m_frames.emplace(dap_frame_id, FrameScopes{locals, globals, registers})
+            .first;
   }
 
-  std::vector<protocol::Scope> scopes = {};
-
-  int64_t locals_ref = GetNewVariableReference(false);
-
-  scopes.push_back(
-      CreateScope(eScopeKindLocals, locals_ref,
-                  GetScope(dap_frame_id, eScopeKindLocals)->GetSize(), false));
-
-  m_scope_kinds[locals_ref] = std::make_pair(eScopeKindLocals, dap_frame_id);
-
-  int64_t globals_ref = GetNewVariableReference(false);
-  scopes.push_back(
-      CreateScope(eScopeKindGlobals, globals_ref,
-                  GetScope(dap_frame_id, eScopeKindGlobals)->GetSize(), false));
-  m_scope_kinds[globals_ref] = std::make_pair(eScopeKindGlobals, dap_frame_id);
-
-  int64_t registers_ref = GetNewVariableReference(false);
-  scopes.push_back(CreateScope(
-      eScopeKindRegisters, registers_ref,
-      GetScope(dap_frame_id, eScopeKindRegisters)->GetSize(), false));
+  const FrameScopes &frame_scopes = iter->second;
 
-  m_scope_kinds[registers_ref] =
-      std::make_pair(eScopeKindRegisters, dap_frame_id);
+  auto create_scope = [&](ScopeKind kind, uint32_t size) {
+    int64_t ref = GetNewVariableReference(false);
+    m_scope_kinds.try_emplace(ref, kind, dap_frame_id);
+    return CreateScope(kind, ref, size, false);
+  };
 
-  return scopes;
+  return {
+      create_scope(eScopeKindLocals, frame_scopes.locals.GetSize()),
+      create_scope(eScopeKindGlobals, frame_scopes.globals.GetSize()),
+      create_scope(eScopeKindRegisters, frame_scopes.registers.GetSize()),
+  };
 }
 
 } // namespace lldb_dap



More information about the lldb-commits mailing list