17330f729Sjoerg //=======- VirtualCallChecker.cpp --------------------------------*- C++ -*-==//
27330f729Sjoerg //
37330f729Sjoerg // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
47330f729Sjoerg // See https://llvm.org/LICENSE.txt for license information.
57330f729Sjoerg // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
67330f729Sjoerg //
77330f729Sjoerg //===----------------------------------------------------------------------===//
87330f729Sjoerg //
97330f729Sjoerg // This file defines a checker that checks virtual method calls during
107330f729Sjoerg // construction or destruction of C++ objects.
117330f729Sjoerg //
127330f729Sjoerg //===----------------------------------------------------------------------===//
137330f729Sjoerg
14*e038c9c4Sjoerg #include "clang/AST/Attr.h"
157330f729Sjoerg #include "clang/AST/DeclCXX.h"
16*e038c9c4Sjoerg #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
177330f729Sjoerg #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
187330f729Sjoerg #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
197330f729Sjoerg #include "clang/StaticAnalyzer/Core/Checker.h"
207330f729Sjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
217330f729Sjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
227330f729Sjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
237330f729Sjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h"
247330f729Sjoerg
257330f729Sjoerg using namespace clang;
267330f729Sjoerg using namespace ento;
277330f729Sjoerg
287330f729Sjoerg namespace {
297330f729Sjoerg enum class ObjectState : bool { CtorCalled, DtorCalled };
307330f729Sjoerg } // end namespace
317330f729Sjoerg // FIXME: Ascending over StackFrameContext maybe another method.
327330f729Sjoerg
337330f729Sjoerg namespace llvm {
347330f729Sjoerg template <> struct FoldingSetTrait<ObjectState> {
Profilellvm::FoldingSetTrait357330f729Sjoerg static inline void Profile(ObjectState X, FoldingSetNodeID &ID) {
367330f729Sjoerg ID.AddInteger(static_cast<int>(X));
377330f729Sjoerg }
387330f729Sjoerg };
397330f729Sjoerg } // end namespace llvm
407330f729Sjoerg
417330f729Sjoerg namespace {
427330f729Sjoerg class VirtualCallChecker
437330f729Sjoerg : public Checker<check::BeginFunction, check::EndFunction, check::PreCall> {
447330f729Sjoerg public:
457330f729Sjoerg // These are going to be null if the respective check is disabled.
467330f729Sjoerg mutable std::unique_ptr<BugType> BT_Pure, BT_Impure;
477330f729Sjoerg bool ShowFixIts = false;
487330f729Sjoerg
497330f729Sjoerg void checkBeginFunction(CheckerContext &C) const;
507330f729Sjoerg void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
517330f729Sjoerg void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
527330f729Sjoerg
537330f729Sjoerg private:
547330f729Sjoerg void registerCtorDtorCallInState(bool IsBeginFunction,
557330f729Sjoerg CheckerContext &C) const;
567330f729Sjoerg };
577330f729Sjoerg } // end namespace
587330f729Sjoerg
597330f729Sjoerg // GDM (generic data map) to the memregion of this for the ctor and dtor.
REGISTER_MAP_WITH_PROGRAMSTATE(CtorDtorMap,const MemRegion *,ObjectState)607330f729Sjoerg REGISTER_MAP_WITH_PROGRAMSTATE(CtorDtorMap, const MemRegion *, ObjectState)
617330f729Sjoerg
627330f729Sjoerg // The function to check if a callexpr is a virtual method call.
637330f729Sjoerg static bool isVirtualCall(const CallExpr *CE) {
647330f729Sjoerg bool CallIsNonVirtual = false;
657330f729Sjoerg
667330f729Sjoerg if (const MemberExpr *CME = dyn_cast<MemberExpr>(CE->getCallee())) {
677330f729Sjoerg // The member access is fully qualified (i.e., X::F).
687330f729Sjoerg // Treat this as a non-virtual call and do not warn.
697330f729Sjoerg if (CME->getQualifier())
707330f729Sjoerg CallIsNonVirtual = true;
717330f729Sjoerg
727330f729Sjoerg if (const Expr *Base = CME->getBase()) {
737330f729Sjoerg // The most derived class is marked final.
747330f729Sjoerg if (Base->getBestDynamicClassType()->hasAttr<FinalAttr>())
757330f729Sjoerg CallIsNonVirtual = true;
767330f729Sjoerg }
777330f729Sjoerg }
787330f729Sjoerg
797330f729Sjoerg const CXXMethodDecl *MD =
807330f729Sjoerg dyn_cast_or_null<CXXMethodDecl>(CE->getDirectCallee());
817330f729Sjoerg if (MD && MD->isVirtual() && !CallIsNonVirtual && !MD->hasAttr<FinalAttr>() &&
827330f729Sjoerg !MD->getParent()->hasAttr<FinalAttr>())
837330f729Sjoerg return true;
847330f729Sjoerg return false;
857330f729Sjoerg }
867330f729Sjoerg
877330f729Sjoerg // The BeginFunction callback when enter a constructor or a destructor.
checkBeginFunction(CheckerContext & C) const887330f729Sjoerg void VirtualCallChecker::checkBeginFunction(CheckerContext &C) const {
897330f729Sjoerg registerCtorDtorCallInState(true, C);
907330f729Sjoerg }
917330f729Sjoerg
927330f729Sjoerg // The EndFunction callback when leave a constructor or a destructor.
checkEndFunction(const ReturnStmt * RS,CheckerContext & C) const937330f729Sjoerg void VirtualCallChecker::checkEndFunction(const ReturnStmt *RS,
947330f729Sjoerg CheckerContext &C) const {
957330f729Sjoerg registerCtorDtorCallInState(false, C);
967330f729Sjoerg }
977330f729Sjoerg
checkPreCall(const CallEvent & Call,CheckerContext & C) const987330f729Sjoerg void VirtualCallChecker::checkPreCall(const CallEvent &Call,
997330f729Sjoerg CheckerContext &C) const {
1007330f729Sjoerg const auto MC = dyn_cast<CXXMemberCall>(&Call);
1017330f729Sjoerg if (!MC)
1027330f729Sjoerg return;
1037330f729Sjoerg
1047330f729Sjoerg const CXXMethodDecl *MD = dyn_cast_or_null<CXXMethodDecl>(Call.getDecl());
1057330f729Sjoerg if (!MD)
1067330f729Sjoerg return;
1077330f729Sjoerg
1087330f729Sjoerg ProgramStateRef State = C.getState();
1097330f729Sjoerg // Member calls are always represented by a call-expression.
1107330f729Sjoerg const auto *CE = cast<CallExpr>(Call.getOriginExpr());
1117330f729Sjoerg if (!isVirtualCall(CE))
1127330f729Sjoerg return;
1137330f729Sjoerg
1147330f729Sjoerg const MemRegion *Reg = MC->getCXXThisVal().getAsRegion();
1157330f729Sjoerg const ObjectState *ObState = State->get<CtorDtorMap>(Reg);
1167330f729Sjoerg if (!ObState)
1177330f729Sjoerg return;
1187330f729Sjoerg
1197330f729Sjoerg bool IsPure = MD->isPure();
1207330f729Sjoerg
1217330f729Sjoerg // At this point we're sure that we're calling a virtual method
1227330f729Sjoerg // during construction or destruction, so we'll emit a report.
1237330f729Sjoerg SmallString<128> Msg;
1247330f729Sjoerg llvm::raw_svector_ostream OS(Msg);
1257330f729Sjoerg OS << "Call to ";
1267330f729Sjoerg if (IsPure)
1277330f729Sjoerg OS << "pure ";
128*e038c9c4Sjoerg OS << "virtual method '" << MD->getParent()->getDeclName()
129*e038c9c4Sjoerg << "::" << MD->getDeclName() << "' during ";
1307330f729Sjoerg if (*ObState == ObjectState::CtorCalled)
1317330f729Sjoerg OS << "construction ";
1327330f729Sjoerg else
1337330f729Sjoerg OS << "destruction ";
1347330f729Sjoerg if (IsPure)
1357330f729Sjoerg OS << "has undefined behavior";
1367330f729Sjoerg else
1377330f729Sjoerg OS << "bypasses virtual dispatch";
1387330f729Sjoerg
1397330f729Sjoerg ExplodedNode *N =
1407330f729Sjoerg IsPure ? C.generateErrorNode() : C.generateNonFatalErrorNode();
1417330f729Sjoerg if (!N)
1427330f729Sjoerg return;
1437330f729Sjoerg
1447330f729Sjoerg const std::unique_ptr<BugType> &BT = IsPure ? BT_Pure : BT_Impure;
1457330f729Sjoerg if (!BT) {
1467330f729Sjoerg // The respective check is disabled.
1477330f729Sjoerg return;
1487330f729Sjoerg }
1497330f729Sjoerg
1507330f729Sjoerg auto Report = std::make_unique<PathSensitiveBugReport>(*BT, OS.str(), N);
1517330f729Sjoerg
1527330f729Sjoerg if (ShowFixIts && !IsPure) {
1537330f729Sjoerg // FIXME: These hints are valid only when the virtual call is made
1547330f729Sjoerg // directly from the constructor/destructor. Otherwise the dispatch
1557330f729Sjoerg // will work just fine from other callees, and the fix may break
1567330f729Sjoerg // the otherwise correct program.
1577330f729Sjoerg FixItHint Fixit = FixItHint::CreateInsertion(
1587330f729Sjoerg CE->getBeginLoc(), MD->getParent()->getNameAsString() + "::");
1597330f729Sjoerg Report->addFixItHint(Fixit);
1607330f729Sjoerg }
1617330f729Sjoerg
1627330f729Sjoerg C.emitReport(std::move(Report));
1637330f729Sjoerg }
1647330f729Sjoerg
registerCtorDtorCallInState(bool IsBeginFunction,CheckerContext & C) const1657330f729Sjoerg void VirtualCallChecker::registerCtorDtorCallInState(bool IsBeginFunction,
1667330f729Sjoerg CheckerContext &C) const {
1677330f729Sjoerg const auto *LCtx = C.getLocationContext();
1687330f729Sjoerg const auto *MD = dyn_cast_or_null<CXXMethodDecl>(LCtx->getDecl());
1697330f729Sjoerg if (!MD)
1707330f729Sjoerg return;
1717330f729Sjoerg
1727330f729Sjoerg ProgramStateRef State = C.getState();
1737330f729Sjoerg auto &SVB = C.getSValBuilder();
1747330f729Sjoerg
1757330f729Sjoerg // Enter a constructor, set the corresponding memregion be true.
1767330f729Sjoerg if (isa<CXXConstructorDecl>(MD)) {
1777330f729Sjoerg auto ThiSVal =
1787330f729Sjoerg State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame()));
1797330f729Sjoerg const MemRegion *Reg = ThiSVal.getAsRegion();
1807330f729Sjoerg if (IsBeginFunction)
1817330f729Sjoerg State = State->set<CtorDtorMap>(Reg, ObjectState::CtorCalled);
1827330f729Sjoerg else
1837330f729Sjoerg State = State->remove<CtorDtorMap>(Reg);
1847330f729Sjoerg
1857330f729Sjoerg C.addTransition(State);
1867330f729Sjoerg return;
1877330f729Sjoerg }
1887330f729Sjoerg
1897330f729Sjoerg // Enter a Destructor, set the corresponding memregion be true.
1907330f729Sjoerg if (isa<CXXDestructorDecl>(MD)) {
1917330f729Sjoerg auto ThiSVal =
1927330f729Sjoerg State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame()));
1937330f729Sjoerg const MemRegion *Reg = ThiSVal.getAsRegion();
1947330f729Sjoerg if (IsBeginFunction)
1957330f729Sjoerg State = State->set<CtorDtorMap>(Reg, ObjectState::DtorCalled);
1967330f729Sjoerg else
1977330f729Sjoerg State = State->remove<CtorDtorMap>(Reg);
1987330f729Sjoerg
1997330f729Sjoerg C.addTransition(State);
2007330f729Sjoerg return;
2017330f729Sjoerg }
2027330f729Sjoerg }
2037330f729Sjoerg
registerVirtualCallModeling(CheckerManager & Mgr)2047330f729Sjoerg void ento::registerVirtualCallModeling(CheckerManager &Mgr) {
2057330f729Sjoerg Mgr.registerChecker<VirtualCallChecker>();
2067330f729Sjoerg }
2077330f729Sjoerg
registerPureVirtualCallChecker(CheckerManager & Mgr)2087330f729Sjoerg void ento::registerPureVirtualCallChecker(CheckerManager &Mgr) {
2097330f729Sjoerg auto *Chk = Mgr.getChecker<VirtualCallChecker>();
2107330f729Sjoerg Chk->BT_Pure = std::make_unique<BugType>(Mgr.getCurrentCheckerName(),
2117330f729Sjoerg "Pure virtual method call",
2127330f729Sjoerg categories::CXXObjectLifecycle);
2137330f729Sjoerg }
2147330f729Sjoerg
registerVirtualCallChecker(CheckerManager & Mgr)2157330f729Sjoerg void ento::registerVirtualCallChecker(CheckerManager &Mgr) {
2167330f729Sjoerg auto *Chk = Mgr.getChecker<VirtualCallChecker>();
2177330f729Sjoerg if (!Mgr.getAnalyzerOptions().getCheckerBooleanOption(
2187330f729Sjoerg Mgr.getCurrentCheckerName(), "PureOnly")) {
2197330f729Sjoerg Chk->BT_Impure = std::make_unique<BugType>(
2207330f729Sjoerg Mgr.getCurrentCheckerName(), "Unexpected loss of virtual dispatch",
2217330f729Sjoerg categories::CXXObjectLifecycle);
2227330f729Sjoerg Chk->ShowFixIts = Mgr.getAnalyzerOptions().getCheckerBooleanOption(
2237330f729Sjoerg Mgr.getCurrentCheckerName(), "ShowFixIts");
2247330f729Sjoerg }
2257330f729Sjoerg }
2267330f729Sjoerg
shouldRegisterVirtualCallModeling(const CheckerManager & mgr)227*e038c9c4Sjoerg bool ento::shouldRegisterVirtualCallModeling(const CheckerManager &mgr) {
228*e038c9c4Sjoerg const LangOptions &LO = mgr.getLangOpts();
2297330f729Sjoerg return LO.CPlusPlus;
2307330f729Sjoerg }
2317330f729Sjoerg
shouldRegisterPureVirtualCallChecker(const CheckerManager & mgr)232*e038c9c4Sjoerg bool ento::shouldRegisterPureVirtualCallChecker(const CheckerManager &mgr) {
233*e038c9c4Sjoerg const LangOptions &LO = mgr.getLangOpts();
2347330f729Sjoerg return LO.CPlusPlus;
2357330f729Sjoerg }
2367330f729Sjoerg
shouldRegisterVirtualCallChecker(const CheckerManager & mgr)237*e038c9c4Sjoerg bool ento::shouldRegisterVirtualCallChecker(const CheckerManager &mgr) {
238*e038c9c4Sjoerg const LangOptions &LO = mgr.getLangOpts();
2397330f729Sjoerg return LO.CPlusPlus;
2407330f729Sjoerg }
241