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
24using namespace clang;
25using namespace dataflow;
26using namespace test;
27
28namespace {
29using ::testing::NotNull;
30using ::testing::UnorderedElementsAre;
31
32static constexpr char ChromiumCheckHeader[] = R"(
33namespace std {
34class ostream;
35} // namespace std
36
37namespace logging {
38class VoidifyStream {
39 public:
40 VoidifyStream() = default;
41 void operator&(std::ostream&) {}
42};
43
44class 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.
90static constexpr char OtherCheckHeader[] = R"(
91namespace other {
92namespace logging {
93class 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`.
102std::string ReplacePattern(std::string S, const std::string &Pattern,
103 const std::string &Replacement) {
104 size_t Pos = 0;
105 Pos = S.find(str: Pattern, pos: Pos);
106 if (Pos != std::string::npos)
107 S.replace(pos: Pos, n: Pattern.size(), str: Replacement);
108 return S;
109}
110
111template <typename Model>
112class ModelAdaptorAnalysis
113 : public DataflowAnalysis<ModelAdaptorAnalysis<Model>, NoopLattice> {
114public:
115 explicit ModelAdaptorAnalysis(ASTContext &Context)
116 : DataflowAnalysis<ModelAdaptorAnalysis, NoopLattice>(Context) {}
117
118 static NoopLattice initialElement() { return NoopLattice(); }
119
120 void transfer(const CFGElement &E, NoopLattice &, Environment &Env) {
121 M.transfer(E, Env);
122 }
123
124private:
125 Model M;
126};
127
128template <typename Matcher>
129void 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
150TEST(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(AnnotationStates: Results, Annotation: "p");
156
157 const ValueDecl *FooDecl = findValueDecl(ASTCtx, Name: "Foo");
158 ASSERT_THAT(FooDecl, NotNull());
159
160 auto *FooVal = cast<BoolValue>(Val: Env.getValue(D: *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(Code: ReplacePattern(S: Code, Pattern: "$check", Replacement: "CHECK"), Match: Expectations);
176 runDataflow(Code: ReplacePattern(S: Code, Pattern: "$check", Replacement: "DCHECK"), Match: Expectations);
177 runDataflow(Code: ReplacePattern(S: Code, Pattern: "$check", Replacement: "PCHECK"), Match: Expectations);
178 runDataflow(Code: ReplacePattern(S: Code, Pattern: "$check", Replacement: "DPCHECK"), Match: Expectations);
179}
180
181TEST(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(AnnotationStates: Results, Annotation: "p");
187
188 const ValueDecl *FooDecl = findValueDecl(ASTCtx, Name: "Foo");
189 ASSERT_THAT(FooDecl, NotNull());
190
191 auto *FooVal = cast<BoolValue>(Val: Env.getValue(D: *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, Match: Expectations);
209}
210} // namespace
211

source code of clang/unittests/Analysis/FlowSensitive/ChromiumCheckModelTest.cpp