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(0); 135 if (IsRef) { 136 // The argument is a reference, so load from it to get the boolean value. 137 if (!isa<Loc>(BooleanArgVal)) 138 return; 139 BooleanArgVal = C.getState()->getSVal(BooleanArgVal.castAs<Loc>()); 140 } 141 142 SVal ThisVal = Call->getCXXThisVal(); 143 144 SVal ThisSuccess = getAssertionResultSuccessFieldValue( 145 Call->getDecl()->getParent(), ThisVal, State); 146 147 State = assumeValuesEqual(ThisSuccess, 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(0); 165 SVal ThisVal = Call->getCXXThisVal(); 166 167 const CXXRecordDecl *AssertResultClassDecl = Call->getDecl()->getParent(); 168 ProgramStateRef State = C.getState(); 169 170 SVal ThisSuccess = getAssertionResultSuccessFieldValue(AssertResultClassDecl, 171 ThisVal, State); 172 SVal OtherSuccess = getAssertionResultSuccessFieldValue(AssertResultClassDecl, 173 OtherVal, State); 174 175 State = assumeValuesEqual(ThisSuccess, 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(C.getASTContext()); 187 188 auto *CtorCall = dyn_cast<CXXConstructorCall>(&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(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(0)->getType() == BoolTy) { 223 // We have AssertionResult(bool) 224 modelAssertionResultBoolConstructor(CtorCall, /*IsRef=*/false, C); 225 return; 226 } 227 if (ParamCount == 2){ 228 auto *RefTy = CtorDecl->getParamDecl(0)->getType()->getAs<ReferenceType>(); 229 if (RefTy && 230 RefTy->getPointeeType()->getCanonicalTypeUnqualified() == BoolTy) { 231 // We have AssertionResult(bool &, ...) 232 modelAssertionResultBoolConstructor(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("AssertionResult"); 243 SuccessII = &Ctx.Idents.get("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(SuccessII); 253 if (Result.empty()) 254 return UnknownVal(); 255 256 auto *SuccessField = dyn_cast<FieldDecl>(Result.front()); 257 if (!SuccessField) 258 return UnknownVal(); 259 260 std::optional<Loc> FieldLoc = 261 State->getLValue(SuccessField, Instance).getAs<Loc>(); 262 if (!FieldLoc) 263 return UnknownVal(); 264 265 return State->getSVal(*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, *DVal1, *DVal2).getAs<DefinedSVal>(); 279 if (!ValuesEqual) 280 return State; 281 282 State = C.getConstraintManager().assume(State, *ValuesEqual, 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