//=======- 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/AST/Attr.h" #include "clang/AST/DeclCXX.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.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 { static inline void Profile(ObjectState X, FoldingSetNodeID &ID) { ID.AddInteger(static_cast(X)); } }; } // end namespace llvm namespace { class VirtualCallChecker : public Checker { public: // These are going to be null if the respective check is disabled. mutable std::unique_ptr 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(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()) CallIsNonVirtual = true; } } const CXXMethodDecl *MD = dyn_cast_or_null(CE->getDirectCallee()); if (MD && MD->isVirtual() && !CallIsNonVirtual && !MD->hasAttr() && !MD->getParent()->hasAttr()) 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(&Call); if (!MC) return; const CXXMethodDecl *MD = dyn_cast_or_null(Call.getDecl()); if (!MD) return; ProgramStateRef State = C.getState(); // Member calls are always represented by a call-expression. const auto *CE = cast(Call.getOriginExpr()); if (!isVirtualCall(CE)) return; const MemRegion *Reg = MC->getCXXThisVal().getAsRegion(); const ObjectState *ObState = State->get(Reg); if (!ObState) return; bool IsPure = MD->isPureVirtual(); // 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()->getDeclName() << "::" << MD->getDeclName() << "' 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 &BT = IsPure ? BT_Pure : BT_Impure; if (!BT) { // The respective check is disabled. return; } auto Report = std::make_unique(*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(LCtx->getDecl()); if (!MD) return; ProgramStateRef State = C.getState(); auto &SVB = C.getSValBuilder(); // Enter a constructor, set the corresponding memregion be true. if (isa(MD)) { auto ThiSVal = State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame())); const MemRegion *Reg = ThiSVal.getAsRegion(); if (IsBeginFunction) State = State->set(Reg, ObjectState::CtorCalled); else State = State->remove(Reg); C.addTransition(State); return; } // Enter a Destructor, set the corresponding memregion be true. if (isa(MD)) { auto ThiSVal = State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame())); const MemRegion *Reg = ThiSVal.getAsRegion(); if (IsBeginFunction) State = State->set(Reg, ObjectState::DtorCalled); else State = State->remove(Reg); C.addTransition(State); return; } } void ento::registerVirtualCallModeling(CheckerManager &Mgr) { Mgr.registerChecker(); } void ento::registerPureVirtualCallChecker(CheckerManager &Mgr) { auto *Chk = Mgr.getChecker(); Chk->BT_Pure = std::make_unique(Mgr.getCurrentCheckerName(), "Pure virtual method call", categories::CXXObjectLifecycle); } void ento::registerVirtualCallChecker(CheckerManager &Mgr) { auto *Chk = Mgr.getChecker(); if (!Mgr.getAnalyzerOptions().getCheckerBooleanOption( Mgr.getCurrentCheckerName(), "PureOnly")) { Chk->BT_Impure = std::make_unique( Mgr.getCurrentCheckerName(), "Unexpected loss of virtual dispatch", categories::CXXObjectLifecycle); Chk->ShowFixIts = Mgr.getAnalyzerOptions().getCheckerBooleanOption( Mgr.getCurrentCheckerName(), "ShowFixIts"); } } bool ento::shouldRegisterVirtualCallModeling(const CheckerManager &mgr) { const LangOptions &LO = mgr.getLangOpts(); return LO.CPlusPlus; } bool ento::shouldRegisterPureVirtualCallChecker(const CheckerManager &mgr) { const LangOptions &LO = mgr.getLangOpts(); return LO.CPlusPlus; } bool ento::shouldRegisterVirtualCallChecker(const CheckerManager &mgr) { const LangOptions &LO = mgr.getLangOpts(); return LO.CPlusPlus; }