Path: blob/main/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/GTestChecker.cpp
35266 views
//==- GTestChecker.cpp - Model gtest API --*- C++ -*-==//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7//8// This checker models the behavior of un-inlined APIs from the gtest9// unit-testing library to avoid false positives when using assertions from10// that library.11//12//===----------------------------------------------------------------------===//1314#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"15#include "clang/AST/Expr.h"16#include "clang/Basic/LangOptions.h"17#include "clang/StaticAnalyzer/Core/Checker.h"18#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"19#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"20#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"21#include "llvm/Support/raw_ostream.h"22#include <optional>2324using namespace clang;25using namespace ento;2627// Modeling of un-inlined AssertionResult constructors28//29// The gtest unit testing API provides macros for assertions that expand30// into an if statement that calls a series of constructors and returns31// when the "assertion" is false.32//33// For example,34//35// ASSERT_TRUE(a == b)36//37// expands into:38//39// switch (0)40// case 0:41// default:42// if (const ::testing::AssertionResult gtest_ar_ =43// ::testing::AssertionResult((a == b)))44// ;45// else46// return ::testing::internal::AssertHelper(47// ::testing::TestPartResult::kFatalFailure,48// "<path to project>",49// <line number>,50// ::testing::internal::GetBoolAssertionFailureMessage(51// gtest_ar_, "a == b", "false", "true")52// .c_str()) = ::testing::Message();53//54// where AssertionResult is defined similarly to55//56// class AssertionResult {57// public:58// AssertionResult(const AssertionResult& other);59// explicit AssertionResult(bool success) : success_(success) {}60// operator bool() const { return success_; }61// ...62// private:63// bool success_;64// };65//66// In order for the analyzer to correctly handle this assertion, it needs to67// know that the boolean value of the expression "a == b" is stored the68// 'success_' field of the original AssertionResult temporary and propagated69// (via the copy constructor) into the 'success_' field of the object stored70// in 'gtest_ar_'. That boolean value will then be returned from the bool71// conversion method in the if statement. This guarantees that the assertion72// holds when the return path is not taken.73//74// If the success value is not properly propagated, then the eager case split75// on evaluating the expression can cause pernicious false positives76// on the non-return path:77//78// ASSERT(ptr != NULL)79// *ptr = 7; // False positive null pointer dereference here80//81// Unfortunately, the bool constructor cannot be inlined (because its82// implementation is not present in the headers) and the copy constructor is83// not inlined (because it is constructed into a temporary and the analyzer84// does not inline these since it does not yet reliably call temporary85// destructors).86//87// This checker compensates for the missing inlining by propagating the88// _success value across the bool and copy constructors so the assertion behaves89// as expected.9091namespace {92class GTestChecker : public Checker<check::PostCall> {9394mutable IdentifierInfo *AssertionResultII = nullptr;95mutable IdentifierInfo *SuccessII = nullptr;9697public:98GTestChecker() = default;99100void checkPostCall(const CallEvent &Call, CheckerContext &C) const;101102private:103void modelAssertionResultBoolConstructor(const CXXConstructorCall *Call,104bool IsRef, CheckerContext &C) const;105106void modelAssertionResultCopyConstructor(const CXXConstructorCall *Call,107CheckerContext &C) const;108109void initIdentifierInfo(ASTContext &Ctx) const;110111SVal112getAssertionResultSuccessFieldValue(const CXXRecordDecl *AssertionResultDecl,113SVal Instance,114ProgramStateRef State) const;115116static ProgramStateRef assumeValuesEqual(SVal Val1, SVal Val2,117ProgramStateRef State,118CheckerContext &C);119};120} // End anonymous namespace.121122/// Model a call to an un-inlined AssertionResult(bool) or123/// AssertionResult(bool &, ...).124/// To do so, constrain the value of the newly-constructed instance's 'success_'125/// field to be equal to the passed-in boolean value.126///127/// \param IsRef Whether the boolean parameter is a reference or not.128void GTestChecker::modelAssertionResultBoolConstructor(129const CXXConstructorCall *Call, bool IsRef, CheckerContext &C) const {130assert(Call->getNumArgs() >= 1 && Call->getNumArgs() <= 2);131132ProgramStateRef State = C.getState();133SVal BooleanArgVal = Call->getArgSVal(0);134if (IsRef) {135// The argument is a reference, so load from it to get the boolean value.136if (!isa<Loc>(BooleanArgVal))137return;138BooleanArgVal = C.getState()->getSVal(BooleanArgVal.castAs<Loc>());139}140141SVal ThisVal = Call->getCXXThisVal();142143SVal ThisSuccess = getAssertionResultSuccessFieldValue(144Call->getDecl()->getParent(), ThisVal, State);145146State = assumeValuesEqual(ThisSuccess, BooleanArgVal, State, C);147C.addTransition(State);148}149150/// Model a call to an un-inlined AssertionResult copy constructor:151///152/// AssertionResult(const &AssertionResult other)153///154/// To do so, constrain the value of the newly-constructed instance's155/// 'success_' field to be equal to the value of the pass-in instance's156/// 'success_' field.157void GTestChecker::modelAssertionResultCopyConstructor(158const CXXConstructorCall *Call, CheckerContext &C) const {159assert(Call->getNumArgs() == 1);160161// The first parameter of the copy constructor must be the other162// instance to initialize this instances fields from.163SVal OtherVal = Call->getArgSVal(0);164SVal ThisVal = Call->getCXXThisVal();165166const CXXRecordDecl *AssertResultClassDecl = Call->getDecl()->getParent();167ProgramStateRef State = C.getState();168169SVal ThisSuccess = getAssertionResultSuccessFieldValue(AssertResultClassDecl,170ThisVal, State);171SVal OtherSuccess = getAssertionResultSuccessFieldValue(AssertResultClassDecl,172OtherVal, State);173174State = assumeValuesEqual(ThisSuccess, OtherSuccess, State, C);175C.addTransition(State);176}177178/// Model calls to AssertionResult constructors that are not inlined.179void GTestChecker::checkPostCall(const CallEvent &Call,180CheckerContext &C) const {181/// If the constructor was inlined, there is no need model it.182if (C.wasInlined)183return;184185initIdentifierInfo(C.getASTContext());186187auto *CtorCall = dyn_cast<CXXConstructorCall>(&Call);188if (!CtorCall)189return;190191const CXXConstructorDecl *CtorDecl = CtorCall->getDecl();192const CXXRecordDecl *CtorParent = CtorDecl->getParent();193if (CtorParent->getIdentifier() != AssertionResultII)194return;195196unsigned ParamCount = CtorDecl->getNumParams();197198// Call the appropriate modeling method based the parameters and their199// types.200201// We have AssertionResult(const &AssertionResult)202if (CtorDecl->isCopyConstructor() && ParamCount == 1) {203modelAssertionResultCopyConstructor(CtorCall, C);204return;205}206207// There are two possible boolean constructors, depending on which208// version of gtest is being used:209//210// v1.7 and earlier:211// AssertionResult(bool success)212//213// v1.8 and greater:214// template <typename T>215// AssertionResult(const T& success,216// typename internal::EnableIf<217// !internal::ImplicitlyConvertible<T,218// AssertionResult>::value>::type*)219//220CanQualType BoolTy = C.getASTContext().BoolTy;221if (ParamCount == 1 && CtorDecl->getParamDecl(0)->getType() == BoolTy) {222// We have AssertionResult(bool)223modelAssertionResultBoolConstructor(CtorCall, /*IsRef=*/false, C);224return;225}226if (ParamCount == 2){227auto *RefTy = CtorDecl->getParamDecl(0)->getType()->getAs<ReferenceType>();228if (RefTy &&229RefTy->getPointeeType()->getCanonicalTypeUnqualified() == BoolTy) {230// We have AssertionResult(bool &, ...)231modelAssertionResultBoolConstructor(CtorCall, /*IsRef=*/true, C);232return;233}234}235}236237void GTestChecker::initIdentifierInfo(ASTContext &Ctx) const {238if (AssertionResultII)239return;240241AssertionResultII = &Ctx.Idents.get("AssertionResult");242SuccessII = &Ctx.Idents.get("success_");243}244245/// Returns the value stored in the 'success_' field of the passed-in246/// AssertionResult instance.247SVal GTestChecker::getAssertionResultSuccessFieldValue(248const CXXRecordDecl *AssertionResultDecl, SVal Instance,249ProgramStateRef State) const {250251DeclContext::lookup_result Result = AssertionResultDecl->lookup(SuccessII);252if (Result.empty())253return UnknownVal();254255auto *SuccessField = dyn_cast<FieldDecl>(Result.front());256if (!SuccessField)257return UnknownVal();258259std::optional<Loc> FieldLoc =260State->getLValue(SuccessField, Instance).getAs<Loc>();261if (!FieldLoc)262return UnknownVal();263264return State->getSVal(*FieldLoc);265}266267/// Constrain the passed-in state to assume two values are equal.268ProgramStateRef GTestChecker::assumeValuesEqual(SVal Val1, SVal Val2,269ProgramStateRef State,270CheckerContext &C) {271auto DVal1 = Val1.getAs<DefinedOrUnknownSVal>();272auto DVal2 = Val2.getAs<DefinedOrUnknownSVal>();273if (!DVal1 || !DVal2)274return State;275276auto ValuesEqual =277C.getSValBuilder().evalEQ(State, *DVal1, *DVal2).getAs<DefinedSVal>();278if (!ValuesEqual)279return State;280281State = C.getConstraintManager().assume(State, *ValuesEqual, true);282return State;283}284285void ento::registerGTestChecker(CheckerManager &Mgr) {286Mgr.registerChecker<GTestChecker>();287}288289bool ento::shouldRegisterGTestChecker(const CheckerManager &mgr) {290// gtest is a C++ API so there is no sense running the checker291// if not compiling for C++.292const LangOptions &LO = mgr.getLangOpts();293return LO.CPlusPlus;294}295296297