llvm-project/lldb/tools/lldb-dap/Handler/SetVariableRequestHandler.cpp
John Harrison e9799e51ed
[lldb-dap] Improve support for variables with anonymous fields and types (#186482)
While looking at the '[raw]' value of a std::vector I noticed we didn't
handle the anonymous inner struct very well. The 'evaluateName' was
incorrect (e.g. the evaluateName would return `<var>.` for the anonymous
struct).

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

* Changed the name of anonymous fields from `<null>` to `(anonymous)`,
which matches other tooling like clangd's representation and how types
are presented if the field is not defined.
* Adjusts variables to not return an 'evaluateName' for anonymous
fields.
* Adjusted '[raw]' values to be marked as 'internal' which deemphasizes
them in the UI.

While working in this area, I also consolidated some helpers that are
only used within Variables.cpp.

Before my changes:
<img width="513" height="460" alt="before"
src="https://github.com/user-attachments/assets/3da0aada-8ba3-415d-bbec-56b41a9b9415"
/>

After my changes:
<img width="414" height="467" alt="after"
src="https://github.com/user-attachments/assets/66a47108-ee44-4e01-8eab-e89edb348fde"
/>
2026-03-18 11:39:22 -07:00

114 lines
4.1 KiB
C++

//===-- SetVariableRequestHandler.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 "JSONUtils.h"
#include "Protocol/DAPTypes.h"
#include "Protocol/ProtocolEvents.h"
#include "Protocol/ProtocolTypes.h"
#include "RequestHandler.h"
using namespace lldb_dap::protocol;
namespace lldb_dap {
static lldb::SBValue EvaluateExpression(lldb::SBTarget &target,
lldb::SBFrame &frame,
const std::string &expression) {
const char *expression_cstr = expression.c_str();
if (frame)
return frame.EvaluateExpression(expression_cstr);
// Evaluate expression in global scope.
return target.EvaluateExpression(expression_cstr);
}
/// Set the variable with the given name in the variable container to a new
/// value. Clients should only call this request if the corresponding capability
/// `supportsSetVariable` is true.
///
/// If a debug adapter implements both `setVariable` and `setExpression`,
/// a client will only use `setExpression` if the variable has an evaluateName
/// property.
llvm::Expected<SetVariableResponseBody>
SetVariableRequestHandler::Run(const SetVariableArguments &args) const {
const auto args_name = llvm::StringRef(args.name);
if (args.variablesReference.Kind() == eReferenceKindInvalid) {
return llvm::make_error<DAPError>(
llvm::formatv("invalid reference {}",
args.variablesReference.AsUInt32())
.str(),
llvm::inconvertibleErrorCode(),
/*show_user=*/false);
}
constexpr llvm::StringRef return_value_name = "(Return Value)";
if (args_name == return_value_name)
return llvm::make_error<DAPError>(
"cannot change the value of the return value");
lldb::SBValue variable =
dap.reference_storage.FindVariable(args.variablesReference, args_name);
if (!variable.IsValid())
return llvm::make_error<DAPError>("could not find variable in scope");
lldb::SBFrame frame = variable.GetFrame();
std::string expression = llvm::StringRef(args.value).trim().str();
lldb::SBValue result = EvaluateExpression(dap.target, frame, expression);
const char *value = result.IsValid() ? result.GetValue() : expression.c_str();
lldb::SBError error;
const bool success = variable.SetValueFromCString(value, error);
if (!success)
return llvm::make_error<DAPError>(error.GetCString());
const bool hex = args.format ? args.format->hex : false;
VariableDescription desc(variable,
dap.configuration.enableAutoVariableSummaries, hex);
SetVariableResponseBody body;
body.value = desc.display_value;
body.type = desc.display_type_name;
// We don't know the index of the variable in our dap.variables
// so always insert a new one to get its variablesReference.
// is_permanent is false because debug console does not support
// setVariable request.
const var_ref_t new_var_ref = dap.reference_storage.Insert(
variable, /*is_permanent=*/false, /*is_internal=*/false);
if (variable.MightHaveChildren()) {
body.variablesReference = new_var_ref;
if (desc.type_obj.IsArrayType())
body.indexedVariables = variable.GetNumChildren();
else
body.namedVariables = variable.GetNumChildren();
}
if (const lldb::addr_t addr = variable.GetLoadAddress();
addr != LLDB_INVALID_ADDRESS)
body.memoryReference = addr;
if (ValuePointsToCode(variable))
body.valueLocationReference = PackLocation(new_var_ref.AsUInt32(), true);
// Also send invalidated event to signal client that some variables
// (e.g. references) can be changed.
SendInvalidatedEvent(dap, {InvalidatedEventBody::eAreaVariables});
// Also send memory event to signal client that variable memory was changed.
SendMemoryEvent(dap, variable);
return body;
}
} // namespace lldb_dap