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 "NoopAnalysis.h"
12 #include "TestingSupport.h"
13 #include "clang/AST/ASTContext.h"
14 #include "clang/ASTMatchers/ASTMatchers.h"
15 #include "clang/Tooling/Tooling.h"
16 #include "llvm/ADT/ArrayRef.h"
17 #include "llvm/ADT/StringExtras.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::_;
30 using ::testing::ElementsAre;
31 using ::testing::NotNull;
32 using ::testing::Pair;
33 
34 static constexpr char ChromiumCheckHeader[] = R"(
35 namespace std {
36 class ostream;
37 } // namespace std
38 
39 namespace logging {
40 class VoidifyStream {
41  public:
42   VoidifyStream() = default;
43   void operator&(std::ostream&) {}
44 };
45 
46 class CheckError {
47  public:
48   static CheckError Check(const char* file, int line, const char* condition);
49   static CheckError DCheck(const char* file, int line, const char* condition);
50   static CheckError PCheck(const char* file, int line, const char* condition);
51   static CheckError PCheck(const char* file, int line);
52   static CheckError DPCheck(const char* file, int line, const char* condition);
53 
54   std::ostream& stream();
55 
56   ~CheckError();
57 
58   CheckError(const CheckError& other) = delete;
59   CheckError& operator=(const CheckError& other) = delete;
60   CheckError(CheckError&& other) = default;
61   CheckError& operator=(CheckError&& other) = default;
62 };
63 
64 } // namespace logging
65 
66 #define LAZY_CHECK_STREAM(stream, condition) \
67   !(condition) ? (void)0 : ::logging::VoidifyStream() & (stream)
68 
69 #define CHECK(condition)                                                     \
70   LAZY_CHECK_STREAM(                                                         \
71       ::logging::CheckError::Check(__FILE__, __LINE__, #condition).stream(), \
72       !(condition))
73 
74 #define PCHECK(condition)                                                     \
75   LAZY_CHECK_STREAM(                                                          \
76       ::logging::CheckError::PCheck(__FILE__, __LINE__, #condition).stream(), \
77       !(condition))
78 
79 #define DCHECK(condition)                                                     \
80   LAZY_CHECK_STREAM(                                                          \
81       ::logging::CheckError::DCheck(__FILE__, __LINE__, #condition).stream(), \
82       !(condition))
83 
84 #define DPCHECK(condition)                                                     \
85   LAZY_CHECK_STREAM(                                                           \
86       ::logging::CheckError::DPCheck(__FILE__, __LINE__, #condition).stream(), \
87       !(condition))
88 )";
89 
90 // A definition of the `CheckError` class that looks like the Chromium one, but
91 // is actually something else.
92 static constexpr char OtherCheckHeader[] = R"(
93 namespace other {
94 namespace logging {
95 class CheckError {
96  public:
97   static CheckError Check(const char* file, int line, const char* condition);
98 };
99 } // namespace logging
100 } // namespace other
101 )";
102 
103 /// Replaces all occurrences of `Pattern` in `S` with `Replacement`.
104 std::string ReplacePattern(std::string S, const std::string &Pattern,
105                            const std::string &Replacement) {
106   size_t Pos = 0;
107   Pos = S.find(Pattern, Pos);
108   if (Pos != std::string::npos)
109     S.replace(Pos, Pattern.size(), Replacement);
110   return S;
111 }
112 
113 template <typename Model>
114 class ModelAdaptorAnalysis
115     : public DataflowAnalysis<ModelAdaptorAnalysis<Model>, NoopLattice> {
116 public:
117   explicit ModelAdaptorAnalysis(ASTContext &Context)
118       : DataflowAnalysis<ModelAdaptorAnalysis, NoopLattice>(
119             Context, /*ApplyBuiltinTransfer=*/true) {}
120 
121   static NoopLattice initialElement() { return NoopLattice(); }
122 
123   void transfer(const Stmt *S, NoopLattice &, Environment &Env) {
124     M.transfer(S, Env);
125   }
126 
127 private:
128   Model M;
129 };
130 
131 class ChromiumCheckModelTest : public ::testing::TestWithParam<std::string> {
132 protected:
133   template <typename Matcher>
134   void runDataflow(llvm::StringRef Code, Matcher Match) {
135     const tooling::FileContentMappings FileContents = {
136         {"check.h", ChromiumCheckHeader}, {"othercheck.h", OtherCheckHeader}};
137 
138     ASSERT_THAT_ERROR(
139         test::checkDataflow<ModelAdaptorAnalysis<ChromiumCheckModel>>(
140             Code, "target",
141             [](ASTContext &C, Environment &) {
142               return ModelAdaptorAnalysis<ChromiumCheckModel>(C);
143             },
144             [&Match](
145                 llvm::ArrayRef<
146                     std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
147                     Results,
148                 ASTContext &ASTCtx) { Match(Results, ASTCtx); },
149             {"-fsyntax-only", "-fno-delayed-template-parsing", "-std=c++17"},
150             FileContents),
151         llvm::Succeeded());
152   }
153 };
154 
155 TEST_F(ChromiumCheckModelTest, CheckSuccessImpliesConditionHolds) {
156   auto Expectations =
157       [](llvm::ArrayRef<
158              std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
159              Results,
160          ASTContext &ASTCtx) {
161         ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
162         const Environment &Env = Results[0].second.Env;
163 
164         const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
165         ASSERT_THAT(FooDecl, NotNull());
166 
167         auto *FooVal = cast<BoolValue>(Env.getValue(*FooDecl, SkipPast::None));
168 
169         EXPECT_TRUE(Env.flowConditionImplies(*FooVal));
170       };
171 
172   std::string Code = R"(
173     #include "check.h"
174 
175     void target(bool Foo) {
176       $check(Foo);
177       bool X = true;
178       (void)X;
179       // [[p]]
180     }
181   )";
182   runDataflow(ReplacePattern(Code, "$check", "CHECK"), Expectations);
183   runDataflow(ReplacePattern(Code, "$check", "DCHECK"), Expectations);
184   runDataflow(ReplacePattern(Code, "$check", "PCHECK"), Expectations);
185   runDataflow(ReplacePattern(Code, "$check", "DPCHECK"), Expectations);
186 }
187 
188 TEST_F(ChromiumCheckModelTest, UnrelatedCheckIgnored) {
189   auto Expectations =
190       [](llvm::ArrayRef<
191              std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
192              Results,
193          ASTContext &ASTCtx) {
194         ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
195         const Environment &Env = Results[0].second.Env;
196 
197         const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
198         ASSERT_THAT(FooDecl, NotNull());
199 
200         auto *FooVal = cast<BoolValue>(Env.getValue(*FooDecl, SkipPast::None));
201 
202         EXPECT_FALSE(Env.flowConditionImplies(*FooVal));
203       };
204 
205   std::string Code = R"(
206     #include "othercheck.h"
207 
208     void target(bool Foo) {
209       if (!Foo) {
210         (void)other::logging::CheckError::Check(__FILE__, __LINE__, "Foo");
211       }
212       bool X = true;
213       (void)X;
214       // [[p]]
215     }
216   )";
217   runDataflow(Code, Expectations);
218 }
219 } // namespace
220