//===- AnalysisDriverTest.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/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisDriver.h" #include "../TestFixture.h" #include "clang/ScalableStaticAnalysisFramework/Core/EntityLinker/LUSummary.h" #include "clang/ScalableStaticAnalysisFramework/Core/Model/BuildNamespace.h" #include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityId.h" #include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityLinkage.h" #include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityName.h" #include "clang/ScalableStaticAnalysisFramework/Core/Model/SummaryName.h" #include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.h" #include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisRegistry.h" #include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisResult.h" #include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/DerivedAnalysis.h" #include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/SummaryAnalysis.h" #include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/WPASuite.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Testing/Support/Error.h" #include "gtest/gtest.h" #include #include #include #include using namespace clang; using namespace ssaf; namespace { // --------------------------------------------------------------------------- // Instance counter // --------------------------------------------------------------------------- static int NextSummaryInstanceId = 0; // --------------------------------------------------------------------------- // Entity summaries // --------------------------------------------------------------------------- class Analysis1EntitySummary final : public EntitySummary { public: int InstanceId = NextSummaryInstanceId++; static SummaryName summaryName() { return SummaryName("Analysis1"); } SummaryName getSummaryName() const override { return SummaryName("Analysis1"); } }; class Analysis2EntitySummary final : public EntitySummary { public: int InstanceId = NextSummaryInstanceId++; static SummaryName summaryName() { return SummaryName("Analysis2"); } SummaryName getSummaryName() const override { return SummaryName("Analysis2"); } }; class Analysis3EntitySummary final : public EntitySummary { public: int InstanceId = NextSummaryInstanceId++; static SummaryName summaryName() { return SummaryName("Analysis3"); } SummaryName getSummaryName() const override { return SummaryName("Analysis3"); } }; class Analysis4EntitySummary final : public EntitySummary { public: int InstanceId = NextSummaryInstanceId++; static SummaryName summaryName() { return SummaryName("Analysis4"); } SummaryName getSummaryName() const override { return SummaryName("Analysis4"); } }; // --------------------------------------------------------------------------- // Analysis names // --------------------------------------------------------------------------- const AnalysisName Analysis1Name("Analysis1"); const AnalysisName Analysis2Name("Analysis2"); const AnalysisName Analysis3Name("Analysis3"); const AnalysisName Analysis4Name("Analysis4"); const AnalysisName Analysis5Name("Analysis5"); const AnalysisName CycleAName("CycleA"); const AnalysisName CycleBName("CycleB"); // --------------------------------------------------------------------------- // Results // --------------------------------------------------------------------------- class Analysis1Result final : public AnalysisResult { public: static AnalysisName analysisName() { return Analysis1Name; } std::vector> Entries; bool WasInitialized = false; bool WasFinalized = false; }; class Analysis2Result final : public AnalysisResult { public: static AnalysisName analysisName() { return Analysis2Name; } std::vector> Entries; bool WasInitialized = false; bool WasFinalized = false; }; // No analysis or registration for Analysis3. Data for Analysis3 is inserted // into the LUSummary to verify the driver silently skips it. class Analysis3Result final : public AnalysisResult { public: static AnalysisName analysisName() { return Analysis3Name; } }; // Analysis4 has a registered analysis but no data is inserted into the // LUSummary, so it is skipped and get() returns nullptr. class Analysis4Result final : public AnalysisResult { public: static AnalysisName analysisName() { return Analysis4Name; } std::vector> Entries; bool WasInitialized = false; bool WasFinalized = false; }; // Analysis5 is a derived analysis that depends on Analysis1, Analysis2, and // Analysis4. It verifies that the driver passes dependency results to // initialize() and that the initialize/step/finalize lifecycle is respected. class Analysis5Result final : public AnalysisResult { public: static AnalysisName analysisName() { return Analysis5Name; } std::vector CallSequence; std::vector> Analysis1Entries; std::vector> Analysis2Entries; std::vector> Analysis4Entries; }; // CycleA and CycleB form a dependency cycle (CycleA → CycleB → CycleA). // Registered solely to exercise cycle detection in AnalysisDriver::toposort(). // initialize() and step() are unreachable stubs - the cycle is caught before // any analysis executes. class CycleAResult final : public AnalysisResult { public: static AnalysisName analysisName() { return CycleAName; } }; class CycleBResult final : public AnalysisResult { public: static AnalysisName analysisName() { return CycleBName; } }; // --------------------------------------------------------------------------- // Analyses // --------------------------------------------------------------------------- class Analysis1 final : public SummaryAnalysis { public: inline static bool WasDestroyed = false; ~Analysis1() { WasDestroyed = true; } llvm::Error initialize() override { getResult().WasInitialized = true; return llvm::Error::success(); } llvm::Error add(EntityId Id, const Analysis1EntitySummary &S) override { getResult().Entries.push_back({Id, S.InstanceId}); return llvm::Error::success(); } llvm::Error finalize() override { getResult().WasFinalized = true; return llvm::Error::success(); } }; static AnalysisRegistry::Add RegAnalysis1("Analysis for Analysis1"); class Analysis2 final : public SummaryAnalysis { public: inline static bool WasDestroyed = false; ~Analysis2() { WasDestroyed = true; } llvm::Error initialize() override { getResult().WasInitialized = true; return llvm::Error::success(); } llvm::Error add(EntityId Id, const Analysis2EntitySummary &S) override { getResult().Entries.push_back({Id, S.InstanceId}); return llvm::Error::success(); } llvm::Error finalize() override { getResult().WasFinalized = true; return llvm::Error::success(); } }; static AnalysisRegistry::Add RegAnalysis2("Analysis for Analysis2"); // No Analysis3 or registration for Analysis3. class Analysis4 final : public SummaryAnalysis { public: inline static bool WasDestroyed = false; ~Analysis4() { WasDestroyed = true; } llvm::Error initialize() override { getResult().WasInitialized = true; return llvm::Error::success(); } llvm::Error add(EntityId Id, const Analysis4EntitySummary &S) override { getResult().Entries.push_back({Id, S.InstanceId}); return llvm::Error::success(); } llvm::Error finalize() override { getResult().WasFinalized = true; return llvm::Error::success(); } }; static AnalysisRegistry::Add RegAnalysis4("Analysis for Analysis4"); class Analysis5 final : public DerivedAnalysis { int StepCount = 0; public: inline static bool WasDestroyed = false; ~Analysis5() { WasDestroyed = true; } llvm::Error initialize(const Analysis1Result &R1, const Analysis2Result &R2, const Analysis4Result &R4) override { getResult().CallSequence.push_back("initialize"); getResult().Analysis1Entries = R1.Entries; getResult().Analysis2Entries = R2.Entries; getResult().Analysis4Entries = R4.Entries; return llvm::Error::success(); } llvm::Expected step() override { getResult().CallSequence.push_back("step"); return ++StepCount < 2; } llvm::Error finalize() override { getResult().CallSequence.push_back("finalize"); return llvm::Error::success(); } }; static AnalysisRegistry::Add RegAnalysis5("Analysis for Analysis5"); class CycleA final : public DerivedAnalysis { public: llvm::Error initialize(const CycleBResult &) override { return llvm::Error::success(); } llvm::Expected step() override { return false; } }; static AnalysisRegistry::Add RegCycleA("Cyclic analysis A (test only)"); class CycleB final : public DerivedAnalysis { public: llvm::Error initialize(const CycleAResult &) override { return llvm::Error::success(); } llvm::Expected step() override { return false; } }; static AnalysisRegistry::Add RegCycleB("Cyclic analysis B (test only)"); // --------------------------------------------------------------------------- // Fixture // --------------------------------------------------------------------------- class AnalysisDriverTest : public TestFixture { protected: static constexpr EntityLinkage ExternalLinkage = EntityLinkage(EntityLinkageType::External); void SetUp() override { NextSummaryInstanceId = 0; Analysis1::WasDestroyed = false; Analysis2::WasDestroyed = false; // No Analysis3 - not registered, so no WasDestroyed flag. Analysis4::WasDestroyed = false; Analysis5::WasDestroyed = false; } std::unique_ptr makeLUSummary() { NestedBuildNamespace NS( {BuildNamespace(BuildNamespaceKind::LinkUnit, "TestLU")}); return std::make_unique(std::move(NS)); } EntityId addEntity(LUSummary &LU, llvm::StringRef USR) { NestedBuildNamespace NS( {BuildNamespace(BuildNamespaceKind::LinkUnit, "TestLU")}); EntityName Name(USR.str(), "", NS); EntityId Id = getIdTable(LU).getId(Name); getLinkageTable(LU).insert({Id, ExternalLinkage}); return Id; } static bool hasEntry(const std::vector> &Entries, EntityId Id, int InstanceId) { return llvm::is_contained(Entries, std::make_pair(Id, InstanceId)); } template int insertSummary(LUSummary &LU, llvm::StringRef SN, EntityId Id) { auto S = std::make_unique(); int InstanceId = S->InstanceId; getData(LU)[SummaryName(SN.str())][Id] = std::move(S); return InstanceId; } }; // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- TEST(AnalysisRegistryTest, AnalysisIsRegistered) { EXPECT_TRUE(AnalysisRegistry::contains(Analysis1Name)); EXPECT_TRUE(AnalysisRegistry::contains(Analysis2Name)); EXPECT_FALSE(AnalysisRegistry::contains(Analysis3Name)); EXPECT_TRUE(AnalysisRegistry::contains(Analysis4Name)); EXPECT_TRUE(AnalysisRegistry::contains(Analysis5Name)); EXPECT_TRUE(AnalysisRegistry::contains(CycleAName)); EXPECT_TRUE(AnalysisRegistry::contains(CycleBName)); } TEST(AnalysisRegistryTest, AnalysisCanBeInstantiated) { constexpr auto instantiate = AnalysisRegistry::instantiate; EXPECT_THAT_EXPECTED( instantiate(AnalysisName("AnalysisNonExisting")), llvm::FailedWithMessage( "no analysis registered for 'AnalysisName(AnalysisNonExisting)'")); EXPECT_THAT_EXPECTED(instantiate(Analysis1Name), llvm::Succeeded()); EXPECT_THAT_EXPECTED(instantiate(Analysis2Name), llvm::Succeeded()); // No Analysis3 - not registered, so instantiate() would fail. EXPECT_THAT_EXPECTED(instantiate(Analysis4Name), llvm::Succeeded()); EXPECT_THAT_EXPECTED(instantiate(Analysis5Name), llvm::Succeeded()); EXPECT_THAT_EXPECTED(instantiate(CycleAName), llvm::Succeeded()); EXPECT_THAT_EXPECTED(instantiate(CycleBName), llvm::Succeeded()); } // run() — processes the non-cyclic analyses in topological order. // CycleA and CycleB are excluded because they form a cycle; run() && would // error on them, so the type-safe subset overload is used here instead. TEST_F(AnalysisDriverTest, RunAll) { auto LU = makeLUSummary(); const auto E1 = addEntity(*LU, "Entity1"); const auto E2 = addEntity(*LU, "Entity2"); const auto E3 = addEntity(*LU, "Entity3"); const auto E4 = addEntity(*LU, "Entity4"); int s1a = insertSummary(*LU, "Analysis1", E1); int s1b = insertSummary(*LU, "Analysis1", E2); int s2a = insertSummary(*LU, "Analysis2", E2); int s2b = insertSummary(*LU, "Analysis2", E3); int s4a = insertSummary(*LU, "Analysis4", E4); // No registered analysis — Analysis3 data silently skipped. (void)insertSummary(*LU, "Analysis3", E1); AnalysisDriver Driver(std::move(LU)); auto WPAOrErr = Driver.run(); ASSERT_THAT_EXPECTED(WPAOrErr, llvm::Succeeded()); { auto R1OrErr = WPAOrErr->get(); ASSERT_THAT_EXPECTED(R1OrErr, llvm::Succeeded()); EXPECT_EQ(R1OrErr->Entries.size(), 2u); EXPECT_TRUE(hasEntry(R1OrErr->Entries, E1, s1a)); EXPECT_TRUE(hasEntry(R1OrErr->Entries, E2, s1b)); EXPECT_TRUE(R1OrErr->WasInitialized); EXPECT_TRUE(R1OrErr->WasFinalized); EXPECT_TRUE(Analysis1::WasDestroyed); } { auto R2OrErr = WPAOrErr->get(); ASSERT_THAT_EXPECTED(R2OrErr, llvm::Succeeded()); EXPECT_EQ(R2OrErr->Entries.size(), 2u); EXPECT_TRUE(hasEntry(R2OrErr->Entries, E2, s2a)); EXPECT_TRUE(hasEntry(R2OrErr->Entries, E3, s2b)); EXPECT_TRUE(R2OrErr->WasInitialized); EXPECT_TRUE(R2OrErr->WasFinalized); EXPECT_TRUE(Analysis2::WasDestroyed); } { auto R4OrErr = WPAOrErr->get(); ASSERT_THAT_EXPECTED(R4OrErr, llvm::Succeeded()); EXPECT_EQ(R4OrErr->Entries.size(), 1u); EXPECT_TRUE(hasEntry(R4OrErr->Entries, E4, s4a)); EXPECT_TRUE(R4OrErr->WasInitialized); EXPECT_TRUE(R4OrErr->WasFinalized); EXPECT_TRUE(Analysis4::WasDestroyed); } { auto R5OrErr = WPAOrErr->get(); ASSERT_THAT_EXPECTED(R5OrErr, llvm::Succeeded()); EXPECT_EQ( R5OrErr->CallSequence, (std::vector{"initialize", "step", "step", "finalize"})); EXPECT_EQ(R5OrErr->Analysis1Entries.size(), 2u); EXPECT_TRUE(hasEntry(R5OrErr->Analysis1Entries, E1, s1a)); EXPECT_TRUE(hasEntry(R5OrErr->Analysis1Entries, E2, s1b)); EXPECT_EQ(R5OrErr->Analysis2Entries.size(), 2u); EXPECT_TRUE(hasEntry(R5OrErr->Analysis2Entries, E2, s2a)); EXPECT_TRUE(hasEntry(R5OrErr->Analysis2Entries, E3, s2b)); EXPECT_EQ(R5OrErr->Analysis4Entries.size(), 1u); EXPECT_TRUE(hasEntry(R5OrErr->Analysis4Entries, E4, s4a)); EXPECT_TRUE(Analysis5::WasDestroyed); } // Unregistered analysis — not present in WPA. EXPECT_THAT_EXPECTED( WPAOrErr->get(), llvm::FailedWithMessage( "no result for 'AnalysisName(Analysis3)' in WPASuite")); } // run(names) — processes only the analyses for the given names. TEST_F(AnalysisDriverTest, RunByName) { auto LU = makeLUSummary(); const auto E1 = addEntity(*LU, "Entity1"); const auto E2 = addEntity(*LU, "Entity2"); int s1a = insertSummary(*LU, "Analysis1", E1); insertSummary(*LU, "Analysis2", E2); AnalysisDriver Driver(std::move(LU)); auto WPAOrErr = Driver.run({AnalysisName("Analysis1")}); ASSERT_THAT_EXPECTED(WPAOrErr, llvm::Succeeded()); // Analysis1 was requested and has data — present. auto R1OrErr = WPAOrErr->get(); ASSERT_THAT_EXPECTED(R1OrErr, llvm::Succeeded()); EXPECT_EQ(R1OrErr->Entries.size(), 1u); EXPECT_TRUE(hasEntry(R1OrErr->Entries, E1, s1a)); EXPECT_TRUE(R1OrErr->WasInitialized); EXPECT_TRUE(R1OrErr->WasFinalized); // Analysis2 was not requested — not present even though data exists. EXPECT_THAT_EXPECTED( WPAOrErr->get(), llvm::FailedWithMessage( "no result for 'AnalysisName(Analysis2)' in WPASuite")); } // run(names) — error when a requested name has no data in LUSummary. TEST_F(AnalysisDriverTest, RunByNameErrorMissingData) { auto LU = makeLUSummary(); AnalysisDriver Driver(std::move(LU)); EXPECT_THAT_EXPECTED( Driver.run({AnalysisName("Analysis1")}), llvm::FailedWithMessage( "no data for analysis 'AnalysisName(Analysis1)' in LUSummary")); } // run(names) — error when a requested name has no registered analysis. TEST_F(AnalysisDriverTest, RunByNameErrorMissingAnalysis) { auto LU = makeLUSummary(); const auto E1 = addEntity(*LU, "Entity1"); insertSummary(*LU, "Analysis3", E1); AnalysisDriver Driver(std::move(LU)); // Analysis3 has data but no registered analysis. EXPECT_THAT_EXPECTED( Driver.run({AnalysisName("Analysis3")}), llvm::FailedWithMessage( "no analysis registered for 'AnalysisName(Analysis3)'")); } // run() — type-safe subset. TEST_F(AnalysisDriverTest, RunByType) { auto LU = makeLUSummary(); const auto E1 = addEntity(*LU, "Entity1"); const auto E2 = addEntity(*LU, "Entity2"); int s1a = insertSummary(*LU, "Analysis1", E1); insertSummary(*LU, "Analysis2", E2); AnalysisDriver Driver(std::move(LU)); auto WPAOrErr = Driver.run(); ASSERT_THAT_EXPECTED(WPAOrErr, llvm::Succeeded()); // Analysis1 was requested — present. auto R1OrErr = WPAOrErr->get(); ASSERT_THAT_EXPECTED(R1OrErr, llvm::Succeeded()); EXPECT_EQ(R1OrErr->Entries.size(), 1u); EXPECT_TRUE(hasEntry(R1OrErr->Entries, E1, s1a)); EXPECT_TRUE(R1OrErr->WasInitialized); EXPECT_TRUE(R1OrErr->WasFinalized); // Analysis2 was not requested — not present even though data exists. EXPECT_THAT_EXPECTED( WPAOrErr->get(), llvm::FailedWithMessage( "no result for 'AnalysisName(Analysis2)' in WPASuite")); } // run() — error when a requested type has no data in LUSummary. TEST_F(AnalysisDriverTest, RunByTypeErrorMissingData) { auto LU = makeLUSummary(); AnalysisDriver Driver(std::move(LU)); EXPECT_THAT_EXPECTED( Driver.run(), llvm::FailedWithMessage( "no data for analysis 'AnalysisName(Analysis1)' in LUSummary")); } // contains() — present entries return true; absent entries return false. TEST_F(AnalysisDriverTest, Contains) { auto LU = makeLUSummary(); const auto E1 = addEntity(*LU, "Entity1"); insertSummary(*LU, "Analysis1", E1); insertSummary(*LU, "Analysis2", E1); insertSummary(*LU, "Analysis4", E1); AnalysisDriver Driver(std::move(LU)); auto WPAOrErr = Driver.run(); ASSERT_THAT_EXPECTED(WPAOrErr, llvm::Succeeded()); EXPECT_TRUE(WPAOrErr->contains()); // Analysis3 has no registered analysis — never present in WPA. EXPECT_FALSE(WPAOrErr->contains()); } // run() && — errors when the registry contains a dependency cycle. TEST_F(AnalysisDriverTest, CycleDetected) { auto LU = makeLUSummary(); AnalysisDriver Driver(std::move(LU)); EXPECT_THAT_EXPECTED( std::move(Driver).run(), llvm::FailedWithMessage("cycle detected: AnalysisName(CycleA) -> " "AnalysisName(CycleB) -> AnalysisName(CycleA)")); } } // namespace