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, 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("")); + return false; + } + llvm::SaveAndRestore 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 &) 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 + 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 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()); +}