123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240 |
- //=======- VirtualCallChecker.cpp --------------------------------*- 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 file defines a checker that checks virtual method calls during
- // construction or destruction of C++ objects.
- //
- //===----------------------------------------------------------------------===//
- #include "clang/AST/Attr.h"
- #include "clang/AST/DeclCXX.h"
- #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
- #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
- #include "clang/StaticAnalyzer/Core/BugReporter/BugType.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/ProgramStateTrait.h"
- #include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h"
- using namespace clang;
- using namespace ento;
- namespace {
- enum class ObjectState : bool { CtorCalled, DtorCalled };
- } // end namespace
- // FIXME: Ascending over StackFrameContext maybe another method.
- namespace llvm {
- template <> struct FoldingSetTrait<ObjectState> {
- static inline void Profile(ObjectState X, FoldingSetNodeID &ID) {
- ID.AddInteger(static_cast<int>(X));
- }
- };
- } // end namespace llvm
- namespace {
- class VirtualCallChecker
- : public Checker<check::BeginFunction, check::EndFunction, check::PreCall> {
- public:
- // These are going to be null if the respective check is disabled.
- mutable std::unique_ptr<BugType> BT_Pure, BT_Impure;
- bool ShowFixIts = false;
- void checkBeginFunction(CheckerContext &C) const;
- void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
- void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
- private:
- void registerCtorDtorCallInState(bool IsBeginFunction,
- CheckerContext &C) const;
- };
- } // end namespace
- // GDM (generic data map) to the memregion of this for the ctor and dtor.
- REGISTER_MAP_WITH_PROGRAMSTATE(CtorDtorMap, const MemRegion *, ObjectState)
- // The function to check if a callexpr is a virtual method call.
- static bool isVirtualCall(const CallExpr *CE) {
- bool CallIsNonVirtual = false;
- if (const MemberExpr *CME = dyn_cast<MemberExpr>(CE->getCallee())) {
- // The member access is fully qualified (i.e., X::F).
- // Treat this as a non-virtual call and do not warn.
- if (CME->getQualifier())
- CallIsNonVirtual = true;
- if (const Expr *Base = CME->getBase()) {
- // The most derived class is marked final.
- if (Base->getBestDynamicClassType()->hasAttr<FinalAttr>())
- CallIsNonVirtual = true;
- }
- }
- const CXXMethodDecl *MD =
- dyn_cast_or_null<CXXMethodDecl>(CE->getDirectCallee());
- if (MD && MD->isVirtual() && !CallIsNonVirtual && !MD->hasAttr<FinalAttr>() &&
- !MD->getParent()->hasAttr<FinalAttr>())
- return true;
- return false;
- }
- // The BeginFunction callback when enter a constructor or a destructor.
- void VirtualCallChecker::checkBeginFunction(CheckerContext &C) const {
- registerCtorDtorCallInState(true, C);
- }
- // The EndFunction callback when leave a constructor or a destructor.
- void VirtualCallChecker::checkEndFunction(const ReturnStmt *RS,
- CheckerContext &C) const {
- registerCtorDtorCallInState(false, C);
- }
- void VirtualCallChecker::checkPreCall(const CallEvent &Call,
- CheckerContext &C) const {
- const auto MC = dyn_cast<CXXMemberCall>(&Call);
- if (!MC)
- return;
- const CXXMethodDecl *MD = dyn_cast_or_null<CXXMethodDecl>(Call.getDecl());
- if (!MD)
- return;
- ProgramStateRef State = C.getState();
- // Member calls are always represented by a call-expression.
- const auto *CE = cast<CallExpr>(Call.getOriginExpr());
- if (!isVirtualCall(CE))
- return;
- const MemRegion *Reg = MC->getCXXThisVal().getAsRegion();
- const ObjectState *ObState = State->get<CtorDtorMap>(Reg);
- if (!ObState)
- return;
- bool IsPure = MD->isPure();
- // At this point we're sure that we're calling a virtual method
- // during construction or destruction, so we'll emit a report.
- SmallString<128> Msg;
- llvm::raw_svector_ostream OS(Msg);
- OS << "Call to ";
- if (IsPure)
- OS << "pure ";
- OS << "virtual method '" << MD->getParent()->getDeclName()
- << "::" << MD->getDeclName() << "' during ";
- if (*ObState == ObjectState::CtorCalled)
- OS << "construction ";
- else
- OS << "destruction ";
- if (IsPure)
- OS << "has undefined behavior";
- else
- OS << "bypasses virtual dispatch";
- ExplodedNode *N =
- IsPure ? C.generateErrorNode() : C.generateNonFatalErrorNode();
- if (!N)
- return;
- const std::unique_ptr<BugType> &BT = IsPure ? BT_Pure : BT_Impure;
- if (!BT) {
- // The respective check is disabled.
- return;
- }
- auto Report = std::make_unique<PathSensitiveBugReport>(*BT, OS.str(), N);
- if (ShowFixIts && !IsPure) {
- // FIXME: These hints are valid only when the virtual call is made
- // directly from the constructor/destructor. Otherwise the dispatch
- // will work just fine from other callees, and the fix may break
- // the otherwise correct program.
- FixItHint Fixit = FixItHint::CreateInsertion(
- CE->getBeginLoc(), MD->getParent()->getNameAsString() + "::");
- Report->addFixItHint(Fixit);
- }
- C.emitReport(std::move(Report));
- }
- void VirtualCallChecker::registerCtorDtorCallInState(bool IsBeginFunction,
- CheckerContext &C) const {
- const auto *LCtx = C.getLocationContext();
- const auto *MD = dyn_cast_or_null<CXXMethodDecl>(LCtx->getDecl());
- if (!MD)
- return;
- ProgramStateRef State = C.getState();
- auto &SVB = C.getSValBuilder();
- // Enter a constructor, set the corresponding memregion be true.
- if (isa<CXXConstructorDecl>(MD)) {
- auto ThiSVal =
- State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame()));
- const MemRegion *Reg = ThiSVal.getAsRegion();
- if (IsBeginFunction)
- State = State->set<CtorDtorMap>(Reg, ObjectState::CtorCalled);
- else
- State = State->remove<CtorDtorMap>(Reg);
- C.addTransition(State);
- return;
- }
- // Enter a Destructor, set the corresponding memregion be true.
- if (isa<CXXDestructorDecl>(MD)) {
- auto ThiSVal =
- State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame()));
- const MemRegion *Reg = ThiSVal.getAsRegion();
- if (IsBeginFunction)
- State = State->set<CtorDtorMap>(Reg, ObjectState::DtorCalled);
- else
- State = State->remove<CtorDtorMap>(Reg);
- C.addTransition(State);
- return;
- }
- }
- void ento::registerVirtualCallModeling(CheckerManager &Mgr) {
- Mgr.registerChecker<VirtualCallChecker>();
- }
- void ento::registerPureVirtualCallChecker(CheckerManager &Mgr) {
- auto *Chk = Mgr.getChecker<VirtualCallChecker>();
- Chk->BT_Pure = std::make_unique<BugType>(Mgr.getCurrentCheckerName(),
- "Pure virtual method call",
- categories::CXXObjectLifecycle);
- }
- void ento::registerVirtualCallChecker(CheckerManager &Mgr) {
- auto *Chk = Mgr.getChecker<VirtualCallChecker>();
- if (!Mgr.getAnalyzerOptions().getCheckerBooleanOption(
- Mgr.getCurrentCheckerName(), "PureOnly")) {
- Chk->BT_Impure = std::make_unique<BugType>(
- Mgr.getCurrentCheckerName(), "Unexpected loss of virtual dispatch",
- categories::CXXObjectLifecycle);
- Chk->ShowFixIts = Mgr.getAnalyzerOptions().getCheckerBooleanOption(
- Mgr.getCurrentCheckerName(), "ShowFixIts");
- }
- }
- bool ento::shouldRegisterVirtualCallModeling(const CheckerManager &mgr) {
- const LangOptions &LO = mgr.getLangOpts();
- return LO.CPlusPlus;
- }
- bool ento::shouldRegisterPureVirtualCallChecker(const CheckerManager &mgr) {
- const LangOptions &LO = mgr.getLangOpts();
- return LO.CPlusPlus;
- }
- bool ento::shouldRegisterVirtualCallChecker(const CheckerManager &mgr) {
- const LangOptions &LO = mgr.getLangOpts();
- return LO.CPlusPlus;
- }
|