blob: 77bbd6b8f3825b95a41c6e361b6a36662504cc8d [file] [log] [blame] [edit]
//===----------------------------------------------------------------------===//
//
// 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 "Plugins/Language/CPlusPlus/LibStdcpp.h"
#include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
#include "TestingSupport/SubsystemRAII.h"
#include "TestingSupport/Symbol/ClangTestUtils.h"
#include "lldb/DataFormatters/TypeSynthetic.h"
#include "lldb/ValueObject/ValueObject.h"
#include "gtest/gtest.h"
using namespace lldb;
using namespace lldb_private;
namespace {
/// A minimal ValueObject mock that returns null from GetChildAtIndex for a
/// specified index. This simulates the crash scenario where GetChildAtIndex()
/// returns null due to incomplete debug info or type system errors.
class ValueObjectWithNullChild : public ValueObject {
public:
static lldb::ValueObjectSP Create(CompilerType type, ConstString name,
size_t null_child_idx) {
auto manager = ValueObjectManager::Create();
auto *obj =
new ValueObjectWithNullChild(*manager, type, name, null_child_idx);
return obj->GetSP();
}
~ValueObjectWithNullChild() override = default;
llvm::Expected<uint64_t> GetByteSize() override { return 4; }
lldb::ValueType GetValueType() const override {
return lldb::eValueTypeConstResult;
}
llvm::Expected<uint32_t> CalculateNumChildren(uint32_t max) override {
auto num_or_err = m_type.GetNumChildren(true, nullptr);
if (!num_or_err)
return num_or_err.takeError();
return *num_or_err;
}
ConstString GetTypeName() override { return m_type.GetTypeName(); }
ConstString GetDisplayTypeName() override { return GetTypeName(); }
bool IsInScope() override { return true; }
protected:
bool UpdateValue() override {
m_error.Clear();
return true;
}
CompilerType GetCompilerTypeImpl() override { return m_type; }
/// This is the key method - return null for the specified child index
/// to simulate the crash scenario.
ValueObject *CreateChildAtIndex(size_t idx) override {
if (idx == m_null_child_idx) {
return nullptr; // Return null to trigger the crash scenario!
}
return ValueObject::CreateChildAtIndex(idx);
}
private:
ValueObjectWithNullChild(ValueObjectManager &manager, CompilerType type,
ConstString name, size_t null_child_idx)
: ValueObject(nullptr, manager), m_type(type),
m_null_child_idx(null_child_idx) {
SetName(name);
}
CompilerType m_type;
size_t m_null_child_idx;
};
class LibStdcppTupleTest : public ::testing::Test {
public:
SubsystemRAII<FileSystem, HostInfo> m_subsystems;
void SetUp() override {
m_holder = std::make_unique<clang_utils::TypeSystemClangHolder>("test");
m_type_system = m_holder->GetAST();
}
/// Create a struct type with a child named "std::_Tuple_impl<0, int>"
/// to trigger the tuple synthetic frontend's child iteration.
CompilerType CreateTypeWithTupleImplChild() {
// Create outer type
CompilerType outer_type = m_type_system->CreateRecordType(
m_type_system->getASTContext().getTranslationUnitDecl(),
OptionalClangModuleID(), "std::tuple<int>", 0,
lldb::LanguageType::eLanguageTypeC_plus_plus);
// Create inner _Tuple_impl type
CompilerType inner_type = m_type_system->CreateRecordType(
m_type_system->getASTContext().getTranslationUnitDecl(),
OptionalClangModuleID(), "std::_Tuple_impl<0, int>", 0,
lldb::LanguageType::eLanguageTypeC_plus_plus);
TypeSystemClang::StartTagDeclarationDefinition(inner_type);
TypeSystemClang::CompleteTagDeclarationDefinition(inner_type);
// Add the inner type as a field of the outer type
TypeSystemClang::StartTagDeclarationDefinition(outer_type);
m_type_system->AddFieldToRecordType(outer_type, "std::_Tuple_impl<0, int>",
inner_type, 0);
TypeSystemClang::CompleteTagDeclarationDefinition(outer_type);
return outer_type;
}
TypeSystemClang *m_type_system;
private:
std::unique_ptr<clang_utils::TypeSystemClangHolder> m_holder;
};
} // anonymous namespace
TEST_F(LibStdcppTupleTest, CreatorHandlesNullValueObject) {
auto *frontend = formatters::LibStdcppTupleSyntheticFrontEndCreator(
nullptr, lldb::ValueObjectSP());
EXPECT_EQ(frontend, nullptr);
}
/// This test verifies the null child handling fix.
/// It creates a ValueObject that returns null from GetChildAtIndex(0),
/// simulating the crash scenario from incomplete debug info.
/// WITHOUT the fix (null check), this test will crash with SIGSEGV.
/// WITH the fix, this test passes.
TEST_F(LibStdcppTupleTest, UpdateHandlesNullChild) {
CompilerType type = CreateTypeWithTupleImplChild();
// Create a ValueObject that returns null for child at index 0
auto valobj_sp = ValueObjectWithNullChild::Create(
type, ConstString("test_tuple"), 0 /* null_child_idx */);
ASSERT_TRUE(valobj_sp);
// Verify our mock returns null for child 0
ASSERT_FALSE(valobj_sp->GetChildAtIndex(0));
// Create the frontend - this calls Update() which iterates through children.
// WITHOUT the null check fix, this crashes with SIGSEGV when trying to call
// GetName() on a null child_sp.
// WITH the fix, this succeeds because null children are skipped.
auto *frontend =
formatters::LibStdcppTupleSyntheticFrontEndCreator(nullptr, valobj_sp);
ASSERT_NE(frontend, nullptr);
// If we get here, the null check worked.
auto num_children = frontend->CalculateNumChildren();
ASSERT_TRUE(static_cast<bool>(num_children));
delete frontend;
}