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 | 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 | const CXXConstructorCall *Call, bool IsRef, CheckerContext &C) const { |
131 | assert(Call->getNumArgs() >= 1 && Call->getNumArgs() <= 2); |
132 | |
133 | ProgramStateRef State = C.getState(); |
134 | SVal BooleanArgVal = Call->getArgSVal(Index: 0); |
135 | if (IsRef) { |
136 | // The argument is a reference, so load from it to get the boolean value. |
137 | if (!isa<Loc>(Val: BooleanArgVal)) |
138 | return; |
139 | BooleanArgVal = C.getState()->getSVal(LV: BooleanArgVal.castAs<Loc>()); |
140 | } |
141 | |
142 | SVal ThisVal = Call->getCXXThisVal(); |
143 | |
144 | SVal ThisSuccess = getAssertionResultSuccessFieldValue( |
145 | AssertionResultDecl: Call->getDecl()->getParent(), Instance: ThisVal, State); |
146 | |
147 | State = assumeValuesEqual(Val1: ThisSuccess, Val2: BooleanArgVal, State, C); |
148 | C.addTransition(State); |
149 | } |
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 | const CXXConstructorCall *Call, CheckerContext &C) const { |
160 | 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 | SVal OtherVal = Call->getArgSVal(Index: 0); |
165 | SVal ThisVal = Call->getCXXThisVal(); |
166 | |
167 | const CXXRecordDecl *AssertResultClassDecl = Call->getDecl()->getParent(); |
168 | ProgramStateRef State = C.getState(); |
169 | |
170 | SVal ThisSuccess = getAssertionResultSuccessFieldValue(AssertionResultDecl: AssertResultClassDecl, |
171 | Instance: ThisVal, State); |
172 | SVal OtherSuccess = getAssertionResultSuccessFieldValue(AssertionResultDecl: AssertResultClassDecl, |
173 | Instance: OtherVal, State); |
174 | |
175 | State = assumeValuesEqual(Val1: ThisSuccess, Val2: OtherSuccess, State, C); |
176 | C.addTransition(State); |
177 | } |
178 | |
179 | /// Model calls to AssertionResult constructors that are not inlined. |
180 | void GTestChecker::checkPostCall(const CallEvent &Call, |
181 | CheckerContext &C) const { |
182 | /// If the constructor was inlined, there is no need model it. |
183 | if (C.wasInlined) |
184 | return; |
185 | |
186 | initIdentifierInfo(Ctx&: C.getASTContext()); |
187 | |
188 | auto *CtorCall = dyn_cast<CXXConstructorCall>(Val: &Call); |
189 | if (!CtorCall) |
190 | return; |
191 | |
192 | const CXXConstructorDecl *CtorDecl = CtorCall->getDecl(); |
193 | const CXXRecordDecl *CtorParent = CtorDecl->getParent(); |
194 | if (CtorParent->getIdentifier() != AssertionResultII) |
195 | return; |
196 | |
197 | 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 | if (CtorDecl->isCopyConstructor() && ParamCount == 1) { |
204 | modelAssertionResultCopyConstructor(Call: CtorCall, C); |
205 | return; |
206 | } |
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 | CanQualType BoolTy = C.getASTContext().BoolTy; |
222 | if (ParamCount == 1 && CtorDecl->getParamDecl(i: 0)->getType() == BoolTy) { |
223 | // We have AssertionResult(bool) |
224 | modelAssertionResultBoolConstructor(Call: CtorCall, /*IsRef=*/false, C); |
225 | return; |
226 | } |
227 | if (ParamCount == 2){ |
228 | auto *RefTy = CtorDecl->getParamDecl(i: 0)->getType()->getAs<ReferenceType>(); |
229 | if (RefTy && |
230 | RefTy->getPointeeType()->getCanonicalTypeUnqualified() == BoolTy) { |
231 | // We have AssertionResult(bool &, ...) |
232 | modelAssertionResultBoolConstructor(Call: CtorCall, /*IsRef=*/true, C); |
233 | return; |
234 | } |
235 | } |
236 | } |
237 | |
238 | void GTestChecker::initIdentifierInfo(ASTContext &Ctx) const { |
239 | if (AssertionResultII) |
240 | return; |
241 | |
242 | AssertionResultII = &Ctx.Idents.get(Name: "AssertionResult" ); |
243 | SuccessII = &Ctx.Idents.get(Name: "success_" ); |
244 | } |
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 | ProgramStateRef State) const { |
251 | |
252 | DeclContext::lookup_result Result = AssertionResultDecl->lookup(Name: SuccessII); |
253 | if (Result.empty()) |
254 | return UnknownVal(); |
255 | |
256 | auto *SuccessField = dyn_cast<FieldDecl>(Val: Result.front()); |
257 | if (!SuccessField) |
258 | return UnknownVal(); |
259 | |
260 | std::optional<Loc> FieldLoc = |
261 | State->getLValue(decl: SuccessField, Base: Instance).getAs<Loc>(); |
262 | if (!FieldLoc) |
263 | return UnknownVal(); |
264 | |
265 | return State->getSVal(LV: *FieldLoc); |
266 | } |
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 | CheckerContext &C) { |
272 | auto DVal1 = Val1.getAs<DefinedOrUnknownSVal>(); |
273 | auto DVal2 = Val2.getAs<DefinedOrUnknownSVal>(); |
274 | if (!DVal1 || !DVal2) |
275 | return State; |
276 | |
277 | auto ValuesEqual = |
278 | C.getSValBuilder().evalEQ(state: State, lhs: *DVal1, rhs: *DVal2).getAs<DefinedSVal>(); |
279 | if (!ValuesEqual) |
280 | return State; |
281 | |
282 | State = C.getConstraintManager().assume(state: State, Cond: *ValuesEqual, Assumption: true); |
283 | return State; |
284 | } |
285 | |
286 | void ento::registerGTestChecker(CheckerManager &Mgr) { |
287 | Mgr.registerChecker<GTestChecker>(); |
288 | } |
289 | |
290 | 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 | const LangOptions &LO = mgr.getLangOpts(); |
294 | return LO.CPlusPlus; |
295 | } |
296 | |