[Lldb-commits] [lldb] [LLDB] Detect cycles during Type resolution (PR #200304)
Adrian Prantl via lldb-commits
lldb-commits at lists.llvm.org
Thu May 28 16:48:54 PDT 2026
https://github.com/adrian-prantl created https://github.com/llvm/llvm-project/pull/200304
I got LLDB crash reports from the Swift plugin where (presumably malformed) debug info sends lldb_private::Type into an infite recursion. Most likely this is a bug in the DWARF parser, however, even malformed inputs shouldn't crash LLDB so this patch adds cycle detection.
rdar://177856769
Assisted-by: claude
>From 1ddf75e484638ac6f67ab1d2cb89a5d8217f937c Mon Sep 17 00:00:00 2001
From: Adrian Prantl <aprantl at apple.com>
Date: Thu, 28 May 2026 16:43:41 -0700
Subject: [PATCH] [LLDB] Detect cycles during Type resolution
I got LLDB crash reports from the Swift plugin where (presumably
malformed) debug info sends lldb_private::Type into an infite
recursion. Most likely this is a bug in the DWARF parser, however,
even malformed inputs shouldn't crash LLDB so this patch adds cycle
detection.
rdar://177856769
Assisted-by: claude
---
lldb/include/lldb/Symbol/Type.h | 1 +
lldb/source/Symbol/Type.cpp | 9 ++++
lldb/unittests/Symbol/TestType.cpp | 84 ++++++++++++++++++++++++++++++
3 files changed, 94 insertions(+)
diff --git a/lldb/include/lldb/Symbol/Type.h b/lldb/include/lldb/Symbol/Type.h
index 02b43e300a83e..84666a04818a5 100644
--- a/lldb/include/lldb/Symbol/Type.h
+++ b/lldb/include/lldb/Symbol/Type.h
@@ -586,6 +586,7 @@ class Type : public std::enable_shared_from_this<Type>, public UserID {
Declaration m_decl;
CompilerType m_compiler_type;
ResolveState m_compiler_type_resolve_state = ResolveState::Unresolved;
+ bool m_resolving_compiler_type = false;
/// Language-specific flags.
Payload m_payload;
diff --git a/lldb/source/Symbol/Type.cpp b/lldb/source/Symbol/Type.cpp
index 2296c3a9ea914..7681d7065a522 100644
--- a/lldb/source/Symbol/Type.cpp
+++ b/lldb/source/Symbol/Type.cpp
@@ -36,6 +36,7 @@
#include "lldb/lldb-private-enumerations.h"
#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/SaveAndRestore.h"
using namespace lldb;
using namespace lldb_private;
@@ -584,6 +585,14 @@ bool Type::WriteToMemory(ExecutionContext *exe_ctx, lldb::addr_t addr,
const Declaration &Type::GetDeclaration() const { return m_decl; }
bool Type::ResolveCompilerType(ResolveState compiler_type_resolve_state) {
+ if (m_resolving_compiler_type) {
+ LLDB_LOG(GetLog(LLDBLog::Symbols),
+ "Cycle detected while resolving type {0:x} ({1})", GetID(),
+ m_name.AsCString("<anonymous>"));
+ return false;
+ }
+ llvm::SaveAndRestore<bool> guard(m_resolving_compiler_type, true);
+
// TODO: This needs to consider the correct type system to use.
Type *encoding_type = nullptr;
if (!m_compiler_type.IsValid()) {
diff --git a/lldb/unittests/Symbol/TestType.cpp b/lldb/unittests/Symbol/TestType.cpp
index e3bb2cf6e69e2..b7dcfc64b55cb 100644
--- a/lldb/unittests/Symbol/TestType.cpp
+++ b/lldb/unittests/Symbol/TestType.cpp
@@ -10,6 +10,8 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include "lldb/Core/Declaration.h"
+#include "lldb/Symbol/SymbolFile.h"
#include "lldb/Symbol/Type.h"
#include "lldb/lldb-enumerations.h"
#include "lldb/lldb-private-enumerations.h"
@@ -168,3 +170,85 @@ TEST(Type, CompilerContextPattern) {
MatchesIgnoringModules(
std::vector{make_namespace("NS"), make_class("C")}));
}
+
+namespace {
+/// Minimal SymbolFile mock that lets us call SymbolFileCommon::MakeType.
+class CyclicTypeSymbolFile : public SymbolFileCommon {
+ static char ID;
+
+public:
+ bool isA(const void *ClassID) const override {
+ return ClassID == &ID || SymbolFileCommon::isA(ClassID);
+ }
+
+ CyclicTypeSymbolFile() : SymbolFileCommon(/*objfile_sp=*/nullptr) {}
+
+ llvm::StringRef GetPluginName() override { return "CyclicTypeSymbolFile"; }
+ uint32_t CalculateAbilities() override { return 0; }
+ lldb::LanguageType ParseLanguage(CompileUnit &) override {
+ return lldb::eLanguageTypeC;
+ }
+ size_t ParseFunctions(CompileUnit &) override { return 0; }
+ bool ParseLineTable(CompileUnit &) override { return false; }
+ bool ParseDebugMacros(CompileUnit &) override { return false; }
+ bool ParseSupportFiles(CompileUnit &, SupportFileList &) override {
+ return false;
+ }
+ size_t ParseTypes(CompileUnit &) override { return 0; }
+ bool ParseImportedModules(const SymbolContext &,
+ std::vector<SourceModule> &) override {
+ return false;
+ }
+ size_t ParseBlocksRecursive(Function &) override { return 0; }
+ size_t ParseVariablesForContext(const SymbolContext &) override { return 0; }
+ Type *ResolveTypeUID(lldb::user_id_t) override { return nullptr; }
+ std::optional<ArrayInfo>
+ GetDynamicArrayInfoForUID(lldb::user_id_t,
+ const ExecutionContext *) override {
+ return std::nullopt;
+ }
+ bool CompleteType(CompilerType &) override { return false; }
+ uint32_t ResolveSymbolContext(const Address &, lldb::SymbolContextItem,
+ SymbolContext &) override {
+ return 0;
+ }
+ void GetTypes(SymbolContextScope *, lldb::TypeClass, TypeList &) override {}
+
+ uint32_t CalculateNumCompileUnits() override { return 0; }
+ lldb::CompUnitSP ParseCompileUnitAtIndex(uint32_t) override { return {}; }
+};
+
+char CyclicTypeSymbolFile::ID;
+} // namespace
+
+// Two (malformed) types whose encoding chain forms a cycle (A's
+// encoding is B, B's encoding is A). Resolving them should terminate.
+TEST(Type, GetForwardCompilerTypeCycle) {
+ CyclicTypeSymbolFile symbol_file;
+ Declaration decl;
+
+ // Create A and B with eEncodingIsConstUID and an unresolved (invalid)
+ // CompilerType, so ResolveCompilerType enters the encoding-resolution path.
+ lldb::user_id_t a_uid = 1;
+ lldb::user_id_t b_uid = 2;
+ std::optional<uint64_t> byte_size;
+ SymbolContextScope *context = nullptr;
+ TypeSP a =
+ symbol_file.MakeType(a_uid, ConstString("A"), byte_size, context, b_uid,
+ Type::eEncodingIsConstUID, decl, CompilerType(),
+ Type::ResolveState::Unresolved);
+ TypeSP b =
+ symbol_file.MakeType(b_uid, ConstString("B"), byte_size, context, a_uid,
+ Type::eEncodingIsConstUID, decl, CompilerType(),
+ Type::ResolveState::Unresolved);
+ ASSERT_TRUE(a);
+ ASSERT_TRUE(b);
+
+ // Pre-populate the encoding pointers so GetEncodingType bypasses
+ // ResolveTypeUID and returns the cyclic peer directly.
+ a->SetEncodingType(b.get());
+ b->SetEncodingType(a.get());
+
+ EXPECT_FALSE(a->GetForwardCompilerType().IsValid());
+ EXPECT_FALSE(b->GetForwardCompilerType().IsValid());
+}
More information about the lldb-commits
mailing list