1 //==- GTestChecker.cpp - Model gtest API --*- C++ -*-==//
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
7 //===----------------------------------------------------------------------===//
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
13 //===----------------------------------------------------------------------===//
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"
25 using namespace clang
;
28 // Modeling of un-inlined AssertionResult constructors
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.
36 // ASSERT_TRUE(a == b)
43 // if (const ::testing::AssertionResult gtest_ar_ =
44 // ::testing::AssertionResult((a == b)))
47 // return ::testing::internal::AssertHelper(
48 // ::testing::TestPartResult::kFatalFailure,
49 // "<path to project>",
51 // ::testing::internal::GetBoolAssertionFailureMessage(
52 // gtest_ar_, "a == b", "false", "true")
53 // .c_str()) = ::testing::Message();
55 // where AssertionResult is defined similarly to
57 // class AssertionResult {
59 // AssertionResult(const AssertionResult& other);
60 // explicit AssertionResult(bool success) : success_(success) {}
61 // operator bool() const { return success_; }
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.
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:
79 // ASSERT(ptr != NULL)
80 // *ptr = 7; // False positive null pointer dereference here
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
88 // This checker compensates for the missing inlining by propagating the
89 // _success value across the bool and copy constructors so the assertion behaves
93 class GTestChecker
: public Checker
<check::PostCall
> {
95 mutable IdentifierInfo
*AssertionResultII
= nullptr;
96 mutable IdentifierInfo
*SuccessII
= nullptr;
99 GTestChecker() = default;
101 void checkPostCall(const CallEvent
&Call
, CheckerContext
&C
) const;
104 void modelAssertionResultBoolConstructor(const CXXConstructorCall
*Call
,
105 bool IsRef
, CheckerContext
&C
) const;
107 void modelAssertionResultCopyConstructor(const CXXConstructorCall
*Call
,
108 CheckerContext
&C
) const;
110 void initIdentifierInfo(ASTContext
&Ctx
) const;
113 getAssertionResultSuccessFieldValue(const CXXRecordDecl
*AssertionResultDecl
,
115 ProgramStateRef State
) const;
117 static ProgramStateRef
assumeValuesEqual(SVal Val1
, SVal Val2
,
118 ProgramStateRef State
,
121 } // End anonymous namespace.
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.
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);
133 ProgramStateRef State
= C
.getState();
134 SVal BooleanArgVal
= Call
->getArgSVal(0);
136 // The argument is a reference, so load from it to get the boolean value.
137 if (!isa
<Loc
>(BooleanArgVal
))
139 BooleanArgVal
= C
.getState()->getSVal(BooleanArgVal
.castAs
<Loc
>());
142 SVal ThisVal
= Call
->getCXXThisVal();
144 SVal ThisSuccess
= getAssertionResultSuccessFieldValue(
145 Call
->getDecl()->getParent(), ThisVal
, State
);
147 State
= assumeValuesEqual(ThisSuccess
, BooleanArgVal
, State
, C
);
148 C
.addTransition(State
);
151 /// Model a call to an un-inlined AssertionResult copy constructor:
153 /// AssertionResult(const &AssertionResult other)
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);
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(0);
165 SVal ThisVal
= Call
->getCXXThisVal();
167 const CXXRecordDecl
*AssertResultClassDecl
= Call
->getDecl()->getParent();
168 ProgramStateRef State
= C
.getState();
170 SVal ThisSuccess
= getAssertionResultSuccessFieldValue(AssertResultClassDecl
,
172 SVal OtherSuccess
= getAssertionResultSuccessFieldValue(AssertResultClassDecl
,
175 State
= assumeValuesEqual(ThisSuccess
, OtherSuccess
, State
, C
);
176 C
.addTransition(State
);
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.
186 initIdentifierInfo(C
.getASTContext());
188 auto *CtorCall
= dyn_cast
<CXXConstructorCall
>(&Call
);
192 const CXXConstructorDecl
*CtorDecl
= CtorCall
->getDecl();
193 const CXXRecordDecl
*CtorParent
= CtorDecl
->getParent();
194 if (CtorParent
->getIdentifier() != AssertionResultII
)
197 unsigned ParamCount
= CtorDecl
->getNumParams();
199 // Call the appropriate modeling method based the parameters and their
202 // We have AssertionResult(const &AssertionResult)
203 if (CtorDecl
->isCopyConstructor() && ParamCount
== 1) {
204 modelAssertionResultCopyConstructor(CtorCall
, C
);
208 // There are two possible boolean constructors, depending on which
209 // version of gtest is being used:
212 // AssertionResult(bool success)
215 // template <typename T>
216 // AssertionResult(const T& success,
217 // typename internal::EnableIf<
218 // !internal::ImplicitlyConvertible<T,
219 // AssertionResult>::value>::type*)
221 CanQualType BoolTy
= C
.getASTContext().BoolTy
;
222 if (ParamCount
== 1 && CtorDecl
->getParamDecl(0)->getType() == BoolTy
) {
223 // We have AssertionResult(bool)
224 modelAssertionResultBoolConstructor(CtorCall
, /*IsRef=*/false, C
);
227 if (ParamCount
== 2){
228 auto *RefTy
= CtorDecl
->getParamDecl(0)->getType()->getAs
<ReferenceType
>();
230 RefTy
->getPointeeType()->getCanonicalTypeUnqualified() == BoolTy
) {
231 // We have AssertionResult(bool &, ...)
232 modelAssertionResultBoolConstructor(CtorCall
, /*IsRef=*/true, C
);
238 void GTestChecker::initIdentifierInfo(ASTContext
&Ctx
) const {
239 if (AssertionResultII
)
242 AssertionResultII
= &Ctx
.Idents
.get("AssertionResult");
243 SuccessII
= &Ctx
.Idents
.get("success_");
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 {
252 DeclContext::lookup_result Result
= AssertionResultDecl
->lookup(SuccessII
);
256 auto *SuccessField
= dyn_cast
<FieldDecl
>(Result
.front());
260 std::optional
<Loc
> FieldLoc
=
261 State
->getLValue(SuccessField
, Instance
).getAs
<Loc
>();
265 return State
->getSVal(*FieldLoc
);
268 /// Constrain the passed-in state to assume two values are equal.
269 ProgramStateRef
GTestChecker::assumeValuesEqual(SVal Val1
, SVal Val2
,
270 ProgramStateRef State
,
272 auto DVal1
= Val1
.getAs
<DefinedOrUnknownSVal
>();
273 auto DVal2
= Val2
.getAs
<DefinedOrUnknownSVal
>();
274 if (!DVal1
|| !DVal2
)
278 C
.getSValBuilder().evalEQ(State
, *DVal1
, *DVal2
).getAs
<DefinedSVal
>();
282 State
= C
.getConstraintManager().assume(State
, *ValuesEqual
, true);
286 void ento::registerGTestChecker(CheckerManager
&Mgr
) {
287 Mgr
.registerChecker
<GTestChecker
>();
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();