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