xref: /freebsd/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp (revision a03411e84728e9b267056fd31c7d1d9d1dc1b01e)
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