123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299 |
- //==- 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 (!BooleanArgVal.getAs<Loc>())
- 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.hasValue())
- 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) {
- if (!Val1.getAs<DefinedOrUnknownSVal>() ||
- !Val2.getAs<DefinedOrUnknownSVal>())
- return State;
- auto ValuesEqual =
- C.getSValBuilder().evalEQ(State, Val1.castAs<DefinedOrUnknownSVal>(),
- Val2.castAs<DefinedOrUnknownSVal>());
- if (!ValuesEqual.getAs<DefinedSVal>())
- return State;
- State = C.getConstraintManager().assume(
- State, ValuesEqual.castAs<DefinedSVal>(), 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;
- }
|