brintos

brintos / llvm-project-archived public Read only

0
0
Text · 10.4 KiB · 6c32a8d Raw
296 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8//9// This checker models the behavior of un-inlined APIs from the gtest10// unit-testing library to avoid false positives when using assertions from11// 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 25using namespace clang;26using namespace ento;27 28// Modeling of un-inlined AssertionResult constructors29//30// The gtest unit testing API provides macros for assertions that expand31// into an if statement that calls a series of constructors and returns32// 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//     else47//       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 to56//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 to68// know that the boolean value of the expression "a == b" is stored the69// 'success_' field of the original AssertionResult temporary and propagated70// (via the copy constructor) into the 'success_' field of the object stored71// in 'gtest_ar_'.  That boolean value will then be returned from the bool72// conversion method in the if statement. This guarantees that the assertion73// holds when the return path is not taken.74//75// If the success value is not properly propagated, then the eager case split76// on evaluating the expression can cause pernicious false positives77// on the non-return path:78//79//   ASSERT(ptr != NULL)80//   *ptr = 7; // False positive null pointer dereference here81//82// Unfortunately, the bool constructor cannot be inlined (because its83// implementation is not present in the headers) and the copy constructor is84// not inlined (because it is constructed into a temporary and the analyzer85// does not inline these since it does not yet reliably call temporary86// destructors).87//88// This checker compensates for the missing inlining by propagating the89// _success value across the bool and copy constructors so the assertion behaves90// as expected.91 92namespace {93class GTestChecker : public Checker<check::PostCall> {94 95  mutable IdentifierInfo *AssertionResultII = nullptr;96  mutable IdentifierInfo *SuccessII = nullptr;97 98public:99  GTestChecker() = default;100 101  void checkPostCall(const CallEvent &Call, CheckerContext &C) const;102 103private: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  SVal113  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) or124/// 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.129void 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's156/// 'success_' field to be equal to the value of the pass-in instance's157/// 'success_' field.158void 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 other163  // 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.180void 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 their200  // 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 which209  // 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 238void 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-in247/// AssertionResult instance.248SVal 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.269ProgramStateRef 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 286void ento::registerGTestChecker(CheckerManager &Mgr) {287  Mgr.registerChecker<GTestChecker>();288}289 290bool ento::shouldRegisterGTestChecker(const CheckerManager &mgr) {291  // gtest is a C++ API so there is no sense running the checker292  // if not compiling for C++.293  const LangOptions &LO = mgr.getLangOpts();294  return LO.CPlusPlus;295}296