blob: 18d9e1735061d0d238c0cc40a5be6f3d41561b9e [file]
//===- TUSummaryExtractorFrontendActionTest.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/Frontend/TUSummaryExtractorFrontendAction.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/ASTContext.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendOptions.h"
#include "clang/Frontend/TextDiagnosticBuffer.h"
#include "clang/Lex/PreprocessorOptions.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Serialization/SerializationFormat.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Serialization/SerializationFormatRegistry.h"
#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/ExtractorRegistry.h"
#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/TUSummaryExtractor.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/VirtualFileSystem.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <memory>
#include <string>
#include <vector>
using namespace clang;
using namespace ssaf;
using ::testing::Contains;
using ::testing::UnorderedElementsAre;
static auto errorsMsgsOf(const TextDiagnosticBuffer &Diags) {
auto Errors = llvm::make_range(Diags.err_begin(), Diags.err_end());
return llvm::make_second_range(Errors);
}
namespace {
/// A no-op TUSummaryExtractor suitable for use with a real TUSummaryBuilder.
class NoOpExtractor : public TUSummaryExtractor {
public:
using TUSummaryExtractor::TUSummaryExtractor;
void HandleTranslationUnit(ASTContext &Ctx) override {}
};
} // namespace
static TUSummaryExtractorRegistry::Add<NoOpExtractor>
RegisterNoOp("NoOpExtractor", "No-op extractor for frontend action tests");
namespace {
class FailingSerializationFormat final : public SerializationFormat {
public:
static llvm::Error failing(llvm::StringRef Component) {
return llvm::createStringError(
"error from always failing serialization format: " + Component);
}
llvm::Expected<TUSummary> readTUSummary(llvm::StringRef Path) override {
return failing("readTUSummary");
}
llvm::Error writeTUSummary(const TUSummary &Summary,
llvm::StringRef Path) override {
return failing("writeTUSummary");
}
llvm::Expected<TUSummaryEncoding>
readTUSummaryEncoding(llvm::StringRef Path) override {
return failing("readTUSummaryEncoding");
}
llvm::Error writeTUSummaryEncoding(const TUSummaryEncoding &SummaryEncoding,
llvm::StringRef Path) override {
return failing("writeTUSummaryEncoding");
}
llvm::Expected<LUSummary> readLUSummary(llvm::StringRef Path) override {
return failing("readLUSummary");
}
llvm::Error writeLUSummary(const LUSummary &Summary,
llvm::StringRef Path) override {
return failing("writeLUSummary");
}
llvm::Expected<LUSummaryEncoding>
readLUSummaryEncoding(llvm::StringRef Path) override {
return failing("readLUSummaryEncoding");
}
llvm::Error writeLUSummaryEncoding(const LUSummaryEncoding &SummaryEncoding,
llvm::StringRef Path) override {
return failing("writeLUSummaryEncoding");
}
llvm::Expected<WPASuite> readWPASuite(llvm::StringRef Path) override {
return failing("readWPASuite");
}
llvm::Error writeWPASuite(const WPASuite &Suite,
llvm::StringRef Path) override {
return failing("writeWPASuite");
}
llvm::Expected<Artifact> readArtifact(llvm::StringRef Path) override {
return failing("readArtifact");
}
llvm::Error writeArtifact(const Artifact &A, llvm::StringRef Path) override {
return failing("writeArtifact");
}
llvm::Expected<ArtifactEncoding>
readArtifactEncoding(llvm::StringRef Path) override {
return failing("readArtifactEncoding");
}
llvm::Error writeArtifactEncoding(const ArtifactEncoding &E,
llvm::StringRef Path) override {
return failing("writeArtifactEncoding");
}
void forEachRegisteredAnalysis(
llvm::function_ref<void(llvm::StringRef Name, llvm::StringRef Desc)>
Callback) const override {}
};
} // namespace
static SerializationFormatRegistry::Add<FailingSerializationFormat>
RegisterFormat(
"FailingSerializationFormat",
"A serialization format that fails on every possible operation.");
namespace {
/// Records the \c CompilationUnit namespace name of the most recently written
/// \c TUSummary into a static, so tests can assert that the runner threads
/// the configured CU name verbatim into the produced summary.
class CapturingSerializationFormat final : public SerializationFormat {
public:
static std::string &lastCapturedName() {
static std::string Name;
return Name;
}
static void resetCapturedName() { lastCapturedName().clear(); }
llvm::Expected<TUSummary> readTUSummary(llvm::StringRef) override {
return llvm::createStringError("not implemented");
}
llvm::Error writeTUSummary(const TUSummary &Summary,
llvm::StringRef) override {
lastCapturedName() = getName(getTUNamespace(Summary));
return llvm::Error::success();
}
llvm::Expected<TUSummaryEncoding>
readTUSummaryEncoding(llvm::StringRef) override {
return llvm::createStringError("not implemented");
}
llvm::Error writeTUSummaryEncoding(const TUSummaryEncoding &,
llvm::StringRef) override {
return llvm::Error::success();
}
llvm::Expected<LUSummary> readLUSummary(llvm::StringRef) override {
return llvm::createStringError("not implemented");
}
llvm::Error writeLUSummary(const LUSummary &, llvm::StringRef) override {
return llvm::Error::success();
}
llvm::Expected<LUSummaryEncoding>
readLUSummaryEncoding(llvm::StringRef) override {
return llvm::createStringError("not implemented");
}
llvm::Error writeLUSummaryEncoding(const LUSummaryEncoding &,
llvm::StringRef) override {
return llvm::Error::success();
}
llvm::Expected<WPASuite> readWPASuite(llvm::StringRef) override {
return llvm::createStringError("not implemented");
}
llvm::Error writeWPASuite(const WPASuite &, llvm::StringRef) override {
return llvm::Error::success();
}
llvm::Expected<Artifact> readArtifact(llvm::StringRef) override {
return llvm::createStringError("not implemented");
}
llvm::Error writeArtifact(const Artifact &, llvm::StringRef) override {
return llvm::Error::success();
}
llvm::Expected<ArtifactEncoding>
readArtifactEncoding(llvm::StringRef) override {
return llvm::createStringError("not implemented");
}
llvm::Error writeArtifactEncoding(const ArtifactEncoding &,
llvm::StringRef) override {
return llvm::Error::success();
}
void forEachRegisteredAnalysis(
llvm::function_ref<void(llvm::StringRef, llvm::StringRef)>)
const override {}
};
} // namespace
static SerializationFormatRegistry::Add<CapturingSerializationFormat>
RegisterCapturingFormat(
"CapturingSerializationFormat",
"A serialization format that captures the CU namespace name.");
using EventLog = std::vector<std::string>;
namespace {
/// An ASTConsumer that logs callback invocations into a shared log.
class RecordingASTConsumer : public ASTConsumer {
public:
RecordingASTConsumer(EventLog &Log, std::string Tag)
: Log(Log), Tag(std::move(Tag)) {}
void Initialize(ASTContext &Ctx) override {
Log.push_back(Tag + "::Initialize");
}
bool HandleTopLevelDecl(DeclGroupRef D) override {
Log.push_back(Tag + "::HandleTopLevelDecl");
return true;
}
void HandleTranslationUnit(ASTContext &Ctx) override {
Log.push_back(Tag + "::HandleTranslationUnit");
}
private:
EventLog &Log;
std::string Tag;
};
/// A FrontendAction that returns a RecordingASTConsumer with the tag "Wrapped".
class RecordingAction : public ASTFrontendAction {
public:
EventLog &getLog() { return Log; }
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &,
StringRef) override {
return std::make_unique<RecordingASTConsumer>(Log, /*Tag=*/"Wrapped");
}
private:
EventLog Log;
};
class FailingAction : public ASTFrontendAction {
public:
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &,
StringRef) override {
return nullptr;
}
};
/// Creates a CompilerInstance configured with an in-memory "test.cc" file
/// containing "int x = 42;".
static std::unique_ptr<CompilerInstance>
makeCompiler(TextDiagnosticBuffer &DiagBuf) {
auto Invocation = std::make_shared<CompilerInvocation>();
Invocation->getPreprocessorOpts().addRemappedFile(
"test.cc", llvm::MemoryBuffer::getMemBuffer("int x = 42;").release());
Invocation->getFrontendOpts().Inputs.push_back(
FrontendInputFile("test.cc", Language::CXX));
Invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly;
Invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu";
auto Compiler = std::make_unique<CompilerInstance>(std::move(Invocation));
Compiler->setVirtualFileSystem(llvm::vfs::getRealFileSystem());
Compiler->createDiagnostics(&DiagBuf, /*ShouldOwnClient=*/false);
return Compiler;
}
struct TUSummaryExtractorFrontendActionTest : testing::Test {
using PathString = llvm::SmallString<128>;
PathString TestDir;
TextDiagnosticBuffer DiagBuf;
std::unique_ptr<CompilerInstance> Compiler = makeCompiler(DiagBuf);
void SetUp() override {
std::error_code EC = llvm::sys::fs::createUniqueDirectory(
"ssaf-frontend-action-test", TestDir);
ASSERT_FALSE(EC) << "Failed to create temp directory: " << EC.message();
}
void TearDown() override { llvm::sys::fs::remove_directories(TestDir); }
std::string makePath(llvm::StringRef FileOrDirectoryName) const {
PathString FullPath = TestDir;
llvm::sys::path::append(FullPath, FileOrDirectoryName);
return FullPath.str().str();
}
};
TEST_F(TUSummaryExtractorFrontendActionTest,
WrappedActionFailsToCreateConsumer) {
// Configure valid SSAF options so the failure is purely from the wrapped
// action, not from runner creation.
std::string Output = makePath("output.MockSerializationFormat");
Compiler->getFrontendOpts().SSAFTUSummaryFile = Output;
Compiler->getFrontendOpts().SSAFExtractSummaries = {"NoOpExtractor"};
Compiler->getFrontendOpts().SSAFCompilationUnitId = "test-cu";
TUSummaryExtractorFrontendAction ExtractorAction(
std::make_unique<FailingAction>());
Compiler->ExecuteAction(ExtractorAction);
// If the wrapped action fails, the ExtractorAction should not output.
EXPECT_FALSE(llvm::sys::fs::exists(Output));
}
TEST_F(TUSummaryExtractorFrontendActionTest,
RunnerFailsWithInvalidFormat_WrappedConsumerStillRuns) {
// Use an unregistered format extension so TUSummaryRunner::create fails.
std::string Output = makePath("output.xyz");
Compiler->getFrontendOpts().SSAFTUSummaryFile = Output;
Compiler->getFrontendOpts().SSAFExtractSummaries = {"NoOpExtractor"};
Compiler->getFrontendOpts().SSAFCompilationUnitId = "test-cu";
auto Wrapped = std::make_unique<RecordingAction>();
const EventLog &Log = Wrapped->getLog();
TUSummaryExtractorFrontendAction ExtractorAction(std::move(Wrapped));
// The runner fails, so ExecuteAction should return false due to the fatal
// diagnostic.
EXPECT_FALSE(Compiler->ExecuteAction(ExtractorAction));
// The wrapped consumer should still have run.
EXPECT_THAT(Log, Contains("Wrapped::Initialize"));
EXPECT_THAT(Log, Contains("Wrapped::HandleTranslationUnit"));
// Exactly one error about the unknown format.
EXPECT_THAT(errorsMsgsOf(DiagBuf),
UnorderedElementsAre(
"unknown output summary file format 'xyz' specified by "
"'--ssaf-tu-summary-file=" +
Output + "'"));
// No output should have been created due to the failure.
EXPECT_FALSE(llvm::sys::fs::exists(Output));
}
TEST_F(TUSummaryExtractorFrontendActionTest,
RunnerFailsWithUnknownExtractor_WrappedConsumerStillRuns) {
std::string Output = makePath("output.MockSerializationFormat");
Compiler->getFrontendOpts().SSAFTUSummaryFile = Output;
Compiler->getFrontendOpts().SSAFExtractSummaries = {"NonExistentExtractor"};
Compiler->getFrontendOpts().SSAFCompilationUnitId = "test-cu";
auto Wrapped = std::make_unique<RecordingAction>();
const EventLog &Log = Wrapped->getLog();
TUSummaryExtractorFrontendAction ExtractorAction(std::move(Wrapped));
EXPECT_FALSE(Compiler->ExecuteAction(ExtractorAction));
// The wrapped consumer should still have run.
EXPECT_THAT(Log, Contains("Wrapped::Initialize"));
EXPECT_THAT(Log, Contains("Wrapped::HandleTranslationUnit"));
// Exactly one error about the unknown extractor.
EXPECT_THAT(errorsMsgsOf(DiagBuf),
UnorderedElementsAre("no summary extractor was registered with "
"name: NonExistentExtractor"));
// No output should have been created due to the failure.
EXPECT_FALSE(llvm::sys::fs::exists(Output));
}
TEST_F(TUSummaryExtractorFrontendActionTest,
RunnerSucceeds_ASTConsumerCallbacksPropagate) {
std::string Output = makePath("output.MockSerializationFormat");
Compiler->getFrontendOpts().SSAFTUSummaryFile = Output;
Compiler->getFrontendOpts().SSAFExtractSummaries = {"NoOpExtractor"};
Compiler->getFrontendOpts().SSAFCompilationUnitId = "test-cu";
auto Wrapped = std::make_unique<RecordingAction>();
const EventLog &Log = Wrapped->getLog();
TUSummaryExtractorFrontendAction ExtractorAction(std::move(Wrapped));
EXPECT_TRUE(Compiler->ExecuteAction(ExtractorAction));
// All wrapped ASTConsumer callbacks should have fired, not just
// HandleTranslationUnit.
EXPECT_THAT(Log, Contains("Wrapped::Initialize"));
EXPECT_THAT(Log, Contains("Wrapped::HandleTopLevelDecl"));
EXPECT_THAT(Log, Contains("Wrapped::HandleTranslationUnit"));
EXPECT_EQ(DiagBuf.getNumErrors(), 0U);
// The runner should have written output.
EXPECT_TRUE(llvm::sys::fs::exists(Output));
}
// Use a custom action that checks whether the output path exists during
// HandleTranslationUnit — it should not, because the wrapped consumer runs
// before the runner.
struct OrderCheckingAction : public ASTFrontendAction {
EventLog Log;
std::string OutputPath;
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
StringRef InFile) override {
struct Consumer : public ASTConsumer {
Consumer(EventLog &Log, std::string OutputPath)
: Log(Log), OutputPath(std::move(OutputPath)) {}
void Initialize(ASTContext &) override {
Log.push_back("Wrapped::Initialize");
}
bool HandleTopLevelDecl(DeclGroupRef) override {
Log.push_back("Wrapped::HandleTopLevelDecl");
return true;
}
void HandleTranslationUnit(ASTContext &) override {
bool Exists = llvm::sys::fs::exists(OutputPath);
Log.push_back(std::string("OutputExistsDuringWrappedHTU=") +
(Exists ? "true" : "false"));
Log.push_back("Wrapped::HandleTranslationUnit");
}
EventLog &Log;
std::string OutputPath;
};
return std::make_unique<Consumer>(Log, OutputPath);
}
};
TEST_F(TUSummaryExtractorFrontendActionTest,
RunnerSucceeds_WrappedRunsBeforeRunner) {
std::string Output = makePath("output.MockSerializationFormat");
Compiler->getFrontendOpts().SSAFTUSummaryFile = Output;
Compiler->getFrontendOpts().SSAFExtractSummaries = {"NoOpExtractor"};
Compiler->getFrontendOpts().SSAFCompilationUnitId = "test-cu";
auto Wrapped = std::make_unique<OrderCheckingAction>();
Wrapped->OutputPath = Output;
const EventLog &Log = Wrapped->Log;
TUSummaryExtractorFrontendAction Action(std::move(Wrapped));
EXPECT_TRUE(Compiler->ExecuteAction(Action));
EXPECT_EQ(DiagBuf.getNumErrors(), 0U);
// The output should NOT have existed when the wrapped consumer's
// HandleTranslationUnit ran (wrapped is at index 0, runner at index 1).
EXPECT_THAT(Log, Contains("OutputExistsDuringWrappedHTU=false"));
// After ExecuteAction, the output should exist.
EXPECT_TRUE(llvm::sys::fs::exists(Output));
}
TEST_F(TUSummaryExtractorFrontendActionTest, RunnerFailsToWrite) {
std::string Output = makePath("output.FailingSerializationFormat");
Compiler->getFrontendOpts().SSAFTUSummaryFile = Output;
Compiler->getFrontendOpts().SSAFExtractSummaries = {"NoOpExtractor"};
Compiler->getFrontendOpts().SSAFCompilationUnitId = "test-cu";
TUSummaryExtractorFrontendAction Action(std::make_unique<RecordingAction>());
// This should fail because the summary writing fails and emits an error
// diagnostic.
EXPECT_FALSE(Compiler->ExecuteAction(Action));
EXPECT_THAT(
errorsMsgsOf(DiagBuf),
UnorderedElementsAre(
"failed to write TU summary to '" + Output +
"': error from always failing serialization format: writeTUSummary"));
// No output should have been created due to the failure.
EXPECT_FALSE(llvm::sys::fs::exists(Output));
}
TEST_F(TUSummaryExtractorFrontendActionTest,
MissingCompilationUnitIdDiagnoses) {
std::string Output = makePath("output.MockSerializationFormat");
Compiler->getFrontendOpts().SSAFTUSummaryFile = Output;
Compiler->getFrontendOpts().SSAFExtractSummaries = {"NoOpExtractor"};
// SSAFCompilationUnitId left empty.
auto Wrapped = std::make_unique<RecordingAction>();
const EventLog &Log = Wrapped->getLog();
TUSummaryExtractorFrontendAction ExtractorAction(std::move(Wrapped));
EXPECT_FALSE(Compiler->ExecuteAction(ExtractorAction));
EXPECT_THAT(Log, Contains("Wrapped::Initialize"));
EXPECT_THAT(Log, Contains("Wrapped::HandleTranslationUnit"));
EXPECT_THAT(
errorsMsgsOf(DiagBuf),
UnorderedElementsAre("option '--ssaf-tu-summary-file=' requires "
"'--ssaf-compilation-unit-id=' to be set"));
EXPECT_FALSE(llvm::sys::fs::exists(Output));
}
TEST_F(TUSummaryExtractorFrontendActionTest,
EmptyCompilationUnitIdDiagnoses) {
std::string Output = makePath("output.MockSerializationFormat");
Compiler->getFrontendOpts().SSAFTUSummaryFile = Output;
Compiler->getFrontendOpts().SSAFExtractSummaries = {"NoOpExtractor"};
Compiler->getFrontendOpts().SSAFCompilationUnitId = "";
auto Wrapped = std::make_unique<RecordingAction>();
const EventLog &Log = Wrapped->getLog();
TUSummaryExtractorFrontendAction ExtractorAction(std::move(Wrapped));
EXPECT_FALSE(Compiler->ExecuteAction(ExtractorAction));
EXPECT_THAT(Log, Contains("Wrapped::Initialize"));
EXPECT_THAT(Log, Contains("Wrapped::HandleTranslationUnit"));
EXPECT_THAT(
errorsMsgsOf(DiagBuf),
UnorderedElementsAre("option '--ssaf-tu-summary-file=' requires "
"'--ssaf-compilation-unit-id=' to be set"));
EXPECT_FALSE(llvm::sys::fs::exists(Output));
}
TEST_F(TUSummaryExtractorFrontendActionTest,
CompilationUnitIdThreadsToSummary) {
CapturingSerializationFormat::resetCapturedName();
const std::string CUId = "cu-X-test";
std::string Output = makePath("output.CapturingSerializationFormat");
Compiler->getFrontendOpts().SSAFTUSummaryFile = Output;
Compiler->getFrontendOpts().SSAFExtractSummaries = {"NoOpExtractor"};
Compiler->getFrontendOpts().SSAFCompilationUnitId = CUId;
TUSummaryExtractorFrontendAction Action(std::make_unique<RecordingAction>());
EXPECT_TRUE(Compiler->ExecuteAction(Action));
EXPECT_EQ(DiagBuf.getNumErrors(), 0U);
EXPECT_EQ(CapturingSerializationFormat::lastCapturedName(), CUId);
}
} // namespace