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 24 using namespace clang; 25 using 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 91 namespace { 92 class GTestChecker : public Checker<check::PostCall> { 93 94 mutable IdentifierInfo *AssertionResultII; 95 mutable IdentifierInfo *SuccessII; 96 97 public: 98 GTestChecker(); 99 100 void checkPostCall(const CallEvent &Call, CheckerContext &C) const; 101 102 private: 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 122 GTestChecker::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. 130 void 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. 159 void 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. 181 void 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 239 void 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. 249 SVal 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. 270 ProgramStateRef 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 290 void ento::registerGTestChecker(CheckerManager &Mgr) { 291 Mgr.registerChecker<GTestChecker>(); 292 } 293 294 bool 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