diff options
Diffstat (limited to 'contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp')
| -rw-r--r-- | contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp | 287 |
1 files changed, 287 insertions, 0 deletions
diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp new file mode 100644 index 000000000000..762c9c1c8d7a --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp @@ -0,0 +1,287 @@ +//=======- 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 function 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> { + mutable std::unique_ptr<BugType> BT; + +public: + // The flag to determine if pure virtual functions should be issued only. + DefaultBool IsPureOnly; + + 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; + void reportBug(StringRef Msg, bool PureError, const MemRegion *Reg, + CheckerContext &C) const; + + class VirtualBugVisitor : public BugReporterVisitor { + 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<PathDiagnosticPiece> VisitNode(const ExplodedNode *N, + 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<PathDiagnosticPiece> +VirtualCallChecker::VirtualBugVisitor::VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &) { + // 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<CXXConstructorDecl>(LCtx->getDecl()); + const CXXDestructorDecl *DD = + dyn_cast_or_null<CXXDestructorDecl>(LCtx->getDecl()); + + if (!CD && !DD) + return nullptr; + + ProgramStateManager &PSM = State->getStateManager(); + auto &SVB = PSM.getSValBuilder(); + const auto *MD = dyn_cast<CXXMethodDecl>(LCtx->getDecl()); + if (!MD) + return nullptr; + auto ThiSVal = + State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame())); + const MemRegion *Reg = ThiSVal.castAs<loc::MemRegionVal>().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<PathDiagnosticEventPiece>(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<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(); + const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); + + if (IsPureOnly && !MD->isPure()) + return; + if (!isVirtualCall(CE)) + return; + + const MemRegion *Reg = MC->getCXXThisVal().getAsRegion(); + const ObjectState *ObState = State->get<CtorDtorMap>(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<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 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<BugReport>(*BT, Msg, N); + Reporter->addVisitor(llvm::make_unique<VirtualBugVisitor>(Reg)); + C.emitReport(std::move(Reporter)); +} + +void ento::registerVirtualCallChecker(CheckerManager &mgr) { + VirtualCallChecker *checker = mgr.registerChecker<VirtualCallChecker>(); + + checker->IsPureOnly = + mgr.getAnalyzerOptions().getCheckerBooleanOption(checker, "PureOnly"); +} + +bool ento::shouldRegisterVirtualCallChecker(const LangOptions &LO) { + return true; +} |
