297 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			297 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
//==- GTestChecker.cpp - Model gtest API --*- C++ -*-==//
 | 
						|
//
 | 
						|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 | 
						|
// See https://llvm.org/LICENSE.txt for license information.
 | 
						|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 | 
						|
//
 | 
						|
//===----------------------------------------------------------------------===//
 | 
						|
//
 | 
						|
// This checker models the behavior of un-inlined APIs from the gtest
 | 
						|
// unit-testing library to avoid false positives when using assertions from
 | 
						|
// that library.
 | 
						|
//
 | 
						|
//===----------------------------------------------------------------------===//
 | 
						|
 | 
						|
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
 | 
						|
#include "clang/AST/Expr.h"
 | 
						|
#include "clang/Basic/LangOptions.h"
 | 
						|
#include "clang/StaticAnalyzer/Core/Checker.h"
 | 
						|
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
 | 
						|
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
 | 
						|
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
 | 
						|
#include "llvm/Support/raw_ostream.h"
 | 
						|
 | 
						|
using namespace clang;
 | 
						|
using namespace ento;
 | 
						|
 | 
						|
// Modeling of un-inlined AssertionResult constructors
 | 
						|
//
 | 
						|
// The gtest unit testing API provides macros for assertions that expand
 | 
						|
// into an if statement that calls a series of constructors and returns
 | 
						|
// when the "assertion" is false.
 | 
						|
//
 | 
						|
// For example,
 | 
						|
//
 | 
						|
//   ASSERT_TRUE(a == b)
 | 
						|
//
 | 
						|
// expands into:
 | 
						|
//
 | 
						|
//   switch (0)
 | 
						|
//   case 0:
 | 
						|
//   default:
 | 
						|
//     if (const ::testing::AssertionResult gtest_ar_ =
 | 
						|
//             ::testing::AssertionResult((a == b)))
 | 
						|
//       ;
 | 
						|
//     else
 | 
						|
//       return ::testing::internal::AssertHelper(
 | 
						|
//                  ::testing::TestPartResult::kFatalFailure,
 | 
						|
//                  "<path to project>",
 | 
						|
//                  <line number>,
 | 
						|
//                  ::testing::internal::GetBoolAssertionFailureMessage(
 | 
						|
//                      gtest_ar_, "a == b", "false", "true")
 | 
						|
//                      .c_str()) = ::testing::Message();
 | 
						|
//
 | 
						|
// where AssertionResult is defined similarly to
 | 
						|
//
 | 
						|
//   class AssertionResult {
 | 
						|
//   public:
 | 
						|
//     AssertionResult(const AssertionResult& other);
 | 
						|
//     explicit AssertionResult(bool success) : success_(success) {}
 | 
						|
//     operator bool() const { return success_; }
 | 
						|
//     ...
 | 
						|
//     private:
 | 
						|
//     bool success_;
 | 
						|
//   };
 | 
						|
//
 | 
						|
// In order for the analyzer to correctly handle this assertion, it needs to
 | 
						|
// know that the boolean value of the expression "a == b" is stored the
 | 
						|
// 'success_' field of the original AssertionResult temporary and propagated
 | 
						|
// (via the copy constructor) into the 'success_' field of the object stored
 | 
						|
// in 'gtest_ar_'.  That boolean value will then be returned from the bool
 | 
						|
// conversion method in the if statement. This guarantees that the assertion
 | 
						|
// holds when the return path is not taken.
 | 
						|
//
 | 
						|
// If the success value is not properly propagated, then the eager case split
 | 
						|
// on evaluating the expression can cause pernicious false positives
 | 
						|
// on the non-return path:
 | 
						|
//
 | 
						|
//   ASSERT(ptr != NULL)
 | 
						|
//   *ptr = 7; // False positive null pointer dereference here
 | 
						|
//
 | 
						|
// Unfortunately, the bool constructor cannot be inlined (because its
 | 
						|
// implementation is not present in the headers) and the copy constructor is
 | 
						|
// not inlined (because it is constructed into a temporary and the analyzer
 | 
						|
// does not inline these since it does not yet reliably call temporary
 | 
						|
// destructors).
 | 
						|
//
 | 
						|
// This checker compensates for the missing inlining by propagating the
 | 
						|
// _success value across the bool and copy constructors so the assertion behaves
 | 
						|
// as expected.
 | 
						|
 | 
						|
namespace {
 | 
						|
class GTestChecker : public Checker<check::PostCall> {
 | 
						|
 | 
						|
  mutable IdentifierInfo *AssertionResultII;
 | 
						|
  mutable IdentifierInfo *SuccessII;
 | 
						|
 | 
						|
public:
 | 
						|
  GTestChecker();
 | 
						|
 | 
						|
  void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
 | 
						|
 | 
						|
private:
 | 
						|
  void modelAssertionResultBoolConstructor(const CXXConstructorCall *Call,
 | 
						|
                                           bool IsRef, CheckerContext &C) const;
 | 
						|
 | 
						|
  void modelAssertionResultCopyConstructor(const CXXConstructorCall *Call,
 | 
						|
                                           CheckerContext &C) const;
 | 
						|
 | 
						|
  void initIdentifierInfo(ASTContext &Ctx) const;
 | 
						|
 | 
						|
  SVal
 | 
						|
  getAssertionResultSuccessFieldValue(const CXXRecordDecl *AssertionResultDecl,
 | 
						|
                                      SVal Instance,
 | 
						|
                                      ProgramStateRef State) const;
 | 
						|
 | 
						|
  static ProgramStateRef assumeValuesEqual(SVal Val1, SVal Val2,
 | 
						|
                                           ProgramStateRef State,
 | 
						|
                                           CheckerContext &C);
 | 
						|
};
 | 
						|
} // End anonymous namespace.
 | 
						|
 | 
						|
GTestChecker::GTestChecker() : AssertionResultII(nullptr), SuccessII(nullptr) {}
 | 
						|
 | 
						|
/// Model a call to an un-inlined AssertionResult(bool) or
 | 
						|
/// AssertionResult(bool &, ...).
 | 
						|
/// To do so, constrain the value of the newly-constructed instance's 'success_'
 | 
						|
/// field to be equal to the passed-in boolean value.
 | 
						|
///
 | 
						|
/// \param IsRef Whether the boolean parameter is a reference or not.
 | 
						|
void GTestChecker::modelAssertionResultBoolConstructor(
 | 
						|
    const CXXConstructorCall *Call, bool IsRef, CheckerContext &C) const {
 | 
						|
  assert(Call->getNumArgs() >= 1 && Call->getNumArgs() <= 2);
 | 
						|
 | 
						|
  ProgramStateRef State = C.getState();
 | 
						|
  SVal BooleanArgVal = Call->getArgSVal(0);
 | 
						|
  if (IsRef) {
 | 
						|
    // The argument is a reference, so load from it to get the boolean value.
 | 
						|
    if (!isa<Loc>(BooleanArgVal))
 | 
						|
      return;
 | 
						|
    BooleanArgVal = C.getState()->getSVal(BooleanArgVal.castAs<Loc>());
 | 
						|
  }
 | 
						|
 | 
						|
  SVal ThisVal = Call->getCXXThisVal();
 | 
						|
 | 
						|
  SVal ThisSuccess = getAssertionResultSuccessFieldValue(
 | 
						|
      Call->getDecl()->getParent(), ThisVal, State);
 | 
						|
 | 
						|
  State = assumeValuesEqual(ThisSuccess, BooleanArgVal, State, C);
 | 
						|
  C.addTransition(State);
 | 
						|
}
 | 
						|
 | 
						|
/// Model a call to an un-inlined AssertionResult copy constructor:
 | 
						|
///
 | 
						|
///   AssertionResult(const &AssertionResult other)
 | 
						|
///
 | 
						|
/// To do so, constrain the value of the newly-constructed instance's
 | 
						|
/// 'success_' field to be equal to the value of the pass-in instance's
 | 
						|
/// 'success_' field.
 | 
						|
void GTestChecker::modelAssertionResultCopyConstructor(
 | 
						|
    const CXXConstructorCall *Call, CheckerContext &C) const {
 | 
						|
  assert(Call->getNumArgs() == 1);
 | 
						|
 | 
						|
  // The first parameter of the copy constructor must be the other
 | 
						|
  // instance to initialize this instances fields from.
 | 
						|
  SVal OtherVal = Call->getArgSVal(0);
 | 
						|
  SVal ThisVal = Call->getCXXThisVal();
 | 
						|
 | 
						|
  const CXXRecordDecl *AssertResultClassDecl = Call->getDecl()->getParent();
 | 
						|
  ProgramStateRef State = C.getState();
 | 
						|
 | 
						|
  SVal ThisSuccess = getAssertionResultSuccessFieldValue(AssertResultClassDecl,
 | 
						|
                                                         ThisVal, State);
 | 
						|
  SVal OtherSuccess = getAssertionResultSuccessFieldValue(AssertResultClassDecl,
 | 
						|
                                                          OtherVal, State);
 | 
						|
 | 
						|
  State = assumeValuesEqual(ThisSuccess, OtherSuccess, State, C);
 | 
						|
  C.addTransition(State);
 | 
						|
}
 | 
						|
 | 
						|
/// Model calls to AssertionResult constructors that are not inlined.
 | 
						|
void GTestChecker::checkPostCall(const CallEvent &Call,
 | 
						|
                                 CheckerContext &C) const {
 | 
						|
  /// If the constructor was inlined, there is no need model it.
 | 
						|
  if (C.wasInlined)
 | 
						|
    return;
 | 
						|
 | 
						|
  initIdentifierInfo(C.getASTContext());
 | 
						|
 | 
						|
  auto *CtorCall = dyn_cast<CXXConstructorCall>(&Call);
 | 
						|
  if (!CtorCall)
 | 
						|
    return;
 | 
						|
 | 
						|
  const CXXConstructorDecl *CtorDecl = CtorCall->getDecl();
 | 
						|
  const CXXRecordDecl *CtorParent = CtorDecl->getParent();
 | 
						|
  if (CtorParent->getIdentifier() != AssertionResultII)
 | 
						|
    return;
 | 
						|
 | 
						|
  unsigned ParamCount = CtorDecl->getNumParams();
 | 
						|
 | 
						|
  // Call the appropriate modeling method based the parameters and their
 | 
						|
  // types.
 | 
						|
 | 
						|
  // We have AssertionResult(const &AssertionResult)
 | 
						|
  if (CtorDecl->isCopyConstructor() && ParamCount == 1) {
 | 
						|
    modelAssertionResultCopyConstructor(CtorCall, C);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  // There are two possible boolean constructors, depending on which
 | 
						|
  // version of gtest is being used:
 | 
						|
  //
 | 
						|
  // v1.7 and earlier:
 | 
						|
  //      AssertionResult(bool success)
 | 
						|
  //
 | 
						|
  // v1.8 and greater:
 | 
						|
  //      template <typename T>
 | 
						|
  //      AssertionResult(const T& success,
 | 
						|
  //                      typename internal::EnableIf<
 | 
						|
  //                          !internal::ImplicitlyConvertible<T,
 | 
						|
  //                              AssertionResult>::value>::type*)
 | 
						|
  //
 | 
						|
  CanQualType BoolTy = C.getASTContext().BoolTy;
 | 
						|
  if (ParamCount == 1 && CtorDecl->getParamDecl(0)->getType() == BoolTy) {
 | 
						|
    // We have AssertionResult(bool)
 | 
						|
    modelAssertionResultBoolConstructor(CtorCall, /*IsRef=*/false, C);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  if (ParamCount == 2){
 | 
						|
    auto *RefTy = CtorDecl->getParamDecl(0)->getType()->getAs<ReferenceType>();
 | 
						|
    if (RefTy &&
 | 
						|
        RefTy->getPointeeType()->getCanonicalTypeUnqualified() == BoolTy) {
 | 
						|
      // We have AssertionResult(bool &, ...)
 | 
						|
      modelAssertionResultBoolConstructor(CtorCall, /*IsRef=*/true, C);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void GTestChecker::initIdentifierInfo(ASTContext &Ctx) const {
 | 
						|
  if (AssertionResultII)
 | 
						|
    return;
 | 
						|
 | 
						|
  AssertionResultII = &Ctx.Idents.get("AssertionResult");
 | 
						|
  SuccessII = &Ctx.Idents.get("success_");
 | 
						|
}
 | 
						|
 | 
						|
/// Returns the value stored in the 'success_' field of the passed-in
 | 
						|
/// AssertionResult instance.
 | 
						|
SVal GTestChecker::getAssertionResultSuccessFieldValue(
 | 
						|
    const CXXRecordDecl *AssertionResultDecl, SVal Instance,
 | 
						|
    ProgramStateRef State) const {
 | 
						|
 | 
						|
  DeclContext::lookup_result Result = AssertionResultDecl->lookup(SuccessII);
 | 
						|
  if (Result.empty())
 | 
						|
    return UnknownVal();
 | 
						|
 | 
						|
  auto *SuccessField = dyn_cast<FieldDecl>(Result.front());
 | 
						|
  if (!SuccessField)
 | 
						|
    return UnknownVal();
 | 
						|
 | 
						|
  Optional<Loc> FieldLoc =
 | 
						|
      State->getLValue(SuccessField, Instance).getAs<Loc>();
 | 
						|
  if (!FieldLoc)
 | 
						|
    return UnknownVal();
 | 
						|
 | 
						|
  return State->getSVal(*FieldLoc);
 | 
						|
}
 | 
						|
 | 
						|
/// Constrain the passed-in state to assume two values are equal.
 | 
						|
ProgramStateRef GTestChecker::assumeValuesEqual(SVal Val1, SVal Val2,
 | 
						|
                                                ProgramStateRef State,
 | 
						|
                                                CheckerContext &C) {
 | 
						|
  auto DVal1 = Val1.getAs<DefinedOrUnknownSVal>();
 | 
						|
  auto DVal2 = Val2.getAs<DefinedOrUnknownSVal>();
 | 
						|
  if (!DVal1 || !DVal2)
 | 
						|
    return State;
 | 
						|
 | 
						|
  auto ValuesEqual =
 | 
						|
      C.getSValBuilder().evalEQ(State, *DVal1, *DVal2).getAs<DefinedSVal>();
 | 
						|
  if (!ValuesEqual)
 | 
						|
    return State;
 | 
						|
 | 
						|
  State = C.getConstraintManager().assume(State, *ValuesEqual, true);
 | 
						|
  return State;
 | 
						|
}
 | 
						|
 | 
						|
void ento::registerGTestChecker(CheckerManager &Mgr) {
 | 
						|
  Mgr.registerChecker<GTestChecker>();
 | 
						|
}
 | 
						|
 | 
						|
bool ento::shouldRegisterGTestChecker(const CheckerManager &mgr) {
 | 
						|
  // gtest is a C++ API so there is no sense running the checker
 | 
						|
  // if not compiling for C++.
 | 
						|
  const LangOptions &LO = mgr.getLangOpts();
 | 
						|
  return LO.CPlusPlus;
 | 
						|
}
 |