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