xref: /freebsd/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp (revision 700637cbb5e582861067a11aaca4d053546871d2)
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> {
Profilellvm::FoldingSetTrait35   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 CheckerFamily<check::BeginFunction, check::EndFunction,
44                            check::PreCall> {
45 public:
46   CheckerFrontendWithBugType PureChecker{"Pure virtual method call",
47                                          categories::CXXObjectLifecycle};
48   CheckerFrontendWithBugType ImpureChecker{
49       "Unexpected loss of virtual dispatch", categories::CXXObjectLifecycle};
50 
51   bool ShowFixIts = false;
52 
53   void checkBeginFunction(CheckerContext &C) const;
54   void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
55   void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
56 
57   /// Identifies this checker family for debugging purposes.
getDebugTag() const58   StringRef getDebugTag() const override { return "VirtualCallChecker"; }
59 
60 private:
61   void registerCtorDtorCallInState(bool IsBeginFunction,
62                                    CheckerContext &C) const;
63 };
64 } // end namespace
65 
66 // GDM (generic data map) to the memregion of this for the ctor and dtor.
REGISTER_MAP_WITH_PROGRAMSTATE(CtorDtorMap,const MemRegion *,ObjectState)67 REGISTER_MAP_WITH_PROGRAMSTATE(CtorDtorMap, const MemRegion *, ObjectState)
68 
69 // The function to check if a callexpr is a virtual method call.
70 static bool isVirtualCall(const CallExpr *CE) {
71   bool CallIsNonVirtual = false;
72 
73   if (const MemberExpr *CME = dyn_cast<MemberExpr>(CE->getCallee())) {
74     // The member access is fully qualified (i.e., X::F).
75     // Treat this as a non-virtual call and do not warn.
76     if (CME->getQualifier())
77       CallIsNonVirtual = true;
78 
79     if (const Expr *Base = CME->getBase()) {
80       // The most derived class is marked final.
81       if (Base->getBestDynamicClassType()->hasAttr<FinalAttr>())
82         CallIsNonVirtual = true;
83     }
84   }
85 
86   const CXXMethodDecl *MD =
87       dyn_cast_or_null<CXXMethodDecl>(CE->getDirectCallee());
88   if (MD && MD->isVirtual() && !CallIsNonVirtual && !MD->hasAttr<FinalAttr>() &&
89       !MD->getParent()->hasAttr<FinalAttr>())
90     return true;
91   return false;
92 }
93 
94 // The BeginFunction callback when enter a constructor or a destructor.
checkBeginFunction(CheckerContext & C) const95 void VirtualCallChecker::checkBeginFunction(CheckerContext &C) const {
96   registerCtorDtorCallInState(true, C);
97 }
98 
99 // The EndFunction callback when leave a constructor or a destructor.
checkEndFunction(const ReturnStmt * RS,CheckerContext & C) const100 void VirtualCallChecker::checkEndFunction(const ReturnStmt *RS,
101                                           CheckerContext &C) const {
102   registerCtorDtorCallInState(false, C);
103 }
104 
checkPreCall(const CallEvent & Call,CheckerContext & C) const105 void VirtualCallChecker::checkPreCall(const CallEvent &Call,
106                                       CheckerContext &C) const {
107   const auto MC = dyn_cast<CXXMemberCall>(&Call);
108   if (!MC)
109     return;
110 
111   const CXXMethodDecl *MD = dyn_cast_or_null<CXXMethodDecl>(Call.getDecl());
112   if (!MD)
113     return;
114 
115   ProgramStateRef State = C.getState();
116   // Member calls are always represented by a call-expression.
117   const auto *CE = cast<CallExpr>(Call.getOriginExpr());
118   if (!isVirtualCall(CE))
119     return;
120 
121   const MemRegion *Reg = MC->getCXXThisVal().getAsRegion();
122   const ObjectState *ObState = State->get<CtorDtorMap>(Reg);
123   if (!ObState)
124     return;
125 
126   bool IsPure = MD->isPureVirtual();
127 
128   // At this point we're sure that we're calling a virtual method
129   // during construction or destruction, so we'll emit a report.
130   SmallString<128> Msg;
131   llvm::raw_svector_ostream OS(Msg);
132   OS << "Call to ";
133   if (IsPure)
134     OS << "pure ";
135   OS << "virtual method '" << MD->getParent()->getDeclName()
136      << "::" << MD->getDeclName() << "' during ";
137   if (*ObState == ObjectState::CtorCalled)
138     OS << "construction ";
139   else
140     OS << "destruction ";
141   if (IsPure)
142     OS << "has undefined behavior";
143   else
144     OS << "bypasses virtual dispatch";
145 
146   ExplodedNode *N =
147       IsPure ? C.generateErrorNode() : C.generateNonFatalErrorNode();
148   if (!N)
149     return;
150 
151   const CheckerFrontendWithBugType &Part = IsPure ? PureChecker : ImpureChecker;
152 
153   if (!Part.isEnabled()) {
154     // The respective check is disabled.
155     return;
156   }
157 
158   auto Report = std::make_unique<PathSensitiveBugReport>(Part, OS.str(), N);
159 
160   if (ShowFixIts && !IsPure) {
161     // FIXME: These hints are valid only when the virtual call is made
162     // directly from the constructor/destructor. Otherwise the dispatch
163     // will work just fine from other callees, and the fix may break
164     // the otherwise correct program.
165     FixItHint Fixit = FixItHint::CreateInsertion(
166         CE->getBeginLoc(), MD->getParent()->getNameAsString() + "::");
167     Report->addFixItHint(Fixit);
168   }
169 
170   C.emitReport(std::move(Report));
171 }
172 
registerCtorDtorCallInState(bool IsBeginFunction,CheckerContext & C) const173 void VirtualCallChecker::registerCtorDtorCallInState(bool IsBeginFunction,
174                                                      CheckerContext &C) const {
175   const auto *LCtx = C.getLocationContext();
176   const auto *MD = dyn_cast_or_null<CXXMethodDecl>(LCtx->getDecl());
177   if (!MD)
178     return;
179 
180   ProgramStateRef State = C.getState();
181   auto &SVB = C.getSValBuilder();
182 
183   // Enter a constructor, set the corresponding memregion be true.
184   if (isa<CXXConstructorDecl>(MD)) {
185     auto ThiSVal =
186         State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame()));
187     const MemRegion *Reg = ThiSVal.getAsRegion();
188     if (IsBeginFunction)
189       State = State->set<CtorDtorMap>(Reg, ObjectState::CtorCalled);
190     else
191       State = State->remove<CtorDtorMap>(Reg);
192 
193     C.addTransition(State);
194     return;
195   }
196 
197   // Enter a Destructor, set the corresponding memregion be true.
198   if (isa<CXXDestructorDecl>(MD)) {
199     auto ThiSVal =
200         State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame()));
201     const MemRegion *Reg = ThiSVal.getAsRegion();
202     if (IsBeginFunction)
203       State = State->set<CtorDtorMap>(Reg, ObjectState::DtorCalled);
204     else
205       State = State->remove<CtorDtorMap>(Reg);
206 
207     C.addTransition(State);
208     return;
209   }
210 }
211 
registerPureVirtualCallChecker(CheckerManager & Mgr)212 void ento::registerPureVirtualCallChecker(CheckerManager &Mgr) {
213   Mgr.getChecker<VirtualCallChecker>()->PureChecker.enable(Mgr);
214 }
215 
shouldRegisterPureVirtualCallChecker(const CheckerManager & Mgr)216 bool ento::shouldRegisterPureVirtualCallChecker(const CheckerManager &Mgr) {
217   return Mgr.getLangOpts().CPlusPlus;
218 }
219 
registerVirtualCallChecker(CheckerManager & Mgr)220 void ento::registerVirtualCallChecker(CheckerManager &Mgr) {
221   auto *Chk = Mgr.getChecker<VirtualCallChecker>();
222   Chk->ImpureChecker.enable(Mgr);
223   Chk->ShowFixIts = Mgr.getAnalyzerOptions().getCheckerBooleanOption(
224       Mgr.getCurrentCheckerName(), "ShowFixIts");
225 }
226 
shouldRegisterVirtualCallChecker(const CheckerManager & Mgr)227 bool ento::shouldRegisterVirtualCallChecker(const CheckerManager &Mgr) {
228   return Mgr.getLangOpts().CPlusPlus;
229 }
230