//=======- VirtualCallChecker.cpp --------------------------------*- C++ -*-==// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // This file defines a checker that checks virtual function calls during // construction or destruction of C++ objects. // //===----------------------------------------------------------------------===// #include "ClangSACheckers.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 { static inline void Profile(ObjectState X, FoldingSetNodeID &ID) { ID.AddInteger(static_cast(X)); } }; } // end namespace llvm namespace { class VirtualCallChecker : public Checker { mutable std::unique_ptr BT; public: // The flag to determine if pure virtual functions should be issued only. DefaultBool IsPureOnly; void checkBeginFunction(CheckerContext &C) const; void checkEndFunction(CheckerContext &C) const; void checkPreCall(const CallEvent &Call, CheckerContext &C) const; private: void registerCtorDtorCallInState(bool IsBeginFunction, CheckerContext &C) const; void reportBug(StringRef Msg, bool PureError, const MemRegion *Reg, CheckerContext &C) const; class VirtualBugVisitor : public BugReporterVisitorImpl { private: const MemRegion *ObjectRegion; bool Found; public: VirtualBugVisitor(const MemRegion *R) : ObjectRegion(R), Found(false) {} void Profile(llvm::FoldingSetNodeID &ID) const override { static int X = 0; ID.AddPointer(&X); ID.AddPointer(ObjectRegion); } std::shared_ptr VisitNode(const ExplodedNode *N, const ExplodedNode *PrevN, BugReporterContext &BRC, BugReport &BR) override; }; }; } // end namespace // GDM (generic data map) to the memregion of this for the ctor and dtor. REGISTER_MAP_WITH_PROGRAMSTATE(CtorDtorMap, const MemRegion *, ObjectState) std::shared_ptr VirtualCallChecker::VirtualBugVisitor::VisitNode(const ExplodedNode *N, const ExplodedNode *PrevN, BugReporterContext &BRC, BugReport &BR) { // We need the last ctor/dtor which call the virtual function. // The visitor walks the ExplodedGraph backwards. if (Found) return nullptr; ProgramStateRef State = N->getState(); const LocationContext *LCtx = N->getLocationContext(); const CXXConstructorDecl *CD = dyn_cast_or_null(LCtx->getDecl()); const CXXDestructorDecl *DD = dyn_cast_or_null(LCtx->getDecl()); if (!CD && !DD) return nullptr; ProgramStateManager &PSM = State->getStateManager(); auto &SVB = PSM.getSValBuilder(); const auto *MD = dyn_cast(LCtx->getDecl()); if (!MD) return nullptr; auto ThiSVal = State->getSVal(SVB.getCXXThis(MD, LCtx->getCurrentStackFrame())); const MemRegion *Reg = ThiSVal.castAs().getRegion(); if (!Reg) return nullptr; if (Reg != ObjectRegion) return nullptr; const Stmt *S = PathDiagnosticLocation::getStmt(N); if (!S) return nullptr; Found = true; std::string InfoText; if (CD) InfoText = "This constructor of an object of type '" + CD->getNameAsString() + "' has not returned when the virtual method was called"; else InfoText = "This destructor of an object of type '" + DD->getNameAsString() + "' has not returned when the virtual method was called"; // Generate the extra diagnostic. PathDiagnosticLocation Pos(S, BRC.getSourceManager(), N->getLocationContext()); return std::make_shared(Pos, InfoText, true); } // The function to check if a callexpr is a virtual function. 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(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(); const CallExpr *CE = dyn_cast_or_null(Call.getOriginExpr()); if (IsPureOnly && !MD->isPure()) return; if (!isVirtualCall(CE)) return; const MemRegion *Reg = MC->getCXXThisVal().getAsRegion(); const ObjectState *ObState = State->get(Reg); if (!ObState) return; // Check if a virtual method is called. // The GDM of constructor and destructor should be true. if (*ObState == ObjectState::CtorCalled) { if (IsPureOnly && MD->isPure()) reportBug("Call to pure virtual function during construction", true, Reg, C); else if (!MD->isPure()) reportBug("Call to virtual function during construction", false, Reg, C); else reportBug("Call to pure virtual function during construction", false, Reg, C); } if (*ObState == ObjectState::DtorCalled) { if (IsPureOnly && MD->isPure()) reportBug("Call to pure virtual function during destruction", true, Reg, C); else if (!MD->isPure()) reportBug("Call to virtual function during destruction", false, Reg, C); else reportBug("Call to pure virtual function during construction", false, Reg, C); } } 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->getCurrentStackFrame())); 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->getCurrentStackFrame())); const MemRegion *Reg = ThiSVal.getAsRegion(); if (IsBeginFunction) State = State->set(Reg, ObjectState::DtorCalled); else State = State->remove(Reg); C.addTransition(State); return; } } void VirtualCallChecker::reportBug(StringRef Msg, bool IsSink, const MemRegion *Reg, CheckerContext &C) const { ExplodedNode *N; if (IsSink) N = C.generateErrorNode(); else N = C.generateNonFatalErrorNode(); if (!N) return; if (!BT) BT.reset(new BugType( this, "Call to virtual function during construction or destruction", "C++ Object Lifecycle")); auto Reporter = llvm::make_unique(*BT, Msg, N); Reporter->addVisitor(llvm::make_unique(Reg)); C.emitReport(std::move(Reporter)); } void ento::registerVirtualCallChecker(CheckerManager &mgr) { VirtualCallChecker *checker = mgr.registerChecker(); checker->IsPureOnly = mgr.getAnalyzerOptions().getBooleanOption("PureOnly", false, checker); }