17330f729Sjoerg //==- GTestChecker.cpp - Model gtest API --*- C++ -*-==//
27330f729Sjoerg //
37330f729Sjoerg // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
47330f729Sjoerg // See https://llvm.org/LICENSE.txt for license information.
57330f729Sjoerg // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
67330f729Sjoerg //
77330f729Sjoerg //===----------------------------------------------------------------------===//
87330f729Sjoerg //
97330f729Sjoerg // This checker models the behavior of un-inlined APIs from the gtest
107330f729Sjoerg // unit-testing library to avoid false positives when using assertions from
117330f729Sjoerg // that library.
127330f729Sjoerg //
137330f729Sjoerg //===----------------------------------------------------------------------===//
147330f729Sjoerg
157330f729Sjoerg #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
167330f729Sjoerg #include "clang/AST/Expr.h"
177330f729Sjoerg #include "clang/Basic/LangOptions.h"
187330f729Sjoerg #include "clang/StaticAnalyzer/Core/Checker.h"
197330f729Sjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
207330f729Sjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
217330f729Sjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
227330f729Sjoerg #include "llvm/Support/raw_ostream.h"
237330f729Sjoerg
247330f729Sjoerg using namespace clang;
257330f729Sjoerg using namespace ento;
267330f729Sjoerg
277330f729Sjoerg // Modeling of un-inlined AssertionResult constructors
287330f729Sjoerg //
297330f729Sjoerg // The gtest unit testing API provides macros for assertions that expand
307330f729Sjoerg // into an if statement that calls a series of constructors and returns
317330f729Sjoerg // when the "assertion" is false.
327330f729Sjoerg //
337330f729Sjoerg // For example,
347330f729Sjoerg //
357330f729Sjoerg // ASSERT_TRUE(a == b)
367330f729Sjoerg //
377330f729Sjoerg // expands into:
387330f729Sjoerg //
397330f729Sjoerg // switch (0)
407330f729Sjoerg // case 0:
417330f729Sjoerg // default:
427330f729Sjoerg // if (const ::testing::AssertionResult gtest_ar_ =
437330f729Sjoerg // ::testing::AssertionResult((a == b)))
447330f729Sjoerg // ;
457330f729Sjoerg // else
467330f729Sjoerg // return ::testing::internal::AssertHelper(
477330f729Sjoerg // ::testing::TestPartResult::kFatalFailure,
487330f729Sjoerg // "<path to project>",
497330f729Sjoerg // <line number>,
507330f729Sjoerg // ::testing::internal::GetBoolAssertionFailureMessage(
517330f729Sjoerg // gtest_ar_, "a == b", "false", "true")
527330f729Sjoerg // .c_str()) = ::testing::Message();
537330f729Sjoerg //
547330f729Sjoerg // where AssertionResult is defined similarly to
557330f729Sjoerg //
567330f729Sjoerg // class AssertionResult {
577330f729Sjoerg // public:
587330f729Sjoerg // AssertionResult(const AssertionResult& other);
597330f729Sjoerg // explicit AssertionResult(bool success) : success_(success) {}
607330f729Sjoerg // operator bool() const { return success_; }
617330f729Sjoerg // ...
627330f729Sjoerg // private:
637330f729Sjoerg // bool success_;
647330f729Sjoerg // };
657330f729Sjoerg //
667330f729Sjoerg // In order for the analyzer to correctly handle this assertion, it needs to
677330f729Sjoerg // know that the boolean value of the expression "a == b" is stored the
687330f729Sjoerg // 'success_' field of the original AssertionResult temporary and propagated
697330f729Sjoerg // (via the copy constructor) into the 'success_' field of the object stored
707330f729Sjoerg // in 'gtest_ar_'. That boolean value will then be returned from the bool
717330f729Sjoerg // conversion method in the if statement. This guarantees that the assertion
727330f729Sjoerg // holds when the return path is not taken.
737330f729Sjoerg //
747330f729Sjoerg // If the success value is not properly propagated, then the eager case split
757330f729Sjoerg // on evaluating the expression can cause pernicious false positives
767330f729Sjoerg // on the non-return path:
777330f729Sjoerg //
787330f729Sjoerg // ASSERT(ptr != NULL)
797330f729Sjoerg // *ptr = 7; // False positive null pointer dereference here
807330f729Sjoerg //
817330f729Sjoerg // Unfortunately, the bool constructor cannot be inlined (because its
827330f729Sjoerg // implementation is not present in the headers) and the copy constructor is
837330f729Sjoerg // not inlined (because it is constructed into a temporary and the analyzer
847330f729Sjoerg // does not inline these since it does not yet reliably call temporary
857330f729Sjoerg // destructors).
867330f729Sjoerg //
877330f729Sjoerg // This checker compensates for the missing inlining by propagating the
887330f729Sjoerg // _success value across the bool and copy constructors so the assertion behaves
897330f729Sjoerg // as expected.
907330f729Sjoerg
917330f729Sjoerg namespace {
927330f729Sjoerg class GTestChecker : public Checker<check::PostCall> {
937330f729Sjoerg
947330f729Sjoerg mutable IdentifierInfo *AssertionResultII;
957330f729Sjoerg mutable IdentifierInfo *SuccessII;
967330f729Sjoerg
977330f729Sjoerg public:
987330f729Sjoerg GTestChecker();
997330f729Sjoerg
1007330f729Sjoerg void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
1017330f729Sjoerg
1027330f729Sjoerg private:
1037330f729Sjoerg void modelAssertionResultBoolConstructor(const CXXConstructorCall *Call,
1047330f729Sjoerg bool IsRef, CheckerContext &C) const;
1057330f729Sjoerg
1067330f729Sjoerg void modelAssertionResultCopyConstructor(const CXXConstructorCall *Call,
1077330f729Sjoerg CheckerContext &C) const;
1087330f729Sjoerg
1097330f729Sjoerg void initIdentifierInfo(ASTContext &Ctx) const;
1107330f729Sjoerg
1117330f729Sjoerg SVal
1127330f729Sjoerg getAssertionResultSuccessFieldValue(const CXXRecordDecl *AssertionResultDecl,
1137330f729Sjoerg SVal Instance,
1147330f729Sjoerg ProgramStateRef State) const;
1157330f729Sjoerg
1167330f729Sjoerg static ProgramStateRef assumeValuesEqual(SVal Val1, SVal Val2,
1177330f729Sjoerg ProgramStateRef State,
1187330f729Sjoerg CheckerContext &C);
1197330f729Sjoerg };
1207330f729Sjoerg } // End anonymous namespace.
1217330f729Sjoerg
GTestChecker()1227330f729Sjoerg GTestChecker::GTestChecker() : AssertionResultII(nullptr), SuccessII(nullptr) {}
1237330f729Sjoerg
1247330f729Sjoerg /// Model a call to an un-inlined AssertionResult(bool) or
1257330f729Sjoerg /// AssertionResult(bool &, ...).
1267330f729Sjoerg /// To do so, constrain the value of the newly-constructed instance's 'success_'
1277330f729Sjoerg /// field to be equal to the passed-in boolean value.
1287330f729Sjoerg ///
1297330f729Sjoerg /// \param IsRef Whether the boolean parameter is a reference or not.
modelAssertionResultBoolConstructor(const CXXConstructorCall * Call,bool IsRef,CheckerContext & C) const1307330f729Sjoerg void GTestChecker::modelAssertionResultBoolConstructor(
1317330f729Sjoerg const CXXConstructorCall *Call, bool IsRef, CheckerContext &C) const {
1327330f729Sjoerg assert(Call->getNumArgs() >= 1 && Call->getNumArgs() <= 2);
1337330f729Sjoerg
1347330f729Sjoerg ProgramStateRef State = C.getState();
1357330f729Sjoerg SVal BooleanArgVal = Call->getArgSVal(0);
1367330f729Sjoerg if (IsRef) {
1377330f729Sjoerg // The argument is a reference, so load from it to get the boolean value.
1387330f729Sjoerg if (!BooleanArgVal.getAs<Loc>())
1397330f729Sjoerg return;
1407330f729Sjoerg BooleanArgVal = C.getState()->getSVal(BooleanArgVal.castAs<Loc>());
1417330f729Sjoerg }
1427330f729Sjoerg
1437330f729Sjoerg SVal ThisVal = Call->getCXXThisVal();
1447330f729Sjoerg
1457330f729Sjoerg SVal ThisSuccess = getAssertionResultSuccessFieldValue(
1467330f729Sjoerg Call->getDecl()->getParent(), ThisVal, State);
1477330f729Sjoerg
1487330f729Sjoerg State = assumeValuesEqual(ThisSuccess, BooleanArgVal, State, C);
1497330f729Sjoerg C.addTransition(State);
1507330f729Sjoerg }
1517330f729Sjoerg
1527330f729Sjoerg /// Model a call to an un-inlined AssertionResult copy constructor:
1537330f729Sjoerg ///
1547330f729Sjoerg /// AssertionResult(const &AssertionResult other)
1557330f729Sjoerg ///
1567330f729Sjoerg /// To do so, constrain the value of the newly-constructed instance's
1577330f729Sjoerg /// 'success_' field to be equal to the value of the pass-in instance's
1587330f729Sjoerg /// 'success_' field.
modelAssertionResultCopyConstructor(const CXXConstructorCall * Call,CheckerContext & C) const1597330f729Sjoerg void GTestChecker::modelAssertionResultCopyConstructor(
1607330f729Sjoerg const CXXConstructorCall *Call, CheckerContext &C) const {
1617330f729Sjoerg assert(Call->getNumArgs() == 1);
1627330f729Sjoerg
1637330f729Sjoerg // The first parameter of the copy constructor must be the other
1647330f729Sjoerg // instance to initialize this instances fields from.
1657330f729Sjoerg SVal OtherVal = Call->getArgSVal(0);
1667330f729Sjoerg SVal ThisVal = Call->getCXXThisVal();
1677330f729Sjoerg
1687330f729Sjoerg const CXXRecordDecl *AssertResultClassDecl = Call->getDecl()->getParent();
1697330f729Sjoerg ProgramStateRef State = C.getState();
1707330f729Sjoerg
1717330f729Sjoerg SVal ThisSuccess = getAssertionResultSuccessFieldValue(AssertResultClassDecl,
1727330f729Sjoerg ThisVal, State);
1737330f729Sjoerg SVal OtherSuccess = getAssertionResultSuccessFieldValue(AssertResultClassDecl,
1747330f729Sjoerg OtherVal, State);
1757330f729Sjoerg
1767330f729Sjoerg State = assumeValuesEqual(ThisSuccess, OtherSuccess, State, C);
1777330f729Sjoerg C.addTransition(State);
1787330f729Sjoerg }
1797330f729Sjoerg
1807330f729Sjoerg /// Model calls to AssertionResult constructors that are not inlined.
checkPostCall(const CallEvent & Call,CheckerContext & C) const1817330f729Sjoerg void GTestChecker::checkPostCall(const CallEvent &Call,
1827330f729Sjoerg CheckerContext &C) const {
1837330f729Sjoerg /// If the constructor was inlined, there is no need model it.
1847330f729Sjoerg if (C.wasInlined)
1857330f729Sjoerg return;
1867330f729Sjoerg
1877330f729Sjoerg initIdentifierInfo(C.getASTContext());
1887330f729Sjoerg
1897330f729Sjoerg auto *CtorCall = dyn_cast<CXXConstructorCall>(&Call);
1907330f729Sjoerg if (!CtorCall)
1917330f729Sjoerg return;
1927330f729Sjoerg
1937330f729Sjoerg const CXXConstructorDecl *CtorDecl = CtorCall->getDecl();
1947330f729Sjoerg const CXXRecordDecl *CtorParent = CtorDecl->getParent();
1957330f729Sjoerg if (CtorParent->getIdentifier() != AssertionResultII)
1967330f729Sjoerg return;
1977330f729Sjoerg
1987330f729Sjoerg unsigned ParamCount = CtorDecl->getNumParams();
1997330f729Sjoerg
2007330f729Sjoerg // Call the appropriate modeling method based the parameters and their
2017330f729Sjoerg // types.
2027330f729Sjoerg
2037330f729Sjoerg // We have AssertionResult(const &AssertionResult)
2047330f729Sjoerg if (CtorDecl->isCopyConstructor() && ParamCount == 1) {
2057330f729Sjoerg modelAssertionResultCopyConstructor(CtorCall, C);
2067330f729Sjoerg return;
2077330f729Sjoerg }
2087330f729Sjoerg
2097330f729Sjoerg // There are two possible boolean constructors, depending on which
2107330f729Sjoerg // version of gtest is being used:
2117330f729Sjoerg //
2127330f729Sjoerg // v1.7 and earlier:
2137330f729Sjoerg // AssertionResult(bool success)
2147330f729Sjoerg //
2157330f729Sjoerg // v1.8 and greater:
2167330f729Sjoerg // template <typename T>
2177330f729Sjoerg // AssertionResult(const T& success,
2187330f729Sjoerg // typename internal::EnableIf<
2197330f729Sjoerg // !internal::ImplicitlyConvertible<T,
2207330f729Sjoerg // AssertionResult>::value>::type*)
2217330f729Sjoerg //
2227330f729Sjoerg CanQualType BoolTy = C.getASTContext().BoolTy;
2237330f729Sjoerg if (ParamCount == 1 && CtorDecl->getParamDecl(0)->getType() == BoolTy) {
2247330f729Sjoerg // We have AssertionResult(bool)
2257330f729Sjoerg modelAssertionResultBoolConstructor(CtorCall, /*IsRef=*/false, C);
2267330f729Sjoerg return;
2277330f729Sjoerg }
2287330f729Sjoerg if (ParamCount == 2){
2297330f729Sjoerg auto *RefTy = CtorDecl->getParamDecl(0)->getType()->getAs<ReferenceType>();
2307330f729Sjoerg if (RefTy &&
2317330f729Sjoerg RefTy->getPointeeType()->getCanonicalTypeUnqualified() == BoolTy) {
2327330f729Sjoerg // We have AssertionResult(bool &, ...)
2337330f729Sjoerg modelAssertionResultBoolConstructor(CtorCall, /*IsRef=*/true, C);
2347330f729Sjoerg return;
2357330f729Sjoerg }
2367330f729Sjoerg }
2377330f729Sjoerg }
2387330f729Sjoerg
initIdentifierInfo(ASTContext & Ctx) const2397330f729Sjoerg void GTestChecker::initIdentifierInfo(ASTContext &Ctx) const {
2407330f729Sjoerg if (AssertionResultII)
2417330f729Sjoerg return;
2427330f729Sjoerg
2437330f729Sjoerg AssertionResultII = &Ctx.Idents.get("AssertionResult");
2447330f729Sjoerg SuccessII = &Ctx.Idents.get("success_");
2457330f729Sjoerg }
2467330f729Sjoerg
2477330f729Sjoerg /// Returns the value stored in the 'success_' field of the passed-in
2487330f729Sjoerg /// AssertionResult instance.
getAssertionResultSuccessFieldValue(const CXXRecordDecl * AssertionResultDecl,SVal Instance,ProgramStateRef State) const2497330f729Sjoerg SVal GTestChecker::getAssertionResultSuccessFieldValue(
2507330f729Sjoerg const CXXRecordDecl *AssertionResultDecl, SVal Instance,
2517330f729Sjoerg ProgramStateRef State) const {
2527330f729Sjoerg
2537330f729Sjoerg DeclContext::lookup_result Result = AssertionResultDecl->lookup(SuccessII);
2547330f729Sjoerg if (Result.empty())
2557330f729Sjoerg return UnknownVal();
2567330f729Sjoerg
2577330f729Sjoerg auto *SuccessField = dyn_cast<FieldDecl>(Result.front());
2587330f729Sjoerg if (!SuccessField)
2597330f729Sjoerg return UnknownVal();
2607330f729Sjoerg
2617330f729Sjoerg Optional<Loc> FieldLoc =
2627330f729Sjoerg State->getLValue(SuccessField, Instance).getAs<Loc>();
2637330f729Sjoerg if (!FieldLoc.hasValue())
2647330f729Sjoerg return UnknownVal();
2657330f729Sjoerg
2667330f729Sjoerg return State->getSVal(*FieldLoc);
2677330f729Sjoerg }
2687330f729Sjoerg
2697330f729Sjoerg /// Constrain the passed-in state to assume two values are equal.
assumeValuesEqual(SVal Val1,SVal Val2,ProgramStateRef State,CheckerContext & C)2707330f729Sjoerg ProgramStateRef GTestChecker::assumeValuesEqual(SVal Val1, SVal Val2,
2717330f729Sjoerg ProgramStateRef State,
2727330f729Sjoerg CheckerContext &C) {
2737330f729Sjoerg if (!Val1.getAs<DefinedOrUnknownSVal>() ||
2747330f729Sjoerg !Val2.getAs<DefinedOrUnknownSVal>())
2757330f729Sjoerg return State;
2767330f729Sjoerg
2777330f729Sjoerg auto ValuesEqual =
2787330f729Sjoerg C.getSValBuilder().evalEQ(State, Val1.castAs<DefinedOrUnknownSVal>(),
2797330f729Sjoerg Val2.castAs<DefinedOrUnknownSVal>());
2807330f729Sjoerg
2817330f729Sjoerg if (!ValuesEqual.getAs<DefinedSVal>())
2827330f729Sjoerg return State;
2837330f729Sjoerg
2847330f729Sjoerg State = C.getConstraintManager().assume(
2857330f729Sjoerg State, ValuesEqual.castAs<DefinedSVal>(), true);
2867330f729Sjoerg
2877330f729Sjoerg return State;
2887330f729Sjoerg }
2897330f729Sjoerg
registerGTestChecker(CheckerManager & Mgr)2907330f729Sjoerg void ento::registerGTestChecker(CheckerManager &Mgr) {
2917330f729Sjoerg Mgr.registerChecker<GTestChecker>();
2927330f729Sjoerg }
2937330f729Sjoerg
shouldRegisterGTestChecker(const CheckerManager & mgr)294*e038c9c4Sjoerg bool ento::shouldRegisterGTestChecker(const CheckerManager &mgr) {
2957330f729Sjoerg // gtest is a C++ API so there is no sense running the checker
2967330f729Sjoerg // if not compiling for C++.
297*e038c9c4Sjoerg const LangOptions &LO = mgr.getLangOpts();
2987330f729Sjoerg return LO.CPlusPlus;
2997330f729Sjoerg }
300