xref: /openbsd-src/gnu/llvm/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp (revision a9ac8606c53d55cee9c3a39778b249c51df111ef)
1e5dd7070Spatrick //=======- VirtualCallChecker.cpp --------------------------------*- C++ -*-==//
2e5dd7070Spatrick //
3e5dd7070Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4e5dd7070Spatrick // See https://llvm.org/LICENSE.txt for license information.
5e5dd7070Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6e5dd7070Spatrick //
7e5dd7070Spatrick //===----------------------------------------------------------------------===//
8e5dd7070Spatrick //
9e5dd7070Spatrick //  This file defines a checker that checks virtual method calls during
10e5dd7070Spatrick //  construction or destruction of C++ objects.
11e5dd7070Spatrick //
12e5dd7070Spatrick //===----------------------------------------------------------------------===//
13e5dd7070Spatrick 
14e5dd7070Spatrick #include "clang/AST/Attr.h"
15e5dd7070Spatrick #include "clang/AST/DeclCXX.h"
16e5dd7070Spatrick #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
17e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
18e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
19e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/Checker.h"
20e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
21e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
22e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
23e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h"
24e5dd7070Spatrick 
25e5dd7070Spatrick using namespace clang;
26e5dd7070Spatrick using namespace ento;
27e5dd7070Spatrick 
28e5dd7070Spatrick namespace {
29e5dd7070Spatrick enum class ObjectState : bool { CtorCalled, DtorCalled };
30e5dd7070Spatrick } // end namespace
31e5dd7070Spatrick   // FIXME: Ascending over StackFrameContext maybe another method.
32e5dd7070Spatrick 
33e5dd7070Spatrick namespace llvm {
34e5dd7070Spatrick template <> struct FoldingSetTrait<ObjectState> {
Profilellvm::FoldingSetTrait35e5dd7070Spatrick   static inline void Profile(ObjectState X, FoldingSetNodeID &ID) {
36e5dd7070Spatrick     ID.AddInteger(static_cast<int>(X));
37e5dd7070Spatrick   }
38e5dd7070Spatrick };
39e5dd7070Spatrick } // end namespace llvm
40e5dd7070Spatrick 
41e5dd7070Spatrick namespace {
42e5dd7070Spatrick class VirtualCallChecker
43e5dd7070Spatrick     : public Checker<check::BeginFunction, check::EndFunction, check::PreCall> {
44e5dd7070Spatrick public:
45e5dd7070Spatrick   // These are going to be null if the respective check is disabled.
46e5dd7070Spatrick   mutable std::unique_ptr<BugType> BT_Pure, BT_Impure;
47e5dd7070Spatrick   bool ShowFixIts = false;
48e5dd7070Spatrick 
49e5dd7070Spatrick   void checkBeginFunction(CheckerContext &C) const;
50e5dd7070Spatrick   void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
51e5dd7070Spatrick   void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
52e5dd7070Spatrick 
53e5dd7070Spatrick private:
54e5dd7070Spatrick   void registerCtorDtorCallInState(bool IsBeginFunction,
55e5dd7070Spatrick                                    CheckerContext &C) const;
56e5dd7070Spatrick };
57e5dd7070Spatrick } // end namespace
58e5dd7070Spatrick 
59e5dd7070Spatrick // GDM (generic data map) to the memregion of this for the ctor and dtor.
REGISTER_MAP_WITH_PROGRAMSTATE(CtorDtorMap,const MemRegion *,ObjectState)60e5dd7070Spatrick REGISTER_MAP_WITH_PROGRAMSTATE(CtorDtorMap, const MemRegion *, ObjectState)
61e5dd7070Spatrick 
62e5dd7070Spatrick // The function to check if a callexpr is a virtual method call.
63e5dd7070Spatrick static bool isVirtualCall(const CallExpr *CE) {
64e5dd7070Spatrick   bool CallIsNonVirtual = false;
65e5dd7070Spatrick 
66e5dd7070Spatrick   if (const MemberExpr *CME = dyn_cast<MemberExpr>(CE->getCallee())) {
67e5dd7070Spatrick     // The member access is fully qualified (i.e., X::F).
68e5dd7070Spatrick     // Treat this as a non-virtual call and do not warn.
69e5dd7070Spatrick     if (CME->getQualifier())
70e5dd7070Spatrick       CallIsNonVirtual = true;
71e5dd7070Spatrick 
72e5dd7070Spatrick     if (const Expr *Base = CME->getBase()) {
73e5dd7070Spatrick       // The most derived class is marked final.
74e5dd7070Spatrick       if (Base->getBestDynamicClassType()->hasAttr<FinalAttr>())
75e5dd7070Spatrick         CallIsNonVirtual = true;
76e5dd7070Spatrick     }
77e5dd7070Spatrick   }
78e5dd7070Spatrick 
79e5dd7070Spatrick   const CXXMethodDecl *MD =
80e5dd7070Spatrick       dyn_cast_or_null<CXXMethodDecl>(CE->getDirectCallee());
81e5dd7070Spatrick   if (MD && MD->isVirtual() && !CallIsNonVirtual && !MD->hasAttr<FinalAttr>() &&
82e5dd7070Spatrick       !MD->getParent()->hasAttr<FinalAttr>())
83e5dd7070Spatrick     return true;
84e5dd7070Spatrick   return false;
85e5dd7070Spatrick }
86e5dd7070Spatrick 
87e5dd7070Spatrick // The BeginFunction callback when enter a constructor or a destructor.
checkBeginFunction(CheckerContext & C) const88e5dd7070Spatrick void VirtualCallChecker::checkBeginFunction(CheckerContext &C) const {
89e5dd7070Spatrick   registerCtorDtorCallInState(true, C);
90e5dd7070Spatrick }
91e5dd7070Spatrick 
92e5dd7070Spatrick // The EndFunction callback when leave a constructor or a destructor.
checkEndFunction(const ReturnStmt * RS,CheckerContext & C) const93e5dd7070Spatrick void VirtualCallChecker::checkEndFunction(const ReturnStmt *RS,
94e5dd7070Spatrick                                           CheckerContext &C) const {
95e5dd7070Spatrick   registerCtorDtorCallInState(false, C);
96e5dd7070Spatrick }
97e5dd7070Spatrick 
checkPreCall(const CallEvent & Call,CheckerContext & C) const98e5dd7070Spatrick void VirtualCallChecker::checkPreCall(const CallEvent &Call,
99e5dd7070Spatrick                                       CheckerContext &C) const {
100e5dd7070Spatrick   const auto MC = dyn_cast<CXXMemberCall>(&Call);
101e5dd7070Spatrick   if (!MC)
102e5dd7070Spatrick     return;
103e5dd7070Spatrick 
104e5dd7070Spatrick   const CXXMethodDecl *MD = dyn_cast_or_null<CXXMethodDecl>(Call.getDecl());
105e5dd7070Spatrick   if (!MD)
106e5dd7070Spatrick     return;
107e5dd7070Spatrick 
108e5dd7070Spatrick   ProgramStateRef State = C.getState();
109e5dd7070Spatrick   // Member calls are always represented by a call-expression.
110e5dd7070Spatrick   const auto *CE = cast<CallExpr>(Call.getOriginExpr());
111e5dd7070Spatrick   if (!isVirtualCall(CE))
112e5dd7070Spatrick     return;
113e5dd7070Spatrick 
114e5dd7070Spatrick   const MemRegion *Reg = MC->getCXXThisVal().getAsRegion();
115e5dd7070Spatrick   const ObjectState *ObState = State->get<CtorDtorMap>(Reg);
116e5dd7070Spatrick   if (!ObState)
117e5dd7070Spatrick     return;
118e5dd7070Spatrick 
119e5dd7070Spatrick   bool IsPure = MD->isPure();
120e5dd7070Spatrick 
121e5dd7070Spatrick   // At this point we're sure that we're calling a virtual method
122e5dd7070Spatrick   // during construction or destruction, so we'll emit a report.
123e5dd7070Spatrick   SmallString<128> Msg;
124e5dd7070Spatrick   llvm::raw_svector_ostream OS(Msg);
125e5dd7070Spatrick   OS << "Call to ";
126e5dd7070Spatrick   if (IsPure)
127e5dd7070Spatrick     OS << "pure ";
128*a9ac8606Spatrick   OS << "virtual method '" << MD->getParent()->getDeclName()
129*a9ac8606Spatrick      << "::" << MD->getDeclName() << "' during ";
130e5dd7070Spatrick   if (*ObState == ObjectState::CtorCalled)
131e5dd7070Spatrick     OS << "construction ";
132e5dd7070Spatrick   else
133e5dd7070Spatrick     OS << "destruction ";
134e5dd7070Spatrick   if (IsPure)
135e5dd7070Spatrick     OS << "has undefined behavior";
136e5dd7070Spatrick   else
137e5dd7070Spatrick     OS << "bypasses virtual dispatch";
138e5dd7070Spatrick 
139e5dd7070Spatrick   ExplodedNode *N =
140e5dd7070Spatrick       IsPure ? C.generateErrorNode() : C.generateNonFatalErrorNode();
141e5dd7070Spatrick   if (!N)
142e5dd7070Spatrick     return;
143e5dd7070Spatrick 
144e5dd7070Spatrick   const std::unique_ptr<BugType> &BT = IsPure ? BT_Pure : BT_Impure;
145e5dd7070Spatrick   if (!BT) {
146e5dd7070Spatrick     // The respective check is disabled.
147e5dd7070Spatrick     return;
148e5dd7070Spatrick   }
149e5dd7070Spatrick 
150e5dd7070Spatrick   auto Report = std::make_unique<PathSensitiveBugReport>(*BT, OS.str(), N);
151e5dd7070Spatrick 
152e5dd7070Spatrick   if (ShowFixIts && !IsPure) {
153e5dd7070Spatrick     // FIXME: These hints are valid only when the virtual call is made
154e5dd7070Spatrick     // directly from the constructor/destructor. Otherwise the dispatch
155e5dd7070Spatrick     // will work just fine from other callees, and the fix may break
156e5dd7070Spatrick     // the otherwise correct program.
157e5dd7070Spatrick     FixItHint Fixit = FixItHint::CreateInsertion(
158e5dd7070Spatrick         CE->getBeginLoc(), MD->getParent()->getNameAsString() + "::");
159e5dd7070Spatrick     Report->addFixItHint(Fixit);
160e5dd7070Spatrick   }
161e5dd7070Spatrick 
162e5dd7070Spatrick   C.emitReport(std::move(Report));
163e5dd7070Spatrick }
164e5dd7070Spatrick 
registerCtorDtorCallInState(bool IsBeginFunction,CheckerContext & C) const165e5dd7070Spatrick void VirtualCallChecker::registerCtorDtorCallInState(bool IsBeginFunction,
166e5dd7070Spatrick                                                      CheckerContext &C) const {
167e5dd7070Spatrick   const auto *LCtx = C.getLocationContext();
168e5dd7070Spatrick   const auto *MD = dyn_cast_or_null<CXXMethodDecl>(LCtx->getDecl());
169e5dd7070Spatrick   if (!MD)
170e5dd7070Spatrick     return;
171e5dd7070Spatrick 
172e5dd7070Spatrick   ProgramStateRef State = C.getState();
173e5dd7070Spatrick   auto &SVB = C.getSValBuilder();
174e5dd7070Spatrick 
175e5dd7070Spatrick   // Enter a constructor, set the corresponding memregion be true.
176e5dd7070Spatrick   if (isa<CXXConstructorDecl>(MD)) {
177e5dd7070Spatrick     auto ThiSVal =
178e5dd7070Spatrick         State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame()));
179e5dd7070Spatrick     const MemRegion *Reg = ThiSVal.getAsRegion();
180e5dd7070Spatrick     if (IsBeginFunction)
181e5dd7070Spatrick       State = State->set<CtorDtorMap>(Reg, ObjectState::CtorCalled);
182e5dd7070Spatrick     else
183e5dd7070Spatrick       State = State->remove<CtorDtorMap>(Reg);
184e5dd7070Spatrick 
185e5dd7070Spatrick     C.addTransition(State);
186e5dd7070Spatrick     return;
187e5dd7070Spatrick   }
188e5dd7070Spatrick 
189e5dd7070Spatrick   // Enter a Destructor, set the corresponding memregion be true.
190e5dd7070Spatrick   if (isa<CXXDestructorDecl>(MD)) {
191e5dd7070Spatrick     auto ThiSVal =
192e5dd7070Spatrick         State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame()));
193e5dd7070Spatrick     const MemRegion *Reg = ThiSVal.getAsRegion();
194e5dd7070Spatrick     if (IsBeginFunction)
195e5dd7070Spatrick       State = State->set<CtorDtorMap>(Reg, ObjectState::DtorCalled);
196e5dd7070Spatrick     else
197e5dd7070Spatrick       State = State->remove<CtorDtorMap>(Reg);
198e5dd7070Spatrick 
199e5dd7070Spatrick     C.addTransition(State);
200e5dd7070Spatrick     return;
201e5dd7070Spatrick   }
202e5dd7070Spatrick }
203e5dd7070Spatrick 
registerVirtualCallModeling(CheckerManager & Mgr)204e5dd7070Spatrick void ento::registerVirtualCallModeling(CheckerManager &Mgr) {
205e5dd7070Spatrick   Mgr.registerChecker<VirtualCallChecker>();
206e5dd7070Spatrick }
207e5dd7070Spatrick 
registerPureVirtualCallChecker(CheckerManager & Mgr)208e5dd7070Spatrick void ento::registerPureVirtualCallChecker(CheckerManager &Mgr) {
209e5dd7070Spatrick   auto *Chk = Mgr.getChecker<VirtualCallChecker>();
210e5dd7070Spatrick   Chk->BT_Pure = std::make_unique<BugType>(Mgr.getCurrentCheckerName(),
211e5dd7070Spatrick                                            "Pure virtual method call",
212e5dd7070Spatrick                                            categories::CXXObjectLifecycle);
213e5dd7070Spatrick }
214e5dd7070Spatrick 
registerVirtualCallChecker(CheckerManager & Mgr)215e5dd7070Spatrick void ento::registerVirtualCallChecker(CheckerManager &Mgr) {
216e5dd7070Spatrick   auto *Chk = Mgr.getChecker<VirtualCallChecker>();
217e5dd7070Spatrick   if (!Mgr.getAnalyzerOptions().getCheckerBooleanOption(
218e5dd7070Spatrick           Mgr.getCurrentCheckerName(), "PureOnly")) {
219e5dd7070Spatrick     Chk->BT_Impure = std::make_unique<BugType>(
220e5dd7070Spatrick         Mgr.getCurrentCheckerName(), "Unexpected loss of virtual dispatch",
221e5dd7070Spatrick         categories::CXXObjectLifecycle);
222e5dd7070Spatrick     Chk->ShowFixIts = Mgr.getAnalyzerOptions().getCheckerBooleanOption(
223e5dd7070Spatrick         Mgr.getCurrentCheckerName(), "ShowFixIts");
224e5dd7070Spatrick   }
225e5dd7070Spatrick }
226e5dd7070Spatrick 
shouldRegisterVirtualCallModeling(const CheckerManager & mgr)227ec727ea7Spatrick bool ento::shouldRegisterVirtualCallModeling(const CheckerManager &mgr) {
228ec727ea7Spatrick   const LangOptions &LO = mgr.getLangOpts();
229e5dd7070Spatrick   return LO.CPlusPlus;
230e5dd7070Spatrick }
231e5dd7070Spatrick 
shouldRegisterPureVirtualCallChecker(const CheckerManager & mgr)232ec727ea7Spatrick bool ento::shouldRegisterPureVirtualCallChecker(const CheckerManager &mgr) {
233ec727ea7Spatrick   const LangOptions &LO = mgr.getLangOpts();
234e5dd7070Spatrick   return LO.CPlusPlus;
235e5dd7070Spatrick }
236e5dd7070Spatrick 
shouldRegisterVirtualCallChecker(const CheckerManager & mgr)237ec727ea7Spatrick bool ento::shouldRegisterVirtualCallChecker(const CheckerManager &mgr) {
238ec727ea7Spatrick   const LangOptions &LO = mgr.getLangOpts();
239e5dd7070Spatrick   return LO.CPlusPlus;
240e5dd7070Spatrick }
241