10b57cec5SDimitry Andric //==- GTestChecker.cpp - Model gtest API --*- C++ -*-==//
20b57cec5SDimitry Andric //
30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
60b57cec5SDimitry Andric //
70b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
80b57cec5SDimitry Andric //
90b57cec5SDimitry Andric // This checker models the behavior of un-inlined APIs from the gtest
100b57cec5SDimitry Andric // unit-testing library to avoid false positives when using assertions from
110b57cec5SDimitry Andric // that library.
120b57cec5SDimitry Andric //
130b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
140b57cec5SDimitry Andric
150b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
160b57cec5SDimitry Andric #include "clang/AST/Expr.h"
170b57cec5SDimitry Andric #include "clang/Basic/LangOptions.h"
180b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/Checker.h"
190b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
200b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
210b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
220b57cec5SDimitry Andric #include "llvm/Support/raw_ostream.h"
23bdd1243dSDimitry Andric #include <optional>
240b57cec5SDimitry Andric
250b57cec5SDimitry Andric using namespace clang;
260b57cec5SDimitry Andric using namespace ento;
270b57cec5SDimitry Andric
280b57cec5SDimitry Andric // Modeling of un-inlined AssertionResult constructors
290b57cec5SDimitry Andric //
300b57cec5SDimitry Andric // The gtest unit testing API provides macros for assertions that expand
310b57cec5SDimitry Andric // into an if statement that calls a series of constructors and returns
320b57cec5SDimitry Andric // when the "assertion" is false.
330b57cec5SDimitry Andric //
340b57cec5SDimitry Andric // For example,
350b57cec5SDimitry Andric //
360b57cec5SDimitry Andric // ASSERT_TRUE(a == b)
370b57cec5SDimitry Andric //
380b57cec5SDimitry Andric // expands into:
390b57cec5SDimitry Andric //
400b57cec5SDimitry Andric // switch (0)
410b57cec5SDimitry Andric // case 0:
420b57cec5SDimitry Andric // default:
430b57cec5SDimitry Andric // if (const ::testing::AssertionResult gtest_ar_ =
440b57cec5SDimitry Andric // ::testing::AssertionResult((a == b)))
450b57cec5SDimitry Andric // ;
460b57cec5SDimitry Andric // else
470b57cec5SDimitry Andric // return ::testing::internal::AssertHelper(
480b57cec5SDimitry Andric // ::testing::TestPartResult::kFatalFailure,
490b57cec5SDimitry Andric // "<path to project>",
500b57cec5SDimitry Andric // <line number>,
510b57cec5SDimitry Andric // ::testing::internal::GetBoolAssertionFailureMessage(
520b57cec5SDimitry Andric // gtest_ar_, "a == b", "false", "true")
530b57cec5SDimitry Andric // .c_str()) = ::testing::Message();
540b57cec5SDimitry Andric //
550b57cec5SDimitry Andric // where AssertionResult is defined similarly to
560b57cec5SDimitry Andric //
570b57cec5SDimitry Andric // class AssertionResult {
580b57cec5SDimitry Andric // public:
590b57cec5SDimitry Andric // AssertionResult(const AssertionResult& other);
600b57cec5SDimitry Andric // explicit AssertionResult(bool success) : success_(success) {}
610b57cec5SDimitry Andric // operator bool() const { return success_; }
620b57cec5SDimitry Andric // ...
630b57cec5SDimitry Andric // private:
640b57cec5SDimitry Andric // bool success_;
650b57cec5SDimitry Andric // };
660b57cec5SDimitry Andric //
670b57cec5SDimitry Andric // In order for the analyzer to correctly handle this assertion, it needs to
680b57cec5SDimitry Andric // know that the boolean value of the expression "a == b" is stored the
690b57cec5SDimitry Andric // 'success_' field of the original AssertionResult temporary and propagated
700b57cec5SDimitry Andric // (via the copy constructor) into the 'success_' field of the object stored
710b57cec5SDimitry Andric // in 'gtest_ar_'. That boolean value will then be returned from the bool
720b57cec5SDimitry Andric // conversion method in the if statement. This guarantees that the assertion
730b57cec5SDimitry Andric // holds when the return path is not taken.
740b57cec5SDimitry Andric //
750b57cec5SDimitry Andric // If the success value is not properly propagated, then the eager case split
760b57cec5SDimitry Andric // on evaluating the expression can cause pernicious false positives
770b57cec5SDimitry Andric // on the non-return path:
780b57cec5SDimitry Andric //
790b57cec5SDimitry Andric // ASSERT(ptr != NULL)
800b57cec5SDimitry Andric // *ptr = 7; // False positive null pointer dereference here
810b57cec5SDimitry Andric //
820b57cec5SDimitry Andric // Unfortunately, the bool constructor cannot be inlined (because its
830b57cec5SDimitry Andric // implementation is not present in the headers) and the copy constructor is
840b57cec5SDimitry Andric // not inlined (because it is constructed into a temporary and the analyzer
850b57cec5SDimitry Andric // does not inline these since it does not yet reliably call temporary
860b57cec5SDimitry Andric // destructors).
870b57cec5SDimitry Andric //
880b57cec5SDimitry Andric // This checker compensates for the missing inlining by propagating the
890b57cec5SDimitry Andric // _success value across the bool and copy constructors so the assertion behaves
900b57cec5SDimitry Andric // as expected.
910b57cec5SDimitry Andric
920b57cec5SDimitry Andric namespace {
930b57cec5SDimitry Andric class GTestChecker : public Checker<check::PostCall> {
940b57cec5SDimitry Andric
955f757f3fSDimitry Andric mutable IdentifierInfo *AssertionResultII = nullptr;
965f757f3fSDimitry Andric mutable IdentifierInfo *SuccessII = nullptr;
970b57cec5SDimitry Andric
980b57cec5SDimitry Andric public:
995f757f3fSDimitry Andric GTestChecker() = default;
1000b57cec5SDimitry Andric
1010b57cec5SDimitry Andric void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
1020b57cec5SDimitry Andric
1030b57cec5SDimitry Andric private:
1040b57cec5SDimitry Andric void modelAssertionResultBoolConstructor(const CXXConstructorCall *Call,
1050b57cec5SDimitry Andric bool IsRef, CheckerContext &C) const;
1060b57cec5SDimitry Andric
1070b57cec5SDimitry Andric void modelAssertionResultCopyConstructor(const CXXConstructorCall *Call,
1080b57cec5SDimitry Andric CheckerContext &C) const;
1090b57cec5SDimitry Andric
1100b57cec5SDimitry Andric void initIdentifierInfo(ASTContext &Ctx) const;
1110b57cec5SDimitry Andric
1120b57cec5SDimitry Andric SVal
1130b57cec5SDimitry Andric getAssertionResultSuccessFieldValue(const CXXRecordDecl *AssertionResultDecl,
1140b57cec5SDimitry Andric SVal Instance,
1150b57cec5SDimitry Andric ProgramStateRef State) const;
1160b57cec5SDimitry Andric
1170b57cec5SDimitry Andric static ProgramStateRef assumeValuesEqual(SVal Val1, SVal Val2,
1180b57cec5SDimitry Andric ProgramStateRef State,
1190b57cec5SDimitry Andric CheckerContext &C);
1200b57cec5SDimitry Andric };
1210b57cec5SDimitry Andric } // End anonymous namespace.
1220b57cec5SDimitry Andric
1230b57cec5SDimitry Andric /// Model a call to an un-inlined AssertionResult(bool) or
1240b57cec5SDimitry Andric /// AssertionResult(bool &, ...).
1250b57cec5SDimitry Andric /// To do so, constrain the value of the newly-constructed instance's 'success_'
1260b57cec5SDimitry Andric /// field to be equal to the passed-in boolean value.
1270b57cec5SDimitry Andric ///
1280b57cec5SDimitry Andric /// \param IsRef Whether the boolean parameter is a reference or not.
modelAssertionResultBoolConstructor(const CXXConstructorCall * Call,bool IsRef,CheckerContext & C) const1290b57cec5SDimitry Andric void GTestChecker::modelAssertionResultBoolConstructor(
1300b57cec5SDimitry Andric const CXXConstructorCall *Call, bool IsRef, CheckerContext &C) const {
1310b57cec5SDimitry Andric assert(Call->getNumArgs() >= 1 && Call->getNumArgs() <= 2);
1320b57cec5SDimitry Andric
1330b57cec5SDimitry Andric ProgramStateRef State = C.getState();
1340b57cec5SDimitry Andric SVal BooleanArgVal = Call->getArgSVal(0);
1350b57cec5SDimitry Andric if (IsRef) {
1360b57cec5SDimitry Andric // The argument is a reference, so load from it to get the boolean value.
13781ad6265SDimitry Andric if (!isa<Loc>(BooleanArgVal))
1380b57cec5SDimitry Andric return;
1390b57cec5SDimitry Andric BooleanArgVal = C.getState()->getSVal(BooleanArgVal.castAs<Loc>());
1400b57cec5SDimitry Andric }
1410b57cec5SDimitry Andric
1420b57cec5SDimitry Andric SVal ThisVal = Call->getCXXThisVal();
1430b57cec5SDimitry Andric
1440b57cec5SDimitry Andric SVal ThisSuccess = getAssertionResultSuccessFieldValue(
1450b57cec5SDimitry Andric Call->getDecl()->getParent(), ThisVal, State);
1460b57cec5SDimitry Andric
1470b57cec5SDimitry Andric State = assumeValuesEqual(ThisSuccess, BooleanArgVal, State, C);
1480b57cec5SDimitry Andric C.addTransition(State);
1490b57cec5SDimitry Andric }
1500b57cec5SDimitry Andric
1510b57cec5SDimitry Andric /// Model a call to an un-inlined AssertionResult copy constructor:
1520b57cec5SDimitry Andric ///
1530b57cec5SDimitry Andric /// AssertionResult(const &AssertionResult other)
1540b57cec5SDimitry Andric ///
1550b57cec5SDimitry Andric /// To do so, constrain the value of the newly-constructed instance's
1560b57cec5SDimitry Andric /// 'success_' field to be equal to the value of the pass-in instance's
1570b57cec5SDimitry Andric /// 'success_' field.
modelAssertionResultCopyConstructor(const CXXConstructorCall * Call,CheckerContext & C) const1580b57cec5SDimitry Andric void GTestChecker::modelAssertionResultCopyConstructor(
1590b57cec5SDimitry Andric const CXXConstructorCall *Call, CheckerContext &C) const {
1600b57cec5SDimitry Andric assert(Call->getNumArgs() == 1);
1610b57cec5SDimitry Andric
1620b57cec5SDimitry Andric // The first parameter of the copy constructor must be the other
1630b57cec5SDimitry Andric // instance to initialize this instances fields from.
1640b57cec5SDimitry Andric SVal OtherVal = Call->getArgSVal(0);
1650b57cec5SDimitry Andric SVal ThisVal = Call->getCXXThisVal();
1660b57cec5SDimitry Andric
1670b57cec5SDimitry Andric const CXXRecordDecl *AssertResultClassDecl = Call->getDecl()->getParent();
1680b57cec5SDimitry Andric ProgramStateRef State = C.getState();
1690b57cec5SDimitry Andric
1700b57cec5SDimitry Andric SVal ThisSuccess = getAssertionResultSuccessFieldValue(AssertResultClassDecl,
1710b57cec5SDimitry Andric ThisVal, State);
1720b57cec5SDimitry Andric SVal OtherSuccess = getAssertionResultSuccessFieldValue(AssertResultClassDecl,
1730b57cec5SDimitry Andric OtherVal, State);
1740b57cec5SDimitry Andric
1750b57cec5SDimitry Andric State = assumeValuesEqual(ThisSuccess, OtherSuccess, State, C);
1760b57cec5SDimitry Andric C.addTransition(State);
1770b57cec5SDimitry Andric }
1780b57cec5SDimitry Andric
1790b57cec5SDimitry Andric /// Model calls to AssertionResult constructors that are not inlined.
checkPostCall(const CallEvent & Call,CheckerContext & C) const1800b57cec5SDimitry Andric void GTestChecker::checkPostCall(const CallEvent &Call,
1810b57cec5SDimitry Andric CheckerContext &C) const {
1820b57cec5SDimitry Andric /// If the constructor was inlined, there is no need model it.
1830b57cec5SDimitry Andric if (C.wasInlined)
1840b57cec5SDimitry Andric return;
1850b57cec5SDimitry Andric
1860b57cec5SDimitry Andric initIdentifierInfo(C.getASTContext());
1870b57cec5SDimitry Andric
1880b57cec5SDimitry Andric auto *CtorCall = dyn_cast<CXXConstructorCall>(&Call);
1890b57cec5SDimitry Andric if (!CtorCall)
1900b57cec5SDimitry Andric return;
1910b57cec5SDimitry Andric
1920b57cec5SDimitry Andric const CXXConstructorDecl *CtorDecl = CtorCall->getDecl();
1930b57cec5SDimitry Andric const CXXRecordDecl *CtorParent = CtorDecl->getParent();
1940b57cec5SDimitry Andric if (CtorParent->getIdentifier() != AssertionResultII)
1950b57cec5SDimitry Andric return;
1960b57cec5SDimitry Andric
1970b57cec5SDimitry Andric unsigned ParamCount = CtorDecl->getNumParams();
1980b57cec5SDimitry Andric
1990b57cec5SDimitry Andric // Call the appropriate modeling method based the parameters and their
2000b57cec5SDimitry Andric // types.
2010b57cec5SDimitry Andric
2020b57cec5SDimitry Andric // We have AssertionResult(const &AssertionResult)
2030b57cec5SDimitry Andric if (CtorDecl->isCopyConstructor() && ParamCount == 1) {
2040b57cec5SDimitry Andric modelAssertionResultCopyConstructor(CtorCall, C);
2050b57cec5SDimitry Andric return;
2060b57cec5SDimitry Andric }
2070b57cec5SDimitry Andric
2080b57cec5SDimitry Andric // There are two possible boolean constructors, depending on which
2090b57cec5SDimitry Andric // version of gtest is being used:
2100b57cec5SDimitry Andric //
2110b57cec5SDimitry Andric // v1.7 and earlier:
2120b57cec5SDimitry Andric // AssertionResult(bool success)
2130b57cec5SDimitry Andric //
2140b57cec5SDimitry Andric // v1.8 and greater:
2150b57cec5SDimitry Andric // template <typename T>
2160b57cec5SDimitry Andric // AssertionResult(const T& success,
2170b57cec5SDimitry Andric // typename internal::EnableIf<
2180b57cec5SDimitry Andric // !internal::ImplicitlyConvertible<T,
2190b57cec5SDimitry Andric // AssertionResult>::value>::type*)
2200b57cec5SDimitry Andric //
2210b57cec5SDimitry Andric CanQualType BoolTy = C.getASTContext().BoolTy;
2220b57cec5SDimitry Andric if (ParamCount == 1 && CtorDecl->getParamDecl(0)->getType() == BoolTy) {
2230b57cec5SDimitry Andric // We have AssertionResult(bool)
2240b57cec5SDimitry Andric modelAssertionResultBoolConstructor(CtorCall, /*IsRef=*/false, C);
2250b57cec5SDimitry Andric return;
2260b57cec5SDimitry Andric }
2270b57cec5SDimitry Andric if (ParamCount == 2){
2280b57cec5SDimitry Andric auto *RefTy = CtorDecl->getParamDecl(0)->getType()->getAs<ReferenceType>();
2290b57cec5SDimitry Andric if (RefTy &&
2300b57cec5SDimitry Andric RefTy->getPointeeType()->getCanonicalTypeUnqualified() == BoolTy) {
2310b57cec5SDimitry Andric // We have AssertionResult(bool &, ...)
2320b57cec5SDimitry Andric modelAssertionResultBoolConstructor(CtorCall, /*IsRef=*/true, C);
2330b57cec5SDimitry Andric return;
2340b57cec5SDimitry Andric }
2350b57cec5SDimitry Andric }
2360b57cec5SDimitry Andric }
2370b57cec5SDimitry Andric
initIdentifierInfo(ASTContext & Ctx) const2380b57cec5SDimitry Andric void GTestChecker::initIdentifierInfo(ASTContext &Ctx) const {
2390b57cec5SDimitry Andric if (AssertionResultII)
2400b57cec5SDimitry Andric return;
2410b57cec5SDimitry Andric
2420b57cec5SDimitry Andric AssertionResultII = &Ctx.Idents.get("AssertionResult");
2430b57cec5SDimitry Andric SuccessII = &Ctx.Idents.get("success_");
2440b57cec5SDimitry Andric }
2450b57cec5SDimitry Andric
2460b57cec5SDimitry Andric /// Returns the value stored in the 'success_' field of the passed-in
2470b57cec5SDimitry Andric /// AssertionResult instance.
getAssertionResultSuccessFieldValue(const CXXRecordDecl * AssertionResultDecl,SVal Instance,ProgramStateRef State) const2480b57cec5SDimitry Andric SVal GTestChecker::getAssertionResultSuccessFieldValue(
2490b57cec5SDimitry Andric const CXXRecordDecl *AssertionResultDecl, SVal Instance,
2500b57cec5SDimitry Andric ProgramStateRef State) const {
2510b57cec5SDimitry Andric
2520b57cec5SDimitry Andric DeclContext::lookup_result Result = AssertionResultDecl->lookup(SuccessII);
2530b57cec5SDimitry Andric if (Result.empty())
2540b57cec5SDimitry Andric return UnknownVal();
2550b57cec5SDimitry Andric
2560b57cec5SDimitry Andric auto *SuccessField = dyn_cast<FieldDecl>(Result.front());
2570b57cec5SDimitry Andric if (!SuccessField)
2580b57cec5SDimitry Andric return UnknownVal();
2590b57cec5SDimitry Andric
260bdd1243dSDimitry Andric std::optional<Loc> FieldLoc =
2610b57cec5SDimitry Andric State->getLValue(SuccessField, Instance).getAs<Loc>();
26281ad6265SDimitry Andric if (!FieldLoc)
2630b57cec5SDimitry Andric return UnknownVal();
2640b57cec5SDimitry Andric
2650b57cec5SDimitry Andric return State->getSVal(*FieldLoc);
2660b57cec5SDimitry Andric }
2670b57cec5SDimitry Andric
2680b57cec5SDimitry Andric /// Constrain the passed-in state to assume two values are equal.
assumeValuesEqual(SVal Val1,SVal Val2,ProgramStateRef State,CheckerContext & C)2690b57cec5SDimitry Andric ProgramStateRef GTestChecker::assumeValuesEqual(SVal Val1, SVal Val2,
2700b57cec5SDimitry Andric ProgramStateRef State,
2710b57cec5SDimitry Andric CheckerContext &C) {
27281ad6265SDimitry Andric auto DVal1 = Val1.getAs<DefinedOrUnknownSVal>();
27381ad6265SDimitry Andric auto DVal2 = Val2.getAs<DefinedOrUnknownSVal>();
27481ad6265SDimitry Andric if (!DVal1 || !DVal2)
2750b57cec5SDimitry Andric return State;
2760b57cec5SDimitry Andric
2770b57cec5SDimitry Andric auto ValuesEqual =
27881ad6265SDimitry Andric C.getSValBuilder().evalEQ(State, *DVal1, *DVal2).getAs<DefinedSVal>();
27981ad6265SDimitry Andric if (!ValuesEqual)
2800b57cec5SDimitry Andric return State;
2810b57cec5SDimitry Andric
28281ad6265SDimitry Andric State = C.getConstraintManager().assume(State, *ValuesEqual, true);
2830b57cec5SDimitry Andric return State;
2840b57cec5SDimitry Andric }
2850b57cec5SDimitry Andric
registerGTestChecker(CheckerManager & Mgr)2860b57cec5SDimitry Andric void ento::registerGTestChecker(CheckerManager &Mgr) {
2870b57cec5SDimitry Andric Mgr.registerChecker<GTestChecker>();
2880b57cec5SDimitry Andric }
2890b57cec5SDimitry Andric
shouldRegisterGTestChecker(const CheckerManager & mgr)2905ffd83dbSDimitry Andric bool ento::shouldRegisterGTestChecker(const CheckerManager &mgr) {
2910b57cec5SDimitry Andric // gtest is a C++ API so there is no sense running the checker
2920b57cec5SDimitry Andric // if not compiling for C++.
2935ffd83dbSDimitry Andric const LangOptions &LO = mgr.getLangOpts();
2940b57cec5SDimitry Andric return LO.CPlusPlus;
2950b57cec5SDimitry Andric }
296