Home | History | Annotate | Line # | Download | only in Checkers
      1 //=======- VirtualCallChecker.cpp --------------------------------*- C++ -*-==//
      2 //
      3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
      4 // See https://llvm.org/LICENSE.txt for license information.
      5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
      6 //
      7 //===----------------------------------------------------------------------===//
      8 //
      9 //  This file defines a checker that checks virtual method calls during
     10 //  construction or destruction of C++ objects.
     11 //
     12 //===----------------------------------------------------------------------===//
     13 
     14 #include "clang/AST/Attr.h"
     15 #include "clang/AST/DeclCXX.h"
     16 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
     17 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
     18 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
     19 #include "clang/StaticAnalyzer/Core/Checker.h"
     20 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
     21 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
     22 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
     23 #include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h"
     24 
     25 using namespace clang;
     26 using namespace ento;
     27 
     28 namespace {
     29 enum class ObjectState : bool { CtorCalled, DtorCalled };
     30 } // end namespace
     31   // FIXME: Ascending over StackFrameContext maybe another method.
     32 
     33 namespace llvm {
     34 template <> struct FoldingSetTrait<ObjectState> {
     35   static inline void Profile(ObjectState X, FoldingSetNodeID &ID) {
     36     ID.AddInteger(static_cast<int>(X));
     37   }
     38 };
     39 } // end namespace llvm
     40 
     41 namespace {
     42 class VirtualCallChecker
     43     : public Checker<check::BeginFunction, check::EndFunction, check::PreCall> {
     44 public:
     45   // These are going to be null if the respective check is disabled.
     46   mutable std::unique_ptr<BugType> BT_Pure, BT_Impure;
     47   bool ShowFixIts = false;
     48 
     49   void checkBeginFunction(CheckerContext &C) const;
     50   void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
     51   void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
     52 
     53 private:
     54   void registerCtorDtorCallInState(bool IsBeginFunction,
     55                                    CheckerContext &C) const;
     56 };
     57 } // end namespace
     58 
     59 // GDM (generic data map) to the memregion of this for the ctor and dtor.
     60 REGISTER_MAP_WITH_PROGRAMSTATE(CtorDtorMap, const MemRegion *, ObjectState)
     61 
     62 // The function to check if a callexpr is a virtual method call.
     63 static bool isVirtualCall(const CallExpr *CE) {
     64   bool CallIsNonVirtual = false;
     65 
     66   if (const MemberExpr *CME = dyn_cast<MemberExpr>(CE->getCallee())) {
     67     // The member access is fully qualified (i.e., X::F).
     68     // Treat this as a non-virtual call and do not warn.
     69     if (CME->getQualifier())
     70       CallIsNonVirtual = true;
     71 
     72     if (const Expr *Base = CME->getBase()) {
     73       // The most derived class is marked final.
     74       if (Base->getBestDynamicClassType()->hasAttr<FinalAttr>())
     75         CallIsNonVirtual = true;
     76     }
     77   }
     78 
     79   const CXXMethodDecl *MD =
     80       dyn_cast_or_null<CXXMethodDecl>(CE->getDirectCallee());
     81   if (MD && MD->isVirtual() && !CallIsNonVirtual && !MD->hasAttr<FinalAttr>() &&
     82       !MD->getParent()->hasAttr<FinalAttr>())
     83     return true;
     84   return false;
     85 }
     86 
     87 // The BeginFunction callback when enter a constructor or a destructor.
     88 void VirtualCallChecker::checkBeginFunction(CheckerContext &C) const {
     89   registerCtorDtorCallInState(true, C);
     90 }
     91 
     92 // The EndFunction callback when leave a constructor or a destructor.
     93 void VirtualCallChecker::checkEndFunction(const ReturnStmt *RS,
     94                                           CheckerContext &C) const {
     95   registerCtorDtorCallInState(false, C);
     96 }
     97 
     98 void VirtualCallChecker::checkPreCall(const CallEvent &Call,
     99                                       CheckerContext &C) const {
    100   const auto MC = dyn_cast<CXXMemberCall>(&Call);
    101   if (!MC)
    102     return;
    103 
    104   const CXXMethodDecl *MD = dyn_cast_or_null<CXXMethodDecl>(Call.getDecl());
    105   if (!MD)
    106     return;
    107 
    108   ProgramStateRef State = C.getState();
    109   // Member calls are always represented by a call-expression.
    110   const auto *CE = cast<CallExpr>(Call.getOriginExpr());
    111   if (!isVirtualCall(CE))
    112     return;
    113 
    114   const MemRegion *Reg = MC->getCXXThisVal().getAsRegion();
    115   const ObjectState *ObState = State->get<CtorDtorMap>(Reg);
    116   if (!ObState)
    117     return;
    118 
    119   bool IsPure = MD->isPure();
    120 
    121   // At this point we're sure that we're calling a virtual method
    122   // during construction or destruction, so we'll emit a report.
    123   SmallString<128> Msg;
    124   llvm::raw_svector_ostream OS(Msg);
    125   OS << "Call to ";
    126   if (IsPure)
    127     OS << "pure ";
    128   OS << "virtual method '" << MD->getParent()->getDeclName()
    129      << "::" << MD->getDeclName() << "' during ";
    130   if (*ObState == ObjectState::CtorCalled)
    131     OS << "construction ";
    132   else
    133     OS << "destruction ";
    134   if (IsPure)
    135     OS << "has undefined behavior";
    136   else
    137     OS << "bypasses virtual dispatch";
    138 
    139   ExplodedNode *N =
    140       IsPure ? C.generateErrorNode() : C.generateNonFatalErrorNode();
    141   if (!N)
    142     return;
    143 
    144   const std::unique_ptr<BugType> &BT = IsPure ? BT_Pure : BT_Impure;
    145   if (!BT) {
    146     // The respective check is disabled.
    147     return;
    148   }
    149 
    150   auto Report = std::make_unique<PathSensitiveBugReport>(*BT, OS.str(), N);
    151 
    152   if (ShowFixIts && !IsPure) {
    153     // FIXME: These hints are valid only when the virtual call is made
    154     // directly from the constructor/destructor. Otherwise the dispatch
    155     // will work just fine from other callees, and the fix may break
    156     // the otherwise correct program.
    157     FixItHint Fixit = FixItHint::CreateInsertion(
    158         CE->getBeginLoc(), MD->getParent()->getNameAsString() + "::");
    159     Report->addFixItHint(Fixit);
    160   }
    161 
    162   C.emitReport(std::move(Report));
    163 }
    164 
    165 void VirtualCallChecker::registerCtorDtorCallInState(bool IsBeginFunction,
    166                                                      CheckerContext &C) const {
    167   const auto *LCtx = C.getLocationContext();
    168   const auto *MD = dyn_cast_or_null<CXXMethodDecl>(LCtx->getDecl());
    169   if (!MD)
    170     return;
    171 
    172   ProgramStateRef State = C.getState();
    173   auto &SVB = C.getSValBuilder();
    174 
    175   // Enter a constructor, set the corresponding memregion be true.
    176   if (isa<CXXConstructorDecl>(MD)) {
    177     auto ThiSVal =
    178         State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame()));
    179     const MemRegion *Reg = ThiSVal.getAsRegion();
    180     if (IsBeginFunction)
    181       State = State->set<CtorDtorMap>(Reg, ObjectState::CtorCalled);
    182     else
    183       State = State->remove<CtorDtorMap>(Reg);
    184 
    185     C.addTransition(State);
    186     return;
    187   }
    188 
    189   // Enter a Destructor, set the corresponding memregion be true.
    190   if (isa<CXXDestructorDecl>(MD)) {
    191     auto ThiSVal =
    192         State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame()));
    193     const MemRegion *Reg = ThiSVal.getAsRegion();
    194     if (IsBeginFunction)
    195       State = State->set<CtorDtorMap>(Reg, ObjectState::DtorCalled);
    196     else
    197       State = State->remove<CtorDtorMap>(Reg);
    198 
    199     C.addTransition(State);
    200     return;
    201   }
    202 }
    203 
    204 void ento::registerVirtualCallModeling(CheckerManager &Mgr) {
    205   Mgr.registerChecker<VirtualCallChecker>();
    206 }
    207 
    208 void ento::registerPureVirtualCallChecker(CheckerManager &Mgr) {
    209   auto *Chk = Mgr.getChecker<VirtualCallChecker>();
    210   Chk->BT_Pure = std::make_unique<BugType>(Mgr.getCurrentCheckerName(),
    211                                            "Pure virtual method call",
    212                                            categories::CXXObjectLifecycle);
    213 }
    214 
    215 void ento::registerVirtualCallChecker(CheckerManager &Mgr) {
    216   auto *Chk = Mgr.getChecker<VirtualCallChecker>();
    217   if (!Mgr.getAnalyzerOptions().getCheckerBooleanOption(
    218           Mgr.getCurrentCheckerName(), "PureOnly")) {
    219     Chk->BT_Impure = std::make_unique<BugType>(
    220         Mgr.getCurrentCheckerName(), "Unexpected loss of virtual dispatch",
    221         categories::CXXObjectLifecycle);
    222     Chk->ShowFixIts = Mgr.getAnalyzerOptions().getCheckerBooleanOption(
    223         Mgr.getCurrentCheckerName(), "ShowFixIts");
    224   }
    225 }
    226 
    227 bool ento::shouldRegisterVirtualCallModeling(const CheckerManager &mgr) {
    228   const LangOptions &LO = mgr.getLangOpts();
    229   return LO.CPlusPlus;
    230 }
    231 
    232 bool ento::shouldRegisterPureVirtualCallChecker(const CheckerManager &mgr) {
    233   const LangOptions &LO = mgr.getLangOpts();
    234   return LO.CPlusPlus;
    235 }
    236 
    237 bool ento::shouldRegisterVirtualCallChecker(const CheckerManager &mgr) {
    238   const LangOptions &LO = mgr.getLangOpts();
    239   return LO.CPlusPlus;
    240 }
    241