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 const MemRegion *Reg = MC->getCXXThisVal().getAsRegion();
122 const ObjectState *ObState = State->get<CtorDtorMap>(key: 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>(args: Part, args: OS.str(), args&: 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 InsertionLoc: CE->getBeginLoc(), Code: MD->getParent()->getNameAsString() + "::");
167 Report->addFixItHint(F: Fixit);
168 }
169
170 C.emitReport(R: std::move(Report));
171}
172
173void VirtualCallChecker::registerCtorDtorCallInState(bool IsBeginFunction,
174 CheckerContext &C) const {
175 const auto *LCtx = C.getLocationContext();
176 const auto *MD = dyn_cast_or_null<CXXMethodDecl>(Val: 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>(Val: MD)) {
185 auto ThiSVal =
186 State->getSVal(LV: SVB.getCXXThis(D: MD, SFC: LCtx->getStackFrame()));
187 const MemRegion *Reg = ThiSVal.getAsRegion();
188 if (IsBeginFunction)
189 State = State->set<CtorDtorMap>(K: Reg, E: ObjectState::CtorCalled);
190 else
191 State = State->remove<CtorDtorMap>(K: Reg);
192
193 C.addTransition(State);
194 return;
195 }
196
197 // Enter a Destructor, set the corresponding memregion be true.
198 if (isa<CXXDestructorDecl>(Val: MD)) {
199 auto ThiSVal =
200 State->getSVal(LV: SVB.getCXXThis(D: MD, SFC: LCtx->getStackFrame()));
201 const MemRegion *Reg = ThiSVal.getAsRegion();
202 if (IsBeginFunction)
203 State = State->set<CtorDtorMap>(K: Reg, E: ObjectState::DtorCalled);
204 else
205 State = State->remove<CtorDtorMap>(K: Reg);
206
207 C.addTransition(State);
208 return;
209 }
210}
211
212void ento::registerPureVirtualCallChecker(CheckerManager &Mgr) {
213 Mgr.getChecker<VirtualCallChecker>()->PureChecker.enable(Mgr);
214}
215
216bool ento::shouldRegisterPureVirtualCallChecker(const CheckerManager &Mgr) {
217 return Mgr.getLangOpts().CPlusPlus;
218}
219
220void ento::registerVirtualCallChecker(CheckerManager &Mgr) {
221 auto *Chk = Mgr.getChecker<VirtualCallChecker>();
222 Chk->ImpureChecker.enable(Mgr);
223 Chk->ShowFixIts = Mgr.getAnalyzerOptions().getCheckerBooleanOption(
224 CheckerName: Mgr.getCurrentCheckerName(), OptionName: "ShowFixIts");
225}
226
227bool ento::shouldRegisterVirtualCallChecker(const CheckerManager &Mgr) {
228 return Mgr.getLangOpts().CPlusPlus;
229}
230