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 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. |
58 | 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. |
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>(Val: 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>(Val: 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. |
95 | void VirtualCallChecker::checkBeginFunction(CheckerContext &C) const { |
96 | registerCtorDtorCallInState(IsBeginFunction: true, C); |
97 | } |
98 | |
99 | // The EndFunction callback when leave a constructor or a destructor. |
100 | void VirtualCallChecker::checkEndFunction(const ReturnStmt *RS, |
101 | CheckerContext &C) const { |
102 | registerCtorDtorCallInState(IsBeginFunction: false, C); |
103 | } |
104 | |
105 | void VirtualCallChecker::checkPreCall(const CallEvent &Call, |
106 | CheckerContext &C) const { |
107 | const auto MC = dyn_cast<CXXMemberCall>(Val: &Call); |
108 | if (!MC) |
109 | return; |
110 | |
111 | const CXXMethodDecl *MD = dyn_cast_or_null<CXXMethodDecl>(Val: 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>(Val: Call.getOriginExpr()); |
118 | if (!isVirtualCall(CE)) |
119 | return; |
120 | |
121 | const MemRegion *Reg = MC->getCXXThisVal().getAsRegion(); |
122 | const ObjectState *ObState = State->get<CtorDtorMap>(key: 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>(args: Part, args: OS.str(), args&: 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 | InsertionLoc: CE->getBeginLoc(), Code: MD->getParent()->getNameAsString() + "::" ); |
167 | Report->addFixItHint(F: Fixit); |
168 | } |
169 | |
170 | C.emitReport(R: std::move(Report)); |
171 | } |
172 | |
173 | void VirtualCallChecker::registerCtorDtorCallInState(bool IsBeginFunction, |
174 | CheckerContext &C) const { |
175 | const auto *LCtx = C.getLocationContext(); |
176 | const auto *MD = dyn_cast_or_null<CXXMethodDecl>(Val: 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>(Val: MD)) { |
185 | auto ThiSVal = |
186 | State->getSVal(LV: SVB.getCXXThis(D: MD, SFC: LCtx->getStackFrame())); |
187 | const MemRegion *Reg = ThiSVal.getAsRegion(); |
188 | if (IsBeginFunction) |
189 | State = State->set<CtorDtorMap>(K: Reg, E: ObjectState::CtorCalled); |
190 | else |
191 | State = State->remove<CtorDtorMap>(K: Reg); |
192 | |
193 | C.addTransition(State); |
194 | return; |
195 | } |
196 | |
197 | // Enter a Destructor, set the corresponding memregion be true. |
198 | if (isa<CXXDestructorDecl>(Val: MD)) { |
199 | auto ThiSVal = |
200 | State->getSVal(LV: SVB.getCXXThis(D: MD, SFC: LCtx->getStackFrame())); |
201 | const MemRegion *Reg = ThiSVal.getAsRegion(); |
202 | if (IsBeginFunction) |
203 | State = State->set<CtorDtorMap>(K: Reg, E: ObjectState::DtorCalled); |
204 | else |
205 | State = State->remove<CtorDtorMap>(K: Reg); |
206 | |
207 | C.addTransition(State); |
208 | return; |
209 | } |
210 | } |
211 | |
212 | void ento::registerPureVirtualCallChecker(CheckerManager &Mgr) { |
213 | Mgr.getChecker<VirtualCallChecker>()->PureChecker.enable(Mgr); |
214 | } |
215 | |
216 | bool ento::shouldRegisterPureVirtualCallChecker(const CheckerManager &Mgr) { |
217 | return Mgr.getLangOpts().CPlusPlus; |
218 | } |
219 | |
220 | void ento::registerVirtualCallChecker(CheckerManager &Mgr) { |
221 | auto *Chk = Mgr.getChecker<VirtualCallChecker>(); |
222 | Chk->ImpureChecker.enable(Mgr); |
223 | Chk->ShowFixIts = Mgr.getAnalyzerOptions().getCheckerBooleanOption( |
224 | CheckerName: Mgr.getCurrentCheckerName(), OptionName: "ShowFixIts" ); |
225 | } |
226 | |
227 | bool ento::shouldRegisterVirtualCallChecker(const CheckerManager &Mgr) { |
228 | return Mgr.getLangOpts().CPlusPlus; |
229 | } |
230 | |