1b3512d3aSTed Kremenek //=======- VirtualCallChecker.cpp --------------------------------*- C++ -*-==//
2b3512d3aSTed Kremenek //
32946cd70SChandler Carruth // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
42946cd70SChandler Carruth // See https://llvm.org/LICENSE.txt for license information.
52946cd70SChandler Carruth // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6b3512d3aSTed Kremenek //
7b3512d3aSTed Kremenek //===----------------------------------------------------------------------===//
8b3512d3aSTed Kremenek //
9d3971fe9SArtem Dergachev //  This file defines a checker that checks virtual method calls during
10b3512d3aSTed Kremenek //  construction or destruction of C++ objects.
11b3512d3aSTed Kremenek //
12b3512d3aSTed Kremenek //===----------------------------------------------------------------------===//
13b3512d3aSTed Kremenek 
1460573ae6SReid Kleckner #include "clang/AST/Attr.h"
15b3512d3aSTed Kremenek #include "clang/AST/DeclCXX.h"
1660573ae6SReid Kleckner #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
17b3512d3aSTed Kremenek #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
18857ccd29SGabor Horvath #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
193a02247dSChandler Carruth #include "clang/StaticAnalyzer/Core/Checker.h"
20857ccd29SGabor Horvath #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
21857ccd29SGabor Horvath #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
22857ccd29SGabor Horvath #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
23857ccd29SGabor Horvath #include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h"
24b3512d3aSTed Kremenek 
25b3512d3aSTed Kremenek using namespace clang;
26b3512d3aSTed Kremenek using namespace ento;
27b3512d3aSTed Kremenek 
28b3512d3aSTed Kremenek namespace {
29857ccd29SGabor Horvath enum class ObjectState : bool { CtorCalled, DtorCalled };
30857ccd29SGabor Horvath } // end namespace
31857ccd29SGabor Horvath   // FIXME: Ascending over StackFrameContext maybe another method.
32b3512d3aSTed Kremenek 
33857ccd29SGabor Horvath namespace llvm {
34857ccd29SGabor Horvath template <> struct FoldingSetTrait<ObjectState> {
Profilellvm::FoldingSetTrait35857ccd29SGabor Horvath   static inline void Profile(ObjectState X, FoldingSetNodeID &ID) {
36857ccd29SGabor Horvath     ID.AddInteger(static_cast<int>(X));
37857ccd29SGabor Horvath   }
38d1d76b2dSBenjamin Kramer };
39857ccd29SGabor Horvath } // end namespace llvm
40b3512d3aSTed Kremenek 
41b3512d3aSTed Kremenek namespace {
42857ccd29SGabor Horvath class VirtualCallChecker
43857ccd29SGabor Horvath     : public Checker<check::BeginFunction, check::EndFunction, check::PreCall> {
44b3512d3aSTed Kremenek public:
45d3971fe9SArtem Dergachev   // These are going to be null if the respective check is disabled.
46d3971fe9SArtem Dergachev   mutable std::unique_ptr<BugType> BT_Pure, BT_Impure;
476cee434eSArtem Dergachev   bool ShowFixIts = false;
483e5f0474SDevin Coughlin 
49857ccd29SGabor Horvath   void checkBeginFunction(CheckerContext &C) const;
50ed8c05ccSReka Kovacs   void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
51857ccd29SGabor Horvath   void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
52b3512d3aSTed Kremenek 
53857ccd29SGabor Horvath private:
54857ccd29SGabor Horvath   void registerCtorDtorCallInState(bool IsBeginFunction,
55857ccd29SGabor Horvath                                    CheckerContext &C) const;
56857ccd29SGabor Horvath };
57857ccd29SGabor Horvath } // end namespace
58857ccd29SGabor Horvath 
59857ccd29SGabor Horvath // GDM (generic data map) to the memregion of this for the ctor and dtor.
REGISTER_MAP_WITH_PROGRAMSTATE(CtorDtorMap,const MemRegion *,ObjectState)60857ccd29SGabor Horvath REGISTER_MAP_WITH_PROGRAMSTATE(CtorDtorMap, const MemRegion *, ObjectState)
61857ccd29SGabor Horvath 
62d3971fe9SArtem Dergachev // The function to check if a callexpr is a virtual method call.
63857ccd29SGabor Horvath static bool isVirtualCall(const CallExpr *CE) {
64857ccd29SGabor Horvath   bool CallIsNonVirtual = false;
65857ccd29SGabor Horvath 
66857ccd29SGabor Horvath   if (const MemberExpr *CME = dyn_cast<MemberExpr>(CE->getCallee())) {
67857ccd29SGabor Horvath     // The member access is fully qualified (i.e., X::F).
68857ccd29SGabor Horvath     // Treat this as a non-virtual call and do not warn.
69857ccd29SGabor Horvath     if (CME->getQualifier())
70857ccd29SGabor Horvath       CallIsNonVirtual = true;
71857ccd29SGabor Horvath 
725536a01aSGabor Horvath     if (const Expr *Base = CME->getBase()) {
73857ccd29SGabor Horvath       // The most derived class is marked final.
74857ccd29SGabor Horvath       if (Base->getBestDynamicClassType()->hasAttr<FinalAttr>())
75857ccd29SGabor Horvath         CallIsNonVirtual = true;
76857ccd29SGabor Horvath     }
77857ccd29SGabor Horvath   }
78857ccd29SGabor Horvath 
79857ccd29SGabor Horvath   const CXXMethodDecl *MD =
80857ccd29SGabor Horvath       dyn_cast_or_null<CXXMethodDecl>(CE->getDirectCallee());
81857ccd29SGabor Horvath   if (MD && MD->isVirtual() && !CallIsNonVirtual && !MD->hasAttr<FinalAttr>() &&
82857ccd29SGabor Horvath       !MD->getParent()->hasAttr<FinalAttr>())
83857ccd29SGabor Horvath     return true;
84857ccd29SGabor Horvath   return false;
85857ccd29SGabor Horvath }
86857ccd29SGabor Horvath 
87857ccd29SGabor Horvath // The BeginFunction callback when enter a constructor or a destructor.
checkBeginFunction(CheckerContext & C) const88857ccd29SGabor Horvath void VirtualCallChecker::checkBeginFunction(CheckerContext &C) const {
89857ccd29SGabor Horvath   registerCtorDtorCallInState(true, C);
90857ccd29SGabor Horvath }
91857ccd29SGabor Horvath 
92857ccd29SGabor Horvath // The EndFunction callback when leave a constructor or a destructor.
checkEndFunction(const ReturnStmt * RS,CheckerContext & C) const93ed8c05ccSReka Kovacs void VirtualCallChecker::checkEndFunction(const ReturnStmt *RS,
94ed8c05ccSReka Kovacs                                           CheckerContext &C) const {
95857ccd29SGabor Horvath   registerCtorDtorCallInState(false, C);
96857ccd29SGabor Horvath }
97857ccd29SGabor Horvath 
checkPreCall(const CallEvent & Call,CheckerContext & C) const98857ccd29SGabor Horvath void VirtualCallChecker::checkPreCall(const CallEvent &Call,
99857ccd29SGabor Horvath                                       CheckerContext &C) const {
100857ccd29SGabor Horvath   const auto MC = dyn_cast<CXXMemberCall>(&Call);
101857ccd29SGabor Horvath   if (!MC)
102857ccd29SGabor Horvath     return;
103857ccd29SGabor Horvath 
104857ccd29SGabor Horvath   const CXXMethodDecl *MD = dyn_cast_or_null<CXXMethodDecl>(Call.getDecl());
105857ccd29SGabor Horvath   if (!MD)
106857ccd29SGabor Horvath     return;
107d3971fe9SArtem Dergachev 
108857ccd29SGabor Horvath   ProgramStateRef State = C.getState();
109630f7dafSArtem Dergachev   // Member calls are always represented by a call-expression.
110630f7dafSArtem Dergachev   const auto *CE = cast<CallExpr>(Call.getOriginExpr());
111857ccd29SGabor Horvath   if (!isVirtualCall(CE))
112857ccd29SGabor Horvath     return;
113857ccd29SGabor Horvath 
114857ccd29SGabor Horvath   const MemRegion *Reg = MC->getCXXThisVal().getAsRegion();
115857ccd29SGabor Horvath   const ObjectState *ObState = State->get<CtorDtorMap>(Reg);
116857ccd29SGabor Horvath   if (!ObState)
117857ccd29SGabor Horvath     return;
118d3971fe9SArtem Dergachev 
119d3971fe9SArtem Dergachev   bool IsPure = MD->isPure();
120d3971fe9SArtem Dergachev 
121d3971fe9SArtem Dergachev   // At this point we're sure that we're calling a virtual method
122d3971fe9SArtem Dergachev   // during construction or destruction, so we'll emit a report.
123d3971fe9SArtem Dergachev   SmallString<128> Msg;
124d3971fe9SArtem Dergachev   llvm::raw_svector_ostream OS(Msg);
125d3971fe9SArtem Dergachev   OS << "Call to ";
126d3971fe9SArtem Dergachev   if (IsPure)
127d3971fe9SArtem Dergachev     OS << "pure ";
128*19701458SBruno Ricci   OS << "virtual method '" << MD->getParent()->getDeclName()
129*19701458SBruno Ricci      << "::" << MD->getDeclName() << "' during ";
130d3971fe9SArtem Dergachev   if (*ObState == ObjectState::CtorCalled)
131d3971fe9SArtem Dergachev     OS << "construction ";
132857ccd29SGabor Horvath   else
133d3971fe9SArtem Dergachev     OS << "destruction ";
134d3971fe9SArtem Dergachev   if (IsPure)
135d3971fe9SArtem Dergachev     OS << "has undefined behavior";
136d3971fe9SArtem Dergachev   else
137d3971fe9SArtem Dergachev     OS << "bypasses virtual dispatch";
138d3971fe9SArtem Dergachev 
139d3971fe9SArtem Dergachev   ExplodedNode *N =
140d3971fe9SArtem Dergachev       IsPure ? C.generateErrorNode() : C.generateNonFatalErrorNode();
141d3971fe9SArtem Dergachev   if (!N)
142d3971fe9SArtem Dergachev     return;
143d3971fe9SArtem Dergachev 
144d3971fe9SArtem Dergachev   const std::unique_ptr<BugType> &BT = IsPure ? BT_Pure : BT_Impure;
145d3971fe9SArtem Dergachev   if (!BT) {
146d3971fe9SArtem Dergachev     // The respective check is disabled.
147d3971fe9SArtem Dergachev     return;
148857ccd29SGabor Horvath   }
149857ccd29SGabor Horvath 
1502f169e7cSArtem Dergachev   auto Report = std::make_unique<PathSensitiveBugReport>(*BT, OS.str(), N);
1516cee434eSArtem Dergachev 
1526cee434eSArtem Dergachev   if (ShowFixIts && !IsPure) {
1536cee434eSArtem Dergachev     // FIXME: These hints are valid only when the virtual call is made
1546cee434eSArtem Dergachev     // directly from the constructor/destructor. Otherwise the dispatch
1556cee434eSArtem Dergachev     // will work just fine from other callees, and the fix may break
1566cee434eSArtem Dergachev     // the otherwise correct program.
1576cee434eSArtem Dergachev     FixItHint Fixit = FixItHint::CreateInsertion(
1586cee434eSArtem Dergachev         CE->getBeginLoc(), MD->getParent()->getNameAsString() + "::");
1596cee434eSArtem Dergachev     Report->addFixItHint(Fixit);
1606cee434eSArtem Dergachev   }
1616cee434eSArtem Dergachev 
162d3971fe9SArtem Dergachev   C.emitReport(std::move(Report));
163857ccd29SGabor Horvath }
164857ccd29SGabor Horvath 
registerCtorDtorCallInState(bool IsBeginFunction,CheckerContext & C) const165857ccd29SGabor Horvath void VirtualCallChecker::registerCtorDtorCallInState(bool IsBeginFunction,
166857ccd29SGabor Horvath                                                      CheckerContext &C) const {
167857ccd29SGabor Horvath   const auto *LCtx = C.getLocationContext();
168857ccd29SGabor Horvath   const auto *MD = dyn_cast_or_null<CXXMethodDecl>(LCtx->getDecl());
169857ccd29SGabor Horvath   if (!MD)
170857ccd29SGabor Horvath     return;
171857ccd29SGabor Horvath 
172857ccd29SGabor Horvath   ProgramStateRef State = C.getState();
173857ccd29SGabor Horvath   auto &SVB = C.getSValBuilder();
174857ccd29SGabor Horvath 
175857ccd29SGabor Horvath   // Enter a constructor, set the corresponding memregion be true.
176857ccd29SGabor Horvath   if (isa<CXXConstructorDecl>(MD)) {
177857ccd29SGabor Horvath     auto ThiSVal =
178dd18b11bSGeorge Karpenkov         State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame()));
179857ccd29SGabor Horvath     const MemRegion *Reg = ThiSVal.getAsRegion();
180857ccd29SGabor Horvath     if (IsBeginFunction)
181857ccd29SGabor Horvath       State = State->set<CtorDtorMap>(Reg, ObjectState::CtorCalled);
182857ccd29SGabor Horvath     else
183857ccd29SGabor Horvath       State = State->remove<CtorDtorMap>(Reg);
184857ccd29SGabor Horvath 
185857ccd29SGabor Horvath     C.addTransition(State);
186857ccd29SGabor Horvath     return;
187857ccd29SGabor Horvath   }
188857ccd29SGabor Horvath 
189857ccd29SGabor Horvath   // Enter a Destructor, set the corresponding memregion be true.
190857ccd29SGabor Horvath   if (isa<CXXDestructorDecl>(MD)) {
191857ccd29SGabor Horvath     auto ThiSVal =
192dd18b11bSGeorge Karpenkov         State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame()));
193857ccd29SGabor Horvath     const MemRegion *Reg = ThiSVal.getAsRegion();
194857ccd29SGabor Horvath     if (IsBeginFunction)
195857ccd29SGabor Horvath       State = State->set<CtorDtorMap>(Reg, ObjectState::DtorCalled);
196857ccd29SGabor Horvath     else
197857ccd29SGabor Horvath       State = State->remove<CtorDtorMap>(Reg);
198857ccd29SGabor Horvath 
199857ccd29SGabor Horvath     C.addTransition(State);
200857ccd29SGabor Horvath     return;
201857ccd29SGabor Horvath   }
202857ccd29SGabor Horvath }
203857ccd29SGabor Horvath 
registerVirtualCallModeling(CheckerManager & Mgr)204d3971fe9SArtem Dergachev void ento::registerVirtualCallModeling(CheckerManager &Mgr) {
205d3971fe9SArtem Dergachev   Mgr.registerChecker<VirtualCallChecker>();
206ab9db510SAlexander Kornienko }
207b3512d3aSTed Kremenek 
registerPureVirtualCallChecker(CheckerManager & Mgr)208d3971fe9SArtem Dergachev void ento::registerPureVirtualCallChecker(CheckerManager &Mgr) {
209d3971fe9SArtem Dergachev   auto *Chk = Mgr.getChecker<VirtualCallChecker>();
21072649423SKristof Umann   Chk->BT_Pure = std::make_unique<BugType>(Mgr.getCurrentCheckerName(),
21172649423SKristof Umann                                            "Pure virtual method call",
212d3971fe9SArtem Dergachev                                            categories::CXXObjectLifecycle);
213d3971fe9SArtem Dergachev }
2143e5f0474SDevin Coughlin 
registerVirtualCallChecker(CheckerManager & Mgr)215d3971fe9SArtem Dergachev void ento::registerVirtualCallChecker(CheckerManager &Mgr) {
216d3971fe9SArtem Dergachev   auto *Chk = Mgr.getChecker<VirtualCallChecker>();
217d3971fe9SArtem Dergachev   if (!Mgr.getAnalyzerOptions().getCheckerBooleanOption(
21872649423SKristof Umann           Mgr.getCurrentCheckerName(), "PureOnly")) {
219d3971fe9SArtem Dergachev     Chk->BT_Impure = std::make_unique<BugType>(
22072649423SKristof Umann         Mgr.getCurrentCheckerName(), "Unexpected loss of virtual dispatch",
221d3971fe9SArtem Dergachev         categories::CXXObjectLifecycle);
2226cee434eSArtem Dergachev     Chk->ShowFixIts = Mgr.getAnalyzerOptions().getCheckerBooleanOption(
22372649423SKristof Umann         Mgr.getCurrentCheckerName(), "ShowFixIts");
224d3971fe9SArtem Dergachev   }
225d3971fe9SArtem Dergachev }
226d3971fe9SArtem Dergachev 
shouldRegisterVirtualCallModeling(const CheckerManager & mgr)227bda3dd0dSKirstóf Umann bool ento::shouldRegisterVirtualCallModeling(const CheckerManager &mgr) {
228bda3dd0dSKirstóf Umann   const LangOptions &LO = mgr.getLangOpts();
229d3971fe9SArtem Dergachev   return LO.CPlusPlus;
230d3971fe9SArtem Dergachev }
231d3971fe9SArtem Dergachev 
shouldRegisterPureVirtualCallChecker(const CheckerManager & mgr)232bda3dd0dSKirstóf Umann bool ento::shouldRegisterPureVirtualCallChecker(const CheckerManager &mgr) {
233bda3dd0dSKirstóf Umann   const LangOptions &LO = mgr.getLangOpts();
234d3971fe9SArtem Dergachev   return LO.CPlusPlus;
235b3512d3aSTed Kremenek }
236058a7a45SKristof Umann 
shouldRegisterVirtualCallChecker(const CheckerManager & mgr)237bda3dd0dSKirstóf Umann bool ento::shouldRegisterVirtualCallChecker(const CheckerManager &mgr) {
238bda3dd0dSKirstóf Umann   const LangOptions &LO = mgr.getLangOpts();
239d3971fe9SArtem Dergachev   return LO.CPlusPlus;
240058a7a45SKristof Umann }
241