xref: /llvm-project/clang/unittests/Analysis/FlowSensitive/ChromiumCheckModelTest.cpp (revision 526c9b7e37fa12abc17eebc68f21c1d213477ba8)
1 //===- ChromiumCheckModelTest.cpp -----------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 // FIXME: Move this to clang/unittests/Analysis/FlowSensitive/Models.
9 
10 #include "clang/Analysis/FlowSensitive/Models/ChromiumCheckModel.h"
11 #include "TestingSupport.h"
12 #include "clang/AST/ASTContext.h"
13 #include "clang/ASTMatchers/ASTMatchers.h"
14 #include "clang/Analysis/CFG.h"
15 #include "clang/Analysis/FlowSensitive/NoopLattice.h"
16 #include "clang/Tooling/Tooling.h"
17 #include "llvm/ADT/ArrayRef.h"
18 #include "llvm/Support/Error.h"
19 #include "llvm/Testing/Support/Error.h"
20 #include "gmock/gmock.h"
21 #include "gtest/gtest.h"
22 #include <string>
23 
24 using namespace clang;
25 using namespace dataflow;
26 using namespace test;
27 
28 namespace {
29 using ::testing::NotNull;
30 using ::testing::UnorderedElementsAre;
31 
32 static constexpr char ChromiumCheckHeader[] = R"(
33 namespace std {
34 class ostream;
35 } // namespace std
36 
37 namespace logging {
38 class VoidifyStream {
39  public:
40   VoidifyStream() = default;
41   void operator&(std::ostream&) {}
42 };
43 
44 class CheckError {
45  public:
46   static CheckError Check(const char* file, int line, const char* condition);
47   static CheckError DCheck(const char* file, int line, const char* condition);
48   static CheckError PCheck(const char* file, int line, const char* condition);
49   static CheckError PCheck(const char* file, int line);
50   static CheckError DPCheck(const char* file, int line, const char* condition);
51 
52   std::ostream& stream();
53 
54   ~CheckError();
55 
56   CheckError(const CheckError& other) = delete;
57   CheckError& operator=(const CheckError& other) = delete;
58   CheckError(CheckError&& other) = default;
59   CheckError& operator=(CheckError&& other) = default;
60 };
61 
62 } // namespace logging
63 
64 #define LAZY_CHECK_STREAM(stream, condition) \
65   !(condition) ? (void)0 : ::logging::VoidifyStream() & (stream)
66 
67 #define CHECK(condition)                                                     \
68   LAZY_CHECK_STREAM(                                                         \
69       ::logging::CheckError::Check(__FILE__, __LINE__, #condition).stream(), \
70       !(condition))
71 
72 #define PCHECK(condition)                                                     \
73   LAZY_CHECK_STREAM(                                                          \
74       ::logging::CheckError::PCheck(__FILE__, __LINE__, #condition).stream(), \
75       !(condition))
76 
77 #define DCHECK(condition)                                                     \
78   LAZY_CHECK_STREAM(                                                          \
79       ::logging::CheckError::DCheck(__FILE__, __LINE__, #condition).stream(), \
80       !(condition))
81 
82 #define DPCHECK(condition)                                                     \
83   LAZY_CHECK_STREAM(                                                           \
84       ::logging::CheckError::DPCheck(__FILE__, __LINE__, #condition).stream(), \
85       !(condition))
86 )";
87 
88 // A definition of the `CheckError` class that looks like the Chromium one, but
89 // is actually something else.
90 static constexpr char OtherCheckHeader[] = R"(
91 namespace other {
92 namespace logging {
93 class CheckError {
94  public:
95   static CheckError Check(const char* file, int line, const char* condition);
96 };
97 } // namespace logging
98 } // namespace other
99 )";
100 
101 /// Replaces all occurrences of `Pattern` in `S` with `Replacement`.
ReplacePattern(std::string S,const std::string & Pattern,const std::string & Replacement)102 std::string ReplacePattern(std::string S, const std::string &Pattern,
103                            const std::string &Replacement) {
104   size_t Pos = 0;
105   Pos = S.find(Pattern, Pos);
106   if (Pos != std::string::npos)
107     S.replace(Pos, Pattern.size(), Replacement);
108   return S;
109 }
110 
111 template <typename Model>
112 class ModelAdaptorAnalysis
113     : public DataflowAnalysis<ModelAdaptorAnalysis<Model>, NoopLattice> {
114 public:
ModelAdaptorAnalysis(ASTContext & Context)115   explicit ModelAdaptorAnalysis(ASTContext &Context)
116       : DataflowAnalysis<ModelAdaptorAnalysis, NoopLattice>(Context) {}
117 
initialElement()118   static NoopLattice initialElement() { return NoopLattice(); }
119 
transfer(const CFGElement & E,NoopLattice &,Environment & Env)120   void transfer(const CFGElement &E, NoopLattice &, Environment &Env) {
121     M.transfer(E, Env);
122   }
123 
124 private:
125   Model M;
126 };
127 
128 template <typename Matcher>
runDataflow(llvm::StringRef Code,Matcher Match)129 void runDataflow(llvm::StringRef Code, Matcher Match) {
130   const tooling::FileContentMappings FileContents = {
131       {"check.h", ChromiumCheckHeader}, {"othercheck.h", OtherCheckHeader}};
132 
133   ASSERT_THAT_ERROR(
134       checkDataflow<ModelAdaptorAnalysis<ChromiumCheckModel>>(
135           AnalysisInputs<ModelAdaptorAnalysis<ChromiumCheckModel>>(
136               Code, ast_matchers::hasName("target"),
137               [](ASTContext &C, Environment &) {
138                 return ModelAdaptorAnalysis<ChromiumCheckModel>(C);
139               })
140               .withASTBuildArgs({"-fsyntax-only",
141                                  "-fno-delayed-template-parsing", "-std=c++17"})
142               .withASTBuildVirtualMappedFiles(std::move(FileContents)),
143           /*VerifyResults=*/
144           [&Match](const llvm::StringMap<DataflowAnalysisState<NoopLattice>>
145                        &Results,
146                    const AnalysisOutputs &AO) { Match(Results, AO.ASTCtx); }),
147       llvm::Succeeded());
148 }
149 
TEST(ChromiumCheckModelTest,CheckSuccessImpliesConditionHolds)150 TEST(ChromiumCheckModelTest, CheckSuccessImpliesConditionHolds) {
151   auto Expectations =
152       [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
153          ASTContext &ASTCtx) {
154         ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
155         const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
156 
157         const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
158         ASSERT_THAT(FooDecl, NotNull());
159 
160         auto *FooVal = cast<BoolValue>(Env.getValue(*FooDecl));
161 
162         EXPECT_TRUE(Env.proves(FooVal->formula()));
163       };
164 
165   std::string Code = R"(
166     #include "check.h"
167 
168     void target(bool Foo) {
169       $check(Foo);
170       bool X = true;
171       (void)X;
172       // [[p]]
173     }
174   )";
175   runDataflow(ReplacePattern(Code, "$check", "CHECK"), Expectations);
176   runDataflow(ReplacePattern(Code, "$check", "DCHECK"), Expectations);
177   runDataflow(ReplacePattern(Code, "$check", "PCHECK"), Expectations);
178   runDataflow(ReplacePattern(Code, "$check", "DPCHECK"), Expectations);
179 }
180 
TEST(ChromiumCheckModelTest,UnrelatedCheckIgnored)181 TEST(ChromiumCheckModelTest, UnrelatedCheckIgnored) {
182   auto Expectations =
183       [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
184          ASTContext &ASTCtx) {
185         ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
186         const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
187 
188         const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
189         ASSERT_THAT(FooDecl, NotNull());
190 
191         auto *FooVal = cast<BoolValue>(Env.getValue(*FooDecl));
192 
193         EXPECT_FALSE(Env.proves(FooVal->formula()));
194       };
195 
196   std::string Code = R"(
197     #include "othercheck.h"
198 
199     void target(bool Foo) {
200       if (!Foo) {
201         (void)other::logging::CheckError::Check(__FILE__, __LINE__, "Foo");
202       }
203       bool X = true;
204       (void)X;
205       // [[p]]
206     }
207   )";
208   runDataflow(Code, Expectations);
209 }
210 } // namespace
211