diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1343,6 +1343,11 @@ HelpText<"Emits a warning for every statement.">, Documentation; +def DebugIteratorModeling : Checker<"DebugIteratorModeling">, + HelpText<"Check the analyzer's understanding of C++ iterators">, + Dependencies<[IteratorModeling]>, + Documentation; + } // end "debug" diff --git a/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp --- a/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp @@ -173,11 +173,12 @@ class IteratorChecker : public Checker, check::Bind, - check::LiveSymbols, check::DeadSymbols> { + check::LiveSymbols, check::DeadSymbols, eval::Call> { std::unique_ptr OutOfRangeBugType; std::unique_ptr MismatchedBugType; std::unique_ptr InvalidatedBugType; + std::unique_ptr DebugMsgBugType; void handleComparison(CheckerContext &C, const Expr *CE, const SVal &RetVal, const SVal &LVal, const SVal &RVal, @@ -236,7 +237,35 @@ ExplodedNode *ErrNode) const; void reportInvalidatedBug(const StringRef &Message, const SVal &Val, CheckerContext &C, ExplodedNode *ErrNode) const; - + template + void analyzerContainerDataField(const CallExpr *CE, CheckerContext &C, + Getter get) const; + void analyzerContainerBegin(const CallExpr *CE, CheckerContext &C) const; + void analyzerContainerEnd(const CallExpr *CE, CheckerContext &C) const; + template + void analyzerIteratorDataField(const CallExpr *CE, CheckerContext &C, + Getter get, SVal Default) const; + void analyzerIteratorPosition(const CallExpr *CE, CheckerContext &C) const; + void analyzerIteratorContainer(const CallExpr *CE, CheckerContext &C) const; + void analyzerIteratorValidity(const CallExpr *CE, CheckerContext &C) const; + ExplodedNode *reportDebugMsg(llvm::StringRef Msg, CheckerContext &C) const; + + typedef void (IteratorChecker::*FnCheck)(const CallExpr *, + CheckerContext &) const; + + CallDescriptionMap Callbacks = { + {{0, "clang_analyzer_container_begin", 1}, + &IteratorChecker::analyzerContainerBegin}, + {{0, "clang_analyzer_container_end", 1}, + &IteratorChecker::analyzerContainerEnd}, + {{0, "clang_analyzer_iterator_position", 1}, + &IteratorChecker::analyzerIteratorPosition}, + {{0, "clang_analyzer_iterator_container", 1}, + &IteratorChecker::analyzerIteratorContainer}, + {{0, "clang_analyzer_iterator_validity", 1}, + &IteratorChecker::analyzerIteratorValidity}, + }; + public: IteratorChecker(); @@ -244,6 +273,7 @@ CK_IteratorRangeChecker, CK_MismatchedIteratorChecker, CK_InvalidatedIteratorChecker, + CK_DebugIteratorModeling, CK_NumCheckKinds }; @@ -259,6 +289,7 @@ CheckerContext &C) const; void checkLiveSymbols(ProgramStateRef State, SymbolReaper &SR) const; void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; + bool evalCall(const CallEvent &Call, CheckerContext &C) const; }; } // namespace @@ -362,6 +393,9 @@ /*SuppressOnSink=*/true)); InvalidatedBugType.reset( new BugType(this, "Iterator invalidated", "Misuse of STL APIs")); + DebugMsgBugType.reset( + new BugType(this, "Checking analyzer assumptions", "debug", + /*SuppressOnSink=*/true)); } void IteratorChecker::checkPreCall(const CallEvent &Call, @@ -1627,6 +1661,124 @@ C.emitReport(std::move(R)); } +bool IteratorChecker::evalCall(const CallEvent &Call, + CheckerContext &C) const { + if (!ChecksEnabled[CK_DebugIteratorModeling]) + return false; + + const auto *CE = dyn_cast_or_null(Call.getOriginExpr()); + if (!CE) + return false; + + const FnCheck *Handler = Callbacks.lookup(Call); + if (!Handler) + return false; + + (this->**Handler)(CE, C); + return true; +} + +template +void IteratorChecker::analyzerContainerDataField(const CallExpr *CE, + CheckerContext &C, + Getter get) const { + if (CE->getNumArgs() == 0) { + reportDebugMsg("Missing container argument", C); + return; + } + + auto State = C.getState(); + const MemRegion *Cont = C.getSVal(CE->getArg(0)).getAsRegion(); + if (Cont) { + const auto *Data = getContainerData(State, Cont); + if (Data) { + SymbolRef Field = get(Data); + if (Field) { + State = State->BindExpr(CE, C.getLocationContext(), + nonloc::SymbolVal(Field)); + C.addTransition(State); + return; + } + } + } + + auto &BVF = C.getSValBuilder().getBasicValueFactory(); + State = State->BindExpr(CE, C.getLocationContext(), + nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(0)))); +} + +void IteratorChecker::analyzerContainerBegin(const CallExpr *CE, + CheckerContext &C) const { + analyzerContainerDataField(CE, C, [](const ContainerData *D) { + return D->getBegin(); + }); +} + +void IteratorChecker::analyzerContainerEnd(const CallExpr *CE, + CheckerContext &C) const { + analyzerContainerDataField(CE, C, [](const ContainerData *D) { + return D->getEnd(); + }); +} + +template +void IteratorChecker::analyzerIteratorDataField(const CallExpr *CE, + CheckerContext &C, + Getter get, + SVal Default) const { + if (CE->getNumArgs() == 0) { + reportDebugMsg("Missing iterator argument", C); + return; + } + + auto State = C.getState(); + SVal V = C.getSVal(CE->getArg(0)); + const auto *Pos = getIteratorPosition(State, V); + if (Pos) { + State = State->BindExpr(CE, C.getLocationContext(), get(Pos)); + } else { + State = State->BindExpr(CE, C.getLocationContext(), Default); + } + C.addTransition(State); +} + +void IteratorChecker::analyzerIteratorPosition(const CallExpr *CE, + CheckerContext &C) const { + auto &BVF = C.getSValBuilder().getBasicValueFactory(); + analyzerIteratorDataField(CE, C, [](const IteratorPosition *P) { + return nonloc::SymbolVal(P->getOffset()); + }, nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(0)))); +} + +void IteratorChecker::analyzerIteratorContainer(const CallExpr *CE, + CheckerContext &C) const { + auto &BVF = C.getSValBuilder().getBasicValueFactory(); + analyzerIteratorDataField(CE, C, [](const IteratorPosition *P) { + return loc::MemRegionVal(P->getContainer()); + }, loc::ConcreteInt(BVF.getValue(llvm::APSInt::get(0)))); +} + +void IteratorChecker::analyzerIteratorValidity(const CallExpr *CE, + CheckerContext &C) const { + auto &BVF = C.getSValBuilder().getBasicValueFactory(); + analyzerIteratorDataField(CE, C, [&BVF](const IteratorPosition *P) { + return + nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get((P->isValid())))); + }, nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(0)))); +} + +ExplodedNode *IteratorChecker::reportDebugMsg(llvm::StringRef Msg, + CheckerContext &C) const { + ExplodedNode *N = C.generateNonFatalErrorNode(); + if (!N) + return nullptr; + + auto &BR = C.getBugReporter(); + BR.emitReport(std::make_unique(*DebugMsgBugType, + Msg, N)); + return N; +} + namespace { bool isLess(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2); @@ -2388,3 +2540,4 @@ REGISTER_CHECKER(IteratorRangeChecker) REGISTER_CHECKER(MismatchedIteratorChecker) REGISTER_CHECKER(InvalidatedIteratorChecker) +REGISTER_CHECKER(DebugIteratorModeling) diff --git a/clang/test/Analysis/debug-iterator-modeling.cpp b/clang/test/Analysis/debug-iterator-modeling.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/debug-iterator-modeling.cpp @@ -0,0 +1,61 @@ +// RUN: %clang_analyze_cc1 -std=c++11\ +// RUN: -analyzer-checker=core,cplusplus\ +// RUN: -analyzer-checker=debug.DebugIteratorModeling,debug.ExprInspection\ +// RUN: -analyzer-config aggressive-binary-operation-simplification=true\ +// RUN: -analyzer-config c++-container-inlining=false %s -verify + +// RUN: %clang_analyze_cc1 -std=c++11\ +// RUN: -analyzer-checker=core,cplusplus\ +// RUN: -analyzer-checker=debug.DebugIteratorModeling,debug.ExprInspection\ +// RUN: -analyzer-config aggressive-binary-operation-simplification=true\ +// RUN: -analyzer-config c++-container-inlining=true -DINLINE=1 %s -verify + +#include "Inputs/system-header-simulator-cxx.h" + +template +long clang_analyzer_container_begin(const Container&); +template +long clang_analyzer_container_end(const Container&); +template +long clang_analyzer_iterator_position(const Iterator&); +template +void* clang_analyzer_iterator_container(const Iterator&); +template +bool clang_analyzer_iterator_validity(const Iterator&); +void clang_analyzer_denote(long, const char*); +void clang_analyzer_express(long); +void clang_analyzer_dump(const void*); +void clang_analyzer_eval(bool); + +void iterator_position(const std::vector v0) { + auto b0 = v0.begin(), e0 = v0.end(); + + clang_analyzer_denote(clang_analyzer_iterator_position(b0), "$b0"); + clang_analyzer_denote(clang_analyzer_iterator_position(e0), "$e0"); + + clang_analyzer_express(clang_analyzer_iterator_position(b0)); // expected-warning{{$b0}} + clang_analyzer_express(clang_analyzer_iterator_position(e0)); // expected-warning{{$e0}} + + clang_analyzer_express(clang_analyzer_container_begin(v0)); // expected-warning{{$b0}} + clang_analyzer_express(clang_analyzer_container_end(v0)); // expected-warning{{$e0}} + + ++b0; + + clang_analyzer_express(clang_analyzer_iterator_position(b0)); // expected-warning{{$b0 + 1}} +} + +void iterator_container(const std::vector v0) { + auto b0 = v0.begin(); + + clang_analyzer_dump(&v0); //expected-warning{{&v0}} + clang_analyzer_eval(clang_analyzer_iterator_container(b0) == &v0); // expected-warning{{TRUE}} +} + +void iterator_validity(std::vector v0) { + auto b0 = v0.begin(); + clang_analyzer_eval(clang_analyzer_iterator_validity(b0)); //expected-warning{{TRUE}} + + v0.clear(); + + clang_analyzer_eval(clang_analyzer_iterator_validity(b0)); //expected-warning{{FALSE}} +}