//===- 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 #include #include #include #include #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 using Alias = absl::StatusOr; kFullyAliased = 2, // using Alias = absl::StatusOr; }; // 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 to ensure // the model is insensitive to whether the StatusOr<> is aliased or not. std::string GetAliasMacros(UncheckedStatusOrAccessModelTestAliasKind AliasKind); std::vector> 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 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 void ExpectDiagnosticsFor( std::string SourceCode, FuncDeclMatcher FuncMatcher, UncheckedStatusOrAccessModelTestAliasKind AliasKind) const { std::vector> Headers = GetHeaders(AliasKind); UncheckedStatusOrAccessModelOptions Options{}; std::vector Diagnostics; llvm::Error Error = test::checkDataflow( test::AnalysisInputs( 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 &Annotations, const test::AnalysisOutputs &AO) { llvm::DenseSet AnnotationLines; for (const auto &[Line, _] : Annotations) AnnotationLines.insert(Line); auto &SrcMgr = AO.ASTCtx.getSourceManager(); llvm::DenseSet 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> { 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_