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