diff options
author | Dimitry Andric <dim@FreeBSD.org> | 2017-05-30 17:37:44 +0000 |
---|---|---|
committer | Dimitry Andric <dim@FreeBSD.org> | 2017-05-30 17:37:44 +0000 |
commit | 550ae89a710bf458d47e5b1d183f5e7039c2b384 (patch) | |
tree | 4eab680d9198cddf87acb23a14c836472b21ae89 /lib/StaticAnalyzer | |
parent | b5aee35cc5d62f11d98539f62e4fe63f0ac9edc6 (diff) | |
download | src-550ae89a710bf458d47e5b1d183f5e7039c2b384.tar.gz src-550ae89a710bf458d47e5b1d183f5e7039c2b384.zip |
Vendor import of clang trunk r304222:vendor/clang/clang-trunk-r304222
Notes
Notes:
svn path=/vendor/clang/dist/; revision=319233
svn path=/vendor/clang/clang-trunk-r304222/; revision=319234; tag=vendor/clang/clang-trunk-r304222
Diffstat (limited to 'lib/StaticAnalyzer')
-rw-r--r-- | lib/StaticAnalyzer/Checkers/CMakeLists.txt | 2 | ||||
-rw-r--r-- | lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp | 83 | ||||
-rw-r--r-- | lib/StaticAnalyzer/Checkers/IteratorChecker.cpp | 833 | ||||
-rw-r--r-- | lib/StaticAnalyzer/Checkers/IteratorPastEndChecker.cpp | 840 | ||||
-rw-r--r-- | lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp | 146 | ||||
-rw-r--r-- | lib/StaticAnalyzer/Core/ProgramState.cpp | 100 | ||||
-rw-r--r-- | lib/StaticAnalyzer/Core/RegionStore.cpp | 5 |
7 files changed, 1075 insertions, 934 deletions
diff --git a/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/lib/StaticAnalyzer/Checkers/CMakeLists.txt index 60d60bc074eb..2759240dd276 100644 --- a/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -39,7 +39,7 @@ add_clang_library(clangStaticAnalyzerCheckers GenericTaintChecker.cpp GTestChecker.cpp IdenticalExprChecker.cpp - IteratorPastEndChecker.cpp + IteratorChecker.cpp IvarInvalidationChecker.cpp LLVMConventionsChecker.cpp LocalizationChecker.cpp diff --git a/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp b/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp index b1a54e77951b..883c6a663291 100644 --- a/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp @@ -65,21 +65,8 @@ private: /// and thus, is tainted. static bool isStdin(const Expr *E, CheckerContext &C); - /// This is called from getPointedToSymbol() to resolve symbol references for - /// the region underlying a LazyCompoundVal. This is the default binding - /// for the LCV, which could be a conjured symbol from a function call that - /// initialized the region. It only returns the conjured symbol if the LCV - /// covers the entire region, e.g. we avoid false positives by not returning - /// a default bindingc for an entire struct if the symbol for only a single - /// field or element within it is requested. - // TODO: Return an appropriate symbol for sub-fields/elements of an LCV so - // that they are also appropriately tainted. - static SymbolRef getLCVSymbol(CheckerContext &C, - nonloc::LazyCompoundVal &LCV); - - /// \brief Given a pointer argument, get the symbol of the value it contains - /// (points to). - static SymbolRef getPointedToSymbol(CheckerContext &C, const Expr *Arg); + /// \brief Given a pointer argument, return the value it points to. + static Optional<SVal> getPointedToSVal(CheckerContext &C, const Expr *Arg); /// Functions defining the attack surface. typedef ProgramStateRef (GenericTaintChecker::*FnCheck)(const CallExpr *, @@ -186,9 +173,14 @@ private: static inline bool isTaintedOrPointsToTainted(const Expr *E, ProgramStateRef State, CheckerContext &C) { - return (State->isTainted(E, C.getLocationContext()) || isStdin(E, C) || - (E->getType().getTypePtr()->isPointerType() && - State->isTainted(getPointedToSymbol(C, E)))); + if (State->isTainted(E, C.getLocationContext()) || isStdin(E, C)) + return true; + + if (!E->getType().getTypePtr()->isPointerType()) + return false; + + Optional<SVal> V = getPointedToSVal(C, E); + return (V && State->isTainted(*V)); } /// \brief Pre-process a function which propagates taint according to the @@ -400,9 +392,9 @@ bool GenericTaintChecker::propagateFromPre(const CallExpr *CE, if (CE->getNumArgs() < (ArgNum + 1)) return false; const Expr* Arg = CE->getArg(ArgNum); - SymbolRef Sym = getPointedToSymbol(C, Arg); - if (Sym) - State = State->addTaint(Sym); + Optional<SVal> V = getPointedToSVal(C, Arg); + if (V) + State = State->addTaint(*V); } // Clear up the taint info from the state. @@ -473,47 +465,20 @@ bool GenericTaintChecker::checkPre(const CallExpr *CE, CheckerContext &C) const{ return false; } -SymbolRef GenericTaintChecker::getLCVSymbol(CheckerContext &C, - nonloc::LazyCompoundVal &LCV) { - StoreManager &StoreMgr = C.getStoreManager(); - - // getLCVSymbol() is reached in a PostStmt so we can always expect a default - // binding to exist if one is present. - if (Optional<SVal> binding = StoreMgr.getDefaultBinding(LCV)) { - SymbolRef Sym = binding->getAsSymbol(); - if (!Sym) - return nullptr; - - // If the LCV covers an entire base region return the default conjured symbol. - if (LCV.getRegion() == LCV.getRegion()->getBaseRegion()) - return Sym; - } - - // Otherwise, return a nullptr as there's not yet a functional way to taint - // sub-regions of LCVs. - return nullptr; -} - -SymbolRef GenericTaintChecker::getPointedToSymbol(CheckerContext &C, - const Expr* Arg) { +Optional<SVal> GenericTaintChecker::getPointedToSVal(CheckerContext &C, + const Expr* Arg) { ProgramStateRef State = C.getState(); SVal AddrVal = State->getSVal(Arg->IgnoreParens(), C.getLocationContext()); if (AddrVal.isUnknownOrUndef()) - return nullptr; + return None; Optional<Loc> AddrLoc = AddrVal.getAs<Loc>(); if (!AddrLoc) - return nullptr; + return None; const PointerType *ArgTy = dyn_cast<PointerType>(Arg->getType().getCanonicalType().getTypePtr()); - SVal Val = State->getSVal(*AddrLoc, - ArgTy ? ArgTy->getPointeeType(): QualType()); - - if (auto LCV = Val.getAs<nonloc::LazyCompoundVal>()) - return getLCVSymbol(C, *LCV); - - return Val.getAsSymbol(); + return State->getSVal(*AddrLoc, ArgTy ? ArgTy->getPointeeType(): QualType()); } ProgramStateRef @@ -633,9 +598,9 @@ ProgramStateRef GenericTaintChecker::postScanf(const CallExpr *CE, // The arguments are pointer arguments. The data they are pointing at is // tainted after the call. const Expr* Arg = CE->getArg(i); - SymbolRef Sym = getPointedToSymbol(C, Arg); - if (Sym) - State = State->addTaint(Sym); + Optional<SVal> V = getPointedToSVal(C, Arg); + if (V) + State = State->addTaint(*V); } return State; } @@ -710,10 +675,10 @@ bool GenericTaintChecker::generateReportIfTainted(const Expr *E, // Check for taint. ProgramStateRef State = C.getState(); - const SymbolRef PointedToSym = getPointedToSymbol(C, E); + Optional<SVal> PointedToSVal = getPointedToSVal(C, E); SVal TaintedSVal; - if (State->isTainted(PointedToSym)) - TaintedSVal = nonloc::SymbolVal(PointedToSym); + if (PointedToSVal && State->isTainted(*PointedToSVal)) + TaintedSVal = *PointedToSVal; else if (State->isTainted(E, C.getLocationContext())) TaintedSVal = C.getSVal(E); else diff --git a/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp b/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp new file mode 100644 index 000000000000..0f9b749506fa --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp @@ -0,0 +1,833 @@ +//===-- IteratorChecker.cpp ---------------------------------------*- C++ -*--// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Defines a checker for using iterators outside their range (past end). Usage +// means here dereferencing, incrementing etc. +// +//===----------------------------------------------------------------------===// +// +// In the code, iterator can be represented as a: +// * type-I: typedef-ed pointer. Operations over such iterator, such as +// comparisons or increments, are modeled straightforwardly by the +// analyzer. +// * type-II: structure with its method bodies available. Operations over such +// iterator are inlined by the analyzer, and results of modeling +// these operations are exposing implementation details of the +// iterators, which is not necessarily helping. +// * type-III: completely opaque structure. Operations over such iterator are +// modeled conservatively, producing conjured symbols everywhere. +// +// To handle all these types in a common way we introduce a structure called +// IteratorPosition which is an abstraction of the position the iterator +// represents using symbolic expressions. The checker handles all the +// operations on this structure. +// +// Additionally, depending on the circumstances, operators of types II and III +// can be represented as: +// * type-IIa, type-IIIa: conjured structure symbols - when returned by value +// from conservatively evaluated methods such as +// `.begin()`. +// * type-IIb, type-IIIb: memory regions of iterator-typed objects, such as +// variables or temporaries, when the iterator object is +// currently treated as an lvalue. +// * type-IIc, type-IIIc: compound values of iterator-typed objects, when the +// iterator object is treated as an rvalue taken of a +// particular lvalue, eg. a copy of "type-a" iterator +// object, or an iterator that existed before the +// analysis has started. +// +// To handle any of these three different representations stored in an SVal we +// use setter and getters functions which separate the three cases. To store +// them we use a pointer union of symbol and memory region. +// +// The checker works the following way: We record the past-end iterator for +// all containers whenever their `.end()` is called. Since the Constraint +// Manager cannot handle SVals we need to take over its role. We post-check +// equality and non-equality comparisons and propagate the position of the +// iterator to the other side of the comparison if it is past-end and we are in +// the 'equal' branch (true-branch for `==` and false-branch for `!=`). +// +// In case of type-I or type-II iterators we get a concrete integer as a result +// of the comparison (1 or 0) but in case of type-III we only get a Symbol. In +// this latter case we record the symbol and reload it in evalAssume() and do +// the propagation there. We also handle (maybe double) negated comparisons +// which are represented in the form of (x == 0 or x !=0 ) where x is the +// comparison itself. + +#include "ClangSACheckers.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" + +using namespace clang; +using namespace ento; + +namespace { + +// Abstract position of an iterator. This helps to handle all three kinds +// of operators in a common way by using a symbolic position. +struct IteratorPosition { +private: + + // Container the iterator belongs to + const MemRegion *Cont; + + // Abstract offset + SymbolRef Offset; + + IteratorPosition(const MemRegion *C, SymbolRef Of) + : Cont(C), Offset(Of) {} + +public: + const MemRegion *getContainer() const { return Cont; } + SymbolRef getOffset() const { return Offset; } + + static IteratorPosition getPosition(const MemRegion *C, SymbolRef Of) { + return IteratorPosition(C, Of); + } + + IteratorPosition setTo(SymbolRef NewOf) const { + return IteratorPosition(Cont, NewOf); + } + + bool operator==(const IteratorPosition &X) const { + return Cont == X.Cont && Offset == X.Offset; + } + + bool operator!=(const IteratorPosition &X) const { + return Cont != X.Cont || Offset != X.Offset; + } + + void Profile(llvm::FoldingSetNodeID &ID) const { + ID.AddPointer(Cont); + ID.Add(Offset); + } +}; + +typedef llvm::PointerUnion<const MemRegion *, SymbolRef> RegionOrSymbol; + +// Structure to record the symbolic end position of a container +struct ContainerData { +private: + SymbolRef End; + + ContainerData(SymbolRef E) : End(E) {} + +public: + static ContainerData fromEnd(SymbolRef E) { + return ContainerData(E); + } + + SymbolRef getEnd() const { return End; } + + ContainerData newEnd(SymbolRef E) const { return ContainerData(E); } + + bool operator==(const ContainerData &X) const { + return End == X.End; + } + + bool operator!=(const ContainerData &X) const { + return End != X.End; + } + + void Profile(llvm::FoldingSetNodeID &ID) const { + ID.Add(End); + } +}; + +// Structure fo recording iterator comparisons. We needed to retrieve the +// original comparison expression in assumptions. +struct IteratorComparison { +private: + RegionOrSymbol Left, Right; + bool Equality; + +public: + IteratorComparison(RegionOrSymbol L, RegionOrSymbol R, bool Eq) + : Left(L), Right(R), Equality(Eq) {} + + RegionOrSymbol getLeft() const { return Left; } + RegionOrSymbol getRight() const { return Right; } + bool isEquality() const { return Equality; } + bool operator==(const IteratorComparison &X) const { + return Left == X.Left && Right == X.Right && Equality == X.Equality; + } + bool operator!=(const IteratorComparison &X) const { + return Left != X.Left || Right != X.Right || Equality != X.Equality; + } + void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(Equality); } +}; + +class IteratorChecker + : public Checker<check::PreCall, check::PostCall, + check::PostStmt<MaterializeTemporaryExpr>, + check::DeadSymbols, + eval::Assume> { + + std::unique_ptr<BugType> OutOfRangeBugType; + + void handleComparison(CheckerContext &C, const SVal &RetVal, const SVal &LVal, + const SVal &RVal, OverloadedOperatorKind Op) const; + void verifyDereference(CheckerContext &C, const SVal &Val) const; + void handleEnd(CheckerContext &C, const Expr *CE, const SVal &RetVal, + const SVal &Cont) const; + void assignToContainer(CheckerContext &C, const Expr *CE, const SVal &RetVal, + const MemRegion *Cont) const; + void reportOutOfRangeBug(const StringRef &Message, const SVal &Val, + CheckerContext &C, ExplodedNode *ErrNode) const; + +public: + IteratorChecker(); + + enum CheckKind { + CK_IteratorRangeChecker, + CK_NumCheckKinds + }; + + DefaultBool ChecksEnabled[CK_NumCheckKinds]; + CheckName CheckNames[CK_NumCheckKinds]; + + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; + void checkPostStmt(const MaterializeTemporaryExpr *MTE, + CheckerContext &C) const; + void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; + ProgramStateRef evalAssume(ProgramStateRef State, SVal Cond, + bool Assumption) const; +}; +} // namespace + +REGISTER_MAP_WITH_PROGRAMSTATE(IteratorSymbolMap, SymbolRef, IteratorPosition) +REGISTER_MAP_WITH_PROGRAMSTATE(IteratorRegionMap, const MemRegion *, + IteratorPosition) + +REGISTER_MAP_WITH_PROGRAMSTATE(ContainerMap, const MemRegion *, ContainerData) + +REGISTER_MAP_WITH_PROGRAMSTATE(IteratorComparisonMap, const SymExpr *, + IteratorComparison) + +namespace { + +bool isIteratorType(const QualType &Type); +bool isIterator(const CXXRecordDecl *CRD); +bool isEndCall(const FunctionDecl *Func); +bool isSimpleComparisonOperator(OverloadedOperatorKind OK); +bool isDereferenceOperator(OverloadedOperatorKind OK); +BinaryOperator::Opcode getOpcode(const SymExpr *SE); +const RegionOrSymbol getRegionOrSymbol(const SVal &Val); +const ProgramStateRef processComparison(ProgramStateRef State, + RegionOrSymbol LVal, + RegionOrSymbol RVal, bool Equal); +const ProgramStateRef saveComparison(ProgramStateRef State, + const SymExpr *Condition, const SVal &LVal, + const SVal &RVal, bool Eq); +const IteratorComparison *loadComparison(ProgramStateRef State, + const SymExpr *Condition); +SymbolRef getContainerEnd(ProgramStateRef State, const MemRegion *Cont); +ProgramStateRef createContainerEnd(ProgramStateRef State, const MemRegion *Cont, + const SymbolRef Sym); +const IteratorPosition *getIteratorPosition(ProgramStateRef State, + const SVal &Val); +const IteratorPosition *getIteratorPosition(ProgramStateRef State, + RegionOrSymbol RegOrSym); +ProgramStateRef setIteratorPosition(ProgramStateRef State, const SVal &Val, + const IteratorPosition &Pos); +ProgramStateRef setIteratorPosition(ProgramStateRef State, + RegionOrSymbol RegOrSym, + const IteratorPosition &Pos); +ProgramStateRef removeIteratorPosition(ProgramStateRef State, const SVal &Val); +ProgramStateRef adjustIteratorPosition(ProgramStateRef State, + RegionOrSymbol RegOrSym, + const IteratorPosition &Pos, bool Equal); +ProgramStateRef relateIteratorPositions(ProgramStateRef State, + const IteratorPosition &Pos1, + const IteratorPosition &Pos2, + bool Equal); +const ContainerData *getContainerData(ProgramStateRef State, + const MemRegion *Cont); +ProgramStateRef setContainerData(ProgramStateRef State, const MemRegion *Cont, + const ContainerData &CData); +bool isOutOfRange(ProgramStateRef State, const IteratorPosition &Pos); +} // namespace + +IteratorChecker::IteratorChecker() { + OutOfRangeBugType.reset( + new BugType(this, "Iterator out of range", "Misuse of STL APIs")); + OutOfRangeBugType->setSuppressOnSink(true); +} + +void IteratorChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + // Check for out of range access + const auto *Func = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); + if (!Func) + return; + + if (Func->isOverloadedOperator()) { + if (ChecksEnabled[CK_IteratorRangeChecker] && + isDereferenceOperator(Func->getOverloadedOperator())) { + // Check for dereference of out-of-range iterators + if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { + verifyDereference(C, InstCall->getCXXThisVal()); + } else { + verifyDereference(C, Call.getArgSVal(0)); + } + } + } +} + +void IteratorChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + // Record new iterator positions and iterator position changes + const auto *Func = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); + if (!Func) + return; + + if (Func->isOverloadedOperator()) { + const auto Op = Func->getOverloadedOperator(); + if (isSimpleComparisonOperator(Op)) { + if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { + handleComparison(C, Call.getReturnValue(), InstCall->getCXXThisVal(), + Call.getArgSVal(0), Op); + } else { + handleComparison(C, Call.getReturnValue(), Call.getArgSVal(0), + Call.getArgSVal(1), Op); + } + } + } else { + const auto *OrigExpr = Call.getOriginExpr(); + if (!OrigExpr) + return; + + if (!isIteratorType(Call.getResultType())) + return; + + auto State = C.getState(); + // Already bound to container? + if (getIteratorPosition(State, Call.getReturnValue())) + return; + + if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { + if (isEndCall(Func)) { + handleEnd(C, OrigExpr, Call.getReturnValue(), + InstCall->getCXXThisVal()); + return; + } + } + + // Copy-like and move constructors + if (isa<CXXConstructorCall>(&Call) && Call.getNumArgs() == 1) { + if (const auto *Pos = getIteratorPosition(State, Call.getArgSVal(0))) { + State = setIteratorPosition(State, Call.getReturnValue(), *Pos); + if (cast<CXXConstructorDecl>(Func)->isMoveConstructor()) { + State = removeIteratorPosition(State, Call.getArgSVal(0)); + } + C.addTransition(State); + return; + } + } + + // Assumption: if return value is an iterator which is not yet bound to a + // container, then look for the first iterator argument, and + // bind the return value to the same container. This approach + // works for STL algorithms. + // FIXME: Add a more conservative mode + for (unsigned i = 0; i < Call.getNumArgs(); ++i) { + if (isIteratorType(Call.getArgExpr(i)->getType())) { + if (const auto *Pos = getIteratorPosition(State, Call.getArgSVal(i))) { + assignToContainer(C, OrigExpr, Call.getReturnValue(), + Pos->getContainer()); + return; + } + } + } + } +} + +void IteratorChecker::checkPostStmt(const MaterializeTemporaryExpr *MTE, + CheckerContext &C) const { + /* Transfer iterator state to temporary objects */ + auto State = C.getState(); + const auto *LCtx = C.getLocationContext(); + const auto *Pos = + getIteratorPosition(State, State->getSVal(MTE->GetTemporaryExpr(), LCtx)); + if (!Pos) + return; + State = setIteratorPosition(State, State->getSVal(MTE, LCtx), *Pos); + C.addTransition(State); +} + +void IteratorChecker::checkDeadSymbols(SymbolReaper &SR, + CheckerContext &C) const { + // Cleanup + auto State = C.getState(); + + auto RegionMap = State->get<IteratorRegionMap>(); + for (const auto Reg : RegionMap) { + if (!SR.isLiveRegion(Reg.first)) { + State = State->remove<IteratorRegionMap>(Reg.first); + } + } + + auto SymbolMap = State->get<IteratorSymbolMap>(); + for (const auto Sym : SymbolMap) { + if (!SR.isLive(Sym.first)) { + State = State->remove<IteratorSymbolMap>(Sym.first); + } + } + + auto ContMap = State->get<ContainerMap>(); + for (const auto Cont : ContMap) { + if (!SR.isLiveRegion(Cont.first)) { + State = State->remove<ContainerMap>(Cont.first); + } + } + + auto ComparisonMap = State->get<IteratorComparisonMap>(); + for (const auto Comp : ComparisonMap) { + if (!SR.isLive(Comp.first)) { + State = State->remove<IteratorComparisonMap>(Comp.first); + } + } +} + +ProgramStateRef IteratorChecker::evalAssume(ProgramStateRef State, SVal Cond, + bool Assumption) const { + // Load recorded comparison and transfer iterator state between sides + // according to comparison operator and assumption + const auto *SE = Cond.getAsSymExpr(); + if (!SE) + return State; + + auto Opc = getOpcode(SE); + if (Opc != BO_EQ && Opc != BO_NE) + return State; + + bool Negated = false; + const auto *Comp = loadComparison(State, SE); + if (!Comp) { + // Try negated comparison, which is a SymExpr to 0 integer comparison + const auto *SIE = dyn_cast<SymIntExpr>(SE); + if (!SIE) + return State; + + if (SIE->getRHS() != 0) + return State; + + SE = SIE->getLHS(); + Negated = SIE->getOpcode() == BO_EQ; // Equal to zero means negation + Opc = getOpcode(SE); + if (Opc != BO_EQ && Opc != BO_NE) + return State; + + Comp = loadComparison(State, SE); + if (!Comp) + return State; + } + + return processComparison(State, Comp->getLeft(), Comp->getRight(), + (Comp->isEquality() == Assumption) != Negated); +} + +void IteratorChecker::handleComparison(CheckerContext &C, const SVal &RetVal, + const SVal &LVal, const SVal &RVal, + OverloadedOperatorKind Op) const { + // Record the operands and the operator of the comparison for the next + // evalAssume, if the result is a symbolic expression. If it is a concrete + // value (only one branch is possible), then transfer the state between + // the operands according to the operator and the result + auto State = C.getState(); + if (const auto *Condition = RetVal.getAsSymbolicExpression()) { + const auto *LPos = getIteratorPosition(State, LVal); + const auto *RPos = getIteratorPosition(State, RVal); + if (!LPos && !RPos) + return; + State = saveComparison(State, Condition, LVal, RVal, Op == OO_EqualEqual); + C.addTransition(State); + } else if (const auto TruthVal = RetVal.getAs<nonloc::ConcreteInt>()) { + if ((State = processComparison( + State, getRegionOrSymbol(LVal), getRegionOrSymbol(RVal), + (Op == OO_EqualEqual) == (TruthVal->getValue() != 0)))) { + C.addTransition(State); + } else { + C.generateSink(State, C.getPredecessor()); + } + } +} + +void IteratorChecker::verifyDereference(CheckerContext &C, + const SVal &Val) const { + auto State = C.getState(); + const auto *Pos = getIteratorPosition(State, Val); + if (Pos && isOutOfRange(State, *Pos)) { + // If I do not put a tag here, some range tests will fail + static CheckerProgramPointTag Tag("IteratorRangeChecker", + "IteratorOutOfRange"); + auto *N = C.generateNonFatalErrorNode(State, &Tag); + if (!N) { + return; + } + reportOutOfRangeBug("Iterator accessed outside of its range.", Val, C, N); + } +} + +void IteratorChecker::handleEnd(CheckerContext &C, const Expr *CE, + const SVal &RetVal, const SVal &Cont) const { + const auto *ContReg = Cont.getAsRegion(); + if (!ContReg) + return; + + while (const auto *CBOR = ContReg->getAs<CXXBaseObjectRegion>()) { + ContReg = CBOR->getSuperRegion(); + } + + // If the container already has an end symbol then use it. Otherwise first + // create a new one. + auto State = C.getState(); + auto EndSym = getContainerEnd(State, ContReg); + if (!EndSym) { + auto &SymMgr = C.getSymbolManager(); + EndSym = SymMgr.conjureSymbol(CE, C.getLocationContext(), + C.getASTContext().LongTy, C.blockCount()); + State = createContainerEnd(State, ContReg, EndSym); + } + State = setIteratorPosition(State, RetVal, + IteratorPosition::getPosition(ContReg, EndSym)); + C.addTransition(State); +} + +void IteratorChecker::assignToContainer(CheckerContext &C, const Expr *CE, + const SVal &RetVal, + const MemRegion *Cont) const { + while (const auto *CBOR = Cont->getAs<CXXBaseObjectRegion>()) { + Cont = CBOR->getSuperRegion(); + } + + auto State = C.getState(); + auto &SymMgr = C.getSymbolManager(); + auto Sym = SymMgr.conjureSymbol(CE, C.getLocationContext(), + C.getASTContext().LongTy, C.blockCount()); + State = setIteratorPosition(State, RetVal, + IteratorPosition::getPosition(Cont, Sym)); + C.addTransition(State); +} + +void IteratorChecker::reportOutOfRangeBug(const StringRef &Message, + const SVal &Val, CheckerContext &C, + ExplodedNode *ErrNode) const { + auto R = llvm::make_unique<BugReport>(*OutOfRangeBugType, Message, ErrNode); + R->markInteresting(Val); + C.emitReport(std::move(R)); +} + +namespace { + +bool isGreaterOrEqual(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2); +bool compare(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2, + BinaryOperator::Opcode Opc); + +bool isIteratorType(const QualType &Type) { + if (Type->isPointerType()) + return true; + + const auto *CRD = Type->getUnqualifiedDesugaredType()->getAsCXXRecordDecl(); + return isIterator(CRD); +} + +bool isIterator(const CXXRecordDecl *CRD) { + if (!CRD) + return false; + + const auto Name = CRD->getName(); + if (!(Name.endswith_lower("iterator") || Name.endswith_lower("iter") || + Name.endswith_lower("it"))) + return false; + + bool HasCopyCtor = false, HasCopyAssign = true, HasDtor = false, + HasPreIncrOp = false, HasPostIncrOp = false, HasDerefOp = false; + for (const auto *Method : CRD->methods()) { + if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(Method)) { + if (Ctor->isCopyConstructor()) { + HasCopyCtor = !Ctor->isDeleted() && Ctor->getAccess() == AS_public; + } + continue; + } + if (const auto *Dtor = dyn_cast<CXXDestructorDecl>(Method)) { + HasDtor = !Dtor->isDeleted() && Dtor->getAccess() == AS_public; + continue; + } + if (Method->isCopyAssignmentOperator()) { + HasCopyAssign = !Method->isDeleted() && Method->getAccess() == AS_public; + continue; + } + if (!Method->isOverloadedOperator()) + continue; + const auto OPK = Method->getOverloadedOperator(); + if (OPK == OO_PlusPlus) { + HasPreIncrOp = HasPreIncrOp || (Method->getNumParams() == 0); + HasPostIncrOp = HasPostIncrOp || (Method->getNumParams() == 1); + continue; + } + if (OPK == OO_Star) { + HasDerefOp = (Method->getNumParams() == 0); + continue; + } + } + + return HasCopyCtor && HasCopyAssign && HasDtor && HasPreIncrOp && + HasPostIncrOp && HasDerefOp; +} + +bool isEndCall(const FunctionDecl *Func) { + const auto *IdInfo = Func->getIdentifier(); + if (!IdInfo) + return false; + return IdInfo->getName().endswith_lower("end"); +} + +bool isSimpleComparisonOperator(OverloadedOperatorKind OK) { + return OK == OO_EqualEqual || OK == OO_ExclaimEqual; +} + +bool isDereferenceOperator(OverloadedOperatorKind OK) { + return OK == OO_Star || OK == OO_Arrow || OK == OO_ArrowStar || + OK == OO_Subscript; +} + +BinaryOperator::Opcode getOpcode(const SymExpr *SE) { + if (const auto *BSE = dyn_cast<BinarySymExpr>(SE)) { + return BSE->getOpcode(); + } else if (const auto *SC = dyn_cast<SymbolConjured>(SE)) { + const auto *COE = dyn_cast<CXXOperatorCallExpr>(SC->getStmt()); + if (!COE) + return BO_Comma; // Extremal value, neither EQ nor NE + if (COE->getOperator() == OO_EqualEqual) { + return BO_EQ; + } else if (COE->getOperator() == OO_ExclaimEqual) { + return BO_NE; + } + return BO_Comma; // Extremal value, neither EQ nor NE + } + return BO_Comma; // Extremal value, neither EQ nor NE +} + +const RegionOrSymbol getRegionOrSymbol(const SVal &Val) { + if (const auto Reg = Val.getAsRegion()) { + return Reg; + } else if (const auto Sym = Val.getAsSymbol()) { + return Sym; + } else if (const auto LCVal = Val.getAs<nonloc::LazyCompoundVal>()) { + return LCVal->getRegion(); + } + return RegionOrSymbol(); +} + +const ProgramStateRef processComparison(ProgramStateRef State, + RegionOrSymbol LVal, + RegionOrSymbol RVal, bool Equal) { + const auto *LPos = getIteratorPosition(State, LVal); + const auto *RPos = getIteratorPosition(State, RVal); + if (LPos && !RPos) { + State = adjustIteratorPosition(State, RVal, *LPos, Equal); + } else if (!LPos && RPos) { + State = adjustIteratorPosition(State, LVal, *RPos, Equal); + } else if (LPos && RPos) { + State = relateIteratorPositions(State, *LPos, *RPos, Equal); + } + return State; +} + +const ProgramStateRef saveComparison(ProgramStateRef State, + const SymExpr *Condition, const SVal &LVal, + const SVal &RVal, bool Eq) { + const auto Left = getRegionOrSymbol(LVal); + const auto Right = getRegionOrSymbol(RVal); + if (!Left || !Right) + return State; + return State->set<IteratorComparisonMap>(Condition, + IteratorComparison(Left, Right, Eq)); +} + +const IteratorComparison *loadComparison(ProgramStateRef State, + const SymExpr *Condition) { + return State->get<IteratorComparisonMap>(Condition); +} + +SymbolRef getContainerEnd(ProgramStateRef State, const MemRegion *Cont) { + const auto *CDataPtr = getContainerData(State, Cont); + if (!CDataPtr) + return nullptr; + + return CDataPtr->getEnd(); +} + +ProgramStateRef createContainerEnd(ProgramStateRef State, const MemRegion *Cont, + const SymbolRef Sym) { + // Only create if it does not exist + const auto *CDataPtr = getContainerData(State, Cont); + if (CDataPtr) { + if (CDataPtr->getEnd()) { + return State; + } else { + const auto CData = CDataPtr->newEnd(Sym); + return setContainerData(State, Cont, CData); + } + } else { + const auto CData = ContainerData::fromEnd(Sym); + return setContainerData(State, Cont, CData); + } +} + +const ContainerData *getContainerData(ProgramStateRef State, + const MemRegion *Cont) { + return State->get<ContainerMap>(Cont); +} + +ProgramStateRef setContainerData(ProgramStateRef State, const MemRegion *Cont, + const ContainerData &CData) { + return State->set<ContainerMap>(Cont, CData); +} + +const IteratorPosition *getIteratorPosition(ProgramStateRef State, + const SVal &Val) { + if (const auto Reg = Val.getAsRegion()) { + return State->get<IteratorRegionMap>(Reg); + } else if (const auto Sym = Val.getAsSymbol()) { + return State->get<IteratorSymbolMap>(Sym); + } else if (const auto LCVal = Val.getAs<nonloc::LazyCompoundVal>()) { + return State->get<IteratorRegionMap>(LCVal->getRegion()); + } + return nullptr; +} + +const IteratorPosition *getIteratorPosition(ProgramStateRef State, + RegionOrSymbol RegOrSym) { + if (RegOrSym.is<const MemRegion *>()) { + return State->get<IteratorRegionMap>(RegOrSym.get<const MemRegion *>()); + } else if (RegOrSym.is<SymbolRef>()) { + return State->get<IteratorSymbolMap>(RegOrSym.get<SymbolRef>()); + } + return nullptr; +} + +ProgramStateRef setIteratorPosition(ProgramStateRef State, const SVal &Val, + const IteratorPosition &Pos) { + if (const auto Reg = Val.getAsRegion()) { + return State->set<IteratorRegionMap>(Reg, Pos); + } else if (const auto Sym = Val.getAsSymbol()) { + return State->set<IteratorSymbolMap>(Sym, Pos); + } else if (const auto LCVal = Val.getAs<nonloc::LazyCompoundVal>()) { + return State->set<IteratorRegionMap>(LCVal->getRegion(), Pos); + } + return nullptr; +} + +ProgramStateRef setIteratorPosition(ProgramStateRef State, + RegionOrSymbol RegOrSym, + const IteratorPosition &Pos) { + if (RegOrSym.is<const MemRegion *>()) { + return State->set<IteratorRegionMap>(RegOrSym.get<const MemRegion *>(), + Pos); + } else if (RegOrSym.is<SymbolRef>()) { + return State->set<IteratorSymbolMap>(RegOrSym.get<SymbolRef>(), Pos); + } + return nullptr; +} + +ProgramStateRef removeIteratorPosition(ProgramStateRef State, const SVal &Val) { + if (const auto Reg = Val.getAsRegion()) { + return State->remove<IteratorRegionMap>(Reg); + } else if (const auto Sym = Val.getAsSymbol()) { + return State->remove<IteratorSymbolMap>(Sym); + } else if (const auto LCVal = Val.getAs<nonloc::LazyCompoundVal>()) { + return State->remove<IteratorRegionMap>(LCVal->getRegion()); + } + return nullptr; +} + +ProgramStateRef adjustIteratorPosition(ProgramStateRef State, + RegionOrSymbol RegOrSym, + const IteratorPosition &Pos, + bool Equal) { + if (Equal) { + return setIteratorPosition(State, RegOrSym, Pos); + } else { + return State; + } +} + +ProgramStateRef relateIteratorPositions(ProgramStateRef State, + const IteratorPosition &Pos1, + const IteratorPosition &Pos2, + bool Equal) { + // Try to compare them and get a defined value + auto &SVB = State->getStateManager().getSValBuilder(); + const auto comparison = + SVB.evalBinOp(State, BO_EQ, nonloc::SymbolVal(Pos1.getOffset()), + nonloc::SymbolVal(Pos2.getOffset()), SVB.getConditionType()) + .getAs<DefinedSVal>(); + if (comparison) { + return State->assume(*comparison, Equal); + } + + return State; +} + +bool isOutOfRange(ProgramStateRef State, const IteratorPosition &Pos) { + const auto *Cont = Pos.getContainer(); + const auto *CData = getContainerData(State, Cont); + if (!CData) + return false; + + // Out of range means less than the begin symbol or greater or equal to the + // end symbol. + + const auto End = CData->getEnd(); + if (End) { + if (isGreaterOrEqual(State, Pos.getOffset(), End)) { + return true; + } + } + + return false; +} + +bool isGreaterOrEqual(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2) { + return compare(State, Sym1, Sym2, BO_GE); +} + +bool compare(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2, + BinaryOperator::Opcode Opc) { + auto &SMgr = State->getStateManager(); + auto &SVB = SMgr.getSValBuilder(); + + const auto comparison = + SVB.evalBinOp(State, Opc, nonloc::SymbolVal(Sym1), + nonloc::SymbolVal(Sym2), SVB.getConditionType()) + .getAs<DefinedSVal>(); + + if(comparison) { + return !!State->assume(*comparison, true); + } + + return false; +} + +} // namespace + +#define REGISTER_CHECKER(name) \ + void ento::register##name(CheckerManager &Mgr) { \ + auto *checker = Mgr.registerChecker<IteratorChecker>(); \ + checker->ChecksEnabled[IteratorChecker::CK_##name] = true; \ + checker->CheckNames[IteratorChecker::CK_##name] = \ + Mgr.getCurrentCheckName(); \ + } + +REGISTER_CHECKER(IteratorRangeChecker) diff --git a/lib/StaticAnalyzer/Checkers/IteratorPastEndChecker.cpp b/lib/StaticAnalyzer/Checkers/IteratorPastEndChecker.cpp deleted file mode 100644 index f0f7c98c9640..000000000000 --- a/lib/StaticAnalyzer/Checkers/IteratorPastEndChecker.cpp +++ /dev/null @@ -1,840 +0,0 @@ -//===-- IteratorPastEndChecker.cpp --------------------------------*- C++ -*--// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// -// -// Defines a checker for using iterators outside their range (past end). Usage -// means here dereferencing, incrementing etc. -// -//===----------------------------------------------------------------------===// - -#include "ClangSACheckers.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 <utility> - -using namespace clang; -using namespace ento; - -namespace { -struct IteratorPosition { -private: - enum Kind { InRange, OutofRange } K; - IteratorPosition(Kind InK) : K(InK) {} - -public: - bool isInRange() const { return K == InRange; } - bool isOutofRange() const { return K == OutofRange; } - - static IteratorPosition getInRange() { return IteratorPosition(InRange); } - static IteratorPosition getOutofRange() { - return IteratorPosition(OutofRange); - } - - bool operator==(const IteratorPosition &X) const { return K == X.K; } - bool operator!=(const IteratorPosition &X) const { return K != X.K; } - void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); } -}; - -typedef llvm::PointerUnion<const MemRegion *, SymbolRef> RegionOrSymbol; - -struct IteratorComparison { -private: - RegionOrSymbol Left, Right; - bool Equality; - -public: - IteratorComparison(RegionOrSymbol L, RegionOrSymbol R, bool Eq) - : Left(L), Right(R), Equality(Eq) {} - - RegionOrSymbol getLeft() const { return Left; } - RegionOrSymbol getRight() const { return Right; } - bool isEquality() const { return Equality; } - bool operator==(const IteratorComparison &X) const { - return Left == X.Left && Right == X.Right && Equality == X.Equality; - } - bool operator!=(const IteratorComparison &X) const { - return Left != X.Left || Right != X.Right || Equality != X.Equality; - } - void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(Equality); } -}; - -class IteratorPastEndChecker - : public Checker< - check::PreCall, check::PostCall, check::PreStmt<CXXOperatorCallExpr>, - check::PostStmt<CXXConstructExpr>, check::PostStmt<DeclStmt>, - check::PostStmt<MaterializeTemporaryExpr>, check::BeginFunction, - check::DeadSymbols, eval::Assume, eval::Call> { - mutable IdentifierInfo *II_find = nullptr, - *II_find_end = nullptr, *II_find_first_of = nullptr, - *II_find_if = nullptr, *II_find_if_not = nullptr, - *II_lower_bound = nullptr, *II_upper_bound = nullptr, - *II_search = nullptr, *II_search_n = nullptr; - - std::unique_ptr<BugType> PastEndBugType; - - void handleComparison(CheckerContext &C, const SVal &RetVal, const SVal &LVal, - const SVal &RVal, OverloadedOperatorKind Op) const; - void handleAccess(CheckerContext &C, const SVal &Val) const; - void handleDecrement(CheckerContext &C, const SVal &Val) const; - void handleEnd(CheckerContext &C, const SVal &RetVal) const; - - bool evalFind(CheckerContext &C, const CallExpr *CE) const; - bool evalFindEnd(CheckerContext &C, const CallExpr *CE) const; - bool evalFindFirstOf(CheckerContext &C, const CallExpr *CE) const; - bool evalFindIf(CheckerContext &C, const CallExpr *CE) const; - bool evalFindIfNot(CheckerContext &C, const CallExpr *CE) const; - bool evalLowerBound(CheckerContext &C, const CallExpr *CE) const; - bool evalUpperBound(CheckerContext &C, const CallExpr *CE) const; - bool evalSearch(CheckerContext &C, const CallExpr *CE) const; - bool evalSearchN(CheckerContext &C, const CallExpr *CE) const; - void Find(CheckerContext &C, const CallExpr *CE) const; - - void reportPastEndBug(const StringRef &Message, const SVal &Val, - CheckerContext &C, ExplodedNode *ErrNode) const; - void initIdentifiers(ASTContext &Ctx) const; - -public: - IteratorPastEndChecker(); - - void checkPreCall(const CallEvent &Call, CheckerContext &C) const; - void checkPostCall(const CallEvent &Call, CheckerContext &C) const; - void checkPreStmt(const CXXOperatorCallExpr *COCE, CheckerContext &C) const; - void checkBeginFunction(CheckerContext &C) const; - void checkPostStmt(const CXXConstructExpr *CCE, CheckerContext &C) const; - void checkPostStmt(const DeclStmt *DS, CheckerContext &C) const; - void checkPostStmt(const MaterializeTemporaryExpr *MTE, - CheckerContext &C) const; - void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; - ProgramStateRef evalAssume(ProgramStateRef State, SVal Cond, - bool Assumption) const; - bool evalCall(const CallExpr *CE, CheckerContext &C) const; -}; -} - -REGISTER_MAP_WITH_PROGRAMSTATE(IteratorSymbolMap, SymbolRef, IteratorPosition) -REGISTER_MAP_WITH_PROGRAMSTATE(IteratorRegionMap, const MemRegion *, - IteratorPosition) - -REGISTER_MAP_WITH_PROGRAMSTATE(IteratorComparisonMap, const SymExpr *, - IteratorComparison) - -#define INIT_ID(Id) \ - if (!II_##Id) \ - II_##Id = &Ctx.Idents.get(#Id) - -namespace { - -bool isIteratorType(const QualType &Type); -bool isIterator(const CXXRecordDecl *CRD); -bool isEndCall(const FunctionDecl *Func); -bool isSimpleComparisonOperator(OverloadedOperatorKind OK); -bool isAccessOperator(OverloadedOperatorKind OK); -bool isDecrementOperator(OverloadedOperatorKind OK); -BinaryOperator::Opcode getOpcode(const SymExpr *SE); -const RegionOrSymbol getRegionOrSymbol(const SVal &Val); -const ProgramStateRef processComparison(ProgramStateRef State, - RegionOrSymbol LVal, - RegionOrSymbol RVal, bool Equal); -const ProgramStateRef saveComparison(ProgramStateRef State, - const SymExpr *Condition, const SVal &LVal, - const SVal &RVal, bool Eq); -const IteratorComparison *loadComparison(ProgramStateRef State, - const SymExpr *Condition); -const IteratorPosition *getIteratorPosition(ProgramStateRef State, - const SVal &Val); -const IteratorPosition *getIteratorPosition(ProgramStateRef State, - RegionOrSymbol RegOrSym); -ProgramStateRef setIteratorPosition(ProgramStateRef State, const SVal &Val, - IteratorPosition Pos); -ProgramStateRef setIteratorPosition(ProgramStateRef State, - RegionOrSymbol RegOrSym, - IteratorPosition Pos); -ProgramStateRef adjustIteratorPosition(ProgramStateRef State, - RegionOrSymbol RegOrSym, - IteratorPosition Pos, bool Equal); -bool contradictingIteratorPositions(IteratorPosition Pos1, - IteratorPosition Pos2, bool Equal); -} - -IteratorPastEndChecker::IteratorPastEndChecker() { - PastEndBugType.reset( - new BugType(this, "Iterator Past End", "Misuse of STL APIs")); - PastEndBugType->setSuppressOnSink(true); -} - -void IteratorPastEndChecker::checkPreCall(const CallEvent &Call, - CheckerContext &C) const { - // Check for access past end - const auto *Func = Call.getDecl()->getAsFunction(); - if (!Func) - return; - if (Func->isOverloadedOperator()) { - if (isAccessOperator(Func->getOverloadedOperator())) { - if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { - handleAccess(C, InstCall->getCXXThisVal()); - } else { - handleAccess(C, Call.getArgSVal(0)); - } - } - } -} - -void IteratorPastEndChecker::checkPostCall(const CallEvent &Call, - CheckerContext &C) const { - // Record end() iterators, iterator decrementation and comparison - const auto *Func = Call.getDecl()->getAsFunction(); - if (!Func) - return; - if (Func->isOverloadedOperator()) { - const auto Op = Func->getOverloadedOperator(); - if (isSimpleComparisonOperator(Op)) { - if (Func->isCXXInstanceMember()) { - const auto &InstCall = static_cast<const CXXInstanceCall &>(Call); - handleComparison(C, InstCall.getReturnValue(), InstCall.getCXXThisVal(), - InstCall.getArgSVal(0), Op); - } else { - handleComparison(C, Call.getReturnValue(), Call.getArgSVal(0), - Call.getArgSVal(1), Op); - } - } else if (isDecrementOperator(Func->getOverloadedOperator())) { - if (Func->isCXXInstanceMember()) { - const auto &InstCall = static_cast<const CXXInstanceCall &>(Call); - handleDecrement(C, InstCall.getCXXThisVal()); - } else { - handleDecrement(C, Call.getArgSVal(0)); - } - } - } else if (Func->isCXXInstanceMember()) { - if (!isEndCall(Func)) - return; - if (!isIteratorType(Call.getResultType())) - return; - handleEnd(C, Call.getReturnValue()); - } -} - -void IteratorPastEndChecker::checkPreStmt(const CXXOperatorCallExpr *COCE, - CheckerContext &C) const { - const auto *ThisExpr = COCE->getArg(0); - - auto State = C.getState(); - const auto *LCtx = C.getPredecessor()->getLocationContext(); - - const auto CurrentThis = State->getSVal(ThisExpr, LCtx); - if (const auto *Reg = CurrentThis.getAsRegion()) { - if (!Reg->getAs<CXXTempObjectRegion>()) - return; - const auto OldState = C.getPredecessor()->getFirstPred()->getState(); - const auto OldThis = OldState->getSVal(ThisExpr, LCtx); - const auto *Pos = getIteratorPosition(OldState, OldThis); - if (!Pos) - return; - State = setIteratorPosition(State, CurrentThis, *Pos); - C.addTransition(State); - } -} - -void IteratorPastEndChecker::checkBeginFunction(CheckerContext &C) const { - // Copy state of iterator arguments to iterator parameters - auto State = C.getState(); - const auto *LCtx = C.getLocationContext(); - - const auto *Site = cast<StackFrameContext>(LCtx)->getCallSite(); - if (!Site) - return; - - const auto *FD = dyn_cast<FunctionDecl>(LCtx->getDecl()); - if (!FD) - return; - - const auto *CE = dyn_cast<CallExpr>(Site); - if (!CE) - return; - - bool Change = false; - int idx = 0; - for (const auto P : FD->parameters()) { - auto Param = State->getLValue(P, LCtx); - auto Arg = State->getSVal(CE->getArg(idx++), LCtx->getParent()); - const auto *Pos = getIteratorPosition(State, Arg); - if (!Pos) - continue; - State = setIteratorPosition(State, Param, *Pos); - Change = true; - } - if (Change) { - C.addTransition(State); - } -} - -void IteratorPastEndChecker::checkPostStmt(const CXXConstructExpr *CCE, - CheckerContext &C) const { - // Transfer iterator state in case of copy or move by constructor - const auto *ctr = CCE->getConstructor(); - if (!ctr->isCopyOrMoveConstructor()) - return; - const auto *RHSExpr = CCE->getArg(0); - - auto State = C.getState(); - const auto *LCtx = C.getLocationContext(); - - const auto RetVal = State->getSVal(CCE, LCtx); - - const auto RHSVal = State->getSVal(RHSExpr, LCtx); - const auto *RHSPos = getIteratorPosition(State, RHSVal); - if (!RHSPos) - return; - State = setIteratorPosition(State, RetVal, *RHSPos); - C.addTransition(State); -} - -void IteratorPastEndChecker::checkPostStmt(const DeclStmt *DS, - CheckerContext &C) const { - // Transfer iterator state to new variable declaration - for (const auto *D : DS->decls()) { - const auto *VD = dyn_cast<VarDecl>(D); - if (!VD || !VD->hasInit()) - continue; - - auto State = C.getState(); - const auto *LCtx = C.getPredecessor()->getLocationContext(); - const auto *Pos = - getIteratorPosition(State, State->getSVal(VD->getInit(), LCtx)); - if (!Pos) - continue; - State = setIteratorPosition(State, State->getLValue(VD, LCtx), *Pos); - C.addTransition(State); - } -} - -void IteratorPastEndChecker::checkPostStmt(const MaterializeTemporaryExpr *MTE, - CheckerContext &C) const { - /* Transfer iterator state for to temporary objects */ - auto State = C.getState(); - const auto *LCtx = C.getPredecessor()->getLocationContext(); - const auto *Pos = - getIteratorPosition(State, State->getSVal(MTE->GetTemporaryExpr(), LCtx)); - if (!Pos) - return; - State = setIteratorPosition(State, State->getSVal(MTE, LCtx), *Pos); - C.addTransition(State); -} - -void IteratorPastEndChecker::checkDeadSymbols(SymbolReaper &SR, - CheckerContext &C) const { - auto State = C.getState(); - - auto RegionMap = State->get<IteratorRegionMap>(); - for (const auto Reg : RegionMap) { - if (!SR.isLiveRegion(Reg.first)) { - State = State->remove<IteratorRegionMap>(Reg.first); - } - } - - auto SymbolMap = State->get<IteratorSymbolMap>(); - for (const auto Sym : SymbolMap) { - if (SR.isDead(Sym.first)) { - State = State->remove<IteratorSymbolMap>(Sym.first); - } - } - - auto ComparisonMap = State->get<IteratorComparisonMap>(); - for (const auto Comp : ComparisonMap) { - if (SR.isDead(Comp.first)) { - State = State->remove<IteratorComparisonMap>(Comp.first); - } - } -} - -ProgramStateRef IteratorPastEndChecker::evalAssume(ProgramStateRef State, - SVal Cond, - bool Assumption) const { - // Load recorded comparison and transfer iterator state between sides - // according to comparison operator and assumption - const auto *SE = Cond.getAsSymExpr(); - if (!SE) - return State; - - auto Opc = getOpcode(SE); - if (Opc != BO_EQ && Opc != BO_NE) - return State; - - bool Negated = false; - const auto *Comp = loadComparison(State, SE); - if (!Comp) { - // Try negated comparison, which is a SymExpr to 0 integer comparison - const auto *SIE = dyn_cast<SymIntExpr>(SE); - if (!SIE) - return State; - - if (SIE->getRHS() != 0) - return State; - - SE = SIE->getLHS(); - Negated = SIE->getOpcode() == BO_EQ; // Equal to zero means negation - Opc = getOpcode(SE); - if (Opc != BO_EQ && Opc != BO_NE) - return State; - - Comp = loadComparison(State, SE); - if (!Comp) - return State; - } - - return processComparison(State, Comp->getLeft(), Comp->getRight(), - (Comp->isEquality() == Assumption) != Negated); -} - -// FIXME: Evaluation of these STL calls should be moved to StdCLibraryFunctions -// checker (see patch r284960) or another similar checker for C++ STL -// functions (e.g. StdCXXLibraryFunctions or StdCppLibraryFunctions). -bool IteratorPastEndChecker::evalCall(const CallExpr *CE, - CheckerContext &C) const { - const FunctionDecl *FD = C.getCalleeDecl(CE); - if (!FD) - return false; - - ASTContext &Ctx = C.getASTContext(); - initIdentifiers(Ctx); - - if (FD->getKind() == Decl::Function) { - if (FD->isInStdNamespace()) { - if (FD->getIdentifier() == II_find) { - return evalFind(C, CE); - } else if (FD->getIdentifier() == II_find_end) { - return evalFindEnd(C, CE); - } else if (FD->getIdentifier() == II_find_first_of) { - return evalFindFirstOf(C, CE); - } else if (FD->getIdentifier() == II_find_if) { - return evalFindIf(C, CE); - } else if (FD->getIdentifier() == II_find_if_not) { - return evalFindIfNot(C, CE); - } else if (FD->getIdentifier() == II_upper_bound) { - return evalUpperBound(C, CE); - } else if (FD->getIdentifier() == II_lower_bound) { - return evalLowerBound(C, CE); - } else if (FD->getIdentifier() == II_search) { - return evalSearch(C, CE); - } else if (FD->getIdentifier() == II_search_n) { - return evalSearchN(C, CE); - } - } - } - - return false; -} - -void IteratorPastEndChecker::handleComparison(CheckerContext &C, - const SVal &RetVal, - const SVal &LVal, - const SVal &RVal, - OverloadedOperatorKind Op) const { - // Record the operands and the operator of the comparison for the next - // evalAssume, if the result is a symbolic expression. If it is a concrete - // value (only one branch is possible), then transfer the state between - // the operands according to the operator and the result - auto State = C.getState(); - if (const auto *Condition = RetVal.getAsSymbolicExpression()) { - const auto *LPos = getIteratorPosition(State, LVal); - const auto *RPos = getIteratorPosition(State, RVal); - if (!LPos && !RPos) - return; - State = saveComparison(State, Condition, LVal, RVal, Op == OO_EqualEqual); - C.addTransition(State); - } else if (const auto TruthVal = RetVal.getAs<nonloc::ConcreteInt>()) { - if ((State = processComparison( - State, getRegionOrSymbol(LVal), getRegionOrSymbol(RVal), - (Op == OO_EqualEqual) == (TruthVal->getValue() != 0)))) { - C.addTransition(State); - } else { - C.generateSink(State, C.getPredecessor()); - } - } -} - -void IteratorPastEndChecker::handleAccess(CheckerContext &C, - const SVal &Val) const { - auto State = C.getState(); - const auto *Pos = getIteratorPosition(State, Val); - if (Pos && Pos->isOutofRange()) { - auto *N = C.generateNonFatalErrorNode(State); - if (!N) { - return; - } - reportPastEndBug("Iterator accessed past its end.", Val, C, N); - } -} - -void IteratorPastEndChecker::handleDecrement(CheckerContext &C, - const SVal &Val) const { - auto State = C.getState(); - const auto *Pos = getIteratorPosition(State, Val); - if (Pos && Pos->isOutofRange()) { - State = setIteratorPosition(State, Val, IteratorPosition::getInRange()); - // FIXME: We could also check for iterators ahead of their beginnig in the - // future, but currently we do not care for such errors. We also - // assume that the iterator is not past its end by more then one - // position. - C.addTransition(State); - } -} - -void IteratorPastEndChecker::handleEnd(CheckerContext &C, - const SVal &RetVal) const { - auto State = C.getState(); - State = setIteratorPosition(State, RetVal, IteratorPosition::getOutofRange()); - C.addTransition(State); -} - -bool IteratorPastEndChecker::evalFind(CheckerContext &C, - const CallExpr *CE) const { - if (CE->getNumArgs() == 3 && isIteratorType(CE->getArg(0)->getType()) && - isIteratorType(CE->getArg(1)->getType())) { - Find(C, CE); - return true; - } - return false; -} - -bool IteratorPastEndChecker::evalFindEnd(CheckerContext &C, - const CallExpr *CE) const { - if ((CE->getNumArgs() == 4 || CE->getNumArgs() == 5) && - isIteratorType(CE->getArg(0)->getType()) && - isIteratorType(CE->getArg(1)->getType()) && - isIteratorType(CE->getArg(2)->getType()) && - isIteratorType(CE->getArg(3)->getType())) { - Find(C, CE); - return true; - } - return false; -} - -bool IteratorPastEndChecker::evalFindFirstOf(CheckerContext &C, - const CallExpr *CE) const { - if ((CE->getNumArgs() == 4 || CE->getNumArgs() == 5) && - isIteratorType(CE->getArg(0)->getType()) && - isIteratorType(CE->getArg(1)->getType()) && - isIteratorType(CE->getArg(2)->getType()) && - isIteratorType(CE->getArg(3)->getType())) { - Find(C, CE); - return true; - } - return false; -} - -bool IteratorPastEndChecker::evalFindIf(CheckerContext &C, - const CallExpr *CE) const { - if (CE->getNumArgs() == 3 && isIteratorType(CE->getArg(0)->getType()) && - isIteratorType(CE->getArg(1)->getType())) { - Find(C, CE); - return true; - } - return false; -} - -bool IteratorPastEndChecker::evalFindIfNot(CheckerContext &C, - const CallExpr *CE) const { - if (CE->getNumArgs() == 3 && isIteratorType(CE->getArg(0)->getType()) && - isIteratorType(CE->getArg(1)->getType())) { - Find(C, CE); - return true; - } - return false; -} - -bool IteratorPastEndChecker::evalLowerBound(CheckerContext &C, - const CallExpr *CE) const { - if ((CE->getNumArgs() == 3 || CE->getNumArgs() == 4) && - isIteratorType(CE->getArg(0)->getType()) && - isIteratorType(CE->getArg(1)->getType())) { - Find(C, CE); - return true; - } - return false; -} - -bool IteratorPastEndChecker::evalUpperBound(CheckerContext &C, - const CallExpr *CE) const { - if ((CE->getNumArgs() == 3 || CE->getNumArgs() == 4) && - isIteratorType(CE->getArg(0)->getType()) && - isIteratorType(CE->getArg(1)->getType())) { - Find(C, CE); - return true; - } - return false; -} - -bool IteratorPastEndChecker::evalSearch(CheckerContext &C, - const CallExpr *CE) const { - if ((CE->getNumArgs() == 4 || CE->getNumArgs() == 5) && - isIteratorType(CE->getArg(0)->getType()) && - isIteratorType(CE->getArg(1)->getType()) && - isIteratorType(CE->getArg(2)->getType()) && - isIteratorType(CE->getArg(3)->getType())) { - Find(C, CE); - return true; - } - return false; -} - -bool IteratorPastEndChecker::evalSearchN(CheckerContext &C, - const CallExpr *CE) const { - if ((CE->getNumArgs() == 4 || CE->getNumArgs() == 5) && - isIteratorType(CE->getArg(0)->getType()) && - isIteratorType(CE->getArg(1)->getType())) { - Find(C, CE); - return true; - } - return false; -} - -void IteratorPastEndChecker::Find(CheckerContext &C, const CallExpr *CE) const { - auto state = C.getState(); - auto &svalBuilder = C.getSValBuilder(); - const auto *LCtx = C.getLocationContext(); - - auto RetVal = svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()); - auto SecondParam = state->getSVal(CE->getArg(1), LCtx); - - auto stateFound = state->BindExpr(CE, LCtx, RetVal); - auto stateNotFound = state->BindExpr(CE, LCtx, SecondParam); - - C.addTransition(stateFound); - C.addTransition(stateNotFound); -} - -void IteratorPastEndChecker::reportPastEndBug(const StringRef &Message, - const SVal &Val, - CheckerContext &C, - ExplodedNode *ErrNode) const { - auto R = llvm::make_unique<BugReport>(*PastEndBugType, Message, ErrNode); - R->markInteresting(Val); - C.emitReport(std::move(R)); -} - -void IteratorPastEndChecker::initIdentifiers(ASTContext &Ctx) const { - INIT_ID(find); - INIT_ID(find_end); - INIT_ID(find_first_of); - INIT_ID(find_if); - INIT_ID(find_if_not); - INIT_ID(lower_bound); - INIT_ID(upper_bound); - INIT_ID(search); - INIT_ID(search_n); -} - -namespace { - -bool isIteratorType(const QualType &Type) { - if (Type->isPointerType()) - return true; - - const auto *CRD = Type->getUnqualifiedDesugaredType()->getAsCXXRecordDecl(); - return isIterator(CRD); -} - -bool isIterator(const CXXRecordDecl *CRD) { - if (!CRD) - return false; - - const auto Name = CRD->getName(); - if (!(Name.endswith_lower("iterator") || Name.endswith_lower("iter") || - Name.endswith_lower("it"))) - return false; - - bool HasCopyCtor = false, HasCopyAssign = true, HasDtor = false, - HasPreIncrOp = false, HasPostIncrOp = false, HasDerefOp = false; - for (const auto *Method : CRD->methods()) { - if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(Method)) { - if (Ctor->isCopyConstructor()) { - HasCopyCtor = !Ctor->isDeleted() && Ctor->getAccess() == AS_public; - } - continue; - } - if (const auto *Dtor = dyn_cast<CXXDestructorDecl>(Method)) { - HasDtor = !Dtor->isDeleted() && Dtor->getAccess() == AS_public; - continue; - } - if (Method->isCopyAssignmentOperator()) { - HasCopyAssign = !Method->isDeleted() && Method->getAccess() == AS_public; - continue; - } - if (!Method->isOverloadedOperator()) - continue; - const auto OPK = Method->getOverloadedOperator(); - if (OPK == OO_PlusPlus) { - HasPreIncrOp = HasPreIncrOp || (Method->getNumParams() == 0); - HasPostIncrOp = HasPostIncrOp || (Method->getNumParams() == 1); - continue; - } - if (OPK == OO_Star) { - HasDerefOp = (Method->getNumParams() == 0); - continue; - } - } - - return HasCopyCtor && HasCopyAssign && HasDtor && HasPreIncrOp && - HasPostIncrOp && HasDerefOp; -} - -bool isEndCall(const FunctionDecl *Func) { - const auto *IdInfo = Func->getIdentifier(); - if (!IdInfo) - return false; - return IdInfo->getName().endswith_lower("end"); -} - -bool isSimpleComparisonOperator(OverloadedOperatorKind OK) { - return OK == OO_EqualEqual || OK == OO_ExclaimEqual; -} - -bool isAccessOperator(OverloadedOperatorKind OK) { - return OK == OO_Star || OK == OO_Arrow || OK == OO_ArrowStar || - OK == OO_Plus || OK == OO_PlusEqual || OK == OO_PlusPlus || - OK == OO_Subscript; -} - -bool isDecrementOperator(OverloadedOperatorKind OK) { - return OK == OO_MinusEqual || OK == OO_MinusMinus; -} - -BinaryOperator::Opcode getOpcode(const SymExpr *SE) { - if (const auto *BSE = dyn_cast<BinarySymExpr>(SE)) { - return BSE->getOpcode(); - } else if (const auto *SC = dyn_cast<SymbolConjured>(SE)) { - const auto *COE = dyn_cast<CXXOperatorCallExpr>(SC->getStmt()); - if (!COE) - return BO_Comma; // Extremal value, neither EQ nor NE - if (COE->getOperator() == OO_EqualEqual) { - return BO_EQ; - } else if (COE->getOperator() == OO_ExclaimEqual) { - return BO_NE; - } - return BO_Comma; // Extremal value, neither EQ nor NE - } - return BO_Comma; // Extremal value, neither EQ nor NE -} - -const RegionOrSymbol getRegionOrSymbol(const SVal &Val) { - if (const auto Reg = Val.getAsRegion()) { - return Reg; - } else if (const auto Sym = Val.getAsSymbol()) { - return Sym; - } else if (const auto LCVal = Val.getAs<nonloc::LazyCompoundVal>()) { - return LCVal->getRegion(); - } - return RegionOrSymbol(); -} - -const ProgramStateRef processComparison(ProgramStateRef State, - RegionOrSymbol LVal, - RegionOrSymbol RVal, bool Equal) { - const auto *LPos = getIteratorPosition(State, LVal); - const auto *RPos = getIteratorPosition(State, RVal); - if (LPos && !RPos) { - State = adjustIteratorPosition(State, RVal, *LPos, Equal); - } else if (!LPos && RPos) { - State = adjustIteratorPosition(State, LVal, *RPos, Equal); - } else if (LPos && RPos) { - if (contradictingIteratorPositions(*LPos, *RPos, Equal)) { - return nullptr; - } - } - return State; -} - -const ProgramStateRef saveComparison(ProgramStateRef State, - const SymExpr *Condition, const SVal &LVal, - const SVal &RVal, bool Eq) { - const auto Left = getRegionOrSymbol(LVal); - const auto Right = getRegionOrSymbol(RVal); - if (!Left || !Right) - return State; - return State->set<IteratorComparisonMap>(Condition, - IteratorComparison(Left, Right, Eq)); -} - -const IteratorComparison *loadComparison(ProgramStateRef State, - const SymExpr *Condition) { - return State->get<IteratorComparisonMap>(Condition); -} - -const IteratorPosition *getIteratorPosition(ProgramStateRef State, - const SVal &Val) { - if (const auto Reg = Val.getAsRegion()) { - return State->get<IteratorRegionMap>(Reg); - } else if (const auto Sym = Val.getAsSymbol()) { - return State->get<IteratorSymbolMap>(Sym); - } else if (const auto LCVal = Val.getAs<nonloc::LazyCompoundVal>()) { - return State->get<IteratorRegionMap>(LCVal->getRegion()); - } - return nullptr; -} - -const IteratorPosition *getIteratorPosition(ProgramStateRef State, - RegionOrSymbol RegOrSym) { - if (RegOrSym.is<const MemRegion *>()) { - return State->get<IteratorRegionMap>(RegOrSym.get<const MemRegion *>()); - } else if (RegOrSym.is<SymbolRef>()) { - return State->get<IteratorSymbolMap>(RegOrSym.get<SymbolRef>()); - } - return nullptr; -} - -ProgramStateRef setIteratorPosition(ProgramStateRef State, const SVal &Val, - IteratorPosition Pos) { - if (const auto Reg = Val.getAsRegion()) { - return State->set<IteratorRegionMap>(Reg, Pos); - } else if (const auto Sym = Val.getAsSymbol()) { - return State->set<IteratorSymbolMap>(Sym, Pos); - } else if (const auto LCVal = Val.getAs<nonloc::LazyCompoundVal>()) { - return State->set<IteratorRegionMap>(LCVal->getRegion(), Pos); - } - return nullptr; -} - -ProgramStateRef setIteratorPosition(ProgramStateRef State, - RegionOrSymbol RegOrSym, - IteratorPosition Pos) { - if (RegOrSym.is<const MemRegion *>()) { - return State->set<IteratorRegionMap>(RegOrSym.get<const MemRegion *>(), - Pos); - } else if (RegOrSym.is<SymbolRef>()) { - return State->set<IteratorSymbolMap>(RegOrSym.get<SymbolRef>(), Pos); - } - return nullptr; -} - -ProgramStateRef adjustIteratorPosition(ProgramStateRef State, - RegionOrSymbol RegOrSym, - IteratorPosition Pos, bool Equal) { - - if ((Pos.isInRange() && Equal) || (Pos.isOutofRange() && !Equal)) { - return setIteratorPosition(State, RegOrSym, IteratorPosition::getInRange()); - } else if (Pos.isOutofRange() && Equal) { - return setIteratorPosition(State, RegOrSym, - IteratorPosition::getOutofRange()); - } else { - return State; - } -} - -bool contradictingIteratorPositions(IteratorPosition Pos1, - IteratorPosition Pos2, bool Equal) { - return ((Pos1 != Pos2) && Equal) || - ((Pos1.isOutofRange() && Pos2.isOutofRange()) && !Equal); -} -} - -void ento::registerIteratorPastEndChecker(CheckerManager &Mgr) { - Mgr.registerChecker<IteratorPastEndChecker>(); -} diff --git a/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp b/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp index 7ef79c683c49..0e3a649e88f7 100644 --- a/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp @@ -25,7 +25,13 @@ using namespace ento; namespace { struct LockState { - enum Kind { Destroyed, Locked, Unlocked } K; + enum Kind { + Destroyed, + Locked, + Unlocked, + UntouchedAndPossiblyDestroyed, + UnlockedAndPossiblyDestroyed + } K; private: LockState(Kind K) : K(K) {} @@ -34,6 +40,12 @@ public: static LockState getLocked() { return LockState(Locked); } static LockState getUnlocked() { return LockState(Unlocked); } static LockState getDestroyed() { return LockState(Destroyed); } + static LockState getUntouchedAndPossiblyDestroyed() { + return LockState(UntouchedAndPossiblyDestroyed); + } + static LockState getUnlockedAndPossiblyDestroyed() { + return LockState(UnlockedAndPossiblyDestroyed); + } bool operator==(const LockState &X) const { return K == X.K; @@ -42,13 +54,20 @@ public: bool isLocked() const { return K == Locked; } bool isUnlocked() const { return K == Unlocked; } bool isDestroyed() const { return K == Destroyed; } + bool isUntouchedAndPossiblyDestroyed() const { + return K == UntouchedAndPossiblyDestroyed; + } + bool isUnlockedAndPossiblyDestroyed() const { + return K == UnlockedAndPossiblyDestroyed; + } void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); } }; -class PthreadLockChecker : public Checker< check::PostStmt<CallExpr> > { +class PthreadLockChecker + : public Checker<check::PostStmt<CallExpr>, check::DeadSymbols> { mutable std::unique_ptr<BugType> BT_doublelock; mutable std::unique_ptr<BugType> BT_doubleunlock; mutable std::unique_ptr<BugType> BT_destroylock; @@ -61,22 +80,31 @@ class PthreadLockChecker : public Checker< check::PostStmt<CallExpr> > { }; public: void checkPostStmt(const CallExpr *CE, CheckerContext &C) const; + void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; void AcquireLock(CheckerContext &C, const CallExpr *CE, SVal lock, bool isTryLock, enum LockingSemantics semantics) const; void ReleaseLock(CheckerContext &C, const CallExpr *CE, SVal lock) const; - void DestroyLock(CheckerContext &C, const CallExpr *CE, SVal Lock) const; + void DestroyLock(CheckerContext &C, const CallExpr *CE, SVal Lock, + enum LockingSemantics semantics) const; void InitLock(CheckerContext &C, const CallExpr *CE, SVal Lock) const; void reportUseDestroyedBug(CheckerContext &C, const CallExpr *CE) const; + ProgramStateRef resolvePossiblyDestroyedMutex(ProgramStateRef state, + const MemRegion *lockR, + const SymbolRef *sym) const; }; } // end anonymous namespace -// GDM Entry for tracking lock state. +// A stack of locks for tracking lock-unlock order. REGISTER_LIST_WITH_PROGRAMSTATE(LockSet, const MemRegion *) +// An entry for tracking lock states. REGISTER_MAP_WITH_PROGRAMSTATE(LockMap, const MemRegion *, LockState) +// Return values for unresolved calls to pthread_mutex_destroy(). +REGISTER_MAP_WITH_PROGRAMSTATE(DestroyRetVal, const MemRegion *, SymbolRef) + void PthreadLockChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const { ProgramStateRef state = C.getState(); @@ -113,13 +141,49 @@ void PthreadLockChecker::checkPostStmt(const CallExpr *CE, FName == "lck_mtx_unlock" || FName == "lck_rw_done") ReleaseLock(C, CE, state->getSVal(CE->getArg(0), LCtx)); - else if (FName == "pthread_mutex_destroy" || - FName == "lck_mtx_destroy") - DestroyLock(C, CE, state->getSVal(CE->getArg(0), LCtx)); + else if (FName == "pthread_mutex_destroy") + DestroyLock(C, CE, state->getSVal(CE->getArg(0), LCtx), PthreadSemantics); + else if (FName == "lck_mtx_destroy") + DestroyLock(C, CE, state->getSVal(CE->getArg(0), LCtx), XNUSemantics); else if (FName == "pthread_mutex_init") InitLock(C, CE, state->getSVal(CE->getArg(0), LCtx)); } +// When a lock is destroyed, in some semantics(like PthreadSemantics) we are not +// sure if the destroy call has succeeded or failed, and the lock enters one of +// the 'possibly destroyed' state. There is a short time frame for the +// programmer to check the return value to see if the lock was successfully +// destroyed. Before we model the next operation over that lock, we call this +// function to see if the return value was checked by now and set the lock state +// - either to destroyed state or back to its previous state. + +// In PthreadSemantics, pthread_mutex_destroy() returns zero if the lock is +// successfully destroyed and it returns a non-zero value otherwise. +ProgramStateRef PthreadLockChecker::resolvePossiblyDestroyedMutex( + ProgramStateRef state, const MemRegion *lockR, const SymbolRef *sym) const { + const LockState *lstate = state->get<LockMap>(lockR); + // Existence in DestroyRetVal ensures existence in LockMap. + // Existence in Destroyed also ensures that the lock state for lockR is either + // UntouchedAndPossiblyDestroyed or UnlockedAndPossiblyDestroyed. + assert(lstate->isUntouchedAndPossiblyDestroyed() || + lstate->isUnlockedAndPossiblyDestroyed()); + + ConstraintManager &CMgr = state->getConstraintManager(); + ConditionTruthVal retZero = CMgr.isNull(state, *sym); + if (retZero.isConstrainedFalse()) { + if (lstate->isUntouchedAndPossiblyDestroyed()) + state = state->remove<LockMap>(lockR); + else if (lstate->isUnlockedAndPossiblyDestroyed()) + state = state->set<LockMap>(lockR, LockState::getUnlocked()); + } else + state = state->set<LockMap>(lockR, LockState::getDestroyed()); + + // Removing the map entry (lockR, sym) from DestroyRetVal as the lock state is + // now resolved. + state = state->remove<DestroyRetVal>(lockR); + return state; +} + void PthreadLockChecker::AcquireLock(CheckerContext &C, const CallExpr *CE, SVal lock, bool isTryLock, enum LockingSemantics semantics) const { @@ -129,6 +193,9 @@ void PthreadLockChecker::AcquireLock(CheckerContext &C, const CallExpr *CE, return; ProgramStateRef state = C.getState(); + const SymbolRef *sym = state->get<DestroyRetVal>(lockR); + if (sym) + state = resolvePossiblyDestroyedMutex(state, lockR, sym); SVal X = state->getSVal(CE, C.getLocationContext()); if (X.isUnknownOrUndef()) @@ -197,6 +264,9 @@ void PthreadLockChecker::ReleaseLock(CheckerContext &C, const CallExpr *CE, return; ProgramStateRef state = C.getState(); + const SymbolRef *sym = state->get<DestroyRetVal>(lockR); + if (sym) + state = resolvePossiblyDestroyedMutex(state, lockR, sym); if (const LockState *LState = state->get<LockMap>(lockR)) { if (LState->isUnlocked()) { @@ -245,7 +315,8 @@ void PthreadLockChecker::ReleaseLock(CheckerContext &C, const CallExpr *CE, } void PthreadLockChecker::DestroyLock(CheckerContext &C, const CallExpr *CE, - SVal Lock) const { + SVal Lock, + enum LockingSemantics semantics) const { const MemRegion *LockR = Lock.getAsRegion(); if (!LockR) @@ -253,13 +324,38 @@ void PthreadLockChecker::DestroyLock(CheckerContext &C, const CallExpr *CE, ProgramStateRef State = C.getState(); + const SymbolRef *sym = State->get<DestroyRetVal>(LockR); + if (sym) + State = resolvePossiblyDestroyedMutex(State, LockR, sym); + const LockState *LState = State->get<LockMap>(LockR); - if (!LState || LState->isUnlocked()) { - State = State->set<LockMap>(LockR, LockState::getDestroyed()); - C.addTransition(State); - return; + // Checking the return value of the destroy method only in the case of + // PthreadSemantics + if (semantics == PthreadSemantics) { + if (!LState || LState->isUnlocked()) { + SymbolRef sym = C.getSVal(CE).getAsSymbol(); + if (!sym) { + State = State->remove<LockMap>(LockR); + C.addTransition(State); + return; + } + State = State->set<DestroyRetVal>(LockR, sym); + if (LState && LState->isUnlocked()) + State = State->set<LockMap>( + LockR, LockState::getUnlockedAndPossiblyDestroyed()); + else + State = State->set<LockMap>( + LockR, LockState::getUntouchedAndPossiblyDestroyed()); + C.addTransition(State); + return; + } + } else { + if (!LState || LState->isUnlocked()) { + State = State->set<LockMap>(LockR, LockState::getDestroyed()); + C.addTransition(State); + return; + } } - StringRef Message; if (LState->isLocked()) { @@ -288,6 +384,10 @@ void PthreadLockChecker::InitLock(CheckerContext &C, const CallExpr *CE, ProgramStateRef State = C.getState(); + const SymbolRef *sym = State->get<DestroyRetVal>(LockR); + if (sym) + State = resolvePossiblyDestroyedMutex(State, LockR, sym); + const struct LockState *LState = State->get<LockMap>(LockR); if (!LState || LState->isDestroyed()) { State = State->set<LockMap>(LockR, LockState::getUnlocked()); @@ -328,6 +428,26 @@ void PthreadLockChecker::reportUseDestroyedBug(CheckerContext &C, C.emitReport(std::move(Report)); } +void PthreadLockChecker::checkDeadSymbols(SymbolReaper &SymReaper, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + + // TODO: Clean LockMap when a mutex region dies. + + DestroyRetValTy TrackedSymbols = State->get<DestroyRetVal>(); + for (DestroyRetValTy::iterator I = TrackedSymbols.begin(), + E = TrackedSymbols.end(); + I != E; ++I) { + const SymbolRef Sym = I->second; + const MemRegion *lockR = I->first; + bool IsSymDead = SymReaper.isDead(Sym); + // Remove the dead symbol from the return value symbols map. + if (IsSymDead) + State = resolvePossiblyDestroyedMutex(State, lockR, &Sym); + } + C.addTransition(State); +} + void ento::registerPthreadLockChecker(CheckerManager &mgr) { mgr.registerChecker<PthreadLockChecker>(); } diff --git a/lib/StaticAnalyzer/Core/ProgramState.cpp b/lib/StaticAnalyzer/Core/ProgramState.cpp index 31556c792fc5..3215c3ccd21e 100644 --- a/lib/StaticAnalyzer/Core/ProgramState.cpp +++ b/lib/StaticAnalyzer/Core/ProgramState.cpp @@ -644,15 +644,33 @@ ProgramStateRef ProgramState::addTaint(const Stmt *S, if (const Expr *E = dyn_cast_or_null<Expr>(S)) S = E->IgnoreParens(); - SymbolRef Sym = getSVal(S, LCtx).getAsSymbol(); + return addTaint(getSVal(S, LCtx), Kind); +} + +ProgramStateRef ProgramState::addTaint(SVal V, + TaintTagType Kind) const { + SymbolRef Sym = V.getAsSymbol(); if (Sym) return addTaint(Sym, Kind); - const MemRegion *R = getSVal(S, LCtx).getAsRegion(); - addTaint(R, Kind); + // If the SVal represents a structure, try to mass-taint all values within the + // structure. For now it only works efficiently on lazy compound values that + // were conjured during a conservative evaluation of a function - either as + // return values of functions that return structures or arrays by value, or as + // values of structures or arrays passed into the function by reference, + // directly or through pointer aliasing. Such lazy compound values are + // characterized by having exactly one binding in their captured store within + // their parent region, which is a conjured symbol default-bound to the base + // region of the parent region. + if (auto LCV = V.getAs<nonloc::LazyCompoundVal>()) { + if (Optional<SVal> binding = getStateManager().StoreMgr->getDefaultBinding(*LCV)) { + if (SymbolRef Sym = binding->getAsSymbol()) + return addPartialTaint(Sym, LCV->getRegion(), Kind); + } + } - // Cannot add taint, so just return the state. - return this; + const MemRegion *R = V.getAsRegion(); + return addTaint(R, Kind); } ProgramStateRef ProgramState::addTaint(const MemRegion *R, @@ -674,6 +692,27 @@ ProgramStateRef ProgramState::addTaint(SymbolRef Sym, return NewState; } +ProgramStateRef ProgramState::addPartialTaint(SymbolRef ParentSym, + const SubRegion *SubRegion, + TaintTagType Kind) const { + // Ignore partial taint if the entire parent symbol is already tainted. + if (contains<TaintMap>(ParentSym) && *get<TaintMap>(ParentSym) == Kind) + return this; + + // Partial taint applies if only a portion of the symbol is tainted. + if (SubRegion == SubRegion->getBaseRegion()) + return addTaint(ParentSym, Kind); + + const TaintedSubRegions *SavedRegs = get<DerivedSymTaint>(ParentSym); + TaintedSubRegions Regs = + SavedRegs ? *SavedRegs : stateMgr->TSRFactory.getEmptyMap(); + + Regs = stateMgr->TSRFactory.add(Regs, SubRegion, Kind); + ProgramStateRef NewState = set<DerivedSymTaint>(ParentSym, Regs); + assert(NewState); + return NewState; +} + bool ProgramState::isTainted(const Stmt *S, const LocationContext *LCtx, TaintTagType Kind) const { if (const Expr *E = dyn_cast_or_null<Expr>(S)) @@ -714,31 +753,52 @@ bool ProgramState::isTainted(SymbolRef Sym, TaintTagType Kind) const { return false; // Traverse all the symbols this symbol depends on to see if any are tainted. - bool Tainted = false; for (SymExpr::symbol_iterator SI = Sym->symbol_begin(), SE =Sym->symbol_end(); SI != SE; ++SI) { if (!isa<SymbolData>(*SI)) continue; - const TaintTagType *Tag = get<TaintMap>(*SI); - Tainted = (Tag && *Tag == Kind); + if (const TaintTagType *Tag = get<TaintMap>(*SI)) { + if (*Tag == Kind) + return true; + } - // If this is a SymbolDerived with a tainted parent, it's also tainted. - if (const SymbolDerived *SD = dyn_cast<SymbolDerived>(*SI)) - Tainted = Tainted || isTainted(SD->getParentSymbol(), Kind); + if (const SymbolDerived *SD = dyn_cast<SymbolDerived>(*SI)) { + // If this is a SymbolDerived with a tainted parent, it's also tainted. + if (isTainted(SD->getParentSymbol(), Kind)) + return true; + + // If this is a SymbolDerived with the same parent symbol as another + // tainted SymbolDerived and a region that's a sub-region of that tainted + // symbol, it's also tainted. + if (const TaintedSubRegions *Regs = + get<DerivedSymTaint>(SD->getParentSymbol())) { + const TypedValueRegion *R = SD->getRegion(); + for (auto I : *Regs) { + // FIXME: The logic to identify tainted regions could be more + // complete. For example, this would not currently identify + // overlapping fields in a union as tainted. To identify this we can + // check for overlapping/nested byte offsets. + if (Kind == I.second && + (R == I.first || R->isSubRegionOf(I.first))) + return true; + } + } + } // If memory region is tainted, data is also tainted. - if (const SymbolRegionValue *SRV = dyn_cast<SymbolRegionValue>(*SI)) - Tainted = Tainted || isTainted(SRV->getRegion(), Kind); - - // If If this is a SymbolCast from a tainted value, it's also tainted. - if (const SymbolCast *SC = dyn_cast<SymbolCast>(*SI)) - Tainted = Tainted || isTainted(SC->getOperand(), Kind); + if (const SymbolRegionValue *SRV = dyn_cast<SymbolRegionValue>(*SI)) { + if (isTainted(SRV->getRegion(), Kind)) + return true; + } - if (Tainted) - return true; + // If this is a SymbolCast from a tainted value, it's also tainted. + if (const SymbolCast *SC = dyn_cast<SymbolCast>(*SI)) { + if (isTainted(SC->getOperand(), Kind)) + return true; + } } - return Tainted; + return false; } diff --git a/lib/StaticAnalyzer/Core/RegionStore.cpp b/lib/StaticAnalyzer/Core/RegionStore.cpp index 3000e13d32c6..28f78fa3ff5e 100644 --- a/lib/StaticAnalyzer/Core/RegionStore.cpp +++ b/lib/StaticAnalyzer/Core/RegionStore.cpp @@ -496,7 +496,10 @@ public: // Part of public interface to class. Optional<SVal> getDefaultBinding(Store S, const MemRegion *R) override { RegionBindingsRef B = getRegionBindings(S); - return B.getDefaultBinding(R); + // Default bindings are always applied over a base region so look up the + // base region's default binding, otherwise the lookup will fail when R + // is at an offset from R->getBaseRegion(). + return B.getDefaultBinding(R->getBaseRegion()); } SVal getBinding(RegionBindingsConstRef B, Loc L, QualType T = QualType()); |