llvm-project/lldb/unittests/DAP/VariablesTest.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

228 lines
7.7 KiB
C++

//===-- VariablesTest.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 "Variables.h"
#include "DAPLog.h"
#include "Protocol/DAPTypes.h"
#include "Protocol/ProtocolTypes.h"
#include "TestingSupport/TestUtilities.h"
#include "lldb/API/SBDebugger.h"
#include "lldb/API/SBFrame.h"
#include "lldb/API/SBThread.h"
#include "lldb/API/SBValue.h"
#include "llvm/Testing/Support/Error.h"
#include "gtest/gtest.h"
#include <optional>
using namespace llvm;
using namespace lldb;
using namespace lldb_dap;
using namespace lldb_dap::protocol;
class VariablesTest : public ::testing::Test {
public:
VariablesTest() : log(llvm::nulls(), mutex), vars(log, config) {}
static void SetUpTestSuite() {
lldb::SBError error = SBDebugger::InitializeWithErrorHandling();
EXPECT_TRUE(error.Success());
}
static void TearDownTestSuite() { SBDebugger::Terminate(); }
void TearDown() override {
if (core)
ASSERT_THAT_ERROR(core->discard(), Succeeded());
if (binary)
ASSERT_THAT_ERROR(binary->discard(), Succeeded());
if (debugger)
debugger.Clear();
}
protected:
enum : bool { Permanent = true, Temporary = false };
Log::Mutex mutex;
Log log;
protocol::Configuration config;
VariableReferenceStorage vars;
lldb::SBDebugger debugger;
lldb::SBTarget target;
lldb::SBProcess process;
static constexpr llvm::StringLiteral k_binary = "linux-x86_64.out.yaml";
static constexpr llvm::StringLiteral k_core = "linux-x86_64.core.yaml";
std::optional<llvm::sys::fs::TempFile> core;
std::optional<llvm::sys::fs::TempFile> binary;
void CreateDebugger() { debugger = lldb::SBDebugger::Create(); }
void LoadCore() {
ASSERT_TRUE(debugger);
llvm::Expected<lldb_private::TestFile> binary_yaml =
lldb_private::TestFile::fromYamlFile(k_binary);
ASSERT_THAT_EXPECTED(binary_yaml, Succeeded());
llvm::Expected<llvm::sys::fs::TempFile> binary_file =
binary_yaml->writeToTemporaryFile();
ASSERT_THAT_EXPECTED(binary_file, Succeeded());
binary = std::move(*binary_file);
target = debugger.CreateTarget(binary->TmpName.data());
ASSERT_TRUE(target);
debugger.SetSelectedTarget(target);
llvm::Expected<lldb_private::TestFile> core_yaml =
lldb_private::TestFile::fromYamlFile(k_core);
ASSERT_THAT_EXPECTED(core_yaml, Succeeded());
llvm::Expected<llvm::sys::fs::TempFile> core_file =
core_yaml->writeToTemporaryFile();
ASSERT_THAT_EXPECTED(core_file, Succeeded());
this->core = std::move(*core_file);
process = target.LoadCore(this->core->TmpName.data());
ASSERT_TRUE(process);
}
static const protocol::Scope *
FindScope(const std::vector<protocol::Scope> &scopes,
const protocol::String &name) {
for (const auto &scope : scopes) {
if (scope.name == name)
return &scope;
}
return nullptr;
}
};
TEST_F(VariablesTest, GetNewVariableReference_UniqueAndRanges) {
CreateDebugger();
LoadCore();
auto x15 = target.CreateValueFromExpression("x", "15");
auto y42 = target.CreateValueFromExpression("y", "42");
auto gzero = target.CreateValueFromExpression("$0", "42");
auto gone = target.CreateValueFromExpression("$1", "7");
const var_ref_t temp1 = vars.Insert(x15, Temporary, /*is_internal=*/false);
const var_ref_t temp2 = vars.Insert(y42, Temporary, /*is_internal=*/false);
const var_ref_t perm1 = vars.Insert(gzero, Permanent, /*is_internal=*/false);
const var_ref_t perm2 = vars.Insert(gone, Permanent, /*is_internal=*/false);
EXPECT_NE(temp1.AsUInt32(), temp2.AsUInt32());
EXPECT_NE(perm1.AsUInt32(), perm2.AsUInt32());
EXPECT_LT(temp1.AsUInt32(), perm1.AsUInt32());
EXPECT_LT(temp2.AsUInt32(), perm1.AsUInt32());
}
TEST_F(VariablesTest, InsertAndGetVariable_Temporary) {
lldb::SBValue dummy;
const var_ref_t ref = vars.Insert(dummy, Temporary, /*is_internal=*/false);
lldb::SBValue out = vars.GetVariable(ref);
EXPECT_EQ(out.IsValid(), dummy.IsValid());
}
TEST_F(VariablesTest, InsertAndGetVariable_Permanent) {
lldb::SBValue dummy;
const var_ref_t ref = vars.Insert(dummy, Permanent, /*is_internal=*/false);
lldb::SBValue out = vars.GetVariable(ref);
EXPECT_EQ(out.IsValid(), dummy.IsValid());
}
TEST_F(VariablesTest, IsPermanentVariableReference) {
const var_ref_t perm =
vars.Insert(lldb::SBValue(), Permanent, /*is_internal=*/false);
const var_ref_t temp =
vars.Insert(lldb::SBValue(), Temporary, /*is_internal=*/false);
EXPECT_EQ(perm.Kind(), eReferenceKindPermanent);
EXPECT_EQ(temp.Kind(), eReferenceKindTemporary);
}
TEST_F(VariablesTest, Clear_RemovesTemporaryKeepsPermanent) {
lldb::SBValue dummy;
const var_ref_t temp = vars.Insert(dummy, Temporary, /*is_internal=*/false);
const var_ref_t perm = vars.Insert(dummy, Permanent, /*is_internal=*/false);
vars.Clear();
EXPECT_FALSE(vars.GetVariable(temp).IsValid());
EXPECT_EQ(vars.GetVariable(perm).IsValid(), dummy.IsValid());
}
TEST_F(VariablesTest, VariablesStore) {
CreateDebugger();
LoadCore();
lldb::SBFrame frame = process.GetSelectedThread().GetSelectedFrame();
std::vector<protocol::Scope> scopes = vars.Insert(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");
ASSERT_NE(locals_scope, nullptr);
ASSERT_NE(globals_scope, nullptr);
ASSERT_NE(registers_scope, nullptr);
auto *locals_store = vars.GetVariableStore(locals_scope->variablesReference);
auto *globals_store =
vars.GetVariableStore(globals_scope->variablesReference);
auto *registers_store =
vars.GetVariableStore(registers_scope->variablesReference);
ASSERT_NE(locals_store, nullptr);
ASSERT_NE(globals_store, nullptr);
ASSERT_NE(registers_store, nullptr);
const var_ref_t local_ref = locals_scope->variablesReference;
const var_ref_t global_ref = globals_scope->variablesReference;
const var_ref_t register_ref = registers_scope->variablesReference;
ASSERT_EQ(global_ref.Kind(), eReferenceKindTemporary);
ASSERT_EQ(local_ref.Kind(), eReferenceKindTemporary);
ASSERT_EQ(register_ref.Kind(), eReferenceKindTemporary);
EXPECT_EQ(vars.GetVariableStore(var_ref_t(9999)), nullptr);
ASSERT_TRUE(vars.FindVariable(local_ref, "rect").IsValid());
auto variables = locals_store->GetVariables({});
ASSERT_THAT_EXPECTED(variables, Succeeded());
ASSERT_EQ(variables->size(), 1u);
auto rect = variables->at(0);
ASSERT_EQ(rect.name, "rect");
VariablesArguments args;
args.variablesReference = rect.variablesReference;
auto *store = vars.GetVariableStore(args.variablesReference);
ASSERT_NE(store, nullptr);
variables = store->GetVariables(args);
ASSERT_THAT_EXPECTED(variables, Succeeded());
ASSERT_EQ(variables->size(), 4u);
EXPECT_EQ(variables->at(0).name, "x");
EXPECT_EQ(variables->at(0).value, "5");
EXPECT_EQ(variables->at(1).name, "y");
EXPECT_EQ(variables->at(1).value, "5");
EXPECT_EQ(variables->at(2).name, "height");
EXPECT_EQ(variables->at(2).value, "25");
EXPECT_EQ(variables->at(3).name, "width");
EXPECT_EQ(variables->at(3).value, "30");
}
TEST_F(VariablesTest, FindVariable_LocalsByName) {
lldb::SBFrame frame;
std::vector<protocol::Scope> scopes = vars.Insert(frame);
const protocol::Scope *locals_scope = FindScope(scopes, "Locals");
ASSERT_NE(locals_scope, nullptr);
lldb::SBValue found = vars.FindVariable(locals_scope->variablesReference, "");
lldb::SBValue dummy;
EXPECT_EQ(found.IsValid(), dummy.IsValid());
}