diff options
Diffstat (limited to 'lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp')
-rw-r--r-- | lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp | 453 |
1 files changed, 223 insertions, 230 deletions
diff --git a/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp b/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp index b47762b915ce..c5010f53785a 100644 --- a/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp @@ -14,279 +14,272 @@ #include "ClangSACheckers.h" #include "clang/AST/DeclCXX.h" -#include "clang/AST/StmtVisitor.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/AnalysisManager.h" -#include "llvm/ADT/SmallString.h" -#include "llvm/Support/SaveAndRestore.h" -#include "llvm/Support/raw_ostream.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 { - -class WalkAST : public StmtVisitor<WalkAST> { - const CheckerBase *Checker; - BugReporter &BR; - AnalysisDeclContext *AC; - - /// The root constructor or destructor whose callees are being analyzed. - const CXXMethodDecl *RootMethod = nullptr; - - /// Whether the checker should walk into bodies of called functions. - /// Controlled by the "Interprocedural" analyzer-config option. - bool IsInterprocedural = false; - - /// Whether the checker should only warn for calls to pure virtual functions - /// (which is undefined behavior) or for all virtual functions (which may - /// may result in unexpected behavior). - bool ReportPureOnly = false; - - typedef const CallExpr * WorkListUnit; - typedef SmallVector<WorkListUnit, 20> DFSWorkList; - - /// A vector representing the worklist which has a chain of CallExprs. - DFSWorkList WList; - - // PreVisited : A CallExpr to this FunctionDecl is in the worklist, but the - // body has not been visited yet. - // PostVisited : A CallExpr to this FunctionDecl is in the worklist, and the - // body has been visited. - enum Kind { NotVisited, - PreVisited, /**< A CallExpr to this FunctionDecl is in the - worklist, but the body has not yet been - visited. */ - PostVisited /**< A CallExpr to this FunctionDecl is in the - worklist, and the body has been visited. */ - }; - - /// A DenseMap that records visited states of FunctionDecls. - llvm::DenseMap<const FunctionDecl *, Kind> VisitedFunctions; - - /// The CallExpr whose body is currently being visited. This is used for - /// generating bug reports. This is null while visiting the body of a - /// constructor or destructor. - const CallExpr *visitingCallExpr; - -public: - WalkAST(const CheckerBase *checker, BugReporter &br, AnalysisDeclContext *ac, - const CXXMethodDecl *rootMethod, bool isInterprocedural, - bool reportPureOnly) - : Checker(checker), BR(br), AC(ac), RootMethod(rootMethod), - IsInterprocedural(isInterprocedural), ReportPureOnly(reportPureOnly), - visitingCallExpr(nullptr) { - // Walking should always start from either a constructor or a destructor. - assert(isa<CXXConstructorDecl>(rootMethod) || - isa<CXXDestructorDecl>(rootMethod)); - } - - bool hasWork() const { return !WList.empty(); } - - /// This method adds a CallExpr to the worklist and marks the callee as - /// being PreVisited. - void Enqueue(WorkListUnit WLUnit) { - const FunctionDecl *FD = WLUnit->getDirectCallee(); - if (!FD || !FD->getBody()) - return; - Kind &K = VisitedFunctions[FD]; - if (K != NotVisited) - return; - K = PreVisited; - WList.push_back(WLUnit); +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 - /// This method returns an item from the worklist without removing it. - WorkListUnit Dequeue() { - assert(!WList.empty()); - return WList.back(); - } +namespace { +class VirtualCallChecker + : public Checker<check::BeginFunction, check::EndFunction, check::PreCall> { + mutable std::unique_ptr<BugType> BT; - void Execute() { - while (hasWork()) { - WorkListUnit WLUnit = Dequeue(); - const FunctionDecl *FD = WLUnit->getDirectCallee(); - assert(FD && FD->getBody()); - - if (VisitedFunctions[FD] == PreVisited) { - // If the callee is PreVisited, walk its body. - // Visit the body. - SaveAndRestore<const CallExpr *> SaveCall(visitingCallExpr, WLUnit); - Visit(FD->getBody()); - - // Mark the function as being PostVisited to indicate we have - // scanned the body. - VisitedFunctions[FD] = PostVisited; - continue; - } - - // Otherwise, the callee is PostVisited. - // Remove it from the worklist. - assert(VisitedFunctions[FD] == PostVisited); - WList.pop_back(); +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<VirtualBugVisitor> { + 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); } - } - - // Stmt visitor methods. - void VisitCallExpr(CallExpr *CE); - void VisitCXXMemberCallExpr(CallExpr *CE); - void VisitStmt(Stmt *S) { VisitChildren(S); } - void VisitChildren(Stmt *S); - - void ReportVirtualCall(const CallExpr *CE, bool isPure); + std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N, + const ExplodedNode *PrevN, + BugReporterContext &BRC, + BugReport &BR) override; + }; }; -} // end anonymous namespace - -//===----------------------------------------------------------------------===// -// AST walking. -//===----------------------------------------------------------------------===// - -void WalkAST::VisitChildren(Stmt *S) { - for (Stmt *Child : S->children()) - if (Child) - Visit(Child); -} - -void WalkAST::VisitCallExpr(CallExpr *CE) { - VisitChildren(CE); - if (IsInterprocedural) - Enqueue(CE); +} // 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, + 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<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->getCurrentStackFrame())); + 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); } -void WalkAST::VisitCXXMemberCallExpr(CallExpr *CE) { - VisitChildren(CE); - bool callIsNonVirtual = false; +// The function to check if a callexpr is a virtual function. +static bool isVirtualCall(const CallExpr *CE) { + bool CallIsNonVirtual = false; - // Several situations to elide for checking. - if (MemberExpr *CME = dyn_cast<MemberExpr>(CE->getCallee())) { - // If the member access is fully qualified (i.e., X::F), then treat - // this as a non-virtual call and do not warn. + 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; + CallIsNonVirtual = true; - if (Expr *base = CME->getBase()->IgnoreImpCasts()) { - // Elide analyzing the call entirely if the base pointer is not 'this'. - if (!isa<CXXThisExpr>(base)) - return; - - // If the most derived class is marked final, we know that now subclass - // can override this member. - if (base->getBestDynamicClassType()->hasAttr<FinalAttr>()) - callIsNonVirtual = true; + if (const Expr *Base = CME->getBase()) { + // The most derived class is marked final. + if (Base->getBestDynamicClassType()->hasAttr<FinalAttr>()) + CallIsNonVirtual = true; } } - // Get the callee. const CXXMethodDecl *MD = dyn_cast_or_null<CXXMethodDecl>(CE->getDirectCallee()); - if (MD && MD->isVirtual() && !callIsNonVirtual && !MD->hasAttr<FinalAttr>() && + if (MD && MD->isVirtual() && !CallIsNonVirtual && !MD->hasAttr<FinalAttr>() && !MD->getParent()->hasAttr<FinalAttr>()) - ReportVirtualCall(CE, MD->isPure()); + return true; + return false; +} + +// The BeginFunction callback when enter a constructor or a destructor. +void VirtualCallChecker::checkBeginFunction(CheckerContext &C) const { + registerCtorDtorCallInState(true, C); +} - if (IsInterprocedural) - Enqueue(CE); +// The EndFunction callback when leave a constructor or a destructor. +void VirtualCallChecker::checkEndFunction(CheckerContext &C) const { + registerCtorDtorCallInState(false, C); } -void WalkAST::ReportVirtualCall(const CallExpr *CE, bool isPure) { - if (ReportPureOnly && !isPure) +void VirtualCallChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + const auto MC = dyn_cast<CXXMemberCall>(&Call); + if (!MC) return; - SmallString<100> buf; - llvm::raw_svector_ostream os(buf); - - // FIXME: The interprocedural diagnostic experience here is not good. - // Ultimately this checker should be re-written to be path sensitive. - // For now, only diagnose intraprocedurally, by default. - if (IsInterprocedural) { - os << "Call Path : "; - // Name of current visiting CallExpr. - os << *CE->getDirectCallee(); - - // Name of the CallExpr whose body is current being walked. - if (visitingCallExpr) - os << " <-- " << *visitingCallExpr->getDirectCallee(); - // Names of FunctionDecls in worklist with state PostVisited. - for (SmallVectorImpl<const CallExpr *>::iterator I = WList.end(), - E = WList.begin(); I != E; --I) { - const FunctionDecl *FD = (*(I-1))->getDirectCallee(); - assert(FD); - if (VisitedFunctions[FD] == PostVisited) - os << " <-- " << *FD; - } + 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()); - os << "\n"; + 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); } - PathDiagnosticLocation CELoc = - PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC); - SourceRange R = CE->getCallee()->getSourceRange(); + 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); + } +} - os << "Call to "; - if (isPure) - os << "pure "; +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; - os << "virtual function during "; + ProgramStateRef State = C.getState(); + auto &SVB = C.getSValBuilder(); - if (isa<CXXConstructorDecl>(RootMethod)) - os << "construction "; - else - os << "destruction "; + // Enter a constructor, set the corresponding memregion be true. + if (isa<CXXConstructorDecl>(MD)) { + auto ThiSVal = + State->getSVal(SVB.getCXXThis(MD, LCtx->getCurrentStackFrame())); + const MemRegion *Reg = ThiSVal.getAsRegion(); + if (IsBeginFunction) + State = State->set<CtorDtorMap>(Reg, ObjectState::CtorCalled); + else + State = State->remove<CtorDtorMap>(Reg); - if (isPure) - os << "has undefined behavior"; - else - os << "will not dispatch to derived class"; + C.addTransition(State); + return; + } - BR.EmitBasicReport(AC->getDecl(), Checker, - "Call to virtual function during construction or " - "destruction", - "C++ Object Lifecycle", os.str(), CELoc, R); + // Enter a Destructor, set the corresponding memregion be true. + if (isa<CXXDestructorDecl>(MD)) { + auto ThiSVal = + State->getSVal(SVB.getCXXThis(MD, LCtx->getCurrentStackFrame())); + const MemRegion *Reg = ThiSVal.getAsRegion(); + if (IsBeginFunction) + State = State->set<CtorDtorMap>(Reg, ObjectState::DtorCalled); + else + State = State->remove<CtorDtorMap>(Reg); + + C.addTransition(State); + return; + } } -//===----------------------------------------------------------------------===// -// VirtualCallChecker -//===----------------------------------------------------------------------===// - -namespace { -class VirtualCallChecker : public Checker<check::ASTDecl<CXXRecordDecl> > { -public: - DefaultBool isInterprocedural; - DefaultBool isPureOnly; - - void checkASTDecl(const CXXRecordDecl *RD, AnalysisManager& mgr, - BugReporter &BR) const { - AnalysisDeclContext *ADC = mgr.getAnalysisDeclContext(RD); - - // Check the constructors. - for (const auto *I : RD->ctors()) { - if (!I->isCopyOrMoveConstructor()) - if (Stmt *Body = I->getBody()) { - WalkAST walker(this, BR, ADC, I, isInterprocedural, isPureOnly); - walker.Visit(Body); - walker.Execute(); - } - } +void VirtualCallChecker::reportBug(StringRef Msg, bool IsSink, + const MemRegion *Reg, + CheckerContext &C) const { + ExplodedNode *N; + if (IsSink) + N = C.generateErrorNode(); + else + N = C.generateNonFatalErrorNode(); - // Check the destructor. - if (CXXDestructorDecl *DD = RD->getDestructor()) - if (Stmt *Body = DD->getBody()) { - WalkAST walker(this, BR, ADC, DD, isInterprocedural, isPureOnly); - walker.Visit(Body); - walker.Execute(); - } - } -}; + 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->isInterprocedural = - mgr.getAnalyzerOptions().getBooleanOption("Interprocedural", false, - checker); - checker->isPureOnly = - mgr.getAnalyzerOptions().getBooleanOption("PureOnly", false, - checker); + checker->IsPureOnly = + mgr.getAnalyzerOptions().getBooleanOption("PureOnly", false, checker); } |