| //=======- 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; |
| } |