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; 96 mutable IdentifierInfo *SuccessII; 97 98 public: 99 GTestChecker(); 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 GTestChecker::GTestChecker() : AssertionResultII(nullptr), SuccessII(nullptr) {} 124 125 /// Model a call to an un-inlined AssertionResult(bool) or 126 /// AssertionResult(bool &, ...). 127 /// To do so, constrain the value of the newly-constructed instance's 'success_' 128 /// field to be equal to the passed-in boolean value. 129 /// 130 /// \param IsRef Whether the boolean parameter is a reference or not. 131 void GTestChecker::modelAssertionResultBoolConstructor( 132 const CXXConstructorCall *Call, bool IsRef, CheckerContext &C) const { 133 assert(Call->getNumArgs() >= 1 && Call->getNumArgs() <= 2); 134 135 ProgramStateRef State = C.getState(); 136 SVal BooleanArgVal = Call->getArgSVal(0); 137 if (IsRef) { 138 // The argument is a reference, so load from it to get the boolean value. 139 if (!isa<Loc>(BooleanArgVal)) 140 return; 141 BooleanArgVal = C.getState()->getSVal(BooleanArgVal.castAs<Loc>()); 142 } 143 144 SVal ThisVal = Call->getCXXThisVal(); 145 146 SVal ThisSuccess = getAssertionResultSuccessFieldValue( 147 Call->getDecl()->getParent(), ThisVal, State); 148 149 State = assumeValuesEqual(ThisSuccess, BooleanArgVal, State, C); 150 C.addTransition(State); 151 } 152 153 /// Model a call to an un-inlined AssertionResult copy constructor: 154 /// 155 /// AssertionResult(const &AssertionResult other) 156 /// 157 /// To do so, constrain the value of the newly-constructed instance's 158 /// 'success_' field to be equal to the value of the pass-in instance's 159 /// 'success_' field. 160 void GTestChecker::modelAssertionResultCopyConstructor( 161 const CXXConstructorCall *Call, CheckerContext &C) const { 162 assert(Call->getNumArgs() == 1); 163 164 // The first parameter of the copy constructor must be the other 165 // instance to initialize this instances fields from. 166 SVal OtherVal = Call->getArgSVal(0); 167 SVal ThisVal = Call->getCXXThisVal(); 168 169 const CXXRecordDecl *AssertResultClassDecl = Call->getDecl()->getParent(); 170 ProgramStateRef State = C.getState(); 171 172 SVal ThisSuccess = getAssertionResultSuccessFieldValue(AssertResultClassDecl, 173 ThisVal, State); 174 SVal OtherSuccess = getAssertionResultSuccessFieldValue(AssertResultClassDecl, 175 OtherVal, State); 176 177 State = assumeValuesEqual(ThisSuccess, OtherSuccess, State, C); 178 C.addTransition(State); 179 } 180 181 /// Model calls to AssertionResult constructors that are not inlined. 182 void GTestChecker::checkPostCall(const CallEvent &Call, 183 CheckerContext &C) const { 184 /// If the constructor was inlined, there is no need model it. 185 if (C.wasInlined) 186 return; 187 188 initIdentifierInfo(C.getASTContext()); 189 190 auto *CtorCall = dyn_cast<CXXConstructorCall>(&Call); 191 if (!CtorCall) 192 return; 193 194 const CXXConstructorDecl *CtorDecl = CtorCall->getDecl(); 195 const CXXRecordDecl *CtorParent = CtorDecl->getParent(); 196 if (CtorParent->getIdentifier() != AssertionResultII) 197 return; 198 199 unsigned ParamCount = CtorDecl->getNumParams(); 200 201 // Call the appropriate modeling method based the parameters and their 202 // types. 203 204 // We have AssertionResult(const &AssertionResult) 205 if (CtorDecl->isCopyConstructor() && ParamCount == 1) { 206 modelAssertionResultCopyConstructor(CtorCall, C); 207 return; 208 } 209 210 // There are two possible boolean constructors, depending on which 211 // version of gtest is being used: 212 // 213 // v1.7 and earlier: 214 // AssertionResult(bool success) 215 // 216 // v1.8 and greater: 217 // template <typename T> 218 // AssertionResult(const T& success, 219 // typename internal::EnableIf< 220 // !internal::ImplicitlyConvertible<T, 221 // AssertionResult>::value>::type*) 222 // 223 CanQualType BoolTy = C.getASTContext().BoolTy; 224 if (ParamCount == 1 && CtorDecl->getParamDecl(0)->getType() == BoolTy) { 225 // We have AssertionResult(bool) 226 modelAssertionResultBoolConstructor(CtorCall, /*IsRef=*/false, C); 227 return; 228 } 229 if (ParamCount == 2){ 230 auto *RefTy = CtorDecl->getParamDecl(0)->getType()->getAs<ReferenceType>(); 231 if (RefTy && 232 RefTy->getPointeeType()->getCanonicalTypeUnqualified() == BoolTy) { 233 // We have AssertionResult(bool &, ...) 234 modelAssertionResultBoolConstructor(CtorCall, /*IsRef=*/true, C); 235 return; 236 } 237 } 238 } 239 240 void GTestChecker::initIdentifierInfo(ASTContext &Ctx) const { 241 if (AssertionResultII) 242 return; 243 244 AssertionResultII = &Ctx.Idents.get("AssertionResult"); 245 SuccessII = &Ctx.Idents.get("success_"); 246 } 247 248 /// Returns the value stored in the 'success_' field of the passed-in 249 /// AssertionResult instance. 250 SVal GTestChecker::getAssertionResultSuccessFieldValue( 251 const CXXRecordDecl *AssertionResultDecl, SVal Instance, 252 ProgramStateRef State) const { 253 254 DeclContext::lookup_result Result = AssertionResultDecl->lookup(SuccessII); 255 if (Result.empty()) 256 return UnknownVal(); 257 258 auto *SuccessField = dyn_cast<FieldDecl>(Result.front()); 259 if (!SuccessField) 260 return UnknownVal(); 261 262 std::optional<Loc> FieldLoc = 263 State->getLValue(SuccessField, Instance).getAs<Loc>(); 264 if (!FieldLoc) 265 return UnknownVal(); 266 267 return State->getSVal(*FieldLoc); 268 } 269 270 /// Constrain the passed-in state to assume two values are equal. 271 ProgramStateRef GTestChecker::assumeValuesEqual(SVal Val1, SVal Val2, 272 ProgramStateRef State, 273 CheckerContext &C) { 274 auto DVal1 = Val1.getAs<DefinedOrUnknownSVal>(); 275 auto DVal2 = Val2.getAs<DefinedOrUnknownSVal>(); 276 if (!DVal1 || !DVal2) 277 return State; 278 279 auto ValuesEqual = 280 C.getSValBuilder().evalEQ(State, *DVal1, *DVal2).getAs<DefinedSVal>(); 281 if (!ValuesEqual) 282 return State; 283 284 State = C.getConstraintManager().assume(State, *ValuesEqual, true); 285 return State; 286 } 287 288 void ento::registerGTestChecker(CheckerManager &Mgr) { 289 Mgr.registerChecker<GTestChecker>(); 290 } 291 292 bool ento::shouldRegisterGTestChecker(const CheckerManager &mgr) { 293 // gtest is a C++ API so there is no sense running the checker 294 // if not compiling for C++. 295 const LangOptions &LO = mgr.getLangOpts(); 296 return LO.CPlusPlus; 297 } 298