1 //=======- VirtualCallChecker.cpp --------------------------------*- C++ -*-==//
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
7 //===----------------------------------------------------------------------===//
9 // This file defines a checker that checks virtual method calls during
10 // construction or destruction of C++ objects.
12 //===----------------------------------------------------------------------===//
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"
25 using namespace clang;
29 enum class ObjectState : bool { CtorCalled, DtorCalled };
31 // FIXME: Ascending over StackFrameContext maybe another method.
34 template <> struct FoldingSetTrait<ObjectState> {
35 static inline void Profile(ObjectState X, FoldingSetNodeID &ID) {
36 ID.AddInteger(static_cast<int>(X));
39 } // end namespace llvm
42 class VirtualCallChecker
43 : public Checker<check::BeginFunction, check::EndFunction, check::PreCall> {
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;
49 void checkBeginFunction(CheckerContext &C) const;
50 void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
51 void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
54 void registerCtorDtorCallInState(bool IsBeginFunction,
55 CheckerContext &C) const;
59 // GDM (generic data map) to the memregion of this for the ctor and dtor.
60 REGISTER_MAP_WITH_PROGRAMSTATE(CtorDtorMap, const MemRegion *, ObjectState)
62 // The function to check if a callexpr is a virtual method call.
63 static bool isVirtualCall(const CallExpr *CE) {
64 bool CallIsNonVirtual = false;
66 if (const MemberExpr *CME = dyn_cast<MemberExpr>(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;
72 if (const Expr *Base = CME->getBase()) {
73 // The most derived class is marked final.
74 if (Base->getBestDynamicClassType()->hasAttr<FinalAttr>())
75 CallIsNonVirtual = true;
79 const CXXMethodDecl *MD =
80 dyn_cast_or_null<CXXMethodDecl>(CE->getDirectCallee());
81 if (MD && MD->isVirtual() && !CallIsNonVirtual && !MD->hasAttr<FinalAttr>() &&
82 !MD->getParent()->hasAttr<FinalAttr>())
87 // The BeginFunction callback when enter a constructor or a destructor.
88 void VirtualCallChecker::checkBeginFunction(CheckerContext &C) const {
89 registerCtorDtorCallInState(true, C);
92 // The EndFunction callback when leave a constructor or a destructor.
93 void VirtualCallChecker::checkEndFunction(const ReturnStmt *RS,
94 CheckerContext &C) const {
95 registerCtorDtorCallInState(false, C);
98 void VirtualCallChecker::checkPreCall(const CallEvent &Call,
99 CheckerContext &C) const {
100 const auto MC = dyn_cast<CXXMemberCall>(&Call);
104 const CXXMethodDecl *MD = dyn_cast_or_null<CXXMethodDecl>(Call.getDecl());
108 ProgramStateRef State = C.getState();
109 // Member calls are always represented by a call-expression.
110 const auto *CE = cast<CallExpr>(Call.getOriginExpr());
111 if (!isVirtualCall(CE))
114 const MemRegion *Reg = MC->getCXXThisVal().getAsRegion();
115 const ObjectState *ObState = State->get<CtorDtorMap>(Reg);
119 bool IsPure = MD->isPure();
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);
128 OS << "virtual method '" << MD->getParent()->getNameAsString()
129 << "::" << MD->getNameAsString() << "' during ";
130 if (*ObState == ObjectState::CtorCalled)
131 OS << "construction ";
133 OS << "destruction ";
135 OS << "has undefined behavior";
137 OS << "bypasses virtual dispatch";
140 IsPure ? C.generateErrorNode() : C.generateNonFatalErrorNode();
144 const std::unique_ptr<BugType> &BT = IsPure ? BT_Pure : BT_Impure;
146 // The respective check is disabled.
150 auto Report = std::make_unique<PathSensitiveBugReport>(*BT, OS.str(), N);
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 CE->getBeginLoc(), MD->getParent()->getNameAsString() + "::");
159 Report->addFixItHint(Fixit);
162 C.emitReport(std::move(Report));
165 void VirtualCallChecker::registerCtorDtorCallInState(bool IsBeginFunction,
166 CheckerContext &C) const {
167 const auto *LCtx = C.getLocationContext();
168 const auto *MD = dyn_cast_or_null<CXXMethodDecl>(LCtx->getDecl());
172 ProgramStateRef State = C.getState();
173 auto &SVB = C.getSValBuilder();
175 // Enter a constructor, set the corresponding memregion be true.
176 if (isa<CXXConstructorDecl>(MD)) {
178 State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame()));
179 const MemRegion *Reg = ThiSVal.getAsRegion();
181 State = State->set<CtorDtorMap>(Reg, ObjectState::CtorCalled);
183 State = State->remove<CtorDtorMap>(Reg);
185 C.addTransition(State);
189 // Enter a Destructor, set the corresponding memregion be true.
190 if (isa<CXXDestructorDecl>(MD)) {
192 State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame()));
193 const MemRegion *Reg = ThiSVal.getAsRegion();
195 State = State->set<CtorDtorMap>(Reg, ObjectState::DtorCalled);
197 State = State->remove<CtorDtorMap>(Reg);
199 C.addTransition(State);
204 void ento::registerVirtualCallModeling(CheckerManager &Mgr) {
205 Mgr.registerChecker<VirtualCallChecker>();
208 void ento::registerPureVirtualCallChecker(CheckerManager &Mgr) {
209 auto *Chk = Mgr.getChecker<VirtualCallChecker>();
210 Chk->BT_Pure = std::make_unique<BugType>(Mgr.getCurrentCheckerName(),
211 "Pure virtual method call",
212 categories::CXXObjectLifecycle);
215 void ento::registerVirtualCallChecker(CheckerManager &Mgr) {
216 auto *Chk = Mgr.getChecker<VirtualCallChecker>();
217 if (!Mgr.getAnalyzerOptions().getCheckerBooleanOption(
218 Mgr.getCurrentCheckerName(), "PureOnly")) {
219 Chk->BT_Impure = std::make_unique<BugType>(
220 Mgr.getCurrentCheckerName(), "Unexpected loss of virtual dispatch",
221 categories::CXXObjectLifecycle);
222 Chk->ShowFixIts = Mgr.getAnalyzerOptions().getCheckerBooleanOption(
223 Mgr.getCurrentCheckerName(), "ShowFixIts");
227 bool ento::shouldRegisterVirtualCallModeling(const LangOptions &LO) {
231 bool ento::shouldRegisterPureVirtualCallChecker(const LangOptions &LO) {
235 bool ento::shouldRegisterVirtualCallChecker(const LangOptions &LO) {