blob: 92dcd9328c322ee0672b892e5d92c3d7d1bf5c5d [file] [log] [blame] [edit]
//===- UncheckedStatusOrAccessModelTestFixture.h --------------------------===//
//
// 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_ANALYSIS_FLOW_SENSITIVE_UNCHECKEDSTATUSORACCESSMODELTESTFIXTURE_H_
#define LLVM_CLANG_ANALYSIS_FLOW_SENSITIVE_UNCHECKEDSTATUSORACCESSMODELTESTFIXTURE_H_
#include <algorithm>
#include <iterator>
#include <string>
#include <utility>
#include <vector>
#include "TestingSupport.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Analysis/CFG.h"
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
#include "clang/Analysis/FlowSensitive/MatchSwitch.h"
#include "clang/Analysis/FlowSensitive/Models/UncheckedStatusOrAccessModel.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Error.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
namespace clang::dataflow::statusor_model {
enum class UncheckedStatusOrAccessModelTestAliasKind {
kUnaliased = 0, // no alias
kPartiallyAliased = 1, // template<typename T> using Alias = absl::StatusOr;
kFullyAliased = 2, // using Alias = absl::StatusOr<int>;
};
// Base class for the test executors. This is needed to abstract away the
// template parameter from the UncheckedStatusOrAccessModelTestExecutor. This
// allows us to use UncheckedStatusOrAccessModelTestExecutorBase* in the
// UncheckedStatusOrAccessModelTest.
class UncheckedStatusOrAccessModelTestExecutorBase {
public:
virtual void
ExpectDiagnosticsFor(std::string SourceCode,
UncheckedStatusOrAccessModelTestAliasKind) const = 0;
virtual void ExpectDiagnosticsForLambda(
std::string SourceCode,
UncheckedStatusOrAccessModelTestAliasKind) const = 0;
virtual ~UncheckedStatusOrAccessModelTestExecutorBase() = default;
};
// Returns these macros according to the alias kind:
// - STATUS
// - STATUSOR_INT
// - STATUSOR_BOOL
// - STATUSOR_VOIDPTR
// Tests should use these macros instead of e.g. absl::StatusOr<int> to ensure
// the model is insensitive to whether the StatusOr<> is aliased or not.
std::string GetAliasMacros(UncheckedStatusOrAccessModelTestAliasKind AliasKind);
std::vector<std::pair<std::string, std::string>>
GetHeaders(UncheckedStatusOrAccessModelTestAliasKind AliasKind);
// This allows us to run the same test suite for multiple models. This allows
// vendors to model internal APIs in an extension of the base model, and make
// sure that these tests still pass.
template <typename Model>
class UncheckedStatusOrAccessModelTestExecutor
: public UncheckedStatusOrAccessModelTestExecutorBase {
public:
void ExpectDiagnosticsFor(
std::string SourceCode,
UncheckedStatusOrAccessModelTestAliasKind AliasKind) const override {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
ExpectDiagnosticsFor(SourceCode, hasName("target"), AliasKind);
}
void ExpectDiagnosticsForLambda(
std::string SourceCode,
UncheckedStatusOrAccessModelTestAliasKind AliasKind) const override {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
ExpectDiagnosticsFor(SourceCode,
allOf(hasOverloadedOperatorName("()"),
hasDeclContext(cxxRecordDecl(isLambda()))),
AliasKind);
}
template <typename FuncDeclMatcher>
void ExpectDiagnosticsFor(
std::string SourceCode, FuncDeclMatcher FuncMatcher,
UncheckedStatusOrAccessModelTestAliasKind AliasKind) const {
std::vector<std::pair<std::string, std::string>> Headers =
GetHeaders(AliasKind);
UncheckedStatusOrAccessModelOptions Options{};
std::vector<SourceLocation> Diagnostics;
llvm::Error Error = test::checkDataflow<Model>(
test::AnalysisInputs<Model>(
SourceCode, std::move(FuncMatcher),
[](ASTContext &Ctx, Environment &Env) { return Model(Ctx, Env); })
.withPostVisitCFG(
[&Diagnostics,
Diagnoser = UncheckedStatusOrAccessDiagnoser(Options)](
ASTContext &Ctx, const CFGElement &Elt,
const TransferStateForDiagnostics<
UncheckedStatusOrAccessModel::Lattice> &State) mutable {
auto EltDiagnostics = Diagnoser(Elt, Ctx, State);
llvm::move(EltDiagnostics, std::back_inserter(Diagnostics));
})
.withASTBuildArgs(
{"-fsyntax-only", "-std=c++17", "-Wno-undefined-inline"})
.withASTBuildVirtualMappedFiles(
tooling::FileContentMappings(Headers.begin(), Headers.end())),
/*VerifyResults=*/[&Diagnostics, SourceCode](
const llvm::DenseMap<unsigned, std::string>
&Annotations,
const test::AnalysisOutputs &AO) {
llvm::DenseSet<unsigned> AnnotationLines;
for (const auto &[Line, _] : Annotations)
AnnotationLines.insert(Line);
auto &SrcMgr = AO.ASTCtx.getSourceManager();
llvm::DenseSet<unsigned> DiagnosticLines;
for (SourceLocation &Loc : Diagnostics)
DiagnosticLines.insert(SrcMgr.getPresumedLineNumber(Loc));
EXPECT_THAT(DiagnosticLines, testing::ContainerEq(AnnotationLines))
<< "\nFailing code:\n"
<< SourceCode;
});
if (Error)
FAIL() << llvm::toString(std::move(Error));
}
~UncheckedStatusOrAccessModelTestExecutor() override = default;
};
class UncheckedStatusOrAccessModelTest
: public ::testing::TestWithParam<
std::pair<UncheckedStatusOrAccessModelTestExecutorBase *,
UncheckedStatusOrAccessModelTestAliasKind>> {
protected:
void ExpectDiagnosticsFor(std::string SourceCode) {
GetParam().first->ExpectDiagnosticsFor(SourceCode, GetParam().second);
}
void ExpectDiagnosticsForLambda(std::string SourceCode) {
GetParam().first->ExpectDiagnosticsForLambda(SourceCode, GetParam().second);
}
};
} // namespace clang::dataflow::statusor_model
#endif // LLVM_CLANG_ANALYSIS_FLOW_SENSITIVE_UNCHECKEDSTATUSORACCESSMODELTESTFIXTURE_H_