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 Checker<check::BeginFunction, check::EndFunction, check::PreCall> {
44public:
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
53private:
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.
60REGISTER_MAP_WITH_PROGRAMSTATE(CtorDtorMap, const MemRegion *, ObjectState)
61
62// The function to check if a callexpr is a virtual method call.
63static bool isVirtualCall(const CallExpr *CE) {
64 bool CallIsNonVirtual = false;
65
66 if (const MemberExpr *CME = dyn_cast<MemberExpr>(Val: 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 if (CME->getQualifier())
70 CallIsNonVirtual = true;
71
72 if (const Expr *Base = CME->getBase()) {
73 // The most derived class is marked final.
74 if (Base->getBestDynamicClassType()->hasAttr<FinalAttr>())
75 CallIsNonVirtual = true;
76 }
77 }
78
79 const CXXMethodDecl *MD =
80 dyn_cast_or_null<CXXMethodDecl>(Val: CE->getDirectCallee());
81 if (MD && MD->isVirtual() && !CallIsNonVirtual && !MD->hasAttr<FinalAttr>() &&
82 !MD->getParent()->hasAttr<FinalAttr>())
83 return true;
84 return false;
85}
86
87// The BeginFunction callback when enter a constructor or a destructor.
88void VirtualCallChecker::checkBeginFunction(CheckerContext &C) const {
89 registerCtorDtorCallInState(IsBeginFunction: true, C);
90}
91
92// The EndFunction callback when leave a constructor or a destructor.
93void VirtualCallChecker::checkEndFunction(const ReturnStmt *RS,
94 CheckerContext &C) const {
95 registerCtorDtorCallInState(IsBeginFunction: false, C);
96}
97
98void VirtualCallChecker::checkPreCall(const CallEvent &Call,
99 CheckerContext &C) const {
100 const auto MC = dyn_cast<CXXMemberCall>(Val: &Call);
101 if (!MC)
102 return;
103
104 const CXXMethodDecl *MD = dyn_cast_or_null<CXXMethodDecl>(Val: Call.getDecl());
105 if (!MD)
106 return;
107
108 ProgramStateRef State = C.getState();
109 // Member calls are always represented by a call-expression.
110 const auto *CE = cast<CallExpr>(Val: Call.getOriginExpr());
111 if (!isVirtualCall(CE))
112 return;
113
114 const MemRegion *Reg = MC->getCXXThisVal().getAsRegion();
115 const ObjectState *ObState = State->get<CtorDtorMap>(key: Reg);
116 if (!ObState)
117 return;
118
119 bool IsPure = MD->isPureVirtual();
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 SmallString<128> Msg;
124 llvm::raw_svector_ostream OS(Msg);
125 OS << "Call to ";
126 if (IsPure)
127 OS << "pure ";
128 OS << "virtual method '" << MD->getParent()->getDeclName()
129 << "::" << MD->getDeclName() << "' during ";
130 if (*ObState == ObjectState::CtorCalled)
131 OS << "construction ";
132 else
133 OS << "destruction ";
134 if (IsPure)
135 OS << "has undefined behavior";
136 else
137 OS << "bypasses virtual dispatch";
138
139 ExplodedNode *N =
140 IsPure ? C.generateErrorNode() : C.generateNonFatalErrorNode();
141 if (!N)
142 return;
143
144 const std::unique_ptr<BugType> &BT = IsPure ? BT_Pure : BT_Impure;
145 if (!BT) {
146 // The respective check is disabled.
147 return;
148 }
149
150 auto Report = std::make_unique<PathSensitiveBugReport>(args&: *BT, args: OS.str(), args&: N);
151
152 if (ShowFixIts && !IsPure) {
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 FixItHint Fixit = FixItHint::CreateInsertion(
158 InsertionLoc: CE->getBeginLoc(), Code: MD->getParent()->getNameAsString() + "::");
159 Report->addFixItHint(F: Fixit);
160 }
161
162 C.emitReport(R: std::move(Report));
163}
164
165void VirtualCallChecker::registerCtorDtorCallInState(bool IsBeginFunction,
166 CheckerContext &C) const {
167 const auto *LCtx = C.getLocationContext();
168 const auto *MD = dyn_cast_or_null<CXXMethodDecl>(Val: LCtx->getDecl());
169 if (!MD)
170 return;
171
172 ProgramStateRef State = C.getState();
173 auto &SVB = C.getSValBuilder();
174
175 // Enter a constructor, set the corresponding memregion be true.
176 if (isa<CXXConstructorDecl>(Val: MD)) {
177 auto ThiSVal =
178 State->getSVal(LV: SVB.getCXXThis(D: MD, SFC: LCtx->getStackFrame()));
179 const MemRegion *Reg = ThiSVal.getAsRegion();
180 if (IsBeginFunction)
181 State = State->set<CtorDtorMap>(K: Reg, E: ObjectState::CtorCalled);
182 else
183 State = State->remove<CtorDtorMap>(K: Reg);
184
185 C.addTransition(State);
186 return;
187 }
188
189 // Enter a Destructor, set the corresponding memregion be true.
190 if (isa<CXXDestructorDecl>(Val: MD)) {
191 auto ThiSVal =
192 State->getSVal(LV: SVB.getCXXThis(D: MD, SFC: LCtx->getStackFrame()));
193 const MemRegion *Reg = ThiSVal.getAsRegion();
194 if (IsBeginFunction)
195 State = State->set<CtorDtorMap>(K: Reg, E: ObjectState::DtorCalled);
196 else
197 State = State->remove<CtorDtorMap>(K: Reg);
198
199 C.addTransition(State);
200 return;
201 }
202}
203
204void ento::registerVirtualCallModeling(CheckerManager &Mgr) {
205 Mgr.registerChecker<VirtualCallChecker>();
206}
207
208void ento::registerPureVirtualCallChecker(CheckerManager &Mgr) {
209 auto *Chk = Mgr.getChecker<VirtualCallChecker>();
210 Chk->BT_Pure = std::make_unique<BugType>(args: Mgr.getCurrentCheckerName(),
211 args: "Pure virtual method call",
212 args: categories::CXXObjectLifecycle);
213}
214
215void ento::registerVirtualCallChecker(CheckerManager &Mgr) {
216 auto *Chk = Mgr.getChecker<VirtualCallChecker>();
217 if (!Mgr.getAnalyzerOptions().getCheckerBooleanOption(
218 CheckerName: Mgr.getCurrentCheckerName(), OptionName: "PureOnly")) {
219 Chk->BT_Impure = std::make_unique<BugType>(
220 args: Mgr.getCurrentCheckerName(), args: "Unexpected loss of virtual dispatch",
221 args: categories::CXXObjectLifecycle);
222 Chk->ShowFixIts = Mgr.getAnalyzerOptions().getCheckerBooleanOption(
223 CheckerName: Mgr.getCurrentCheckerName(), OptionName: "ShowFixIts");
224 }
225}
226
227bool ento::shouldRegisterVirtualCallModeling(const CheckerManager &mgr) {
228 const LangOptions &LO = mgr.getLangOpts();
229 return LO.CPlusPlus;
230}
231
232bool ento::shouldRegisterPureVirtualCallChecker(const CheckerManager &mgr) {
233 const LangOptions &LO = mgr.getLangOpts();
234 return LO.CPlusPlus;
235}
236
237bool ento::shouldRegisterVirtualCallChecker(const CheckerManager &mgr) {
238 const LangOptions &LO = mgr.getLangOpts();
239 return LO.CPlusPlus;
240}
241