Author: Balázs Benics Date: 2026-02-17T19:33:51Z New Revision: 98c76d36432ccd85b88a82949391bd0ce7243bfd
URL: https://github.com/llvm/llvm-project/commit/98c76d36432ccd85b88a82949391bd0ce7243bfd DIFF: https://github.com/llvm/llvm-project/commit/98c76d36432ccd85b88a82949391bd0ce7243bfd.diff LOG: [clang][ssaf] Implement TUSummaryBuilder with test infrastructure (#181220) Also adds a ssaf::TestFixture to provide access to the private fields of the SSAF object for introspection. Assisted-By: claude rdar://168773578 Added: clang/lib/Analysis/Scalable/TUSummary/TUSummaryBuilder.cpp clang/unittests/Analysis/Scalable/TUSummaryBuilderTest.cpp clang/unittests/Analysis/Scalable/TestFixture.cpp clang/unittests/Analysis/Scalable/TestFixture.h Modified: clang/include/clang/Analysis/Scalable/Model/BuildNamespace.h clang/include/clang/Analysis/Scalable/Model/EntityId.h clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h clang/include/clang/Analysis/Scalable/Model/EntityLinkage.h clang/include/clang/Analysis/Scalable/Model/EntityName.h clang/include/clang/Analysis/Scalable/Model/SummaryName.h clang/include/clang/Analysis/Scalable/TUSummary/EntitySummary.h clang/include/clang/Analysis/Scalable/TUSummary/TUSummary.h clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h clang/lib/Analysis/Scalable/CMakeLists.txt clang/unittests/Analysis/Scalable/CMakeLists.txt clang/unittests/Analysis/Scalable/Registries/MockTUSummaryBuilder.h clang/unittests/Analysis/Scalable/Registries/SummaryExtractorRegistryTest.cpp Removed: ################################################################################ diff --git a/clang/include/clang/Analysis/Scalable/Model/BuildNamespace.h b/clang/include/clang/Analysis/Scalable/Model/BuildNamespace.h index 5ca26df1e9252..2cd8990708b8d 100644 --- a/clang/include/clang/Analysis/Scalable/Model/BuildNamespace.h +++ b/clang/include/clang/Analysis/Scalable/Model/BuildNamespace.h @@ -63,6 +63,7 @@ class BuildNamespace { bool operator<(const BuildNamespace &Other) const; friend class SerializationFormat; + friend class TestFixture; }; /// Represents a hierarchical sequence of build namespaces. @@ -75,8 +76,6 @@ class BuildNamespace { /// For example, an entity might be qualified by a compilation unit namespace /// followed by a shared library namespace. class NestedBuildNamespace { - friend class SerializationFormat; - std::vector<BuildNamespace> Namespaces; public: @@ -114,8 +113,8 @@ class NestedBuildNamespace { bool operator!=(const NestedBuildNamespace &Other) const; bool operator<(const NestedBuildNamespace &Other) const; - friend class JSONWriter; - friend class LinkUnitResolution; + friend class SerializationFormat; + friend class TestFixture; }; } // namespace clang::ssaf diff --git a/clang/include/clang/Analysis/Scalable/Model/EntityId.h b/clang/include/clang/Analysis/Scalable/Model/EntityId.h index e348486386cb6..00a6f3447bbce 100644 --- a/clang/include/clang/Analysis/Scalable/Model/EntityId.h +++ b/clang/include/clang/Analysis/Scalable/Model/EntityId.h @@ -30,6 +30,7 @@ class EntityIdTable; class EntityId { friend class EntityIdTable; friend class SerializationFormat; + friend class TestFixture; size_t Index; diff --git a/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h b/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h index a1099c4e4d0f8..6c5f27907adb4 100644 --- a/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h +++ b/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h @@ -22,6 +22,7 @@ namespace clang::ssaf { /// Entities are never removed. class EntityIdTable { friend class SerializationFormat; + friend class TestFixture; std::map<EntityName, EntityId> Entities; diff --git a/clang/include/clang/Analysis/Scalable/Model/EntityLinkage.h b/clang/include/clang/Analysis/Scalable/Model/EntityLinkage.h index ba5f7d3073a30..155ec69ea0b3a 100644 --- a/clang/include/clang/Analysis/Scalable/Model/EntityLinkage.h +++ b/clang/include/clang/Analysis/Scalable/Model/EntityLinkage.h @@ -18,6 +18,7 @@ namespace clang::ssaf { /// across translation units. class EntityLinkage { friend class SerializationFormat; + friend class TestFixture; public: enum class LinkageType { diff --git a/clang/include/clang/Analysis/Scalable/Model/EntityName.h b/clang/include/clang/Analysis/Scalable/Model/EntityName.h index 23890ab7bea43..72dd9ac803052 100644 --- a/clang/include/clang/Analysis/Scalable/Model/EntityName.h +++ b/clang/include/clang/Analysis/Scalable/Model/EntityName.h @@ -47,8 +47,8 @@ class EntityName { /// \param Namespace The namespace steps to append to this entity's namespace. EntityName makeQualified(NestedBuildNamespace Namespace) const; - friend class LinkUnitResolution; friend class SerializationFormat; + friend class TestFixture; }; } // namespace clang::ssaf diff --git a/clang/include/clang/Analysis/Scalable/Model/SummaryName.h b/clang/include/clang/Analysis/Scalable/Model/SummaryName.h index 785fe0eb10372..36a8cf5da78b1 100644 --- a/clang/include/clang/Analysis/Scalable/Model/SummaryName.h +++ b/clang/include/clang/Analysis/Scalable/Model/SummaryName.h @@ -31,6 +31,7 @@ class SummaryName { private: std::string Name; + friend class TestFixture; }; } // namespace clang::ssaf diff --git a/clang/include/clang/Analysis/Scalable/TUSummary/EntitySummary.h b/clang/include/clang/Analysis/Scalable/TUSummary/EntitySummary.h index 4bdb385d49a01..dddd69cb7d372 100644 --- a/clang/include/clang/Analysis/Scalable/TUSummary/EntitySummary.h +++ b/clang/include/clang/Analysis/Scalable/TUSummary/EntitySummary.h @@ -10,6 +10,7 @@ #define LLVM_CLANG_ANALYSIS_SCALABLE_TUSUMMARY_ENTITYSUMMARY_H #include "clang/Analysis/Scalable/Model/SummaryName.h" +#include <type_traits> namespace clang::ssaf { @@ -20,6 +21,10 @@ class EntitySummary { virtual SummaryName getSummaryName() const = 0; }; +template <typename Derived> +using DerivesFromEntitySummary = + std::enable_if_t<std::is_base_of_v<EntitySummary, Derived>>; + } // namespace clang::ssaf #endif // LLVM_CLANG_ANALYSIS_SCALABLE_TUSUMMARY_ENTITYSUMMARY_H diff --git a/clang/include/clang/Analysis/Scalable/TUSummary/TUSummary.h b/clang/include/clang/Analysis/Scalable/TUSummary/TUSummary.h index 2520ce87f3959..40cb7d582cf6e 100644 --- a/clang/include/clang/Analysis/Scalable/TUSummary/TUSummary.h +++ b/clang/include/clang/Analysis/Scalable/TUSummary/TUSummary.h @@ -36,6 +36,8 @@ class TUSummary { TUSummary(BuildNamespace TUNamespace) : TUNamespace(std::move(TUNamespace)) {} friend class SerializationFormat; + friend class TestFixture; + friend class TUSummaryBuilder; }; } // namespace clang::ssaf diff --git a/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h b/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h index fa679c145faa5..64d8adc0791c1 100644 --- a/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h +++ b/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h @@ -9,12 +9,68 @@ #ifndef LLVM_CLANG_ANALYSIS_SCALABLE_TUSUMMARY_TUSUMMARYBUILDER_H #define LLVM_CLANG_ANALYSIS_SCALABLE_TUSUMMARY_TUSUMMARYBUILDER_H +#include "clang/Analysis/Scalable/Model/EntityId.h" +#include "clang/Analysis/Scalable/TUSummary/EntitySummary.h" +#include <memory> +#include <utility> + namespace clang::ssaf { +class EntityName; +class TUSummary; + class TUSummaryBuilder { - // Empty for now. +public: + explicit TUSummaryBuilder(TUSummary &Summary) : Summary(Summary) {} + + /// Add an entity to the summary and return its EntityId. + /// If the entity already exists, returns the existing ID (idempotent). + EntityId addEntity(const EntityName &E); + + /// Associate the \p Data \c EntitySummary with the \p Entity. + /// This consumes the \p Data only if \p Entity wasn't associated yet with the + /// same kind of \c EntitySummary. + /// \returns a pointer to the \c EntitySummary and whether it inserted or not. + template <typename ConcreteEntitySummary, + DerivesFromEntitySummary<ConcreteEntitySummary> * = nullptr> + std::pair<EntitySummary *, bool> + addSummary(EntityId Entity, std::unique_ptr<ConcreteEntitySummary> &&Data); + +private: + TUSummary &Summary; + + std::pair<EntitySummary *, bool> + addSummaryImpl(EntityId Entity, std::unique_ptr<EntitySummary> &&Data); }; +// Why is this a template? +// +// We use template here to avoid an implicit conversion from +// `std::unique_ptr<ConcreteEntitySummary>` to `std::unique_ptr<EntitySummary>` +// because constructing that implicit temporary would unconditionally "consume" +// the Data. This would make it impossible to recover from the call-site the +// Data you pass in even if no insertion happens. +template <typename ConcreteEntitySummary, + DerivesFromEntitySummary<ConcreteEntitySummary> *> +std::pair<EntitySummary *, bool> +TUSummaryBuilder::addSummary(EntityId Entity, + std::unique_ptr<ConcreteEntitySummary> &&Data) { + // Prepare a unique_ptr of the base type to avoid implicit conversions at the + // call-site. + std::unique_ptr<EntitySummary> TypeErasedData = std::move(Data); + + // This only moves (consumes) TypeErasedData if insertion happened. + // Otherwise it doesn't touch the `TypeErasedData`. + auto [It, Inserted] = addSummaryImpl(Entity, std::move(TypeErasedData)); + + // Move it back on failue to keep the `Data` unconsumed. + if (!Inserted) { + Data = std::unique_ptr<ConcreteEntitySummary>( + static_cast<ConcreteEntitySummary *>(TypeErasedData.release())); + } + return {It, Inserted}; +} + } // namespace clang::ssaf #endif // LLVM_CLANG_ANALYSIS_SCALABLE_TUSUMMARY_TUSUMMARYBUILDER_H diff --git a/clang/lib/Analysis/Scalable/CMakeLists.txt b/clang/lib/Analysis/Scalable/CMakeLists.txt index 522fc9dcf078d..a7311e4d69d67 100644 --- a/clang/lib/Analysis/Scalable/CMakeLists.txt +++ b/clang/lib/Analysis/Scalable/CMakeLists.txt @@ -10,6 +10,7 @@ add_clang_library(clangAnalysisScalable Serialization/JSONFormat.cpp Serialization/SerializationFormatRegistry.cpp TUSummary/ExtractorRegistry.cpp + TUSummary/TUSummaryBuilder.cpp LINK_LIBS clangAST diff --git a/clang/lib/Analysis/Scalable/TUSummary/TUSummaryBuilder.cpp b/clang/lib/Analysis/Scalable/TUSummary/TUSummaryBuilder.cpp new file mode 100644 index 0000000000000..61c332e831ccb --- /dev/null +++ b/clang/lib/Analysis/Scalable/TUSummary/TUSummaryBuilder.cpp @@ -0,0 +1,21 @@ +#include "clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h" +#include "clang/Analysis/Scalable/Model/EntityId.h" +#include "clang/Analysis/Scalable/TUSummary/EntitySummary.h" +#include "clang/Analysis/Scalable/TUSummary/TUSummary.h" +#include <memory> +#include <utility> + +using namespace clang; +using namespace ssaf; + +EntityId TUSummaryBuilder::addEntity(const EntityName &E) { + return Summary.IdTable.getId(E); +} + +std::pair<EntitySummary *, bool> +TUSummaryBuilder::addSummaryImpl(EntityId Entity, + std::unique_ptr<EntitySummary> &&Data) { + auto &EntitySummaries = Summary.Data[Data->getSummaryName()]; + auto [It, Inserted] = EntitySummaries.try_emplace(Entity, std::move(Data)); + return {It->second.get(), Inserted}; +} diff --git a/clang/unittests/Analysis/Scalable/CMakeLists.txt b/clang/unittests/Analysis/Scalable/CMakeLists.txt index f1e1c874cc5cd..5529ca06de170 100644 --- a/clang/unittests/Analysis/Scalable/CMakeLists.txt +++ b/clang/unittests/Analysis/Scalable/CMakeLists.txt @@ -13,6 +13,8 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests Registries/SummaryExtractorRegistryTest.cpp Serialization/JSONFormatTest.cpp SummaryNameTest.cpp + TestFixture.cpp + TUSummaryBuilderTest.cpp CLANG_LIBS clangAnalysisScalable diff --git a/clang/unittests/Analysis/Scalable/Registries/MockTUSummaryBuilder.h b/clang/unittests/Analysis/Scalable/Registries/MockTUSummaryBuilder.h index ccb79ae042625..755a47471a9f8 100644 --- a/clang/unittests/Analysis/Scalable/Registries/MockTUSummaryBuilder.h +++ b/clang/unittests/Analysis/Scalable/Registries/MockTUSummaryBuilder.h @@ -14,6 +14,7 @@ namespace clang::ssaf { class MockTUSummaryBuilder : public TUSummaryBuilder { public: + using TUSummaryBuilder::TUSummaryBuilder; void sendMessage(llvm::Twine Message) { Stream << Message << '\n'; } std::string consumeMessages() { return std::move(OutputBuffer); } diff --git a/clang/unittests/Analysis/Scalable/Registries/SummaryExtractorRegistryTest.cpp b/clang/unittests/Analysis/Scalable/Registries/SummaryExtractorRegistryTest.cpp index 2076fae0b5ab0..a17a4f145b038 100644 --- a/clang/unittests/Analysis/Scalable/Registries/SummaryExtractorRegistryTest.cpp +++ b/clang/unittests/Analysis/Scalable/Registries/SummaryExtractorRegistryTest.cpp @@ -8,6 +8,7 @@ #include "MockTUSummaryBuilder.h" #include "clang/Analysis/Scalable/TUSummary/ExtractorRegistry.h" +#include "clang/Analysis/Scalable/TUSummary/TUSummary.h" #include "clang/Frontend/MultiplexConsumer.h" #include "clang/Tooling/Tooling.h" #include "llvm/ADT/StringRef.h" @@ -17,6 +18,11 @@ using namespace clang; using namespace ssaf; +[[nodiscard]] +static TUSummary makeFakeSummary() { + return BuildNamespace(BuildNamespaceKind::CompilationUnit, "Mock.cpp"); +} + namespace { TEST(SummaryExtractorRegistryTest, isTUSummaryExtractorRegistered) { @@ -39,7 +45,8 @@ TEST(SummaryExtractorRegistryTest, EnumeratingRegistryEntries) { } TEST(SummaryExtractorRegistryTest, InstantiatingExtractor1) { - MockTUSummaryBuilder FakeBuilder; + TUSummary Summary = makeFakeSummary(); + MockTUSummaryBuilder FakeBuilder(Summary); { auto Consumer = makeTUSummaryExtractor("MockSummaryExtractor1", FakeBuilder); @@ -52,7 +59,8 @@ TEST(SummaryExtractorRegistryTest, InstantiatingExtractor1) { } TEST(SummaryExtractorRegistryTest, InstantiatingExtractor2) { - MockTUSummaryBuilder FakeBuilder; + TUSummary Summary = makeFakeSummary(); + MockTUSummaryBuilder FakeBuilder(Summary); { auto Consumer = makeTUSummaryExtractor("MockSummaryExtractor2", FakeBuilder); @@ -65,7 +73,8 @@ TEST(SummaryExtractorRegistryTest, InstantiatingExtractor2) { } TEST(SummaryExtractorRegistryTest, InvokingExtractors) { - MockTUSummaryBuilder FakeBuilder; + TUSummary Summary = makeFakeSummary(); + MockTUSummaryBuilder FakeBuilder(Summary); std::vector<std::unique_ptr<ASTConsumer>> Consumers; for (std::string Name : {"MockSummaryExtractor1", "MockSummaryExtractor2"}) { auto Consumer = makeTUSummaryExtractor(Name, FakeBuilder); diff --git a/clang/unittests/Analysis/Scalable/TUSummaryBuilderTest.cpp b/clang/unittests/Analysis/Scalable/TUSummaryBuilderTest.cpp new file mode 100644 index 0000000000000..f70b39b97b841 --- /dev/null +++ b/clang/unittests/Analysis/Scalable/TUSummaryBuilderTest.cpp @@ -0,0 +1,269 @@ +//===- unittests/Analysis/Scalable/TUSummaryBuilderTest.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 "clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h" +#include "TestFixture.h" +#include "clang/Analysis/Scalable/Model/BuildNamespace.h" +#include "clang/Analysis/Scalable/Model/EntityId.h" +#include "clang/Analysis/Scalable/Model/EntityName.h" +#include "clang/Analysis/Scalable/Model/SummaryName.h" +#include "clang/Analysis/Scalable/Serialization/SerializationFormat.h" +#include "clang/Analysis/Scalable/TUSummary/EntitySummary.h" +#include "clang/Analysis/Scalable/TUSummary/TUSummary.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include <memory> +#include <type_traits> + +using namespace clang; +using namespace ssaf; + +using llvm::SmallVector; +using testing::Field; +using testing::Optional; +using testing::UnorderedElementsAre; + +[[nodiscard]] +static EntityId addTestEntity(TUSummaryBuilder &Builder, llvm::StringRef USR) { + return Builder.addEntity(EntityName(USR, /*Suffix=*/"", /*Namespace=*/{})); +} + +struct SummaryResult { + EntitySummary *Summary; + bool Inserted; +}; + +template <class ConcreteEntitySummary> +[[nodiscard]] +static auto addSummaryTo(TUSummaryBuilder &Builder, EntityId ID, + ConcreteEntitySummary Summary) { + static_assert(std::is_base_of_v<EntitySummary, ConcreteEntitySummary>); + auto NewSummary = std::make_unique<ConcreteEntitySummary>(std::move(Summary)); + SummaryName Name = NewSummary->getSummaryName(); + auto [Place, Inserted] = Builder.addSummary(ID, std::move(NewSummary)); + return std::pair{Name, SummaryResult{Place, Inserted}}; +} + +namespace { + +// Mock EntitySummary classes for testing +struct MockSummaryData1 final : public EntitySummary { + explicit MockSummaryData1(int Value) : Value(Value) {} + SummaryName getSummaryName() const override { + return SummaryName("MockSummary1"); + } + int Value; +}; + +struct MockSummaryData2 final : public EntitySummary { + explicit MockSummaryData2(std::string Text) : Text(std::move(Text)) {} + SummaryName getSummaryName() const override { + return SummaryName("MockSummary2"); + } + std::string Text; +}; + +struct MockSummaryData3 final : public EntitySummary { + explicit MockSummaryData3(bool Flag) : Flag(Flag) {} + SummaryName getSummaryName() const override { + return SummaryName("MockSummary3"); + } + bool Flag; +}; + +void PrintTo(const MockSummaryData1 &S, std::ostream *OS) { + *OS << "MockSummaryData1(" << S.getSummaryName().str().str() << ")"; +} +void PrintTo(const MockSummaryData2 &S, std::ostream *OS) { + *OS << "MockSummaryData2(" << S.getSummaryName().str().str() << ")"; +} +void PrintTo(const MockSummaryData3 &S, std::ostream *OS) { + *OS << "MockSummaryData3(" << S.getSummaryName().str().str() << ")"; +} + +struct TUSummaryBuilderTest : ssaf::TestFixture { + TUSummary Summary = + BuildNamespace(BuildNamespaceKind::CompilationUnit, "Mock.cpp"); + TUSummaryBuilder Builder = TUSummaryBuilder(this->Summary); + + [[nodiscard]] static SmallVector<SummaryName> + summaryNames(const TUSummary &Summary) { + return llvm::to_vector(llvm::make_first_range(getData(Summary))); + } + + [[nodiscard]] static SmallVector<EntityId> + entitiesOfSummary(const TUSummary &Summary, const SummaryName &Name) { + const auto &MappingIt = getData(Summary).find(Name); + if (MappingIt == getData(Summary).end()) + return {}; + return llvm::to_vector(llvm::make_first_range(MappingIt->second)); + } + + template <class ConcreteSummaryData> + [[nodiscard]] static std::optional<ConcreteSummaryData> + getAsEntitySummary(const TUSummary &Summary, const SummaryName &Name, + EntityId E) { + static_assert(std::is_base_of_v<EntitySummary, ConcreteSummaryData>); + const auto &MappingIt = getData(Summary).find(Name); + if (MappingIt == getData(Summary).end()) + return std::nullopt; + auto SummaryIt = MappingIt->second.find(E); + if (SummaryIt == MappingIt->second.end()) + return std::nullopt; + assert(Name == SummaryIt->second->getSummaryName()); + return static_cast<const ConcreteSummaryData &>(*SummaryIt->second); + } +}; + +TEST_F(TUSummaryBuilderTest, AddEntity) { + EntityName EN1("c:@F@foo", "", /*Namespace=*/{}); + EntityName EN2("c:@F@bar", "", /*Namespace=*/{}); + + EntityId ID = Builder.addEntity(EN1); + EntityId IDAlias = Builder.addEntity(EN1); + EXPECT_EQ(ID, IDAlias); // Idenpotency + + EntityId ID2 = Builder.addEntity(EN2); + EXPECT_NE(ID, ID2); + EXPECT_NE(IDAlias, ID2); + + const EntityIdTable &IdTable = getIdTable(Summary); + EXPECT_EQ(IdTable.count(), 2U); + EXPECT_TRUE(IdTable.contains(EN1)); + EXPECT_TRUE(IdTable.contains(EN2)); +} + +TEST_F(TUSummaryBuilderTest, TUSummaryBuilderAddSingleSummary) { + EntityId ID = addTestEntity(Builder, "c:@F@foo"); + auto [Name, Res] = addSummaryTo(Builder, ID, MockSummaryData1(10)); + ASSERT_TRUE(Res.Inserted); + ASSERT_TRUE(Res.Summary); + + // Should have a summary type with an entity. + EXPECT_THAT(summaryNames(Summary), UnorderedElementsAre(Name)); + EXPECT_THAT(entitiesOfSummary(Summary, Name), UnorderedElementsAre(ID)); + + EXPECT_THAT(getAsEntitySummary<MockSummaryData1>(Summary, Name, ID), + Optional(Field(&MockSummaryData1::Value, 10))); +} + +TEST_F(TUSummaryBuilderTest, AddMultipleSummariesToSameEntity) { + EntityId ID = addTestEntity(Builder, "c:@F@foo"); + + // Add diff erent summary types to the same entity. + auto [Name1, Res1] = addSummaryTo(Builder, ID, MockSummaryData1(42)); + auto [Name2, Res2] = addSummaryTo(Builder, ID, MockSummaryData2("test data")); + auto [Name3, Res3] = addSummaryTo(Builder, ID, MockSummaryData3(true)); + ASSERT_TRUE(Res1.Inserted); + ASSERT_TRUE(Res2.Inserted); + ASSERT_TRUE(Res3.Inserted); + ASSERT_TRUE(Res1.Summary); + ASSERT_TRUE(Res2.Summary); + ASSERT_TRUE(Res3.Summary); + + // All Names must be unique + EXPECT_EQ((std::set<SummaryName>{Name1, Name2, Name3}.size()), 3U); + + // Should have 3 summary type with the same entity. + EXPECT_THAT(summaryNames(Summary), UnorderedElementsAre(Name1, Name2, Name3)); + EXPECT_THAT(entitiesOfSummary(Summary, Name1), UnorderedElementsAre(ID)); + EXPECT_THAT(entitiesOfSummary(Summary, Name2), UnorderedElementsAre(ID)); + EXPECT_THAT(entitiesOfSummary(Summary, Name3), UnorderedElementsAre(ID)); + + EXPECT_THAT(getAsEntitySummary<MockSummaryData1>(Summary, Name1, ID), + Optional(Field(&MockSummaryData1::Value, 42))); + EXPECT_THAT(getAsEntitySummary<MockSummaryData2>(Summary, Name2, ID), + Optional(Field(&MockSummaryData2::Text, "test data"))); + EXPECT_THAT(getAsEntitySummary<MockSummaryData3>(Summary, Name3, ID), + Optional(Field(&MockSummaryData3::Flag, true))); +} + +TEST_F(TUSummaryBuilderTest, AddSameSummaryTypeToMultipleEntities) { + EntityId ID1 = addTestEntity(Builder, "c:@F@foo"); + EntityId ID2 = addTestEntity(Builder, "c:@F@bar"); + EntityId ID3 = addTestEntity(Builder, "c:@F@baz"); + + // Add the same summary type to diff erent entities. + auto [Name1, Res1] = addSummaryTo(Builder, ID1, MockSummaryData1(1)); + auto [Name2, Res2] = addSummaryTo(Builder, ID2, MockSummaryData1(2)); + auto [Name3, Res3] = addSummaryTo(Builder, ID3, MockSummaryData1(3)); + ASSERT_TRUE(Res1.Inserted); + ASSERT_TRUE(Res2.Inserted); + ASSERT_TRUE(Res3.Inserted); + ASSERT_TRUE(Res1.Summary); + ASSERT_TRUE(Res2.Summary); + ASSERT_TRUE(Res3.Summary); + + // All 3 should be the same summary type. + EXPECT_THAT((llvm::ArrayRef{Name1, Name2, Name3}), testing::Each(Name1)); + + // Should have only 1 summary type with 3 entities. + EXPECT_THAT(summaryNames(Summary), UnorderedElementsAre(Name1)); + EXPECT_THAT(entitiesOfSummary(Summary, Name1), + UnorderedElementsAre(ID1, ID2, ID3)); + + EXPECT_THAT(getAsEntitySummary<MockSummaryData1>(Summary, Name1, ID1), + Optional(Field(&MockSummaryData1::Value, 1))); + EXPECT_THAT(getAsEntitySummary<MockSummaryData1>(Summary, Name2, ID2), + Optional(Field(&MockSummaryData1::Value, 2))); + EXPECT_THAT(getAsEntitySummary<MockSummaryData1>(Summary, Name3, ID3), + Optional(Field(&MockSummaryData1::Value, 3))); +} + +TEST_F(TUSummaryBuilderTest, AddConflictingSummaryToSameEntity) { + EntityId ID = addTestEntity(Builder, "c:@F@foo"); + + auto [Name, Res] = addSummaryTo(Builder, ID, MockSummaryData1(10)); + ASSERT_TRUE(Res.Inserted); + ASSERT_TRUE(Res.Summary); + + // Check the initial value. + EXPECT_THAT(summaryNames(Summary), UnorderedElementsAre(Name)); + EXPECT_THAT(entitiesOfSummary(Summary, Name), UnorderedElementsAre(ID)); + EXPECT_THAT(getAsEntitySummary<MockSummaryData1>(Summary, Name, ID), + Optional(Field(&MockSummaryData1::Value, 10))); + + // This is a diff erent summary of the same kind. + auto NewSummary = std::make_unique<MockSummaryData1>(20); + ASSERT_EQ(NewSummary->getSummaryName(), Name); + + // Let's add this diff erent summary. + // This should keep the map intact and give us the existing entity summary. + auto [Slot, Inserted] = Builder.addSummary(ID, std::move(NewSummary)); + ASSERT_FALSE(Inserted); + ASSERT_TRUE(Slot); + + // Check that the summary object is not consumed and remained the same. + ASSERT_TRUE(NewSummary); + ASSERT_EQ(static_cast<MockSummaryData1 *>(NewSummary.get())->Value, 20); + + // Check that the Slot refers to the existing entity summary. + ASSERT_EQ(Slot->getSummaryName(), Name); + ASSERT_EQ(static_cast<MockSummaryData1 *>(Slot)->Value, 10); + + // Check that the values remained the same. + EXPECT_THAT(summaryNames(Summary), UnorderedElementsAre(Name)); + EXPECT_THAT(entitiesOfSummary(Summary, Name), UnorderedElementsAre(ID)); + EXPECT_THAT(getAsEntitySummary<MockSummaryData1>(Summary, Name, ID), + Optional(Field(&MockSummaryData1::Value, 10))); + + // We can update the existing summary. + static_cast<MockSummaryData1 *>(Res.Summary)->Value = 30; + + // Check that the values remained the same except what we updated. + EXPECT_THAT(summaryNames(Summary), UnorderedElementsAre(Name)); + EXPECT_THAT(entitiesOfSummary(Summary, Name), UnorderedElementsAre(ID)); + EXPECT_THAT(getAsEntitySummary<MockSummaryData1>(Summary, Name, ID), + Optional(Field(&MockSummaryData1::Value, 30))); +} + +} // namespace diff --git a/clang/unittests/Analysis/Scalable/TestFixture.cpp b/clang/unittests/Analysis/Scalable/TestFixture.cpp new file mode 100644 index 0000000000000..c3c7027c1dc3f --- /dev/null +++ b/clang/unittests/Analysis/Scalable/TestFixture.cpp @@ -0,0 +1,29 @@ +//===- unittests/Analysis/Scalable/TestFixture.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 "TestFixture.h" +#include "clang/Analysis/Scalable/Model/EntityId.h" +#include "llvm/Support/raw_ostream.h" +#include <ostream> +#include <string> + +using namespace clang; +using namespace ssaf; + +template <class T> static std::string asString(const T &Obj) { + std::string Repr; + llvm::raw_string_ostream(Repr) << Obj; + return Repr; +} + +void TestFixture::PrintTo(const EntityId &E, std::ostream *OS) { + *OS << "EntityId(" << E.Index << ")"; +} +void TestFixture::PrintTo(const SummaryName &N, std::ostream *OS) { + *OS << "SummaryName(" << N.Name << ")"; +} diff --git a/clang/unittests/Analysis/Scalable/TestFixture.h b/clang/unittests/Analysis/Scalable/TestFixture.h new file mode 100644 index 0000000000000..427769819a703 --- /dev/null +++ b/clang/unittests/Analysis/Scalable/TestFixture.h @@ -0,0 +1,36 @@ +//===- TestFixture.h --------------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_UNITTESTS_ANALYSIS_SCALABLE_TESTFIXTURE_H +#define LLVM_CLANG_UNITTESTS_ANALYSIS_SCALABLE_TESTFIXTURE_H + +#include "clang/Analysis/Scalable/Model/BuildNamespace.h" +#include "clang/Analysis/Scalable/Model/EntityId.h" +#include "clang/Analysis/Scalable/Model/EntityIdTable.h" +#include "clang/Analysis/Scalable/Model/EntityLinkage.h" +#include "clang/Analysis/Scalable/Model/SummaryName.h" +#include "clang/Analysis/Scalable/TUSummary/TUSummary.h" +#include "gtest/gtest.h" +#include <iosfwd> + +namespace clang::ssaf { + +class TestFixture : public ::testing::Test { +protected: +#define FIELD(CLASS, FIELD_NAME) \ + static const auto &get##FIELD_NAME(const CLASS &X) { return X.FIELD_NAME; } \ + static auto &get##FIELD_NAME(CLASS &X) { return X.FIELD_NAME; } +#include "clang/Analysis/Scalable/Model/PrivateFieldNames.def" + + static void PrintTo(const EntityId &, std::ostream *); + static void PrintTo(const SummaryName &, std::ostream *); +}; + +} // namespace clang::ssaf + +#endif // LLVM_CLANG_UNITTESTS_ANALYSIS_SCALABLE_TESTFIXTURE_H _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
