/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp
Line | Count | Source (jump to first uncovered line) |
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 | 2.87k | static inline void Profile(ObjectState X, FoldingSetNodeID &ID) { |
36 | 2.87k | ID.AddInteger(static_cast<int>(X)); |
37 | 2.87k | } |
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 | 3.72k | static bool isVirtualCall(const CallExpr *CE) { |
64 | 3.72k | bool CallIsNonVirtual = false; |
65 | | |
66 | 3.72k | 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 | 3.71k | if (CME->getQualifier()) |
70 | 31 | CallIsNonVirtual = true; |
71 | | |
72 | 3.71k | if (const Expr *Base = CME->getBase()) { |
73 | | // The most derived class is marked final. |
74 | 3.71k | if (Base->getBestDynamicClassType()->hasAttr<FinalAttr>()) |
75 | 10 | CallIsNonVirtual = true; |
76 | 3.71k | } |
77 | 3.71k | } |
78 | | |
79 | 3.72k | const CXXMethodDecl *MD = |
80 | 3.72k | dyn_cast_or_null<CXXMethodDecl>(CE->getDirectCallee()); |
81 | 3.72k | if (MD && MD->isVirtual()3.71k && !CallIsNonVirtual202 && !MD->hasAttr<FinalAttr>()161 && |
82 | 3.72k | !MD->getParent()->hasAttr<FinalAttr>()146 ) |
83 | 146 | return true; |
84 | 3.58k | return false; |
85 | 3.72k | } |
86 | | |
87 | | // The BeginFunction callback when enter a constructor or a destructor. |
88 | 9.51k | void VirtualCallChecker::checkBeginFunction(CheckerContext &C) const { |
89 | 9.51k | registerCtorDtorCallInState(true, C); |
90 | 9.51k | } |
91 | | |
92 | | // The EndFunction callback when leave a constructor or a destructor. |
93 | | void VirtualCallChecker::checkEndFunction(const ReturnStmt *RS, |
94 | 15.7k | CheckerContext &C) const { |
95 | 15.7k | registerCtorDtorCallInState(false, C); |
96 | 15.7k | } |
97 | | |
98 | | void VirtualCallChecker::checkPreCall(const CallEvent &Call, |
99 | 31.4k | CheckerContext &C) const { |
100 | 31.4k | const auto MC = dyn_cast<CXXMemberCall>(&Call); |
101 | 31.4k | if (!MC) |
102 | 27.6k | return; |
103 | | |
104 | 3.72k | const CXXMethodDecl *MD = dyn_cast_or_null<CXXMethodDecl>(Call.getDecl()); |
105 | 3.72k | if (!MD) |
106 | 0 | return; |
107 | | |
108 | 3.72k | ProgramStateRef State = C.getState(); |
109 | | // Member calls are always represented by a call-expression. |
110 | 3.72k | const auto *CE = cast<CallExpr>(Call.getOriginExpr()); |
111 | 3.72k | if (!isVirtualCall(CE)) |
112 | 3.58k | return; |
113 | | |
114 | 146 | const MemRegion *Reg = MC->getCXXThisVal().getAsRegion(); |
115 | 146 | const ObjectState *ObState = State->get<CtorDtorMap>(Reg); |
116 | 146 | if (!ObState) |
117 | 35 | return; |
118 | | |
119 | 111 | 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 | 111 | SmallString<128> Msg; |
124 | 111 | llvm::raw_svector_ostream OS(Msg); |
125 | 111 | OS << "Call to "; |
126 | 111 | if (IsPure) |
127 | 8 | OS << "pure "; |
128 | 111 | OS << "virtual method '" << MD->getParent()->getDeclName() |
129 | 111 | << "::" << MD->getDeclName() << "' during "; |
130 | 111 | if (*ObState == ObjectState::CtorCalled) |
131 | 78 | OS << "construction "; |
132 | 33 | else |
133 | 33 | OS << "destruction "; |
134 | 111 | if (IsPure) |
135 | 8 | OS << "has undefined behavior"; |
136 | 103 | else |
137 | 103 | OS << "bypasses virtual dispatch"; |
138 | | |
139 | 111 | ExplodedNode *N = |
140 | 111 | IsPure ? C.generateErrorNode()8 : C.generateNonFatalErrorNode()103 ; |
141 | 111 | if (!N) |
142 | 0 | return; |
143 | | |
144 | 111 | const std::unique_ptr<BugType> &BT = IsPure ? BT_Pure8 : BT_Impure103 ; |
145 | 111 | if (!BT) { |
146 | | // The respective check is disabled. |
147 | 64 | return; |
148 | 64 | } |
149 | | |
150 | 47 | auto Report = std::make_unique<PathSensitiveBugReport>(*BT, OS.str(), N); |
151 | | |
152 | 47 | if (ShowFixIts && !IsPure2 ) { |
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 | 2 | FixItHint Fixit = FixItHint::CreateInsertion( |
158 | 2 | CE->getBeginLoc(), MD->getParent()->getNameAsString() + "::"); |
159 | 2 | Report->addFixItHint(Fixit); |
160 | 2 | } |
161 | | |
162 | 47 | C.emitReport(std::move(Report)); |
163 | 47 | } |
164 | | |
165 | | void VirtualCallChecker::registerCtorDtorCallInState(bool IsBeginFunction, |
166 | 25.2k | CheckerContext &C) const { |
167 | 25.2k | const auto *LCtx = C.getLocationContext(); |
168 | 25.2k | const auto *MD = dyn_cast_or_null<CXXMethodDecl>(LCtx->getDecl()); |
169 | 25.2k | if (!MD) |
170 | 15.3k | return; |
171 | | |
172 | 9.92k | ProgramStateRef State = C.getState(); |
173 | 9.92k | auto &SVB = C.getSValBuilder(); |
174 | | |
175 | | // Enter a constructor, set the corresponding memregion be true. |
176 | 9.92k | if (isa<CXXConstructorDecl>(MD)) { |
177 | 4.39k | auto ThiSVal = |
178 | 4.39k | State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame())); |
179 | 4.39k | const MemRegion *Reg = ThiSVal.getAsRegion(); |
180 | 4.39k | if (IsBeginFunction) |
181 | 2.20k | State = State->set<CtorDtorMap>(Reg, ObjectState::CtorCalled); |
182 | 2.19k | else |
183 | 2.19k | State = State->remove<CtorDtorMap>(Reg); |
184 | | |
185 | 4.39k | C.addTransition(State); |
186 | 4.39k | return; |
187 | 4.39k | } |
188 | | |
189 | | // Enter a Destructor, set the corresponding memregion be true. |
190 | 5.53k | if (isa<CXXDestructorDecl>(MD)) { |
191 | 603 | auto ThiSVal = |
192 | 603 | State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame())); |
193 | 603 | const MemRegion *Reg = ThiSVal.getAsRegion(); |
194 | 603 | if (IsBeginFunction) |
195 | 310 | State = State->set<CtorDtorMap>(Reg, ObjectState::DtorCalled); |
196 | 293 | else |
197 | 293 | State = State->remove<CtorDtorMap>(Reg); |
198 | | |
199 | 603 | C.addTransition(State); |
200 | 603 | return; |
201 | 603 | } |
202 | 5.53k | } |
203 | | |
204 | 73 | void ento::registerVirtualCallModeling(CheckerManager &Mgr) { |
205 | 73 | Mgr.registerChecker<VirtualCallChecker>(); |
206 | 73 | } |
207 | | |
208 | 68 | void ento::registerPureVirtualCallChecker(CheckerManager &Mgr) { |
209 | 68 | auto *Chk = Mgr.getChecker<VirtualCallChecker>(); |
210 | 68 | Chk->BT_Pure = std::make_unique<BugType>(Mgr.getCurrentCheckerName(), |
211 | 68 | "Pure virtual method call", |
212 | 68 | categories::CXXObjectLifecycle); |
213 | 68 | } |
214 | | |
215 | 8 | void ento::registerVirtualCallChecker(CheckerManager &Mgr) { |
216 | 8 | auto *Chk = Mgr.getChecker<VirtualCallChecker>(); |
217 | 8 | if (!Mgr.getAnalyzerOptions().getCheckerBooleanOption( |
218 | 8 | Mgr.getCurrentCheckerName(), "PureOnly")) { |
219 | 6 | Chk->BT_Impure = std::make_unique<BugType>( |
220 | 6 | Mgr.getCurrentCheckerName(), "Unexpected loss of virtual dispatch", |
221 | 6 | categories::CXXObjectLifecycle); |
222 | 6 | Chk->ShowFixIts = Mgr.getAnalyzerOptions().getCheckerBooleanOption( |
223 | 6 | Mgr.getCurrentCheckerName(), "ShowFixIts"); |
224 | 6 | } |
225 | 8 | } |
226 | | |
227 | 282 | bool ento::shouldRegisterVirtualCallModeling(const CheckerManager &mgr) { |
228 | 282 | const LangOptions &LO = mgr.getLangOpts(); |
229 | 282 | return LO.CPlusPlus; |
230 | 282 | } |
231 | | |
232 | 136 | bool ento::shouldRegisterPureVirtualCallChecker(const CheckerManager &mgr) { |
233 | 136 | const LangOptions &LO = mgr.getLangOpts(); |
234 | 136 | return LO.CPlusPlus; |
235 | 136 | } |
236 | | |
237 | 16 | bool ento::shouldRegisterVirtualCallChecker(const CheckerManager &mgr) { |
238 | 16 | const LangOptions &LO = mgr.getLangOpts(); |
239 | 16 | return LO.CPlusPlus; |
240 | 16 | } |