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