diff options
Diffstat (limited to 'clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp')
| -rw-r--r-- | clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp | 236 | 
1 files changed, 236 insertions, 0 deletions
| diff --git a/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp new file mode 100644 index 000000000000..12cee5f8d4f7 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp @@ -0,0 +1,236 @@ +//=======- VirtualCallChecker.cpp --------------------------------*- C++ -*-==// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +//  This file defines a checker that checks virtual method calls during +//  construction or destruction of C++ objects. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/AST/DeclCXX.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h" + +using namespace clang; +using namespace ento; + +namespace { +enum class ObjectState : bool { CtorCalled, DtorCalled }; +} // end namespace +  // FIXME: Ascending over StackFrameContext maybe another method. + +namespace llvm { +template <> struct FoldingSetTrait<ObjectState> { +  static inline void Profile(ObjectState X, FoldingSetNodeID &ID) { +    ID.AddInteger(static_cast<int>(X)); +  } +}; +} // end namespace llvm + +namespace { +class VirtualCallChecker +    : public Checker<check::BeginFunction, check::EndFunction, check::PreCall> { +public: +  // These are going to be null if the respective check is disabled. +  mutable std::unique_ptr<BugType> BT_Pure, BT_Impure; +  bool ShowFixIts = false; + +  void checkBeginFunction(CheckerContext &C) const; +  void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const; +  void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + +private: +  void registerCtorDtorCallInState(bool IsBeginFunction, +                                   CheckerContext &C) const; +}; +} // end namespace + +// GDM (generic data map) to the memregion of this for the ctor and dtor. +REGISTER_MAP_WITH_PROGRAMSTATE(CtorDtorMap, const MemRegion *, ObjectState) + +// The function to check if a callexpr is a virtual method call. +static bool isVirtualCall(const CallExpr *CE) { +  bool CallIsNonVirtual = false; + +  if (const MemberExpr *CME = dyn_cast<MemberExpr>(CE->getCallee())) { +    // The member access is fully qualified (i.e., X::F). +    // Treat this as a non-virtual call and do not warn. +    if (CME->getQualifier()) +      CallIsNonVirtual = true; + +    if (const Expr *Base = CME->getBase()) { +      // The most derived class is marked final. +      if (Base->getBestDynamicClassType()->hasAttr<FinalAttr>()) +        CallIsNonVirtual = true; +    } +  } + +  const CXXMethodDecl *MD = +      dyn_cast_or_null<CXXMethodDecl>(CE->getDirectCallee()); +  if (MD && MD->isVirtual() && !CallIsNonVirtual && !MD->hasAttr<FinalAttr>() && +      !MD->getParent()->hasAttr<FinalAttr>()) +    return true; +  return false; +} + +// The BeginFunction callback when enter a constructor or a destructor. +void VirtualCallChecker::checkBeginFunction(CheckerContext &C) const { +  registerCtorDtorCallInState(true, C); +} + +// The EndFunction callback when leave a constructor or a destructor. +void VirtualCallChecker::checkEndFunction(const ReturnStmt *RS, +                                          CheckerContext &C) const { +  registerCtorDtorCallInState(false, C); +} + +void VirtualCallChecker::checkPreCall(const CallEvent &Call, +                                      CheckerContext &C) const { +  const auto MC = dyn_cast<CXXMemberCall>(&Call); +  if (!MC) +    return; + +  const CXXMethodDecl *MD = dyn_cast_or_null<CXXMethodDecl>(Call.getDecl()); +  if (!MD) +    return; + +  ProgramStateRef State = C.getState(); +  // Member calls are always represented by a call-expression. +  const auto *CE = cast<CallExpr>(Call.getOriginExpr()); +  if (!isVirtualCall(CE)) +    return; + +  const MemRegion *Reg = MC->getCXXThisVal().getAsRegion(); +  const ObjectState *ObState = State->get<CtorDtorMap>(Reg); +  if (!ObState) +    return; + +  bool IsPure = MD->isPure(); + +  // At this point we're sure that we're calling a virtual method +  // during construction or destruction, so we'll emit a report. +  SmallString<128> Msg; +  llvm::raw_svector_ostream OS(Msg); +  OS << "Call to "; +  if (IsPure) +    OS << "pure "; +  OS << "virtual method '" << MD->getParent()->getNameAsString() +     << "::" << MD->getNameAsString() << "' during "; +  if (*ObState == ObjectState::CtorCalled) +    OS << "construction "; +  else +    OS << "destruction "; +  if (IsPure) +    OS << "has undefined behavior"; +  else +    OS << "bypasses virtual dispatch"; + +  ExplodedNode *N = +      IsPure ? C.generateErrorNode() : C.generateNonFatalErrorNode(); +  if (!N) +    return; + +  const std::unique_ptr<BugType> &BT = IsPure ? BT_Pure : BT_Impure; +  if (!BT) { +    // The respective check is disabled. +    return; +  } + +  auto Report = std::make_unique<PathSensitiveBugReport>(*BT, OS.str(), N); + +  if (ShowFixIts && !IsPure) { +    // FIXME: These hints are valid only when the virtual call is made +    // directly from the constructor/destructor. Otherwise the dispatch +    // will work just fine from other callees, and the fix may break +    // the otherwise correct program. +    FixItHint Fixit = FixItHint::CreateInsertion( +        CE->getBeginLoc(), MD->getParent()->getNameAsString() + "::"); +    Report->addFixItHint(Fixit); +  } + +  C.emitReport(std::move(Report)); +} + +void VirtualCallChecker::registerCtorDtorCallInState(bool IsBeginFunction, +                                                     CheckerContext &C) const { +  const auto *LCtx = C.getLocationContext(); +  const auto *MD = dyn_cast_or_null<CXXMethodDecl>(LCtx->getDecl()); +  if (!MD) +    return; + +  ProgramStateRef State = C.getState(); +  auto &SVB = C.getSValBuilder(); + +  // Enter a constructor, set the corresponding memregion be true. +  if (isa<CXXConstructorDecl>(MD)) { +    auto ThiSVal = +        State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame())); +    const MemRegion *Reg = ThiSVal.getAsRegion(); +    if (IsBeginFunction) +      State = State->set<CtorDtorMap>(Reg, ObjectState::CtorCalled); +    else +      State = State->remove<CtorDtorMap>(Reg); + +    C.addTransition(State); +    return; +  } + +  // Enter a Destructor, set the corresponding memregion be true. +  if (isa<CXXDestructorDecl>(MD)) { +    auto ThiSVal = +        State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame())); +    const MemRegion *Reg = ThiSVal.getAsRegion(); +    if (IsBeginFunction) +      State = State->set<CtorDtorMap>(Reg, ObjectState::DtorCalled); +    else +      State = State->remove<CtorDtorMap>(Reg); + +    C.addTransition(State); +    return; +  } +} + +void ento::registerVirtualCallModeling(CheckerManager &Mgr) { +  Mgr.registerChecker<VirtualCallChecker>(); +} + +void ento::registerPureVirtualCallChecker(CheckerManager &Mgr) { +  auto *Chk = Mgr.getChecker<VirtualCallChecker>(); +  Chk->BT_Pure = std::make_unique<BugType>(Mgr.getCurrentCheckerName(), +                                           "Pure virtual method call", +                                           categories::CXXObjectLifecycle); +} + +void ento::registerVirtualCallChecker(CheckerManager &Mgr) { +  auto *Chk = Mgr.getChecker<VirtualCallChecker>(); +  if (!Mgr.getAnalyzerOptions().getCheckerBooleanOption( +          Mgr.getCurrentCheckerName(), "PureOnly")) { +    Chk->BT_Impure = std::make_unique<BugType>( +        Mgr.getCurrentCheckerName(), "Unexpected loss of virtual dispatch", +        categories::CXXObjectLifecycle); +    Chk->ShowFixIts = Mgr.getAnalyzerOptions().getCheckerBooleanOption( +        Mgr.getCurrentCheckerName(), "ShowFixIts"); +  } +} + +bool ento::shouldRegisterVirtualCallModeling(const LangOptions &LO) { +  return LO.CPlusPlus; +} + +bool ento::shouldRegisterPureVirtualCallChecker(const LangOptions &LO) { +  return LO.CPlusPlus; +} + +bool ento::shouldRegisterVirtualCallChecker(const LangOptions &LO) { +  return LO.CPlusPlus; +} | 
