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