/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/StaticAnalyzer/Checkers/GTestChecker.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | //==- GTestChecker.cpp - Model gtest API --*- C++ -*-==// |
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 | | // |
9 | | // This checker models the behavior of un-inlined APIs from the gtest |
10 | | // unit-testing library to avoid false positives when using assertions from |
11 | | // that library. |
12 | | // |
13 | | //===----------------------------------------------------------------------===// |
14 | | |
15 | | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
16 | | #include "clang/AST/Expr.h" |
17 | | #include "clang/Basic/LangOptions.h" |
18 | | #include "clang/StaticAnalyzer/Core/Checker.h" |
19 | | #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" |
20 | | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
21 | | #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" |
22 | | #include "llvm/Support/raw_ostream.h" |
23 | | #include <optional> |
24 | | |
25 | | using namespace clang; |
26 | | using namespace ento; |
27 | | |
28 | | // Modeling of un-inlined AssertionResult constructors |
29 | | // |
30 | | // The gtest unit testing API provides macros for assertions that expand |
31 | | // into an if statement that calls a series of constructors and returns |
32 | | // when the "assertion" is false. |
33 | | // |
34 | | // For example, |
35 | | // |
36 | | // ASSERT_TRUE(a == b) |
37 | | // |
38 | | // expands into: |
39 | | // |
40 | | // switch (0) |
41 | | // case 0: |
42 | | // default: |
43 | | // if (const ::testing::AssertionResult gtest_ar_ = |
44 | | // ::testing::AssertionResult((a == b))) |
45 | | // ; |
46 | | // else |
47 | | // return ::testing::internal::AssertHelper( |
48 | | // ::testing::TestPartResult::kFatalFailure, |
49 | | // "<path to project>", |
50 | | // <line number>, |
51 | | // ::testing::internal::GetBoolAssertionFailureMessage( |
52 | | // gtest_ar_, "a == b", "false", "true") |
53 | | // .c_str()) = ::testing::Message(); |
54 | | // |
55 | | // where AssertionResult is defined similarly to |
56 | | // |
57 | | // class AssertionResult { |
58 | | // public: |
59 | | // AssertionResult(const AssertionResult& other); |
60 | | // explicit AssertionResult(bool success) : success_(success) {} |
61 | | // operator bool() const { return success_; } |
62 | | // ... |
63 | | // private: |
64 | | // bool success_; |
65 | | // }; |
66 | | // |
67 | | // In order for the analyzer to correctly handle this assertion, it needs to |
68 | | // know that the boolean value of the expression "a == b" is stored the |
69 | | // 'success_' field of the original AssertionResult temporary and propagated |
70 | | // (via the copy constructor) into the 'success_' field of the object stored |
71 | | // in 'gtest_ar_'. That boolean value will then be returned from the bool |
72 | | // conversion method in the if statement. This guarantees that the assertion |
73 | | // holds when the return path is not taken. |
74 | | // |
75 | | // If the success value is not properly propagated, then the eager case split |
76 | | // on evaluating the expression can cause pernicious false positives |
77 | | // on the non-return path: |
78 | | // |
79 | | // ASSERT(ptr != NULL) |
80 | | // *ptr = 7; // False positive null pointer dereference here |
81 | | // |
82 | | // Unfortunately, the bool constructor cannot be inlined (because its |
83 | | // implementation is not present in the headers) and the copy constructor is |
84 | | // not inlined (because it is constructed into a temporary and the analyzer |
85 | | // does not inline these since it does not yet reliably call temporary |
86 | | // destructors). |
87 | | // |
88 | | // This checker compensates for the missing inlining by propagating the |
89 | | // _success value across the bool and copy constructors so the assertion behaves |
90 | | // as expected. |
91 | | |
92 | | namespace { |
93 | | class GTestChecker : public Checker<check::PostCall> { |
94 | | |
95 | | mutable IdentifierInfo *AssertionResultII = nullptr; |
96 | | mutable IdentifierInfo *SuccessII = nullptr; |
97 | | |
98 | | public: |
99 | 17 | GTestChecker() = default; |
100 | | |
101 | | void checkPostCall(const CallEvent &Call, CheckerContext &C) const; |
102 | | |
103 | | private: |
104 | | void modelAssertionResultBoolConstructor(const CXXConstructorCall *Call, |
105 | | bool IsRef, CheckerContext &C) const; |
106 | | |
107 | | void modelAssertionResultCopyConstructor(const CXXConstructorCall *Call, |
108 | | CheckerContext &C) const; |
109 | | |
110 | | void initIdentifierInfo(ASTContext &Ctx) const; |
111 | | |
112 | | SVal |
113 | | getAssertionResultSuccessFieldValue(const CXXRecordDecl *AssertionResultDecl, |
114 | | SVal Instance, |
115 | | ProgramStateRef State) const; |
116 | | |
117 | | static ProgramStateRef assumeValuesEqual(SVal Val1, SVal Val2, |
118 | | ProgramStateRef State, |
119 | | CheckerContext &C); |
120 | | }; |
121 | | } // End anonymous namespace. |
122 | | |
123 | | /// Model a call to an un-inlined AssertionResult(bool) or |
124 | | /// AssertionResult(bool &, ...). |
125 | | /// To do so, constrain the value of the newly-constructed instance's 'success_' |
126 | | /// field to be equal to the passed-in boolean value. |
127 | | /// |
128 | | /// \param IsRef Whether the boolean parameter is a reference or not. |
129 | | void GTestChecker::modelAssertionResultBoolConstructor( |
130 | 0 | const CXXConstructorCall *Call, bool IsRef, CheckerContext &C) const { |
131 | 0 | assert(Call->getNumArgs() >= 1 && Call->getNumArgs() <= 2); |
132 | | |
133 | 0 | ProgramStateRef State = C.getState(); |
134 | 0 | SVal BooleanArgVal = Call->getArgSVal(0); |
135 | 0 | if (IsRef) { |
136 | | // The argument is a reference, so load from it to get the boolean value. |
137 | 0 | if (!isa<Loc>(BooleanArgVal)) |
138 | 0 | return; |
139 | 0 | BooleanArgVal = C.getState()->getSVal(BooleanArgVal.castAs<Loc>()); |
140 | 0 | } |
141 | | |
142 | 0 | SVal ThisVal = Call->getCXXThisVal(); |
143 | |
|
144 | 0 | SVal ThisSuccess = getAssertionResultSuccessFieldValue( |
145 | 0 | Call->getDecl()->getParent(), ThisVal, State); |
146 | |
|
147 | 0 | State = assumeValuesEqual(ThisSuccess, BooleanArgVal, State, C); |
148 | 0 | C.addTransition(State); |
149 | 0 | } |
150 | | |
151 | | /// Model a call to an un-inlined AssertionResult copy constructor: |
152 | | /// |
153 | | /// AssertionResult(const &AssertionResult other) |
154 | | /// |
155 | | /// To do so, constrain the value of the newly-constructed instance's |
156 | | /// 'success_' field to be equal to the value of the pass-in instance's |
157 | | /// 'success_' field. |
158 | | void GTestChecker::modelAssertionResultCopyConstructor( |
159 | 0 | const CXXConstructorCall *Call, CheckerContext &C) const { |
160 | 0 | assert(Call->getNumArgs() == 1); |
161 | | |
162 | | // The first parameter of the copy constructor must be the other |
163 | | // instance to initialize this instances fields from. |
164 | 0 | SVal OtherVal = Call->getArgSVal(0); |
165 | 0 | SVal ThisVal = Call->getCXXThisVal(); |
166 | |
|
167 | 0 | const CXXRecordDecl *AssertResultClassDecl = Call->getDecl()->getParent(); |
168 | 0 | ProgramStateRef State = C.getState(); |
169 | |
|
170 | 0 | SVal ThisSuccess = getAssertionResultSuccessFieldValue(AssertResultClassDecl, |
171 | 0 | ThisVal, State); |
172 | 0 | SVal OtherSuccess = getAssertionResultSuccessFieldValue(AssertResultClassDecl, |
173 | 0 | OtherVal, State); |
174 | |
|
175 | 0 | State = assumeValuesEqual(ThisSuccess, OtherSuccess, State, C); |
176 | 0 | C.addTransition(State); |
177 | 0 | } |
178 | | |
179 | | /// Model calls to AssertionResult constructors that are not inlined. |
180 | | void GTestChecker::checkPostCall(const CallEvent &Call, |
181 | 224 | CheckerContext &C) const { |
182 | | /// If the constructor was inlined, there is no need model it. |
183 | 224 | if (C.wasInlined) |
184 | 63 | return; |
185 | | |
186 | 161 | initIdentifierInfo(C.getASTContext()); |
187 | | |
188 | 161 | auto *CtorCall = dyn_cast<CXXConstructorCall>(&Call); |
189 | 161 | if (!CtorCall) |
190 | 129 | return; |
191 | | |
192 | 32 | const CXXConstructorDecl *CtorDecl = CtorCall->getDecl(); |
193 | 32 | const CXXRecordDecl *CtorParent = CtorDecl->getParent(); |
194 | 32 | if (CtorParent->getIdentifier() != AssertionResultII) |
195 | 32 | return; |
196 | | |
197 | 0 | unsigned ParamCount = CtorDecl->getNumParams(); |
198 | | |
199 | | // Call the appropriate modeling method based the parameters and their |
200 | | // types. |
201 | | |
202 | | // We have AssertionResult(const &AssertionResult) |
203 | 0 | if (CtorDecl->isCopyConstructor() && ParamCount == 1) { |
204 | 0 | modelAssertionResultCopyConstructor(CtorCall, C); |
205 | 0 | return; |
206 | 0 | } |
207 | | |
208 | | // There are two possible boolean constructors, depending on which |
209 | | // version of gtest is being used: |
210 | | // |
211 | | // v1.7 and earlier: |
212 | | // AssertionResult(bool success) |
213 | | // |
214 | | // v1.8 and greater: |
215 | | // template <typename T> |
216 | | // AssertionResult(const T& success, |
217 | | // typename internal::EnableIf< |
218 | | // !internal::ImplicitlyConvertible<T, |
219 | | // AssertionResult>::value>::type*) |
220 | | // |
221 | 0 | CanQualType BoolTy = C.getASTContext().BoolTy; |
222 | 0 | if (ParamCount == 1 && CtorDecl->getParamDecl(0)->getType() == BoolTy) { |
223 | | // We have AssertionResult(bool) |
224 | 0 | modelAssertionResultBoolConstructor(CtorCall, /*IsRef=*/false, C); |
225 | 0 | return; |
226 | 0 | } |
227 | 0 | if (ParamCount == 2){ |
228 | 0 | auto *RefTy = CtorDecl->getParamDecl(0)->getType()->getAs<ReferenceType>(); |
229 | 0 | if (RefTy && |
230 | 0 | RefTy->getPointeeType()->getCanonicalTypeUnqualified() == BoolTy) { |
231 | | // We have AssertionResult(bool &, ...) |
232 | 0 | modelAssertionResultBoolConstructor(CtorCall, /*IsRef=*/true, C); |
233 | 0 | return; |
234 | 0 | } |
235 | 0 | } |
236 | 0 | } |
237 | | |
238 | 161 | void GTestChecker::initIdentifierInfo(ASTContext &Ctx) const { |
239 | 161 | if (AssertionResultII) |
240 | 153 | return; |
241 | | |
242 | 8 | AssertionResultII = &Ctx.Idents.get("AssertionResult"); |
243 | 8 | SuccessII = &Ctx.Idents.get("success_"); |
244 | 8 | } |
245 | | |
246 | | /// Returns the value stored in the 'success_' field of the passed-in |
247 | | /// AssertionResult instance. |
248 | | SVal GTestChecker::getAssertionResultSuccessFieldValue( |
249 | | const CXXRecordDecl *AssertionResultDecl, SVal Instance, |
250 | 0 | ProgramStateRef State) const { |
251 | |
|
252 | 0 | DeclContext::lookup_result Result = AssertionResultDecl->lookup(SuccessII); |
253 | 0 | if (Result.empty()) |
254 | 0 | return UnknownVal(); |
255 | | |
256 | 0 | auto *SuccessField = dyn_cast<FieldDecl>(Result.front()); |
257 | 0 | if (!SuccessField) |
258 | 0 | return UnknownVal(); |
259 | | |
260 | 0 | std::optional<Loc> FieldLoc = |
261 | 0 | State->getLValue(SuccessField, Instance).getAs<Loc>(); |
262 | 0 | if (!FieldLoc) |
263 | 0 | return UnknownVal(); |
264 | | |
265 | 0 | return State->getSVal(*FieldLoc); |
266 | 0 | } |
267 | | |
268 | | /// Constrain the passed-in state to assume two values are equal. |
269 | | ProgramStateRef GTestChecker::assumeValuesEqual(SVal Val1, SVal Val2, |
270 | | ProgramStateRef State, |
271 | 0 | CheckerContext &C) { |
272 | 0 | auto DVal1 = Val1.getAs<DefinedOrUnknownSVal>(); |
273 | 0 | auto DVal2 = Val2.getAs<DefinedOrUnknownSVal>(); |
274 | 0 | if (!DVal1 || !DVal2) |
275 | 0 | return State; |
276 | | |
277 | 0 | auto ValuesEqual = |
278 | 0 | C.getSValBuilder().evalEQ(State, *DVal1, *DVal2).getAs<DefinedSVal>(); |
279 | 0 | if (!ValuesEqual) |
280 | 0 | return State; |
281 | | |
282 | 0 | State = C.getConstraintManager().assume(State, *ValuesEqual, true); |
283 | 0 | return State; |
284 | 0 | } |
285 | | |
286 | 17 | void ento::registerGTestChecker(CheckerManager &Mgr) { |
287 | 17 | Mgr.registerChecker<GTestChecker>(); |
288 | 17 | } |
289 | | |
290 | 90 | bool ento::shouldRegisterGTestChecker(const CheckerManager &mgr) { |
291 | | // gtest is a C++ API so there is no sense running the checker |
292 | | // if not compiling for C++. |
293 | 90 | const LangOptions &LO = mgr.getLangOpts(); |
294 | 90 | return LO.CPlusPlus; |
295 | 90 | } |