VirtualCallChecker.cpp revision 360784
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(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>(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>(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(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(false, C);
96}
97
98void VirtualCallChecker::checkPreCall(const CallEvent &Call,
99                                      CheckerContext &C) const {
100  const auto MC = dyn_cast<CXXMemberCall>(&Call);
101  if (!MC)
102    return;
103
104  const CXXMethodDecl *MD = dyn_cast_or_null<CXXMethodDecl>(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>(Call.getOriginExpr());
111  if (!isVirtualCall(CE))
112    return;
113
114  const MemRegion *Reg = MC->getCXXThisVal().getAsRegion();
115  const ObjectState *ObState = State->get<CtorDtorMap>(Reg);
116  if (!ObState)
117    return;
118
119  bool IsPure = MD->isPure();
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()->getNameAsString()
129     << "::" << MD->getNameAsString() << "' 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>(*BT, OS.str(), 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        CE->getBeginLoc(), MD->getParent()->getNameAsString() + "::");
159    Report->addFixItHint(Fixit);
160  }
161
162  C.emitReport(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>(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>(MD)) {
177    auto ThiSVal =
178        State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame()));
179    const MemRegion *Reg = ThiSVal.getAsRegion();
180    if (IsBeginFunction)
181      State = State->set<CtorDtorMap>(Reg, ObjectState::CtorCalled);
182    else
183      State = State->remove<CtorDtorMap>(Reg);
184
185    C.addTransition(State);
186    return;
187  }
188
189  // Enter a Destructor, set the corresponding memregion be true.
190  if (isa<CXXDestructorDecl>(MD)) {
191    auto ThiSVal =
192        State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame()));
193    const MemRegion *Reg = ThiSVal.getAsRegion();
194    if (IsBeginFunction)
195      State = State->set<CtorDtorMap>(Reg, ObjectState::DtorCalled);
196    else
197      State = State->remove<CtorDtorMap>(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>(Mgr.getCurrentCheckerName(),
211                                           "Pure virtual method call",
212                                           categories::CXXObjectLifecycle);
213}
214
215void 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");
224  }
225}
226
227bool ento::shouldRegisterVirtualCallModeling(const LangOptions &LO) {
228  return LO.CPlusPlus;
229}
230
231bool ento::shouldRegisterPureVirtualCallChecker(const LangOptions &LO) {
232  return LO.CPlusPlus;
233}
234
235bool ento::shouldRegisterVirtualCallChecker(const LangOptions &LO) {
236  return LO.CPlusPlus;
237}
238