llvm-project/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
Anthony Eid 6ca8f79fc8
lldb-dap: Stop using replicated variable ids (#124232)
Closes #119784 

Probably closes #147105 as well, but I couldn't test due to #156473: 

This PR fixes two bugs:
1. It generates unique variable reference IDs per suspended debuggee
state.
2. It stores all created variables in a stopped state instead of
dropping variables in unselected scopes. So it can properly handle all
scope/variable requests

It does this by storing all variables in their respective scopes and
using that mapping in request handlers that relied on the old mapping.
It dynamically creates new variable/scope IDs instead of resetting IDs
whenever a new scope is created.

I also removed some unused code as well.

---------

Co-authored-by: Med Ismail Bennani <ismail@bennani.ma>
Co-authored-by: Jonas Devlieghere <jonas@devlieghere.com>
Co-authored-by: Ebuka Ezike <yerimyah1@gmail.com>
2026-01-30 12:51:47 -08:00

174 lines
7.3 KiB
C++

//===-- VariablesRequestHandler.cpp ---------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "DAP.h"
#include "EventHelper.h"
#include "Handler/RequestHandler.h"
#include "JSONUtils.h"
#include "ProtocolUtils.h"
#include "Variables.h"
using namespace llvm;
using namespace lldb_dap::protocol;
namespace lldb_dap {
/// Retrieves all child variables for the given variable reference.
///
/// A filter can be used to limit the fetched children to either named or
/// indexed children.
Expected<VariablesResponseBody>
VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
const uint64_t var_ref = arguments.variablesReference;
const uint64_t count = arguments.count;
const uint64_t start = arguments.start;
const bool hex = arguments.format ? arguments.format->hex : false;
std::vector<Variable> variables;
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;
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
// 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 = 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);
const lldb::Format format = reg.GetFormat();
if (format == lldb::eFormatDefault || format == lldb::eFormatHex) {
if (reg.GetByteSize() == addr_size)
reg.SetFormat(lldb::eFormatAddressInfo);
}
}
}
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 = scope_data->scope.GetError();
const char *var_err = error.GetCString();
if (var_err) {
// Create a fake variable named "error" to explain why variables were
// not available. This new error will help let users know when there was
// a problem that kept variables from being available for display and
// allow users to fix this issue instead of seeing no variables. The
// errors are only set when there is a problem that the user could
// fix, so no error will show up when you have no debug info, only when
// we do have debug info and something that is fixable can be done.
Variable var;
var.name = "<error>";
var.type = "const char *";
var.value = var_err;
variables.emplace_back(var);
}
}
const int64_t end_idx = start_idx + ((count == 0) ? num_children : count);
// 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 = scope_data->scope.GetValueAtIndex(i);
if (!variable.IsValid())
break;
variable_name_counts[GetNonNullVariableName(variable)]++;
}
// Show return value if there is any ( in the local top frame )
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();
if (stop_return_value.IsValid() &&
(selected_thread.GetSelectedFrame().GetFrameID() == 0)) {
auto renamed_return_value = stop_return_value.Clone("(Return Value)");
int64_t return_var_ref = 0;
if (stop_return_value.MightHaveChildren() ||
stop_return_value.IsSynthetic()) {
return_var_ref = dap.variables.InsertVariable(stop_return_value,
/*is_permanent=*/false);
}
variables.emplace_back(CreateVariable(
renamed_return_value, return_var_ref, hex,
dap.configuration.enableAutoVariableSummaries,
dap.configuration.enableSyntheticChildDebugging, false));
}
}
// Now we construct the result with unique display variable names
for (auto i = start_idx; i < end_idx; ++i) {
lldb::SBValue variable = scope_data->scope.GetValueAtIndex(i);
if (!variable.IsValid())
break;
const int64_t frame_var_ref =
dap.variables.InsertVariable(variable, /*is_permanent=*/false);
variables.emplace_back(CreateVariable(
variable, frame_var_ref, hex,
dap.configuration.enableAutoVariableSummaries,
dap.configuration.enableSyntheticChildDebugging,
variable_name_counts[GetNonNullVariableName(variable)] > 1));
}
} else {
// We are expanding a variable that has children, so we will return its
// children.
lldb::SBValue variable = dap.variables.GetVariable(var_ref);
if (variable.IsValid()) {
const bool is_permanent =
dap.variables.IsPermanentVariableReference(var_ref);
auto addChild = [&](lldb::SBValue child,
std::optional<llvm::StringRef> custom_name = {}) {
if (!child.IsValid())
return;
const int64_t child_var_ref =
dap.variables.InsertVariable(child, is_permanent);
variables.emplace_back(
CreateVariable(child, child_var_ref, hex,
dap.configuration.enableAutoVariableSummaries,
dap.configuration.enableSyntheticChildDebugging,
/*is_name_duplicated=*/false, custom_name));
};
const int64_t num_children = variable.GetNumChildren();
const int64_t end_idx = start + ((count == 0) ? num_children : count);
int64_t i = start;
for (; i < end_idx && i < num_children; ++i)
addChild(variable.GetChildAtIndex(i));
// If we haven't filled the count quota from the request, we insert a new
// "[raw]" child that can be used to inspect the raw version of a
// synthetic member. That eliminates the need for the user to go to the
// debug console and type `frame var <variable> to get these values.
if (dap.configuration.enableSyntheticChildDebugging &&
variable.IsSynthetic() && i == num_children)
addChild(variable.GetNonSyntheticValue(), "[raw]");
}
}
return VariablesResponseBody{std::move(variables)};
}
} // namespace lldb_dap