[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