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 [] = 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 [] = 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`. |
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(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 | |
111 | template <typename Model> |
112 | class ModelAdaptorAnalysis |
113 | : public DataflowAnalysis<ModelAdaptorAnalysis<Model>, NoopLattice> { |
114 | public: |
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 | |
124 | private: |
125 | Model M; |
126 | }; |
127 | |
128 | template <typename Matcher> |
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 | |
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(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 | |
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(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 | |