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(I: 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>(Val: 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>(Val: 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(IsBeginFunction: 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(IsBeginFunction: false, C); |
96 | } |
97 | |
98 | void VirtualCallChecker::checkPreCall(const CallEvent &Call, |
99 | CheckerContext &C) const { |
100 | const auto MC = dyn_cast<CXXMemberCall>(Val: &Call); |
101 | if (!MC) |
102 | return; |
103 | |
104 | const CXXMethodDecl *MD = dyn_cast_or_null<CXXMethodDecl>(Val: 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>(Val: Call.getOriginExpr()); |
111 | if (!isVirtualCall(CE)) |
112 | return; |
113 | |
114 | const MemRegion *Reg = MC->getCXXThisVal().getAsRegion(); |
115 | const ObjectState *ObState = State->get<CtorDtorMap>(key: Reg); |
116 | if (!ObState) |
117 | return; |
118 | |
119 | bool IsPure = MD->isPureVirtual(); |
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>(args&: *BT, args: OS.str(), args&: 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 | InsertionLoc: CE->getBeginLoc(), Code: MD->getParent()->getNameAsString() + "::" ); |
159 | Report->addFixItHint(F: Fixit); |
160 | } |
161 | |
162 | C.emitReport(R: 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>(Val: 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>(Val: MD)) { |
177 | auto ThiSVal = |
178 | State->getSVal(LV: SVB.getCXXThis(D: MD, SFC: LCtx->getStackFrame())); |
179 | const MemRegion *Reg = ThiSVal.getAsRegion(); |
180 | if (IsBeginFunction) |
181 | State = State->set<CtorDtorMap>(K: Reg, E: ObjectState::CtorCalled); |
182 | else |
183 | State = State->remove<CtorDtorMap>(K: Reg); |
184 | |
185 | C.addTransition(State); |
186 | return; |
187 | } |
188 | |
189 | // Enter a Destructor, set the corresponding memregion be true. |
190 | if (isa<CXXDestructorDecl>(Val: MD)) { |
191 | auto ThiSVal = |
192 | State->getSVal(LV: SVB.getCXXThis(D: MD, SFC: LCtx->getStackFrame())); |
193 | const MemRegion *Reg = ThiSVal.getAsRegion(); |
194 | if (IsBeginFunction) |
195 | State = State->set<CtorDtorMap>(K: Reg, E: ObjectState::DtorCalled); |
196 | else |
197 | State = State->remove<CtorDtorMap>(K: 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>(args: Mgr.getCurrentCheckerName(), |
211 | args: "Pure virtual method call" , |
212 | args: categories::CXXObjectLifecycle); |
213 | } |
214 | |
215 | void ento::registerVirtualCallChecker(CheckerManager &Mgr) { |
216 | auto *Chk = Mgr.getChecker<VirtualCallChecker>(); |
217 | if (!Mgr.getAnalyzerOptions().getCheckerBooleanOption( |
218 | CheckerName: Mgr.getCurrentCheckerName(), OptionName: "PureOnly" )) { |
219 | Chk->BT_Impure = std::make_unique<BugType>( |
220 | args: Mgr.getCurrentCheckerName(), args: "Unexpected loss of virtual dispatch" , |
221 | args: categories::CXXObjectLifecycle); |
222 | Chk->ShowFixIts = Mgr.getAnalyzerOptions().getCheckerBooleanOption( |
223 | CheckerName: Mgr.getCurrentCheckerName(), OptionName: "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 | |