blob: e8290a7c3cd1fbcd98613c4a9a30556e43570772 [file]
//===- 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 <memory>
#include <string>
#include <utility>
#include <vector>
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<std::pair<EntityId, int>> Entries;
bool WasInitialized = false;
bool WasFinalized = false;
};
class Analysis2Result final : public AnalysisResult {
public:
static AnalysisName analysisName() { return Analysis2Name; }
std::vector<std::pair<EntityId, int>> 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<std::pair<EntityId, int>> 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<std::string> CallSequence;
std::vector<std::pair<EntityId, int>> Analysis1Entries;
std::vector<std::pair<EntityId, int>> Analysis2Entries;
std::vector<std::pair<EntityId, int>> 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<Analysis1Result, Analysis1EntitySummary> {
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();
}
};
// These static registrations are safe without SSAFBuiltinTestForceLinker.h
// because this translation unit is compiled directly into the test binary -
// the linker cannot dead-strip it, so all static initializers are guaranteed
// to run.
static AnalysisRegistry::Add<Analysis1> RegAnalysis1("Analysis for Analysis1");
class Analysis2 final
: public SummaryAnalysis<Analysis2Result, Analysis2EntitySummary> {
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<Analysis2> RegAnalysis2("Analysis for Analysis2");
// No Analysis3 or registration for Analysis3.
class Analysis4 final
: public SummaryAnalysis<Analysis4Result, Analysis4EntitySummary> {
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<Analysis4> RegAnalysis4("Analysis for Analysis4");
class Analysis5 final
: public DerivedAnalysis<Analysis5Result, Analysis1Result, Analysis2Result,
Analysis4Result> {
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<bool> 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<Analysis5> RegAnalysis5("Analysis for Analysis5");
class CycleA final : public DerivedAnalysis<CycleAResult, CycleBResult> {
public:
llvm::Error initialize(const CycleBResult &) override {
return llvm::Error::success();
}
llvm::Expected<bool> step() override { return false; }
};
static AnalysisRegistry::Add<CycleA> RegCycleA("Cyclic analysis A (test only)");
class CycleB final : public DerivedAnalysis<CycleBResult, CycleAResult> {
public:
llvm::Error initialize(const CycleAResult &) override {
return llvm::Error::success();
}
llvm::Expected<bool> step() override { return false; }
};
static AnalysisRegistry::Add<CycleB> 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<LUSummary> makeLUSummary() {
NestedBuildNamespace NS(
{BuildNamespace(BuildNamespaceKind::LinkUnit, "TestLU")});
return std::make_unique<LUSummary>(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<std::pair<EntityId, int>> &Entries,
EntityId Id, int InstanceId) {
return llvm::is_contained(Entries, std::make_pair(Id, InstanceId));
}
template <typename SummaryT>
int insertSummary(LUSummary &LU, llvm::StringRef SN, EntityId Id) {
auto S = std::make_unique<SummaryT>();
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<T...>() — 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<Analysis1EntitySummary>(*LU, "Analysis1", E1);
int s1b = insertSummary<Analysis1EntitySummary>(*LU, "Analysis1", E2);
int s2a = insertSummary<Analysis2EntitySummary>(*LU, "Analysis2", E2);
int s2b = insertSummary<Analysis2EntitySummary>(*LU, "Analysis2", E3);
int s4a = insertSummary<Analysis4EntitySummary>(*LU, "Analysis4", E4);
// No registered analysis — Analysis3 data silently skipped.
(void)insertSummary<Analysis3EntitySummary>(*LU, "Analysis3", E1);
AnalysisDriver Driver(std::move(LU));
auto WPAOrErr = Driver.run<Analysis1Result, Analysis2Result, Analysis4Result,
Analysis5Result>();
ASSERT_THAT_EXPECTED(WPAOrErr, llvm::Succeeded());
{
auto R1OrErr = WPAOrErr->get<Analysis1Result>();
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<Analysis2Result>();
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<Analysis4Result>();
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<Analysis5Result>();
ASSERT_THAT_EXPECTED(R5OrErr, llvm::Succeeded());
EXPECT_EQ(
R5OrErr->CallSequence,
(std::vector<std::string>{"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<Analysis3Result>(),
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<Analysis1EntitySummary>(*LU, "Analysis1", E1);
insertSummary<Analysis2EntitySummary>(*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<Analysis1Result>();
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<Analysis2Result>(),
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<Analysis3EntitySummary>(*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<ResultTs...>() — 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<Analysis1EntitySummary>(*LU, "Analysis1", E1);
insertSummary<Analysis2EntitySummary>(*LU, "Analysis2", E2);
AnalysisDriver Driver(std::move(LU));
auto WPAOrErr = Driver.run<Analysis1Result>();
ASSERT_THAT_EXPECTED(WPAOrErr, llvm::Succeeded());
// Analysis1 was requested — present.
auto R1OrErr = WPAOrErr->get<Analysis1Result>();
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<Analysis2Result>(),
llvm::FailedWithMessage(
"no result for 'AnalysisName(Analysis2)' in WPASuite"));
}
// run<ResultTs...>() — 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<Analysis1Result>(),
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<Analysis1EntitySummary>(*LU, "Analysis1", E1);
insertSummary<Analysis2EntitySummary>(*LU, "Analysis2", E1);
insertSummary<Analysis4EntitySummary>(*LU, "Analysis4", E1);
AnalysisDriver Driver(std::move(LU));
auto WPAOrErr = Driver.run<Analysis1Result, Analysis2Result, Analysis4Result,
Analysis5Result>();
ASSERT_THAT_EXPECTED(WPAOrErr, llvm::Succeeded());
EXPECT_TRUE(WPAOrErr->contains<Analysis1Result>());
// Analysis3 has no registered analysis — never present in WPA.
EXPECT_FALSE(WPAOrErr->contains<Analysis3Result>());
}
// 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