clang 23.0.0git
VirtualCallChecker.cpp
Go to the documentation of this file.
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"
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(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",
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>(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.
95void VirtualCallChecker::checkBeginFunction(CheckerContext &C) const {
96 registerCtorDtorCallInState(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(false, C);
103}
104
105void 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 // 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(CE->getBeginLoc()))
124 return;
125
126 const MemRegion *Reg = MC->getCXXThisVal().getAsRegion();
127 const ObjectState *ObState = State->get<CtorDtorMap>(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>(Part, OS.str(), 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 CE->getBeginLoc(), MD->getParent()->getNameAsString() + "::");
172 Report->addFixItHint(Fixit);
173 }
174
175 C.emitReport(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>(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>(MD)) {
190 auto ThiSVal =
191 State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame()));
192 const MemRegion *Reg = ThiSVal.getAsRegion();
193 if (IsBeginFunction)
194 State = State->set<CtorDtorMap>(Reg, ObjectState::CtorCalled);
195 else
196 State = State->remove<CtorDtorMap>(Reg);
197
198 C.addTransition(State);
199 return;
200 }
201
202 // Enter a Destructor, set the corresponding memregion be true.
203 if (isa<CXXDestructorDecl>(MD)) {
204 auto ThiSVal =
205 State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame()));
206 const MemRegion *Reg = ThiSVal.getAsRegion();
207 if (IsBeginFunction)
208 State = State->set<CtorDtorMap>(Reg, ObjectState::DtorCalled);
209 else
210 State = State->remove<CtorDtorMap>(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 Mgr.getCurrentCheckerName(), "ShowFixIts");
230}
231
232bool ento::shouldRegisterVirtualCallChecker(const CheckerManager &Mgr) {
233 return Mgr.getLangOpts().CPlusPlus;
234}
Defines the C++ Decl subclasses, other than those for templates (found in DeclTemplate....
#define X(type, name)
Definition Value.h:97
#define REGISTER_MAP_WITH_PROGRAMSTATE(Name, Key, Value)
Declares an immutable map of type NameTy, suitable for placement into the ProgramState.
static bool isVirtualCall(const CallExpr *CE)
Represents a static or instance method of a struct/union/class.
Definition DeclCXX.h:2136
bool isVirtual() const
Definition DeclCXX.h:2191
const CXXRecordDecl * getParent() const
Return the parent of this method declaration, which is the class in which this method is defined.
Definition DeclCXX.h:2262
CallExpr - Represents a function call (C99 6.5.2.2, C++ [expr.call]).
Definition Expr.h:2946
bool hasAttr() const
Definition DeclBase.h:577
static FixItHint CreateInsertion(SourceLocation InsertionLoc, StringRef Code, bool BeforePreviousInsertions=false)
Create a code modification hint that inserts the given code string at a specific location.
Definition Diagnostic.h:103
bool isPureVirtual() const
Whether this virtual function is pure, i.e.
Definition Decl.h:2353
MemberExpr - [C99 6.5.2.3] Structure and Union Members.
Definition Expr.h:3367
DeclarationName getDeclName() const
Get the actual, stored name of the declaration, which may be a special name.
Definition Decl.h:340
std::string getNameAsString() const
Get a human-readable name for the declaration, even if it is one of the special kinds of names (C++ c...
Definition Decl.h:317
Checker families (where a single backend class implements multiple related frontends) should derive f...
Definition Checker.h:584
const AnalyzerOptions & getAnalyzerOptions() const
const LangOptions & getLangOpts() const
CheckerNameRef getCurrentCheckerName() const
CHECKER * getChecker(AT &&...Args)
If the the singleton instance of a checker class is not yet constructed, then construct it (with the ...
MemRegion - The root abstract class for all memory regions.
Definition MemRegion.h:98
const char *const CXXObjectLifecycle
IntrusiveRefCntPtr< const ProgramState > ProgramStateRef
@ OS
Indicates that the tracking object is a descendant of a referenced-counted OSObject,...
The JSON file list parser is used to communicate input to InstallAPI.
bool isa(CodeGen::Address addr)
Definition Address.h:330
U cast(CodeGen::Address addr)
Definition Address.h:327
Diagnostic wrappers for TextAPI types for error reporting.
Definition Dominators.h:30
static void Profile(ObjectState X, FoldingSetNodeID &ID)