https://github.com/GeorgeHuyubo updated 
https://github.com/llvm/llvm-project/pull/175700

>From 80c562db68695ba5bb4508d92270bc6eba3e5aff Mon Sep 17 00:00:00 2001
From: George Hu <[email protected]>
Date: Mon, 12 Jan 2026 18:29:23 -0800
Subject: [PATCH] [lldb] Fix null pointer crash in
 LibStdcppTupleSyntheticFrontEnd::Update

---
 .../Language/CPlusPlus/LibStdcppTuple.cpp     |   2 +
 .../Language/CPlusPlus/CMakeLists.txt         |   4 +
 .../Language/CPlusPlus/LibStdcppTupleTest.cpp | 167 ++++++++++++++++++
 3 files changed, 173 insertions(+)
 create mode 100644 lldb/unittests/Language/CPlusPlus/LibStdcppTupleTest.cpp

diff --git a/lldb/source/Plugins/Language/CPlusPlus/LibStdcppTuple.cpp 
b/lldb/source/Plugins/Language/CPlusPlus/LibStdcppTuple.cpp
index cf72265bfbad3..076bbbb87448d 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/LibStdcppTuple.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/LibStdcppTuple.cpp
@@ -66,6 +66,8 @@ lldb::ChildCacheState 
LibStdcppTupleSyntheticFrontEnd::Update() {
     size_t child_count = current_child->GetNumChildrenIgnoringErrors();
     for (size_t i = 0; i < child_count; ++i) {
       ValueObjectSP child_sp = current_child->GetChildAtIndex(i);
+      if (!child_sp)
+        continue;
       llvm::StringRef name_str = child_sp->GetName().GetStringRef();
       if (name_str.starts_with("std::_Tuple_impl<")) {
         next_child_sp = child_sp;
diff --git a/lldb/unittests/Language/CPlusPlus/CMakeLists.txt 
b/lldb/unittests/Language/CPlusPlus/CMakeLists.txt
index 1d96fcf3db1b8..e680b5db77904 100644
--- a/lldb/unittests/Language/CPlusPlus/CMakeLists.txt
+++ b/lldb/unittests/Language/CPlusPlus/CMakeLists.txt
@@ -1,7 +1,11 @@
 add_lldb_unittest(LanguageCPlusPlusTests
   CPlusPlusLanguageTest.cpp
+  LibStdcppTupleTest.cpp
 
   LINK_LIBS
+    lldbCore
+    lldbHost
     lldbPluginCPlusPlusLanguage
+    lldbPluginTypeSystemClang
     LLVMTestingSupport
   )
diff --git a/lldb/unittests/Language/CPlusPlus/LibStdcppTupleTest.cpp 
b/lldb/unittests/Language/CPlusPlus/LibStdcppTupleTest.cpp
new file mode 100644
index 0000000000000..9c72796e0052f
--- /dev/null
+++ b/lldb/unittests/Language/CPlusPlus/LibStdcppTupleTest.cpp
@@ -0,0 +1,167 @@
+//===-- LibStdcppTupleTest.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 "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(), lldb::AccessType::eAccessPublic,
+        "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(), lldb::AccessType::eAccessPublic,
+        "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,
+                                        lldb::AccessType::eAccessPublic, 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;
+}

_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to