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