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/FlowSensitive/NoopAnalysis.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`.
ReplacePattern(std::string S,const std::string & Pattern,const std::string & 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:
ModelAdaptorAnalysis(ASTContext & Context)117   explicit ModelAdaptorAnalysis(ASTContext &Context)
118       : DataflowAnalysis<ModelAdaptorAnalysis, NoopLattice>(
119             Context, /*ApplyBuiltinTransfer=*/true) {}
120 
initialElement()121   static NoopLattice initialElement() { return NoopLattice(); }
122 
transfer(const Stmt * S,NoopLattice &,Environment & Env)123   void transfer(const Stmt *S, NoopLattice &, Environment &Env) {
124     M.transfer(S, Env);
125   }
126 
127 private:
128   Model M;
129 };
130 
131 template <typename Matcher>
runDataflow(llvm::StringRef Code,Matcher Match)132 void runDataflow(llvm::StringRef Code, Matcher Match) {
133   const tooling::FileContentMappings FileContents = {
134       {"check.h", ChromiumCheckHeader}, {"othercheck.h", OtherCheckHeader}};
135 
136   ASSERT_THAT_ERROR(
137       test::checkDataflow<ModelAdaptorAnalysis<ChromiumCheckModel>>(
138           Code, "target",
139           [](ASTContext &C, Environment &) {
140             return ModelAdaptorAnalysis<ChromiumCheckModel>(C);
141           },
142           [&Match](
143               llvm::ArrayRef<
144                   std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
145                   Results,
146               ASTContext &ASTCtx) { Match(Results, ASTCtx); },
147           {"-fsyntax-only", "-fno-delayed-template-parsing", "-std=c++17"},
148           FileContents),
149       llvm::Succeeded());
150 }
151 
TEST(ChromiumCheckModelTest,CheckSuccessImpliesConditionHolds)152 TEST(ChromiumCheckModelTest, CheckSuccessImpliesConditionHolds) {
153   auto Expectations =
154       [](llvm::ArrayRef<
155              std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
156              Results,
157          ASTContext &ASTCtx) {
158         ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
159         const Environment &Env = Results[0].second.Env;
160 
161         const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
162         ASSERT_THAT(FooDecl, NotNull());
163 
164         auto *FooVal = cast<BoolValue>(Env.getValue(*FooDecl, SkipPast::None));
165 
166         EXPECT_TRUE(Env.flowConditionImplies(*FooVal));
167       };
168 
169   std::string Code = R"(
170     #include "check.h"
171 
172     void target(bool Foo) {
173       $check(Foo);
174       bool X = true;
175       (void)X;
176       // [[p]]
177     }
178   )";
179   runDataflow(ReplacePattern(Code, "$check", "CHECK"), Expectations);
180   runDataflow(ReplacePattern(Code, "$check", "DCHECK"), Expectations);
181   runDataflow(ReplacePattern(Code, "$check", "PCHECK"), Expectations);
182   runDataflow(ReplacePattern(Code, "$check", "DPCHECK"), Expectations);
183 }
184 
TEST(ChromiumCheckModelTest,UnrelatedCheckIgnored)185 TEST(ChromiumCheckModelTest, UnrelatedCheckIgnored) {
186   auto Expectations =
187       [](llvm::ArrayRef<
188              std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
189              Results,
190          ASTContext &ASTCtx) {
191         ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
192         const Environment &Env = Results[0].second.Env;
193 
194         const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
195         ASSERT_THAT(FooDecl, NotNull());
196 
197         auto *FooVal = cast<BoolValue>(Env.getValue(*FooDecl, SkipPast::None));
198 
199         EXPECT_FALSE(Env.flowConditionImplies(*FooVal));
200       };
201 
202   std::string Code = R"(
203     #include "othercheck.h"
204 
205     void target(bool Foo) {
206       if (!Foo) {
207         (void)other::logging::CheckError::Check(__FILE__, __LINE__, "Foo");
208       }
209       bool X = true;
210       (void)X;
211       // [[p]]
212     }
213   )";
214   runDataflow(Code, Expectations);
215 }
216 } // namespace
217