[Lldb-commits] [lldb] WIP: Stop using replicated variable ids (PR #124232)
Anthony Eid via lldb-commits
lldb-commits at lists.llvm.org
Wed Aug 27 23:26:29 PDT 2025
https://github.com/Anthony-Eid updated https://github.com/llvm/llvm-project/pull/124232
>From 30658e994b18b7c0db114a297036421c8de2dea3 Mon Sep 17 00:00:00 2001
From: Anthony <hello at anthonyeid.me>
Date: Wed, 27 Aug 2025 13:04:26 -0400
Subject: [PATCH 1/8] Fix variable request from reusing variable_ids
---
.../lldb-dap/Handler/ScopesRequestHandler.cpp | 45 ++++++-----
lldb/tools/lldb-dap/JSONUtils.h | 1 +
lldb/tools/lldb-dap/Variables.cpp | 80 +++++++++++++++++--
lldb/tools/lldb-dap/Variables.h | 18 ++++-
4 files changed, 119 insertions(+), 25 deletions(-)
diff --git a/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
index aaad0e20f9c21..160d8e264d089 100644
--- a/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
@@ -29,8 +29,8 @@ namespace lldb_dap {
///
/// \return
/// A `protocol::Scope`
-static Scope CreateScope(const llvm::StringRef name, int64_t variablesReference,
- int64_t namedVariables, bool expensive) {
+Scope CreateScope(const llvm::StringRef name, int64_t variablesReference,
+ int64_t namedVariables, bool expensive) {
Scope scope;
scope.name = name;
@@ -75,22 +75,31 @@ ScopesRequestHandler::Run(const ScopesArguments &args) const {
frame.GetThread().GetProcess().SetSelectedThread(frame.GetThread());
frame.GetThread().SetSelectedFrame(frame.GetFrameID());
}
- dap.variables.locals = frame.GetVariables(/*arguments=*/true,
- /*locals=*/true,
- /*statics=*/false,
- /*in_scope_only=*/true);
- dap.variables.globals = frame.GetVariables(/*arguments=*/false,
- /*locals=*/false,
- /*statics=*/true,
- /*in_scope_only=*/true);
- dap.variables.registers = frame.GetRegisters();
-
- std::vector scopes = {CreateScope("Locals", VARREF_LOCALS,
- dap.variables.locals.GetSize(), false),
- CreateScope("Globals", VARREF_GLOBALS,
- dap.variables.globals.GetSize(), false),
- CreateScope("Registers", VARREF_REGS,
- dap.variables.registers.GetSize(), false)};
+
+ uint32_t frame_id = frame.GetFrameID();
+
+ dap.variables.ReadyFrame(frame_id, frame);
+ dap.variables.SwitchFrame(frame_id);
+
+ std::vector<Scope> scopes = {};
+
+ int64_t variable_reference = dap.variables.GetNewVariableReference(false);
+ scopes.push_back(CreateScope("Locals", variable_reference,
+ dap.variables.locals.GetSize(), false));
+
+ dap.variables.AddScopeKind(variable_reference, ScopeKind::Locals, frame_id);
+
+ variable_reference = dap.variables.GetNewVariableReference(false);
+ scopes.push_back(CreateScope("Globals", variable_reference,
+ dap.variables.globals.GetSize(), false));
+ dap.variables.AddScopeKind(variable_reference, ScopeKind::Globals, frame_id);
+
+ variable_reference = dap.variables.GetNewVariableReference(false);
+ scopes.push_back(CreateScope("Registers", variable_reference,
+ dap.variables.registers.GetSize(), false));
+
+ dap.variables.AddScopeKind(variable_reference, ScopeKind::Registers,
+ frame_id);
return ScopesResponseBody{std::move(scopes)};
}
diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h
index e9094f67b94ec..6575411acd878 100644
--- a/lldb/tools/lldb-dap/JSONUtils.h
+++ b/lldb/tools/lldb-dap/JSONUtils.h
@@ -9,6 +9,7 @@
#ifndef LLDB_TOOLS_LLDB_DAP_JSONUTILS_H
#define LLDB_TOOLS_LLDB_DAP_JSONUTILS_H
+#include "DAP.h"
#include "DAPForward.h"
#include "Protocol/ProtocolTypes.h"
#include "lldb/API/SBCompileUnit.h"
diff --git a/lldb/tools/lldb-dap/Variables.cpp b/lldb/tools/lldb-dap/Variables.cpp
index 777e3183d8c0d..9ed3773df817d 100644
--- a/lldb/tools/lldb-dap/Variables.cpp
+++ b/lldb/tools/lldb-dap/Variables.cpp
@@ -8,20 +8,33 @@
#include "Variables.h"
#include "JSONUtils.h"
+#include "lldb/API/SBFrame.h"
using namespace lldb_dap;
lldb::SBValueList *Variables::GetTopLevelScope(int64_t variablesReference) {
- switch (variablesReference) {
- case VARREF_LOCALS:
+ auto iter = m_scope_kinds.find(variablesReference);
+ if (iter == m_scope_kinds.end()) {
+ return nullptr;
+ }
+
+ ScopeKind scope_kind = iter->second.first;
+ uint32_t frame_id = iter->second.second;
+
+ if (!SwitchFrame(frame_id)) {
+ return nullptr;
+ }
+
+ switch (scope_kind) {
+ case lldb_dap::ScopeKind::Locals:
return &locals;
- case VARREF_GLOBALS:
+ case lldb_dap::ScopeKind::Globals:
return &globals;
- case VARREF_REGS:
+ case lldb_dap::ScopeKind::Registers:
return ®isters;
- default:
- return nullptr;
}
+
+ return nullptr;
}
void Variables::Clear() {
@@ -29,6 +42,8 @@ void Variables::Clear() {
globals.Clear();
registers.Clear();
m_referencedvariables.clear();
+ m_frames.clear();
+ m_next_temporary_var_ref = VARREF_FIRST_VAR_IDX;
}
int64_t Variables::GetNewVariableReference(bool is_permanent) {
@@ -103,3 +118,56 @@ lldb::SBValue Variables::FindVariable(uint64_t variablesReference,
}
return variable;
}
+
+std::optional<ScopeKind>
+Variables::GetScopeKind(const int64_t variablesReference) {
+ auto iter = m_scope_kinds.find(variablesReference);
+ if (iter != m_scope_kinds.end()) {
+ return iter->second.first;
+ }
+
+ return std::nullopt;
+}
+
+bool Variables::SwitchFrame(const uint32_t frame_id) {
+ auto iter = m_frames.find(frame_id);
+
+ if (iter == m_frames.end()) {
+ return false;
+ }
+
+ auto [frame_locals, frame_globals, frame_registers] = iter->second;
+
+ locals = frame_locals;
+ globals = frame_globals;
+ registers = frame_registers;
+
+ return true;
+}
+
+void Variables::ReadyFrame(uint32_t frame_id, lldb::SBFrame &frame) {
+ if (m_frames.find(frame_id) == m_frames.end()) {
+
+ auto locals = frame.GetVariables(/*arguments=*/true,
+ /*locals=*/true,
+ /*statics=*/false,
+ /*in_scope_only=*/true);
+
+ auto globals = frame.GetVariables(/*arguments=*/false,
+ /*locals=*/false,
+ /*statics=*/true,
+ /*in_scope_only=*/true);
+
+ auto registers = frame.GetRegisters();
+
+ m_frames.insert(
+ std::make_pair(frame_id, std::make_tuple(locals, globals, registers)));
+ }
+}
+
+void Variables::AddScopeKind(int64_t variable_reference, ScopeKind kind,
+ uint32_t frame_id) {
+
+ m_scope_kinds[variable_reference] =
+ std::make_pair(ScopeKind::Globals, frame_id);
+}
diff --git a/lldb/tools/lldb-dap/Variables.h b/lldb/tools/lldb-dap/Variables.h
index 0ed84b36aef99..2f40f87e9b4bb 100644
--- a/lldb/tools/lldb-dap/Variables.h
+++ b/lldb/tools/lldb-dap/Variables.h
@@ -12,6 +12,7 @@
#include "lldb/API/SBValue.h"
#include "lldb/API/SBValueList.h"
#include "llvm/ADT/DenseMap.h"
+#include <map>
#define VARREF_FIRST_VAR_IDX (int64_t)4
#define VARREF_LOCALS (int64_t)1
@@ -20,6 +21,8 @@
namespace lldb_dap {
+enum ScopeKind { Locals, Globals, Registers };
+
struct Variables {
lldb::SBValueList locals;
lldb::SBValueList globals;
@@ -47,12 +50,23 @@ struct Variables {
lldb::SBValue FindVariable(uint64_t variablesReference, llvm::StringRef name);
+ bool SwitchFrame(uint32_t frame_id);
+ /// Initialize a frame if it hasn't been already, otherwise do nothing
+ void ReadyFrame(uint32_t frame_id, lldb::SBFrame &frame);
+ std::optional<ScopeKind> GetScopeKind(const int64_t variablesReference);
+
/// Clear all scope variables and non-permanent expandable variables.
void Clear();
+ void AddScopeKind(int64_t variable_reference, ScopeKind kind,
+ uint32_t frame_id);
+
private:
/// Variable_reference start index of permanent expandable variable.
static constexpr int64_t PermanentVariableStartIndex = (1ll << 32);
+ int64_t m_next_temporary_var_ref{VARREF_FIRST_VAR_IDX};
+
+ std::map<int, std::pair<ScopeKind, uint32_t>> m_scope_kinds;
/// Variables that are alive in this stop state.
/// Will be cleared when debuggee resumes.
@@ -62,7 +76,9 @@ struct Variables {
/// These are the variables evaluated from debug console REPL.
llvm::DenseMap<int64_t, lldb::SBValue> m_referencedpermanent_variables;
- int64_t m_next_temporary_var_ref{VARREF_FIRST_VAR_IDX};
+ std::map<uint32_t,
+ std::tuple<lldb::SBValueList, lldb::SBValueList, lldb::SBValueList>>
+ m_frames;
int64_t m_next_permanent_var_ref{PermanentVariableStartIndex};
};
>From 9707978154cc43ee6f7e3d54d0159a0c40d5bbf0 Mon Sep 17 00:00:00 2001
From: Anthony <hello at anthonyeid.me>
Date: Wed, 27 Aug 2025 13:12:02 -0400
Subject: [PATCH 2/8] Add createScope changes
---
lldb/tools/lldb-dap/JSONUtils.cpp | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp
index 4f26599a49bac..7c256481f8c71 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -11,6 +11,7 @@
#include "ExceptionBreakpoint.h"
#include "LLDBUtils.h"
#include "ProtocolUtils.h"
+#include "Variables.h"
#include "lldb/API/SBAddress.h"
#include "lldb/API/SBCompileUnit.h"
#include "lldb/API/SBDeclaration.h"
@@ -358,16 +359,16 @@ void FillResponse(const llvm::json::Object &request,
// "required": [ "name", "variablesReference", "expensive" ]
// }
llvm::json::Value CreateScope(const llvm::StringRef name,
- int64_t variablesReference,
+ int64_t variablesReference, ScopeKind kind,
int64_t namedVariables, bool expensive) {
llvm::json::Object object;
EmplaceSafeString(object, "name", name.str());
// TODO: Support "arguments" scope. At the moment lldb-dap includes the
// arguments into the "locals" scope.
- if (variablesReference == VARREF_LOCALS) {
+ if (kind == ScopeKind::Locals) {
object.try_emplace("presentationHint", "locals");
- } else if (variablesReference == VARREF_REGS) {
+ } else if (kind == ScopeKind::Registers) {
object.try_emplace("presentationHint", "registers");
}
>From 50230c201673a58236bf5bccfe3036ca5d14cc95 Mon Sep 17 00:00:00 2001
From: Anthony <hello at anthonyeid.me>
Date: Wed, 27 Aug 2025 18:19:44 -0400
Subject: [PATCH 3/8] Fix some bugs
---
lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp | 1 +
lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp | 4 ++--
lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp | 7 ++++---
lldb/tools/lldb-dap/Variables.cpp | 4 ++--
4 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp
index e1556846dff19..e207f830183b6 100644
--- a/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp
@@ -162,6 +162,7 @@ void EvaluateRequestHandler::operator()(
// focus_tid to the current frame for any thread related events.
if (frame.IsValid()) {
dap.focus_tid = frame.GetThread().GetThreadID();
+ dap.variables.SwitchFrame(frame.GetFrameID());
}
bool required_command_failed = false;
diff --git a/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
index 160d8e264d089..553a7b30adba2 100644
--- a/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
@@ -41,9 +41,9 @@ Scope CreateScope(const llvm::StringRef name, int64_t variablesReference,
// if we add the arguments above the local scope as the locals scope will not
// be expanded if we enter a function with arguments. It becomes more
// annoying when the scope has arguments, return_value and locals.
- if (variablesReference == VARREF_LOCALS)
+ if (name == "Locals")
scope.presentationHint = Scope::eScopePresentationHintLocals;
- else if (variablesReference == VARREF_REGS)
+ else if (name == "Registers")
scope.presentationHint = Scope::eScopePresentationHintRegisters;
scope.variablesReference = variablesReference;
diff --git a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
index 5fa2b1ef5e20d..3d99983ac0c83 100644
--- a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
@@ -38,7 +38,8 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
int64_t start_idx = 0;
int64_t num_children = 0;
- if (var_ref == VARREF_REGS) {
+ std::optional<ScopeKind> scope_kind = dap.variables.GetScopeKind(var_ref);
+ if (scope_kind && *scope_kind == ScopeKind::Registers) {
// Change the default format of any pointer sized registers in the first
// register set to be the lldb::eFormatAddressInfo so we show the pointer
// and resolve what the pointer resolves to. Only change the format if the
@@ -58,7 +59,7 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
}
num_children = top_scope->GetSize();
- if (num_children == 0 && var_ref == VARREF_LOCALS) {
+ if (num_children == 0 && scope_kind && *scope_kind == ScopeKind::Locals) {
// Check for an error in the SBValueList that might explain why we don't
// have locals. If we have an error display it as the sole value in the
// the locals.
@@ -94,7 +95,7 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
}
// Show return value if there is any ( in the local top frame )
- if (var_ref == VARREF_LOCALS) {
+ if (scope_kind && *scope_kind == ScopeKind::Locals) {
auto process = dap.target.GetProcess();
auto selected_thread = process.GetSelectedThread();
lldb::SBValue stop_return_value = selected_thread.GetStopReturnValue();
diff --git a/lldb/tools/lldb-dap/Variables.cpp b/lldb/tools/lldb-dap/Variables.cpp
index 9ed3773df817d..b5cd9d69d385f 100644
--- a/lldb/tools/lldb-dap/Variables.cpp
+++ b/lldb/tools/lldb-dap/Variables.cpp
@@ -42,6 +42,7 @@ void Variables::Clear() {
globals.Clear();
registers.Clear();
m_referencedvariables.clear();
+ m_scope_kinds.clear();
m_frames.clear();
m_next_temporary_var_ref = VARREF_FIRST_VAR_IDX;
}
@@ -168,6 +169,5 @@ void Variables::ReadyFrame(uint32_t frame_id, lldb::SBFrame &frame) {
void Variables::AddScopeKind(int64_t variable_reference, ScopeKind kind,
uint32_t frame_id) {
- m_scope_kinds[variable_reference] =
- std::make_pair(ScopeKind::Globals, frame_id);
+ m_scope_kinds[variable_reference] = std::make_pair(kind, frame_id);
}
>From c73d4c4f82afbee0399396909a0becf98aac257e Mon Sep 17 00:00:00 2001
From: Anthony <hello at anthonyeid.me>
Date: Wed, 27 Aug 2025 18:31:25 -0400
Subject: [PATCH 4/8] remove unused GetScopedsKind func
---
lldb/tools/lldb-dap/JSONUtils.cpp | 85 -------------------------------
lldb/tools/lldb-dap/Variables.h | 2 +-
2 files changed, 1 insertion(+), 86 deletions(-)
diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp
index 7c256481f8c71..ad1f035caaf54 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -293,91 +293,6 @@ void FillResponse(const llvm::json::Object &request,
response.try_emplace("success", true);
}
-// "Scope": {
-// "type": "object",
-// "description": "A Scope is a named container for variables. Optionally
-// a scope can map to a source or a range within a source.",
-// "properties": {
-// "name": {
-// "type": "string",
-// "description": "Name of the scope such as 'Arguments', 'Locals'."
-// },
-// "presentationHint": {
-// "type": "string",
-// "description": "An optional hint for how to present this scope in the
-// UI. If this attribute is missing, the scope is shown
-// with a generic UI.",
-// "_enum": [ "arguments", "locals", "registers" ],
-// },
-// "variablesReference": {
-// "type": "integer",
-// "description": "The variables of this scope can be retrieved by
-// passing the value of variablesReference to the
-// VariablesRequest."
-// },
-// "namedVariables": {
-// "type": "integer",
-// "description": "The number of named variables in this scope. The
-// client can use this optional information to present
-// the variables in a paged UI and fetch them in chunks."
-// },
-// "indexedVariables": {
-// "type": "integer",
-// "description": "The number of indexed variables in this scope. The
-// client can use this optional information to present
-// the variables in a paged UI and fetch them in chunks."
-// },
-// "expensive": {
-// "type": "boolean",
-// "description": "If true, the number of variables in this scope is
-// large or expensive to retrieve."
-// },
-// "source": {
-// "$ref": "#/definitions/Source",
-// "description": "Optional source for this scope."
-// },
-// "line": {
-// "type": "integer",
-// "description": "Optional start line of the range covered by this
-// scope."
-// },
-// "column": {
-// "type": "integer",
-// "description": "Optional start column of the range covered by this
-// scope."
-// },
-// "endLine": {
-// "type": "integer",
-// "description": "Optional end line of the range covered by this scope."
-// },
-// "endColumn": {
-// "type": "integer",
-// "description": "Optional end column of the range covered by this
-// scope."
-// }
-// },
-// "required": [ "name", "variablesReference", "expensive" ]
-// }
-llvm::json::Value CreateScope(const llvm::StringRef name,
- int64_t variablesReference, ScopeKind kind,
- int64_t namedVariables, bool expensive) {
- llvm::json::Object object;
- EmplaceSafeString(object, "name", name.str());
-
- // TODO: Support "arguments" scope. At the moment lldb-dap includes the
- // arguments into the "locals" scope.
- if (kind == ScopeKind::Locals) {
- object.try_emplace("presentationHint", "locals");
- } else if (kind == ScopeKind::Registers) {
- object.try_emplace("presentationHint", "registers");
- }
-
- object.try_emplace("variablesReference", variablesReference);
- object.try_emplace("expensive", expensive);
- object.try_emplace("namedVariables", namedVariables);
- return llvm::json::Value(std::move(object));
-}
-
// "Event": {
// "allOf": [ { "$ref": "#/definitions/ProtocolMessage" }, {
// "type": "object",
diff --git a/lldb/tools/lldb-dap/Variables.h b/lldb/tools/lldb-dap/Variables.h
index 2f40f87e9b4bb..a8fad707e2e08 100644
--- a/lldb/tools/lldb-dap/Variables.h
+++ b/lldb/tools/lldb-dap/Variables.h
@@ -66,7 +66,7 @@ struct Variables {
static constexpr int64_t PermanentVariableStartIndex = (1ll << 32);
int64_t m_next_temporary_var_ref{VARREF_FIRST_VAR_IDX};
- std::map<int, std::pair<ScopeKind, uint32_t>> m_scope_kinds;
+ std::map<int64_t, std::pair<ScopeKind, uint32_t>> m_scope_kinds;
/// Variables that are alive in this stop state.
/// Will be cleared when debuggee resumes.
>From 2712abdb5a21e466a4ee8a289c52c8bc9e537c63 Mon Sep 17 00:00:00 2001
From: Anthony <hello at anthonyeid.me>
Date: Thu, 28 Aug 2025 00:25:00 -0400
Subject: [PATCH 5/8] Fix hardcoded scope references in DAP variable handling
The test and API changes now get scope references dynamically instead of
using hardcoded values.
This fixes the below tests:
- testDAP_memory
- TestDAP_variables
- GetTopLevelScope_ReturnsCorrectScope
---
.../test/tools/lldb-dap/lldbdap_testcase.py | 33 ++++++++++++++++--
.../tools/lldb-dap/memory/TestDAP_memory.py | 4 ++-
.../lldb-dap/variables/TestDAP_variables.py | 17 +++++-----
lldb/unittests/DAP/VariablesTest.cpp | 34 ++++++++++++++++---
4 files changed, 73 insertions(+), 15 deletions(-)
diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
index b28a78792c70f..a26be6e5d1bfb 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
@@ -351,11 +351,40 @@ def get_local_as_int(self, name, threadId=None):
def set_local(self, name, value, id=None):
"""Set a top level local variable only."""
- return self.dap_server.request_setVariable(1, name, str(value), id=id)
+ # Get the locals scope reference dynamically
+ locals_ref = self.get_locals_scope_reference()
+ if locals_ref is None:
+ return None
+ return self.dap_server.request_setVariable(locals_ref, name, str(value), id=id)
def set_global(self, name, value, id=None):
"""Set a top level global variable only."""
- return self.dap_server.request_setVariable(2, name, str(value), id=id)
+ # Get the globals scope reference dynamically
+ stackFrame = self.dap_server.get_stackFrame()
+ if stackFrame is None:
+ return None
+ frameId = stackFrame["id"]
+ scopes_response = self.dap_server.request_scopes(frameId)
+ frame_scopes = scopes_response["body"]["scopes"]
+ for scope in frame_scopes:
+ if scope["name"] == "Globals":
+ varRef = scope["variablesReference"]
+ return self.dap_server.request_setVariable(varRef, name, str(value), id=id)
+ return None
+
+
+ def get_locals_scope_reference(self):
+ """Get the variablesReference for the locals scope."""
+ stackFrame = self.dap_server.get_stackFrame()
+ if stackFrame is None:
+ return None
+ frameId = stackFrame["id"]
+ scopes_response = self.dap_server.request_scopes(frameId)
+ frame_scopes = scopes_response["body"]["scopes"]
+ for scope in frame_scopes:
+ if scope["name"] == "Locals":
+ return scope["variablesReference"]
+ return None
def stepIn(
self,
diff --git a/lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py b/lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py
index f51056d7020c6..5e1dc8d30ff8d 100644
--- a/lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py
+++ b/lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py
@@ -70,9 +70,11 @@ def test_memory_refs_set_variable(self):
self.continue_to_next_stop()
ptr_value = self.get_local_as_int("rawptr")
+ locals_ref = self.get_locals_scope_reference()
+ self.assertIsNotNone(locals_ref, "Failed to get locals scope reference")
self.assertIn(
"memoryReference",
- self.dap_server.request_setVariable(1, "rawptr", ptr_value + 2)[
+ self.dap_server.request_setVariable(locals_ref, "rawptr", ptr_value + 2)[
"body"
].keys(),
)
diff --git a/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py b/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py
index a3a4bdaaf40a6..a51d1f0ee179c 100644
--- a/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py
+++ b/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py
@@ -341,24 +341,25 @@ def do_test_scopes_variables_setVariable_evaluate(
self.verify_variables(verify_locals, self.dap_server.get_local_variables())
# Now we verify that we correctly change the name of a variable with and without differentiator suffix
- self.assertFalse(self.dap_server.request_setVariable(1, "x2", 9)["success"])
+ local_scope_ref = self.get_locals_scope_reference()
+ self.assertFalse(self.dap_server.request_setVariable(local_scope_ref, "x2", 9)["success"])
self.assertFalse(
- self.dap_server.request_setVariable(1, "x @ main.cpp:0", 9)["success"]
+ self.dap_server.request_setVariable(local_scope_ref, "x @ main.cpp:0", 9)["success"]
)
self.assertTrue(
- self.dap_server.request_setVariable(1, "x @ main.cpp:19", 19)["success"]
+ self.dap_server.request_setVariable(local_scope_ref, "x @ main.cpp:19", 19)["success"]
)
self.assertTrue(
- self.dap_server.request_setVariable(1, "x @ main.cpp:21", 21)["success"]
+ self.dap_server.request_setVariable(local_scope_ref, "x @ main.cpp:21", 21)["success"]
)
self.assertTrue(
- self.dap_server.request_setVariable(1, "x @ main.cpp:23", 23)["success"]
+ self.dap_server.request_setVariable(local_scope_ref, "x @ main.cpp:23", 23)["success"]
)
# The following should have no effect
self.assertFalse(
- self.dap_server.request_setVariable(1, "x @ main.cpp:23", "invalid")[
+ self.dap_server.request_setVariable(local_scope_ref, "x @ main.cpp:23", "invalid")[
"success"
]
)
@@ -370,7 +371,7 @@ def do_test_scopes_variables_setVariable_evaluate(
self.verify_variables(verify_locals, self.dap_server.get_local_variables())
# The plain x variable shold refer to the innermost x
- self.assertTrue(self.dap_server.request_setVariable(1, "x", 22)["success"])
+ self.assertTrue(self.dap_server.request_setVariable(local_scope_ref, "x", 22)["success"])
verify_locals["x @ main.cpp:23"]["equals"]["value"] = "22"
self.verify_variables(verify_locals, self.dap_server.get_local_variables())
@@ -709,7 +710,7 @@ def test_return_variables(self):
break
self.assertFalse(
- self.dap_server.request_setVariable(1, "(Return Value)", 20)["success"]
+ self.dap_server.request_setVariable(self.get_locals_scope_reference(), "(Return Value)", 20)["success"]
)
@skipIfWindows
diff --git a/lldb/unittests/DAP/VariablesTest.cpp b/lldb/unittests/DAP/VariablesTest.cpp
index 6b14fc6c3945d..40884e89e0c54 100644
--- a/lldb/unittests/DAP/VariablesTest.cpp
+++ b/lldb/unittests/DAP/VariablesTest.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "Variables.h"
+#include "lldb/API/SBFrame.h"
#include "lldb/API/SBValue.h"
#include "lldb/API/SBValueList.h"
#include "gtest/gtest.h"
@@ -66,20 +67,45 @@ TEST_F(VariablesTest, Clear_RemovesTemporaryKeepsPermanent) {
}
TEST_F(VariablesTest, GetTopLevelScope_ReturnsCorrectScope) {
+ lldb::SBFrame frame;
+ uint32_t frame_id = 0;
+
+ vars.ReadyFrame(frame_id, frame);
+ vars.SwitchFrame(frame_id);
+
vars.locals.Append(lldb::SBValue());
vars.globals.Append(lldb::SBValue());
vars.registers.Append(lldb::SBValue());
- EXPECT_EQ(vars.GetTopLevelScope(VARREF_LOCALS), &vars.locals);
- EXPECT_EQ(vars.GetTopLevelScope(VARREF_GLOBALS), &vars.globals);
- EXPECT_EQ(vars.GetTopLevelScope(VARREF_REGS), &vars.registers);
+ int64_t locals_ref = vars.GetNewVariableReference(false);
+ vars.AddScopeKind(locals_ref, ScopeKind::Locals, frame_id);
+
+ int64_t globals_ref = vars.GetNewVariableReference(false);
+ vars.AddScopeKind(globals_ref, ScopeKind::Globals, frame_id);
+
+ int64_t registers_ref = vars.GetNewVariableReference(false);
+ vars.AddScopeKind(registers_ref, ScopeKind::Registers, frame_id);
+
+ EXPECT_EQ(vars.GetTopLevelScope(locals_ref), &vars.locals);
+ EXPECT_EQ(vars.GetTopLevelScope(globals_ref), &vars.globals);
+ EXPECT_EQ(vars.GetTopLevelScope(registers_ref), &vars.registers);
EXPECT_EQ(vars.GetTopLevelScope(9999), nullptr);
}
TEST_F(VariablesTest, FindVariable_LocalsByName) {
+ lldb::SBFrame frame;
+ uint32_t frame_id = 0;
+
+ vars.ReadyFrame(frame_id, frame);
+ vars.SwitchFrame(frame_id);
+
lldb::SBValue dummy;
vars.locals.Append(dummy);
- lldb::SBValue found = vars.FindVariable(VARREF_LOCALS, "");
+
+ int64_t locals_ref = vars.GetNewVariableReference(false);
+ vars.AddScopeKind(locals_ref, ScopeKind::Locals, frame_id);
+
+ lldb::SBValue found = vars.FindVariable(locals_ref, "");
EXPECT_EQ(found.IsValid(), dummy.IsValid());
}
>From d0f1e86dd1cd081427329c5b53b262274653556c Mon Sep 17 00:00:00 2001
From: Anthony <hello at anthonyeid.me>
Date: Thu, 28 Aug 2025 00:54:37 -0400
Subject: [PATCH 6/8] Fix set data breakpoint test by using the correct
variable reference
Variable reference 1 was hard coded to always be the local scope but
since the var_ref of local scope is dynamic now it uses a helper
function in the test
---
.../lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py b/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py
index a542a318050dd..211d71d43d155 100644
--- a/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py
+++ b/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py
@@ -106,8 +106,10 @@ def test_functionality(self):
self.set_source_breakpoints(source, [first_loop_break_line])
self.continue_to_next_stop()
self.dap_server.get_local_variables()
+ locals_ref = self.get_locals_scope_reference()
+ self.assertIsNotNone(locals_ref, "Failed to get locals scope reference")
# Test write watchpoints on x, arr[2]
- response_x = self.dap_server.request_dataBreakpointInfo(1, "x")
+ response_x = self.dap_server.request_dataBreakpointInfo(locals_ref, "x")
arr = self.dap_server.get_local_variable("arr")
response_arr_2 = self.dap_server.request_dataBreakpointInfo(
arr["variablesReference"], "[2]"
>From 875b926e8b04b072d56dbf2d75ce9f5732341382 Mon Sep 17 00:00:00 2001
From: Anthony <hello at anthonyeid.me>
Date: Thu, 28 Aug 2025 02:16:27 -0400
Subject: [PATCH 7/8] Have llm (Claude Opus 4) Generate a regression test
---
.../lldb-dap/variables/TestDAP_variables.py | 41 +++++++++++++++++++
1 file changed, 41 insertions(+)
diff --git a/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py b/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py
index a51d1f0ee179c..342dc4de7b895 100644
--- a/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py
+++ b/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py
@@ -832,3 +832,44 @@ def test_value_format(self):
self.assertEqual(var_pt_x["value"], "11")
var_pt_y = self.dap_server.get_local_variable_child("pt", "y", is_hex=is_hex)
self.assertEqual(var_pt_y["value"], "22")
+
+ @skipIfWindows
+ def test_variable_id_uniqueness_simple(self):
+ """
+ Simple regression test for variable ID uniqueness across frames.
+ Ensures variable IDs are not reused between different scopes/frames.
+ """
+ program = self.getBuildArtifact("a.out")
+ self.build_and_launch(program)
+ source = "main.cpp"
+
+ # Set breakpoint at test_indexedVariables call to get multiple frames
+ bp_line = line_number(source, "// breakpoint 3")
+ self.set_source_breakpoints(source, [bp_line])
+ self.continue_to_next_stop()
+
+ # Get stack frames
+ frames = self.get_stackFrames()
+ self.assertGreaterEqual(len(frames), 2, "Need at least 2 frames")
+
+ # Track all variable references for uniqueness check
+ all_refs = set()
+
+ # Check first 2-3 frames
+ for i in range(min(3, len(frames))):
+ frame_id = frames[i]['id']
+ scopes = self.dap_server.request_scopes(frame_id)["body"]["scopes"]
+
+ for scope in scopes:
+ ref = scope['variablesReference']
+ if ref != 0: # 0 means no variables
+ # Ensure this reference hasn't been used before
+ self.assertNotIn(ref, all_refs,
+ f"Variable reference {ref} was reused!")
+ all_refs.add(ref)
+
+ # Verify we collected references and they're still accessible
+ self.assertGreater(len(all_refs), 0, "Should have found variable references")
+ for ref in all_refs:
+ response = self.dap_server.request_variables(ref)
+ self.assertTrue(response['success'], f"Failed to access reference {ref}")
>From 4dae801003ed014378cfd95bfea1b6ddac0664b2 Mon Sep 17 00:00:00 2001
From: Anthony <hello at anthonyeid.me>
Date: Thu, 28 Aug 2025 02:26:15 -0400
Subject: [PATCH 8/8] Format test files with darker
---
.../lldb-dap/variables/TestDAP_variables.py | 19 +++++++------------
1 file changed, 7 insertions(+), 12 deletions(-)
diff --git a/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py b/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py
index 342dc4de7b895..705863edbc861 100644
--- a/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py
+++ b/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py
@@ -843,33 +843,28 @@ def test_variable_id_uniqueness_simple(self):
self.build_and_launch(program)
source = "main.cpp"
- # Set breakpoint at test_indexedVariables call to get multiple frames
bp_line = line_number(source, "// breakpoint 3")
self.set_source_breakpoints(source, [bp_line])
self.continue_to_next_stop()
- # Get stack frames
frames = self.get_stackFrames()
self.assertGreaterEqual(len(frames), 2, "Need at least 2 frames")
- # Track all variable references for uniqueness check
all_refs = set()
- # Check first 2-3 frames
for i in range(min(3, len(frames))):
- frame_id = frames[i]['id']
+ frame_id = frames[i]["id"]
scopes = self.dap_server.request_scopes(frame_id)["body"]["scopes"]
for scope in scopes:
- ref = scope['variablesReference']
- if ref != 0: # 0 means no variables
- # Ensure this reference hasn't been used before
- self.assertNotIn(ref, all_refs,
- f"Variable reference {ref} was reused!")
+ ref = scope["variablesReference"]
+ if ref != 0:
+ self.assertNotIn(
+ ref, all_refs, f"Variable reference {ref} was reused!"
+ )
all_refs.add(ref)
- # Verify we collected references and they're still accessible
self.assertGreater(len(all_refs), 0, "Should have found variable references")
for ref in all_refs:
response = self.dap_server.request_variables(ref)
- self.assertTrue(response['success'], f"Failed to access reference {ref}")
+ self.assertTrue(response["success"], f"Failed to access reference {ref}")
More information about the lldb-commits
mailing list