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
25using namespace clang;
26using namespace ento;
27
28namespace {
29enum class ObjectState : bool { CtorCalled, DtorCalled };
30} // end namespace
31 // FIXME: Ascending over StackFrameContext maybe another method.
32
33namespace llvm {
34template <> 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
41namespace {
42class VirtualCallChecker
43 : public CheckerFamily<check::BeginFunction, check::EndFunction,
44 check::PreCall> {
45public:
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
60private:
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.
67REGISTER_MAP_WITH_PROGRAMSTATE(CtorDtorMap, const MemRegion *, ObjectState)
68
69// The function to check if a callexpr is a virtual method call.
70static 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.
95void VirtualCallChecker::checkBeginFunction(CheckerContext &C) const {
96 registerCtorDtorCallInState(IsBeginFunction: true, C);
97}
98
99// The EndFunction callback when leave a constructor or a destructor.
100void VirtualCallChecker::checkEndFunction(const ReturnStmt *RS,
101 CheckerContext &C) const {
102 registerCtorDtorCallInState(IsBeginFunction: false, C);
103}
104
105void 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 // Don't warn about virtual calls in system headers (e.g. libraries included
122 // via -isystem), as the user has no control over such code.
123 if (C.getSourceManager().isInSystemHeader(Loc: CE->getBeginLoc()))
124 return;
125
126 const MemRegion *Reg = MC->getCXXThisVal().getAsRegion();
127 const ObjectState *ObState = State->get<CtorDtorMap>(key: Reg);
128 if (!ObState)
129 return;
130
131 bool IsPure = MD->isPureVirtual();
132
133 // At this point we're sure that we're calling a virtual method
134 // during construction or destruction, so we'll emit a report.
135 SmallString<128> Msg;
136 llvm::raw_svector_ostream OS(Msg);
137 OS << "Call to ";
138 if (IsPure)
139 OS << "pure ";
140 OS << "virtual method '" << MD->getParent()->getDeclName()
141 << "::" << MD->getDeclName() << "' during ";
142 if (*ObState == ObjectState::CtorCalled)
143 OS << "construction ";
144 else
145 OS << "destruction ";
146 if (IsPure)
147 OS << "has undefined behavior";
148 else
149 OS << "bypasses virtual dispatch";
150
151 ExplodedNode *N =
152 IsPure ? C.generateErrorNode() : C.generateNonFatalErrorNode();
153 if (!N)
154 return;
155
156 const CheckerFrontendWithBugType &Part = IsPure ? PureChecker : ImpureChecker;
157
158 if (!Part.isEnabled()) {
159 // The respective check is disabled.
160 return;
161 }
162
163 auto Report = std::make_unique<PathSensitiveBugReport>(args: Part, args: OS.str(), args&: N);
164
165 if (ShowFixIts && !IsPure) {
166 // FIXME: These hints are valid only when the virtual call is made
167 // directly from the constructor/destructor. Otherwise the dispatch
168 // will work just fine from other callees, and the fix may break
169 // the otherwise correct program.
170 FixItHint Fixit = FixItHint::CreateInsertion(
171 InsertionLoc: CE->getBeginLoc(), Code: MD->getParent()->getNameAsString() + "::");
172 Report->addFixItHint(F: Fixit);
173 }
174
175 C.emitReport(R: std::move(Report));
176}
177
178void VirtualCallChecker::registerCtorDtorCallInState(bool IsBeginFunction,
179 CheckerContext &C) const {
180 const auto *LCtx = C.getLocationContext();
181 const auto *MD = dyn_cast_or_null<CXXMethodDecl>(Val: LCtx->getDecl());
182 if (!MD)
183 return;
184
185 ProgramStateRef State = C.getState();
186 auto &SVB = C.getSValBuilder();
187
188 // Enter a constructor, set the corresponding memregion be true.
189 if (isa<CXXConstructorDecl>(Val: MD)) {
190 auto ThiSVal =
191 State->getSVal(LV: SVB.getCXXThis(D: MD, SFC: LCtx->getStackFrame()));
192 const MemRegion *Reg = ThiSVal.getAsRegion();
193 if (IsBeginFunction)
194 State = State->set<CtorDtorMap>(K: Reg, E: ObjectState::CtorCalled);
195 else
196 State = State->remove<CtorDtorMap>(K: Reg);
197
198 C.addTransition(State);
199 return;
200 }
201
202 // Enter a Destructor, set the corresponding memregion be true.
203 if (isa<CXXDestructorDecl>(Val: MD)) {
204 auto ThiSVal =
205 State->getSVal(LV: SVB.getCXXThis(D: MD, SFC: LCtx->getStackFrame()));
206 const MemRegion *Reg = ThiSVal.getAsRegion();
207 if (IsBeginFunction)
208 State = State->set<CtorDtorMap>(K: Reg, E: ObjectState::DtorCalled);
209 else
210 State = State->remove<CtorDtorMap>(K: Reg);
211
212 C.addTransition(State);
213 return;
214 }
215}
216
217void ento::registerPureVirtualCallChecker(CheckerManager &Mgr) {
218 Mgr.getChecker<VirtualCallChecker>()->PureChecker.enable(Mgr);
219}
220
221bool ento::shouldRegisterPureVirtualCallChecker(const CheckerManager &Mgr) {
222 return Mgr.getLangOpts().CPlusPlus;
223}
224
225void ento::registerVirtualCallChecker(CheckerManager &Mgr) {
226 auto *Chk = Mgr.getChecker<VirtualCallChecker>();
227 Chk->ImpureChecker.enable(Mgr);
228 Chk->ShowFixIts = Mgr.getAnalyzerOptions().getCheckerBooleanOption(
229 CheckerName: Mgr.getCurrentCheckerName(), OptionName: "ShowFixIts");
230}
231
232bool ento::shouldRegisterVirtualCallChecker(const CheckerManager &Mgr) {
233 return Mgr.getLangOpts().CPlusPlus;
234}
235