diff options
author | dim <dim@FreeBSD.org> | 2012-04-14 14:01:31 +0000 |
---|---|---|
committer | dim <dim@FreeBSD.org> | 2012-04-14 14:01:31 +0000 |
commit | 50b73317314e889cf39c7b1d6cbf419fa7502f22 (patch) | |
tree | be1815eb79b42ff482a8562b13c2dcbf0c5dcbee /lib/StaticAnalyzer/Checkers | |
parent | dc04cb328508e61aad809d9b53b12f9799a00e7d (diff) | |
download | FreeBSD-src-50b73317314e889cf39c7b1d6cbf419fa7502f22.zip FreeBSD-src-50b73317314e889cf39c7b1d6cbf419fa7502f22.tar.gz |
Vendor import of clang trunk r154661:
http://llvm.org/svn/llvm-project/cfe/trunk@r154661
Diffstat (limited to 'lib/StaticAnalyzer/Checkers')
65 files changed, 5584 insertions, 1723 deletions
diff --git a/lib/StaticAnalyzer/Checkers/AdjustedReturnValueChecker.cpp b/lib/StaticAnalyzer/Checkers/AdjustedReturnValueChecker.cpp index dc524ba..84ea8c7 100644 --- a/lib/StaticAnalyzer/Checkers/AdjustedReturnValueChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/AdjustedReturnValueChecker.cpp @@ -37,20 +37,21 @@ void AdjustedReturnValueChecker::checkPostStmt(const CallExpr *CE, QualType expectedResultTy = CE->getType(); // Fetch the signature of the called function. - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); + const LocationContext *LCtx = C.getLocationContext(); - SVal V = state->getSVal(CE); + SVal V = state->getSVal(CE, LCtx); if (V.isUnknown()) return; // Casting to void? Discard the value. if (expectedResultTy->isVoidType()) { - C.generateNode(state->BindExpr(CE, UnknownVal())); + C.addTransition(state->BindExpr(CE, LCtx, UnknownVal())); return; } - const MemRegion *callee = state->getSVal(CE->getCallee()).getAsRegion(); + const MemRegion *callee = state->getSVal(CE->getCallee(), LCtx).getAsRegion(); if (!callee) return; @@ -82,7 +83,7 @@ void AdjustedReturnValueChecker::checkPostStmt(const CallExpr *CE, // the cast avoids some assertion failures elsewhere. SValBuilder &svalBuilder = C.getSValBuilder(); V = svalBuilder.evalCast(V, expectedResultTy, actualResultTy); - C.generateNode(state->BindExpr(CE, V)); + C.addTransition(state->BindExpr(CE, LCtx, V)); } } diff --git a/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp b/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp index cd977bf..aa6f97b 100644 --- a/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp @@ -8,6 +8,7 @@ //===----------------------------------------------------------------------===// // This file reports various statistics about analyzer visitation. //===----------------------------------------------------------------------===// +#define DEBUG_TYPE "StatsChecker" #include "ClangSACheckers.h" #include "clang/StaticAnalyzer/Core/Checker.h" @@ -16,12 +17,20 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" +#include "clang/AST/DeclObjC.h" #include "clang/Basic/SourceManager.h" #include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/Statistic.h" using namespace clang; using namespace ento; +STATISTIC(NumBlocks, + "The # of blocks in top level functions"); +STATISTIC(NumBlocksUnreachable, + "The # of unreachable blocks in analyzing top level functions"); + namespace { class AnalyzerStatsChecker : public Checker<check::EndAnalysis> { public: @@ -33,18 +42,23 @@ void AnalyzerStatsChecker::checkEndAnalysis(ExplodedGraph &G, BugReporter &B, ExprEngine &Eng) const { const CFG *C = 0; - const Decl *D = 0; - const LocationContext *LC = 0; const SourceManager &SM = B.getSourceManager(); llvm::SmallPtrSet<const CFGBlock*, 256> reachable; - // Iterate over explodedgraph + // Root node should have the location context of the top most function. + const ExplodedNode *GraphRoot = *G.roots_begin(); + const LocationContext *LC = GraphRoot->getLocation().getLocationContext(); + + const Decl *D = LC->getDecl(); + + // Iterate over the exploded graph. for (ExplodedGraph::node_iterator I = G.nodes_begin(); I != G.nodes_end(); ++I) { const ProgramPoint &P = I->getLocation(); - // Save the LocationContext if we don't have it already - if (!LC) - LC = P.getLocationContext(); + + // Only check the coverage in the top level function (optimization). + if (D != P.getLocationContext()->getDecl()) + continue; if (const BlockEntrance *BE = dyn_cast<BlockEntrance>(&P)) { const CFGBlock *CB = BE->getBlock(); @@ -52,9 +66,8 @@ void AnalyzerStatsChecker::checkEndAnalysis(ExplodedGraph &G, } } - // Get the CFG and the Decl of this block + // Get the CFG and the Decl of this block. C = LC->getCFG(); - D = LC->getAnalysisContext()->getDecl(); unsigned total = 0, unreachable = 0; @@ -70,33 +83,39 @@ void AnalyzerStatsChecker::checkEndAnalysis(ExplodedGraph &G, // We never 'reach' the entry block, so correct the unreachable count unreachable--; + // There is no BlockEntrance corresponding to the exit block as well, so + // assume it is reached as well. + unreachable--; // Generate the warning string - llvm::SmallString<128> buf; + SmallString<128> buf; llvm::raw_svector_ostream output(buf); PresumedLoc Loc = SM.getPresumedLoc(D->getLocation()); - if (Loc.isValid()) { - output << Loc.getFilename() << " : "; + if (!Loc.isValid()) + return; - if (isa<FunctionDecl>(D) || isa<ObjCMethodDecl>(D)) { - const NamedDecl *ND = cast<NamedDecl>(D); - output << *ND; - } - else if (isa<BlockDecl>(D)) { - output << "block(line:" << Loc.getLine() << ":col:" << Loc.getColumn(); - } + if (isa<FunctionDecl>(D) || isa<ObjCMethodDecl>(D)) { + const NamedDecl *ND = cast<NamedDecl>(D); + output << *ND; + } + else if (isa<BlockDecl>(D)) { + output << "block(line:" << Loc.getLine() << ":col:" << Loc.getColumn(); } + NumBlocksUnreachable += unreachable; + NumBlocks += total; + std::string NameOfRootFunction = output.str(); + output << " -> Total CFGBlocks: " << total << " | Unreachable CFGBlocks: " << unreachable << " | Exhausted Block: " << (Eng.wasBlocksExhausted() ? "yes" : "no") << " | Empty WorkList: " << (Eng.hasEmptyWorkList() ? "yes" : "no"); - B.EmitBasicReport("Analyzer Statistics", "Internal Statistics", output.str(), - PathDiagnosticLocation(D, SM)); + B.EmitBasicReport(D, "Analyzer Statistics", "Internal Statistics", + output.str(), PathDiagnosticLocation(D, SM)); - // Emit warning for each block we bailed out on + // Emit warning for each block we bailed out on. typedef CoreEngine::BlocksExhausted::const_iterator ExhaustedIterator; const CoreEngine &CE = Eng.getCoreEngine(); for (ExhaustedIterator I = CE.blocks_exhausted_begin(), @@ -104,10 +123,15 @@ void AnalyzerStatsChecker::checkEndAnalysis(ExplodedGraph &G, const BlockEdge &BE = I->first; const CFGBlock *Exit = BE.getDst(); const CFGElement &CE = Exit->front(); - if (const CFGStmt *CS = dyn_cast<CFGStmt>(&CE)) - B.EmitBasicReport("Bailout Point", "Internal Statistics", "The analyzer " - "stopped analyzing at this point", - PathDiagnosticLocation::createBegin(CS->getStmt(), SM, LC)); + if (const CFGStmt *CS = dyn_cast<CFGStmt>(&CE)) { + SmallString<128> bufI; + llvm::raw_svector_ostream outputI(bufI); + outputI << "(" << NameOfRootFunction << ")" << + ": The analyzer generated a sink at this point"; + B.EmitBasicReport(D, "Sink Point", "Internal Statistics", outputI.str(), + PathDiagnosticLocation::createBegin(CS->getStmt(), + SM, LC)); + } } } diff --git a/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp b/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp index 6935c5f..b2ad184 100644 --- a/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp @@ -25,7 +25,7 @@ using namespace ento; namespace { class ArrayBoundChecker : public Checker<check::Location> { - mutable llvm::OwningPtr<BuiltinBug> BT; + mutable OwningPtr<BuiltinBug> BT; public: void checkLocation(SVal l, bool isLoad, const Stmt* S, CheckerContext &C) const; @@ -51,15 +51,15 @@ void ArrayBoundChecker::checkLocation(SVal l, bool isLoad, const Stmt* LoadS, if (Idx.isZeroConstant()) return; - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); // Get the size of the array. DefinedOrUnknownSVal NumElements = C.getStoreManager().getSizeInElements(state, ER->getSuperRegion(), ER->getValueType()); - const ProgramState *StInBound = state->assumeInBound(Idx, NumElements, true); - const ProgramState *StOutBound = state->assumeInBound(Idx, NumElements, false); + ProgramStateRef StInBound = state->assumeInBound(Idx, NumElements, true); + ProgramStateRef StOutBound = state->assumeInBound(Idx, NumElements, false); if (StOutBound && !StInBound) { ExplodedNode *N = C.generateSink(StOutBound); if (!N) @@ -84,7 +84,6 @@ void ArrayBoundChecker::checkLocation(SVal l, bool isLoad, const Stmt* LoadS, // Array bound check succeeded. From this point forward the array bound // should always succeed. - assert(StInBound); C.addTransition(StInBound); } diff --git a/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp b/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp index 6175028..c6efe94 100644 --- a/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp +++ b/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp @@ -19,6 +19,8 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "clang/AST/CharUnits.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/STLExtras.h" using namespace clang; using namespace ento; @@ -26,11 +28,11 @@ using namespace ento; namespace { class ArrayBoundCheckerV2 : public Checker<check::Location> { - mutable llvm::OwningPtr<BuiltinBug> BT; + mutable OwningPtr<BuiltinBug> BT; - enum OOB_Kind { OOB_Precedes, OOB_Excedes }; + enum OOB_Kind { OOB_Precedes, OOB_Excedes, OOB_Tainted }; - void reportOOB(CheckerContext &C, const ProgramState *errorState, + void reportOOB(CheckerContext &C, ProgramStateRef errorState, OOB_Kind kind) const; public: @@ -54,7 +56,7 @@ public: NonLoc getByteOffset() const { return cast<NonLoc>(byteOffset); } const SubRegion *getRegion() const { return baseRegion; } - static RegionRawOffsetV2 computeOffset(const ProgramState *state, + static RegionRawOffsetV2 computeOffset(ProgramStateRef state, SValBuilder &svalBuilder, SVal location); @@ -92,8 +94,8 @@ void ArrayBoundCheckerV2::checkLocation(SVal location, bool isLoad, // memory access is within the extent of the base region. Since we // have some flexibility in defining the base region, we can achieve // various levels of conservatism in our buffer overflow checking. - const ProgramState *state = checkerContext.getState(); - const ProgramState *originalState = state; + ProgramStateRef state = checkerContext.getState(); + ProgramStateRef originalState = state; SValBuilder &svalBuilder = checkerContext.getSValBuilder(); const RegionRawOffsetV2 &rawOffset = @@ -118,7 +120,7 @@ void ArrayBoundCheckerV2::checkLocation(SVal location, bool isLoad, if (!lowerBoundToCheck) return; - const ProgramState *state_precedesLowerBound, *state_withinLowerBound; + ProgramStateRef state_precedesLowerBound, state_withinLowerBound; llvm::tie(state_precedesLowerBound, state_withinLowerBound) = state->assume(*lowerBoundToCheck); @@ -150,12 +152,20 @@ void ArrayBoundCheckerV2::checkLocation(SVal location, bool isLoad, if (!upperboundToCheck) break; - const ProgramState *state_exceedsUpperBound, *state_withinUpperBound; + ProgramStateRef state_exceedsUpperBound, state_withinUpperBound; llvm::tie(state_exceedsUpperBound, state_withinUpperBound) = state->assume(*upperboundToCheck); + + // If we are under constrained and the index variables are tainted, report. + if (state_exceedsUpperBound && state_withinUpperBound) { + if (state->isTainted(rawOffset.getByteOffset())) + reportOOB(checkerContext, state_exceedsUpperBound, OOB_Tainted); + return; + } - // Are we constrained enough to definitely exceed the upper bound? - if (state_exceedsUpperBound && !state_withinUpperBound) { + // If we are constrained enough to definitely exceed the upper bound, report. + if (state_exceedsUpperBound) { + assert(!state_withinUpperBound); reportOOB(checkerContext, state_exceedsUpperBound, OOB_Excedes); return; } @@ -166,11 +176,11 @@ void ArrayBoundCheckerV2::checkLocation(SVal location, bool isLoad, while (false); if (state != originalState) - checkerContext.generateNode(state); + checkerContext.addTransition(state); } void ArrayBoundCheckerV2::reportOOB(CheckerContext &checkerContext, - const ProgramState *errorState, + ProgramStateRef errorState, OOB_Kind kind) const { ExplodedNode *errorNode = checkerContext.generateSink(errorState); @@ -183,11 +193,20 @@ void ArrayBoundCheckerV2::reportOOB(CheckerContext &checkerContext, // FIXME: This diagnostics are preliminary. We should get far better // diagnostics for explaining buffer overruns. - llvm::SmallString<256> buf; + SmallString<256> buf; llvm::raw_svector_ostream os(buf); - os << "Out of bound memory access " - << (kind == OOB_Precedes ? "(accessed memory precedes memory block)" - : "(access exceeds upper limit of memory block)"); + os << "Out of bound memory access "; + switch (kind) { + case OOB_Precedes: + os << "(accessed memory precedes memory block)"; + break; + case OOB_Excedes: + os << "(access exceeds upper limit of memory block)"; + break; + case OOB_Tainted: + os << "(index is tainted)"; + break; + } checkerContext.EmitReport(new BugReport(*BT, os.str(), errorNode)); } @@ -221,7 +240,7 @@ static inline SVal getValue(SVal val, SValBuilder &svalBuilder) { // Scale a base value by a scaling factor, and return the scaled // value as an SVal. Used by 'computeOffset'. -static inline SVal scaleValue(const ProgramState *state, +static inline SVal scaleValue(ProgramStateRef state, NonLoc baseVal, CharUnits scaling, SValBuilder &sb) { return sb.evalBinOpNN(state, BO_Mul, baseVal, @@ -231,7 +250,7 @@ static inline SVal scaleValue(const ProgramState *state, // Add an SVal to another, treating unknown and undefined values as // summing to UnknownVal. Used by 'computeOffset'. -static SVal addValue(const ProgramState *state, SVal x, SVal y, +static SVal addValue(ProgramStateRef state, SVal x, SVal y, SValBuilder &svalBuilder) { // We treat UnknownVals and UndefinedVals the same here because we // only care about computing offsets. @@ -245,7 +264,7 @@ static SVal addValue(const ProgramState *state, SVal x, SVal y, /// Compute a raw byte offset from a base region. Used for array bounds /// checking. -RegionRawOffsetV2 RegionRawOffsetV2::computeOffset(const ProgramState *state, +RegionRawOffsetV2 RegionRawOffsetV2::computeOffset(ProgramStateRef state, SValBuilder &svalBuilder, SVal location) { @@ -277,9 +296,9 @@ RegionRawOffsetV2 RegionRawOffsetV2::computeOffset(const ProgramState *state, offset = addValue(state, getValue(offset, svalBuilder), scaleValue(state, - cast<NonLoc>(index), - astContext.getTypeSizeInChars(elemType), - svalBuilder), + cast<NonLoc>(index), + astContext.getTypeSizeInChars(elemType), + svalBuilder), svalBuilder); if (offset.isUnknownOrUndef()) diff --git a/lib/StaticAnalyzer/Checkers/AttrNonNullChecker.cpp b/lib/StaticAnalyzer/Checkers/AttrNonNullChecker.cpp index 8296eb9..ab66e98 100644 --- a/lib/StaticAnalyzer/Checkers/AttrNonNullChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/AttrNonNullChecker.cpp @@ -24,7 +24,7 @@ using namespace ento; namespace { class AttrNonNullChecker : public Checker< check::PreStmt<CallExpr> > { - mutable llvm::OwningPtr<BugType> BT; + mutable OwningPtr<BugType> BT; public: void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; @@ -33,10 +33,11 @@ public: void AttrNonNullChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const { - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); + const LocationContext *LCtx = C.getLocationContext(); // Check if the callee has a 'nonnull' attribute. - SVal X = state->getSVal(CE->getCallee()); + SVal X = state->getSVal(CE->getCallee(), LCtx); const FunctionDecl *FD = X.getAsFunctionDecl(); if (!FD) @@ -55,7 +56,7 @@ void AttrNonNullChecker::checkPreStmt(const CallExpr *CE, if (!Att->isNonNull(idx)) continue; - SVal V = state->getSVal(*I); + SVal V = state->getSVal(*I, LCtx); DefinedSVal *DV = dyn_cast<DefinedSVal>(&V); // If the value is unknown or undefined, we can't perform this check. @@ -85,7 +86,7 @@ void AttrNonNullChecker::checkPreStmt(const CallExpr *CE, } ConstraintManager &CM = C.getConstraintManager(); - const ProgramState *stateNotNull, *stateNull; + ProgramStateRef stateNotNull, stateNull; llvm::tie(stateNotNull, stateNull) = CM.assumeDual(state, *DV); if (stateNull && !stateNotNull) { @@ -108,7 +109,7 @@ void AttrNonNullChecker::checkPreStmt(const CallExpr *CE, const Expr *arg = *I; R->addRange(arg->getSourceRange()); R->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(errorNode, - arg)); + arg, R)); // Emit the bug report. C.EmitReport(R); } diff --git a/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp b/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp index 08cff0f..6dd0a8c 100644 --- a/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp +++ b/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp @@ -28,6 +28,7 @@ #include "clang/AST/Expr.h" #include "clang/AST/ExprObjC.h" #include "clang/AST/ASTContext.h" +#include "llvm/ADT/SmallString.h" using namespace clang; using namespace ento; @@ -70,7 +71,7 @@ static inline bool isNil(SVal X) { namespace { class NilArgChecker : public Checker<check::PreObjCMessage> { - mutable llvm::OwningPtr<APIMisuse> BT; + mutable OwningPtr<APIMisuse> BT; void WarnNilArg(CheckerContext &C, const ObjCMessage &msg, unsigned Arg) const; @@ -88,7 +89,7 @@ void NilArgChecker::WarnNilArg(CheckerContext &C, BT.reset(new APIMisuse("nil argument")); if (ExplodedNode *N = C.generateSink()) { - llvm::SmallString<128> sbuf; + SmallString<128> sbuf; llvm::raw_svector_ostream os(sbuf); os << "Argument to '" << GetReceiverNameType(msg) << "' method '" << msg.getSelector().getAsString() << "' cannot be nil"; @@ -129,7 +130,7 @@ void NilArgChecker::checkPreObjCMessage(ObjCMessage msg, Name == "compare:options:range:locale:" || Name == "componentsSeparatedByCharactersInSet:" || Name == "initWithFormat:") { - if (isNil(msg.getArgSVal(0, C.getState()))) + if (isNil(msg.getArgSVal(0, C.getLocationContext(), C.getState()))) WarnNilArg(C, msg, 0); } } @@ -141,7 +142,7 @@ void NilArgChecker::checkPreObjCMessage(ObjCMessage msg, namespace { class CFNumberCreateChecker : public Checker< check::PreStmt<CallExpr> > { - mutable llvm::OwningPtr<APIMisuse> BT; + mutable OwningPtr<APIMisuse> BT; mutable IdentifierInfo* II; public: CFNumberCreateChecker() : II(0) {} @@ -249,11 +250,8 @@ static const char* GetCFNumberTypeStr(uint64_t i) { void CFNumberCreateChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const { - const Expr *Callee = CE->getCallee(); - const ProgramState *state = C.getState(); - SVal CallV = state->getSVal(Callee); - const FunctionDecl *FD = CallV.getAsFunctionDecl(); - + ProgramStateRef state = C.getState(); + const FunctionDecl *FD = C.getCalleeDecl(CE); if (!FD) return; @@ -265,7 +263,8 @@ void CFNumberCreateChecker::checkPreStmt(const CallExpr *CE, return; // Get the value of the "theType" argument. - SVal TheTypeVal = state->getSVal(CE->getArg(1)); + const LocationContext *LCtx = C.getLocationContext(); + SVal TheTypeVal = state->getSVal(CE->getArg(1), LCtx); // FIXME: We really should allow ranges of valid theType values, and // bifurcate the state appropriately. @@ -283,7 +282,7 @@ void CFNumberCreateChecker::checkPreStmt(const CallExpr *CE, // Look at the value of the integer being passed by reference. Essentially // we want to catch cases where the value passed in is not equal to the // size of the type being created. - SVal TheValueExpr = state->getSVal(CE->getArg(2)); + SVal TheValueExpr = state->getSVal(CE->getArg(2), LCtx); // FIXME: Eventually we should handle arbitrary locations. We can do this // by having an enhanced memory model that does low-level typing. @@ -316,8 +315,8 @@ void CFNumberCreateChecker::checkPreStmt(const CallExpr *CE, // the bits initialized to the provided values. // if (ExplodedNode *N = SourceSize < TargetSize ? C.generateSink() - : C.generateNode()) { - llvm::SmallString<128> sbuf; + : C.addTransition()) { + SmallString<128> sbuf; llvm::raw_svector_ostream os(sbuf); os << (SourceSize == 8 ? "An " : "A ") @@ -348,7 +347,7 @@ void CFNumberCreateChecker::checkPreStmt(const CallExpr *CE, namespace { class CFRetainReleaseChecker : public Checker< check::PreStmt<CallExpr> > { - mutable llvm::OwningPtr<APIMisuse> BT; + mutable OwningPtr<APIMisuse> BT; mutable IdentifierInfo *Retain, *Release; public: CFRetainReleaseChecker(): Retain(0), Release(0) {} @@ -363,11 +362,8 @@ void CFRetainReleaseChecker::checkPreStmt(const CallExpr *CE, if (CE->getNumArgs() != 1) return; - // Get the function declaration of the callee. - const ProgramState *state = C.getState(); - SVal X = state->getSVal(CE->getCallee()); - const FunctionDecl *FD = X.getAsFunctionDecl(); - + ProgramStateRef state = C.getState(); + const FunctionDecl *FD = C.getCalleeDecl(CE); if (!FD) return; @@ -388,7 +384,7 @@ void CFRetainReleaseChecker::checkPreStmt(const CallExpr *CE, // Get the argument's value. const Expr *Arg = CE->getArg(0); - SVal ArgVal = state->getSVal(Arg); + SVal ArgVal = state->getSVal(Arg, C.getLocationContext()); DefinedSVal *DefArgVal = dyn_cast<DefinedSVal>(&ArgVal); if (!DefArgVal) return; @@ -401,7 +397,7 @@ void CFRetainReleaseChecker::checkPreStmt(const CallExpr *CE, DefinedOrUnknownSVal ArgIsNull = svalBuilder.evalEQ(state, zero, *DefArgVal); // Are they equal? - const ProgramState *stateTrue, *stateFalse; + ProgramStateRef stateTrue, stateFalse; llvm::tie(stateTrue, stateFalse) = state->assume(ArgIsNull); if (stateTrue && !stateFalse) { @@ -415,7 +411,8 @@ void CFRetainReleaseChecker::checkPreStmt(const CallExpr *CE, BugReport *report = new BugReport(*BT, description, N); report->addRange(Arg->getSourceRange()); - report->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, Arg)); + report->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, Arg, + report)); C.EmitReport(report); return; } @@ -434,7 +431,7 @@ class ClassReleaseChecker : public Checker<check::PreObjCMessage> { mutable Selector retainS; mutable Selector autoreleaseS; mutable Selector drainS; - mutable llvm::OwningPtr<BugType> BT; + mutable OwningPtr<BugType> BT; public: void checkPreObjCMessage(ObjCMessage msg, CheckerContext &C) const; @@ -464,8 +461,8 @@ void ClassReleaseChecker::checkPreObjCMessage(ObjCMessage msg, if (!(S == releaseS || S == retainS || S == autoreleaseS || S == drainS)) return; - if (ExplodedNode *N = C.generateNode()) { - llvm::SmallString<200> buf; + if (ExplodedNode *N = C.addTransition()) { + SmallString<200> buf; llvm::raw_svector_ostream os(buf); os << "The '" << S.getAsString() << "' message should be sent to instances " @@ -488,9 +485,10 @@ class VariadicMethodTypeChecker : public Checker<check::PreObjCMessage> { mutable Selector arrayWithObjectsS; mutable Selector dictionaryWithObjectsAndKeysS; mutable Selector setWithObjectsS; + mutable Selector orderedSetWithObjectsS; mutable Selector initWithObjectsS; mutable Selector initWithObjectsAndKeysS; - mutable llvm::OwningPtr<BugType> BT; + mutable OwningPtr<BugType> BT; bool isVariadicMessage(const ObjCMessage &msg) const; @@ -533,6 +531,11 @@ VariadicMethodTypeChecker::isVariadicMessage(const ObjCMessage &msg) const { if (isReceiverClassOrSuperclass(Class, "NSSet") && S == initWithObjectsS) return true; + + // -[NSOrderedSet initWithObjects:] + if (isReceiverClassOrSuperclass(Class, "NSOrderedSet") && + S == initWithObjectsS) + return true; } else { const ObjCInterfaceDecl *Class = msg.getReceiverInterface(); @@ -550,6 +553,11 @@ VariadicMethodTypeChecker::isVariadicMessage(const ObjCMessage &msg) const { if (isReceiverClassOrSuperclass(Class, "NSSet") && S == setWithObjectsS) return true; + + // -[NSOrderedSet orderedSetWithObjects:] + if (isReceiverClassOrSuperclass(Class, "NSOrderedSet") && + S == orderedSetWithObjectsS) + return true; } return false; @@ -566,6 +574,7 @@ void VariadicMethodTypeChecker::checkPreObjCMessage(ObjCMessage msg, dictionaryWithObjectsAndKeysS = GetUnarySelector("dictionaryWithObjectsAndKeys", Ctx); setWithObjectsS = GetUnarySelector("setWithObjects", Ctx); + orderedSetWithObjectsS = GetUnarySelector("orderedSetWithObjects", Ctx); initWithObjectsS = GetUnarySelector("initWithObjects", Ctx); initWithObjectsAndKeysS = GetUnarySelector("initWithObjectsAndKeys", Ctx); @@ -587,7 +596,7 @@ void VariadicMethodTypeChecker::checkPreObjCMessage(ObjCMessage msg, // Verify that all arguments have Objective-C types. llvm::Optional<ExplodedNode*> errorNode; - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); for (unsigned I = variadicArgsBegin; I != variadicArgsEnd; ++I) { QualType ArgTy = msg.getArgType(I); @@ -599,7 +608,8 @@ void VariadicMethodTypeChecker::checkPreObjCMessage(ObjCMessage msg, continue; // Ignore pointer constants. - if (isa<loc::ConcreteInt>(msg.getArgSVal(I, state))) + if (isa<loc::ConcreteInt>(msg.getArgSVal(I, C.getLocationContext(), + state))) continue; // Ignore pointer types annotated with 'NSObject' attribute. @@ -612,13 +622,13 @@ void VariadicMethodTypeChecker::checkPreObjCMessage(ObjCMessage msg, // Generate only one error node to use for all bug reports. if (!errorNode.hasValue()) { - errorNode = C.generateNode(); + errorNode = C.addTransition(); } if (!errorNode.getValue()) continue; - llvm::SmallString<128> sbuf; + SmallString<128> sbuf; llvm::raw_svector_ostream os(sbuf); if (const char *TypeName = GetReceiverNameType(msg)) diff --git a/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp b/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp new file mode 100644 index 0000000..a4fc396 --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp @@ -0,0 +1,157 @@ +//== BoolAssignmentChecker.cpp - Boolean assignment checker -----*- C++ -*--==// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This defines BoolAssignmentChecker, a builtin check in ExprEngine that +// performs checks for assignment of non-Boolean values to Boolean variables. +// +//===----------------------------------------------------------------------===// + +#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" + +using namespace clang; +using namespace ento; + +namespace { + class BoolAssignmentChecker : public Checker< check::Bind > { + mutable llvm::OwningPtr<BuiltinBug> BT; + void emitReport(ProgramStateRef state, CheckerContext &C) const; + public: + void checkBind(SVal loc, SVal val, const Stmt *S, CheckerContext &C) const; + }; +} // end anonymous namespace + +void BoolAssignmentChecker::emitReport(ProgramStateRef state, + CheckerContext &C) const { + if (ExplodedNode *N = C.addTransition(state)) { + if (!BT) + BT.reset(new BuiltinBug("Assignment of a non-Boolean value")); + C.EmitReport(new BugReport(*BT, BT->getDescription(), N)); + } +} + +static bool isBooleanType(QualType Ty) { + if (Ty->isBooleanType()) // C++ or C99 + return true; + + if (const TypedefType *TT = Ty->getAs<TypedefType>()) + return TT->getDecl()->getName() == "BOOL" || // Objective-C + TT->getDecl()->getName() == "_Bool" || // stdbool.h < C99 + TT->getDecl()->getName() == "Boolean"; // MacTypes.h + + return false; +} + +void BoolAssignmentChecker::checkBind(SVal loc, SVal val, const Stmt *S, + CheckerContext &C) const { + + // We are only interested in stores into Booleans. + const TypedValueRegion *TR = + dyn_cast_or_null<TypedValueRegion>(loc.getAsRegion()); + + if (!TR) + return; + + QualType valTy = TR->getValueType(); + + if (!isBooleanType(valTy)) + return; + + // Get the value of the right-hand side. We only care about values + // that are defined (UnknownVals and UndefinedVals are handled by other + // checkers). + const DefinedSVal *DV = dyn_cast<DefinedSVal>(&val); + if (!DV) + return; + + // Check if the assigned value meets our criteria for correctness. It must + // be a value that is either 0 or 1. One way to check this is to see if + // the value is possibly < 0 (for a negative value) or greater than 1. + ProgramStateRef state = C.getState(); + SValBuilder &svalBuilder = C.getSValBuilder(); + ConstraintManager &CM = C.getConstraintManager(); + + // First, ensure that the value is >= 0. + DefinedSVal zeroVal = svalBuilder.makeIntVal(0, valTy); + SVal greaterThanOrEqualToZeroVal = + svalBuilder.evalBinOp(state, BO_GE, *DV, zeroVal, + svalBuilder.getConditionType()); + + DefinedSVal *greaterThanEqualToZero = + dyn_cast<DefinedSVal>(&greaterThanOrEqualToZeroVal); + + if (!greaterThanEqualToZero) { + // The SValBuilder cannot construct a valid SVal for this condition. + // This means we cannot properly reason about it. + return; + } + + ProgramStateRef stateLT, stateGE; + llvm::tie(stateGE, stateLT) = CM.assumeDual(state, *greaterThanEqualToZero); + + // Is it possible for the value to be less than zero? + if (stateLT) { + // It is possible for the value to be less than zero. We only + // want to emit a warning, however, if that value is fully constrained. + // If it it possible for the value to be >= 0, then essentially the + // value is underconstrained and there is nothing left to be done. + if (!stateGE) + emitReport(stateLT, C); + + // In either case, we are done. + return; + } + + // If we reach here, it must be the case that the value is constrained + // to only be >= 0. + assert(stateGE == state); + + // At this point we know that the value is >= 0. + // Now check to ensure that the value is <= 1. + DefinedSVal OneVal = svalBuilder.makeIntVal(1, valTy); + SVal lessThanEqToOneVal = + svalBuilder.evalBinOp(state, BO_LE, *DV, OneVal, + svalBuilder.getConditionType()); + + DefinedSVal *lessThanEqToOne = + dyn_cast<DefinedSVal>(&lessThanEqToOneVal); + + if (!lessThanEqToOne) { + // The SValBuilder cannot construct a valid SVal for this condition. + // This means we cannot properly reason about it. + return; + } + + ProgramStateRef stateGT, stateLE; + llvm::tie(stateLE, stateGT) = CM.assumeDual(state, *lessThanEqToOne); + + // Is it possible for the value to be greater than one? + if (stateGT) { + // It is possible for the value to be greater than one. We only + // want to emit a warning, however, if that value is fully constrained. + // If it is possible for the value to be <= 1, then essentially the + // value is underconstrained and there is nothing left to be done. + if (!stateLE) + emitReport(stateGT, C); + + // In either case, we are done. + return; + } + + // If we reach here, it must be the case that the value is constrained + // to only be <= 1. + assert(stateLE == state); +} + +void ento::registerBoolAssignmentChecker(CheckerManager &mgr) { + mgr.registerChecker<BoolAssignmentChecker>(); +} diff --git a/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp b/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp index a57d031..509bc79 100644 --- a/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp @@ -30,12 +30,10 @@ public: } bool BuiltinFunctionChecker::evalCall(const CallExpr *CE, - CheckerContext &C) const{ - const ProgramState *state = C.getState(); - const Expr *Callee = CE->getCallee(); - SVal L = state->getSVal(Callee); - const FunctionDecl *FD = L.getAsFunctionDecl(); - + CheckerContext &C) const { + ProgramStateRef state = C.getState(); + const FunctionDecl *FD = C.getCalleeDecl(CE); + const LocationContext *LCtx = C.getLocationContext(); if (!FD) return false; @@ -48,8 +46,8 @@ bool BuiltinFunctionChecker::evalCall(const CallExpr *CE, case Builtin::BI__builtin_expect: { // For __builtin_expect, just return the value of the subexpression. assert (CE->arg_begin() != CE->arg_end()); - SVal X = state->getSVal(*(CE->arg_begin())); - C.generateNode(state->BindExpr(CE, X)); + SVal X = state->getSVal(*(CE->arg_begin()), LCtx); + C.addTransition(state->BindExpr(CE, LCtx, X)); return true; } @@ -57,14 +55,13 @@ bool BuiltinFunctionChecker::evalCall(const CallExpr *CE, // FIXME: Refactor into StoreManager itself? MemRegionManager& RM = C.getStoreManager().getRegionManager(); const AllocaRegion* R = - RM.getAllocaRegion(CE, C.getCurrentBlockCount(), - C.getPredecessor()->getLocationContext()); + RM.getAllocaRegion(CE, C.getCurrentBlockCount(), C.getLocationContext()); // Set the extent of the region in bytes. This enables us to use the // SVal of the argument directly. If we save the extent in bits, we // cannot represent values like symbol*8. DefinedOrUnknownSVal Size = - cast<DefinedOrUnknownSVal>(state->getSVal(*(CE->arg_begin()))); + cast<DefinedOrUnknownSVal>(state->getSVal(*(CE->arg_begin()), LCtx)); SValBuilder& svalBuilder = C.getSValBuilder(); DefinedOrUnknownSVal Extent = R->getExtent(svalBuilder); @@ -72,7 +69,7 @@ bool BuiltinFunctionChecker::evalCall(const CallExpr *CE, svalBuilder.evalEQ(state, Extent, Size); state = state->assume(extentMatchesSizeArg, true); - C.generateNode(state->BindExpr(CE, loc::MemRegionVal(R))); + C.addTransition(state->BindExpr(CE, LCtx, loc::MemRegionVal(R))); return true; } } diff --git a/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/lib/StaticAnalyzer/Checkers/CMakeLists.txt index 3e0d094..a377ca9 100644 --- a/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -3,7 +3,7 @@ clang_tablegen(Checkers.inc -gen-clang-sa-checkers SOURCE Checkers.td TARGET ClangSACheckers) -set(LLVM_USED_LIBS clangBasic clangAST) +set(LLVM_USED_LIBS clangBasic clangAST clangStaticAnalyzerCore) add_clang_library(clangStaticAnalyzerCheckers AdjustedReturnValueChecker.cpp @@ -12,8 +12,10 @@ add_clang_library(clangStaticAnalyzerCheckers ArrayBoundCheckerV2.cpp AttrNonNullChecker.cpp BasicObjCFoundationChecks.cpp + BoolAssignmentChecker.cpp BuiltinFunctionChecker.cpp CStringChecker.cpp + CStringSyntaxChecker.cpp CallAndMessageChecker.cpp CastSizeChecker.cpp CastToStructChecker.cpp @@ -21,13 +23,16 @@ add_clang_library(clangStaticAnalyzerCheckers CheckObjCInstMethSignature.cpp CheckSecuritySyntaxOnly.cpp CheckSizeofPointer.cpp + CheckerDocumentation.cpp ChrootChecker.cpp ClangCheckers.cpp + CommonBugCategories.cpp DeadStoresChecker.cpp DebugCheckers.cpp DereferenceChecker.cpp DivZeroChecker.cpp FixedAddressChecker.cpp + GenericTaintChecker.cpp IdempotentOperationChecker.cpp IteratorsChecker.cpp LLVMConventionsChecker.cpp @@ -35,11 +40,14 @@ add_clang_library(clangStaticAnalyzerCheckers MacOSXAPIChecker.cpp MallocChecker.cpp MallocOverflowSecurityChecker.cpp + MallocSizeofChecker.cpp NSAutoreleasePoolChecker.cpp NSErrorChecker.cpp NoReturnFunctionChecker.cpp OSAtomicChecker.cpp ObjCAtSyncChecker.cpp + ObjCContainersASTChecker.cpp + ObjCContainersChecker.cpp ObjCSelfInitChecker.cpp ObjCUnusedIVarsChecker.cpp PointerArithChecker.cpp @@ -50,6 +58,7 @@ add_clang_library(clangStaticAnalyzerCheckers ReturnUndefChecker.cpp StackAddrEscapeChecker.cpp StreamChecker.cpp + TaintTesterChecker.cpp UndefBranchChecker.cpp UndefCapturedBlockVarChecker.cpp UndefResultChecker.cpp @@ -58,6 +67,7 @@ add_clang_library(clangStaticAnalyzerCheckers UnixAPIChecker.cpp UnreachableCodeChecker.cpp VLASizeChecker.cpp + VirtualCallChecker.cpp ) add_dependencies(clangStaticAnalyzerCheckers diff --git a/lib/StaticAnalyzer/Checkers/CStringChecker.cpp b/lib/StaticAnalyzer/Checkers/CStringChecker.cpp index 1625219..9eb7edf 100644 --- a/lib/StaticAnalyzer/Checkers/CStringChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/CStringChecker.cpp @@ -13,11 +13,14 @@ //===----------------------------------------------------------------------===// #include "ClangSACheckers.h" +#include "InterCheckerAPI.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringSwitch.h" using namespace clang; @@ -30,25 +33,40 @@ class CStringChecker : public Checker< eval::Call, check::DeadSymbols, check::RegionChanges > { - mutable llvm::OwningPtr<BugType> BT_Null, BT_Bounds, - BT_Overlap, BT_NotCString, - BT_AdditionOverflow; + mutable OwningPtr<BugType> BT_Null, + BT_Bounds, + BT_Overlap, + BT_NotCString, + BT_AdditionOverflow; + mutable const char *CurrentFunctionDescription; public: + /// The filter is used to filter out the diagnostics which are not enabled by + /// the user. + struct CStringChecksFilter { + DefaultBool CheckCStringNullArg; + DefaultBool CheckCStringOutOfBounds; + DefaultBool CheckCStringBufferOverlap; + DefaultBool CheckCStringNotNullTerm; + }; + + CStringChecksFilter Filter; + static void *getTag() { static int tag; return &tag; } bool evalCall(const CallExpr *CE, CheckerContext &C) const; void checkPreStmt(const DeclStmt *DS, CheckerContext &C) const; - void checkLiveSymbols(const ProgramState *state, SymbolReaper &SR) const; + void checkLiveSymbols(ProgramStateRef state, SymbolReaper &SR) const; void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; - bool wantsRegionChangeUpdate(const ProgramState *state) const; + bool wantsRegionChangeUpdate(ProgramStateRef state) const; - const ProgramState * - checkRegionChanges(const ProgramState *state, + ProgramStateRef + checkRegionChanges(ProgramStateRef state, const StoreManager::InvalidatedSymbols *, ArrayRef<const MemRegion *> ExplicitRegions, - ArrayRef<const MemRegion *> Regions) const; + ArrayRef<const MemRegion *> Regions, + const CallOrObjCMessage *Call) const; typedef void (CStringChecker::*FnCheck)(CheckerContext &, const CallExpr *) const; @@ -58,7 +76,7 @@ public: void evalMemmove(CheckerContext &C, const CallExpr *CE) const; void evalBcopy(CheckerContext &C, const CallExpr *CE) const; void evalCopyCommon(CheckerContext &C, const CallExpr *CE, - const ProgramState *state, + ProgramStateRef state, const Expr *Size, const Expr *Source, const Expr *Dest, @@ -95,48 +113,48 @@ public: bool ignoreCase = false) const; // Utility methods - std::pair<const ProgramState*, const ProgramState*> + std::pair<ProgramStateRef , ProgramStateRef > static assumeZero(CheckerContext &C, - const ProgramState *state, SVal V, QualType Ty); + ProgramStateRef state, SVal V, QualType Ty); - static const ProgramState *setCStringLength(const ProgramState *state, + static ProgramStateRef setCStringLength(ProgramStateRef state, const MemRegion *MR, SVal strLength); static SVal getCStringLengthForRegion(CheckerContext &C, - const ProgramState *&state, + ProgramStateRef &state, const Expr *Ex, const MemRegion *MR, bool hypothetical); SVal getCStringLength(CheckerContext &C, - const ProgramState *&state, + ProgramStateRef &state, const Expr *Ex, SVal Buf, bool hypothetical = false) const; const StringLiteral *getCStringLiteral(CheckerContext &C, - const ProgramState *&state, + ProgramStateRef &state, const Expr *expr, SVal val) const; - static const ProgramState *InvalidateBuffer(CheckerContext &C, - const ProgramState *state, + static ProgramStateRef InvalidateBuffer(CheckerContext &C, + ProgramStateRef state, const Expr *Ex, SVal V); static bool SummarizeRegion(raw_ostream &os, ASTContext &Ctx, const MemRegion *MR); // Re-usable checks - const ProgramState *checkNonNull(CheckerContext &C, - const ProgramState *state, + ProgramStateRef checkNonNull(CheckerContext &C, + ProgramStateRef state, const Expr *S, SVal l) const; - const ProgramState *CheckLocation(CheckerContext &C, - const ProgramState *state, + ProgramStateRef CheckLocation(CheckerContext &C, + ProgramStateRef state, const Expr *S, SVal l, const char *message = NULL) const; - const ProgramState *CheckBufferAccess(CheckerContext &C, - const ProgramState *state, + ProgramStateRef CheckBufferAccess(CheckerContext &C, + ProgramStateRef state, const Expr *Size, const Expr *FirstBuf, const Expr *SecondBuf, @@ -144,8 +162,8 @@ public: const char *secondMessage = NULL, bool WarnAboutSize = false) const; - const ProgramState *CheckBufferAccess(CheckerContext &C, - const ProgramState *state, + ProgramStateRef CheckBufferAccess(CheckerContext &C, + ProgramStateRef state, const Expr *Size, const Expr *Buf, const char *message = NULL, @@ -154,18 +172,18 @@ public: return CheckBufferAccess(C, state, Size, Buf, NULL, message, NULL, WarnAboutSize); } - const ProgramState *CheckOverlap(CheckerContext &C, - const ProgramState *state, + ProgramStateRef CheckOverlap(CheckerContext &C, + ProgramStateRef state, const Expr *Size, const Expr *First, const Expr *Second) const; void emitOverlapBug(CheckerContext &C, - const ProgramState *state, + ProgramStateRef state, const Stmt *First, const Stmt *Second) const; - const ProgramState *checkAdditionOverflow(CheckerContext &C, - const ProgramState *state, + ProgramStateRef checkAdditionOverflow(CheckerContext &C, + ProgramStateRef state, NonLoc left, NonLoc right) const; }; @@ -190,38 +208,41 @@ namespace ento { // Individual checks and utility methods. //===----------------------------------------------------------------------===// -std::pair<const ProgramState*, const ProgramState*> -CStringChecker::assumeZero(CheckerContext &C, const ProgramState *state, SVal V, +std::pair<ProgramStateRef , ProgramStateRef > +CStringChecker::assumeZero(CheckerContext &C, ProgramStateRef state, SVal V, QualType Ty) { DefinedSVal *val = dyn_cast<DefinedSVal>(&V); if (!val) - return std::pair<const ProgramState*, const ProgramState *>(state, state); + return std::pair<ProgramStateRef , ProgramStateRef >(state, state); SValBuilder &svalBuilder = C.getSValBuilder(); DefinedOrUnknownSVal zero = svalBuilder.makeZeroVal(Ty); return state->assume(svalBuilder.evalEQ(state, *val, zero)); } -const ProgramState *CStringChecker::checkNonNull(CheckerContext &C, - const ProgramState *state, +ProgramStateRef CStringChecker::checkNonNull(CheckerContext &C, + ProgramStateRef state, const Expr *S, SVal l) const { // If a previous check has failed, propagate the failure. if (!state) return NULL; - const ProgramState *stateNull, *stateNonNull; + ProgramStateRef stateNull, stateNonNull; llvm::tie(stateNull, stateNonNull) = assumeZero(C, state, l, S->getType()); if (stateNull && !stateNonNull) { + if (!Filter.CheckCStringNullArg) + return NULL; + ExplodedNode *N = C.generateSink(stateNull); if (!N) return NULL; if (!BT_Null) - BT_Null.reset(new BuiltinBug("API", + BT_Null.reset(new BuiltinBug("Unix API", "Null pointer argument in call to byte string function")); - llvm::SmallString<80> buf; + SmallString<80> buf; llvm::raw_svector_ostream os(buf); assert(CurrentFunctionDescription); os << "Null pointer argument in call to " << CurrentFunctionDescription; @@ -231,7 +252,8 @@ const ProgramState *CStringChecker::checkNonNull(CheckerContext &C, BugReport *report = new BugReport(*BT, os.str(), N); report->addRange(S->getSourceRange()); - report->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, S)); + report->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, S, + report)); C.EmitReport(report); return NULL; } @@ -242,8 +264,8 @@ const ProgramState *CStringChecker::checkNonNull(CheckerContext &C, } // FIXME: This was originally copied from ArrayBoundChecker.cpp. Refactor? -const ProgramState *CStringChecker::CheckLocation(CheckerContext &C, - const ProgramState *state, +ProgramStateRef CStringChecker::CheckLocation(CheckerContext &C, + ProgramStateRef state, const Expr *S, SVal l, const char *warningMsg) const { // If a previous check has failed, propagate the failure. @@ -272,8 +294,8 @@ const ProgramState *CStringChecker::CheckLocation(CheckerContext &C, // Get the index of the accessed element. DefinedOrUnknownSVal Idx = cast<DefinedOrUnknownSVal>(ER->getIndex()); - const ProgramState *StInBound = state->assumeInBound(Idx, Size, true); - const ProgramState *StOutBound = state->assumeInBound(Idx, Size, false); + ProgramStateRef StInBound = state->assumeInBound(Idx, Size, true); + ProgramStateRef StOutBound = state->assumeInBound(Idx, Size, false); if (StOutBound && !StInBound) { ExplodedNode *N = C.generateSink(StOutBound); if (!N) @@ -293,7 +315,7 @@ const ProgramState *CStringChecker::CheckLocation(CheckerContext &C, assert(CurrentFunctionDescription); assert(CurrentFunctionDescription[0] != '\0'); - llvm::SmallString<80> buf; + SmallString<80> buf; llvm::raw_svector_ostream os(buf); os << (char)toupper(CurrentFunctionDescription[0]) << &CurrentFunctionDescription[1] @@ -315,8 +337,8 @@ const ProgramState *CStringChecker::CheckLocation(CheckerContext &C, return StInBound; } -const ProgramState *CStringChecker::CheckBufferAccess(CheckerContext &C, - const ProgramState *state, +ProgramStateRef CStringChecker::CheckBufferAccess(CheckerContext &C, + ProgramStateRef state, const Expr *Size, const Expr *FirstBuf, const Expr *SecondBuf, @@ -329,20 +351,25 @@ const ProgramState *CStringChecker::CheckBufferAccess(CheckerContext &C, SValBuilder &svalBuilder = C.getSValBuilder(); ASTContext &Ctx = svalBuilder.getContext(); + const LocationContext *LCtx = C.getLocationContext(); QualType sizeTy = Size->getType(); QualType PtrTy = Ctx.getPointerType(Ctx.CharTy); // Check that the first buffer is non-null. - SVal BufVal = state->getSVal(FirstBuf); + SVal BufVal = state->getSVal(FirstBuf, LCtx); state = checkNonNull(C, state, FirstBuf, BufVal); if (!state) return NULL; + // If out-of-bounds checking is turned off, skip the rest. + if (!Filter.CheckCStringOutOfBounds) + return state; + // Get the access length and make sure it is known. // FIXME: This assumes the caller has already checked that the access length // is positive. And that it's unsigned. - SVal LengthVal = state->getSVal(Size); + SVal LengthVal = state->getSVal(Size, LCtx); NonLoc *Length = dyn_cast<NonLoc>(&LengthVal); if (!Length) return state; @@ -368,7 +395,7 @@ const ProgramState *CStringChecker::CheckBufferAccess(CheckerContext &C, // If there's a second buffer, check it as well. if (SecondBuf) { - BufVal = state->getSVal(SecondBuf); + BufVal = state->getSVal(SecondBuf, LCtx); state = checkNonNull(C, state, SecondBuf, BufVal); if (!state) return NULL; @@ -387,11 +414,14 @@ const ProgramState *CStringChecker::CheckBufferAccess(CheckerContext &C, return state; } -const ProgramState *CStringChecker::CheckOverlap(CheckerContext &C, - const ProgramState *state, +ProgramStateRef CStringChecker::CheckOverlap(CheckerContext &C, + ProgramStateRef state, const Expr *Size, const Expr *First, const Expr *Second) const { + if (!Filter.CheckCStringBufferOverlap) + return state; + // Do a simple check for overlap: if the two arguments are from the same // buffer, see if the end of the first is greater than the start of the second // or vice versa. @@ -400,11 +430,12 @@ const ProgramState *CStringChecker::CheckOverlap(CheckerContext &C, if (!state) return NULL; - const ProgramState *stateTrue, *stateFalse; + ProgramStateRef stateTrue, stateFalse; // Get the buffer values and make sure they're known locations. - SVal firstVal = state->getSVal(First); - SVal secondVal = state->getSVal(Second); + const LocationContext *LCtx = C.getLocationContext(); + SVal firstVal = state->getSVal(First, LCtx); + SVal secondVal = state->getSVal(Second, LCtx); Loc *firstLoc = dyn_cast<Loc>(&firstVal); if (!firstLoc) @@ -456,7 +487,7 @@ const ProgramState *CStringChecker::CheckOverlap(CheckerContext &C, } // Get the length, and make sure it too is known. - SVal LengthVal = state->getSVal(Size); + SVal LengthVal = state->getSVal(Size, LCtx); NonLoc *Length = dyn_cast<NonLoc>(&LengthVal); if (!Length) return state; @@ -498,7 +529,7 @@ const ProgramState *CStringChecker::CheckOverlap(CheckerContext &C, return stateFalse; } -void CStringChecker::emitOverlapBug(CheckerContext &C, const ProgramState *state, +void CStringChecker::emitOverlapBug(CheckerContext &C, ProgramStateRef state, const Stmt *First, const Stmt *Second) const { ExplodedNode *N = C.generateSink(state); if (!N) @@ -517,10 +548,14 @@ void CStringChecker::emitOverlapBug(CheckerContext &C, const ProgramState *state C.EmitReport(report); } -const ProgramState *CStringChecker::checkAdditionOverflow(CheckerContext &C, - const ProgramState *state, +ProgramStateRef CStringChecker::checkAdditionOverflow(CheckerContext &C, + ProgramStateRef state, NonLoc left, NonLoc right) const { + // If out-of-bounds checking is turned off, skip the rest. + if (!Filter.CheckCStringOutOfBounds) + return state; + // If a previous check has failed, propagate the failure. if (!state) return NULL; @@ -532,10 +567,11 @@ const ProgramState *CStringChecker::checkAdditionOverflow(CheckerContext &C, const llvm::APSInt &maxValInt = BVF.getMaxValue(sizeTy); NonLoc maxVal = svalBuilder.makeIntVal(maxValInt); - SVal maxMinusRight = svalBuilder.evalBinOpNN(state, BO_Sub, maxVal, right, - sizeTy); - - if (maxMinusRight.isUnknownOrUndef()) { + SVal maxMinusRight; + if (isa<nonloc::ConcreteInt>(right)) { + maxMinusRight = svalBuilder.evalBinOpNN(state, BO_Sub, maxVal, right, + sizeTy); + } else { // Try switching the operands. (The order of these two assignments is // important!) maxMinusRight = svalBuilder.evalBinOpNN(state, BO_Sub, maxVal, left, @@ -549,7 +585,7 @@ const ProgramState *CStringChecker::checkAdditionOverflow(CheckerContext &C, SVal willOverflow = svalBuilder.evalBinOpNN(state, BO_GT, left, *maxMinusRightNL, cmpTy); - const ProgramState *stateOverflow, *stateOkay; + ProgramStateRef stateOverflow, stateOkay; llvm::tie(stateOverflow, stateOkay) = state->assume(cast<DefinedOrUnknownSVal>(willOverflow)); @@ -585,7 +621,7 @@ const ProgramState *CStringChecker::checkAdditionOverflow(CheckerContext &C, return state; } -const ProgramState *CStringChecker::setCStringLength(const ProgramState *state, +ProgramStateRef CStringChecker::setCStringLength(ProgramStateRef state, const MemRegion *MR, SVal strLength) { assert(!strLength.isUndef() && "Attempt to set an undefined string length"); @@ -626,7 +662,7 @@ const ProgramState *CStringChecker::setCStringLength(const ProgramState *state, } SVal CStringChecker::getCStringLengthForRegion(CheckerContext &C, - const ProgramState *&state, + ProgramStateRef &state, const Expr *Ex, const MemRegion *MR, bool hypothetical) { @@ -650,7 +686,7 @@ SVal CStringChecker::getCStringLengthForRegion(CheckerContext &C, return strLength; } -SVal CStringChecker::getCStringLength(CheckerContext &C, const ProgramState *&state, +SVal CStringChecker::getCStringLength(CheckerContext &C, ProgramStateRef &state, const Expr *Ex, SVal Buf, bool hypothetical) const { const MemRegion *MR = Buf.getAsRegion(); @@ -659,12 +695,15 @@ SVal CStringChecker::getCStringLength(CheckerContext &C, const ProgramState *&st // C string. In the context of locations, the only time we can issue such // a warning is for labels. if (loc::GotoLabel *Label = dyn_cast<loc::GotoLabel>(&Buf)) { - if (ExplodedNode *N = C.generateNode(state)) { + if (!Filter.CheckCStringNotNullTerm) + return UndefinedVal(); + + if (ExplodedNode *N = C.addTransition(state)) { if (!BT_NotCString) - BT_NotCString.reset(new BuiltinBug("API", + BT_NotCString.reset(new BuiltinBug("Unix API", "Argument is not a null-terminated string.")); - llvm::SmallString<120> buf; + SmallString<120> buf; llvm::raw_svector_ostream os(buf); assert(CurrentFunctionDescription); os << "Argument to " << CurrentFunctionDescription @@ -678,8 +717,8 @@ SVal CStringChecker::getCStringLength(CheckerContext &C, const ProgramState *&st report->addRange(Ex->getSourceRange()); C.EmitReport(report); } - return UndefinedVal(); + } // If it's not a region and not a label, give up. @@ -716,12 +755,15 @@ SVal CStringChecker::getCStringLength(CheckerContext &C, const ProgramState *&st // Other regions (mostly non-data) can't have a reliable C string length. // In this case, an error is emitted and UndefinedVal is returned. // The caller should always be prepared to handle this case. - if (ExplodedNode *N = C.generateNode(state)) { + if (!Filter.CheckCStringNotNullTerm) + return UndefinedVal(); + + if (ExplodedNode *N = C.addTransition(state)) { if (!BT_NotCString) - BT_NotCString.reset(new BuiltinBug("API", + BT_NotCString.reset(new BuiltinBug("Unix API", "Argument is not a null-terminated string.")); - llvm::SmallString<120> buf; + SmallString<120> buf; llvm::raw_svector_ostream os(buf); assert(CurrentFunctionDescription); @@ -745,7 +787,7 @@ SVal CStringChecker::getCStringLength(CheckerContext &C, const ProgramState *&st } const StringLiteral *CStringChecker::getCStringLiteral(CheckerContext &C, - const ProgramState *&state, const Expr *expr, SVal val) const { + ProgramStateRef &state, const Expr *expr, SVal val) const { // Get the memory region pointed to by the val. const MemRegion *bufRegion = val.getAsRegion(); @@ -764,8 +806,8 @@ const StringLiteral *CStringChecker::getCStringLiteral(CheckerContext &C, return strRegion->getStringLiteral(); } -const ProgramState *CStringChecker::InvalidateBuffer(CheckerContext &C, - const ProgramState *state, +ProgramStateRef CStringChecker::InvalidateBuffer(CheckerContext &C, + ProgramStateRef state, const Expr *E, SVal V) { Loc *L = dyn_cast<Loc>(&V); if (!L) @@ -786,7 +828,8 @@ const ProgramState *CStringChecker::InvalidateBuffer(CheckerContext &C, // Invalidate this region. unsigned Count = C.getCurrentBlockCount(); - return state->invalidateRegions(R, E, Count); + const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); + return state->invalidateRegions(R, E, Count, LCtx); } // If we have a non-region value by chance, just remove the binding. @@ -838,27 +881,28 @@ bool CStringChecker::SummarizeRegion(raw_ostream &os, ASTContext &Ctx, void CStringChecker::evalCopyCommon(CheckerContext &C, const CallExpr *CE, - const ProgramState *state, + ProgramStateRef state, const Expr *Size, const Expr *Dest, const Expr *Source, bool Restricted, bool IsMempcpy) const { CurrentFunctionDescription = "memory copy function"; // See if the size argument is zero. - SVal sizeVal = state->getSVal(Size); + const LocationContext *LCtx = C.getLocationContext(); + SVal sizeVal = state->getSVal(Size, LCtx); QualType sizeTy = Size->getType(); - const ProgramState *stateZeroSize, *stateNonZeroSize; + ProgramStateRef stateZeroSize, stateNonZeroSize; llvm::tie(stateZeroSize, stateNonZeroSize) = assumeZero(C, state, sizeVal, sizeTy); // Get the value of the Dest. - SVal destVal = state->getSVal(Dest); + SVal destVal = state->getSVal(Dest, LCtx); // If the size is zero, there won't be any actual memory access, so // just bind the return value to the destination buffer and return. if (stateZeroSize) { - stateZeroSize = stateZeroSize->BindExpr(CE, destVal); + stateZeroSize = stateZeroSize->BindExpr(CE, LCtx, destVal); C.addTransition(stateZeroSize); } @@ -873,7 +917,7 @@ void CStringChecker::evalCopyCommon(CheckerContext &C, return; // Get the value of the Src. - SVal srcVal = state->getSVal(Source); + SVal srcVal = state->getSVal(Source, LCtx); // Ensure the source is not null. If it is NULL there will be a // NULL pointer dereference. @@ -909,20 +953,20 @@ void CStringChecker::evalCopyCommon(CheckerContext &C, Dest->getType()); // The byte after the last byte copied is the return value. - state = state->BindExpr(CE, lastElement); + state = state->BindExpr(CE, LCtx, lastElement); } else { // If we don't know how much we copied, we can at least // conjure a return value for later. unsigned Count = C.getCurrentBlockCount(); SVal result = - C.getSValBuilder().getConjuredSymbolVal(NULL, CE, Count); - state = state->BindExpr(CE, result); + C.getSValBuilder().getConjuredSymbolVal(NULL, CE, LCtx, Count); + state = state->BindExpr(CE, LCtx, result); } } else { // All other copies return the destination buffer. // (Well, bcopy() has a void return type, but this won't hurt.) - state = state->BindExpr(CE, destVal); + state = state->BindExpr(CE, LCtx, destVal); } // Invalidate the destination. @@ -930,46 +974,62 @@ void CStringChecker::evalCopyCommon(CheckerContext &C, // can use LazyCompoundVals to copy the source values into the destination. // This would probably remove any existing bindings past the end of the // copied region, but that's still an improvement over blank invalidation. - state = InvalidateBuffer(C, state, Dest, state->getSVal(Dest)); + state = InvalidateBuffer(C, state, Dest, + state->getSVal(Dest, C.getLocationContext())); C.addTransition(state); } } void CStringChecker::evalMemcpy(CheckerContext &C, const CallExpr *CE) const { + if (CE->getNumArgs() < 3) + return; + // void *memcpy(void *restrict dst, const void *restrict src, size_t n); // The return value is the address of the destination buffer. const Expr *Dest = CE->getArg(0); - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); evalCopyCommon(C, CE, state, CE->getArg(2), Dest, CE->getArg(1), true); } void CStringChecker::evalMempcpy(CheckerContext &C, const CallExpr *CE) const { + if (CE->getNumArgs() < 3) + return; + // void *mempcpy(void *restrict dst, const void *restrict src, size_t n); // The return value is a pointer to the byte following the last written byte. const Expr *Dest = CE->getArg(0); - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); evalCopyCommon(C, CE, state, CE->getArg(2), Dest, CE->getArg(1), true, true); } void CStringChecker::evalMemmove(CheckerContext &C, const CallExpr *CE) const { + if (CE->getNumArgs() < 3) + return; + // void *memmove(void *dst, const void *src, size_t n); // The return value is the address of the destination buffer. const Expr *Dest = CE->getArg(0); - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); evalCopyCommon(C, CE, state, CE->getArg(2), Dest, CE->getArg(1)); } void CStringChecker::evalBcopy(CheckerContext &C, const CallExpr *CE) const { + if (CE->getNumArgs() < 3) + return; + // void bcopy(const void *src, void *dst, size_t n); evalCopyCommon(C, CE, C.getState(), CE->getArg(2), CE->getArg(1), CE->getArg(0)); } void CStringChecker::evalMemcmp(CheckerContext &C, const CallExpr *CE) const { + if (CE->getNumArgs() < 3) + return; + // int memcmp(const void *s1, const void *s2, size_t n); CurrentFunctionDescription = "memory comparison function"; @@ -977,14 +1037,15 @@ void CStringChecker::evalMemcmp(CheckerContext &C, const CallExpr *CE) const { const Expr *Right = CE->getArg(1); const Expr *Size = CE->getArg(2); - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); SValBuilder &svalBuilder = C.getSValBuilder(); // See if the size argument is zero. - SVal sizeVal = state->getSVal(Size); + const LocationContext *LCtx = C.getLocationContext(); + SVal sizeVal = state->getSVal(Size, LCtx); QualType sizeTy = Size->getType(); - const ProgramState *stateZeroSize, *stateNonZeroSize; + ProgramStateRef stateZeroSize, stateNonZeroSize; llvm::tie(stateZeroSize, stateNonZeroSize) = assumeZero(C, state, sizeVal, sizeTy); @@ -992,7 +1053,8 @@ void CStringChecker::evalMemcmp(CheckerContext &C, const CallExpr *CE) const { // have to check either of the buffers. if (stateZeroSize) { state = stateZeroSize; - state = state->BindExpr(CE, svalBuilder.makeZeroVal(CE->getType())); + state = state->BindExpr(CE, LCtx, + svalBuilder.makeZeroVal(CE->getType())); C.addTransition(state); } @@ -1002,12 +1064,14 @@ void CStringChecker::evalMemcmp(CheckerContext &C, const CallExpr *CE) const { // If we know the two buffers are the same, we know the result is 0. // First, get the two buffers' addresses. Another checker will have already // made sure they're not undefined. - DefinedOrUnknownSVal LV = cast<DefinedOrUnknownSVal>(state->getSVal(Left)); - DefinedOrUnknownSVal RV = cast<DefinedOrUnknownSVal>(state->getSVal(Right)); + DefinedOrUnknownSVal LV = + cast<DefinedOrUnknownSVal>(state->getSVal(Left, LCtx)); + DefinedOrUnknownSVal RV = + cast<DefinedOrUnknownSVal>(state->getSVal(Right, LCtx)); // See if they are the same. DefinedOrUnknownSVal SameBuf = svalBuilder.evalEQ(state, LV, RV); - const ProgramState *StSameBuf, *StNotSameBuf; + ProgramStateRef StSameBuf, StNotSameBuf; llvm::tie(StSameBuf, StNotSameBuf) = state->assume(SameBuf); // If the two arguments might be the same buffer, we know the result is 0, @@ -1016,8 +1080,9 @@ void CStringChecker::evalMemcmp(CheckerContext &C, const CallExpr *CE) const { state = StSameBuf; state = CheckBufferAccess(C, state, Size, Left); if (state) { - state = StSameBuf->BindExpr(CE, svalBuilder.makeZeroVal(CE->getType())); - C.addTransition(state); + state = StSameBuf->BindExpr(CE, LCtx, + svalBuilder.makeZeroVal(CE->getType())); + C.addTransition(state); } } @@ -1029,8 +1094,8 @@ void CStringChecker::evalMemcmp(CheckerContext &C, const CallExpr *CE) const { if (state) { // The return value is the comparison result, which we don't know. unsigned Count = C.getCurrentBlockCount(); - SVal CmpV = svalBuilder.getConjuredSymbolVal(NULL, CE, Count); - state = state->BindExpr(CE, CmpV); + SVal CmpV = svalBuilder.getConjuredSymbolVal(NULL, CE, LCtx, Count); + state = state->BindExpr(CE, LCtx, CmpV); C.addTransition(state); } } @@ -1039,12 +1104,18 @@ void CStringChecker::evalMemcmp(CheckerContext &C, const CallExpr *CE) const { void CStringChecker::evalstrLength(CheckerContext &C, const CallExpr *CE) const { + if (CE->getNumArgs() < 1) + return; + // size_t strlen(const char *s); evalstrLengthCommon(C, CE, /* IsStrnlen = */ false); } void CStringChecker::evalstrnLength(CheckerContext &C, const CallExpr *CE) const { + if (CE->getNumArgs() < 2) + return; + // size_t strnlen(const char *s, size_t maxlen); evalstrLengthCommon(C, CE, /* IsStrnlen = */ true); } @@ -1052,13 +1123,14 @@ void CStringChecker::evalstrnLength(CheckerContext &C, void CStringChecker::evalstrLengthCommon(CheckerContext &C, const CallExpr *CE, bool IsStrnlen) const { CurrentFunctionDescription = "string length function"; - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); + const LocationContext *LCtx = C.getLocationContext(); if (IsStrnlen) { const Expr *maxlenExpr = CE->getArg(1); - SVal maxlenVal = state->getSVal(maxlenExpr); + SVal maxlenVal = state->getSVal(maxlenExpr, LCtx); - const ProgramState *stateZeroSize, *stateNonZeroSize; + ProgramStateRef stateZeroSize, stateNonZeroSize; llvm::tie(stateZeroSize, stateNonZeroSize) = assumeZero(C, state, maxlenVal, maxlenExpr->getType()); @@ -1066,7 +1138,7 @@ void CStringChecker::evalstrLengthCommon(CheckerContext &C, const CallExpr *CE, // have to check the string itself. if (stateZeroSize) { SVal zero = C.getSValBuilder().makeZeroVal(CE->getType()); - stateZeroSize = stateZeroSize->BindExpr(CE, zero); + stateZeroSize = stateZeroSize->BindExpr(CE, LCtx, zero); C.addTransition(stateZeroSize); } @@ -1080,7 +1152,7 @@ void CStringChecker::evalstrLengthCommon(CheckerContext &C, const CallExpr *CE, // Check that the string argument is non-null. const Expr *Arg = CE->getArg(0); - SVal ArgVal = state->getSVal(Arg); + SVal ArgVal = state->getSVal(Arg, LCtx); state = checkNonNull(C, state, Arg, ArgVal); @@ -1104,13 +1176,13 @@ void CStringChecker::evalstrLengthCommon(CheckerContext &C, const CallExpr *CE, // It's a little unfortunate to be getting this again, // but it's not that expensive... const Expr *maxlenExpr = CE->getArg(1); - SVal maxlenVal = state->getSVal(maxlenExpr); + SVal maxlenVal = state->getSVal(maxlenExpr, LCtx); NonLoc *strLengthNL = dyn_cast<NonLoc>(&strLength); NonLoc *maxlenValNL = dyn_cast<NonLoc>(&maxlenVal); if (strLengthNL && maxlenValNL) { - const ProgramState *stateStringTooLong, *stateStringNotTooLong; + ProgramStateRef stateStringTooLong, stateStringNotTooLong; // Check if the strLength is greater than the maxlen. llvm::tie(stateStringTooLong, stateStringNotTooLong) = @@ -1135,7 +1207,7 @@ void CStringChecker::evalstrLengthCommon(CheckerContext &C, const CallExpr *CE, // All we know is the return value is the min of the string length // and the limit. This is better than nothing. unsigned Count = C.getCurrentBlockCount(); - result = C.getSValBuilder().getConjuredSymbolVal(NULL, CE, Count); + result = C.getSValBuilder().getConjuredSymbolVal(NULL, CE, LCtx, Count); NonLoc *resultNL = cast<NonLoc>(&result); if (strLengthNL) { @@ -1163,17 +1235,20 @@ void CStringChecker::evalstrLengthCommon(CheckerContext &C, const CallExpr *CE, // value, so it can be used in constraints, at least. if (result.isUnknown()) { unsigned Count = C.getCurrentBlockCount(); - result = C.getSValBuilder().getConjuredSymbolVal(NULL, CE, Count); + result = C.getSValBuilder().getConjuredSymbolVal(NULL, CE, LCtx, Count); } } // Bind the return value. assert(!result.isUnknown() && "Should have conjured a value by now"); - state = state->BindExpr(CE, result); + state = state->BindExpr(CE, LCtx, result); C.addTransition(state); } void CStringChecker::evalStrcpy(CheckerContext &C, const CallExpr *CE) const { + if (CE->getNumArgs() < 2) + return; + // char *strcpy(char *restrict dst, const char *restrict src); evalStrcpyCommon(C, CE, /* returnEnd = */ false, @@ -1182,6 +1257,9 @@ void CStringChecker::evalStrcpy(CheckerContext &C, const CallExpr *CE) const { } void CStringChecker::evalStrncpy(CheckerContext &C, const CallExpr *CE) const { + if (CE->getNumArgs() < 3) + return; + // char *strncpy(char *restrict dst, const char *restrict src, size_t n); evalStrcpyCommon(C, CE, /* returnEnd = */ false, @@ -1190,6 +1268,9 @@ void CStringChecker::evalStrncpy(CheckerContext &C, const CallExpr *CE) const { } void CStringChecker::evalStpcpy(CheckerContext &C, const CallExpr *CE) const { + if (CE->getNumArgs() < 2) + return; + // char *stpcpy(char *restrict dst, const char *restrict src); evalStrcpyCommon(C, CE, /* returnEnd = */ true, @@ -1198,6 +1279,9 @@ void CStringChecker::evalStpcpy(CheckerContext &C, const CallExpr *CE) const { } void CStringChecker::evalStrcat(CheckerContext &C, const CallExpr *CE) const { + if (CE->getNumArgs() < 2) + return; + //char *strcat(char *restrict s1, const char *restrict s2); evalStrcpyCommon(C, CE, /* returnEnd = */ false, @@ -1206,6 +1290,9 @@ void CStringChecker::evalStrcat(CheckerContext &C, const CallExpr *CE) const { } void CStringChecker::evalStrncat(CheckerContext &C, const CallExpr *CE) const { + if (CE->getNumArgs() < 3) + return; + //char *strncat(char *restrict s1, const char *restrict s2, size_t n); evalStrcpyCommon(C, CE, /* returnEnd = */ false, @@ -1217,11 +1304,12 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, bool returnEnd, bool isBounded, bool isAppending) const { CurrentFunctionDescription = "string copy function"; - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); + const LocationContext *LCtx = C.getLocationContext(); // Check that the destination is non-null. const Expr *Dst = CE->getArg(0); - SVal DstVal = state->getSVal(Dst); + SVal DstVal = state->getSVal(Dst, LCtx); state = checkNonNull(C, state, Dst, DstVal); if (!state) @@ -1229,7 +1317,7 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, // Check that the source is non-null. const Expr *srcExpr = CE->getArg(1); - SVal srcVal = state->getSVal(srcExpr); + SVal srcVal = state->getSVal(srcExpr, LCtx); state = checkNonNull(C, state, srcExpr, srcVal); if (!state) return; @@ -1256,7 +1344,7 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, if (isBounded) { // Get the max number of characters to copy. const Expr *lenExpr = CE->getArg(2); - SVal lenVal = state->getSVal(lenExpr); + SVal lenVal = state->getSVal(lenExpr, LCtx); // Protect against misdeclared strncpy(). lenVal = svalBuilder.evalCast(lenVal, sizeTy, lenExpr->getType()); @@ -1267,7 +1355,7 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, // If we know both values, we might be able to figure out how much // we're copying. if (strLengthNL && lenValNL) { - const ProgramState *stateSourceTooLong, *stateSourceNotTooLong; + ProgramStateRef stateSourceTooLong, stateSourceNotTooLong; // Check if the max number to copy is less than the length of the src. // If the bound is equal to the source length, strncpy won't null- @@ -1507,32 +1595,44 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, // overflow, we still need a result. Conjure a return value. if (returnEnd && Result.isUnknown()) { unsigned Count = C.getCurrentBlockCount(); - Result = svalBuilder.getConjuredSymbolVal(NULL, CE, Count); + Result = svalBuilder.getConjuredSymbolVal(NULL, CE, LCtx, Count); } // Set the return value. - state = state->BindExpr(CE, Result); + state = state->BindExpr(CE, LCtx, Result); C.addTransition(state); } void CStringChecker::evalStrcmp(CheckerContext &C, const CallExpr *CE) const { + if (CE->getNumArgs() < 2) + return; + //int strcmp(const char *s1, const char *s2); evalStrcmpCommon(C, CE, /* isBounded = */ false, /* ignoreCase = */ false); } void CStringChecker::evalStrncmp(CheckerContext &C, const CallExpr *CE) const { + if (CE->getNumArgs() < 3) + return; + //int strncmp(const char *s1, const char *s2, size_t n); evalStrcmpCommon(C, CE, /* isBounded = */ true, /* ignoreCase = */ false); } void CStringChecker::evalStrcasecmp(CheckerContext &C, const CallExpr *CE) const { + if (CE->getNumArgs() < 2) + return; + //int strcasecmp(const char *s1, const char *s2); evalStrcmpCommon(C, CE, /* isBounded = */ false, /* ignoreCase = */ true); } void CStringChecker::evalStrncasecmp(CheckerContext &C, const CallExpr *CE) const { + if (CE->getNumArgs() < 3) + return; + //int strncasecmp(const char *s1, const char *s2, size_t n); evalStrcmpCommon(C, CE, /* isBounded = */ true, /* ignoreCase = */ true); } @@ -1540,18 +1640,19 @@ void CStringChecker::evalStrncasecmp(CheckerContext &C, void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, bool isBounded, bool ignoreCase) const { CurrentFunctionDescription = "string comparison function"; - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); + const LocationContext *LCtx = C.getLocationContext(); // Check that the first string is non-null const Expr *s1 = CE->getArg(0); - SVal s1Val = state->getSVal(s1); + SVal s1Val = state->getSVal(s1, LCtx); state = checkNonNull(C, state, s1, s1Val); if (!state) return; // Check that the second string is non-null. const Expr *s2 = CE->getArg(1); - SVal s2Val = state->getSVal(s2); + SVal s2Val = state->getSVal(s2, LCtx); state = checkNonNull(C, state, s2, s2Val); if (!state) return; @@ -1575,13 +1676,14 @@ void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, // See if they are the same. SValBuilder &svalBuilder = C.getSValBuilder(); DefinedOrUnknownSVal SameBuf = svalBuilder.evalEQ(state, LV, RV); - const ProgramState *StSameBuf, *StNotSameBuf; + ProgramStateRef StSameBuf, StNotSameBuf; llvm::tie(StSameBuf, StNotSameBuf) = state->assume(SameBuf); // If the two arguments might be the same buffer, we know the result is 0, // and we only need to check one size. if (StSameBuf) { - StSameBuf = StSameBuf->BindExpr(CE, svalBuilder.makeZeroVal(CE->getType())); + StSameBuf = StSameBuf->BindExpr(CE, LCtx, + svalBuilder.makeZeroVal(CE->getType())); C.addTransition(StSameBuf); // If the two arguments are GUARANTEED to be the same, we're done! @@ -1607,7 +1709,7 @@ void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, if (isBounded) { // Get the max number of characters to compare. const Expr *lenExpr = CE->getArg(2); - SVal lenVal = state->getSVal(lenExpr); + SVal lenVal = state->getSVal(lenExpr, LCtx); // If the length is known, we can get the right substrings. if (const llvm::APSInt *len = svalBuilder.getKnownValue(state, lenVal)) { @@ -1644,15 +1746,15 @@ void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, // Build the SVal of the comparison and bind the return value. SVal resultVal = svalBuilder.makeIntVal(result, CE->getType()); - state = state->BindExpr(CE, resultVal); + state = state->BindExpr(CE, LCtx, resultVal); } } if (!canComputeResult) { // Conjure a symbolic value. It's the best we can do. unsigned Count = C.getCurrentBlockCount(); - SVal resultVal = svalBuilder.getConjuredSymbolVal(NULL, CE, Count); - state = state->BindExpr(CE, resultVal); + SVal resultVal = svalBuilder.getConjuredSymbolVal(NULL, CE, LCtx, Count); + state = state->BindExpr(CE, LCtx, resultVal); } // Record this as a possible path. @@ -1664,42 +1766,47 @@ void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, //===----------------------------------------------------------------------===// bool CStringChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { - // Get the callee. All the functions we care about are C functions - // with simple identifiers. - const ProgramState *state = C.getState(); - const Expr *Callee = CE->getCallee(); - const FunctionDecl *FD = state->getSVal(Callee).getAsFunctionDecl(); - - if (!FD) - return false; + const FunctionDecl *FDecl = C.getCalleeDecl(CE); - // Get the name of the callee. If it's a builtin, strip off the prefix. - IdentifierInfo *II = FD->getIdentifier(); - if (!II) // if no identifier, not a simple C function + if (!FDecl) return false; - StringRef Name = II->getName(); - if (Name.startswith("__builtin_")) - Name = Name.substr(10); - - FnCheck evalFunction = llvm::StringSwitch<FnCheck>(Name) - .Cases("memcpy", "__memcpy_chk", &CStringChecker::evalMemcpy) - .Cases("mempcpy", "__mempcpy_chk", &CStringChecker::evalMempcpy) - .Cases("memcmp", "bcmp", &CStringChecker::evalMemcmp) - .Cases("memmove", "__memmove_chk", &CStringChecker::evalMemmove) - .Cases("strcpy", "__strcpy_chk", &CStringChecker::evalStrcpy) - .Cases("strncpy", "__strncpy_chk", &CStringChecker::evalStrncpy) - .Cases("stpcpy", "__stpcpy_chk", &CStringChecker::evalStpcpy) - .Cases("strcat", "__strcat_chk", &CStringChecker::evalStrcat) - .Cases("strncat", "__strncat_chk", &CStringChecker::evalStrncat) - .Case("strlen", &CStringChecker::evalstrLength) - .Case("strnlen", &CStringChecker::evalstrnLength) - .Case("strcmp", &CStringChecker::evalStrcmp) - .Case("strncmp", &CStringChecker::evalStrncmp) - .Case("strcasecmp", &CStringChecker::evalStrcasecmp) - .Case("strncasecmp", &CStringChecker::evalStrncasecmp) - .Case("bcopy", &CStringChecker::evalBcopy) - .Default(NULL); + FnCheck evalFunction = 0; + if (C.isCLibraryFunction(FDecl, "memcpy")) + evalFunction = &CStringChecker::evalMemcpy; + else if (C.isCLibraryFunction(FDecl, "mempcpy")) + evalFunction = &CStringChecker::evalMempcpy; + else if (C.isCLibraryFunction(FDecl, "memcmp")) + evalFunction = &CStringChecker::evalMemcmp; + else if (C.isCLibraryFunction(FDecl, "memmove")) + evalFunction = &CStringChecker::evalMemmove; + else if (C.isCLibraryFunction(FDecl, "strcpy")) + evalFunction = &CStringChecker::evalStrcpy; + else if (C.isCLibraryFunction(FDecl, "strncpy")) + evalFunction = &CStringChecker::evalStrncpy; + else if (C.isCLibraryFunction(FDecl, "stpcpy")) + evalFunction = &CStringChecker::evalStpcpy; + else if (C.isCLibraryFunction(FDecl, "strcat")) + evalFunction = &CStringChecker::evalStrcat; + else if (C.isCLibraryFunction(FDecl, "strncat")) + evalFunction = &CStringChecker::evalStrncat; + else if (C.isCLibraryFunction(FDecl, "strlen")) + evalFunction = &CStringChecker::evalstrLength; + else if (C.isCLibraryFunction(FDecl, "strnlen")) + evalFunction = &CStringChecker::evalstrnLength; + else if (C.isCLibraryFunction(FDecl, "strcmp")) + evalFunction = &CStringChecker::evalStrcmp; + else if (C.isCLibraryFunction(FDecl, "strncmp")) + evalFunction = &CStringChecker::evalStrncmp; + else if (C.isCLibraryFunction(FDecl, "strcasecmp")) + evalFunction = &CStringChecker::evalStrcasecmp; + else if (C.isCLibraryFunction(FDecl, "strncasecmp")) + evalFunction = &CStringChecker::evalStrncasecmp; + else if (C.isCLibraryFunction(FDecl, "bcopy")) + evalFunction = &CStringChecker::evalBcopy; + else if (C.isCLibraryFunction(FDecl, "bcmp")) + evalFunction = &CStringChecker::evalMemcmp; + // If the callee isn't a string function, let another checker handle it. if (!evalFunction) return false; @@ -1710,12 +1817,22 @@ bool CStringChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { // Check and evaluate the call. (this->*evalFunction)(C, CE); + + // If the evaluate call resulted in no change, chain to the next eval call + // handler. + // Note, the custom CString evaluation calls assume that basic safety + // properties are held. However, if the user chooses to turn off some of these + // checks, we ignore the issues and leave the call evaluation to a generic + // handler. + if (!C.isDifferent()) + return false; + return true; } void CStringChecker::checkPreStmt(const DeclStmt *DS, CheckerContext &C) const { // Record string length for char a[] = "abc"; - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); for (DeclStmt::const_decl_iterator I = DS->decl_begin(), E = DS->decl_end(); I != E; ++I) { @@ -1733,12 +1850,12 @@ void CStringChecker::checkPreStmt(const DeclStmt *DS, CheckerContext &C) const { if (!isa<StringLiteral>(Init)) continue; - Loc VarLoc = state->getLValue(D, C.getPredecessor()->getLocationContext()); + Loc VarLoc = state->getLValue(D, C.getLocationContext()); const MemRegion *MR = VarLoc.getAsRegion(); if (!MR) continue; - SVal StrVal = state->getSVal(Init); + SVal StrVal = state->getSVal(Init, C.getLocationContext()); assert(StrVal.isValid() && "Initializer string is unknown or undefined"); DefinedOrUnknownSVal strLength = cast<DefinedOrUnknownSVal>(getCStringLength(C, state, Init, StrVal)); @@ -1749,16 +1866,17 @@ void CStringChecker::checkPreStmt(const DeclStmt *DS, CheckerContext &C) const { C.addTransition(state); } -bool CStringChecker::wantsRegionChangeUpdate(const ProgramState *state) const { +bool CStringChecker::wantsRegionChangeUpdate(ProgramStateRef state) const { CStringLength::EntryMap Entries = state->get<CStringLength>(); return !Entries.isEmpty(); } -const ProgramState * -CStringChecker::checkRegionChanges(const ProgramState *state, +ProgramStateRef +CStringChecker::checkRegionChanges(ProgramStateRef state, const StoreManager::InvalidatedSymbols *, ArrayRef<const MemRegion *> ExplicitRegions, - ArrayRef<const MemRegion *> Regions) const { + ArrayRef<const MemRegion *> Regions, + const CallOrObjCMessage *Call) const { CStringLength::EntryMap Entries = state->get<CStringLength>(); if (Entries.isEmpty()) return state; @@ -1806,7 +1924,7 @@ CStringChecker::checkRegionChanges(const ProgramState *state, return state->set<CStringLength>(Entries); } -void CStringChecker::checkLiveSymbols(const ProgramState *state, +void CStringChecker::checkLiveSymbols(ProgramStateRef state, SymbolReaper &SR) const { // Mark all symbols in our string length map as valid. CStringLength::EntryMap Entries = state->get<CStringLength>(); @@ -1815,8 +1933,8 @@ void CStringChecker::checkLiveSymbols(const ProgramState *state, I != E; ++I) { SVal Len = I.getData(); - for (SVal::symbol_iterator si = Len.symbol_begin(), se = Len.symbol_end(); - si != se; ++si) + for (SymExpr::symbol_iterator si = Len.symbol_begin(), + se = Len.symbol_end(); si != se; ++si) SR.markInUse(*si); } } @@ -1826,7 +1944,7 @@ void CStringChecker::checkDeadSymbols(SymbolReaper &SR, if (!SR.hasDeadSymbols()) return; - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); CStringLength::EntryMap Entries = state->get<CStringLength>(); if (Entries.isEmpty()) return; @@ -1842,9 +1960,22 @@ void CStringChecker::checkDeadSymbols(SymbolReaper &SR, } state = state->set<CStringLength>(Entries); - C.generateNode(state); + C.addTransition(state); } -void ento::registerCStringChecker(CheckerManager &mgr) { - mgr.registerChecker<CStringChecker>(); +#define REGISTER_CHECKER(name) \ +void ento::register##name(CheckerManager &mgr) {\ + static CStringChecker *TheChecker = 0; \ + if (TheChecker == 0) \ + TheChecker = mgr.registerChecker<CStringChecker>(); \ + TheChecker->Filter.Check##name = true; \ +} + +REGISTER_CHECKER(CStringNullArg) +REGISTER_CHECKER(CStringOutOfBounds) +REGISTER_CHECKER(CStringBufferOverlap) +REGISTER_CHECKER(CStringNotNullTerm) + +void ento::registerCStringCheckerBasic(CheckerManager &Mgr) { + registerCStringNullArg(Mgr); } diff --git a/lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp b/lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp new file mode 100644 index 0000000..befc935 --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp @@ -0,0 +1,191 @@ +//== CStringSyntaxChecker.cpp - CoreFoundation containers API *- C++ -*-==// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// An AST checker that looks for common pitfalls when using C string APIs. +// - Identifies erroneous patterns in the last argument to strncat - the number +// of bytes to copy. +// +//===----------------------------------------------------------------------===// +#include "ClangSACheckers.h" +#include "clang/Analysis/AnalysisContext.h" +#include "clang/AST/Expr.h" +#include "clang/AST/OperationKinds.h" +#include "clang/AST/StmtVisitor.h" +#include "clang/Basic/TargetInfo.h" +#include "clang/Basic/TypeTraits.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/raw_ostream.h" + +using namespace clang; +using namespace ento; + +namespace { +class WalkAST: public StmtVisitor<WalkAST> { + BugReporter &BR; + AnalysisDeclContext* AC; + ASTContext &ASTC; + + /// Check if two expressions refer to the same declaration. + inline bool sameDecl(const Expr *A1, const Expr *A2) { + if (const DeclRefExpr *D1 = dyn_cast<DeclRefExpr>(A1->IgnoreParenCasts())) + if (const DeclRefExpr *D2 = dyn_cast<DeclRefExpr>(A2->IgnoreParenCasts())) + return D1->getDecl() == D2->getDecl(); + return false; + } + + /// Check if the expression E is a sizeof(WithArg). + inline bool isSizeof(const Expr *E, const Expr *WithArg) { + if (const UnaryExprOrTypeTraitExpr *UE = + dyn_cast<UnaryExprOrTypeTraitExpr>(E)) + if (UE->getKind() == UETT_SizeOf) + return sameDecl(UE->getArgumentExpr(), WithArg); + return false; + } + + /// Check if the expression E is a strlen(WithArg). + inline bool isStrlen(const Expr *E, const Expr *WithArg) { + if (const CallExpr *CE = dyn_cast<CallExpr>(E)) { + const FunctionDecl *FD = CE->getDirectCallee(); + if (!FD) + return false; + return (CheckerContext::isCLibraryFunction(FD, "strlen", ASTC) + && sameDecl(CE->getArg(0), WithArg)); + } + return false; + } + + /// Check if the expression is an integer literal with value 1. + inline bool isOne(const Expr *E) { + if (const IntegerLiteral *IL = dyn_cast<IntegerLiteral>(E)) + return (IL->getValue().isIntN(1)); + return false; + } + + inline StringRef getPrintableName(const Expr *E) { + if (const DeclRefExpr *D = dyn_cast<DeclRefExpr>(E->IgnoreParenCasts())) + return D->getDecl()->getName(); + return StringRef(); + } + + /// Identify erroneous patterns in the last argument to strncat - the number + /// of bytes to copy. + bool containsBadStrncatPattern(const CallExpr *CE); + +public: + WalkAST(BugReporter &br, AnalysisDeclContext* ac) : + BR(br), AC(ac), ASTC(AC->getASTContext()) { + } + + // Statement visitor methods. + void VisitChildren(Stmt *S); + void VisitStmt(Stmt *S) { + VisitChildren(S); + } + void VisitCallExpr(CallExpr *CE); +}; +} // end anonymous namespace + +// The correct size argument should look like following: +// strncat(dst, src, sizeof(dst) - strlen(dest) - 1); +// We look for the following anti-patterns: +// - strncat(dst, src, sizeof(dst) - strlen(dst)); +// - strncat(dst, src, sizeof(dst) - 1); +// - strncat(dst, src, sizeof(dst)); +bool WalkAST::containsBadStrncatPattern(const CallExpr *CE) { + const Expr *DstArg = CE->getArg(0); + const Expr *SrcArg = CE->getArg(1); + const Expr *LenArg = CE->getArg(2); + + // Identify wrong size expressions, which are commonly used instead. + if (const BinaryOperator *BE = + dyn_cast<BinaryOperator>(LenArg->IgnoreParenCasts())) { + // - sizeof(dst) - strlen(dst) + if (BE->getOpcode() == BO_Sub) { + const Expr *L = BE->getLHS(); + const Expr *R = BE->getRHS(); + if (isSizeof(L, DstArg) && isStrlen(R, DstArg)) + return true; + + // - sizeof(dst) - 1 + if (isSizeof(L, DstArg) && isOne(R->IgnoreParenCasts())) + return true; + } + } + // - sizeof(dst) + if (isSizeof(LenArg, DstArg)) + return true; + + // - sizeof(src) + if (isSizeof(LenArg, SrcArg)) + return true; + return false; +} + +void WalkAST::VisitCallExpr(CallExpr *CE) { + const FunctionDecl *FD = CE->getDirectCallee(); + if (!FD) + return; + + if (CheckerContext::isCLibraryFunction(FD, "strncat", ASTC)) { + if (containsBadStrncatPattern(CE)) { + const Expr *DstArg = CE->getArg(0); + const Expr *LenArg = CE->getArg(2); + SourceRange R = LenArg->getSourceRange(); + PathDiagnosticLocation Loc = + PathDiagnosticLocation::createBegin(LenArg, BR.getSourceManager(), AC); + + StringRef DstName = getPrintableName(DstArg); + + SmallString<256> S; + llvm::raw_svector_ostream os(S); + os << "Potential buffer overflow. "; + if (!DstName.empty()) { + os << "Replace with 'sizeof(" << DstName << ") " + "- strlen(" << DstName <<") - 1'"; + os << " or u"; + } else + os << "U"; + os << "se a safer 'strlcat' API"; + + BR.EmitBasicReport(FD, "Anti-pattern in the argument", "C String API", + os.str(), Loc, &R, 1); + } + } + + // Recurse and check children. + VisitChildren(CE); +} + +void WalkAST::VisitChildren(Stmt *S) { + for (Stmt::child_iterator I = S->child_begin(), E = S->child_end(); I != E; + ++I) + if (Stmt *child = *I) + Visit(child); +} + +namespace { +class CStringSyntaxChecker: public Checker<check::ASTCodeBody> { +public: + + void checkASTCodeBody(const Decl *D, AnalysisManager& Mgr, + BugReporter &BR) const { + WalkAST walker(BR, Mgr.getAnalysisDeclContext(D)); + walker.Visit(D->getBody()); + } +}; +} + +void ento::registerCStringSyntaxChecker(CheckerManager &mgr) { + mgr.registerChecker<CStringSyntaxChecker>(); +} + diff --git a/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp b/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp index 4db6ac0..f601431 100644 --- a/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp @@ -20,6 +20,7 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/AST/ParentMap.h" #include "clang/Basic/TargetInfo.h" +#include "llvm/ADT/SmallString.h" using namespace clang; using namespace ento; @@ -27,12 +28,13 @@ using namespace ento; namespace { class CallAndMessageChecker : public Checker< check::PreStmt<CallExpr>, check::PreObjCMessage > { - mutable llvm::OwningPtr<BugType> BT_call_null; - mutable llvm::OwningPtr<BugType> BT_call_undef; - mutable llvm::OwningPtr<BugType> BT_call_arg; - mutable llvm::OwningPtr<BugType> BT_msg_undef; - mutable llvm::OwningPtr<BugType> BT_msg_arg; - mutable llvm::OwningPtr<BugType> BT_msg_ret; + mutable OwningPtr<BugType> BT_call_null; + mutable OwningPtr<BugType> BT_call_undef; + mutable OwningPtr<BugType> BT_call_arg; + mutable OwningPtr<BugType> BT_msg_undef; + mutable OwningPtr<BugType> BT_objc_prop_undef; + mutable OwningPtr<BugType> BT_msg_arg; + mutable OwningPtr<BugType> BT_msg_ret; public: void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; @@ -40,19 +42,22 @@ public: private: static void PreVisitProcessArgs(CheckerContext &C,CallOrObjCMessage callOrMsg, - const char *BT_desc, llvm::OwningPtr<BugType> &BT); + const char *BT_desc, OwningPtr<BugType> &BT); static bool PreVisitProcessArg(CheckerContext &C, SVal V,SourceRange argRange, - const Expr *argEx, const char *BT_desc, llvm::OwningPtr<BugType> &BT); + const Expr *argEx, + const bool checkUninitFields, + const char *BT_desc, + OwningPtr<BugType> &BT); static void EmitBadCall(BugType *BT, CheckerContext &C, const CallExpr *CE); void emitNilReceiverBug(CheckerContext &C, const ObjCMessage &msg, ExplodedNode *N) const; void HandleNilReceiver(CheckerContext &C, - const ProgramState *state, + ProgramStateRef state, ObjCMessage msg) const; - static void LazyInit_BT(const char *desc, llvm::OwningPtr<BugType> &BT) { + static void LazyInit_BT(const char *desc, OwningPtr<BugType> &BT) { if (!BT) BT.reset(new BuiltinBug(desc)); } @@ -67,17 +72,27 @@ void CallAndMessageChecker::EmitBadCall(BugType *BT, CheckerContext &C, BugReport *R = new BugReport(*BT, BT->getName(), N); R->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, - bugreporter::GetCalleeExpr(N))); + bugreporter::GetCalleeExpr(N), R)); C.EmitReport(R); } void CallAndMessageChecker::PreVisitProcessArgs(CheckerContext &C, CallOrObjCMessage callOrMsg, const char *BT_desc, - llvm::OwningPtr<BugType> &BT) { + OwningPtr<BugType> &BT) { + // Don't check for uninitialized field values in arguments if the + // caller has a body that is available and we have the chance to inline it. + // This is a hack, but is a reasonable compromise betweens sometimes warning + // and sometimes not depending on if we decide to inline a function. + const Decl *D = callOrMsg.getDecl(); + const bool checkUninitFields = + !(C.getAnalysisManager().shouldInlineCall() && + (D && D->getBody())); + for (unsigned i = 0, e = callOrMsg.getNumArgs(); i != e; ++i) if (PreVisitProcessArg(C, callOrMsg.getArgSVal(i), callOrMsg.getArgSourceRange(i), callOrMsg.getArg(i), + checkUninitFields, BT_desc, BT)) return; } @@ -85,9 +100,9 @@ void CallAndMessageChecker::PreVisitProcessArgs(CheckerContext &C, bool CallAndMessageChecker::PreVisitProcessArg(CheckerContext &C, SVal V, SourceRange argRange, const Expr *argEx, + const bool checkUninitFields, const char *BT_desc, - llvm::OwningPtr<BugType> &BT) { - + OwningPtr<BugType> &BT) { if (V.isUndef()) { if (ExplodedNode *N = C.generateSink()) { LazyInit_BT(BT_desc, BT); @@ -96,12 +111,16 @@ bool CallAndMessageChecker::PreVisitProcessArg(CheckerContext &C, BugReport *R = new BugReport(*BT, BT->getName(), N); R->addRange(argRange); if (argEx) - R->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, argEx)); + R->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, argEx, + R)); C.EmitReport(R); } return true; } + if (!checkUninitFields) + return false; + if (const nonloc::LazyCompoundVal *LV = dyn_cast<nonloc::LazyCompoundVal>(&V)) { @@ -133,7 +152,7 @@ bool CallAndMessageChecker::PreVisitProcessArg(CheckerContext &C, return true; } else { - const SVal &V = StoreMgr.Retrieve(store, loc::MemRegionVal(FR)); + const SVal &V = StoreMgr.getBinding(store, loc::MemRegionVal(FR)); if (V.isUndef()) return true; } @@ -154,7 +173,7 @@ bool CallAndMessageChecker::PreVisitProcessArg(CheckerContext &C, if (F.Find(D->getRegion())) { if (ExplodedNode *N = C.generateSink()) { LazyInit_BT(BT_desc, BT); - llvm::SmallString<512> Str; + SmallString<512> Str; llvm::raw_svector_ostream os(Str); os << "Passed-by-value struct argument contains uninitialized data"; @@ -193,7 +212,8 @@ void CallAndMessageChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const{ const Expr *Callee = CE->getCallee()->IgnoreParens(); - SVal L = C.getState()->getSVal(Callee); + const LocationContext *LCtx = C.getLocationContext(); + SVal L = C.getState()->getSVal(Callee, LCtx); if (L.isUndef()) { if (!BT_call_undef) @@ -210,7 +230,7 @@ void CallAndMessageChecker::checkPreStmt(const CallExpr *CE, EmitBadCall(BT_call_null.get(), C, CE); } - PreVisitProcessArgs(C, CallOrObjCMessage(CE, C.getState()), + PreVisitProcessArgs(C, CallOrObjCMessage(CE, C.getState(), LCtx), "Function call argument is an uninitialized value", BT_call_arg); } @@ -218,21 +238,33 @@ void CallAndMessageChecker::checkPreStmt(const CallExpr *CE, void CallAndMessageChecker::checkPreObjCMessage(ObjCMessage msg, CheckerContext &C) const { - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); + const LocationContext *LCtx = C.getLocationContext(); // FIXME: Handle 'super'? if (const Expr *receiver = msg.getInstanceReceiver()) { - SVal recVal = state->getSVal(receiver); + SVal recVal = state->getSVal(receiver, LCtx); if (recVal.isUndef()) { if (ExplodedNode *N = C.generateSink()) { - if (!BT_msg_undef) - BT_msg_undef.reset(new BuiltinBug("Receiver in message expression is " - "an uninitialized value")); + BugType *BT = 0; + if (msg.isPureMessageExpr()) { + if (!BT_msg_undef) + BT_msg_undef.reset(new BuiltinBug("Receiver in message expression " + "is an uninitialized value")); + BT = BT_msg_undef.get(); + } + else { + if (!BT_objc_prop_undef) + BT_objc_prop_undef.reset(new BuiltinBug("Property access on an " + "uninitialized object pointer")); + BT = BT_objc_prop_undef.get(); + } BugReport *R = - new BugReport(*BT_msg_undef, BT_msg_undef->getName(), N); + new BugReport(*BT, BT->getName(), N); R->addRange(receiver->getSourceRange()); R->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, - receiver)); + receiver, + R)); C.EmitReport(R); } return; @@ -240,7 +272,7 @@ void CallAndMessageChecker::checkPreObjCMessage(ObjCMessage msg, // Bifurcate the state into nil and non-nil ones. DefinedOrUnknownSVal receiverVal = cast<DefinedOrUnknownSVal>(recVal); - const ProgramState *notNilState, *nilState; + ProgramStateRef notNilState, nilState; llvm::tie(notNilState, nilState) = state->assume(receiverVal); // Handle receiver must be nil. @@ -255,7 +287,8 @@ void CallAndMessageChecker::checkPreObjCMessage(ObjCMessage msg, "Argument for property setter is an uninitialized value" : "Argument in message expression is an uninitialized value"; // Check for any arguments that are uninitialized/undefined. - PreVisitProcessArgs(C, CallOrObjCMessage(msg, state), bugDesc, BT_msg_arg); + PreVisitProcessArgs(C, CallOrObjCMessage(msg, state, LCtx), + bugDesc, BT_msg_arg); } void CallAndMessageChecker::emitNilReceiverBug(CheckerContext &C, @@ -267,7 +300,7 @@ void CallAndMessageChecker::emitNilReceiverBug(CheckerContext &C, new BuiltinBug("Receiver in message expression is " "'nil' and returns a garbage value")); - llvm::SmallString<200> buf; + SmallString<200> buf; llvm::raw_svector_ostream os(buf); os << "The receiver of message '" << msg.getSelector().getAsString() << "' is nil and returns a value of type '" @@ -277,48 +310,39 @@ void CallAndMessageChecker::emitNilReceiverBug(CheckerContext &C, if (const Expr *receiver = msg.getInstanceReceiver()) { report->addRange(receiver->getSourceRange()); report->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, - receiver)); + receiver, + report)); } C.EmitReport(report); } static bool supportsNilWithFloatRet(const llvm::Triple &triple) { - return triple.getVendor() == llvm::Triple::Apple && - (!triple.isMacOSXVersionLT(10,5) || - triple.getArch() == llvm::Triple::arm || - triple.getArch() == llvm::Triple::thumb); + return (triple.getVendor() == llvm::Triple::Apple && + (triple.getOS() == llvm::Triple::IOS || + !triple.isMacOSXVersionLT(10,5))); } void CallAndMessageChecker::HandleNilReceiver(CheckerContext &C, - const ProgramState *state, + ProgramStateRef state, ObjCMessage msg) const { ASTContext &Ctx = C.getASTContext(); // Check the return type of the message expression. A message to nil will // return different values depending on the return type and the architecture. QualType RetTy = msg.getType(Ctx); - CanQualType CanRetTy = Ctx.getCanonicalType(RetTy); + const LocationContext *LCtx = C.getLocationContext(); if (CanRetTy->isStructureOrClassType()) { - // FIXME: At some point we shouldn't rely on isConsumedExpr(), but instead - // have the "use of undefined value" be smarter about where the - // undefined value came from. - if (C.getPredecessor()->getParentMap().isConsumedExpr(msg.getOriginExpr())){ - if (ExplodedNode *N = C.generateSink(state)) - emitNilReceiverBug(C, msg, N); - return; - } - - // The result is not consumed by a surrounding expression. Just propagate - // the current state. - C.addTransition(state); + // Structure returns are safe since the compiler zeroes them out. + SVal V = C.getSValBuilder().makeZeroVal(msg.getType(Ctx)); + C.addTransition(state->BindExpr(msg.getMessageExpr(), LCtx, V)); return; } - // Other cases: check if the return type is smaller than void*. - if (CanRetTy != Ctx.VoidTy && - C.getPredecessor()->getParentMap().isConsumedExpr(msg.getOriginExpr())) { + // Other cases: check if sizeof(return type) > sizeof(void*) + if (CanRetTy != Ctx.VoidTy && C.getLocationContext()->getParentMap() + .isConsumedExpr(msg.getMessageExpr())) { // Compute: sizeof(void *) and sizeof(return type) const uint64_t voidPtrSize = Ctx.getTypeSize(Ctx.VoidPtrTy); const uint64_t returnTypeSize = Ctx.getTypeSize(CanRetTy); @@ -349,7 +373,7 @@ void CallAndMessageChecker::HandleNilReceiver(CheckerContext &C, // of this case unless we have *a lot* more knowledge. // SVal V = C.getSValBuilder().makeZeroVal(msg.getType(Ctx)); - C.generateNode(state->BindExpr(msg.getOriginExpr(), V)); + C.addTransition(state->BindExpr(msg.getMessageExpr(), LCtx, V)); return; } diff --git a/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp b/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp index 84a9e6b..2e184fb 100644 --- a/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp @@ -23,7 +23,7 @@ using namespace ento; namespace { class CastSizeChecker : public Checker< check::PreStmt<CastExpr> > { - mutable llvm::OwningPtr<BuiltinBug> BT; + mutable OwningPtr<BuiltinBug> BT; public: void checkPreStmt(const CastExpr *CE, CheckerContext &C) const; }; @@ -44,8 +44,8 @@ void CastSizeChecker::checkPreStmt(const CastExpr *CE,CheckerContext &C) const { if (ToPointeeTy->isIncompleteType()) return; - const ProgramState *state = C.getState(); - const MemRegion *R = state->getSVal(E).getAsRegion(); + ProgramStateRef state = C.getState(); + const MemRegion *R = state->getSVal(E, C.getLocationContext()).getAsRegion(); if (R == 0) return; diff --git a/lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp b/lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp index c855210..1407638 100644 --- a/lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp @@ -24,7 +24,7 @@ using namespace ento; namespace { class CastToStructChecker : public Checker< check::PreStmt<CastExpr> > { - mutable llvm::OwningPtr<BuiltinBug> BT; + mutable OwningPtr<BuiltinBug> BT; public: void checkPreStmt(const CastExpr *CE, CheckerContext &C) const; @@ -56,7 +56,7 @@ void CastToStructChecker::checkPreStmt(const CastExpr *CE, // Now the cast-to-type is struct pointer, the original type is not void*. if (!OrigPointeeTy->isRecordType()) { - if (ExplodedNode *N = C.generateNode()) { + if (ExplodedNode *N = C.addTransition()) { if (!BT) BT.reset(new BuiltinBug("Cast from non-struct type to struct type", "Casting a non-structure type to a structure type " diff --git a/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp b/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp index c325bb1..133204a 100644 --- a/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp +++ b/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp @@ -177,9 +177,10 @@ static void checkObjCDealloc(const ObjCImplementationDecl *D, std::string buf; llvm::raw_string_ostream os(buf); - os << "Objective-C class '" << D << "' lacks a 'dealloc' instance method"; + os << "Objective-C class '" << *D << "' lacks a 'dealloc' instance method"; - BR.EmitBasicReport(name, os.str(), DLoc); + BR.EmitBasicReport(D, name, categories::CoreFoundationObjectiveC, + os.str(), DLoc); return; } @@ -192,11 +193,12 @@ static void checkObjCDealloc(const ObjCImplementationDecl *D, std::string buf; llvm::raw_string_ostream os(buf); - os << "The 'dealloc' instance method in Objective-C class '" << D + os << "The 'dealloc' instance method in Objective-C class '" << *D << "' does not send a 'dealloc' message to its super class" " (missing [super dealloc])"; - BR.EmitBasicReport(name, os.str(), DLoc); + BR.EmitBasicReport(MD, name, categories::CoreFoundationObjectiveC, + os.str(), DLoc); return; } @@ -236,9 +238,7 @@ static void checkObjCDealloc(const ObjCImplementationDecl *D, bool requiresRelease = PD->getSetterKind() != ObjCPropertyDecl::Assign; if (scan_ivar_release(MD->getBody(), ID, PD, RS, SelfII, Ctx) != requiresRelease) { - const char *name; - const char* category = "Memory (Core Foundation/Objective-C)"; - + const char *name = 0; std::string buf; llvm::raw_string_ostream os(buf); @@ -263,7 +263,8 @@ static void checkObjCDealloc(const ObjCImplementationDecl *D, PathDiagnosticLocation SDLoc = PathDiagnosticLocation::createBegin((*I), BR.getSourceManager()); - BR.EmitBasicReport(name, category, os.str(), SDLoc); + BR.EmitBasicReport(MD, name, categories::CoreFoundationObjectiveC, + os.str(), SDLoc); } } } @@ -278,9 +279,9 @@ class ObjCDeallocChecker : public Checker< public: void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager& mgr, BugReporter &BR) const { - if (mgr.getLangOptions().getGC() == LangOptions::GCOnly) + if (mgr.getLangOpts().getGC() == LangOptions::GCOnly) return; - checkObjCDealloc(cast<ObjCImplementationDecl>(D), mgr.getLangOptions(), BR); + checkObjCDealloc(cast<ObjCImplementationDecl>(D), mgr.getLangOpts(), BR); } }; } diff --git a/lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp b/lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp index c076c1e3..6df47b1 100644 --- a/lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp +++ b/lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp @@ -70,7 +70,9 @@ static void CompareReturnTypes(const ObjCMethodDecl *MethDerived, PathDiagnosticLocation::createBegin(MethDerived, BR.getSourceManager()); - BR.EmitBasicReport("Incompatible instance method return type", + BR.EmitBasicReport(MethDerived, + "Incompatible instance method return type", + categories::CoreFoundationObjectiveC, os.str(), MethDLoc); } } diff --git a/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp b/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp index bf7ba18..dde9071 100644 --- a/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp +++ b/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp @@ -18,6 +18,7 @@ #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" +#include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Support/raw_ostream.h" @@ -34,18 +35,40 @@ static bool isArc4RandomAvailable(const ASTContext &Ctx) { } namespace { +struct DefaultBool { + bool val; + DefaultBool() : val(false) {} + operator bool() const { return val; } + DefaultBool &operator=(bool b) { val = b; return *this; } +}; + +struct ChecksFilter { + DefaultBool check_gets; + DefaultBool check_getpw; + DefaultBool check_mktemp; + DefaultBool check_mkstemp; + DefaultBool check_strcpy; + DefaultBool check_rand; + DefaultBool check_vfork; + DefaultBool check_FloatLoopCounter; + DefaultBool check_UncheckedReturn; +}; + class WalkAST : public StmtVisitor<WalkAST> { BugReporter &BR; - AnalysisContext* AC; + AnalysisDeclContext* AC; enum { num_setids = 6 }; IdentifierInfo *II_setid[num_setids]; const bool CheckRand; + const ChecksFilter &filter; public: - WalkAST(BugReporter &br, AnalysisContext* ac) + WalkAST(BugReporter &br, AnalysisDeclContext* ac, + const ChecksFilter &f) : BR(br), AC(ac), II_setid(), - CheckRand(isArc4RandomAvailable(BR.getContext())) {} + CheckRand(isArc4RandomAvailable(BR.getContext())), + filter(f) {} // Statement visitor methods. void VisitCallExpr(CallExpr *CE); @@ -66,6 +89,7 @@ public: void checkCall_gets(const CallExpr *CE, const FunctionDecl *FD); void checkCall_getpw(const CallExpr *CE, const FunctionDecl *FD); void checkCall_mktemp(const CallExpr *CE, const FunctionDecl *FD); + void checkCall_mkstemp(const CallExpr *CE, const FunctionDecl *FD); void checkCall_strcpy(const CallExpr *CE, const FunctionDecl *FD); void checkCall_strcat(const CallExpr *CE, const FunctionDecl *FD); void checkCall_rand(const CallExpr *CE, const FunctionDecl *FD); @@ -105,6 +129,9 @@ void WalkAST::VisitCallExpr(CallExpr *CE) { .Case("gets", &WalkAST::checkCall_gets) .Case("getpw", &WalkAST::checkCall_getpw) .Case("mktemp", &WalkAST::checkCall_mktemp) + .Case("mkstemp", &WalkAST::checkCall_mkstemp) + .Case("mkdtemp", &WalkAST::checkCall_mkstemp) + .Case("mkstemps", &WalkAST::checkCall_mkstemp) .Cases("strcpy", "__strcpy_chk", &WalkAST::checkCall_strcpy) .Cases("strcat", "__strcat_chk", &WalkAST::checkCall_strcat) .Case("drand48", &WalkAST::checkCall_rand) @@ -186,6 +213,9 @@ getIncrementedVar(const Expr *expr, const VarDecl *x, const VarDecl *y) { /// CERT: FLP30-C, FLP30-CPP. /// void WalkAST::checkLoopConditionForFloat(const ForStmt *FS) { + if (!filter.check_FloatLoopCounter) + return; + // Does the loop have a condition? const Expr *condition = FS->getCond(); @@ -242,7 +272,7 @@ void WalkAST::checkLoopConditionForFloat(const ForStmt *FS) { const DeclRefExpr *drCond = vdLHS == drInc->getDecl() ? drLHS : drRHS; SmallVector<SourceRange, 2> ranges; - llvm::SmallString<256> sbuf; + SmallString<256> sbuf; llvm::raw_svector_ostream os(sbuf); os << "Variable '" << drCond->getDecl()->getName() @@ -256,7 +286,8 @@ void WalkAST::checkLoopConditionForFloat(const ForStmt *FS) { PathDiagnosticLocation FSLoc = PathDiagnosticLocation::createBegin(FS, BR.getSourceManager(), AC); - BR.EmitBasicReport(bugType, "Security", os.str(), + BR.EmitBasicReport(AC->getDecl(), + bugType, "Security", os.str(), FSLoc, ranges.data(), ranges.size()); } @@ -268,6 +299,9 @@ void WalkAST::checkLoopConditionForFloat(const ForStmt *FS) { //===----------------------------------------------------------------------===// void WalkAST::checkCall_gets(const CallExpr *CE, const FunctionDecl *FD) { + if (!filter.check_gets) + return; + const FunctionProtoType *FPT = dyn_cast<FunctionProtoType>(FD->getType().IgnoreParens()); if (!FPT) @@ -289,7 +323,8 @@ void WalkAST::checkCall_gets(const CallExpr *CE, const FunctionDecl *FD) { SourceRange R = CE->getCallee()->getSourceRange(); PathDiagnosticLocation CELoc = PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC); - BR.EmitBasicReport("Potential buffer overflow in call to 'gets'", + BR.EmitBasicReport(AC->getDecl(), + "Potential buffer overflow in call to 'gets'", "Security", "Call to function 'gets' is extremely insecure as it can " "always result in a buffer overflow", @@ -302,6 +337,9 @@ void WalkAST::checkCall_gets(const CallExpr *CE, const FunctionDecl *FD) { //===----------------------------------------------------------------------===// void WalkAST::checkCall_getpw(const CallExpr *CE, const FunctionDecl *FD) { + if (!filter.check_getpw) + return; + const FunctionProtoType *FPT = dyn_cast<FunctionProtoType>(FD->getType().IgnoreParens()); if (!FPT) @@ -327,7 +365,8 @@ void WalkAST::checkCall_getpw(const CallExpr *CE, const FunctionDecl *FD) { SourceRange R = CE->getCallee()->getSourceRange(); PathDiagnosticLocation CELoc = PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC); - BR.EmitBasicReport("Potential buffer overflow in call to 'getpw'", + BR.EmitBasicReport(AC->getDecl(), + "Potential buffer overflow in call to 'getpw'", "Security", "The getpw() function is dangerous as it may overflow the " "provided buffer. It is obsoleted by getpwuid().", @@ -335,11 +374,18 @@ void WalkAST::checkCall_getpw(const CallExpr *CE, const FunctionDecl *FD) { } //===----------------------------------------------------------------------===// -// Check: Any use of 'mktemp' is insecure.It is obsoleted by mkstemp(). +// Check: Any use of 'mktemp' is insecure. It is obsoleted by mkstemp(). // CWE-377: Insecure Temporary File //===----------------------------------------------------------------------===// void WalkAST::checkCall_mktemp(const CallExpr *CE, const FunctionDecl *FD) { + if (!filter.check_mktemp) { + // Fall back to the security check of looking for enough 'X's in the + // format string, since that is a less severe warning. + checkCall_mkstemp(CE, FD); + return; + } + const FunctionProtoType *FPT = dyn_cast<FunctionProtoType>(FD->getType().IgnoreParens()); if(!FPT) @@ -362,13 +408,98 @@ void WalkAST::checkCall_mktemp(const CallExpr *CE, const FunctionDecl *FD) { SourceRange R = CE->getCallee()->getSourceRange(); PathDiagnosticLocation CELoc = PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC); - BR.EmitBasicReport("Potential insecure temporary file in call 'mktemp'", + BR.EmitBasicReport(AC->getDecl(), + "Potential insecure temporary file in call 'mktemp'", "Security", "Call to function 'mktemp' is insecure as it always " - "creates or uses insecure temporary file. Use 'mkstemp' instead", + "creates or uses insecure temporary file. Use 'mkstemp' " + "instead", CELoc, &R, 1); } + +//===----------------------------------------------------------------------===// +// Check: Use of 'mkstemp', 'mktemp', 'mkdtemp' should contain at least 6 X's. +//===----------------------------------------------------------------------===// + +void WalkAST::checkCall_mkstemp(const CallExpr *CE, const FunctionDecl *FD) { + if (!filter.check_mkstemp) + return; + + StringRef Name = FD->getIdentifier()->getName(); + std::pair<signed, signed> ArgSuffix = + llvm::StringSwitch<std::pair<signed, signed> >(Name) + .Case("mktemp", std::make_pair(0,-1)) + .Case("mkstemp", std::make_pair(0,-1)) + .Case("mkdtemp", std::make_pair(0,-1)) + .Case("mkstemps", std::make_pair(0,1)) + .Default(std::make_pair(-1, -1)); + + assert(ArgSuffix.first >= 0 && "Unsupported function"); + + // Check if the number of arguments is consistent with out expectations. + unsigned numArgs = CE->getNumArgs(); + if ((signed) numArgs <= ArgSuffix.first) + return; + + const StringLiteral *strArg = + dyn_cast<StringLiteral>(CE->getArg((unsigned)ArgSuffix.first) + ->IgnoreParenImpCasts()); + + // Currently we only handle string literals. It is possible to do better, + // either by looking at references to const variables, or by doing real + // flow analysis. + if (!strArg || strArg->getCharByteWidth() != 1) + return; + + // Count the number of X's, taking into account a possible cutoff suffix. + StringRef str = strArg->getString(); + unsigned numX = 0; + unsigned n = str.size(); + + // Take into account the suffix. + unsigned suffix = 0; + if (ArgSuffix.second >= 0) { + const Expr *suffixEx = CE->getArg((unsigned)ArgSuffix.second); + llvm::APSInt Result; + if (!suffixEx->EvaluateAsInt(Result, BR.getContext())) + return; + // FIXME: Issue a warning. + if (Result.isNegative()) + return; + suffix = (unsigned) Result.getZExtValue(); + n = (n > suffix) ? n - suffix : 0; + } + + for (unsigned i = 0; i < n; ++i) + if (str[i] == 'X') ++numX; + + if (numX >= 6) + return; + + // Issue a warning. + SourceRange R = strArg->getSourceRange(); + PathDiagnosticLocation CELoc = + PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC); + SmallString<512> buf; + llvm::raw_svector_ostream out(buf); + out << "Call to '" << Name << "' should have at least 6 'X's in the" + " format string to be secure (" << numX << " 'X'"; + if (numX != 1) + out << 's'; + out << " seen"; + if (suffix) { + out << ", " << suffix << " character"; + if (suffix > 1) + out << 's'; + out << " used as a suffix"; + } + out << ')'; + BR.EmitBasicReport(AC->getDecl(), + "Insecure temporary file creation", "Security", + out.str(), CELoc, &R, 1); +} + //===----------------------------------------------------------------------===// // Check: Any use of 'strcpy' is insecure. // @@ -376,6 +507,9 @@ void WalkAST::checkCall_mktemp(const CallExpr *CE, const FunctionDecl *FD) { // the Bounds of a Memory Buffer //===----------------------------------------------------------------------===// void WalkAST::checkCall_strcpy(const CallExpr *CE, const FunctionDecl *FD) { + if (!filter.check_strcpy) + return; + if (!checkCall_strCommon(CE, FD)) return; @@ -383,13 +517,14 @@ void WalkAST::checkCall_strcpy(const CallExpr *CE, const FunctionDecl *FD) { SourceRange R = CE->getCallee()->getSourceRange(); PathDiagnosticLocation CELoc = PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC); - BR.EmitBasicReport("Potential insecure memory buffer bounds restriction in " + BR.EmitBasicReport(AC->getDecl(), + "Potential insecure memory buffer bounds restriction in " "call 'strcpy'", "Security", "Call to function 'strcpy' is insecure as it does not " - "provide bounding of the memory buffer. Replace " - "unbounded copy functions with analogous functions that " - "support length arguments such as 'strncpy'. CWE-119.", + "provide bounding of the memory buffer. Replace " + "unbounded copy functions with analogous functions that " + "support length arguments such as 'strlcpy'. CWE-119.", CELoc, &R, 1); } @@ -400,6 +535,9 @@ void WalkAST::checkCall_strcpy(const CallExpr *CE, const FunctionDecl *FD) { // the Bounds of a Memory Buffer //===----------------------------------------------------------------------===// void WalkAST::checkCall_strcat(const CallExpr *CE, const FunctionDecl *FD) { + if (!filter.check_strcpy) + return; + if (!checkCall_strCommon(CE, FD)) return; @@ -407,13 +545,14 @@ void WalkAST::checkCall_strcat(const CallExpr *CE, const FunctionDecl *FD) { SourceRange R = CE->getCallee()->getSourceRange(); PathDiagnosticLocation CELoc = PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC); - BR.EmitBasicReport("Potential insecure memory buffer bounds restriction in " - "call 'strcat'", - "Security", - "Call to function 'strcat' is insecure as it does not " - "provide bounding of the memory buffer. Replace " - "unbounded copy functions with analogous functions that " - "support length arguments such as 'strncat'. CWE-119.", + BR.EmitBasicReport(AC->getDecl(), + "Potential insecure memory buffer bounds restriction in " + "call 'strcat'", + "Security", + "Call to function 'strcat' is insecure as it does not " + "provide bounding of the memory buffer. Replace " + "unbounded copy functions with analogous functions that " + "support length arguments such as 'strlcat'. CWE-119.", CELoc, &R, 1); } @@ -453,7 +592,7 @@ bool WalkAST::checkCall_strCommon(const CallExpr *CE, const FunctionDecl *FD) { //===----------------------------------------------------------------------===// void WalkAST::checkCall_rand(const CallExpr *CE, const FunctionDecl *FD) { - if (!CheckRand) + if (!filter.check_rand || !CheckRand) return; const FunctionProtoType *FTP @@ -475,11 +614,11 @@ void WalkAST::checkCall_rand(const CallExpr *CE, const FunctionDecl *FD) { return; // Issue a warning. - llvm::SmallString<256> buf1; + SmallString<256> buf1; llvm::raw_svector_ostream os1(buf1); os1 << '\'' << *FD << "' is a poor random number generator"; - llvm::SmallString<256> buf2; + SmallString<256> buf2; llvm::raw_svector_ostream os2(buf2); os2 << "Function '" << *FD << "' is obsolete because it implements a poor random number generator." @@ -488,7 +627,8 @@ void WalkAST::checkCall_rand(const CallExpr *CE, const FunctionDecl *FD) { SourceRange R = CE->getCallee()->getSourceRange(); PathDiagnosticLocation CELoc = PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC); - BR.EmitBasicReport(os1.str(), "Security", os2.str(), CELoc, &R, 1); + BR.EmitBasicReport(AC->getDecl(), os1.str(), "Security", os2.str(), + CELoc, &R, 1); } //===----------------------------------------------------------------------===// @@ -497,7 +637,7 @@ void WalkAST::checkCall_rand(const CallExpr *CE, const FunctionDecl *FD) { //===----------------------------------------------------------------------===// void WalkAST::checkCall_random(const CallExpr *CE, const FunctionDecl *FD) { - if (!CheckRand) + if (!CheckRand || !filter.check_rand) return; const FunctionProtoType *FTP @@ -513,7 +653,8 @@ void WalkAST::checkCall_random(const CallExpr *CE, const FunctionDecl *FD) { SourceRange R = CE->getCallee()->getSourceRange(); PathDiagnosticLocation CELoc = PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC); - BR.EmitBasicReport("'random' is not a secure random number generator", + BR.EmitBasicReport(AC->getDecl(), + "'random' is not a secure random number generator", "Security", "The 'random' function produces a sequence of values that " "an adversary may be able to predict. Use 'arc4random' " @@ -526,11 +667,15 @@ void WalkAST::checkCall_random(const CallExpr *CE, const FunctionDecl *FD) { //===----------------------------------------------------------------------===// void WalkAST::checkCall_vfork(const CallExpr *CE, const FunctionDecl *FD) { + if (!filter.check_vfork) + return; + // All calls to vfork() are insecure, issue a warning. SourceRange R = CE->getCallee()->getSourceRange(); PathDiagnosticLocation CELoc = PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC); - BR.EmitBasicReport("Potential insecure implementation-specific behavior in " + BR.EmitBasicReport(AC->getDecl(), + "Potential insecure implementation-specific behavior in " "call 'vfork'", "Security", "Call to function 'vfork' is insecure as it can lead to " @@ -546,6 +691,9 @@ void WalkAST::checkCall_vfork(const CallExpr *CE, const FunctionDecl *FD) { //===----------------------------------------------------------------------===// void WalkAST::checkUncheckedReturnValue(CallExpr *CE) { + if (!filter.check_UncheckedReturn) + return; + const FunctionDecl *FD = CE->getDirectCallee(); if (!FD) return; @@ -586,11 +734,11 @@ void WalkAST::checkUncheckedReturnValue(CallExpr *CE) { return; // Issue a warning. - llvm::SmallString<256> buf1; + SmallString<256> buf1; llvm::raw_svector_ostream os1(buf1); os1 << "Return value is not checked in call to '" << *FD << '\''; - llvm::SmallString<256> buf2; + SmallString<256> buf2; llvm::raw_svector_ostream os2(buf2); os2 << "The return value from the call to '" << *FD << "' is not checked. If an error occurs in '" << *FD @@ -599,7 +747,8 @@ void WalkAST::checkUncheckedReturnValue(CallExpr *CE) { SourceRange R = CE->getCallee()->getSourceRange(); PathDiagnosticLocation CELoc = PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC); - BR.EmitBasicReport(os1.str(), "Security", os2.str(), CELoc, &R, 1); + BR.EmitBasicReport(AC->getDecl(), os1.str(), "Security", os2.str(), + CELoc, &R, 1); } //===----------------------------------------------------------------------===// @@ -609,14 +758,29 @@ void WalkAST::checkUncheckedReturnValue(CallExpr *CE) { namespace { class SecuritySyntaxChecker : public Checker<check::ASTCodeBody> { public: + ChecksFilter filter; + void checkASTCodeBody(const Decl *D, AnalysisManager& mgr, BugReporter &BR) const { - WalkAST walker(BR, mgr.getAnalysisContext(D)); + WalkAST walker(BR, mgr.getAnalysisDeclContext(D), filter); walker.Visit(D->getBody()); } }; } -void ento::registerSecuritySyntaxChecker(CheckerManager &mgr) { - mgr.registerChecker<SecuritySyntaxChecker>(); +#define REGISTER_CHECKER(name) \ +void ento::register##name(CheckerManager &mgr) {\ + mgr.registerChecker<SecuritySyntaxChecker>()->filter.check_##name = true;\ } + +REGISTER_CHECKER(gets) +REGISTER_CHECKER(getpw) +REGISTER_CHECKER(mkstemp) +REGISTER_CHECKER(mktemp) +REGISTER_CHECKER(strcpy) +REGISTER_CHECKER(rand) +REGISTER_CHECKER(vfork) +REGISTER_CHECKER(FloatLoopCounter) +REGISTER_CHECKER(UncheckedReturn) + + diff --git a/lib/StaticAnalyzer/Checkers/CheckSizeofPointer.cpp b/lib/StaticAnalyzer/Checkers/CheckSizeofPointer.cpp index 469be05..cc7fd37 100644 --- a/lib/StaticAnalyzer/Checkers/CheckSizeofPointer.cpp +++ b/lib/StaticAnalyzer/Checkers/CheckSizeofPointer.cpp @@ -24,10 +24,10 @@ using namespace ento; namespace { class WalkAST : public StmtVisitor<WalkAST> { BugReporter &BR; - AnalysisContext* AC; + AnalysisDeclContext* AC; public: - WalkAST(BugReporter &br, AnalysisContext* ac) : BR(br), AC(ac) {} + WalkAST(BugReporter &br, AnalysisDeclContext* ac) : BR(br), AC(ac) {} void VisitUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *E); void VisitStmt(Stmt *S) { VisitChildren(S); } void VisitChildren(Stmt *S); @@ -63,7 +63,8 @@ void WalkAST::VisitUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *E) { SourceRange R = ArgEx->getSourceRange(); PathDiagnosticLocation ELoc = PathDiagnosticLocation::createBegin(E, BR.getSourceManager(), AC); - BR.EmitBasicReport("Potential unintended use of sizeof() on pointer type", + BR.EmitBasicReport(AC->getDecl(), + "Potential unintended use of sizeof() on pointer type", "Logic", "The code calls sizeof() on a pointer type. " "This can produce an unexpected result.", @@ -80,7 +81,7 @@ class SizeofPointerChecker : public Checker<check::ASTCodeBody> { public: void checkASTCodeBody(const Decl *D, AnalysisManager& mgr, BugReporter &BR) const { - WalkAST walker(BR, mgr.getAnalysisContext(D)); + WalkAST walker(BR, mgr.getAnalysisDeclContext(D)); walker.Visit(D->getBody()); } }; diff --git a/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp b/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp new file mode 100644 index 0000000..843502f --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp @@ -0,0 +1,233 @@ +//= CheckerDocumentation.cpp - Documentation checker ---------------*- C++ -*-// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This checker lists all the checker callbacks and provides documentation for +// checker writers. +// +//===----------------------------------------------------------------------===// + +#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" + +using namespace clang; +using namespace ento; + +// All checkers should be placed into anonymous namespace. +// We place the CheckerDocumentation inside ento namespace to make the +// it visible in doxygen. +namespace ento { + +/// This checker documents the callback functions checkers can use to implement +/// the custom handling of the specific events during path exploration as well +/// as reporting bugs. Most of the callbacks are targeted at path-sensitive +/// checking. +/// +/// \sa CheckerContext +class CheckerDocumentation : public Checker< check::PreStmt<DeclStmt>, + check::PostStmt<CallExpr>, + check::PreObjCMessage, + check::PostObjCMessage, + check::BranchCondition, + check::Location, + check::Bind, + check::DeadSymbols, + check::EndPath, + check::EndAnalysis, + check::EndOfTranslationUnit, + eval::Call, + eval::Assume, + check::LiveSymbols, + check::RegionChanges, + check::Event<ImplicitNullDerefEvent>, + check::ASTDecl<FunctionDecl> > { +public: + + /// \brief Pre-visit the Statement. + /// + /// The method will be called before the analyzer core processes the + /// statement. The notification is performed for every explored CFGElement, + /// which does not include the control flow statements such as IfStmt. The + /// callback can be specialized to be called with any subclass of Stmt. + /// + /// See checkBranchCondition() callback for performing custom processing of + /// the branching statements. + /// + /// check::PreStmt<DeclStmt> + void checkPreStmt(const DeclStmt *DS, CheckerContext &C) const {} + + /// \brief Post-visit the Statement. + /// + /// The method will be called after the analyzer core processes the + /// statement. The notification is performed for every explored CFGElement, + /// which does not include the control flow statements such as IfStmt. The + /// callback can be specialized to be called with any subclass of Stmt. + /// + /// check::PostStmt<DeclStmt> + void checkPostStmt(const CallExpr *DS, CheckerContext &C) const; + + /// \brief Pre-visit the Objective C messages. + void checkPreObjCMessage(const ObjCMessage &Msg, CheckerContext &C) const {} + + /// \brief Post-visit the Objective C messages. + void checkPostObjCMessage(const ObjCMessage &Msg, CheckerContext &C) const {} + + /// \brief Pre-visit of the condition statement of a branch (such as IfStmt). + void checkBranchCondition(const Stmt *Condition, CheckerContext &Ctx) const {} + + /// \brief Called on a load from and a store to a location. + /// + /// The method will be called each time a location (pointer) value is + /// accessed. + /// \param Loc The value of the location (pointer). + /// \param IsLoad The flag specifying if the location is a store or a load. + /// \param S The load is performed while processing the statement. + /// + /// check::Location + void checkLocation(SVal Loc, bool IsLoad, const Stmt *S, + CheckerContext &C) const {} + + /// \brief Called on binding of a value to a location. + /// + /// \param Loc The value of the location (pointer). + /// \param Val The value which will be stored at the location Loc. + /// \param S The bind is performed while processing the statement S. + /// + /// check::Bind + void checkBind(SVal Loc, SVal Val, const Stmt *S, CheckerContext &C) const {} + + + /// \brief Called whenever a symbol becomes dead. + /// + /// This callback should be used by the checkers to aggressively clean + /// up/reduce the checker state, which is important for reducing the overall + /// memory usage. Specifically, if a checker keeps symbol specific information + /// in the sate, it can and should be dropped after the symbol becomes dead. + /// In addition, reporting a bug as soon as the checker becomes dead leads to + /// more precise diagnostics. (For example, one should report that a malloced + /// variable is not freed right after it goes out of scope.) + /// + /// \param SR The SymbolReaper object can be queried to determine which + /// symbols are dead. + /// + /// check::DeadSymbols + void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const {} + + /// \brief Called when an end of path is reached in the ExplodedGraph. + /// + /// This callback should be used to check if the allocated resources are freed. + /// + /// check::EndPath + void checkEndPath(CheckerContext &Ctx) const {} + + /// \brief Called after all the paths in the ExplodedGraph reach end of path + /// - the symbolic execution graph is fully explored. + /// + /// This callback should be used in cases when a checker needs to have a + /// global view of the information generated on all paths. For example, to + /// compare execution summary/result several paths. + /// See IdempotentOperationChecker for a usage example. + /// + /// check::EndAnalysis + void checkEndAnalysis(ExplodedGraph &G, + BugReporter &BR, + ExprEngine &Eng) const {} + + /// \brief Called after analysis of a TranslationUnit is complete. + /// + /// check::EndOfTranslationUnit + void checkEndOfTranslationUnit(const TranslationUnitDecl *TU, + AnalysisManager &Mgr, + BugReporter &BR) const {} + + + /// \brief Evaluates function call. + /// + /// The analysis core threats all function calls in the same way. However, some + /// functions have special meaning, which should be reflected in the program + /// state. This callback allows a checker to provide domain specific knowledge + /// about the particular functions it knows about. + /// + /// \returns true if the call has been successfully evaluated + /// and false otherwise. Note, that only one checker can evaluate a call. If + /// more then one checker claim that they can evaluate the same call the + /// first one wins. + /// + /// eval::Call + bool evalCall(const CallExpr *CE, CheckerContext &C) const { return true; } + + /// \brief Handles assumptions on symbolic values. + /// + /// This method is called when a symbolic expression is assumed to be true or + /// false. For example, the assumptions are performed when evaluating a + /// condition at a branch. The callback allows checkers track the assumptions + /// performed on the symbols of interest and change the state accordingly. + /// + /// eval::Assume + ProgramStateRef evalAssume(ProgramStateRef State, + SVal Cond, + bool Assumption) const { return State; } + + /// Allows modifying SymbolReaper object. For example, checkers can explicitly + /// register symbols of interest as live. These symbols will not be marked + /// dead and removed. + /// + /// check::LiveSymbols + void checkLiveSymbols(ProgramStateRef State, SymbolReaper &SR) const {} + + + bool wantsRegionChangeUpdate(ProgramStateRef St) const { return true; } + + /// check::RegionChanges + /// Allows tracking regions which get invalidated. + /// \param state The current program state. + /// \param invalidated A set of all symbols potentially touched by the change. + /// \param ExplicitRegions The regions explicitly requested for invalidation. + /// For example, in the case of a function call, these would be arguments. + /// \param Regions The transitive closure of accessible regions, + /// i.e. all regions that may have been touched by this change. + /// \param The call expression wrapper if the regions are invalidated by a + /// call, 0 otherwise. + /// Note, in order to be notified, the checker should also implement + /// wantsRegionChangeUpdate callback. + ProgramStateRef + checkRegionChanges(ProgramStateRef State, + const StoreManager::InvalidatedSymbols *, + ArrayRef<const MemRegion *> ExplicitRegions, + ArrayRef<const MemRegion *> Regions, + const CallOrObjCMessage *Call) const { + return State; + } + + /// check::Event<ImplicitNullDerefEvent> + void checkEvent(ImplicitNullDerefEvent Event) const {} + + /// \brief Check every declaration in the AST. + /// + /// An AST traversal callback, which should only be used when the checker is + /// not path sensitive. It will be called for every Declaration in the AST and + /// can be specialized to only be called on subclasses of Decl, for example, + /// FunctionDecl. + /// + /// check::ASTDecl<FunctionDecl> + void checkASTDecl(const FunctionDecl *D, + AnalysisManager &Mgr, + BugReporter &BR) const {} + +}; + +void CheckerDocumentation::checkPostStmt(const CallExpr *DS, + CheckerContext &C) const { + return; +} + +} // end namespace diff --git a/lib/StaticAnalyzer/Checkers/Checkers.td b/lib/StaticAnalyzer/Checkers/Checkers.td index d53e0b8..96a8d26 100644 --- a/lib/StaticAnalyzer/Checkers/Checkers.td +++ b/lib/StaticAnalyzer/Checkers/Checkers.td @@ -27,16 +27,21 @@ def DeadCode : Package<"deadcode">; def DeadCodeExperimental : Package<"deadcode">, InPackage<Experimental>, Hidden; def Security : Package <"security">; +def InsecureAPI : Package<"insecureAPI">, InPackage<Security>; def SecurityExperimental : Package<"security">, InPackage<Experimental>, Hidden; +def Taint : Package<"taint">, InPackage<SecurityExperimental>, Hidden; def Unix : Package<"unix">; def UnixExperimental : Package<"unix">, InPackage<Experimental>, Hidden; +def CString : Package<"cstring">, InPackage<Unix>, Hidden; +def CStringExperimental : Package<"cstring">, InPackage<UnixExperimental>, Hidden; def OSX : Package<"osx">; def OSXExperimental : Package<"osx">, InPackage<Experimental>, Hidden; def Cocoa : Package<"cocoa">, InPackage<OSX>; def CocoaExperimental : Package<"cocoa">, InPackage<OSXExperimental>, Hidden; def CoreFoundation : Package<"coreFoundation">, InPackage<OSX>; +def Containers : Package<"containers">, InPackage<CoreFoundation>; def LLVM : Package<"llvm">; def Debug : Package<"debug">; @@ -83,6 +88,10 @@ def StackAddrEscapeChecker : Checker<"StackAddressEscape">, let ParentPackage = CoreExperimental in { +def BoolAssignmentChecker : Checker<"BoolAssignment">, + HelpText<"Warn about assigning non-{0,1} values to Boolean variables">, + DescFile<"BoolAssignmentChecker.cpp">; + def CastSizeChecker : Checker<"CastSize">, HelpText<"Check when casting a malloc'ed type T, whether the size is a multiple of the size of T">, DescFile<"CastSizeChecker.cpp">; @@ -163,6 +172,10 @@ def IteratorsChecker : Checker<"Iterators">, HelpText<"Check improper uses of STL vector iterators">, DescFile<"IteratorsChecker.cpp">; +def VirtualCallChecker : Checker<"VirtualCall">, + HelpText<"Check virtual function calls during construction or destruction">, + DescFile<"VirtualCallChecker.cpp">; + } // end: "cplusplus.experimental" //===----------------------------------------------------------------------===// @@ -174,15 +187,14 @@ let ParentPackage = DeadCode in { def DeadStoresChecker : Checker<"DeadStores">, HelpText<"Check for values stored to variables that are never read afterwards">, DescFile<"DeadStoresChecker.cpp">; +} // end DeadCode + +let ParentPackage = DeadCodeExperimental in { def IdempotentOperationChecker : Checker<"IdempotentOperations">, HelpText<"Warn about idempotent operations">, DescFile<"IdempotentOperationChecker.cpp">; -} // end DeadCode - -let ParentPackage = DeadCodeExperimental in { - def UnreachableCodeChecker : Checker<"UnreachableCode">, HelpText<"Check unreachable code">, DescFile<"UnreachableCodeChecker.cpp">; @@ -193,11 +205,39 @@ def UnreachableCodeChecker : Checker<"UnreachableCode">, // Security checkers. //===----------------------------------------------------------------------===// -let ParentPackage = SecurityExperimental in { +let ParentPackage = InsecureAPI in { + def gets : Checker<"gets">, + HelpText<"Warn on uses of the 'gets' function">, + DescFile<"CheckSecuritySyntaxOnly.cpp">; + def getpw : Checker<"getpw">, + HelpText<"Warn on uses of the 'getpw' function">, + DescFile<"CheckSecuritySyntaxOnly.cpp">; + def mktemp : Checker<"mktemp">, + HelpText<"Warn on uses of the 'mktemp' function">, + DescFile<"CheckSecuritySyntaxOnly.cpp">; + def mkstemp : Checker<"mkstemp">, + HelpText<"Warn when 'mkstemp' is passed fewer than 6 X's in the format string">, + DescFile<"CheckSecuritySyntaxOnly.cpp">; + def rand : Checker<"rand">, + HelpText<"Warn on uses of the 'rand', 'random', and related functions">, + DescFile<"CheckSecuritySyntaxOnly.cpp">; + def strcpy : Checker<"strcpy">, + HelpText<"Warn on uses of the 'strcpy' and 'strcat' functions">, + DescFile<"CheckSecuritySyntaxOnly.cpp">; + def vfork : Checker<"vfork">, + HelpText<"Warn on uses of the 'vfork' function">, + DescFile<"CheckSecuritySyntaxOnly.cpp">; + def UncheckedReturn : Checker<"UncheckedReturn">, + HelpText<"Warn on uses of functions whose return values must be always checked">, + DescFile<"CheckSecuritySyntaxOnly.cpp">; +} +let ParentPackage = Security in { + def FloatLoopCounter : Checker<"FloatLoopCounter">, + HelpText<"Warn on using a floating point value as a loop counter (CERT: FLP30-C, FLP30-CPP)">, + DescFile<"CheckSecuritySyntaxOnly.cpp">; +} -def SecuritySyntaxChecker : Checker<"SecuritySyntactic">, - HelpText<"Perform quick security API checks that require no data flow">, - DescFile<"CheckSecuritySyntaxOnly.cpp">; +let ParentPackage = SecurityExperimental in { def ArrayBoundChecker : Checker<"ArrayBound">, HelpText<"Warn about buffer overflows (older checker)">, @@ -218,6 +258,18 @@ def MallocOverflowSecurityChecker : Checker<"MallocOverflow">, } // end "security.experimental" //===----------------------------------------------------------------------===// +// Taint checkers. +//===----------------------------------------------------------------------===// + +let ParentPackage = Taint in { + +def GenericTaintChecker : Checker<"TaintPropagation">, + HelpText<"Generate taint information used by other checkers">, + DescFile<"GenericTaintChecker.cpp">; + +} // end "experimental.security.taint" + +//===----------------------------------------------------------------------===// // Unix API checkers. //===----------------------------------------------------------------------===// @@ -226,6 +278,10 @@ let ParentPackage = Unix in { def UnixAPIChecker : Checker<"API">, HelpText<"Check calls to various UNIX/Posix functions">, DescFile<"UnixAPIChecker.cpp">; + +def MallocPessimistic : Checker<"Malloc">, + HelpText<"Check for memory leaks, double free, and use-after-free problems.">, + DescFile<"MallocChecker.cpp">; } // end "unix" @@ -235,14 +291,14 @@ def ChrootChecker : Checker<"Chroot">, HelpText<"Check improper use of chroot">, DescFile<"ChrootChecker.cpp">; -def CStringChecker : Checker<"CString">, - HelpText<"Check calls to functions in <string.h>">, - DescFile<"CStringChecker.cpp">; - -def MallocChecker : Checker<"Malloc">, - HelpText<"Check for potential memory leaks, double free, and use-after-free problems">, +def MallocOptimistic : Checker<"MallocWithAnnotations">, + HelpText<"Check for memory leaks, double free, and use-after-free problems. Assumes that all user-defined functions which might free a pointer are annotated.">, DescFile<"MallocChecker.cpp">; +def MallocSizeofChecker : Checker<"MallocSizeof">, + HelpText<"Check for dubious malloc arguments involving sizeof">, + DescFile<"MallocSizeofChecker.cpp">; + def PthreadLockChecker : Checker<"PthreadLock">, HelpText<"Simple lock -> unlock checker">, DescFile<"PthreadLockChecker.cpp">; @@ -253,6 +309,32 @@ def StreamChecker : Checker<"Stream">, } // end "unix.experimental" +let ParentPackage = CString in { + +def CStringNullArg : Checker<"NullArg">, + HelpText<"Check for null pointers being passed as arguments to C string functions">, + DescFile<"CStringChecker.cpp">; + +def CStringSyntaxChecker : Checker<"BadSizeArg">, + HelpText<"Check the size argument passed into C string functions for common erroneous patterns">, + DescFile<"CStringSyntaxChecker.cpp">; +} + +let ParentPackage = CStringExperimental in { + +def CStringOutOfBounds : Checker<"OutOfBounds">, + HelpText<"Check for out-of-bounds access in string functions">, + DescFile<"CStringChecker.cpp">; + +def CStringBufferOverlap : Checker<"BufferOverlap">, + HelpText<"Checks for overlap in two buffer arguments">, + DescFile<"CStringChecker.cpp">; + +def CStringNotNullTerm : Checker<"NotNullTerminated">, + HelpText<"Check for arguments which are not null-terminating strings">, + DescFile<"CStringChecker.cpp">; +} + //===----------------------------------------------------------------------===// // Mac OS X, Cocoa, and Core Foundation checkers. //===----------------------------------------------------------------------===// @@ -291,7 +373,7 @@ def ClassReleaseChecker : Checker<"ClassRelease">, DescFile<"BasicObjCFoundationChecks.cpp">; def VariadicMethodTypeChecker : Checker<"VariadicMethodTypes">, - HelpText<"Check for passing non-Objective-C types to variadic methods that expect" + HelpText<"Check for passing non-Objective-C types to variadic methods that expect " "only Objective-C types">, DescFile<"BasicObjCFoundationChecks.cpp">; @@ -306,7 +388,11 @@ def ObjCMethSigsChecker : Checker<"IncompatibleMethodTypes">, def ObjCUnusedIvarsChecker : Checker<"UnusedIvars">, HelpText<"Warn about private ivars that are never used">, DescFile<"ObjCUnusedIVarsChecker.cpp">; - + +def ObjCSelfInitChecker : Checker<"SelfInit">, + HelpText<"Check that 'self' is properly initialized inside an initializer method">, + DescFile<"ObjCSelfInitChecker.cpp">; + def NSErrorChecker : Checker<"NSError">, HelpText<"Check usage of NSError** parameters">, DescFile<"NSErrorChecker.cpp">; @@ -319,10 +405,6 @@ def RetainCountChecker : Checker<"RetainCount">, let ParentPackage = CocoaExperimental in { -def ObjCSelfInitChecker : Checker<"SelfInit">, - HelpText<"Check that 'self' is properly initialized inside an initializer method">, - DescFile<"ObjCSelfInitChecker.cpp">; - def ObjCDeallocChecker : Checker<"Dealloc">, HelpText<"Warn about Objective-C classes that lack a correct implementation of -dealloc">, DescFile<"CheckObjCDealloc.cpp">; @@ -344,6 +426,16 @@ def CFErrorChecker : Checker<"CFError">, DescFile<"NSErrorChecker.cpp">; } +let ParentPackage = Containers in { +def ObjCContainersASTChecker : Checker<"PointerSizedValues">, + HelpText<"Warns if 'CFArray', 'CFDictionary', 'CFSet' are created with non-pointer-size values">, + DescFile<"ObjCContainersASTChecker.cpp">; + +def ObjCContainersChecker : Checker<"OutOfBounds">, + HelpText<"Checks for index out-of-bounds when using 'CFArray' API">, + DescFile<"ObjCContainersChecker.cpp">; + +} //===----------------------------------------------------------------------===// // Checkers for LLVM development. //===----------------------------------------------------------------------===// @@ -359,6 +451,10 @@ def LLVMConventionsChecker : Checker<"Conventions">, let ParentPackage = Debug in { +def DominatorsTreeDumper : Checker<"DumpDominators">, + HelpText<"Print the dominance tree for a given CFG">, + DescFile<"DebugCheckers.cpp">; + def LiveVariablesDumper : Checker<"DumpLiveVars">, HelpText<"Print results of live variable analysis">, DescFile<"DebugCheckers.cpp">; @@ -371,9 +467,21 @@ def CFGDumper : Checker<"DumpCFG">, HelpText<"Display Control-Flow Graphs">, DescFile<"DebugCheckers.cpp">; +def CallGraphViewer : Checker<"ViewCallGraph">, + HelpText<"View Call Graph using GraphViz">, + DescFile<"DebugCheckers.cpp">; + +def CallGraphDumper : Checker<"DumpCallGraph">, + HelpText<"Display Call Graph">, + DescFile<"DebugCheckers.cpp">; + def AnalyzerStatsChecker : Checker<"Stats">, HelpText<"Emit warnings with analyzer statistics">, DescFile<"AnalyzerStatsChecker.cpp">; +def TaintTesterChecker : Checker<"TaintTest">, + HelpText<"Mark tainted symbols as such.">, + DescFile<"TaintTesterChecker.cpp">; + } // end "debug" diff --git a/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp b/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp index 3c92381..30d0609 100644 --- a/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp @@ -41,7 +41,7 @@ bool isRootChanged(intptr_t k) { return k == ROOT_CHANGED; } class ChrootChecker : public Checker<eval::Call, check::PreStmt<CallExpr> > { mutable IdentifierInfo *II_chroot, *II_chdir; // This bug refers to possibly break out of a chroot() jail. - mutable llvm::OwningPtr<BuiltinBug> BT_BreakJail; + mutable OwningPtr<BuiltinBug> BT_BreakJail; public: ChrootChecker() : II_chroot(0), II_chdir(0) {} @@ -62,10 +62,7 @@ private: } // end anonymous namespace bool ChrootChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { - const ProgramState *state = C.getState(); - const Expr *Callee = CE->getCallee(); - SVal L = state->getSVal(Callee); - const FunctionDecl *FD = L.getAsFunctionDecl(); + const FunctionDecl *FD = C.getCalleeDecl(CE); if (!FD) return false; @@ -88,7 +85,7 @@ bool ChrootChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { } void ChrootChecker::Chroot(CheckerContext &C, const CallExpr *CE) const { - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); ProgramStateManager &Mgr = state->getStateManager(); // Once encouter a chroot(), set the enum value ROOT_CHANGED directly in @@ -98,7 +95,7 @@ void ChrootChecker::Chroot(CheckerContext &C, const CallExpr *CE) const { } void ChrootChecker::Chdir(CheckerContext &C, const CallExpr *CE) const { - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); ProgramStateManager &Mgr = state->getStateManager(); // If there are no jail state in the GDM, just return. @@ -108,7 +105,7 @@ void ChrootChecker::Chdir(CheckerContext &C, const CallExpr *CE) const { // After chdir("/"), enter the jail, set the enum value JAIL_ENTERED. const Expr *ArgExpr = CE->getArg(0); - SVal ArgVal = state->getSVal(ArgExpr); + SVal ArgVal = state->getSVal(ArgExpr, C.getLocationContext()); if (const MemRegion *R = ArgVal.getAsRegion()) { R = R->StripCasts(); @@ -125,10 +122,7 @@ void ChrootChecker::Chdir(CheckerContext &C, const CallExpr *CE) const { // Check the jail state before any function call except chroot and chdir(). void ChrootChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const { - const ProgramState *state = C.getState(); - const Expr *Callee = CE->getCallee(); - SVal L = state->getSVal(Callee); - const FunctionDecl *FD = L.getAsFunctionDecl(); + const FunctionDecl *FD = C.getCalleeDecl(CE); if (!FD) return; @@ -143,10 +137,10 @@ void ChrootChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const { return; // If jail state is ROOT_CHANGED, generate BugReport. - void *const* k = state->FindGDM(ChrootChecker::getTag()); + void *const* k = C.getState()->FindGDM(ChrootChecker::getTag()); if (k) if (isRootChanged((intptr_t) *k)) - if (ExplodedNode *N = C.generateNode()) { + if (ExplodedNode *N = C.addTransition()) { if (!BT_BreakJail) BT_BreakJail.reset(new BuiltinBug("Break out of jail", "No call of chdir(\"/\") immediately " diff --git a/lib/StaticAnalyzer/Checkers/ClangSACheckers.h b/lib/StaticAnalyzer/Checkers/ClangSACheckers.h index 289ce8d..230baa7 100644 --- a/lib/StaticAnalyzer/Checkers/ClangSACheckers.h +++ b/lib/StaticAnalyzer/Checkers/ClangSACheckers.h @@ -12,6 +12,8 @@ // //===----------------------------------------------------------------------===// +#include "clang/StaticAnalyzer/Checkers/CommonBugCategories.h" + #ifndef LLVM_CLANG_SA_LIB_CHECKERS_CLANGSACHECKERS_H #define LLVM_CLANG_SA_LIB_CHECKERS_CLANGSACHECKERS_H diff --git a/lib/StaticAnalyzer/Checkers/CommonBugCategories.cpp b/lib/StaticAnalyzer/Checkers/CommonBugCategories.cpp new file mode 100644 index 0000000..e2a8ea6 --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/CommonBugCategories.cpp @@ -0,0 +1,18 @@ +//=--- CommonBugCategories.cpp - Provides common issue categories -*- C++ -*-=// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// Common strings used for the "category" of many static analyzer issues. +namespace clang { namespace ento { namespace categories { + +const char *CoreFoundationObjectiveC = "Core Foundation/Objective-C"; +const char *MemoryCoreFoundationObjectiveC = + "Memory (Core Foundation/Objective-C)"; +const char *UnixAPI = "Unix API"; +}}} + diff --git a/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp b/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp index 901af43..510e8cd 100644 --- a/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp @@ -23,6 +23,7 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/ParentMap.h" #include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallString.h" using namespace clang; using namespace ento; @@ -67,22 +68,37 @@ void ReachableCode::computeReachableBlocks() { } } +static const Expr *LookThroughTransitiveAssignments(const Expr *Ex) { + while (Ex) { + const BinaryOperator *BO = + dyn_cast<BinaryOperator>(Ex->IgnoreParenCasts()); + if (!BO) + break; + if (BO->getOpcode() == BO_Assign) { + Ex = BO->getRHS(); + continue; + } + break; + } + return Ex; +} + namespace { class DeadStoreObs : public LiveVariables::Observer { const CFG &cfg; ASTContext &Ctx; BugReporter& BR; - AnalysisContext* AC; + AnalysisDeclContext* AC; ParentMap& Parents; llvm::SmallPtrSet<const VarDecl*, 20> Escaped; - llvm::OwningPtr<ReachableCode> reachableCode; + OwningPtr<ReachableCode> reachableCode; const CFGBlock *currentBlock; enum DeadStoreKind { Standard, Enclosing, DeadIncrement, DeadInit }; public: DeadStoreObs(const CFG &cfg, ASTContext &ctx, - BugReporter& br, AnalysisContext* ac, ParentMap& parents, + BugReporter& br, AnalysisDeclContext* ac, ParentMap& parents, llvm::SmallPtrSet<const VarDecl*, 20> &escaped) : cfg(cfg), Ctx(ctx), BR(br), AC(ac), Parents(parents), Escaped(escaped), currentBlock(0) {} @@ -104,14 +120,11 @@ public: if (!reachableCode->isReachable(currentBlock)) return; - llvm::SmallString<64> buf; + SmallString<64> buf; llvm::raw_svector_ostream os(buf); const char *BugType = 0; switch (dsk) { - default: - llvm_unreachable("Impossible dead store type."); - case DeadInit: BugType = "Dead initialization"; os << "Value stored to '" << *V @@ -132,7 +145,7 @@ public: return; } - BR.EmitBasicReport(BugType, "Dead store", os.str(), L, R); + BR.EmitBasicReport(AC->getDecl(), BugType, "Dead store", os.str(), L, R); } void CheckVarDecl(const VarDecl *VD, const Expr *Ex, const Expr *Val, @@ -202,17 +215,18 @@ public: if (VarDecl *VD = dyn_cast<VarDecl>(DR->getDecl())) { // Special case: check for assigning null to a pointer. // This is a common form of defensive programming. + const Expr *RHS = LookThroughTransitiveAssignments(B->getRHS()); + QualType T = VD->getType(); if (T->isPointerType() || T->isObjCObjectPointerType()) { - if (B->getRHS()->isNullPointerConstant(Ctx, - Expr::NPC_ValueDependentIsNull)) + if (RHS->isNullPointerConstant(Ctx, Expr::NPC_ValueDependentIsNull)) return; } - Expr *RHS = B->getRHS()->IgnoreParenCasts(); + RHS = RHS->IgnoreParenCasts(); // Special case: self-assignments. These are often used to shut up // "unused variable" compiler warnings. - if (DeclRefExpr *RhsDR = dyn_cast<DeclRefExpr>(RHS)) + if (const DeclRefExpr *RhsDR = dyn_cast<DeclRefExpr>(RHS)) if (VD == dyn_cast<VarDecl>(RhsDR->getDecl())) return; @@ -254,10 +268,15 @@ public: if (V->getType()->getAs<ReferenceType>()) return; - if (Expr *E = V->getInit()) { - while (ExprWithCleanups *exprClean = dyn_cast<ExprWithCleanups>(E)) + if (const Expr *E = V->getInit()) { + while (const ExprWithCleanups *exprClean = + dyn_cast<ExprWithCleanups>(E)) E = exprClean->getSubExpr(); + // Look through transitive assignments, e.g.: + // int x = y = 0; + E = LookThroughTransitiveAssignments(E); + // Don't warn on C++ objects (yet) until we can show that their // constructors/destructors don't have side effects. if (isa<CXXConstructExpr>(E)) @@ -274,11 +293,12 @@ public: // If x is EVER assigned a new value later, don't issue // a warning. This is because such initialization can be // due to defensive programming. - if (E->isConstantInitializer(Ctx, false)) + if (E->isEvaluatable(Ctx)) return; - if (DeclRefExpr *DRE=dyn_cast<DeclRefExpr>(E->IgnoreParenCasts())) - if (VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl())) { + if (const DeclRefExpr *DRE = + dyn_cast<DeclRefExpr>(E->IgnoreParenCasts())) + if (const VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl())) { // Special case: check for initialization from constant // variables. // @@ -350,7 +370,7 @@ public: BugReporter &BR) const { if (LiveVariables *L = mgr.getAnalysis<LiveVariables>(D)) { CFG &cfg = *mgr.getCFG(D); - AnalysisContext *AC = mgr.getAnalysisContext(D); + AnalysisDeclContext *AC = mgr.getAnalysisDeclContext(D); ParentMap &pmap = mgr.getParentMap(D); FindEscaped FS(&cfg); FS.getCFG().VisitBlockStmts(FS); diff --git a/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp b/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp index d9d5694..34053cd 100644 --- a/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp +++ b/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp @@ -15,11 +15,36 @@ #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/Analysis/Analyses/LiveVariables.h" +#include "clang/Analysis/Analyses/Dominators.h" +#include "clang/Analysis/CallGraph.h" +#include "llvm/Support/Process.h" using namespace clang; using namespace ento; //===----------------------------------------------------------------------===// +// DominatorsTreeDumper +//===----------------------------------------------------------------------===// + +namespace { +class DominatorsTreeDumper : public Checker<check::ASTCodeBody> { +public: + void checkASTCodeBody(const Decl *D, AnalysisManager& mgr, + BugReporter &BR) const { + if (AnalysisDeclContext *AC = mgr.getAnalysisDeclContext(D)) { + DominatorTree dom; + dom.buildDominatorTree(*AC); + dom.dump(); + } + } +}; +} + +void ento::registerDominatorsTreeDumper(CheckerManager &mgr) { + mgr.registerChecker<DominatorsTreeDumper>(); +} + +//===----------------------------------------------------------------------===// // LiveVariablesDumper //===----------------------------------------------------------------------===// @@ -49,7 +74,7 @@ public: void checkASTCodeBody(const Decl *D, AnalysisManager& mgr, BugReporter &BR) const { if (CFG *cfg = mgr.getCFG(D)) { - cfg->viewCFG(mgr.getLangOptions()); + cfg->viewCFG(mgr.getLangOpts()); } } }; @@ -69,7 +94,8 @@ public: void checkASTCodeBody(const Decl *D, AnalysisManager& mgr, BugReporter &BR) const { if (CFG *cfg = mgr.getCFG(D)) { - cfg->dump(mgr.getLangOptions()); + cfg->dump(mgr.getLangOpts(), + llvm::sys::Process::StandardErrHasColors()); } } }; @@ -78,3 +104,43 @@ public: void ento::registerCFGDumper(CheckerManager &mgr) { mgr.registerChecker<CFGDumper>(); } + +//===----------------------------------------------------------------------===// +// CallGraphViewer +//===----------------------------------------------------------------------===// + +namespace { +class CallGraphViewer : public Checker< check::ASTDecl<TranslationUnitDecl> > { +public: + void checkASTDecl(const TranslationUnitDecl *TU, AnalysisManager& mgr, + BugReporter &BR) const { + CallGraph CG; + CG.addToCallGraph(const_cast<TranslationUnitDecl*>(TU)); + CG.viewGraph(); + } +}; +} + +void ento::registerCallGraphViewer(CheckerManager &mgr) { + mgr.registerChecker<CallGraphViewer>(); +} + +//===----------------------------------------------------------------------===// +// CallGraphDumper +//===----------------------------------------------------------------------===// + +namespace { +class CallGraphDumper : public Checker< check::ASTDecl<TranslationUnitDecl> > { +public: + void checkASTDecl(const TranslationUnitDecl *TU, AnalysisManager& mgr, + BugReporter &BR) const { + CallGraph CG; + CG.addToCallGraph(const_cast<TranslationUnitDecl*>(TU)); + CG.dump(); + } +}; +} + +void ento::registerCallGraphDumper(CheckerManager &mgr) { + mgr.registerChecker<CallGraphDumper>(); +} diff --git a/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp b/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp index eeda734..81a2745 100644 --- a/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp @@ -13,10 +13,12 @@ //===----------------------------------------------------------------------===// #include "ClangSACheckers.h" +#include "clang/AST/ExprObjC.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "llvm/ADT/SmallString.h" using namespace clang; using namespace ento; @@ -25,35 +27,42 @@ namespace { class DereferenceChecker : public Checker< check::Location, EventDispatcher<ImplicitNullDerefEvent> > { - mutable llvm::OwningPtr<BuiltinBug> BT_null; - mutable llvm::OwningPtr<BuiltinBug> BT_undef; + mutable OwningPtr<BuiltinBug> BT_null; + mutable OwningPtr<BuiltinBug> BT_undef; public: void checkLocation(SVal location, bool isLoad, const Stmt* S, CheckerContext &C) const; - static void AddDerefSource(raw_ostream &os, + static const MemRegion *AddDerefSource(raw_ostream &os, SmallVectorImpl<SourceRange> &Ranges, - const Expr *Ex, bool loadedFrom = false); + const Expr *Ex, const ProgramState *state, + const LocationContext *LCtx, + bool loadedFrom = false); }; } // end anonymous namespace -void DereferenceChecker::AddDerefSource(raw_ostream &os, - SmallVectorImpl<SourceRange> &Ranges, - const Expr *Ex, - bool loadedFrom) { +const MemRegion * +DereferenceChecker::AddDerefSource(raw_ostream &os, + SmallVectorImpl<SourceRange> &Ranges, + const Expr *Ex, + const ProgramState *state, + const LocationContext *LCtx, + bool loadedFrom) { Ex = Ex->IgnoreParenLValueCasts(); + const MemRegion *sourceR = 0; switch (Ex->getStmtClass()) { default: - return; + break; case Stmt::DeclRefExprClass: { const DeclRefExpr *DR = cast<DeclRefExpr>(Ex); if (const VarDecl *VD = dyn_cast<VarDecl>(DR->getDecl())) { os << " (" << (loadedFrom ? "loaded from" : "from") << " variable '" << VD->getName() << "')"; Ranges.push_back(DR->getSourceRange()); + sourceR = state->getLValue(VD, LCtx).getAsRegion(); } - return; + break; } case Stmt::MemberExprClass: { const MemberExpr *ME = cast<MemberExpr>(Ex); @@ -64,6 +73,7 @@ void DereferenceChecker::AddDerefSource(raw_ostream &os, break; } } + return sourceR; } void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S, @@ -77,7 +87,7 @@ void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S, BugReport *report = new BugReport(*BT_undef, BT_undef->getDescription(), N); report->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, - bugreporter::GetDerefExpr(N))); + bugreporter::GetDerefExpr(N), report)); C.EmitReport(report); } return; @@ -89,8 +99,9 @@ void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S, if (!isa<Loc>(location)) return; - const ProgramState *state = C.getState(); - const ProgramState *notNullState, *nullState; + ProgramStateRef state = C.getState(); + const LocationContext *LCtx = C.getLocationContext(); + ProgramStateRef notNullState, nullState; llvm::tie(notNullState, nullState) = state->assume(location); // The explicit NULL case. @@ -106,20 +117,24 @@ void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S, if (!BT_null) BT_null.reset(new BuiltinBug("Dereference of null pointer")); - llvm::SmallString<100> buf; + SmallString<100> buf; SmallVector<SourceRange, 2> Ranges; // Walk through lvalue casts to get the original expression // that syntactically caused the load. if (const Expr *expr = dyn_cast<Expr>(S)) S = expr->IgnoreParenLValueCasts(); + + const MemRegion *sourceR = 0; switch (S->getStmtClass()) { case Stmt::ArraySubscriptExprClass: { llvm::raw_svector_ostream os(buf); os << "Array access"; const ArraySubscriptExpr *AE = cast<ArraySubscriptExpr>(S); - AddDerefSource(os, Ranges, AE->getBase()->IgnoreParenCasts()); + sourceR = + AddDerefSource(os, Ranges, AE->getBase()->IgnoreParenCasts(), + state.getPtr(), LCtx); os << " results in a null pointer dereference"; break; } @@ -127,7 +142,9 @@ void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S, llvm::raw_svector_ostream os(buf); os << "Dereference of null pointer"; const UnaryOperator *U = cast<UnaryOperator>(S); - AddDerefSource(os, Ranges, U->getSubExpr()->IgnoreParens(), true); + sourceR = + AddDerefSource(os, Ranges, U->getSubExpr()->IgnoreParens(), + state.getPtr(), LCtx, true); break; } case Stmt::MemberExprClass: { @@ -136,7 +153,9 @@ void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S, llvm::raw_svector_ostream os(buf); os << "Access to field '" << M->getMemberNameInfo() << "' results in a dereference of a null pointer"; - AddDerefSource(os, Ranges, M->getBase()->IgnoreParenCasts(), true); + sourceR = + AddDerefSource(os, Ranges, M->getBase()->IgnoreParenCasts(), + state.getPtr(), LCtx, true); } break; } @@ -163,12 +182,17 @@ void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S, N); report->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, - bugreporter::GetDerefExpr(N))); + bugreporter::GetDerefExpr(N), report)); for (SmallVectorImpl<SourceRange>::iterator I = Ranges.begin(), E = Ranges.end(); I!=E; ++I) report->addRange(*I); + if (sourceR) { + report->markInteresting(sourceR); + report->markInteresting(state->getRawSVal(loc::MemRegionVal(sourceR))); + } + C.EmitReport(report); return; } diff --git a/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp b/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp index 75b7cc4..2627f0c 100644 --- a/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp @@ -23,12 +23,31 @@ using namespace ento; namespace { class DivZeroChecker : public Checker< check::PreStmt<BinaryOperator> > { - mutable llvm::OwningPtr<BuiltinBug> BT; + mutable OwningPtr<BuiltinBug> BT; + void reportBug(const char *Msg, + ProgramStateRef StateZero, + CheckerContext &C) const ; public: void checkPreStmt(const BinaryOperator *B, CheckerContext &C) const; }; } // end anonymous namespace +void DivZeroChecker::reportBug(const char *Msg, + ProgramStateRef StateZero, + CheckerContext &C) const { + if (ExplodedNode *N = C.generateSink(StateZero)) { + if (!BT) + BT.reset(new BuiltinBug("Division by zero")); + + BugReport *R = + new BugReport(*BT, Msg, N); + + R->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, + bugreporter::GetDenomExpr(N), R)); + C.EmitReport(R); + } +} + void DivZeroChecker::checkPreStmt(const BinaryOperator *B, CheckerContext &C) const { BinaryOperator::Opcode Op = B->getOpcode(); @@ -42,7 +61,7 @@ void DivZeroChecker::checkPreStmt(const BinaryOperator *B, !B->getRHS()->getType()->isScalarType()) return; - SVal Denom = C.getState()->getSVal(B->getRHS()); + SVal Denom = C.getState()->getSVal(B->getRHS(), C.getLocationContext()); const DefinedSVal *DV = dyn_cast<DefinedSVal>(&Denom); // Divide-by-undefined handled in the generic checking for uses of @@ -52,22 +71,18 @@ void DivZeroChecker::checkPreStmt(const BinaryOperator *B, // Check for divide by zero. ConstraintManager &CM = C.getConstraintManager(); - const ProgramState *stateNotZero, *stateZero; + ProgramStateRef stateNotZero, stateZero; llvm::tie(stateNotZero, stateZero) = CM.assumeDual(C.getState(), *DV); - if (stateZero && !stateNotZero) { - if (ExplodedNode *N = C.generateSink(stateZero)) { - if (!BT) - BT.reset(new BuiltinBug("Division by zero")); - - BugReport *R = - new BugReport(*BT, BT->getDescription(), N); - - R->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, - bugreporter::GetDenomExpr(N))); + if (!stateNotZero) { + assert(stateZero); + reportBug("Division by zero", stateZero, C); + return; + } - C.EmitReport(R); - } + bool TaintedD = C.getState()->isTainted(*DV); + if ((stateNotZero && stateZero && TaintedD)) { + reportBug("Division by a tainted value, possibly zero", stateZero, C); return; } diff --git a/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp b/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp index 531d87e..a1f2f3b 100644 --- a/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp @@ -25,7 +25,7 @@ using namespace ento; namespace { class FixedAddressChecker : public Checker< check::PreStmt<BinaryOperator> > { - mutable llvm::OwningPtr<BuiltinBug> BT; + mutable OwningPtr<BuiltinBug> BT; public: void checkPreStmt(const BinaryOperator *B, CheckerContext &C) const; @@ -44,14 +44,13 @@ void FixedAddressChecker::checkPreStmt(const BinaryOperator *B, if (!T->isPointerType()) return; - const ProgramState *state = C.getState(); - - SVal RV = state->getSVal(B->getRHS()); + ProgramStateRef state = C.getState(); + SVal RV = state->getSVal(B->getRHS(), C.getLocationContext()); if (!RV.isConstant() || RV.isZeroConstant()) return; - if (ExplodedNode *N = C.generateNode()) { + if (ExplodedNode *N = C.addTransition()) { if (!BT) BT.reset(new BuiltinBug("Use fixed address", "Using a fixed address is not portable because that " diff --git a/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp b/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp new file mode 100644 index 0000000..135b81d --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp @@ -0,0 +1,740 @@ +//== GenericTaintChecker.cpp ----------------------------------- -*- C++ -*--=// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This checker defines the attack surface for generic taint propagation. +// +// The taint information produced by it might be useful to other checkers. For +// example, checkers should report errors which involve tainted data more +// aggressively, even if the involved symbols are under constrained. +// +//===----------------------------------------------------------------------===// +#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/Basic/Builtins.h" +#include <climits> + +using namespace clang; +using namespace ento; + +namespace { +class GenericTaintChecker : public Checker< check::PostStmt<CallExpr>, + check::PreStmt<CallExpr> > { +public: + static void *getTag() { static int Tag; return &Tag; } + + void checkPostStmt(const CallExpr *CE, CheckerContext &C) const; + void checkPostStmt(const DeclRefExpr *DRE, CheckerContext &C) const; + + void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; + +private: + static const unsigned InvalidArgIndex = UINT_MAX; + /// Denotes the return vale. + static const unsigned ReturnValueIndex = UINT_MAX - 1; + + mutable OwningPtr<BugType> BT; + inline void initBugType() const { + if (!BT) + BT.reset(new BugType("Use of Untrusted Data", "Untrusted Data")); + } + + /// \brief Catch taint related bugs. Check if tainted data is passed to a + /// system call etc. + bool checkPre(const CallExpr *CE, CheckerContext &C) const; + + /// \brief Add taint sources on a pre-visit. + void addSourcesPre(const CallExpr *CE, CheckerContext &C) const; + + /// \brief Propagate taint generated at pre-visit. + bool propagateFromPre(const CallExpr *CE, CheckerContext &C) const; + + /// \brief Add taint sources on a post visit. + void addSourcesPost(const CallExpr *CE, CheckerContext &C) const; + + /// Check if the region the expression evaluates to is the standard input, + /// and thus, is tainted. + static bool isStdin(const Expr *E, CheckerContext &C); + + /// \brief Given a pointer argument, get the symbol of the value it contains + /// (points to). + static SymbolRef getPointedToSymbol(CheckerContext &C, const Expr *Arg); + + /// Functions defining the attack surface. + typedef ProgramStateRef (GenericTaintChecker::*FnCheck)(const CallExpr *, + CheckerContext &C) const; + ProgramStateRef postScanf(const CallExpr *CE, CheckerContext &C) const; + ProgramStateRef postSocket(const CallExpr *CE, CheckerContext &C) const; + ProgramStateRef postRetTaint(const CallExpr *CE, CheckerContext &C) const; + + /// Taint the scanned input if the file is tainted. + ProgramStateRef preFscanf(const CallExpr *CE, CheckerContext &C) const; + + /// Check for CWE-134: Uncontrolled Format String. + static const char MsgUncontrolledFormatString[]; + bool checkUncontrolledFormatString(const CallExpr *CE, + CheckerContext &C) const; + + /// Check for: + /// CERT/STR02-C. "Sanitize data passed to complex subsystems" + /// CWE-78, "Failure to Sanitize Data into an OS Command" + static const char MsgSanitizeSystemArgs[]; + bool checkSystemCall(const CallExpr *CE, StringRef Name, + CheckerContext &C) const; + + /// Check if tainted data is used as a buffer size ins strn.. functions, + /// and allocators. + static const char MsgTaintedBufferSize[]; + bool checkTaintedBufferSize(const CallExpr *CE, const FunctionDecl *FDecl, + CheckerContext &C) const; + + /// Generate a report if the expression is tainted or points to tainted data. + bool generateReportIfTainted(const Expr *E, const char Msg[], + CheckerContext &C) const; + + + typedef llvm::SmallVector<unsigned, 2> ArgVector; + + /// \brief A struct used to specify taint propagation rules for a function. + /// + /// If any of the possible taint source arguments is tainted, all of the + /// destination arguments should also be tainted. Use InvalidArgIndex in the + /// src list to specify that all of the arguments can introduce taint. Use + /// InvalidArgIndex in the dst arguments to signify that all the non-const + /// pointer and reference arguments might be tainted on return. If + /// ReturnValueIndex is added to the dst list, the return value will be + /// tainted. + struct TaintPropagationRule { + /// List of arguments which can be taint sources and should be checked. + ArgVector SrcArgs; + /// List of arguments which should be tainted on function return. + ArgVector DstArgs; + // TODO: Check if using other data structures would be more optimal. + + TaintPropagationRule() {} + + TaintPropagationRule(unsigned SArg, + unsigned DArg, bool TaintRet = false) { + SrcArgs.push_back(SArg); + DstArgs.push_back(DArg); + if (TaintRet) + DstArgs.push_back(ReturnValueIndex); + } + + TaintPropagationRule(unsigned SArg1, unsigned SArg2, + unsigned DArg, bool TaintRet = false) { + SrcArgs.push_back(SArg1); + SrcArgs.push_back(SArg2); + DstArgs.push_back(DArg); + if (TaintRet) + DstArgs.push_back(ReturnValueIndex); + } + + /// Get the propagation rule for a given function. + static TaintPropagationRule + getTaintPropagationRule(const FunctionDecl *FDecl, + StringRef Name, + CheckerContext &C); + + inline void addSrcArg(unsigned A) { SrcArgs.push_back(A); } + inline void addDstArg(unsigned A) { DstArgs.push_back(A); } + + inline bool isNull() const { return SrcArgs.empty(); } + + inline bool isDestinationArgument(unsigned ArgNum) const { + return (std::find(DstArgs.begin(), + DstArgs.end(), ArgNum) != DstArgs.end()); + } + + 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)))); + } + + /// \brief Pre-process a function which propagates taint according to the + /// taint rule. + ProgramStateRef process(const CallExpr *CE, CheckerContext &C) const; + + }; +}; + +const unsigned GenericTaintChecker::ReturnValueIndex; +const unsigned GenericTaintChecker::InvalidArgIndex; + +const char GenericTaintChecker::MsgUncontrolledFormatString[] = + "Untrusted data is used as a format string " + "(CWE-134: Uncontrolled Format String)"; + +const char GenericTaintChecker::MsgSanitizeSystemArgs[] = + "Untrusted data is passed to a system call " + "(CERT/STR02-C. Sanitize data passed to complex subsystems)"; + +const char GenericTaintChecker::MsgTaintedBufferSize[] = + "Untrusted data is used to specify the buffer size " + "(CERT/STR31-C. Guarantee that storage for strings has sufficient space for " + "character data and the null terminator)"; + +} // end of anonymous namespace + +/// A set which is used to pass information from call pre-visit instruction +/// to the call post-visit. The values are unsigned integers, which are either +/// ReturnValueIndex, or indexes of the pointer/reference argument, which +/// points to data, which should be tainted on return. +namespace { struct TaintArgsOnPostVisit{}; } +namespace clang { namespace ento { +template<> struct ProgramStateTrait<TaintArgsOnPostVisit> + : public ProgramStatePartialTrait<llvm::ImmutableSet<unsigned> > { + static void *GDMIndex() { return GenericTaintChecker::getTag(); } +}; +}} + +GenericTaintChecker::TaintPropagationRule +GenericTaintChecker::TaintPropagationRule::getTaintPropagationRule( + const FunctionDecl *FDecl, + StringRef Name, + CheckerContext &C) { + // TODO: Currently, we might loose precision here: we always mark a return + // value as tainted even if it's just a pointer, pointing to tainted data. + + // Check for exact name match for functions without builtin substitutes. + TaintPropagationRule Rule = llvm::StringSwitch<TaintPropagationRule>(Name) + .Case("atoi", TaintPropagationRule(0, ReturnValueIndex)) + .Case("atol", TaintPropagationRule(0, ReturnValueIndex)) + .Case("atoll", TaintPropagationRule(0, ReturnValueIndex)) + .Case("getc", TaintPropagationRule(0, ReturnValueIndex)) + .Case("fgetc", TaintPropagationRule(0, ReturnValueIndex)) + .Case("getc_unlocked", TaintPropagationRule(0, ReturnValueIndex)) + .Case("getw", TaintPropagationRule(0, ReturnValueIndex)) + .Case("toupper", TaintPropagationRule(0, ReturnValueIndex)) + .Case("tolower", TaintPropagationRule(0, ReturnValueIndex)) + .Case("strchr", TaintPropagationRule(0, ReturnValueIndex)) + .Case("strrchr", TaintPropagationRule(0, ReturnValueIndex)) + .Case("read", TaintPropagationRule(0, 2, 1, true)) + .Case("pread", TaintPropagationRule(InvalidArgIndex, 1, true)) + .Case("gets", TaintPropagationRule(InvalidArgIndex, 0, true)) + .Case("fgets", TaintPropagationRule(2, 0, true)) + .Case("getline", TaintPropagationRule(2, 0)) + .Case("getdelim", TaintPropagationRule(3, 0)) + .Case("fgetln", TaintPropagationRule(0, ReturnValueIndex)) + .Default(TaintPropagationRule()); + + if (!Rule.isNull()) + return Rule; + + // Check if it's one of the memory setting/copying functions. + // This check is specialized but faster then calling isCLibraryFunction. + unsigned BId = 0; + if ( (BId = FDecl->getMemoryFunctionKind()) ) + switch(BId) { + case Builtin::BImemcpy: + case Builtin::BImemmove: + case Builtin::BIstrncpy: + case Builtin::BIstrncat: + return TaintPropagationRule(1, 2, 0, true); + case Builtin::BIstrlcpy: + case Builtin::BIstrlcat: + return TaintPropagationRule(1, 2, 0, false); + case Builtin::BIstrndup: + return TaintPropagationRule(0, 1, ReturnValueIndex); + + default: + break; + }; + + // Process all other functions which could be defined as builtins. + if (Rule.isNull()) { + if (C.isCLibraryFunction(FDecl, "snprintf") || + C.isCLibraryFunction(FDecl, "sprintf")) + return TaintPropagationRule(InvalidArgIndex, 0, true); + else if (C.isCLibraryFunction(FDecl, "strcpy") || + C.isCLibraryFunction(FDecl, "stpcpy") || + C.isCLibraryFunction(FDecl, "strcat")) + return TaintPropagationRule(1, 0, true); + else if (C.isCLibraryFunction(FDecl, "bcopy")) + return TaintPropagationRule(0, 2, 1, false); + else if (C.isCLibraryFunction(FDecl, "strdup") || + C.isCLibraryFunction(FDecl, "strdupa")) + return TaintPropagationRule(0, ReturnValueIndex); + else if (C.isCLibraryFunction(FDecl, "wcsdup")) + return TaintPropagationRule(0, ReturnValueIndex); + } + + // Skipping the following functions, since they might be used for cleansing + // or smart memory copy: + // - memccpy - copying untill hitting a special character. + + return TaintPropagationRule(); +} + +void GenericTaintChecker::checkPreStmt(const CallExpr *CE, + CheckerContext &C) const { + // Check for errors first. + if (checkPre(CE, C)) + return; + + // Add taint second. + addSourcesPre(CE, C); +} + +void GenericTaintChecker::checkPostStmt(const CallExpr *CE, + CheckerContext &C) const { + if (propagateFromPre(CE, C)) + return; + addSourcesPost(CE, C); +} + +void GenericTaintChecker::addSourcesPre(const CallExpr *CE, + CheckerContext &C) const { + ProgramStateRef State = 0; + const FunctionDecl *FDecl = C.getCalleeDecl(CE); + StringRef Name = C.getCalleeName(FDecl); + if (Name.empty()) + return; + + // First, try generating a propagation rule for this function. + TaintPropagationRule Rule = + TaintPropagationRule::getTaintPropagationRule(FDecl, Name, C); + if (!Rule.isNull()) { + State = Rule.process(CE, C); + if (!State) + return; + C.addTransition(State); + return; + } + + // Otherwise, check if we have custom pre-processing implemented. + FnCheck evalFunction = llvm::StringSwitch<FnCheck>(Name) + .Case("fscanf", &GenericTaintChecker::preFscanf) + .Default(0); + // Check and evaluate the call. + if (evalFunction) + State = (this->*evalFunction)(CE, C); + if (!State) + return; + C.addTransition(State); + +} + +bool GenericTaintChecker::propagateFromPre(const CallExpr *CE, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + + // Depending on what was tainted at pre-visit, we determined a set of + // arguments which should be tainted after the function returns. These are + // stored in the state as TaintArgsOnPostVisit set. + llvm::ImmutableSet<unsigned> TaintArgs = State->get<TaintArgsOnPostVisit>(); + if (TaintArgs.isEmpty()) + return false; + + for (llvm::ImmutableSet<unsigned>::iterator + I = TaintArgs.begin(), E = TaintArgs.end(); I != E; ++I) { + unsigned ArgNum = *I; + + // Special handling for the tainted return value. + if (ArgNum == ReturnValueIndex) { + State = State->addTaint(CE, C.getLocationContext()); + continue; + } + + // The arguments are pointer arguments. The data they are pointing at is + // tainted after the call. + if (CE->getNumArgs() < (ArgNum + 1)) + return false; + const Expr* Arg = CE->getArg(ArgNum); + SymbolRef Sym = getPointedToSymbol(C, Arg); + if (Sym) + State = State->addTaint(Sym); + } + + // Clear up the taint info from the state. + State = State->remove<TaintArgsOnPostVisit>(); + + if (State != C.getState()) { + C.addTransition(State); + return true; + } + return false; +} + +void GenericTaintChecker::addSourcesPost(const CallExpr *CE, + CheckerContext &C) const { + // Define the attack surface. + // Set the evaluation function by switching on the callee name. + StringRef Name = C.getCalleeName(CE); + if (Name.empty()) + return; + FnCheck evalFunction = llvm::StringSwitch<FnCheck>(Name) + .Case("scanf", &GenericTaintChecker::postScanf) + // TODO: Add support for vfscanf & family. + .Case("getchar", &GenericTaintChecker::postRetTaint) + .Case("getchar_unlocked", &GenericTaintChecker::postRetTaint) + .Case("getenv", &GenericTaintChecker::postRetTaint) + .Case("fopen", &GenericTaintChecker::postRetTaint) + .Case("fdopen", &GenericTaintChecker::postRetTaint) + .Case("freopen", &GenericTaintChecker::postRetTaint) + .Case("getch", &GenericTaintChecker::postRetTaint) + .Case("wgetch", &GenericTaintChecker::postRetTaint) + .Case("socket", &GenericTaintChecker::postSocket) + .Default(0); + + // If the callee isn't defined, it is not of security concern. + // Check and evaluate the call. + ProgramStateRef State = 0; + if (evalFunction) + State = (this->*evalFunction)(CE, C); + if (!State) + return; + + C.addTransition(State); +} + +bool GenericTaintChecker::checkPre(const CallExpr *CE, CheckerContext &C) const{ + + if (checkUncontrolledFormatString(CE, C)) + return true; + + const FunctionDecl *FDecl = C.getCalleeDecl(CE); + StringRef Name = C.getCalleeName(FDecl); + if (Name.empty()) + return false; + + if (checkSystemCall(CE, Name, C)) + return true; + + if (checkTaintedBufferSize(CE, FDecl, C)) + return true; + + return false; +} + +SymbolRef GenericTaintChecker::getPointedToSymbol(CheckerContext &C, + const Expr* Arg) { + ProgramStateRef State = C.getState(); + SVal AddrVal = State->getSVal(Arg->IgnoreParens(), C.getLocationContext()); + if (AddrVal.isUnknownOrUndef()) + return 0; + + Loc *AddrLoc = dyn_cast<Loc>(&AddrVal); + if (!AddrLoc) + return 0; + + const PointerType *ArgTy = + dyn_cast<PointerType>(Arg->getType().getCanonicalType().getTypePtr()); + SVal Val = State->getSVal(*AddrLoc, + ArgTy ? ArgTy->getPointeeType(): QualType()); + return Val.getAsSymbol(); +} + +ProgramStateRef +GenericTaintChecker::TaintPropagationRule::process(const CallExpr *CE, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + + // Check for taint in arguments. + bool IsTainted = false; + for (ArgVector::const_iterator I = SrcArgs.begin(), + E = SrcArgs.end(); I != E; ++I) { + unsigned ArgNum = *I; + + if (ArgNum == InvalidArgIndex) { + // Check if any of the arguments is tainted, but skip the + // destination arguments. + for (unsigned int i = 0; i < CE->getNumArgs(); ++i) { + if (isDestinationArgument(i)) + continue; + if ((IsTainted = isTaintedOrPointsToTainted(CE->getArg(i), State, C))) + break; + } + break; + } + + if (CE->getNumArgs() < (ArgNum + 1)) + return State; + if ((IsTainted = isTaintedOrPointsToTainted(CE->getArg(ArgNum), State, C))) + break; + } + if (!IsTainted) + return State; + + // Mark the arguments which should be tainted after the function returns. + for (ArgVector::const_iterator I = DstArgs.begin(), + E = DstArgs.end(); I != E; ++I) { + unsigned ArgNum = *I; + + // Should we mark all arguments as tainted? + if (ArgNum == InvalidArgIndex) { + // For all pointer and references that were passed in: + // If they are not pointing to const data, mark data as tainted. + // TODO: So far we are just going one level down; ideally we'd need to + // recurse here. + for (unsigned int i = 0; i < CE->getNumArgs(); ++i) { + const Expr *Arg = CE->getArg(i); + // Process pointer argument. + const Type *ArgTy = Arg->getType().getTypePtr(); + QualType PType = ArgTy->getPointeeType(); + if ((!PType.isNull() && !PType.isConstQualified()) + || (ArgTy->isReferenceType() && !Arg->getType().isConstQualified())) + State = State->add<TaintArgsOnPostVisit>(i); + } + continue; + } + + // Should mark the return value? + if (ArgNum == ReturnValueIndex) { + State = State->add<TaintArgsOnPostVisit>(ReturnValueIndex); + continue; + } + + // Mark the given argument. + assert(ArgNum < CE->getNumArgs()); + State = State->add<TaintArgsOnPostVisit>(ArgNum); + } + + return State; +} + + +// If argument 0 (file descriptor) is tainted, all arguments except for arg 0 +// and arg 1 should get taint. +ProgramStateRef GenericTaintChecker::preFscanf(const CallExpr *CE, + CheckerContext &C) const { + assert(CE->getNumArgs() >= 2); + ProgramStateRef State = C.getState(); + + // Check is the file descriptor is tainted. + if (State->isTainted(CE->getArg(0), C.getLocationContext()) || + isStdin(CE->getArg(0), C)) { + // All arguments except for the first two should get taint. + for (unsigned int i = 2; i < CE->getNumArgs(); ++i) + State = State->add<TaintArgsOnPostVisit>(i); + return State; + } + + return 0; +} + + +// If argument 0(protocol domain) is network, the return value should get taint. +ProgramStateRef GenericTaintChecker::postSocket(const CallExpr *CE, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + if (CE->getNumArgs() < 3) + return State; + + SourceLocation DomLoc = CE->getArg(0)->getExprLoc(); + StringRef DomName = C.getMacroNameOrSpelling(DomLoc); + // White list the internal communication protocols. + if (DomName.equals("AF_SYSTEM") || DomName.equals("AF_LOCAL") || + DomName.equals("AF_UNIX") || DomName.equals("AF_RESERVED_36")) + return State; + State = State->addTaint(CE, C.getLocationContext()); + return State; +} + +ProgramStateRef GenericTaintChecker::postScanf(const CallExpr *CE, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + if (CE->getNumArgs() < 2) + return State; + + SVal x = State->getSVal(CE->getArg(1), C.getLocationContext()); + // All arguments except for the very first one should get taint. + for (unsigned int i = 1; i < CE->getNumArgs(); ++i) { + // 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); + } + return State; +} + +ProgramStateRef GenericTaintChecker::postRetTaint(const CallExpr *CE, + CheckerContext &C) const { + return C.getState()->addTaint(CE, C.getLocationContext()); +} + +bool GenericTaintChecker::isStdin(const Expr *E, CheckerContext &C) { + ProgramStateRef State = C.getState(); + SVal Val = State->getSVal(E, C.getLocationContext()); + + // stdin is a pointer, so it would be a region. + const MemRegion *MemReg = Val.getAsRegion(); + + // The region should be symbolic, we do not know it's value. + const SymbolicRegion *SymReg = dyn_cast_or_null<SymbolicRegion>(MemReg); + if (!SymReg) + return false; + + // Get it's symbol and find the declaration region it's pointing to. + const SymbolRegionValue *Sm =dyn_cast<SymbolRegionValue>(SymReg->getSymbol()); + if (!Sm) + return false; + const DeclRegion *DeclReg = dyn_cast_or_null<DeclRegion>(Sm->getRegion()); + if (!DeclReg) + return false; + + // This region corresponds to a declaration, find out if it's a global/extern + // variable named stdin with the proper type. + if (const VarDecl *D = dyn_cast_or_null<VarDecl>(DeclReg->getDecl())) { + D = D->getCanonicalDecl(); + if ((D->getName().find("stdin") != StringRef::npos) && D->isExternC()) + if (const PointerType * PtrTy = + dyn_cast<PointerType>(D->getType().getTypePtr())) + if (PtrTy->getPointeeType() == C.getASTContext().getFILEType()) + return true; + } + return false; +} + +static bool getPrintfFormatArgumentNum(const CallExpr *CE, + const CheckerContext &C, + unsigned int &ArgNum) { + // Find if the function contains a format string argument. + // Handles: fprintf, printf, sprintf, snprintf, vfprintf, vprintf, vsprintf, + // vsnprintf, syslog, custom annotated functions. + const FunctionDecl *FDecl = C.getCalleeDecl(CE); + if (!FDecl) + return false; + for (specific_attr_iterator<FormatAttr> + i = FDecl->specific_attr_begin<FormatAttr>(), + e = FDecl->specific_attr_end<FormatAttr>(); i != e ; ++i) { + + const FormatAttr *Format = *i; + ArgNum = Format->getFormatIdx() - 1; + if ((Format->getType() == "printf") && CE->getNumArgs() > ArgNum) + return true; + } + + // Or if a function is named setproctitle (this is a heuristic). + if (C.getCalleeName(CE).find("setproctitle") != StringRef::npos) { + ArgNum = 0; + return true; + } + + return false; +} + +bool GenericTaintChecker::generateReportIfTainted(const Expr *E, + const char Msg[], + CheckerContext &C) const { + assert(E); + + // Check for taint. + ProgramStateRef State = C.getState(); + if (!State->isTainted(getPointedToSymbol(C, E)) && + !State->isTainted(E, C.getLocationContext())) + return false; + + // Generate diagnostic. + if (ExplodedNode *N = C.addTransition()) { + initBugType(); + BugReport *report = new BugReport(*BT, Msg, N); + report->addRange(E->getSourceRange()); + C.EmitReport(report); + return true; + } + return false; +} + +bool GenericTaintChecker::checkUncontrolledFormatString(const CallExpr *CE, + CheckerContext &C) const{ + // Check if the function contains a format string argument. + unsigned int ArgNum = 0; + if (!getPrintfFormatArgumentNum(CE, C, ArgNum)) + return false; + + // If either the format string content or the pointer itself are tainted, warn. + if (generateReportIfTainted(CE->getArg(ArgNum), + MsgUncontrolledFormatString, C)) + return true; + return false; +} + +bool GenericTaintChecker::checkSystemCall(const CallExpr *CE, + StringRef Name, + CheckerContext &C) const { + // TODO: It might make sense to run this check on demand. In some cases, + // we should check if the environment has been cleansed here. We also might + // need to know if the user was reset before these calls(seteuid). + unsigned ArgNum = llvm::StringSwitch<unsigned>(Name) + .Case("system", 0) + .Case("popen", 0) + .Case("execl", 0) + .Case("execle", 0) + .Case("execlp", 0) + .Case("execv", 0) + .Case("execvp", 0) + .Case("execvP", 0) + .Case("execve", 0) + .Case("dlopen", 0) + .Default(UINT_MAX); + + if (ArgNum == UINT_MAX || CE->getNumArgs() < (ArgNum + 1)) + return false; + + if (generateReportIfTainted(CE->getArg(ArgNum), + MsgSanitizeSystemArgs, C)) + return true; + + return false; +} + +// TODO: Should this check be a part of the CString checker? +// If yes, should taint be a global setting? +bool GenericTaintChecker::checkTaintedBufferSize(const CallExpr *CE, + const FunctionDecl *FDecl, + CheckerContext &C) const { + // If the function has a buffer size argument, set ArgNum. + unsigned ArgNum = InvalidArgIndex; + unsigned BId = 0; + if ( (BId = FDecl->getMemoryFunctionKind()) ) + switch(BId) { + case Builtin::BImemcpy: + case Builtin::BImemmove: + case Builtin::BIstrncpy: + ArgNum = 2; + break; + case Builtin::BIstrndup: + ArgNum = 1; + break; + default: + break; + }; + + if (ArgNum == InvalidArgIndex) { + if (C.isCLibraryFunction(FDecl, "malloc") || + C.isCLibraryFunction(FDecl, "calloc") || + C.isCLibraryFunction(FDecl, "alloca")) + ArgNum = 0; + else if (C.isCLibraryFunction(FDecl, "memccpy")) + ArgNum = 3; + else if (C.isCLibraryFunction(FDecl, "realloc")) + ArgNum = 1; + else if (C.isCLibraryFunction(FDecl, "bcopy")) + ArgNum = 2; + } + + if (ArgNum != InvalidArgIndex && CE->getNumArgs() > ArgNum && + generateReportIfTainted(CE->getArg(ArgNum), MsgTaintedBufferSize, C)) + return true; + + return false; +} + +void ento::registerGenericTaintChecker(CheckerManager &mgr) { + mgr.registerChecker<GenericTaintChecker>(); +} diff --git a/lib/StaticAnalyzer/Checkers/IdempotentOperationChecker.cpp b/lib/StaticAnalyzer/Checkers/IdempotentOperationChecker.cpp index 5c257e5..c08f163 100644 --- a/lib/StaticAnalyzer/Checkers/IdempotentOperationChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/IdempotentOperationChecker.cpp @@ -57,6 +57,7 @@ #include "clang/AST/Stmt.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/SmallSet.h" +#include "llvm/ADT/SmallString.h" #include "llvm/ADT/BitVector.h" #include "llvm/Support/ErrorHandling.h" @@ -82,16 +83,16 @@ private: // False positive reduction methods static bool isSelfAssign(const Expr *LHS, const Expr *RHS); - static bool isUnused(const Expr *E, AnalysisContext *AC); + static bool isUnused(const Expr *E, AnalysisDeclContext *AC); static bool isTruncationExtensionAssignment(const Expr *LHS, const Expr *RHS); - static bool pathWasCompletelyAnalyzed(AnalysisContext *AC, + static bool pathWasCompletelyAnalyzed(AnalysisDeclContext *AC, const CFGBlock *CB, const CoreEngine &CE); static bool CanVary(const Expr *Ex, - AnalysisContext *AC); + AnalysisDeclContext *AC); static bool isConstantOrPseudoConstant(const DeclRefExpr *DR, - AnalysisContext *AC); + AnalysisDeclContext *AC); static bool containsNonLocalVarDecl(const Stmt *S); // Hash table and related data structures @@ -116,7 +117,7 @@ void IdempotentOperationChecker::checkPreStmt(const BinaryOperator *B, // been created yet. BinaryOperatorData &Data = hash[B]; Assumption &A = Data.assumption; - AnalysisContext *AC = C.getCurrentAnalysisContext(); + AnalysisDeclContext *AC = C.getCurrentAnalysisDeclContext(); // If we already have visited this node on a path that does not contain an // idempotent operation, return immediately. @@ -141,10 +142,10 @@ void IdempotentOperationChecker::checkPreStmt(const BinaryOperator *B, || containsNonLocalVarDecl(RHS); } - const ProgramState *state = C.getState(); - - SVal LHSVal = state->getSVal(LHS); - SVal RHSVal = state->getSVal(RHS); + ProgramStateRef state = C.getState(); + const LocationContext *LCtx = C.getLocationContext(); + SVal LHSVal = state->getSVal(LHS, LCtx); + SVal RHSVal = state->getSVal(RHS, LCtx); // If either value is unknown, we can't be 100% sure of all paths. if (LHSVal.isUnknownOrUndef() || RHSVal.isUnknownOrUndef()) { @@ -366,8 +367,8 @@ void IdempotentOperationChecker::checkEndAnalysis(ExplodedGraph &G, // warning if (Eng.hasWorkRemaining()) { // If we can trace back - AnalysisContext *AC = (*ES.begin())->getLocationContext() - ->getAnalysisContext(); + AnalysisDeclContext *AC = (*ES.begin())->getLocationContext() + ->getAnalysisDeclContext(); if (!pathWasCompletelyAnalyzed(AC, AC->getCFGStmtMap()->getBlock(B), Eng.getCoreEngine())) @@ -375,7 +376,7 @@ void IdempotentOperationChecker::checkEndAnalysis(ExplodedGraph &G, } // Select the error message and SourceRanges to report. - llvm::SmallString<128> buf; + SmallString<128> buf; llvm::raw_svector_ostream os(buf); bool LHSRelevant = false, RHSRelevant = false; switch (A) { @@ -487,7 +488,7 @@ bool IdempotentOperationChecker::isSelfAssign(const Expr *LHS, const Expr *RHS) // Returns true if the Expr points to a VarDecl that is not read anywhere // outside of self-assignments. bool IdempotentOperationChecker::isUnused(const Expr *E, - AnalysisContext *AC) { + AnalysisDeclContext *AC) { if (!E) return false; @@ -531,7 +532,7 @@ bool IdempotentOperationChecker::isTruncationExtensionAssignment( // Returns false if a path to this block was not completely analyzed, or true // otherwise. bool -IdempotentOperationChecker::pathWasCompletelyAnalyzed(AnalysisContext *AC, +IdempotentOperationChecker::pathWasCompletelyAnalyzed(AnalysisDeclContext *AC, const CFGBlock *CB, const CoreEngine &CE) { @@ -615,7 +616,7 @@ IdempotentOperationChecker::pathWasCompletelyAnalyzed(AnalysisContext *AC, // expression may also involve a variable that behaves like a constant. The // function returns true if the expression varies, and false otherwise. bool IdempotentOperationChecker::CanVary(const Expr *Ex, - AnalysisContext *AC) { + AnalysisDeclContext *AC) { // Parentheses and casts are irrelevant here Ex = Ex->IgnoreParenCasts(); @@ -649,7 +650,6 @@ bool IdempotentOperationChecker::CanVary(const Expr *Ex, case Stmt::InitListExprClass: case Stmt::DesignatedInitExprClass: case Stmt::BlockExprClass: - case Stmt::BlockDeclRefExprClass: return false; // Cases requiring custom logic @@ -699,7 +699,7 @@ bool IdempotentOperationChecker::CanVary(const Expr *Ex, // Returns true if a DeclRefExpr is or behaves like a constant. bool IdempotentOperationChecker::isConstantOrPseudoConstant( const DeclRefExpr *DR, - AnalysisContext *AC) { + AnalysisDeclContext *AC) { // Check if the type of the Decl is const-qualified if (DR->getType().isConstQualified()) return true; diff --git a/lib/StaticAnalyzer/Checkers/InterCheckerAPI.h b/lib/StaticAnalyzer/Checkers/InterCheckerAPI.h new file mode 100644 index 0000000..e35557f --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/InterCheckerAPI.h @@ -0,0 +1,22 @@ +//==--- InterCheckerAPI.h ---------------------------------------*- C++ -*-==// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// This file allows introduction of checker dependencies. It contains APIs for +// inter-checker communications. +//===----------------------------------------------------------------------===// + +#ifndef INTERCHECKERAPI_H_ +#define INTERCHECKERAPI_H_ +namespace clang { +namespace ento { + +/// Register the checker which evaluates CString API calls. +void registerCStringCheckerBasic(CheckerManager &Mgr); + +}} +#endif /* INTERCHECKERAPI_H_ */ diff --git a/lib/StaticAnalyzer/Checkers/IteratorsChecker.cpp b/lib/StaticAnalyzer/Checkers/IteratorsChecker.cpp index fbc57d3..b0bac33 100644 --- a/lib/StaticAnalyzer/Checkers/IteratorsChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/IteratorsChecker.cpp @@ -22,7 +22,7 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "clang/AST/DeclCXX.h" -#include "clang/AST/Decl.h" +#include "clang/AST/ExprCXX.h" #include "clang/AST/Type.h" #include "clang/AST/PrettyPrinter.h" #include "llvm/ADT/SmallPtrSet.h" @@ -117,17 +117,17 @@ public: CheckerContext &C) const; private: - const ProgramState *handleAssign(const ProgramState *state, + ProgramStateRef handleAssign(ProgramStateRef state, const Expr *lexp, const Expr *rexp, const LocationContext *LC) const; - const ProgramState *handleAssign(const ProgramState *state, + ProgramStateRef handleAssign(ProgramStateRef state, const MemRegion *MR, const Expr *rexp, const LocationContext *LC) const; - const ProgramState *invalidateIterators(const ProgramState *state, + ProgramStateRef invalidateIterators(ProgramStateRef state, const MemRegion *MR, const MemberExpr *ME) const; @@ -135,7 +135,7 @@ private: void checkArgs(CheckerContext &C, const CallExpr *CE) const; - const MemRegion *getRegion(const ProgramState *state, + const MemRegion *getRegion(ProgramStateRef state, const Expr *E, const LocationContext *LC) const; @@ -227,7 +227,7 @@ static RefKind getTemplateKind(QualType T) { // Iterate through our map and invalidate any iterators that were // initialized fromt the specified instance MemRegion. -const ProgramState *IteratorsChecker::invalidateIterators(const ProgramState *state, +ProgramStateRef IteratorsChecker::invalidateIterators(ProgramStateRef state, const MemRegion *MR, const MemberExpr *ME) const { IteratorState::EntryMap Map = state->get<IteratorState>(); if (Map.isEmpty()) @@ -246,7 +246,7 @@ const ProgramState *IteratorsChecker::invalidateIterators(const ProgramState *st } // Handle assigning to an iterator where we don't have the LValue MemRegion. -const ProgramState *IteratorsChecker::handleAssign(const ProgramState *state, +ProgramStateRef IteratorsChecker::handleAssign(ProgramStateRef state, const Expr *lexp, const Expr *rexp, const LocationContext *LC) const { // Skip the cast if present. if (const MaterializeTemporaryExpr *M @@ -254,7 +254,7 @@ const ProgramState *IteratorsChecker::handleAssign(const ProgramState *state, lexp = M->GetTemporaryExpr(); if (const ImplicitCastExpr *ICE = dyn_cast<ImplicitCastExpr>(lexp)) lexp = ICE->getSubExpr(); - SVal sv = state->getSVal(lexp); + SVal sv = state->getSVal(lexp, LC); const MemRegion *MR = sv.getAsRegion(); if (!MR) return state; @@ -271,7 +271,7 @@ const ProgramState *IteratorsChecker::handleAssign(const ProgramState *state, } // handle assigning to an iterator -const ProgramState *IteratorsChecker::handleAssign(const ProgramState *state, +ProgramStateRef IteratorsChecker::handleAssign(ProgramStateRef state, const MemRegion *MR, const Expr *rexp, const LocationContext *LC) const { // Assume unknown until we find something definite. state = state->set<IteratorState>(MR, RefState::getUnknown()); @@ -376,7 +376,7 @@ const DeclRefExpr *IteratorsChecker::getDeclRefExpr(const Expr *E) const { } // Get the MemRegion associated with the expresssion. -const MemRegion *IteratorsChecker::getRegion(const ProgramState *state, +const MemRegion *IteratorsChecker::getRegion(ProgramStateRef state, const Expr *E, const LocationContext *LC) const { const DeclRefExpr *DRE = getDeclRefExpr(E); if (!DRE) @@ -394,9 +394,8 @@ const MemRegion *IteratorsChecker::getRegion(const ProgramState *state, // use those nodes. We also cannot create multiple nodes at one ProgramPoint // with the same tag. void IteratorsChecker::checkExpr(CheckerContext &C, const Expr *E) const { - const ProgramState *state = C.getState(); - const MemRegion *MR = getRegion(state, E, - C.getPredecessor()->getLocationContext()); + ProgramStateRef state = C.getState(); + const MemRegion *MR = getRegion(state, E, C.getLocationContext()); if (!MR) return; @@ -405,7 +404,7 @@ void IteratorsChecker::checkExpr(CheckerContext &C, const Expr *E) const { if (!RS) return; if (RS->isInvalid()) { - if (ExplodedNode *N = C.generateNode()) { + if (ExplodedNode *N = C.addTransition()) { if (!BT_Invalid) // FIXME: We are eluding constness here. const_cast<IteratorsChecker*>(this)->BT_Invalid = new BuiltinBug(""); @@ -428,7 +427,7 @@ void IteratorsChecker::checkExpr(CheckerContext &C, const Expr *E) const { } } else if (RS->isUndefined()) { - if (ExplodedNode *N = C.generateNode()) { + if (ExplodedNode *N = C.addTransition()) { if (!BT_Undefined) // FIXME: We are eluding constness here. const_cast<IteratorsChecker*>(this)->BT_Undefined = @@ -466,8 +465,8 @@ void IteratorsChecker::checkPreStmt(const CallExpr *CE, void IteratorsChecker::checkPreStmt(const CXXOperatorCallExpr *OCE, CheckerContext &C) const { - const LocationContext *LC = C.getPredecessor()->getLocationContext(); - const ProgramState *state = C.getState(); + const LocationContext *LC = C.getLocationContext(); + ProgramStateRef state = C.getState(); OverloadedOperatorKind Kind = OCE->getOperator(); if (Kind == OO_Equal) { checkExpr(C, OCE->getArg(1)); @@ -497,7 +496,7 @@ void IteratorsChecker::checkPreStmt(const CXXOperatorCallExpr *OCE, if (!RS1) return; if (RS0->getMemRegion() != RS1->getMemRegion()) { - if (ExplodedNode *N = C.generateNode()) { + if (ExplodedNode *N = C.addTransition()) { if (!BT_Incompatible) const_cast<IteratorsChecker*>(this)->BT_Incompatible = new BuiltinBug( @@ -524,8 +523,8 @@ void IteratorsChecker::checkPreStmt(const DeclStmt *DS, return; // Get the MemRegion associated with the iterator and mark it as Undefined. - const ProgramState *state = C.getState(); - Loc VarLoc = state->getLValue(VD, C.getPredecessor()->getLocationContext()); + ProgramStateRef state = C.getState(); + Loc VarLoc = state->getLValue(VD, C.getLocationContext()); const MemRegion *MR = VarLoc.getAsRegion(); if (!MR) return; @@ -545,8 +544,7 @@ void IteratorsChecker::checkPreStmt(const DeclStmt *DS, E = M->GetTemporaryExpr(); if (const ImplicitCastExpr *ICE = dyn_cast<ImplicitCastExpr>(E)) InitEx = ICE->getSubExpr(); - state = handleAssign(state, MR, InitEx, - C.getPredecessor()->getLocationContext()); + state = handleAssign(state, MR, InitEx, C.getLocationContext()); } } } @@ -576,14 +574,14 @@ void IteratorsChecker::checkPreStmt(const CXXMemberCallExpr *MCE, const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(ME->getBase()); if (!DRE || getTemplateKind(DRE->getType()) != VectorKind) return; - SVal tsv = C.getState()->getSVal(DRE); + SVal tsv = C.getState()->getSVal(DRE, C.getLocationContext()); // Get the MemRegion associated with the container instance. const MemRegion *MR = tsv.getAsRegion(); if (!MR) return; // If we are calling a function that invalidates iterators, mark them // appropriately by finding matching instances. - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); StringRef mName = ME->getMemberDecl()->getName(); if (llvm::StringSwitch<bool>(mName) .Cases("insert", "reserve", "push_back", true) diff --git a/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp b/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp index e398fcb..757a4ce 100644 --- a/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp @@ -17,8 +17,7 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/StmtVisitor.h" -#include <string> -#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/SmallString.h" using namespace clang; using namespace ento; @@ -116,8 +115,10 @@ static bool IsSmallVector(QualType T) { namespace { class StringRefCheckerVisitor : public StmtVisitor<StringRefCheckerVisitor> { BugReporter &BR; + const Decl *DeclWithIssue; public: - StringRefCheckerVisitor(BugReporter &br) : BR(br) {} + StringRefCheckerVisitor(const Decl *declWithIssue, BugReporter &br) + : BR(br), DeclWithIssue(declWithIssue) {} void VisitChildren(Stmt *S) { for (Stmt::child_iterator I = S->child_begin(), E = S->child_end() ; I != E; ++I) @@ -132,7 +133,7 @@ private: } // end anonymous namespace static void CheckStringRefAssignedTemporary(const Decl *D, BugReporter &BR) { - StringRefCheckerVisitor walker(BR); + StringRefCheckerVisitor walker(D, BR); walker.Visit(D->getBody()); } @@ -177,7 +178,7 @@ void StringRefCheckerVisitor::VisitVarDecl(VarDecl *VD) { "std::string that it outlives"; PathDiagnosticLocation VDLoc = PathDiagnosticLocation::createBegin(VD, BR.getSourceManager()); - BR.EmitBasicReport(desc, "LLVM Conventions", desc, + BR.EmitBasicReport(DeclWithIssue, desc, "LLVM Conventions", desc, VDLoc, Init->getSourceRange()); } @@ -253,7 +254,7 @@ void ASTFieldVisitor::Visit(FieldDecl *D) { } void ASTFieldVisitor::ReportError(QualType T) { - llvm::SmallString<1024> buf; + SmallString<1024> buf; llvm::raw_svector_ostream os(buf); os << "AST class '" << Root->getName() << "' has a field '" @@ -282,7 +283,7 @@ void ASTFieldVisitor::ReportError(QualType T) { // the class may be in the header file, for example). PathDiagnosticLocation L = PathDiagnosticLocation::createBegin( FieldChain.front(), BR.getSourceManager()); - BR.EmitBasicReport("AST node allocates heap memory", "LLVM Conventions", + BR.EmitBasicReport(Root, "AST node allocates heap memory", "LLVM Conventions", os.str(), L); } diff --git a/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp b/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp index 2607db8..cb976e0 100644 --- a/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp @@ -19,6 +19,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" +#include "llvm/ADT/SmallString.h" using namespace clang; using namespace ento; @@ -29,7 +30,7 @@ class MacOSKeychainAPIChecker : public Checker<check::PreStmt<CallExpr>, check::PostStmt<CallExpr>, check::EndPath, check::DeadSymbols> { - mutable llvm::OwningPtr<BugType> BT; + mutable OwningPtr<BugType> BT; public: /// AllocationState is a part of the checker specific state together with the @@ -58,7 +59,7 @@ public: void checkPreStmt(const ReturnStmt *S, CheckerContext &C) const; void checkPostStmt(const CallExpr *S, CheckerContext &C) const; void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; - void checkEndPath(EndOfFunctionNodeBuilder &B, ExprEngine &Eng) const; + void checkEndPath(CheckerContext &C) const; private: typedef std::pair<SymbolRef, const AllocationState*> AllocationPair; @@ -100,26 +101,38 @@ private: const Expr *ArgExpr, CheckerContext &C) const; + /// Find the allocation site for Sym on the path leading to the node N. + const Stmt *getAllocationSite(const ExplodedNode *N, SymbolRef Sym, + CheckerContext &C) const; + BugReport *generateAllocatedDataNotReleasedReport(const AllocationPair &AP, - ExplodedNode *N) const; + ExplodedNode *N, + CheckerContext &C) const; /// Check if RetSym evaluates to an error value in the current state. bool definitelyReturnedError(SymbolRef RetSym, - const ProgramState *State, + ProgramStateRef State, SValBuilder &Builder, bool noError = false) const; /// Check if RetSym evaluates to a NoErr value in the current state. bool definitelyDidnotReturnError(SymbolRef RetSym, - const ProgramState *State, + ProgramStateRef State, SValBuilder &Builder) const { return definitelyReturnedError(RetSym, State, Builder, true); } + + /// Mark an AllocationPair interesting for diagnostic reporting. + void markInteresting(BugReport *R, const AllocationPair &AP) const { + R->markInteresting(AP.first); + R->markInteresting(AP.second->Region); + } /// The bug visitor which allows us to print extra diagnostics along the /// BugReport path. For example, showing the allocation site of the leaked /// region. - class SecKeychainBugVisitor : public BugReporterVisitor { + class SecKeychainBugVisitor + : public BugReporterVisitorImpl<SecKeychainBugVisitor> { protected: // The allocated region symbol tracked by the main analysis. SymbolRef Sym; @@ -196,18 +209,9 @@ unsigned MacOSKeychainAPIChecker::getTrackedFunctionIndex(StringRef Name, return InvalidIdx; } -static SymbolRef getSymbolForRegion(CheckerContext &C, - const MemRegion *R) { - // Implicit casts (ex: void* -> char*) can turn Symbolic region into element - // region, if that is the case, get the underlining region. - R = R->StripCasts(); - if (!isa<SymbolicRegion>(R)) { - return 0; - } - return cast<SymbolicRegion>(R)->getSymbol(); -} - static bool isBadDeallocationArgument(const MemRegion *Arg) { + if (!Arg) + return false; if (isa<AllocaRegion>(Arg) || isa<BlockDataRegion>(Arg) || isa<TypedRegion>(Arg)) { @@ -215,18 +219,19 @@ static bool isBadDeallocationArgument(const MemRegion *Arg) { } return false; } + /// Given the address expression, retrieve the value it's pointing to. Assume /// that value is itself an address, and return the corresponding symbol. static SymbolRef getAsPointeeSymbol(const Expr *Expr, CheckerContext &C) { - const ProgramState *State = C.getState(); - SVal ArgV = State->getSVal(Expr); + ProgramStateRef State = C.getState(); + SVal ArgV = State->getSVal(Expr, C.getLocationContext()); if (const loc::MemRegionVal *X = dyn_cast<loc::MemRegionVal>(&ArgV)) { StoreManager& SM = C.getStoreManager(); - const MemRegion *V = SM.Retrieve(State->getStore(), *X).getAsRegion(); - if (V) - return getSymbolForRegion(C, V); + SymbolRef sym = SM.getBinding(State->getStore(), *X).getAsLocSymbol(); + if (sym) + return sym; } return 0; } @@ -238,14 +243,14 @@ static SymbolRef getAsPointeeSymbol(const Expr *Expr, // If noError, returns true iff (1). // If !noError, returns true iff (2). bool MacOSKeychainAPIChecker::definitelyReturnedError(SymbolRef RetSym, - const ProgramState *State, + ProgramStateRef State, SValBuilder &Builder, bool noError) const { DefinedOrUnknownSVal NoErrVal = Builder.makeIntVal(NoErr, Builder.getSymbolManager().getType(RetSym)); DefinedOrUnknownSVal NoErr = Builder.evalEQ(State, NoErrVal, nonloc::SymbolVal(RetSym)); - const ProgramState *ErrState = State->assume(NoErr, noError); + ProgramStateRef ErrState = State->assume(NoErr, noError); if (ErrState == State) { return true; } @@ -259,14 +264,14 @@ void MacOSKeychainAPIChecker:: generateDeallocatorMismatchReport(const AllocationPair &AP, const Expr *ArgExpr, CheckerContext &C) const { - const ProgramState *State = C.getState(); + ProgramStateRef State = C.getState(); State = State->remove<AllocatedData>(AP.first); - ExplodedNode *N = C.generateNode(State); + ExplodedNode *N = C.addTransition(State); if (!N) return; initBugType(); - llvm::SmallString<80> sbuf; + SmallString<80> sbuf; llvm::raw_svector_ostream os(sbuf); unsigned int PDeallocIdx = FunctionsToTrack[AP.second->AllocatorIdx].DeallocatorIdx; @@ -276,23 +281,18 @@ void MacOSKeychainAPIChecker:: BugReport *Report = new BugReport(*BT, os.str(), N); Report->addVisitor(new SecKeychainBugVisitor(AP.first)); Report->addRange(ArgExpr->getSourceRange()); + markInteresting(Report, AP); C.EmitReport(Report); } void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const { - const ProgramState *State = C.getState(); - const Expr *Callee = CE->getCallee(); - SVal L = State->getSVal(Callee); unsigned idx = InvalidIdx; + ProgramStateRef State = C.getState(); - const FunctionDecl *funDecl = L.getAsFunctionDecl(); - if (!funDecl) - return; - IdentifierInfo *funI = funDecl->getIdentifier(); - if (!funI) + StringRef funName = C.getCalleeName(CE); + if (funName.empty()) return; - StringRef funName = funI->getName(); // If it is a call to an allocator function, it could be a double allocation. idx = getTrackedFunctionIndex(funName, true); @@ -304,11 +304,11 @@ void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE, // Remove the value from the state. The new symbol will be added for // tracking when the second allocator is processed in checkPostStmt(). State = State->remove<AllocatedData>(V); - ExplodedNode *N = C.generateNode(State); + ExplodedNode *N = C.addTransition(State); if (!N) return; initBugType(); - llvm::SmallString<128> sbuf; + SmallString<128> sbuf; llvm::raw_svector_ostream os(sbuf); unsigned int DIdx = FunctionsToTrack[AS->AllocatorIdx].DeallocatorIdx; os << "Allocated data should be released before another call to " @@ -318,6 +318,7 @@ void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE, BugReport *Report = new BugReport(*BT, os.str(), N); Report->addVisitor(new SecKeychainBugVisitor(V)); Report->addRange(ArgExpr->getSourceRange()); + Report->markInteresting(AS->Region); C.EmitReport(Report); } } @@ -331,22 +332,22 @@ void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE, // Check the argument to the deallocator. const Expr *ArgExpr = CE->getArg(FunctionsToTrack[idx].Param); - SVal ArgSVal = State->getSVal(ArgExpr); + SVal ArgSVal = State->getSVal(ArgExpr, C.getLocationContext()); // Undef is reported by another checker. if (ArgSVal.isUndef()) return; - const MemRegion *Arg = ArgSVal.getAsRegion(); - if (!Arg) - return; + SymbolRef ArgSM = ArgSVal.getAsLocSymbol(); - SymbolRef ArgSM = getSymbolForRegion(C, Arg); - bool RegionArgIsBad = ArgSM ? false : isBadDeallocationArgument(Arg); // If the argument is coming from the heap, globals, or unknown, do not // report it. - if (!ArgSM && !RegionArgIsBad) - return; + bool RegionArgIsBad = false; + if (!ArgSM) { + if (!isBadDeallocationArgument(ArgSVal.getAsRegion())) + return; + RegionArgIsBad = true; + } // Is the argument to the call being tracked? const AllocationState *AS = State->get<AllocatedData>(ArgSM); @@ -362,13 +363,15 @@ void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE, if (isEnclosingFunctionParam(ArgExpr)) return; - ExplodedNode *N = C.generateNode(State); + ExplodedNode *N = C.addTransition(State); if (!N) return; initBugType(); BugReport *Report = new BugReport(*BT, "Trying to free data which has not been allocated.", N); Report->addRange(ArgExpr->getSourceRange()); + if (AS) + Report->markInteresting(AS->Region); C.EmitReport(Report); return; } @@ -420,16 +423,19 @@ void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE, return; } - // If the return status is undefined or is error, report a bad call to free. - if (!definitelyDidnotReturnError(AS->Region, State, C.getSValBuilder())) { - ExplodedNode *N = C.generateNode(State); + // If the buffer can be null and the return status can be an error, + // report a bad call to free. + if (State->assume(cast<DefinedSVal>(ArgSVal), false) && + !definitelyDidnotReturnError(AS->Region, State, C.getSValBuilder())) { + ExplodedNode *N = C.addTransition(State); if (!N) return; initBugType(); BugReport *Report = new BugReport(*BT, - "Call to free data when error was returned during allocation.", N); + "Only call free if a valid (non-NULL) buffer was returned.", N); Report->addVisitor(new SecKeychainBugVisitor(ArgSM)); Report->addRange(ArgExpr->getSourceRange()); + Report->markInteresting(AS->Region); C.EmitReport(Report); return; } @@ -439,17 +445,8 @@ void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE, void MacOSKeychainAPIChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const { - const ProgramState *State = C.getState(); - const Expr *Callee = CE->getCallee(); - SVal L = State->getSVal(Callee); - - const FunctionDecl *funDecl = L.getAsFunctionDecl(); - if (!funDecl) - return; - IdentifierInfo *funI = funDecl->getIdentifier(); - if (!funI) - return; - StringRef funName = funI->getName(); + ProgramStateRef State = C.getState(); + StringRef funName = C.getCalleeName(CE); // If a value has been allocated, add it to the set for tracking. unsigned idx = getTrackedFunctionIndex(funName, true); @@ -459,7 +456,8 @@ void MacOSKeychainAPIChecker::checkPostStmt(const CallExpr *CE, const Expr *ArgExpr = CE->getArg(FunctionsToTrack[idx].Param); // If the argument entered as an enclosing function parameter, skip it to // avoid false positives. - if (isEnclosingFunctionParam(ArgExpr)) + if (isEnclosingFunctionParam(ArgExpr) && + C.getLocationContext()->getParent() == 0) return; if (SymbolRef V = getAsPointeeSymbol(ArgExpr, C)) { @@ -475,7 +473,8 @@ void MacOSKeychainAPIChecker::checkPostStmt(const CallExpr *CE, // allocated value symbol, since our diagnostics depend on the value // returned by the call. Ex: Data should only be freed if noErr was // returned during allocation.) - SymbolRef RetStatusSymbol = State->getSVal(CE).getAsSymbol(); + SymbolRef RetStatusSymbol = + State->getSVal(CE, C.getLocationContext()).getAsSymbol(); C.getSymbolManager().addSymbolDependency(V, RetStatusSymbol); // Track the allocated value in the checker state. @@ -492,36 +491,76 @@ void MacOSKeychainAPIChecker::checkPreStmt(const ReturnStmt *S, if (!retExpr) return; + // If inside inlined call, skip it. + const LocationContext *LC = C.getLocationContext(); + if (LC->getParent() != 0) + return; + // Check if the value is escaping through the return. - const ProgramState *state = C.getState(); - const MemRegion *V = state->getSVal(retExpr).getAsRegion(); - if (!V) + ProgramStateRef state = C.getState(); + SymbolRef sym = state->getSVal(retExpr, LC).getAsLocSymbol(); + if (!sym) return; - state = state->remove<AllocatedData>(getSymbolForRegion(C, V)); + state = state->remove<AllocatedData>(sym); // Proceed from the new state. C.addTransition(state); } +// TODO: This logic is the same as in Malloc checker. +const Stmt * +MacOSKeychainAPIChecker::getAllocationSite(const ExplodedNode *N, + SymbolRef Sym, + CheckerContext &C) const { + const LocationContext *LeakContext = N->getLocationContext(); + // Walk the ExplodedGraph backwards and find the first node that referred to + // the tracked symbol. + const ExplodedNode *AllocNode = N; + + while (N) { + if (!N->getState()->get<AllocatedData>(Sym)) + break; + // Allocation node, is the last node in the current context in which the + // symbol was tracked. + if (N->getLocationContext() == LeakContext) + AllocNode = N; + N = N->pred_empty() ? NULL : *(N->pred_begin()); + } + + ProgramPoint P = AllocNode->getLocation(); + if (!isa<StmtPoint>(P)) + return 0; + return cast<clang::PostStmt>(P).getStmt(); +} + BugReport *MacOSKeychainAPIChecker:: generateAllocatedDataNotReleasedReport(const AllocationPair &AP, - ExplodedNode *N) const { + ExplodedNode *N, + CheckerContext &C) const { const ADFunctionInfo &FI = FunctionsToTrack[AP.second->AllocatorIdx]; initBugType(); - llvm::SmallString<70> sbuf; + SmallString<70> sbuf; llvm::raw_svector_ostream os(sbuf); - os << "Allocated data is not released: missing a call to '" << FunctionsToTrack[FI.DeallocatorIdx].Name << "'."; - BugReport *Report = new BugReport(*BT, os.str(), N); + + // Most bug reports are cached at the location where they occurred. + // With leaks, we want to unique them by the location where they were + // allocated, and only report a single path. + PathDiagnosticLocation LocUsedForUniqueing; + if (const Stmt *AllocStmt = getAllocationSite(N, AP.first, C)) + LocUsedForUniqueing = PathDiagnosticLocation::createBegin(AllocStmt, + C.getSourceManager(), N->getLocationContext()); + + BugReport *Report = new BugReport(*BT, os.str(), N, LocUsedForUniqueing); Report->addVisitor(new SecKeychainBugVisitor(AP.first)); - Report->addRange(SourceRange()); + markInteresting(Report, AP); return Report; } void MacOSKeychainAPIChecker::checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const { - const ProgramState *State = C.getState(); + ProgramStateRef State = C.getState(); AllocatedSetTy ASet = State->get<AllocatedData>(); if (ASet.isEmpty()) return; @@ -541,25 +580,33 @@ void MacOSKeychainAPIChecker::checkDeadSymbols(SymbolReaper &SR, continue; Errors.push_back(std::make_pair(I->first, &I->second)); } - if (!Changed) + if (!Changed) { + // Generate the new, cleaned up state. + C.addTransition(State); return; + } - // Generate the new, cleaned up state. - ExplodedNode *N = C.generateNode(State); - if (!N) - return; + static SimpleProgramPointTag Tag("MacOSKeychainAPIChecker : DeadSymbolsLeak"); + ExplodedNode *N = C.addTransition(C.getState(), C.getPredecessor(), &Tag); // Generate the error reports. for (AllocationPairVec::iterator I = Errors.begin(), E = Errors.end(); I != E; ++I) { - C.EmitReport(generateAllocatedDataNotReleasedReport(*I, N)); + C.EmitReport(generateAllocatedDataNotReleasedReport(*I, N, C)); } + + // Generate the new, cleaned up state. + C.addTransition(State, N); } // TODO: Remove this after we ensure that checkDeadSymbols are always called. -void MacOSKeychainAPIChecker::checkEndPath(EndOfFunctionNodeBuilder &B, - ExprEngine &Eng) const { - const ProgramState *state = B.getState(); +void MacOSKeychainAPIChecker::checkEndPath(CheckerContext &C) const { + ProgramStateRef state = C.getState(); + + // If inside inlined call, skip it. + if (C.getLocationContext()->getParent() != 0) + return; + AllocatedSetTy AS = state->get<AllocatedData>(); if (AS.isEmpty()) return; @@ -575,26 +622,28 @@ void MacOSKeychainAPIChecker::checkEndPath(EndOfFunctionNodeBuilder &B, // allocation, do not report. if (state->getSymVal(I.getKey()) || definitelyReturnedError(I->second.Region, state, - Eng.getSValBuilder())) { + C.getSValBuilder())) { continue; } Errors.push_back(std::make_pair(I->first, &I->second)); } // If no change, do not generate a new state. - if (!Changed) + if (!Changed) { + C.addTransition(state); return; + } - ExplodedNode *N = B.generateNode(state); - if (!N) - return; + static SimpleProgramPointTag Tag("MacOSKeychainAPIChecker : EndPathLeak"); + ExplodedNode *N = C.addTransition(C.getState(), C.getPredecessor(), &Tag); // Generate the error reports. for (AllocationPairVec::iterator I = Errors.begin(), E = Errors.end(); I != E; ++I) { - Eng.getBugReporter().EmitReport( - generateAllocatedDataNotReleasedReport(*I, N)); + C.EmitReport(generateAllocatedDataNotReleasedReport(*I, N, C)); } + + C.addTransition(state, N); } diff --git a/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp b/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp index 88d492e..cfdb55d 100644 --- a/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp @@ -31,17 +31,17 @@ using namespace ento; namespace { class MacOSXAPIChecker : public Checker< check::PreStmt<CallExpr> > { - mutable llvm::OwningPtr<BugType> BT_dispatchOnce; + mutable OwningPtr<BugType> BT_dispatchOnce; public: void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; void CheckDispatchOnce(CheckerContext &C, const CallExpr *CE, - const IdentifierInfo *FI) const; + StringRef FName) const; typedef void (MacOSXAPIChecker::*SubChecker)(CheckerContext &, const CallExpr *, - const IdentifierInfo *) const; + StringRef FName) const; }; } //end anonymous namespace @@ -50,14 +50,15 @@ public: //===----------------------------------------------------------------------===// void MacOSXAPIChecker::CheckDispatchOnce(CheckerContext &C, const CallExpr *CE, - const IdentifierInfo *FI) const { + StringRef FName) const { if (CE->getNumArgs() < 1) return; // Check if the first argument is stack allocated. If so, issue a warning // because that's likely to be bad news. - const ProgramState *state = C.getState(); - const MemRegion *R = state->getSVal(CE->getArg(0)).getAsRegion(); + ProgramStateRef state = C.getState(); + const MemRegion *R = + state->getSVal(CE->getArg(0), C.getLocationContext()).getAsRegion(); if (!R || !isa<StackSpaceRegion>(R->getMemorySpace())) return; @@ -69,9 +70,9 @@ void MacOSXAPIChecker::CheckDispatchOnce(CheckerContext &C, const CallExpr *CE, BT_dispatchOnce.reset(new BugType("Improper use of 'dispatch_once'", "Mac OS X API")); - llvm::SmallString<256> S; + SmallString<256> S; llvm::raw_svector_ostream os(S); - os << "Call to '" << FI->getName() << "' uses"; + os << "Call to '" << FName << "' uses"; if (const VarRegion *VR = dyn_cast<VarRegion>(R)) os << " the local variable '" << VR->getDecl()->getName() << '\''; else @@ -92,27 +93,18 @@ void MacOSXAPIChecker::CheckDispatchOnce(CheckerContext &C, const CallExpr *CE, void MacOSXAPIChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const { - // FIXME: This sort of logic is common to several checkers, including - // UnixAPIChecker, PthreadLockChecker, and CStringChecker. Should refactor. - const ProgramState *state = C.getState(); - const Expr *Callee = CE->getCallee(); - const FunctionDecl *Fn = state->getSVal(Callee).getAsFunctionDecl(); - - if (!Fn) - return; - - const IdentifierInfo *FI = Fn->getIdentifier(); - if (!FI) + StringRef Name = C.getCalleeName(CE); + if (Name.empty()) return; SubChecker SC = - llvm::StringSwitch<SubChecker>(FI->getName()) + llvm::StringSwitch<SubChecker>(Name) .Cases("dispatch_once", "dispatch_once_f", &MacOSXAPIChecker::CheckDispatchOnce) .Default(NULL); if (SC) - (this->*SC)(C, CE, FI); + (this->*SC)(C, CE, Name); } //===----------------------------------------------------------------------===// diff --git a/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index 5631802..8bce88a 100644 --- a/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -13,14 +13,21 @@ //===----------------------------------------------------------------------===// #include "ClangSACheckers.h" +#include "InterCheckerAPI.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ObjCMessage.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" +#include "clang/Basic/SourceManager.h" #include "llvm/ADT/ImmutableMap.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/STLExtras.h" +#include <climits> + using namespace clang; using namespace ento; @@ -35,10 +42,9 @@ public: RefState(Kind k, const Stmt *s) : K(k), S(s) {} bool isAllocated() const { return K == AllocateUnchecked; } - //bool isFailed() const { return K == AllocateFailed; } bool isReleased() const { return K == Released; } - //bool isEscaped() const { return K == Escaped; } - //bool isRelinquished() const { return K == Relinquished; } + + const Stmt *getStmt() const { return S; } bool operator==(const RefState &X) const { return K == X.K && S == X.S; @@ -62,61 +68,222 @@ public: } }; -class RegionState {}; +struct ReallocPair { + SymbolRef ReallocatedSym; + bool IsFreeOnFailure; + ReallocPair(SymbolRef S, bool F) : ReallocatedSym(S), IsFreeOnFailure(F) {} + void Profile(llvm::FoldingSetNodeID &ID) const { + ID.AddInteger(IsFreeOnFailure); + ID.AddPointer(ReallocatedSym); + } + bool operator==(const ReallocPair &X) const { + return ReallocatedSym == X.ReallocatedSym && + IsFreeOnFailure == X.IsFreeOnFailure; + } +}; -class MallocChecker : public Checker<eval::Call, check::DeadSymbols, check::EndPath, check::PreStmt<ReturnStmt>, check::Location, - check::Bind, eval::Assume> { - mutable llvm::OwningPtr<BuiltinBug> BT_DoubleFree; - mutable llvm::OwningPtr<BuiltinBug> BT_Leak; - mutable llvm::OwningPtr<BuiltinBug> BT_UseFree; - mutable llvm::OwningPtr<BuiltinBug> BT_UseRelinquished; - mutable llvm::OwningPtr<BuiltinBug> BT_BadFree; - mutable IdentifierInfo *II_malloc, *II_free, *II_realloc, *II_calloc; +typedef std::pair<const Stmt*, const MemRegion*> LeakInfo; + +class MallocChecker : public Checker<check::DeadSymbols, + check::EndPath, + check::PreStmt<ReturnStmt>, + check::PreStmt<CallExpr>, + check::PostStmt<CallExpr>, + check::PostStmt<BlockExpr>, + check::Location, + check::Bind, + eval::Assume, + check::RegionChanges> +{ + mutable OwningPtr<BugType> BT_DoubleFree; + mutable OwningPtr<BugType> BT_Leak; + mutable OwningPtr<BugType> BT_UseFree; + mutable OwningPtr<BugType> BT_BadFree; + mutable IdentifierInfo *II_malloc, *II_free, *II_realloc, *II_calloc, + *II_valloc, *II_reallocf, *II_strndup, *II_strdup; public: - MallocChecker() : II_malloc(0), II_free(0), II_realloc(0), II_calloc(0) {} + MallocChecker() : II_malloc(0), II_free(0), II_realloc(0), II_calloc(0), + II_valloc(0), II_reallocf(0), II_strndup(0), II_strdup(0) {} + + /// In pessimistic mode, the checker assumes that it does not know which + /// functions might free the memory. + struct ChecksFilter { + DefaultBool CMallocPessimistic; + DefaultBool CMallocOptimistic; + }; - bool evalCall(const CallExpr *CE, CheckerContext &C) const; + ChecksFilter Filter; + + void checkPreStmt(const CallExpr *S, CheckerContext &C) const; + void checkPostStmt(const CallExpr *CE, CheckerContext &C) const; + void checkPostStmt(const BlockExpr *BE, CheckerContext &C) const; void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; - void checkEndPath(EndOfFunctionNodeBuilder &B, ExprEngine &Eng) const; + void checkEndPath(CheckerContext &C) const; void checkPreStmt(const ReturnStmt *S, CheckerContext &C) const; - const ProgramState *evalAssume(const ProgramState *state, SVal Cond, + ProgramStateRef evalAssume(ProgramStateRef state, SVal Cond, bool Assumption) const; void checkLocation(SVal l, bool isLoad, const Stmt *S, CheckerContext &C) const; void checkBind(SVal location, SVal val, const Stmt*S, CheckerContext &C) const; + ProgramStateRef + checkRegionChanges(ProgramStateRef state, + const StoreManager::InvalidatedSymbols *invalidated, + ArrayRef<const MemRegion *> ExplicitRegions, + ArrayRef<const MemRegion *> Regions, + const CallOrObjCMessage *Call) const; + bool wantsRegionChangeUpdate(ProgramStateRef state) const { + return true; + } private: - static void MallocMem(CheckerContext &C, const CallExpr *CE); - static void MallocMemReturnsAttr(CheckerContext &C, const CallExpr *CE, - const OwnershipAttr* Att); - static const ProgramState *MallocMemAux(CheckerContext &C, const CallExpr *CE, + void initIdentifierInfo(ASTContext &C) const; + + /// Check if this is one of the functions which can allocate/reallocate memory + /// pointed to by one of its arguments. + bool isMemFunction(const FunctionDecl *FD, ASTContext &C) const; + + static ProgramStateRef MallocMemReturnsAttr(CheckerContext &C, + const CallExpr *CE, + const OwnershipAttr* Att); + static ProgramStateRef MallocMemAux(CheckerContext &C, const CallExpr *CE, const Expr *SizeEx, SVal Init, - const ProgramState *state) { - return MallocMemAux(C, CE, state->getSVal(SizeEx), Init, state); + ProgramStateRef state) { + return MallocMemAux(C, CE, + state->getSVal(SizeEx, C.getLocationContext()), + Init, state); } - static const ProgramState *MallocMemAux(CheckerContext &C, const CallExpr *CE, + + static ProgramStateRef MallocMemAux(CheckerContext &C, const CallExpr *CE, SVal SizeEx, SVal Init, - const ProgramState *state); + ProgramStateRef state); + + /// Update the RefState to reflect the new memory allocation. + static ProgramStateRef MallocUpdateRefState(CheckerContext &C, + const CallExpr *CE, + ProgramStateRef state); + + ProgramStateRef FreeMemAttr(CheckerContext &C, const CallExpr *CE, + const OwnershipAttr* Att) const; + ProgramStateRef FreeMemAux(CheckerContext &C, const CallExpr *CE, + ProgramStateRef state, unsigned Num, + bool Hold) const; + + ProgramStateRef ReallocMem(CheckerContext &C, const CallExpr *CE, + bool FreesMemOnFailure) const; + static ProgramStateRef CallocMem(CheckerContext &C, const CallExpr *CE); + + bool checkEscape(SymbolRef Sym, const Stmt *S, CheckerContext &C) const; + bool checkUseAfterFree(SymbolRef Sym, CheckerContext &C, + const Stmt *S = 0) const; - void FreeMem(CheckerContext &C, const CallExpr *CE) const; - void FreeMemAttr(CheckerContext &C, const CallExpr *CE, - const OwnershipAttr* Att) const; - const ProgramState *FreeMemAux(CheckerContext &C, const CallExpr *CE, - const ProgramState *state, unsigned Num, bool Hold) const; + /// Check if the function is not known to us. So, for example, we could + /// conservatively assume it can free/reallocate it's pointer arguments. + bool doesNotFreeMemory(const CallOrObjCMessage *Call, + ProgramStateRef State) const; - void ReallocMem(CheckerContext &C, const CallExpr *CE) const; - static void CallocMem(CheckerContext &C, const CallExpr *CE); - static bool SummarizeValue(raw_ostream &os, SVal V); static bool SummarizeRegion(raw_ostream &os, const MemRegion *MR); void ReportBadFree(CheckerContext &C, SVal ArgVal, SourceRange range) const; + + /// Find the location of the allocation for Sym on the path leading to the + /// exploded node N. + LeakInfo getAllocationSite(const ExplodedNode *N, SymbolRef Sym, + CheckerContext &C) const; + + void reportLeak(SymbolRef Sym, ExplodedNode *N, CheckerContext &C) const; + + /// The bug visitor which allows us to print extra diagnostics along the + /// BugReport path. For example, showing the allocation site of the leaked + /// region. + class MallocBugVisitor : public BugReporterVisitorImpl<MallocBugVisitor> { + protected: + enum NotificationMode { + Normal, + ReallocationFailed + }; + + // The allocated region symbol tracked by the main analysis. + SymbolRef Sym; + + // The mode we are in, i.e. what kind of diagnostics will be emitted. + NotificationMode Mode; + + // A symbol from when the primary region should have been reallocated. + SymbolRef FailedReallocSymbol; + + public: + MallocBugVisitor(SymbolRef S) + : Sym(S), Mode(Normal), FailedReallocSymbol(0) {} + + virtual ~MallocBugVisitor() {} + + void Profile(llvm::FoldingSetNodeID &ID) const { + static int X = 0; + ID.AddPointer(&X); + ID.AddPointer(Sym); + } + + inline bool isAllocated(const RefState *S, const RefState *SPrev, + const Stmt *Stmt) { + // Did not track -> allocated. Other state (released) -> allocated. + return (Stmt && isa<CallExpr>(Stmt) && + (S && S->isAllocated()) && (!SPrev || !SPrev->isAllocated())); + } + + inline bool isReleased(const RefState *S, const RefState *SPrev, + const Stmt *Stmt) { + // Did not track -> released. Other state (allocated) -> released. + return (Stmt && isa<CallExpr>(Stmt) && + (S && S->isReleased()) && (!SPrev || !SPrev->isReleased())); + } + + inline bool isReallocFailedCheck(const RefState *S, const RefState *SPrev, + const Stmt *Stmt) { + // If the expression is not a call, and the state change is + // released -> allocated, it must be the realloc return value + // check. If we have to handle more cases here, it might be cleaner just + // to track this extra bit in the state itself. + return ((!Stmt || !isa<CallExpr>(Stmt)) && + (S && S->isAllocated()) && (SPrev && !SPrev->isAllocated())); + } + + PathDiagnosticPiece *VisitNode(const ExplodedNode *N, + const ExplodedNode *PrevN, + BugReporterContext &BRC, + BugReport &BR); + private: + class StackHintGeneratorForReallocationFailed + : public StackHintGeneratorForSymbol { + public: + StackHintGeneratorForReallocationFailed(SymbolRef S, StringRef M) + : StackHintGeneratorForSymbol(S, M) {} + + virtual std::string getMessageForArg(const Expr *ArgE, unsigned ArgIndex) { + SmallString<200> buf; + llvm::raw_svector_ostream os(buf); + + os << "Reallocation of "; + // Printed parameters start at 1, not 0. + printOrdinal(++ArgIndex, os); + os << " parameter failed"; + + return os.str(); + } + + virtual std::string getMessageForReturn(const CallExpr *CallExpr) { + return "Reallocation of returned value failed"; + } + }; + }; }; } // end anonymous namespace typedef llvm::ImmutableMap<SymbolRef, RefState> RegionStateTy; - +typedef llvm::ImmutableMap<SymbolRef, ReallocPair > ReallocMap; +class RegionState {}; +class ReallocPairs {}; namespace clang { namespace ento { template <> @@ -124,178 +291,230 @@ namespace ento { : public ProgramStatePartialTrait<RegionStateTy> { static void *GDMIndex() { static int x; return &x; } }; + + template <> + struct ProgramStateTrait<ReallocPairs> + : public ProgramStatePartialTrait<ReallocMap> { + static void *GDMIndex() { static int x; return &x; } + }; } } -bool MallocChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { - const ProgramState *state = C.getState(); - const Expr *Callee = CE->getCallee(); - SVal L = state->getSVal(Callee); +namespace { +class StopTrackingCallback : public SymbolVisitor { + ProgramStateRef state; +public: + StopTrackingCallback(ProgramStateRef st) : state(st) {} + ProgramStateRef getState() const { return state; } - const FunctionDecl *FD = L.getAsFunctionDecl(); - if (!FD) - return false; + bool VisitSymbol(SymbolRef sym) { + state = state->remove<RegionState>(sym); + return true; + } +}; +} // end anonymous namespace - ASTContext &Ctx = C.getASTContext(); +void MallocChecker::initIdentifierInfo(ASTContext &Ctx) const { if (!II_malloc) II_malloc = &Ctx.Idents.get("malloc"); if (!II_free) II_free = &Ctx.Idents.get("free"); if (!II_realloc) II_realloc = &Ctx.Idents.get("realloc"); + if (!II_reallocf) + II_reallocf = &Ctx.Idents.get("reallocf"); if (!II_calloc) II_calloc = &Ctx.Idents.get("calloc"); + if (!II_valloc) + II_valloc = &Ctx.Idents.get("valloc"); + if (!II_strdup) + II_strdup = &Ctx.Idents.get("strdup"); + if (!II_strndup) + II_strndup = &Ctx.Idents.get("strndup"); +} - if (FD->getIdentifier() == II_malloc) { - MallocMem(C, CE); - return true; - } +bool MallocChecker::isMemFunction(const FunctionDecl *FD, ASTContext &C) const { + if (!FD) + return false; + IdentifierInfo *FunI = FD->getIdentifier(); + if (!FunI) + return false; - if (FD->getIdentifier() == II_free) { - FreeMem(C, CE); - return true; - } + initIdentifierInfo(C); - if (FD->getIdentifier() == II_realloc) { - ReallocMem(C, CE); + if (FunI == II_malloc || FunI == II_free || FunI == II_realloc || + FunI == II_reallocf || FunI == II_calloc || FunI == II_valloc || + FunI == II_strdup || FunI == II_strndup) return true; - } - if (FD->getIdentifier() == II_calloc) { - CallocMem(C, CE); + if (Filter.CMallocOptimistic && FD->hasAttrs() && + FD->specific_attr_begin<OwnershipAttr>() != + FD->specific_attr_end<OwnershipAttr>()) return true; - } - // Check all the attributes, if there are any. - // There can be multiple of these attributes. - bool rv = false; - if (FD->hasAttrs()) { - for (specific_attr_iterator<OwnershipAttr> - i = FD->specific_attr_begin<OwnershipAttr>(), - e = FD->specific_attr_end<OwnershipAttr>(); - i != e; ++i) { - switch ((*i)->getOwnKind()) { - case OwnershipAttr::Returns: { - MallocMemReturnsAttr(C, CE, *i); - rv = true; - break; - } - case OwnershipAttr::Takes: - case OwnershipAttr::Holds: { - FreeMemAttr(C, CE, *i); - rv = true; - break; - } - default: - break; - } - } - } - return rv; + + return false; } -void MallocChecker::MallocMem(CheckerContext &C, const CallExpr *CE) { - const ProgramState *state = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), - C.getState()); - C.addTransition(state); +void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const { + const FunctionDecl *FD = C.getCalleeDecl(CE); + if (!FD) + return; + + initIdentifierInfo(C.getASTContext()); + IdentifierInfo *FunI = FD->getIdentifier(); + if (!FunI) + return; + + ProgramStateRef State = C.getState(); + if (FunI == II_malloc || FunI == II_valloc) { + if (CE->getNumArgs() < 1) + return; + State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State); + } else if (FunI == II_realloc) { + State = ReallocMem(C, CE, false); + } else if (FunI == II_reallocf) { + State = ReallocMem(C, CE, true); + } else if (FunI == II_calloc) { + State = CallocMem(C, CE); + } else if (FunI == II_free) { + State = FreeMemAux(C, CE, C.getState(), 0, false); + } else if (FunI == II_strdup) { + State = MallocUpdateRefState(C, CE, State); + } else if (FunI == II_strndup) { + State = MallocUpdateRefState(C, CE, State); + } else if (Filter.CMallocOptimistic) { + // Check all the attributes, if there are any. + // There can be multiple of these attributes. + if (FD->hasAttrs()) + for (specific_attr_iterator<OwnershipAttr> + i = FD->specific_attr_begin<OwnershipAttr>(), + e = FD->specific_attr_end<OwnershipAttr>(); + i != e; ++i) { + switch ((*i)->getOwnKind()) { + case OwnershipAttr::Returns: + State = MallocMemReturnsAttr(C, CE, *i); + break; + case OwnershipAttr::Takes: + case OwnershipAttr::Holds: + State = FreeMemAttr(C, CE, *i); + break; + } + } + } + C.addTransition(State); } -void MallocChecker::MallocMemReturnsAttr(CheckerContext &C, const CallExpr *CE, - const OwnershipAttr* Att) { +ProgramStateRef MallocChecker::MallocMemReturnsAttr(CheckerContext &C, + const CallExpr *CE, + const OwnershipAttr* Att) { if (Att->getModule() != "malloc") - return; + return 0; OwnershipAttr::args_iterator I = Att->args_begin(), E = Att->args_end(); if (I != E) { - const ProgramState *state = - MallocMemAux(C, CE, CE->getArg(*I), UndefinedVal(), C.getState()); - C.addTransition(state); - return; + return MallocMemAux(C, CE, CE->getArg(*I), UndefinedVal(), C.getState()); } - const ProgramState *state = MallocMemAux(C, CE, UnknownVal(), UndefinedVal(), - C.getState()); - C.addTransition(state); + return MallocMemAux(C, CE, UnknownVal(), UndefinedVal(), C.getState()); } -const ProgramState *MallocChecker::MallocMemAux(CheckerContext &C, +ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C, const CallExpr *CE, SVal Size, SVal Init, - const ProgramState *state) { - unsigned Count = C.getCurrentBlockCount(); - SValBuilder &svalBuilder = C.getSValBuilder(); + ProgramStateRef state) { + // Get the return value. + SVal retVal = state->getSVal(CE, C.getLocationContext()); - // Set the return value. - SVal retVal = svalBuilder.getConjuredSymbolVal(NULL, CE, CE->getType(), Count); - state = state->BindExpr(CE, retVal); + // We expect the malloc functions to return a pointer. + if (!isa<Loc>(retVal)) + return 0; // Fill the region with the initialization value. state = state->bindDefault(retVal, Init); // Set the region's extent equal to the Size parameter. - const SymbolicRegion *R = cast<SymbolicRegion>(retVal.getAsRegion()); - DefinedOrUnknownSVal Extent = R->getExtent(svalBuilder); - DefinedOrUnknownSVal DefinedSize = cast<DefinedOrUnknownSVal>(Size); - DefinedOrUnknownSVal extentMatchesSize = - svalBuilder.evalEQ(state, Extent, DefinedSize); - - state = state->assume(extentMatchesSize, true); - assert(state); + const SymbolicRegion *R = + dyn_cast_or_null<SymbolicRegion>(retVal.getAsRegion()); + if (!R) + return 0; + if (isa<DefinedOrUnknownSVal>(Size)) { + SValBuilder &svalBuilder = C.getSValBuilder(); + DefinedOrUnknownSVal Extent = R->getExtent(svalBuilder); + DefinedOrUnknownSVal DefinedSize = cast<DefinedOrUnknownSVal>(Size); + DefinedOrUnknownSVal extentMatchesSize = + svalBuilder.evalEQ(state, Extent, DefinedSize); + + state = state->assume(extentMatchesSize, true); + assert(state); + } + return MallocUpdateRefState(C, CE, state); +} + +ProgramStateRef MallocChecker::MallocUpdateRefState(CheckerContext &C, + const CallExpr *CE, + ProgramStateRef state) { + // Get the return value. + SVal retVal = state->getSVal(CE, C.getLocationContext()); + + // We expect the malloc functions to return a pointer. + if (!isa<Loc>(retVal)) + return 0; + SymbolRef Sym = retVal.getAsLocSymbol(); assert(Sym); // Set the symbol's state to Allocated. return state->set<RegionState>(Sym, RefState::getAllocateUnchecked(CE)); -} -void MallocChecker::FreeMem(CheckerContext &C, const CallExpr *CE) const { - const ProgramState *state = FreeMemAux(C, CE, C.getState(), 0, false); - - if (state) - C.addTransition(state); } -void MallocChecker::FreeMemAttr(CheckerContext &C, const CallExpr *CE, - const OwnershipAttr* Att) const { +ProgramStateRef MallocChecker::FreeMemAttr(CheckerContext &C, + const CallExpr *CE, + const OwnershipAttr* Att) const { if (Att->getModule() != "malloc") - return; + return 0; + + ProgramStateRef State = C.getState(); for (OwnershipAttr::args_iterator I = Att->args_begin(), E = Att->args_end(); I != E; ++I) { - const ProgramState *state = FreeMemAux(C, CE, C.getState(), *I, - Att->getOwnKind() == OwnershipAttr::Holds); - if (state) - C.addTransition(state); + ProgramStateRef StateI = FreeMemAux(C, CE, State, *I, + Att->getOwnKind() == OwnershipAttr::Holds); + if (StateI) + State = StateI; } + return State; } -const ProgramState *MallocChecker::FreeMemAux(CheckerContext &C, const CallExpr *CE, - const ProgramState *state, unsigned Num, - bool Hold) const { - const Expr *ArgExpr = CE->getArg(Num); - SVal ArgVal = state->getSVal(ArgExpr); +ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, + const CallExpr *CE, + ProgramStateRef state, + unsigned Num, + bool Hold) const { + if (CE->getNumArgs() < (Num + 1)) + return 0; + const Expr *ArgExpr = CE->getArg(Num); + SVal ArgVal = state->getSVal(ArgExpr, C.getLocationContext()); + if (!isa<DefinedOrUnknownSVal>(ArgVal)) + return 0; DefinedOrUnknownSVal location = cast<DefinedOrUnknownSVal>(ArgVal); // Check for null dereferences. if (!isa<Loc>(location)) - return state; - - // FIXME: Technically using 'Assume' here can result in a path - // bifurcation. In such cases we need to return two states, not just one. - const ProgramState *notNullState, *nullState; - llvm::tie(notNullState, nullState) = state->assume(location); + return 0; // The explicit NULL case, no operation is performed. + ProgramStateRef notNullState, nullState; + llvm::tie(notNullState, nullState) = state->assume(location); if (nullState && !notNullState) - return nullState; - - assert(notNullState); + return 0; // Unknown values could easily be okay // Undefined values are handled elsewhere if (ArgVal.isUnknownOrUndef()) - return notNullState; + return 0; const MemRegion *R = ArgVal.getAsRegion(); @@ -303,7 +522,7 @@ const ProgramState *MallocChecker::FreeMemAux(CheckerContext &C, const CallExpr // Non-region locations (labels and fixed addresses) also shouldn't be freed. if (!R) { ReportBadFree(C, ArgVal, ArgExpr->getSourceRange()); - return NULL; + return 0; } R = R->StripCasts(); @@ -311,7 +530,7 @@ const ProgramState *MallocChecker::FreeMemAux(CheckerContext &C, const CallExpr // Blocks might show up as heap data, but should not be free()d if (isa<BlockDataRegion>(R)) { ReportBadFree(C, ArgVal, ArgExpr->getSourceRange()); - return NULL; + return 0; } const MemSpaceRegion *MS = R->getMemorySpace(); @@ -327,14 +546,14 @@ const ProgramState *MallocChecker::FreeMemAux(CheckerContext &C, const CallExpr // False negatives are better than false positives. ReportBadFree(C, ArgVal, ArgExpr->getSourceRange()); - return NULL; + return 0; } const SymbolicRegion *SR = dyn_cast<SymbolicRegion>(R); // Various cases could lead to non-symbol values here. // For now, ignore them. if (!SR) - return notNullState; + return 0; SymbolRef Sym = SR->getSymbol(); const RefState *RS = state->get<RegionState>(Sym); @@ -343,27 +562,28 @@ const ProgramState *MallocChecker::FreeMemAux(CheckerContext &C, const CallExpr // called on a pointer that does not get its pointee directly from malloc(). // Full support of this requires inter-procedural analysis. if (!RS) - return notNullState; + return 0; // Check double free. if (RS->isReleased()) { if (ExplodedNode *N = C.generateSink()) { if (!BT_DoubleFree) BT_DoubleFree.reset( - new BuiltinBug("Double free", - "Try to free a memory block that has been released")); - // FIXME: should find where it's freed last time. + new BugType("Double free", "Memory Error")); BugReport *R = new BugReport(*BT_DoubleFree, - BT_DoubleFree->getDescription(), N); + "Attempt to free released memory", N); + R->addRange(ArgExpr->getSourceRange()); + R->markInteresting(Sym); + R->addVisitor(new MallocBugVisitor(Sym)); C.EmitReport(R); } - return NULL; + return 0; } // Normal free. if (Hold) - return notNullState->set<RegionState>(Sym, RefState::getRelinquished(CE)); - return notNullState->set<RegionState>(Sym, RefState::getReleased(CE)); + return state->set<RegionState>(Sym, RefState::getRelinquished(CE)); + return state->set<RegionState>(Sym, RefState::getReleased(CE)); } bool MallocChecker::SummarizeValue(raw_ostream &os, SVal V) { @@ -400,8 +620,7 @@ bool MallocChecker::SummarizeRegion(raw_ostream &os, default: { const MemSpaceRegion *MS = MR->getMemorySpace(); - switch (MS->getKind()) { - case MemRegion::StackLocalsSpaceRegionKind: { + if (isa<StackLocalsSpaceRegion>(MS)) { const VarRegion *VR = dyn_cast<VarRegion>(MR); const VarDecl *VD; if (VR) @@ -415,7 +634,8 @@ bool MallocChecker::SummarizeRegion(raw_ostream &os, os << "the address of a local stack variable"; return true; } - case MemRegion::StackArgumentsSpaceRegionKind: { + + if (isa<StackArgumentsSpaceRegion>(MS)) { const VarRegion *VR = dyn_cast<VarRegion>(MR); const VarDecl *VD; if (VR) @@ -429,8 +649,8 @@ bool MallocChecker::SummarizeRegion(raw_ostream &os, os << "the address of a parameter"; return true; } - case MemRegion::NonStaticGlobalSpaceRegionKind: - case MemRegion::StaticGlobalSpaceRegionKind: { + + if (isa<GlobalsSpaceRegion>(MS)) { const VarRegion *VR = dyn_cast<VarRegion>(MR); const VarDecl *VD; if (VR) @@ -447,9 +667,8 @@ bool MallocChecker::SummarizeRegion(raw_ostream &os, os << "the address of a global variable"; return true; } - default: - return false; - } + + return false; } } } @@ -458,9 +677,9 @@ void MallocChecker::ReportBadFree(CheckerContext &C, SVal ArgVal, SourceRange range) const { if (ExplodedNode *N = C.generateSink()) { if (!BT_BadFree) - BT_BadFree.reset(new BuiltinBug("Bad free")); + BT_BadFree.reset(new BugType("Bad free", "Memory Error")); - llvm::SmallString<100> buf; + SmallString<100> buf; llvm::raw_svector_ostream os(buf); const MemRegion *MR = ArgVal.getAsRegion(); @@ -487,16 +706,25 @@ void MallocChecker::ReportBadFree(CheckerContext &C, SVal ArgVal, } BugReport *R = new BugReport(*BT_BadFree, os.str(), N); + R->markInteresting(MR); R->addRange(range); C.EmitReport(R); } } -void MallocChecker::ReallocMem(CheckerContext &C, const CallExpr *CE) const { - const ProgramState *state = C.getState(); +ProgramStateRef MallocChecker::ReallocMem(CheckerContext &C, + const CallExpr *CE, + bool FreesOnFail) const { + if (CE->getNumArgs() < 2) + return 0; + + ProgramStateRef state = C.getState(); const Expr *arg0Expr = CE->getArg(0); - DefinedOrUnknownSVal arg0Val - = cast<DefinedOrUnknownSVal>(state->getSVal(arg0Expr)); + const LocationContext *LCtx = C.getLocationContext(); + SVal Arg0Val = state->getSVal(arg0Expr, LCtx); + if (!isa<DefinedOrUnknownSVal>(Arg0Val)) + return 0; + DefinedOrUnknownSVal arg0Val = cast<DefinedOrUnknownSVal>(Arg0Val); SValBuilder &svalBuilder = C.getSValBuilder(); @@ -506,65 +734,166 @@ void MallocChecker::ReallocMem(CheckerContext &C, const CallExpr *CE) const { // Get the size argument. If there is no size arg then give up. const Expr *Arg1 = CE->getArg(1); if (!Arg1) - return; + return 0; // Get the value of the size argument. - DefinedOrUnknownSVal Arg1Val = - cast<DefinedOrUnknownSVal>(state->getSVal(Arg1)); + SVal Arg1ValG = state->getSVal(Arg1, LCtx); + if (!isa<DefinedOrUnknownSVal>(Arg1ValG)) + return 0; + DefinedOrUnknownSVal Arg1Val = cast<DefinedOrUnknownSVal>(Arg1ValG); // Compare the size argument to 0. DefinedOrUnknownSVal SizeZero = svalBuilder.evalEQ(state, Arg1Val, svalBuilder.makeIntValWithPtrWidth(0, false)); + ProgramStateRef StatePtrIsNull, StatePtrNotNull; + llvm::tie(StatePtrIsNull, StatePtrNotNull) = state->assume(PtrEQ); + ProgramStateRef StateSizeIsZero, StateSizeNotZero; + llvm::tie(StateSizeIsZero, StateSizeNotZero) = state->assume(SizeZero); + // We only assume exceptional states if they are definitely true; if the + // state is under-constrained, assume regular realloc behavior. + bool PrtIsNull = StatePtrIsNull && !StatePtrNotNull; + bool SizeIsZero = StateSizeIsZero && !StateSizeNotZero; + // If the ptr is NULL and the size is not 0, the call is equivalent to // malloc(size). - const ProgramState *stateEqual = state->assume(PtrEQ, true); - if (stateEqual && state->assume(SizeZero, false)) { - // Hack: set the NULL symbolic region to released to suppress false warning. - // In the future we should add more states for allocated regions, e.g., - // CheckedNull, CheckedNonNull. - - SymbolRef Sym = arg0Val.getAsLocSymbol(); - if (Sym) - stateEqual = stateEqual->set<RegionState>(Sym, RefState::getReleased(CE)); - - const ProgramState *stateMalloc = MallocMemAux(C, CE, CE->getArg(1), - UndefinedVal(), stateEqual); - C.addTransition(stateMalloc); + if ( PrtIsNull && !SizeIsZero) { + ProgramStateRef stateMalloc = MallocMemAux(C, CE, CE->getArg(1), + UndefinedVal(), StatePtrIsNull); + return stateMalloc; } - if (const ProgramState *stateNotEqual = state->assume(PtrEQ, false)) { - // If the size is 0, free the memory. - if (const ProgramState *stateSizeZero = stateNotEqual->assume(SizeZero, true)) - if (const ProgramState *stateFree = - FreeMemAux(C, CE, stateSizeZero, 0, false)) { + if (PrtIsNull && SizeIsZero) + return 0; + + // Get the from and to pointer symbols as in toPtr = realloc(fromPtr, size). + assert(!PrtIsNull); + SymbolRef FromPtr = arg0Val.getAsSymbol(); + SVal RetVal = state->getSVal(CE, LCtx); + SymbolRef ToPtr = RetVal.getAsSymbol(); + if (!FromPtr || !ToPtr) + return 0; + + // If the size is 0, free the memory. + if (SizeIsZero) + if (ProgramStateRef stateFree = FreeMemAux(C, CE, StateSizeIsZero,0,false)){ + // The semantics of the return value are: + // If size was equal to 0, either NULL or a pointer suitable to be passed + // to free() is returned. + stateFree = stateFree->set<ReallocPairs>(ToPtr, + ReallocPair(FromPtr, FreesOnFail)); + C.getSymbolManager().addSymbolDependency(ToPtr, FromPtr); + return stateFree; + } - // Bind the return value to NULL because it is now free. - C.addTransition(stateFree->BindExpr(CE, svalBuilder.makeNull(), true)); - } - if (const ProgramState *stateSizeNotZero = stateNotEqual->assume(SizeZero,false)) - if (const ProgramState *stateFree = FreeMemAux(C, CE, stateSizeNotZero, - 0, false)) { - // FIXME: We should copy the content of the original buffer. - const ProgramState *stateRealloc = MallocMemAux(C, CE, CE->getArg(1), - UnknownVal(), stateFree); - C.addTransition(stateRealloc); - } + // Default behavior. + if (ProgramStateRef stateFree = FreeMemAux(C, CE, state, 0, false)) { + // FIXME: We should copy the content of the original buffer. + ProgramStateRef stateRealloc = MallocMemAux(C, CE, CE->getArg(1), + UnknownVal(), stateFree); + if (!stateRealloc) + return 0; + stateRealloc = stateRealloc->set<ReallocPairs>(ToPtr, + ReallocPair(FromPtr, FreesOnFail)); + C.getSymbolManager().addSymbolDependency(ToPtr, FromPtr); + return stateRealloc; } + return 0; } -void MallocChecker::CallocMem(CheckerContext &C, const CallExpr *CE) { - const ProgramState *state = C.getState(); - SValBuilder &svalBuilder = C.getSValBuilder(); +ProgramStateRef MallocChecker::CallocMem(CheckerContext &C, const CallExpr *CE){ + if (CE->getNumArgs() < 2) + return 0; - SVal count = state->getSVal(CE->getArg(0)); - SVal elementSize = state->getSVal(CE->getArg(1)); + ProgramStateRef state = C.getState(); + SValBuilder &svalBuilder = C.getSValBuilder(); + const LocationContext *LCtx = C.getLocationContext(); + SVal count = state->getSVal(CE->getArg(0), LCtx); + SVal elementSize = state->getSVal(CE->getArg(1), LCtx); SVal TotalSize = svalBuilder.evalBinOp(state, BO_Mul, count, elementSize, svalBuilder.getContext().getSizeType()); SVal zeroVal = svalBuilder.makeZeroVal(svalBuilder.getContext().CharTy); - C.addTransition(MallocMemAux(C, CE, TotalSize, zeroVal, state)); + return MallocMemAux(C, CE, TotalSize, zeroVal, state); +} + +LeakInfo +MallocChecker::getAllocationSite(const ExplodedNode *N, SymbolRef Sym, + CheckerContext &C) const { + const LocationContext *LeakContext = N->getLocationContext(); + // Walk the ExplodedGraph backwards and find the first node that referred to + // the tracked symbol. + const ExplodedNode *AllocNode = N; + const MemRegion *ReferenceRegion = 0; + + while (N) { + ProgramStateRef State = N->getState(); + if (!State->get<RegionState>(Sym)) + break; + + // Find the most recent expression bound to the symbol in the current + // context. + if (!ReferenceRegion) { + if (const MemRegion *MR = C.getLocationRegionIfPostStore(N)) { + SVal Val = State->getSVal(MR); + if (Val.getAsLocSymbol() == Sym) + ReferenceRegion = MR; + } + } + + // Allocation node, is the last node in the current context in which the + // symbol was tracked. + if (N->getLocationContext() == LeakContext) + AllocNode = N; + N = N->pred_empty() ? NULL : *(N->pred_begin()); + } + + ProgramPoint P = AllocNode->getLocation(); + const Stmt *AllocationStmt = 0; + if (isa<StmtPoint>(P)) + AllocationStmt = cast<StmtPoint>(P).getStmt(); + + return LeakInfo(AllocationStmt, ReferenceRegion); +} + +void MallocChecker::reportLeak(SymbolRef Sym, ExplodedNode *N, + CheckerContext &C) const { + assert(N); + if (!BT_Leak) { + BT_Leak.reset(new BugType("Memory leak", "Memory Error")); + // Leaks should not be reported if they are post-dominated by a sink: + // (1) Sinks are higher importance bugs. + // (2) NoReturnFunctionChecker uses sink nodes to represent paths ending + // with __noreturn functions such as assert() or exit(). We choose not + // to report leaks on such paths. + BT_Leak->setSuppressOnSink(true); + } + + // Most bug reports are cached at the location where they occurred. + // With leaks, we want to unique them by the location where they were + // allocated, and only report a single path. + PathDiagnosticLocation LocUsedForUniqueing; + const Stmt *AllocStmt = 0; + const MemRegion *Region = 0; + llvm::tie(AllocStmt, Region) = getAllocationSite(N, Sym, C); + if (AllocStmt) + LocUsedForUniqueing = PathDiagnosticLocation::createBegin(AllocStmt, + C.getSourceManager(), N->getLocationContext()); + + SmallString<200> buf; + llvm::raw_svector_ostream os(buf); + os << "Memory is never released; potential leak"; + if (Region) { + os << " of memory pointed to by '"; + Region->dumpPretty(os); + os <<'\''; + } + + BugReport *R = new BugReport(*BT_Leak, os.str(), N, LocUsedForUniqueing); + R->markInteresting(Sym); + R->addVisitor(new MallocBugVisitor(Sym)); + C.EmitReport(R); } void MallocChecker::checkDeadSymbols(SymbolReaper &SymReaper, @@ -573,174 +902,562 @@ void MallocChecker::checkDeadSymbols(SymbolReaper &SymReaper, if (!SymReaper.hasDeadSymbols()) return; - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); RegionStateTy RS = state->get<RegionState>(); RegionStateTy::Factory &F = state->get_context<RegionState>(); bool generateReport = false; - + llvm::SmallVector<SymbolRef, 2> Errors; for (RegionStateTy::iterator I = RS.begin(), E = RS.end(); I != E; ++I) { if (SymReaper.isDead(I->first)) { - if (I->second.isAllocated()) + if (I->second.isAllocated()) { generateReport = true; - + Errors.push_back(I->first); + } // Remove the dead symbol from the map. RS = F.remove(RS, I->first); } } - ExplodedNode *N = C.generateNode(state->set<RegionState>(RS)); - - // FIXME: This does not handle when we have multiple leaks at a single - // place. - if (N && generateReport) { - if (!BT_Leak) - BT_Leak.reset(new BuiltinBug("Memory leak", - "Allocated memory never released. Potential memory leak.")); - // FIXME: where it is allocated. - BugReport *R = new BugReport(*BT_Leak, BT_Leak->getDescription(), N); - C.EmitReport(R); + // Cleanup the Realloc Pairs Map. + ReallocMap RP = state->get<ReallocPairs>(); + for (ReallocMap::iterator I = RP.begin(), E = RP.end(); I != E; ++I) { + if (SymReaper.isDead(I->first) || + SymReaper.isDead(I->second.ReallocatedSym)) { + state = state->remove<ReallocPairs>(I->first); + } + } + + // Generate leak node. + static SimpleProgramPointTag Tag("MallocChecker : DeadSymbolsLeak"); + ExplodedNode *N = C.addTransition(C.getState(), C.getPredecessor(), &Tag); + + if (generateReport) { + for (llvm::SmallVector<SymbolRef, 2>::iterator + I = Errors.begin(), E = Errors.end(); I != E; ++I) { + reportLeak(*I, N, C); + } } + C.addTransition(state->set<RegionState>(RS), N); } -void MallocChecker::checkEndPath(EndOfFunctionNodeBuilder &B, - ExprEngine &Eng) const { - const ProgramState *state = B.getState(); +void MallocChecker::checkEndPath(CheckerContext &C) const { + ProgramStateRef state = C.getState(); RegionStateTy M = state->get<RegionState>(); + // If inside inlined call, skip it. + if (C.getLocationContext()->getParent() != 0) + return; + for (RegionStateTy::iterator I = M.begin(), E = M.end(); I != E; ++I) { RefState RS = I->second; if (RS.isAllocated()) { - ExplodedNode *N = B.generateNode(state); - if (N) { - if (!BT_Leak) - BT_Leak.reset(new BuiltinBug("Memory leak", - "Allocated memory never released. Potential memory leak.")); - BugReport *R = new BugReport(*BT_Leak, BT_Leak->getDescription(), N); - Eng.getBugReporter().EmitReport(R); - } + ExplodedNode *N = C.addTransition(state); + if (N) + reportLeak(I->first, N, C); } } } -void MallocChecker::checkPreStmt(const ReturnStmt *S, CheckerContext &C) const { - const Expr *retExpr = S->getRetValue(); - if (!retExpr) +bool MallocChecker::checkEscape(SymbolRef Sym, const Stmt *S, + CheckerContext &C) const { + ProgramStateRef state = C.getState(); + const RefState *RS = state->get<RegionState>(Sym); + if (!RS) + return false; + + if (RS->isAllocated()) { + state = state->set<RegionState>(Sym, RefState::getEscaped(S)); + C.addTransition(state); + return true; + } + return false; +} + +void MallocChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const { + if (isMemFunction(C.getCalleeDecl(CE), C.getASTContext())) return; - const ProgramState *state = C.getState(); + // Check use after free, when a freed pointer is passed to a call. + ProgramStateRef State = C.getState(); + for (CallExpr::const_arg_iterator I = CE->arg_begin(), + E = CE->arg_end(); I != E; ++I) { + const Expr *A = *I; + if (A->getType().getTypePtr()->isAnyPointerType()) { + SymbolRef Sym = State->getSVal(A, C.getLocationContext()).getAsSymbol(); + if (!Sym) + continue; + if (checkUseAfterFree(Sym, C, A)) + return; + } + } +} - SymbolRef Sym = state->getSVal(retExpr).getAsSymbol(); - if (!Sym) +void MallocChecker::checkPreStmt(const ReturnStmt *S, CheckerContext &C) const { + const Expr *E = S->getRetValue(); + if (!E) return; - const RefState *RS = state->get<RegionState>(Sym); - if (!RS) + // Check if we are returning a symbol. + SVal RetVal = C.getState()->getSVal(E, C.getLocationContext()); + SymbolRef Sym = RetVal.getAsSymbol(); + if (!Sym) + // If we are returning a field of the allocated struct or an array element, + // the callee could still free the memory. + // TODO: This logic should be a part of generic symbol escape callback. + if (const MemRegion *MR = RetVal.getAsRegion()) + if (isa<FieldRegion>(MR) || isa<ElementRegion>(MR)) + if (const SymbolicRegion *BMR = + dyn_cast<SymbolicRegion>(MR->getBaseRegion())) + Sym = BMR->getSymbol(); + if (!Sym) return; - // FIXME: check other cases. - if (RS->isAllocated()) - state = state->set<RegionState>(Sym, RefState::getEscaped(S)); + // Check if we are returning freed memory. + if (checkUseAfterFree(Sym, C, E)) + return; - C.addTransition(state); + // If this function body is not inlined, check if the symbol is escaping. + if (C.getLocationContext()->getParent() == 0) + checkEscape(Sym, E, C); } -const ProgramState *MallocChecker::evalAssume(const ProgramState *state, SVal Cond, - bool Assumption) const { - // If a symblic region is assumed to NULL, set its state to AllocateFailed. - // FIXME: should also check symbols assumed to non-null. +// TODO: Blocks should be either inlined or should call invalidate regions +// upon invocation. After that's in place, special casing here will not be +// needed. +void MallocChecker::checkPostStmt(const BlockExpr *BE, + CheckerContext &C) const { - RegionStateTy RS = state->get<RegionState>(); + // Scan the BlockDecRefExprs for any object the retain count checker + // may be tracking. + if (!BE->getBlockDecl()->hasCaptures()) + return; - for (RegionStateTy::iterator I = RS.begin(), E = RS.end(); I != E; ++I) { - // If the symbol is assumed to NULL, this will return an APSInt*. - if (state->getSymVal(I.getKey())) - state = state->set<RegionState>(I.getKey(),RefState::getAllocateFailed()); + ProgramStateRef state = C.getState(); + const BlockDataRegion *R = + cast<BlockDataRegion>(state->getSVal(BE, + C.getLocationContext()).getAsRegion()); + + BlockDataRegion::referenced_vars_iterator I = R->referenced_vars_begin(), + E = R->referenced_vars_end(); + + if (I == E) + return; + + SmallVector<const MemRegion*, 10> Regions; + const LocationContext *LC = C.getLocationContext(); + MemRegionManager &MemMgr = C.getSValBuilder().getRegionManager(); + + for ( ; I != E; ++I) { + const VarRegion *VR = *I; + if (VR->getSuperRegion() == R) { + VR = MemMgr.getVarRegion(VR->getDecl(), LC); + } + Regions.push_back(VR); } - return state; + state = + state->scanReachableSymbols<StopTrackingCallback>(Regions.data(), + Regions.data() + Regions.size()).getState(); + C.addTransition(state); +} + +bool MallocChecker::checkUseAfterFree(SymbolRef Sym, CheckerContext &C, + const Stmt *S) const { + assert(Sym); + const RefState *RS = C.getState()->get<RegionState>(Sym); + if (RS && RS->isReleased()) { + if (ExplodedNode *N = C.generateSink()) { + if (!BT_UseFree) + BT_UseFree.reset(new BugType("Use-after-free", "Memory Error")); + + BugReport *R = new BugReport(*BT_UseFree, + "Use of memory after it is freed",N); + if (S) + R->addRange(S->getSourceRange()); + R->markInteresting(Sym); + R->addVisitor(new MallocBugVisitor(Sym)); + C.EmitReport(R); + return true; + } + } + return false; } // Check if the location is a freed symbolic region. void MallocChecker::checkLocation(SVal l, bool isLoad, const Stmt *S, CheckerContext &C) const { SymbolRef Sym = l.getLocSymbolInBase(); - if (Sym) { - const RefState *RS = C.getState()->get<RegionState>(Sym); - if (RS && RS->isReleased()) { - if (ExplodedNode *N = C.generateNode()) { - if (!BT_UseFree) - BT_UseFree.reset(new BuiltinBug("Use dynamically allocated memory " - "after it is freed.")); - - BugReport *R = new BugReport(*BT_UseFree, BT_UseFree->getDescription(), - N); - C.EmitReport(R); + if (Sym) + checkUseAfterFree(Sym, C); +} + +//===----------------------------------------------------------------------===// +// Check various ways a symbol can be invalidated. +// TODO: This logic (the next 3 functions) is copied/similar to the +// RetainRelease checker. We might want to factor this out. +//===----------------------------------------------------------------------===// + +// Stop tracking symbols when a value escapes as a result of checkBind. +// A value escapes in three possible cases: +// (1) we are binding to something that is not a memory region. +// (2) we are binding to a memregion that does not have stack storage +// (3) we are binding to a memregion with stack storage that the store +// does not understand. +void MallocChecker::checkBind(SVal loc, SVal val, const Stmt *S, + CheckerContext &C) const { + // Are we storing to something that causes the value to "escape"? + bool escapes = true; + ProgramStateRef state = C.getState(); + + if (loc::MemRegionVal *regionLoc = dyn_cast<loc::MemRegionVal>(&loc)) { + escapes = !regionLoc->getRegion()->hasStackStorage(); + + if (!escapes) { + // To test (3), generate a new state with the binding added. If it is + // the same state, then it escapes (since the store cannot represent + // the binding). + escapes = (state == (state->bindLoc(*regionLoc, val))); + } + if (!escapes) { + // Case 4: We do not currently model what happens when a symbol is + // assigned to a struct field, so be conservative here and let the symbol + // go. TODO: This could definitely be improved upon. + escapes = !isa<VarRegion>(regionLoc->getRegion()); + } + } + + // If our store can represent the binding and we aren't storing to something + // that doesn't have local storage then just return and have the simulation + // state continue as is. + if (!escapes) + return; + + // Otherwise, find all symbols referenced by 'val' that we are tracking + // and stop tracking them. + state = state->scanReachableSymbols<StopTrackingCallback>(val).getState(); + C.addTransition(state); +} + +// If a symbolic region is assumed to NULL (or another constant), stop tracking +// it - assuming that allocation failed on this path. +ProgramStateRef MallocChecker::evalAssume(ProgramStateRef state, + SVal Cond, + bool Assumption) const { + RegionStateTy RS = state->get<RegionState>(); + for (RegionStateTy::iterator I = RS.begin(), E = RS.end(); I != E; ++I) { + // If the symbol is assumed to NULL or another constant, this will + // return an APSInt*. + if (state->getSymVal(I.getKey())) + state = state->remove<RegionState>(I.getKey()); + } + + // Realloc returns 0 when reallocation fails, which means that we should + // restore the state of the pointer being reallocated. + ReallocMap RP = state->get<ReallocPairs>(); + for (ReallocMap::iterator I = RP.begin(), E = RP.end(); I != E; ++I) { + // If the symbol is assumed to NULL or another constant, this will + // return an APSInt*. + if (state->getSymVal(I.getKey())) { + SymbolRef ReallocSym = I.getData().ReallocatedSym; + const RefState *RS = state->get<RegionState>(ReallocSym); + if (RS) { + if (RS->isReleased() && ! I.getData().IsFreeOnFailure) + state = state->set<RegionState>(ReallocSym, + RefState::getAllocateUnchecked(RS->getStmt())); } + state = state->remove<ReallocPairs>(I.getKey()); } } + + return state; } -void MallocChecker::checkBind(SVal location, SVal val, - const Stmt *BindS, CheckerContext &C) const { - // The PreVisitBind implements the same algorithm as already used by the - // Objective C ownership checker: if the pointer escaped from this scope by - // assignment, let it go. However, assigning to fields of a stack-storage - // structure does not transfer ownership. +// Check if the function is known to us. So, for example, we could +// conservatively assume it can free/reallocate it's pointer arguments. +// (We assume that the pointers cannot escape through calls to system +// functions not handled by this checker.) +bool MallocChecker::doesNotFreeMemory(const CallOrObjCMessage *Call, + ProgramStateRef State) const { + if (!Call) + return false; - const ProgramState *state = C.getState(); - DefinedOrUnknownSVal l = cast<DefinedOrUnknownSVal>(location); + // For now, assume that any C++ call can free memory. + // TODO: If we want to be more optimistic here, we'll need to make sure that + // regions escape to C++ containers. They seem to do that even now, but for + // mysterious reasons. + if (Call->isCXXCall()) + return false; - // Check for null dereferences. - if (!isa<Loc>(l)) - return; + const Decl *D = Call->getDecl(); + if (!D) + return false; - // Before checking if the state is null, check if 'val' has a RefState. - // Only then should we check for null and bifurcate the state. - SymbolRef Sym = val.getLocSymbolInBase(); - if (Sym) { - if (const RefState *RS = state->get<RegionState>(Sym)) { - // If ptr is NULL, no operation is performed. - const ProgramState *notNullState, *nullState; - llvm::tie(notNullState, nullState) = state->assume(l); - - // Generate a transition for 'nullState' to record the assumption - // that the state was null. - if (nullState) - C.addTransition(nullState); - - if (!notNullState) - return; + ASTContext &ASTC = State->getStateManager().getContext(); + + // If it's one of the allocation functions we can reason about, we model + // its behavior explicitly. + if (isa<FunctionDecl>(D) && isMemFunction(cast<FunctionDecl>(D), ASTC)) { + return true; + } + + // If it's not a system call, assume it frees memory. + SourceManager &SM = ASTC.getSourceManager(); + if (!SM.isInSystemHeader(D->getLocation())) + return false; + + // Process C/ObjC functions. + if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) { + // White list the system functions whose arguments escape. + const IdentifierInfo *II = FD->getIdentifier(); + if (!II) + return true; + StringRef FName = II->getName(); + + // White list thread local storage. + if (FName.equals("pthread_setspecific")) + return false; - if (RS->isAllocated()) { - // Something we presently own is being assigned somewhere. - const MemRegion *AR = location.getAsRegion(); - if (!AR) - return; - AR = AR->StripCasts()->getBaseRegion(); - do { - // If it is on the stack, we still own it. - if (AR->hasStackNonParametersStorage()) - break; - - // If the state can't represent this binding, we still own it. - if (notNullState == (notNullState->bindLoc(cast<Loc>(location), - UnknownVal()))) - break; - - // We no longer own this pointer. - notNullState = - notNullState->set<RegionState>(Sym, - RefState::getRelinquished(BindS)); + // White list the 'XXXNoCopy' ObjC functions. + if (FName.endswith("NoCopy")) { + // Look for the deallocator argument. We know that the memory ownership + // is not transfered only if the deallocator argument is + // 'kCFAllocatorNull'. + for (unsigned i = 1; i < Call->getNumArgs(); ++i) { + const Expr *ArgE = Call->getArg(i)->IgnoreParenCasts(); + if (const DeclRefExpr *DE = dyn_cast<DeclRefExpr>(ArgE)) { + StringRef DeallocatorName = DE->getFoundDecl()->getName(); + if (DeallocatorName == "kCFAllocatorNull") + return true; } - while (false); } - C.addTransition(notNullState); + return false; + } + + // PR12101 + // Many CoreFoundation and CoreGraphics might allow a tracked object + // to escape. + if (Call->isCFCGAllowingEscape(FName)) + return false; + + // Associating streams with malloced buffers. The pointer can escape if + // 'closefn' is specified (and if that function does free memory). + // Currently, we do not inspect the 'closefn' function (PR12101). + if (FName == "funopen") + if (Call->getNumArgs() >= 4 && !Call->getArgSVal(4).isConstant(0)) + return false; + + // Do not warn on pointers passed to 'setbuf' when used with std streams, + // these leaks might be intentional when setting the buffer for stdio. + // http://stackoverflow.com/questions/2671151/who-frees-setvbuf-buffer + if (FName == "setbuf" || FName =="setbuffer" || + FName == "setlinebuf" || FName == "setvbuf") { + if (Call->getNumArgs() >= 1) + if (const DeclRefExpr *Arg = + dyn_cast<DeclRefExpr>(Call->getArg(0)->IgnoreParenCasts())) + if (const VarDecl *D = dyn_cast<VarDecl>(Arg->getDecl())) + if (D->getCanonicalDecl()->getName().find("std") + != StringRef::npos) + return false; + } + + // A bunch of other functions, which take ownership of a pointer (See retain + // release checker). Not all the parameters here are invalidated, but the + // Malloc checker cannot differentiate between them. The right way of doing + // this would be to implement a pointer escapes callback. + if (FName == "CVPixelBufferCreateWithBytes" || + FName == "CGBitmapContextCreateWithData" || + FName == "CVPixelBufferCreateWithPlanarBytes" || + FName == "OSAtomicEnqueue") { + return false; + } + + // Whitelist NSXXInsertXX, for example NSMapInsertIfAbsent, since they can + // be deallocated by NSMapRemove. + if (FName.startswith("NS") && (FName.find("Insert") != StringRef::npos)) + return false; + + // Otherwise, assume that the function does not free memory. + // Most system calls, do not free the memory. + return true; + + // Process ObjC functions. + } else if (const ObjCMethodDecl * ObjCD = dyn_cast<ObjCMethodDecl>(D)) { + Selector S = ObjCD->getSelector(); + + // White list the ObjC functions which do free memory. + // - Anything containing 'freeWhenDone' param set to 1. + // Ex: dataWithBytesNoCopy:length:freeWhenDone. + for (unsigned i = 1; i < S.getNumArgs(); ++i) { + if (S.getNameForSlot(i).equals("freeWhenDone")) { + if (Call->getArgSVal(i).isConstant(1)) + return false; + else + return true; + } } + + // If the first selector ends with NoCopy, assume that the ownership is + // transfered as well. + // Ex: [NSData dataWithBytesNoCopy:bytes length:10]; + if (S.getNameForSlot(0).endswith("NoCopy")) { + return false; + } + + // Otherwise, assume that the function does not free memory. + // Most system calls, do not free the memory. + return true; } + + // Otherwise, assume that the function can free memory. + return false; + } -void ento::registerMallocChecker(CheckerManager &mgr) { - mgr.registerChecker<MallocChecker>(); +// If the symbol we are tracking is invalidated, but not explicitly (ex: the &p +// escapes, when we are tracking p), do not track the symbol as we cannot reason +// about it anymore. +ProgramStateRef +MallocChecker::checkRegionChanges(ProgramStateRef State, + const StoreManager::InvalidatedSymbols *invalidated, + ArrayRef<const MemRegion *> ExplicitRegions, + ArrayRef<const MemRegion *> Regions, + const CallOrObjCMessage *Call) const { + if (!invalidated || invalidated->empty()) + return State; + llvm::SmallPtrSet<SymbolRef, 8> WhitelistedSymbols; + + // If it's a call which might free or reallocate memory, we assume that all + // regions (explicit and implicit) escaped. + + // Otherwise, whitelist explicit pointers; we still can track them. + if (!Call || doesNotFreeMemory(Call, State)) { + for (ArrayRef<const MemRegion *>::iterator I = ExplicitRegions.begin(), + E = ExplicitRegions.end(); I != E; ++I) { + if (const SymbolicRegion *R = (*I)->StripCasts()->getAs<SymbolicRegion>()) + WhitelistedSymbols.insert(R->getSymbol()); + } + } + + for (StoreManager::InvalidatedSymbols::const_iterator I=invalidated->begin(), + E = invalidated->end(); I!=E; ++I) { + SymbolRef sym = *I; + if (WhitelistedSymbols.count(sym)) + continue; + // The symbol escaped. + if (const RefState *RS = State->get<RegionState>(sym)) + State = State->set<RegionState>(sym, RefState::getEscaped(RS->getStmt())); + } + return State; +} + +static SymbolRef findFailedReallocSymbol(ProgramStateRef currState, + ProgramStateRef prevState) { + ReallocMap currMap = currState->get<ReallocPairs>(); + ReallocMap prevMap = prevState->get<ReallocPairs>(); + + for (ReallocMap::iterator I = prevMap.begin(), E = prevMap.end(); + I != E; ++I) { + SymbolRef sym = I.getKey(); + if (!currMap.lookup(sym)) + return sym; + } + + return NULL; } + +PathDiagnosticPiece * +MallocChecker::MallocBugVisitor::VisitNode(const ExplodedNode *N, + const ExplodedNode *PrevN, + BugReporterContext &BRC, + BugReport &BR) { + ProgramStateRef state = N->getState(); + ProgramStateRef statePrev = PrevN->getState(); + + const RefState *RS = state->get<RegionState>(Sym); + const RefState *RSPrev = statePrev->get<RegionState>(Sym); + if (!RS && !RSPrev) + return 0; + + const Stmt *S = 0; + const char *Msg = 0; + StackHintGeneratorForSymbol *StackHint = 0; + + // Retrieve the associated statement. + ProgramPoint ProgLoc = N->getLocation(); + if (isa<StmtPoint>(ProgLoc)) + S = cast<StmtPoint>(ProgLoc).getStmt(); + // If an assumption was made on a branch, it should be caught + // here by looking at the state transition. + if (isa<BlockEdge>(ProgLoc)) { + const CFGBlock *srcBlk = cast<BlockEdge>(ProgLoc).getSrc(); + S = srcBlk->getTerminator(); + } + if (!S) + return 0; + + // Find out if this is an interesting point and what is the kind. + if (Mode == Normal) { + if (isAllocated(RS, RSPrev, S)) { + Msg = "Memory is allocated"; + StackHint = new StackHintGeneratorForSymbol(Sym, + "Returned allocated memory"); + } else if (isReleased(RS, RSPrev, S)) { + Msg = "Memory is released"; + StackHint = new StackHintGeneratorForSymbol(Sym, + "Returned released memory"); + } else if (isReallocFailedCheck(RS, RSPrev, S)) { + Mode = ReallocationFailed; + Msg = "Reallocation failed"; + StackHint = new StackHintGeneratorForReallocationFailed(Sym, + "Reallocation failed"); + + if (SymbolRef sym = findFailedReallocSymbol(state, statePrev)) { + // Is it possible to fail two reallocs WITHOUT testing in between? + assert((!FailedReallocSymbol || FailedReallocSymbol == sym) && + "We only support one failed realloc at a time."); + BR.markInteresting(sym); + FailedReallocSymbol = sym; + } + } + + // We are in a special mode if a reallocation failed later in the path. + } else if (Mode == ReallocationFailed) { + assert(FailedReallocSymbol && "No symbol to look for."); + + // Is this is the first appearance of the reallocated symbol? + if (!statePrev->get<RegionState>(FailedReallocSymbol)) { + // If we ever hit this assert, that means BugReporter has decided to skip + // node pairs or visit them out of order. + assert(state->get<RegionState>(FailedReallocSymbol) && + "Missed the reallocation point"); + + // We're at the reallocation point. + Msg = "Attempt to reallocate memory"; + StackHint = new StackHintGeneratorForSymbol(Sym, + "Returned reallocated memory"); + FailedReallocSymbol = NULL; + Mode = Normal; + } + } + + if (!Msg) + return 0; + assert(StackHint); + + // Generate the extra diagnostic. + PathDiagnosticLocation Pos(S, BRC.getSourceManager(), + N->getLocationContext()); + return new PathDiagnosticEventPiece(Pos, Msg, true, StackHint); +} + + +#define REGISTER_CHECKER(name) \ +void ento::register##name(CheckerManager &mgr) {\ + registerCStringCheckerBasic(mgr); \ + mgr.registerChecker<MallocChecker>()->Filter.C##name = true;\ +} + +REGISTER_CHECKER(MallocPessimistic) +REGISTER_CHECKER(MallocOptimistic) diff --git a/lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp b/lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp index cf5501a..daec418 100644 --- a/lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp @@ -205,7 +205,7 @@ void MallocOverflowSecurityChecker::OutputPossibleOverflows( // Delete any possible overflows which have a comparison. CheckOverflowOps c(PossibleMallocOverflows, BR.getContext()); - c.Visit(mgr.getAnalysisContext(D)->getBody()); + c.Visit(mgr.getAnalysisDeclContext(D)->getBody()); // Output warnings for all overflows that are left. for (CheckOverflowOps::theVecType::iterator @@ -214,11 +214,10 @@ void MallocOverflowSecurityChecker::OutputPossibleOverflows( i != e; ++i) { SourceRange R = i->mulop->getSourceRange(); - BR.EmitBasicReport("MallocOverflowSecurityChecker", + BR.EmitBasicReport(D, "malloc() size overflow", categories::UnixAPI, "the computation of the size of the memory allocation may overflow", PathDiagnosticLocation::createOperatorLoc(i->mulop, - BR.getSourceManager()), - &R, 1); + BR.getSourceManager()), &R, 1); } } diff --git a/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp b/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp new file mode 100644 index 0000000..08a9da1 --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp @@ -0,0 +1,211 @@ +// MallocSizeofChecker.cpp - Check for dubious malloc arguments ---*- C++ -*-=// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Reports inconsistencies between the casted type of the return value of a +// malloc/calloc/realloc call and the operand of any sizeof expressions +// contained within its argument(s). +// +//===----------------------------------------------------------------------===// + +#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" +#include "clang/AST/StmtVisitor.h" +#include "clang/AST/TypeLoc.h" +#include "llvm/ADT/SmallString.h" + +using namespace clang; +using namespace ento; + +namespace { + +typedef std::pair<const TypeSourceInfo *, const CallExpr *> TypeCallPair; +typedef llvm::PointerUnion<const Stmt *, const VarDecl *> ExprParent; + +class CastedAllocFinder + : public ConstStmtVisitor<CastedAllocFinder, TypeCallPair> { + IdentifierInfo *II_malloc, *II_calloc, *II_realloc; + +public: + struct CallRecord { + ExprParent CastedExprParent; + const Expr *CastedExpr; + const TypeSourceInfo *ExplicitCastType; + const CallExpr *AllocCall; + + CallRecord(ExprParent CastedExprParent, const Expr *CastedExpr, + const TypeSourceInfo *ExplicitCastType, + const CallExpr *AllocCall) + : CastedExprParent(CastedExprParent), CastedExpr(CastedExpr), + ExplicitCastType(ExplicitCastType), AllocCall(AllocCall) {} + }; + + typedef std::vector<CallRecord> CallVec; + CallVec Calls; + + CastedAllocFinder(ASTContext *Ctx) : + II_malloc(&Ctx->Idents.get("malloc")), + II_calloc(&Ctx->Idents.get("calloc")), + II_realloc(&Ctx->Idents.get("realloc")) {} + + void VisitChild(ExprParent Parent, const Stmt *S) { + TypeCallPair AllocCall = Visit(S); + if (AllocCall.second && AllocCall.second != S) + Calls.push_back(CallRecord(Parent, cast<Expr>(S), AllocCall.first, + AllocCall.second)); + } + + void VisitChildren(const Stmt *S) { + for (Stmt::const_child_iterator I = S->child_begin(), E = S->child_end(); + I!=E; ++I) + if (const Stmt *child = *I) + VisitChild(S, child); + } + + TypeCallPair VisitCastExpr(const CastExpr *E) { + return Visit(E->getSubExpr()); + } + + TypeCallPair VisitExplicitCastExpr(const ExplicitCastExpr *E) { + return TypeCallPair(E->getTypeInfoAsWritten(), + Visit(E->getSubExpr()).second); + } + + TypeCallPair VisitParenExpr(const ParenExpr *E) { + return Visit(E->getSubExpr()); + } + + TypeCallPair VisitStmt(const Stmt *S) { + VisitChildren(S); + return TypeCallPair(); + } + + TypeCallPair VisitCallExpr(const CallExpr *E) { + VisitChildren(E); + const FunctionDecl *FD = E->getDirectCallee(); + if (FD) { + IdentifierInfo *II = FD->getIdentifier(); + if (II == II_malloc || II == II_calloc || II == II_realloc) + return TypeCallPair((const TypeSourceInfo *)0, E); + } + return TypeCallPair(); + } + + TypeCallPair VisitDeclStmt(const DeclStmt *S) { + for (DeclStmt::const_decl_iterator I = S->decl_begin(), E = S->decl_end(); + I!=E; ++I) + if (const VarDecl *VD = dyn_cast<VarDecl>(*I)) + if (const Expr *Init = VD->getInit()) + VisitChild(VD, Init); + return TypeCallPair(); + } +}; + +class SizeofFinder : public ConstStmtVisitor<SizeofFinder> { +public: + std::vector<const UnaryExprOrTypeTraitExpr *> Sizeofs; + + void VisitBinMul(const BinaryOperator *E) { + Visit(E->getLHS()); + Visit(E->getRHS()); + } + + void VisitBinAdd(const BinaryOperator *E) { + Visit(E->getLHS()); + Visit(E->getRHS()); + } + + void VisitImplicitCastExpr(const ImplicitCastExpr *E) { + return Visit(E->getSubExpr()); + } + + void VisitParenExpr(const ParenExpr *E) { + return Visit(E->getSubExpr()); + } + + void VisitUnaryExprOrTypeTraitExpr(const UnaryExprOrTypeTraitExpr *E) { + if (E->getKind() != UETT_SizeOf) + return; + + Sizeofs.push_back(E); + } +}; + +class MallocSizeofChecker : public Checker<check::ASTCodeBody> { +public: + void checkASTCodeBody(const Decl *D, AnalysisManager& mgr, + BugReporter &BR) const { + AnalysisDeclContext *ADC = mgr.getAnalysisDeclContext(D); + CastedAllocFinder Finder(&BR.getContext()); + Finder.Visit(D->getBody()); + for (CastedAllocFinder::CallVec::iterator i = Finder.Calls.begin(), + e = Finder.Calls.end(); i != e; ++i) { + QualType CastedType = i->CastedExpr->getType(); + if (!CastedType->isPointerType()) + continue; + QualType PointeeType = CastedType->getAs<PointerType>()->getPointeeType(); + if (PointeeType->isVoidType()) + continue; + + for (CallExpr::const_arg_iterator ai = i->AllocCall->arg_begin(), + ae = i->AllocCall->arg_end(); ai != ae; ++ai) { + if (!(*ai)->getType()->isIntegerType()) + continue; + + SizeofFinder SFinder; + SFinder.Visit(*ai); + if (SFinder.Sizeofs.size() != 1) + continue; + + QualType SizeofType = SFinder.Sizeofs[0]->getTypeOfArgument(); + if (!BR.getContext().hasSameUnqualifiedType(PointeeType, SizeofType)) { + const TypeSourceInfo *TSI = 0; + if (i->CastedExprParent.is<const VarDecl *>()) { + TSI = + i->CastedExprParent.get<const VarDecl *>()->getTypeSourceInfo(); + } else { + TSI = i->ExplicitCastType; + } + + SmallString<64> buf; + llvm::raw_svector_ostream OS(buf); + + OS << "Result of '" + << i->AllocCall->getDirectCallee()->getIdentifier()->getName() + << "' is converted to type '" + << CastedType.getAsString() << "', whose pointee type '" + << PointeeType.getAsString() << "' is incompatible with " + << "sizeof operand type '" << SizeofType.getAsString() << "'"; + llvm::SmallVector<SourceRange, 4> Ranges; + Ranges.push_back(i->AllocCall->getCallee()->getSourceRange()); + Ranges.push_back(SFinder.Sizeofs[0]->getSourceRange()); + if (TSI) + Ranges.push_back(TSI->getTypeLoc().getSourceRange()); + + PathDiagnosticLocation L = + PathDiagnosticLocation::createBegin(i->AllocCall->getCallee(), + BR.getSourceManager(), ADC); + + BR.EmitBasicReport(D, "allocator sizeof operand mismatch", + categories::UnixAPI, + OS.str(), + L, Ranges.data(), Ranges.size()); + } + } + } + } +}; + +} + +void ento::registerMallocSizeofChecker(CheckerManager &mgr) { + mgr.registerChecker<MallocSizeofChecker>(); +} diff --git a/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp b/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp index 7f74a7d..4989ba8 100644 --- a/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp @@ -19,6 +19,7 @@ #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ObjCMessage.h" @@ -31,7 +32,7 @@ using namespace ento; namespace { class NSAutoreleasePoolChecker : public Checker<check::PreObjCMessage> { - + mutable OwningPtr<BugType> BT; mutable Selector releaseS; public: @@ -65,20 +66,24 @@ void NSAutoreleasePoolChecker::checkPreObjCMessage(ObjCMessage msg, // Sending 'release' message? if (msg.getSelector() != releaseS) return; - - SourceRange R = msg.getSourceRange(); - BugReporter &BR = C.getBugReporter(); - const LocationContext *LC = C.getPredecessor()->getLocationContext(); - const SourceManager &SM = BR.getSourceManager(); - const Expr *E = msg.getMsgOrPropExpr(); - PathDiagnosticLocation L = PathDiagnosticLocation::createBegin(E, SM, LC); - C.getBugReporter().EmitBasicReport("Use -drain instead of -release", - "API Upgrade (Apple)", - "Use -drain instead of -release when using NSAutoreleasePool " - "and garbage collection", L, &R, 1); + + if (!BT) + BT.reset(new BugType("Use -drain instead of -release", + "API Upgrade (Apple)")); + + ExplodedNode *N = C.addTransition(); + if (!N) { + assert(0); + return; + } + + BugReport *Report = new BugReport(*BT, "Use -drain instead of -release when " + "using NSAutoreleasePool and garbage collection", N); + Report->addRange(msg.getSourceRange()); + C.EmitReport(Report); } void ento::registerNSAutoreleasePoolChecker(CheckerManager &mgr) { - if (mgr.getLangOptions().getGC() != LangOptions::NonGC) + if (mgr.getLangOpts().getGC() != LangOptions::NonGC) mgr.registerChecker<NSAutoreleasePoolChecker>(); } diff --git a/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp b/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp index 5678998..f826573 100644 --- a/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp @@ -74,7 +74,7 @@ void NSErrorMethodChecker::checkASTDecl(const ObjCMethodDecl *D, "error occurred"; PathDiagnosticLocation L = PathDiagnosticLocation::create(D, BR.getSourceManager()); - BR.EmitBasicReport("Bad return type when passing NSError**", + BR.EmitBasicReport(D, "Bad return type when passing NSError**", "Coding conventions (Apple)", err, L); } } @@ -122,7 +122,7 @@ void CFErrorFunctionChecker::checkASTDecl(const FunctionDecl *D, "error occurred"; PathDiagnosticLocation L = PathDiagnosticLocation::create(D, BR.getSourceManager()); - BR.EmitBasicReport("Bad return type when passing CFErrorRef*", + BR.EmitBasicReport(D, "Bad return type when passing CFErrorRef*", "Coding conventions (Apple)", err, L); } } @@ -182,7 +182,7 @@ namespace ento { } template <typename T> -static bool hasFlag(SVal val, const ProgramState *state) { +static bool hasFlag(SVal val, ProgramStateRef state) { if (SymbolRef sym = val.getAsSymbol()) if (const unsigned *attachedFlags = state->get<T>(sym)) return *attachedFlags; @@ -190,7 +190,7 @@ static bool hasFlag(SVal val, const ProgramState *state) { } template <typename T> -static void setFlag(const ProgramState *state, SVal val, CheckerContext &C) { +static void setFlag(ProgramStateRef state, SVal val, CheckerContext &C) { // We tag the symbol that the SVal wraps. if (SymbolRef sym = val.getAsSymbol()) C.addTransition(state->set<T>(sym, true)); @@ -198,7 +198,7 @@ static void setFlag(const ProgramState *state, SVal val, CheckerContext &C) { static QualType parameterTypeFromSVal(SVal val, CheckerContext &C) { const StackFrameContext * - SFC = C.getPredecessor()->getLocationContext()->getCurrentStackFrame(); + SFC = C.getLocationContext()->getCurrentStackFrame(); if (const loc::MemRegionVal* X = dyn_cast<loc::MemRegionVal>(&val)) { const MemRegion* R = X->getRegion(); if (const VarRegion *VR = R->getAs<VarRegion>()) @@ -220,7 +220,7 @@ void NSOrCFErrorDerefChecker::checkLocation(SVal loc, bool isLoad, return; ASTContext &Ctx = C.getASTContext(); - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); // If we are loading from NSError**/CFErrorRef* parameter, mark the resulting // SVal so that we can later check it when handling the @@ -253,7 +253,7 @@ void NSOrCFErrorDerefChecker::checkEvent(ImplicitNullDerefEvent event) const { return; SVal loc = event.Location; - const ProgramState *state = event.SinkNode->getState(); + ProgramStateRef state = event.SinkNode->getState(); BugReporter &BR = *event.BR; bool isNSError = hasFlag<NSErrorOut>(loc, state); diff --git a/lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp b/lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp index 81f1924..c2d7c09 100644 --- a/lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp @@ -36,13 +36,13 @@ public: void NoReturnFunctionChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const { - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); const Expr *Callee = CE->getCallee(); bool BuildSinks = getFunctionExtInfo(Callee->getType()).getNoReturn(); if (!BuildSinks) { - SVal L = state->getSVal(Callee); + SVal L = state->getSVal(Callee, C.getLocationContext()); const FunctionDecl *FD = L.getAsFunctionDecl(); if (!FD) return; diff --git a/lib/StaticAnalyzer/Checkers/OSAtomicChecker.cpp b/lib/StaticAnalyzer/Checkers/OSAtomicChecker.cpp index f426265..7b724d2 100644 --- a/lib/StaticAnalyzer/Checkers/OSAtomicChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/OSAtomicChecker.cpp @@ -32,31 +32,32 @@ private: ExprEngine &Eng, ExplodedNode *Pred, ExplodedNodeSet &Dst) const; - - ExplodedNode *generateNode(const ProgramState *State, - ExplodedNode *Pred, const CallExpr *Statement, - StmtNodeBuilder &B, ExplodedNodeSet &Dst) const; }; } +static StringRef getCalleeName(ProgramStateRef State, + const CallExpr *CE, + const LocationContext *LCtx) { + const Expr *Callee = CE->getCallee(); + SVal L = State->getSVal(Callee, LCtx); + const FunctionDecl *funDecl = L.getAsFunctionDecl(); + if (!funDecl) + return StringRef(); + IdentifierInfo *funI = funDecl->getIdentifier(); + if (!funI) + return StringRef(); + return funI->getName(); +} + bool OSAtomicChecker::inlineCall(const CallExpr *CE, ExprEngine &Eng, ExplodedNode *Pred, ExplodedNodeSet &Dst) const { - const ProgramState *state = Pred->getState(); - const Expr *Callee = CE->getCallee(); - SVal L = state->getSVal(Callee); - - const FunctionDecl *FD = L.getAsFunctionDecl(); - if (!FD) + StringRef FName = getCalleeName(Pred->getState(), + CE, Pred->getLocationContext()); + if (FName.empty()) return false; - const IdentifierInfo *II = FD->getIdentifier(); - if (!II) - return false; - - StringRef FName(II->getName()); - // Check for compare and swap. if (FName.startswith("OSAtomicCompareAndSwap") || FName.startswith("objc_atomicCompareAndSwap")) @@ -66,17 +67,6 @@ bool OSAtomicChecker::inlineCall(const CallExpr *CE, return false; } -ExplodedNode *OSAtomicChecker::generateNode(const ProgramState *State, - ExplodedNode *Pred, - const CallExpr *Statement, - StmtNodeBuilder &B, - ExplodedNodeSet &Dst) const { - ExplodedNode *N = B.generateNode(Statement, State, Pred, this); - if (N) - Dst.Add(N); - return N; -} - bool OSAtomicChecker::evalOSAtomicCompareAndSwap(const CallExpr *CE, ExprEngine &Eng, ExplodedNode *Pred, @@ -85,7 +75,6 @@ bool OSAtomicChecker::evalOSAtomicCompareAndSwap(const CallExpr *CE, if (CE->getNumArgs() != 3) return false; - StmtNodeBuilder &Builder = Eng.getBuilder(); ASTContext &Ctx = Eng.getContext(); const Expr *oldValueExpr = CE->getArg(0); QualType oldValueType = Ctx.getCanonicalType(oldValueExpr->getType()); @@ -115,9 +104,10 @@ bool OSAtomicChecker::evalOSAtomicCompareAndSwap(const CallExpr *CE, static SimpleProgramPointTag OSAtomicStoreTag("OSAtomicChecker : Store"); // Load 'theValue'. - const ProgramState *state = Pred->getState(); + ProgramStateRef state = Pred->getState(); + const LocationContext *LCtx = Pred->getLocationContext(); ExplodedNodeSet Tmp; - SVal location = state->getSVal(theValueExpr); + SVal location = state->getSVal(theValueExpr, LCtx); // Here we should use the value type of the region as the load type, because // we are simulating the semantics of the function, not the semantics of // passing argument. So the type of theValue expr is not we are loading. @@ -130,15 +120,12 @@ bool OSAtomicChecker::evalOSAtomicCompareAndSwap(const CallExpr *CE, dyn_cast_or_null<TypedValueRegion>(location.getAsRegion())) { LoadTy = TR->getValueType(); } - Eng.evalLoad(Tmp, theValueExpr, Pred, - state, location, &OSAtomicLoadTag, LoadTy); + Eng.evalLoad(Tmp, CE, theValueExpr, Pred, + state, location, &OSAtomicLoadTag, LoadTy); if (Tmp.empty()) { - // If no nodes were generated, other checkers must generated sinks. But - // since the builder state was restored, we set it manually to prevent - // auto transition. - // FIXME: there should be a better approach. - Builder.BuildSinks = true; + // If no nodes were generated, other checkers must have generated sinks. + // We return an empty Dst. return true; } @@ -146,14 +133,14 @@ bool OSAtomicChecker::evalOSAtomicCompareAndSwap(const CallExpr *CE, I != E; ++I) { ExplodedNode *N = *I; - const ProgramState *stateLoad = N->getState(); + ProgramStateRef stateLoad = N->getState(); // Use direct bindings from the environment since we are forcing a load // from a location that the Environment would typically not be used // to bind a value. - SVal theValueVal_untested = stateLoad->getSVal(theValueExpr, true); + SVal theValueVal_untested = stateLoad->getSVal(theValueExpr, LCtx, true); - SVal oldValueVal_untested = stateLoad->getSVal(oldValueExpr); + SVal oldValueVal_untested = stateLoad->getSVal(oldValueExpr, LCtx); // FIXME: Issue an error. if (theValueVal_untested.isUndef() || oldValueVal_untested.isUndef()) { @@ -171,13 +158,13 @@ bool OSAtomicChecker::evalOSAtomicCompareAndSwap(const CallExpr *CE, DefinedOrUnknownSVal Cmp = svalBuilder.evalEQ(stateLoad,theValueVal,oldValueVal); - const ProgramState *stateEqual = stateLoad->assume(Cmp, true); + ProgramStateRef stateEqual = stateLoad->assume(Cmp, true); // Were they equal? if (stateEqual) { // Perform the store. ExplodedNodeSet TmpStore; - SVal val = stateEqual->getSVal(newValueExpr); + SVal val = stateEqual->getSVal(newValueExpr, LCtx); // Handle implicit value casts. if (const TypedValueRegion *R = @@ -185,40 +172,41 @@ bool OSAtomicChecker::evalOSAtomicCompareAndSwap(const CallExpr *CE, val = svalBuilder.evalCast(val,R->getValueType(), newValueExpr->getType()); } - Eng.evalStore(TmpStore, NULL, theValueExpr, N, - stateEqual, location, val, &OSAtomicStoreTag); + Eng.evalStore(TmpStore, CE, theValueExpr, N, + stateEqual, location, val, &OSAtomicStoreTag); if (TmpStore.empty()) { - // If no nodes were generated, other checkers must generated sinks. But - // since the builder state was restored, we set it manually to prevent - // auto transition. - // FIXME: there should be a better approach. - Builder.BuildSinks = true; + // If no nodes were generated, other checkers must have generated sinks. + // We return an empty Dst. return true; } - + + StmtNodeBuilder B(TmpStore, Dst, Eng.getBuilderContext()); // Now bind the result of the comparison. for (ExplodedNodeSet::iterator I2 = TmpStore.begin(), E2 = TmpStore.end(); I2 != E2; ++I2) { ExplodedNode *predNew = *I2; - const ProgramState *stateNew = predNew->getState(); + ProgramStateRef stateNew = predNew->getState(); // Check for 'void' return type if we have a bogus function prototype. SVal Res = UnknownVal(); QualType T = CE->getType(); if (!T->isVoidType()) Res = Eng.getSValBuilder().makeTruthVal(true, T); - generateNode(stateNew->BindExpr(CE, Res), predNew, CE, Builder, Dst); + B.generateNode(CE, predNew, stateNew->BindExpr(CE, LCtx, Res), + false, this); } } // Were they not equal? - if (const ProgramState *stateNotEqual = stateLoad->assume(Cmp, false)) { + if (ProgramStateRef stateNotEqual = stateLoad->assume(Cmp, false)) { // Check for 'void' return type if we have a bogus function prototype. SVal Res = UnknownVal(); QualType T = CE->getType(); if (!T->isVoidType()) Res = Eng.getSValBuilder().makeTruthVal(false, CE->getType()); - generateNode(stateNotEqual->BindExpr(CE, Res), N, CE, Builder, Dst); + StmtNodeBuilder B(N, Dst, Eng.getBuilderContext()); + B.generateNode(CE, N, stateNotEqual->BindExpr(CE, LCtx, Res), + false, this); } } diff --git a/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp index 3e4e07b..777e9ea 100644 --- a/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// #include "ClangSACheckers.h" +#include "clang/AST/StmtObjC.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" @@ -26,8 +27,8 @@ using namespace ento; namespace { class ObjCAtSyncChecker : public Checker< check::PreStmt<ObjCAtSynchronizedStmt> > { - mutable llvm::OwningPtr<BuiltinBug> BT_null; - mutable llvm::OwningPtr<BuiltinBug> BT_undef; + mutable OwningPtr<BuiltinBug> BT_null; + mutable OwningPtr<BuiltinBug> BT_undef; public: void checkPreStmt(const ObjCAtSynchronizedStmt *S, CheckerContext &C) const; @@ -38,8 +39,8 @@ void ObjCAtSyncChecker::checkPreStmt(const ObjCAtSynchronizedStmt *S, CheckerContext &C) const { const Expr *Ex = S->getSynchExpr(); - const ProgramState *state = C.getState(); - SVal V = state->getSVal(Ex); + ProgramStateRef state = C.getState(); + SVal V = state->getSVal(Ex, C.getLocationContext()); // Uninitialized value used for the mutex? if (isa<UndefinedVal>(V)) { @@ -49,7 +50,8 @@ void ObjCAtSyncChecker::checkPreStmt(const ObjCAtSynchronizedStmt *S, "for @synchronized")); BugReport *report = new BugReport(*BT_undef, BT_undef->getDescription(), N); - report->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, Ex)); + report->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, Ex, + report)); C.EmitReport(report); } return; @@ -59,20 +61,21 @@ void ObjCAtSyncChecker::checkPreStmt(const ObjCAtSynchronizedStmt *S, return; // Check for null mutexes. - const ProgramState *notNullState, *nullState; + ProgramStateRef notNullState, nullState; llvm::tie(notNullState, nullState) = state->assume(cast<DefinedSVal>(V)); if (nullState) { if (!notNullState) { // Generate an error node. This isn't a sink since // a null mutex just means no synchronization occurs. - if (ExplodedNode *N = C.generateNode(nullState)) { + if (ExplodedNode *N = C.addTransition(nullState)) { if (!BT_null) BT_null.reset(new BuiltinBug("Nil value used as mutex for @synchronized() " "(no synchronization will occur)")); BugReport *report = new BugReport(*BT_null, BT_null->getDescription(), N); - report->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, Ex)); + report->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, Ex, + report)); C.EmitReport(report); return; @@ -88,6 +91,6 @@ void ObjCAtSyncChecker::checkPreStmt(const ObjCAtSynchronizedStmt *S, } void ento::registerObjCAtSyncChecker(CheckerManager &mgr) { - if (mgr.getLangOptions().ObjC2) + if (mgr.getLangOpts().ObjC2) mgr.registerChecker<ObjCAtSyncChecker>(); } diff --git a/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp new file mode 100644 index 0000000..f2929c0 --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp @@ -0,0 +1,174 @@ +//== ObjCContainersASTChecker.cpp - CoreFoundation containers API *- C++ -*-==// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// An AST checker that looks for common pitfalls when using 'CFArray', +// 'CFDictionary', 'CFSet' APIs. +// +//===----------------------------------------------------------------------===// +#include "ClangSACheckers.h" +#include "clang/Analysis/AnalysisContext.h" +#include "clang/AST/StmtVisitor.h" +#include "clang/Basic/TargetInfo.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/raw_ostream.h" + +using namespace clang; +using namespace ento; + +namespace { +class WalkAST : public StmtVisitor<WalkAST> { + BugReporter &BR; + AnalysisDeclContext* AC; + ASTContext &ASTC; + uint64_t PtrWidth; + + static const unsigned InvalidArgIndex = UINT_MAX; + + /// Check if the type has pointer size (very conservative). + inline bool isPointerSize(const Type *T) { + if (!T) + return true; + if (T->isIncompleteType()) + return true; + return (ASTC.getTypeSize(T) == PtrWidth); + } + + /// Check if the type is a pointer/array to pointer sized values. + inline bool hasPointerToPointerSizedType(const Expr *E) { + QualType T = E->getType(); + + // The type could be either a pointer or array. + const Type *TP = T.getTypePtr(); + QualType PointeeT = TP->getPointeeType(); + if (!PointeeT.isNull()) { + // If the type is a pointer to an array, check the size of the array + // elements. To avoid false positives coming from assumption that the + // values x and &x are equal when x is an array. + if (const Type *TElem = PointeeT->getArrayElementTypeNoTypeQual()) + if (isPointerSize(TElem)) + return true; + + // Else, check the pointee size. + return isPointerSize(PointeeT.getTypePtr()); + } + + if (const Type *TElem = TP->getArrayElementTypeNoTypeQual()) + return isPointerSize(TElem); + + // The type must be an array/pointer type. + + // This could be a null constant, which is allowed. + if (E->isNullPointerConstant(ASTC, Expr::NPC_ValueDependentIsNull)) + return true; + return false; + } + +public: + WalkAST(BugReporter &br, AnalysisDeclContext* ac) + : BR(br), AC(ac), ASTC(AC->getASTContext()), + PtrWidth(ASTC.getTargetInfo().getPointerWidth(0)) {} + + // Statement visitor methods. + void VisitChildren(Stmt *S); + void VisitStmt(Stmt *S) { VisitChildren(S); } + void VisitCallExpr(CallExpr *CE); +}; +} // end anonymous namespace + +static StringRef getCalleeName(CallExpr *CE) { + const FunctionDecl *FD = CE->getDirectCallee(); + if (!FD) + return StringRef(); + + IdentifierInfo *II = FD->getIdentifier(); + if (!II) // if no identifier, not a simple C function + return StringRef(); + + return II->getName(); +} + +void WalkAST::VisitCallExpr(CallExpr *CE) { + StringRef Name = getCalleeName(CE); + if (Name.empty()) + return; + + const Expr *Arg = 0; + unsigned ArgNum = InvalidArgIndex; + + if (Name.equals("CFArrayCreate") || Name.equals("CFSetCreate")) { + ArgNum = 1; + Arg = CE->getArg(ArgNum)->IgnoreParenCasts(); + if (hasPointerToPointerSizedType(Arg)) + return; + } + + if (Arg == 0 && Name.equals("CFDictionaryCreate")) { + // Check first argument. + ArgNum = 1; + Arg = CE->getArg(ArgNum)->IgnoreParenCasts(); + if (hasPointerToPointerSizedType(Arg)) { + // Check second argument. + ArgNum = 2; + Arg = CE->getArg(ArgNum)->IgnoreParenCasts(); + if (hasPointerToPointerSizedType(Arg)) + // Both are good, return. + return; + } + } + + if (ArgNum != InvalidArgIndex) { + assert(ArgNum == 1 || ArgNum == 2); + + SmallString<256> BufName; + llvm::raw_svector_ostream OsName(BufName); + assert(ArgNum == 1 || ArgNum == 2); + OsName << " Invalid use of '" << Name << "'" ; + + SmallString<256> Buf; + llvm::raw_svector_ostream Os(Buf); + Os << " The "<< ((ArgNum == 1) ? "first" : "second") << " argument to '" + << Name << "' must be a C array of pointer-sized values, not '" + << Arg->getType().getAsString() << "'"; + + SourceRange R = Arg->getSourceRange(); + PathDiagnosticLocation CELoc = + PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC); + BR.EmitBasicReport(AC->getDecl(), + OsName.str(), categories::CoreFoundationObjectiveC, + Os.str(), CELoc, &R, 1); + } + + // Recurse and check children. + VisitChildren(CE); +} + +void WalkAST::VisitChildren(Stmt *S) { + for (Stmt::child_iterator I = S->child_begin(), E = S->child_end(); I!=E; ++I) + if (Stmt *child = *I) + Visit(child); +} + +namespace { +class ObjCContainersASTChecker : public Checker<check::ASTCodeBody> { +public: + + void checkASTCodeBody(const Decl *D, AnalysisManager& Mgr, + BugReporter &BR) const { + WalkAST walker(BR, Mgr.getAnalysisDeclContext(D)); + walker.Visit(D->getBody()); + } +}; +} + +void ento::registerObjCContainersASTChecker(CheckerManager &mgr) { + mgr.registerChecker<ObjCContainersASTChecker>(); +} diff --git a/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp new file mode 100644 index 0000000..f4655b6 --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp @@ -0,0 +1,159 @@ +//== ObjCContainersChecker.cpp - Path sensitive checker for CFArray *- C++ -*=// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Performs path sensitive checks of Core Foundation static containers like +// CFArray. +// 1) Check for buffer overflows: +// In CFArrayGetArrayAtIndex( myArray, index), if the index is outside the +// index space of theArray (0 to N-1 inclusive (where N is the count of +// theArray), the behavior is undefined. +// +//===----------------------------------------------------------------------===// + +#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ObjCMessage.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/AST/ParentMap.h" + +using namespace clang; +using namespace ento; + +namespace { +class ObjCContainersChecker : public Checker< check::PreStmt<CallExpr>, + check::PostStmt<CallExpr> > { + mutable OwningPtr<BugType> BT; + inline void initBugType() const { + if (!BT) + BT.reset(new BugType("CFArray API", + categories::CoreFoundationObjectiveC)); + } + + inline SymbolRef getArraySym(const Expr *E, CheckerContext &C) const { + SVal ArrayRef = C.getState()->getSVal(E, C.getLocationContext()); + SymbolRef ArraySym = ArrayRef.getAsSymbol(); + return ArraySym; + } + + void addSizeInfo(const Expr *Array, const Expr *Size, + CheckerContext &C) const; + +public: + /// A tag to id this checker. + static void *getTag() { static int Tag; return &Tag; } + + void checkPostStmt(const CallExpr *CE, CheckerContext &C) const; + void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; +}; +} // end anonymous namespace + +// ProgramState trait - a map from array symbol to it's state. +typedef llvm::ImmutableMap<SymbolRef, DefinedSVal> ArraySizeM; + +namespace { struct ArraySizeMap {}; } +namespace clang { namespace ento { +template<> struct ProgramStateTrait<ArraySizeMap> + : public ProgramStatePartialTrait<ArraySizeM > { + static void *GDMIndex() { return ObjCContainersChecker::getTag(); } +}; +}} + +void ObjCContainersChecker::addSizeInfo(const Expr *Array, const Expr *Size, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SVal SizeV = State->getSVal(Size, C.getLocationContext()); + // Undefined is reported by another checker. + if (SizeV.isUnknownOrUndef()) + return; + + // Get the ArrayRef symbol. + SVal ArrayRef = State->getSVal(Array, C.getLocationContext()); + SymbolRef ArraySym = ArrayRef.getAsSymbol(); + if (!ArraySym) + return; + + C.addTransition(State->set<ArraySizeMap>(ArraySym, cast<DefinedSVal>(SizeV))); + return; +} + +void ObjCContainersChecker::checkPostStmt(const CallExpr *CE, + CheckerContext &C) const { + StringRef Name = C.getCalleeName(CE); + if (Name.empty() || CE->getNumArgs() < 1) + return; + + // Add array size information to the state. + if (Name.equals("CFArrayCreate")) { + if (CE->getNumArgs() < 3) + return; + // Note, we can visit the Create method in the post-visit because + // the CFIndex parameter is passed in by value and will not be invalidated + // by the call. + addSizeInfo(CE, CE->getArg(2), C); + return; + } + + if (Name.equals("CFArrayGetCount")) { + addSizeInfo(CE->getArg(0), CE, C); + return; + } +} + +void ObjCContainersChecker::checkPreStmt(const CallExpr *CE, + CheckerContext &C) const { + StringRef Name = C.getCalleeName(CE); + if (Name.empty() || CE->getNumArgs() < 2) + return; + + // Check the array access. + if (Name.equals("CFArrayGetValueAtIndex")) { + ProgramStateRef State = C.getState(); + // Retrieve the size. + // Find out if we saw this array symbol before and have information about it. + const Expr *ArrayExpr = CE->getArg(0); + SymbolRef ArraySym = getArraySym(ArrayExpr, C); + if (!ArraySym) + return; + + const DefinedSVal *Size = State->get<ArraySizeMap>(ArraySym); + + if (!Size) + return; + + // Get the index. + const Expr *IdxExpr = CE->getArg(1); + SVal IdxVal = State->getSVal(IdxExpr, C.getLocationContext()); + if (IdxVal.isUnknownOrUndef()) + return; + DefinedSVal Idx = cast<DefinedSVal>(IdxVal); + + // Now, check if 'Idx in [0, Size-1]'. + const QualType T = IdxExpr->getType(); + ProgramStateRef StInBound = State->assumeInBound(Idx, *Size, true, T); + ProgramStateRef StOutBound = State->assumeInBound(Idx, *Size, false, T); + if (StOutBound && !StInBound) { + ExplodedNode *N = C.generateSink(StOutBound); + if (!N) + return; + initBugType(); + BugReport *R = new BugReport(*BT, "Index is out of bounds", N); + R->addRange(IdxExpr->getSourceRange()); + C.EmitReport(R); + return; + } + } +} + +/// Register checker. +void ento::registerObjCContainersChecker(CheckerManager &mgr) { + mgr.registerChecker<ObjCContainersChecker>(); +} diff --git a/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp index 2fb9944..d15c8ba 100644 --- a/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp @@ -34,18 +34,8 @@ // receives a reference to 'self', the checker keeps track and passes the flags // for 1) and 2) to the new object that 'self' points to after the call. // -// FIXME (rdar://7937506): In the case of: -// [super init]; -// return self; -// Have an extra PathDiagnosticPiece in the path that says "called [super init], -// but didn't assign the result to self." - //===----------------------------------------------------------------------===// -// FIXME: Somehow stick the link to Apple's documentation about initializing -// objects in the diagnostics. -// http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjectiveC/Articles/ocAllocInit.html - #include "ClangSACheckers.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" @@ -64,7 +54,7 @@ static bool isInitMessage(const ObjCMessage &msg); static bool isSelfVar(SVal location, CheckerContext &C); namespace { -class ObjCSelfInitChecker : public Checker< +class ObjCSelfInitChecker : public Checker< check::PreObjCMessage, check::PostObjCMessage, check::PostStmt<ObjCIvarRefExpr>, check::PreStmt<ReturnStmt>, @@ -72,6 +62,7 @@ class ObjCSelfInitChecker : public Checker< check::PostStmt<CallExpr>, check::Location > { public: + void checkPreObjCMessage(ObjCMessage msg, CheckerContext &C) const; void checkPostObjCMessage(ObjCMessage msg, CheckerContext &C) const; void checkPostStmt(const ObjCIvarRefExpr *E, CheckerContext &C) const; void checkPreStmt(const ReturnStmt *S, CheckerContext &C) const; @@ -79,6 +70,10 @@ public: void checkPostStmt(const CallExpr *CE, CheckerContext &C) const; void checkLocation(SVal location, bool isLoad, const Stmt *S, CheckerContext &C) const; + + void checkPreStmt(const CallOrObjCMessage &CE, CheckerContext &C) const; + void checkPostStmt(const CallOrObjCMessage &CE, CheckerContext &C) const; + }; } // end anonymous namespace @@ -87,8 +82,8 @@ namespace { class InitSelfBug : public BugType { const std::string desc; public: - InitSelfBug() : BugType("missing \"self = [(super or self) init...]\"", - "missing \"self = [(super or self) init...]\"") {} + InitSelfBug() : BugType("Missing \"self = [(super or self) init...]\"", + categories::CoreFoundationObjectiveC) {} }; } // end anonymous namespace @@ -130,7 +125,7 @@ namespace ento { } } -static SelfFlagEnum getSelfFlags(SVal val, const ProgramState *state) { +static SelfFlagEnum getSelfFlags(SVal val, ProgramStateRef state) { if (SymbolRef sym = val.getAsSymbol()) if (const unsigned *attachedFlags = state->get<SelfFlag>(sym)) return (SelfFlagEnum)*attachedFlags; @@ -141,7 +136,7 @@ static SelfFlagEnum getSelfFlags(SVal val, CheckerContext &C) { return getSelfFlags(val, C.getState()); } -static void addSelfFlag(const ProgramState *state, SVal val, +static void addSelfFlag(ProgramStateRef state, SVal val, SelfFlagEnum flag, CheckerContext &C) { // We tag the symbol that the SVal wraps. if (SymbolRef sym = val.getAsSymbol()) @@ -156,7 +151,7 @@ static bool hasSelfFlag(SVal val, SelfFlagEnum flag, CheckerContext &C) { /// points to and is an object that did not come from the result of calling /// an initializer. static bool isInvalidSelf(const Expr *E, CheckerContext &C) { - SVal exprVal = C.getState()->getSVal(E); + SVal exprVal = C.getState()->getSVal(E, C.getLocationContext()); if (!hasSelfFlag(exprVal, SelfFlag_Self, C)) return false; // value did not come from 'self'. if (hasSelfFlag(exprVal, SelfFlag_InitRes, C)) @@ -188,25 +183,28 @@ static void checkForInvalidSelf(const Expr *E, CheckerContext &C, void ObjCSelfInitChecker::checkPostObjCMessage(ObjCMessage msg, CheckerContext &C) const { + CallOrObjCMessage MsgWrapper(msg, C.getState(), C.getLocationContext()); + checkPostStmt(MsgWrapper, C); + // When encountering a message that does initialization (init rule), // tag the return value so that we know later on that if self has this value // then it is properly initialized. // FIXME: A callback should disable checkers at the start of functions. if (!shouldRunOnFunctionOrMethod(dyn_cast<NamedDecl>( - C.getCurrentAnalysisContext()->getDecl()))) + C.getCurrentAnalysisDeclContext()->getDecl()))) return; if (isInitMessage(msg)) { // Tag the return value as the result of an initializer. - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); // FIXME this really should be context sensitive, where we record // the current stack frame (for IPA). Also, we need to clean this // value out when we return from this method. state = state->set<CalledInit>(true); - SVal V = state->getSVal(msg.getOriginExpr()); + SVal V = state->getSVal(msg.getMessageExpr(), C.getLocationContext()); addSelfFlag(state, V, SelfFlag_InitRes, C); return; } @@ -221,7 +219,7 @@ void ObjCSelfInitChecker::checkPostStmt(const ObjCIvarRefExpr *E, CheckerContext &C) const { // FIXME: A callback should disable checkers at the start of functions. if (!shouldRunOnFunctionOrMethod(dyn_cast<NamedDecl>( - C.getCurrentAnalysisContext()->getDecl()))) + C.getCurrentAnalysisDeclContext()->getDecl()))) return; checkForInvalidSelf(E->getBase(), C, @@ -233,7 +231,7 @@ void ObjCSelfInitChecker::checkPreStmt(const ReturnStmt *S, CheckerContext &C) const { // FIXME: A callback should disable checkers at the start of functions. if (!shouldRunOnFunctionOrMethod(dyn_cast<NamedDecl>( - C.getCurrentAnalysisContext()->getDecl()))) + C.getCurrentAnalysisDeclContext()->getDecl()))) return; checkForInvalidSelf(S->getRetValue(), C, @@ -259,10 +257,28 @@ void ObjCSelfInitChecker::checkPreStmt(const ReturnStmt *S, void ObjCSelfInitChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const { - const ProgramState *state = C.getState(); - for (CallExpr::const_arg_iterator - I = CE->arg_begin(), E = CE->arg_end(); I != E; ++I) { - SVal argV = state->getSVal(*I); + CallOrObjCMessage CEWrapper(CE, C.getState(), C.getLocationContext()); + checkPreStmt(CEWrapper, C); +} + +void ObjCSelfInitChecker::checkPostStmt(const CallExpr *CE, + CheckerContext &C) const { + CallOrObjCMessage CEWrapper(CE, C.getState(), C.getLocationContext()); + checkPostStmt(CEWrapper, C); +} + +void ObjCSelfInitChecker::checkPreObjCMessage(ObjCMessage Msg, + CheckerContext &C) const { + CallOrObjCMessage MsgWrapper(Msg, C.getState(), C.getLocationContext()); + checkPreStmt(MsgWrapper, C); +} + +void ObjCSelfInitChecker::checkPreStmt(const CallOrObjCMessage &CE, + CheckerContext &C) const { + ProgramStateRef state = C.getState(); + unsigned NumArgs = CE.getNumArgs(); + for (unsigned i = 0; i < NumArgs; ++i) { + SVal argV = CE.getArgSVal(i); if (isSelfVar(argV, C)) { unsigned selfFlags = getSelfFlags(state->getSVal(cast<Loc>(argV)), C); C.addTransition(state->set<PreCallSelfFlags>(selfFlags)); @@ -275,12 +291,12 @@ void ObjCSelfInitChecker::checkPreStmt(const CallExpr *CE, } } -void ObjCSelfInitChecker::checkPostStmt(const CallExpr *CE, +void ObjCSelfInitChecker::checkPostStmt(const CallOrObjCMessage &CE, CheckerContext &C) const { - const ProgramState *state = C.getState(); - for (CallExpr::const_arg_iterator - I = CE->arg_begin(), E = CE->arg_end(); I != E; ++I) { - SVal argV = state->getSVal(*I); + ProgramStateRef state = C.getState(); + unsigned NumArgs = CE.getNumArgs(); + for (unsigned i = 0; i < NumArgs; ++i) { + SVal argV = CE.getArgSVal(i); if (isSelfVar(argV, C)) { SelfFlagEnum prevFlags = (SelfFlagEnum)state->get<PreCallSelfFlags>(); state = state->remove<PreCallSelfFlags>(); @@ -289,7 +305,7 @@ void ObjCSelfInitChecker::checkPostStmt(const CallExpr *CE, } else if (hasSelfFlag(argV, SelfFlag_Self, C)) { SelfFlagEnum prevFlags = (SelfFlagEnum)state->get<PreCallSelfFlags>(); state = state->remove<PreCallSelfFlags>(); - addSelfFlag(state, state->getSVal(CE), prevFlags, C); + addSelfFlag(state, state->getSVal(cast<Loc>(argV)), prevFlags, C); return; } } @@ -300,7 +316,7 @@ void ObjCSelfInitChecker::checkLocation(SVal location, bool isLoad, CheckerContext &C) const { // Tag the result of a load from 'self' so that we can easily know that the // value is the object that 'self' points to. - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); if (isSelfVar(location, C)) addSelfFlag(state, state->getSVal(cast<Loc>(location)), SelfFlag_Self, C); } @@ -335,7 +351,7 @@ static bool shouldRunOnFunctionOrMethod(const NamedDecl *ND) { /// \brief Returns true if the location is 'self'. static bool isSelfVar(SVal location, CheckerContext &C) { - AnalysisContext *analCtx = C.getCurrentAnalysisContext(); + AnalysisDeclContext *analCtx = C.getCurrentAnalysisDeclContext(); if (!analCtx->getSelfDecl()) return false; if (!isa<loc::MemRegionVal>(location)) diff --git a/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp index bbc262f..4718dc7 100644 --- a/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp @@ -161,7 +161,7 @@ static void checkObjCUnusedIvar(const ObjCImplementationDecl *D, PathDiagnosticLocation L = PathDiagnosticLocation::create(I->first, BR.getSourceManager()); - BR.EmitBasicReport("Unused instance variable", "Optimization", + BR.EmitBasicReport(D, "Unused instance variable", "Optimization", os.str(), L); } } diff --git a/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp b/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp index 202522b..fe4845b 100644 --- a/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp @@ -24,7 +24,7 @@ using namespace ento; namespace { class PointerArithChecker : public Checker< check::PreStmt<BinaryOperator> > { - mutable llvm::OwningPtr<BuiltinBug> BT; + mutable OwningPtr<BuiltinBug> BT; public: void checkPreStmt(const BinaryOperator *B, CheckerContext &C) const; @@ -36,9 +36,10 @@ void PointerArithChecker::checkPreStmt(const BinaryOperator *B, if (B->getOpcode() != BO_Sub && B->getOpcode() != BO_Add) return; - const ProgramState *state = C.getState(); - SVal LV = state->getSVal(B->getLHS()); - SVal RV = state->getSVal(B->getRHS()); + ProgramStateRef state = C.getState(); + const LocationContext *LCtx = C.getLocationContext(); + SVal LV = state->getSVal(B->getLHS(), LCtx); + SVal RV = state->getSVal(B->getRHS(), LCtx); const MemRegion *LR = LV.getAsRegion(); @@ -50,7 +51,7 @@ void PointerArithChecker::checkPreStmt(const BinaryOperator *B, if (isa<VarRegion>(LR) || isa<CodeTextRegion>(LR) || isa<CompoundLiteralRegion>(LR)) { - if (ExplodedNode *N = C.generateNode()) { + if (ExplodedNode *N = C.addTransition()) { if (!BT) BT.reset(new BuiltinBug("Dangerous pointer arithmetic", "Pointer arithmetic done on non-array variables " diff --git a/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp b/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp index 924c7f2..fa5c6a3 100644 --- a/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp @@ -25,7 +25,7 @@ using namespace ento; namespace { class PointerSubChecker : public Checker< check::PreStmt<BinaryOperator> > { - mutable llvm::OwningPtr<BuiltinBug> BT; + mutable OwningPtr<BuiltinBug> BT; public: void checkPreStmt(const BinaryOperator *B, CheckerContext &C) const; @@ -39,9 +39,10 @@ void PointerSubChecker::checkPreStmt(const BinaryOperator *B, if (B->getOpcode() != BO_Sub) return; - const ProgramState *state = C.getState(); - SVal LV = state->getSVal(B->getLHS()); - SVal RV = state->getSVal(B->getRHS()); + ProgramStateRef state = C.getState(); + const LocationContext *LCtx = C.getLocationContext(); + SVal LV = state->getSVal(B->getLHS(), LCtx); + SVal RV = state->getSVal(B->getRHS(), LCtx); const MemRegion *LR = LV.getAsRegion(); const MemRegion *RR = RV.getAsRegion(); @@ -59,7 +60,7 @@ void PointerSubChecker::checkPreStmt(const BinaryOperator *B, if (isa<SymbolicRegion>(BaseLR) || isa<SymbolicRegion>(BaseRR)) return; - if (ExplodedNode *N = C.generateNode()) { + if (ExplodedNode *N = C.addTransition()) { if (!BT) BT.reset(new BuiltinBug("Pointer subtraction", "Subtraction of two pointers that do not point to " diff --git a/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp b/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp index c02b5b1..2d018ef 100644 --- a/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp @@ -25,8 +25,8 @@ using namespace ento; namespace { class PthreadLockChecker : public Checker< check::PostStmt<CallExpr> > { - mutable llvm::OwningPtr<BugType> BT_doublelock; - mutable llvm::OwningPtr<BugType> BT_lor; + mutable OwningPtr<BugType> BT_doublelock; + mutable OwningPtr<BugType> BT_lor; enum LockingSemantics { NotApplicable = 0, PthreadSemantics, @@ -56,18 +56,11 @@ template <> struct ProgramStateTrait<LockSet> : void PthreadLockChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const { - const ProgramState *state = C.getState(); - const Expr *Callee = CE->getCallee(); - const FunctionDecl *FD = state->getSVal(Callee).getAsFunctionDecl(); - - if (!FD) - return; - - // Get the name of the callee. - IdentifierInfo *II = FD->getIdentifier(); - if (!II) // if no identifier, not a simple C function + ProgramStateRef state = C.getState(); + const LocationContext *LCtx = C.getLocationContext(); + StringRef FName = C.getCalleeName(CE); + if (FName.empty()) return; - StringRef FName = II->getName(); if (CE->getNumArgs() != 1) return; @@ -75,24 +68,28 @@ void PthreadLockChecker::checkPostStmt(const CallExpr *CE, if (FName == "pthread_mutex_lock" || FName == "pthread_rwlock_rdlock" || FName == "pthread_rwlock_wrlock") - AcquireLock(C, CE, state->getSVal(CE->getArg(0)), false, PthreadSemantics); + AcquireLock(C, CE, state->getSVal(CE->getArg(0), LCtx), + false, PthreadSemantics); else if (FName == "lck_mtx_lock" || FName == "lck_rw_lock_exclusive" || FName == "lck_rw_lock_shared") - AcquireLock(C, CE, state->getSVal(CE->getArg(0)), false, XNUSemantics); + AcquireLock(C, CE, state->getSVal(CE->getArg(0), LCtx), + false, XNUSemantics); else if (FName == "pthread_mutex_trylock" || FName == "pthread_rwlock_tryrdlock" || FName == "pthread_rwlock_tryrwlock") - AcquireLock(C, CE, state->getSVal(CE->getArg(0)), true, PthreadSemantics); + AcquireLock(C, CE, state->getSVal(CE->getArg(0), LCtx), + true, PthreadSemantics); else if (FName == "lck_mtx_try_lock" || FName == "lck_rw_try_lock_exclusive" || FName == "lck_rw_try_lock_shared") - AcquireLock(C, CE, state->getSVal(CE->getArg(0)), true, XNUSemantics); + AcquireLock(C, CE, state->getSVal(CE->getArg(0), LCtx), + true, XNUSemantics); else if (FName == "pthread_mutex_unlock" || FName == "pthread_rwlock_unlock" || FName == "lck_mtx_unlock" || FName == "lck_rw_done") - ReleaseLock(C, CE, state->getSVal(CE->getArg(0))); + ReleaseLock(C, CE, state->getSVal(CE->getArg(0), LCtx)); } void PthreadLockChecker::AcquireLock(CheckerContext &C, const CallExpr *CE, @@ -103,9 +100,9 @@ void PthreadLockChecker::AcquireLock(CheckerContext &C, const CallExpr *CE, if (!lockR) return; - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); - SVal X = state->getSVal(CE); + SVal X = state->getSVal(CE, C.getLocationContext()); if (X.isUnknownOrUndef()) return; @@ -125,10 +122,10 @@ void PthreadLockChecker::AcquireLock(CheckerContext &C, const CallExpr *CE, return; } - const ProgramState *lockSucc = state; + ProgramStateRef lockSucc = state; if (isTryLock) { // Bifurcate the state, and allow a mode where the lock acquisition fails. - const ProgramState *lockFail; + ProgramStateRef lockFail; switch (semantics) { case PthreadSemantics: llvm::tie(lockFail, lockSucc) = state->assume(retVal); @@ -138,7 +135,6 @@ void PthreadLockChecker::AcquireLock(CheckerContext &C, const CallExpr *CE, break; default: llvm_unreachable("Unknown tryLock locking semantics"); - break; } assert(lockFail && lockSucc); C.addTransition(lockFail); @@ -166,7 +162,7 @@ void PthreadLockChecker::ReleaseLock(CheckerContext &C, const CallExpr *CE, if (!lockR) return; - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); llvm::ImmutableList<const MemRegion*> LS = state->get<LockSet>(); // FIXME: Better analysis requires IPA for wrappers. diff --git a/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp b/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp index 93e0fe5..b569e41 100644 --- a/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp @@ -18,12 +18,12 @@ #include "clang/Basic/LangOptions.h" #include "clang/Basic/SourceManager.h" #include "clang/Analysis/DomainSpecific/CocoaConventions.h" +#include "clang/AST/ParentMap.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngineBuilders.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ObjCMessage.h" @@ -31,6 +31,7 @@ #include "llvm/ADT/FoldingSet.h" #include "llvm/ADT/ImmutableList.h" #include "llvm/ADT/ImmutableMap.h" +#include "llvm/ADT/SmallString.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringExtras.h" #include <cstdarg> @@ -45,21 +46,14 @@ namespace { class GenericNodeBuilderRefCount { CheckerContext *C; const ProgramPointTag *tag; - EndOfFunctionNodeBuilder *ENB; public: GenericNodeBuilderRefCount(CheckerContext &c, - const ProgramPointTag *t) - : C(&c), tag(t), ENB(0) {} + const ProgramPointTag *t = 0) + : C(&c), tag(t){} - GenericNodeBuilderRefCount(EndOfFunctionNodeBuilder &enb) - : C(0), tag(0), ENB(&enb) {} - - ExplodedNode *MakeNode(const ProgramState *state, ExplodedNode *Pred) { - if (C) - return C->generateNode(state, Pred, tag, false); - - assert(ENB); - return ENB->generateNode(state, Pred); + ExplodedNode *MakeNode(ProgramStateRef state, ExplodedNode *Pred, + bool MarkAsSink = false) { + return C->addTransition(state, Pred, tag, MarkAsSink); } }; } // end anonymous namespace @@ -138,6 +132,11 @@ public: static RetEffect MakeNoRet() { return RetEffect(NoRet); } + + void Profile(llvm::FoldingSetNodeID& ID) const { + ID.AddInteger((unsigned) K); + ID.AddInteger((unsigned) O); + } }; //===----------------------------------------------------------------------===// @@ -358,22 +357,22 @@ struct ProgramStateTrait<RefBindings> namespace { class RetainSummary { - /// Args - an ordered vector of (index, ArgEffect) pairs, where index + /// Args - a map of (index, ArgEffect) pairs, where index /// specifies the argument (starting from 0). This can be sparsely /// populated; arguments with no entry in Args use 'DefaultArgEffect'. ArgEffects Args; /// DefaultArgEffect - The default ArgEffect to apply to arguments that /// do not have an entry in Args. - ArgEffect DefaultArgEffect; + ArgEffect DefaultArgEffect; /// Receiver - If this summary applies to an Objective-C message expression, /// this is the effect applied to the state of the receiver. - ArgEffect Receiver; + ArgEffect Receiver; /// Ret - The effect on the return value. Used to indicate if the /// function/method call returns a new tracked symbol. - RetEffect Ret; + RetEffect Ret; public: RetainSummary(ArgEffects A, RetEffect R, ArgEffect defaultEff, @@ -419,6 +418,19 @@ public: return Args == Other.Args && DefaultArgEffect == Other.DefaultArgEffect && Receiver == Other.Receiver && Ret == Other.Ret; } + + /// Profile this summary for inclusion in a FoldingSet. + void Profile(llvm::FoldingSetNodeID& ID) const { + ID.Add(Args); + ID.Add(DefaultArgEffect); + ID.Add(Receiver); + ID.Add(Ret); + } + + /// A retain summary is simple if it has no ArgEffects other than the default. + bool isSimple() const { + return Args.isEmpty(); + } }; } // end anonymous namespace @@ -443,7 +455,7 @@ public: ObjCSummaryKey(Selector s) : II(0), S(s) {} - IdentifierInfo* getIdentifier() const { return II; } + IdentifierInfo *getIdentifier() const { return II; } Selector getSelector() const { return S; } }; } @@ -523,7 +535,7 @@ public: return Summ; } - const RetainSummary * find(IdentifierInfo* II, Selector S) { + const RetainSummary *find(IdentifierInfo* II, Selector S) { // FIXME: Class method lookup. Right now we dont' have a good way // of going between IdentifierInfo* and the class hierarchy. MapTy::iterator I = M.find(ObjCSummaryKey(II, S)); @@ -560,6 +572,8 @@ class RetainSummaryManager { typedef ObjCSummaryCache ObjCMethodSummariesTy; + typedef llvm::FoldingSetNodeWrapper<RetainSummary> CachedSummaryNode; + //==-----------------------------------------------------------------==// // Data. //==-----------------------------------------------------------------==// @@ -591,7 +605,7 @@ class RetainSummaryManager { ArgEffects::Factory AF; /// ScratchArgs - A holding buffer for construct ArgEffects. - ArgEffects ScratchArgs; + ArgEffects ScratchArgs; /// ObjCAllocRetE - Default return effect for methods returning Objective-C /// objects. @@ -601,8 +615,9 @@ class RetainSummaryManager { /// Objective-C objects. RetEffect ObjCInitRetE; - RetainSummary DefaultSummary; - const RetainSummary *StopSummary; + /// SimpleSummaries - Used for uniquing summaries that don't have special + /// effects. + llvm::FoldingSet<CachedSummaryNode> SimpleSummaries; //==-----------------------------------------------------------------==// // Methods. @@ -616,39 +631,32 @@ class RetainSummaryManager { public: RetEffect getObjAllocRetEffect() const { return ObjCAllocRetE; } - - const RetainSummary *getDefaultSummary() { - return &DefaultSummary; - } - const RetainSummary * getUnarySummary(const FunctionType* FT, + const RetainSummary *getUnarySummary(const FunctionType* FT, UnaryFuncKind func); - const RetainSummary * getCFSummaryCreateRule(const FunctionDecl *FD); - const RetainSummary * getCFSummaryGetRule(const FunctionDecl *FD); - const RetainSummary * getCFCreateGetRuleSummary(const FunctionDecl *FD); + const RetainSummary *getCFSummaryCreateRule(const FunctionDecl *FD); + const RetainSummary *getCFSummaryGetRule(const FunctionDecl *FD); + const RetainSummary *getCFCreateGetRuleSummary(const FunctionDecl *FD); - const RetainSummary * getPersistentSummary(ArgEffects AE, RetEffect RetEff, - ArgEffect ReceiverEff = DoNothing, - ArgEffect DefaultEff = MayEscape); + const RetainSummary *getPersistentSummary(const RetainSummary &OldSumm); - const RetainSummary * getPersistentSummary(RetEffect RE, + const RetainSummary *getPersistentSummary(RetEffect RetEff, ArgEffect ReceiverEff = DoNothing, ArgEffect DefaultEff = MayEscape) { - return getPersistentSummary(getArgEffects(), RE, ReceiverEff, DefaultEff); + RetainSummary Summ(getArgEffects(), RetEff, DefaultEff, ReceiverEff); + return getPersistentSummary(Summ); } - const RetainSummary *getPersistentStopSummary() { - if (StopSummary) - return StopSummary; - - StopSummary = getPersistentSummary(RetEffect::MakeNoRet(), - StopTracking, StopTracking); - - return StopSummary; + const RetainSummary *getDefaultSummary() { + return getPersistentSummary(RetEffect::MakeNoRet(), + DoNothing, MayEscape); } - const RetainSummary *getInitMethodSummary(QualType RetTy); + const RetainSummary *getPersistentStopSummary() { + return getPersistentSummary(RetEffect::MakeNoRet(), + StopTracking, StopTracking); + } void InitializeClassMethodSummaries(); void InitializeMethodSummaries(); @@ -661,10 +669,11 @@ private: ObjCMethodSummaries[S] = Summ; } - void addClassMethSummary(const char* Cls, const char* nullaryName, - const RetainSummary *Summ) { + void addClassMethSummary(const char* Cls, const char* name, + const RetainSummary *Summ, bool isNullary = true) { IdentifierInfo* ClsII = &Ctx.Idents.get(Cls); - Selector S = GetNullarySelector(nullaryName, Ctx); + Selector S = isNullary ? GetNullarySelector(name, Ctx) + : GetUnarySelector(name, Ctx); ObjCClassMethodSummaries[ObjCSummaryKey(ClsII, S)] = Summ; } @@ -725,50 +734,37 @@ public: ObjCInitRetE(gcenabled ? RetEffect::MakeGCNotOwned() : (usesARC ? RetEffect::MakeARCNotOwned() - : RetEffect::MakeOwnedWhenTrackedReceiver())), - DefaultSummary(AF.getEmptyMap() /* per-argument effects (none) */, - RetEffect::MakeNoRet() /* return effect */, - MayEscape, /* default argument effect */ - DoNothing /* receiver effect */), - StopSummary(0) { - + : RetEffect::MakeOwnedWhenTrackedReceiver())) { InitializeClassMethodSummaries(); InitializeMethodSummaries(); } - const RetainSummary * getSummary(const FunctionDecl *FD); + const RetainSummary *getSummary(const FunctionDecl *FD); + + const RetainSummary *getMethodSummary(Selector S, IdentifierInfo *ClsName, + const ObjCInterfaceDecl *ID, + const ObjCMethodDecl *MD, + QualType RetTy, + ObjCMethodSummariesTy &CachedSummaries); const RetainSummary *getInstanceMethodSummary(const ObjCMessage &msg, - const ProgramState *state, + ProgramStateRef state, const LocationContext *LC); - const RetainSummary * getInstanceMethodSummary(const ObjCMessage &msg, + const RetainSummary *getInstanceMethodSummary(const ObjCMessage &msg, const ObjCInterfaceDecl *ID) { - return getInstanceMethodSummary(msg.getSelector(), 0, - ID, msg.getMethodDecl(), msg.getType(Ctx)); + return getMethodSummary(msg.getSelector(), 0, ID, msg.getMethodDecl(), + msg.getType(Ctx), ObjCMethodSummaries); } - const RetainSummary * getInstanceMethodSummary(Selector S, - IdentifierInfo *ClsName, - const ObjCInterfaceDecl *ID, - const ObjCMethodDecl *MD, - QualType RetTy); - - const RetainSummary *getClassMethodSummary(Selector S, - IdentifierInfo *ClsName, - const ObjCInterfaceDecl *ID, - const ObjCMethodDecl *MD, - QualType RetTy); - const RetainSummary *getClassMethodSummary(const ObjCMessage &msg) { const ObjCInterfaceDecl *Class = 0; if (!msg.isInstanceMessage()) Class = msg.getReceiverInterface(); - return getClassMethodSummary(msg.getSelector(), - Class? Class->getIdentifier() : 0, - Class, - msg.getMethodDecl(), msg.getType(Ctx)); + return getMethodSummary(msg.getSelector(), Class->getIdentifier(), + Class, msg.getMethodDecl(), msg.getType(Ctx), + ObjCClassMethodSummaries); } /// getMethodSummary - This version of getMethodSummary is used to query @@ -780,13 +776,16 @@ public: IdentifierInfo *ClsName = ID->getIdentifier(); QualType ResultTy = MD->getResultType(); + ObjCMethodSummariesTy *CachedSummaries; if (MD->isInstanceMethod()) - return getInstanceMethodSummary(S, ClsName, ID, MD, ResultTy); + CachedSummaries = &ObjCMethodSummaries; else - return getClassMethodSummary(S, ClsName, ID, MD, ResultTy); + CachedSummaries = &ObjCClassMethodSummaries; + + return getMethodSummary(S, ClsName, ID, MD, ResultTy, *CachedSummaries); } - const RetainSummary * getCommonMethodSummary(const ObjCMethodDecl *MD, + const RetainSummary *getStandardMethodSummary(const ObjCMethodDecl *MD, Selector S, QualType RetTy); void updateSummaryFromAnnotations(const RetainSummary *&Summ, @@ -800,12 +799,6 @@ public: bool isARCEnabled() const { return ARCEnabled; } bool isARCorGCEnabled() const { return GCEnabled || ARCEnabled; } - - const RetainSummary *copySummary(const RetainSummary *OldSumm) { - RetainSummary *Summ = (RetainSummary *) BPAlloc.Allocate<RetainSummary>(); - new (Summ) RetainSummary(*OldSumm); - return Summ; - } }; // Used to avoid allocating long-term (BPAlloc'd) memory for default retain @@ -815,23 +808,17 @@ public: class RetainSummaryTemplate { RetainSummaryManager &Manager; const RetainSummary *&RealSummary; - const RetainSummary *BaseSummary; RetainSummary ScratchSummary; bool Accessed; public: - RetainSummaryTemplate(const RetainSummary *&real, const RetainSummary &base, - RetainSummaryManager &manager) - : Manager(manager), - RealSummary(real), - BaseSummary(&base), - ScratchSummary(base), + RetainSummaryTemplate(const RetainSummary *&real, const RetainSummary &base, + RetainSummaryManager &mgr) + : Manager(mgr), RealSummary(real), ScratchSummary(real ? *real : base), Accessed(false) {} ~RetainSummaryTemplate() { if (Accessed) - RealSummary = Manager.copySummary(&ScratchSummary); - else if (!RealSummary) - RealSummary = BaseSummary; + RealSummary = Manager.getPersistentSummary(ScratchSummary); } RetainSummary &operator*() { @@ -858,12 +845,26 @@ ArgEffects RetainSummaryManager::getArgEffects() { } const RetainSummary * -RetainSummaryManager::getPersistentSummary(ArgEffects AE, RetEffect RetEff, - ArgEffect ReceiverEff, - ArgEffect DefaultEff) { - // Create the summary and return it. +RetainSummaryManager::getPersistentSummary(const RetainSummary &OldSumm) { + // Unique "simple" summaries -- those without ArgEffects. + if (OldSumm.isSimple()) { + llvm::FoldingSetNodeID ID; + OldSumm.Profile(ID); + + void *Pos; + CachedSummaryNode *N = SimpleSummaries.FindNodeOrInsertPos(ID, Pos); + + if (!N) { + N = (CachedSummaryNode *) BPAlloc.Allocate<CachedSummaryNode>(); + new (N) CachedSummaryNode(OldSumm); + SimpleSummaries.InsertNode(N, Pos); + } + + return &N->getValue(); + } + RetainSummary *Summ = (RetainSummary *) BPAlloc.Allocate<RetainSummary>(); - new (Summ) RetainSummary(AE, RetEff, DefaultEff, ReceiverEff); + new (Summ) RetainSummary(OldSumm); return Summ; } @@ -984,6 +985,21 @@ const RetainSummary * RetainSummaryManager::getSummary(const FunctionDecl *FD) { // correctly. ScratchArgs = AF.add(ScratchArgs, 12, StopTracking); S = getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing); + } else if (FName == "dispatch_set_context") { + // <rdar://problem/11059275> - The analyzer currently doesn't have + // a good way to reason about the finalizer function for libdispatch. + // If we pass a context object that is memory managed, stop tracking it. + // FIXME: this hack should possibly go away once we can handle + // libdispatch finalizers. + ScratchArgs = AF.add(ScratchArgs, 1, StopTracking); + S = getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing); + } else if (FName.startswith("NS") && + (FName.find("Insert") != StringRef::npos)) { + // Whitelist NSXXInsertXX, for example NSMapInsertIfAbsent, since they can + // be deallocated by NSMapRemove. (radar://11152419) + ScratchArgs = AF.add(ScratchArgs, 1, StopTracking); + ScratchArgs = AF.add(ScratchArgs, 2, StopTracking); + S = getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing); } // Did we get a summary? @@ -1106,7 +1122,6 @@ RetainSummaryManager::getUnarySummary(const FunctionType* FT, case cfretain: Effect = IncRef; break; case cfrelease: Effect = DecRef; break; case cfmakecollectable: Effect = MakeCollectable; break; - default: llvm_unreachable("Not a supported unary function."); } ScratchArgs = AF.add(ScratchArgs, 0, Effect); @@ -1131,25 +1146,13 @@ RetainSummaryManager::getCFSummaryGetRule(const FunctionDecl *FD) { // Summary creation for Selectors. //===----------------------------------------------------------------------===// -const RetainSummary * -RetainSummaryManager::getInitMethodSummary(QualType RetTy) { - assert(ScratchArgs.isEmpty()); - // 'init' methods conceptually return a newly allocated object and claim - // the receiver. - if (cocoa::isCocoaObjectRef(RetTy) || - coreFoundation::isCFObjectRef(RetTy)) - return getPersistentSummary(ObjCInitRetE, DecRefMsg); - - return getDefaultSummary(); -} - void RetainSummaryManager::updateSummaryFromAnnotations(const RetainSummary *&Summ, const FunctionDecl *FD) { if (!FD) return; - RetainSummaryTemplate Template(Summ, DefaultSummary, *this); + RetainSummaryTemplate Template(Summ, *getDefaultSummary(), *this); // Effects on the parameters. unsigned parm_idx = 0; @@ -1197,8 +1200,7 @@ RetainSummaryManager::updateSummaryFromAnnotations(const RetainSummary *&Summ, if (!MD) return; - RetainSummaryTemplate Template(Summ, DefaultSummary, *this); - + RetainSummaryTemplate Template(Summ, *getDefaultSummary(), *this); bool isTrackedLoc = false; // Effects on the receiver. @@ -1247,8 +1249,8 @@ RetainSummaryManager::updateSummaryFromAnnotations(const RetainSummary *&Summ, } const RetainSummary * -RetainSummaryManager::getCommonMethodSummary(const ObjCMethodDecl *MD, - Selector S, QualType RetTy) { +RetainSummaryManager::getStandardMethodSummary(const ObjCMethodDecl *MD, + Selector S, QualType RetTy) { if (MD) { // Scan the method decl for 'void*' arguments. These should be treated @@ -1265,8 +1267,74 @@ RetainSummaryManager::getCommonMethodSummary(const ObjCMethodDecl *MD, } } - // Any special effect for the receiver? + // Any special effects? ArgEffect ReceiverEff = DoNothing; + RetEffect ResultEff = RetEffect::MakeNoRet(); + + // Check the method family, and apply any default annotations. + switch (MD ? MD->getMethodFamily() : S.getMethodFamily()) { + case OMF_None: + case OMF_performSelector: + // Assume all Objective-C methods follow Cocoa Memory Management rules. + // FIXME: Does the non-threaded performSelector family really belong here? + // The selector could be, say, @selector(copy). + if (cocoa::isCocoaObjectRef(RetTy)) + ResultEff = RetEffect::MakeNotOwned(RetEffect::ObjC); + else if (coreFoundation::isCFObjectRef(RetTy)) { + // ObjCMethodDecl currently doesn't consider CF objects as valid return + // values for alloc, new, copy, or mutableCopy, so we have to + // double-check with the selector. This is ugly, but there aren't that + // many Objective-C methods that return CF objects, right? + if (MD) { + switch (S.getMethodFamily()) { + case OMF_alloc: + case OMF_new: + case OMF_copy: + case OMF_mutableCopy: + ResultEff = RetEffect::MakeOwned(RetEffect::CF, true); + break; + default: + ResultEff = RetEffect::MakeNotOwned(RetEffect::CF); + break; + } + } else { + ResultEff = RetEffect::MakeNotOwned(RetEffect::CF); + } + } + break; + case OMF_init: + ResultEff = ObjCInitRetE; + ReceiverEff = DecRefMsg; + break; + case OMF_alloc: + case OMF_new: + case OMF_copy: + case OMF_mutableCopy: + if (cocoa::isCocoaObjectRef(RetTy)) + ResultEff = ObjCAllocRetE; + else if (coreFoundation::isCFObjectRef(RetTy)) + ResultEff = RetEffect::MakeOwned(RetEffect::CF, true); + break; + case OMF_autorelease: + ReceiverEff = Autorelease; + break; + case OMF_retain: + ReceiverEff = IncRefMsg; + break; + case OMF_release: + ReceiverEff = DecRefMsg; + break; + case OMF_dealloc: + ReceiverEff = Dealloc; + break; + case OMF_self: + // -self is handled specially by the ExprEngine to propagate the receiver. + break; + case OMF_retainCount: + case OMF_finalize: + // These methods don't return objects. + break; + } // If one of the arguments in the selector has the keyword 'delegate' we // should stop tracking the reference count for the receiver. This is @@ -1279,34 +1347,16 @@ RetainSummaryManager::getCommonMethodSummary(const ObjCMethodDecl *MD, ReceiverEff = StopTracking; } - // Look for methods that return an owned object. - if (cocoa::isCocoaObjectRef(RetTy)) { - // EXPERIMENTAL: assume the Cocoa conventions for all objects returned - // by instance methods. - RetEffect E = cocoa::followsFundamentalRule(S, MD) - ? ObjCAllocRetE : RetEffect::MakeNotOwned(RetEffect::ObjC); - - return getPersistentSummary(E, ReceiverEff, MayEscape); - } - - // Look for methods that return an owned core foundation object. - if (coreFoundation::isCFObjectRef(RetTy)) { - RetEffect E = cocoa::followsFundamentalRule(S, MD) - ? RetEffect::MakeOwned(RetEffect::CF, true) - : RetEffect::MakeNotOwned(RetEffect::CF); - - return getPersistentSummary(E, ReceiverEff, MayEscape); - } - - if (ScratchArgs.isEmpty() && ReceiverEff == DoNothing) + if (ScratchArgs.isEmpty() && ReceiverEff == DoNothing && + ResultEff.getKind() == RetEffect::NoRet) return getDefaultSummary(); - return getPersistentSummary(RetEffect::MakeNoRet(), ReceiverEff, MayEscape); + return getPersistentSummary(ResultEff, ReceiverEff, MayEscape); } const RetainSummary * RetainSummaryManager::getInstanceMethodSummary(const ObjCMessage &msg, - const ProgramState *state, + ProgramStateRef state, const LocationContext *LC) { // We need the type-information of the tracked receiver object @@ -1319,7 +1369,7 @@ RetainSummaryManager::getInstanceMethodSummary(const ObjCMessage &msg, SVal receiverV; if (Receiver) { - receiverV = state->getSValAsScalarOrLoc(Receiver); + receiverV = state->getSValAsScalarOrLoc(Receiver, LC); // FIXME: Eventually replace the use of state->get<RefBindings> with // a generic API for reasoning about the Objective-C types of symbolic @@ -1348,51 +1398,22 @@ RetainSummaryManager::getInstanceMethodSummary(const ObjCMessage &msg, } const RetainSummary * -RetainSummaryManager::getInstanceMethodSummary(Selector S, - IdentifierInfo *ClsName, - const ObjCInterfaceDecl *ID, - const ObjCMethodDecl *MD, - QualType RetTy) { +RetainSummaryManager::getMethodSummary(Selector S, IdentifierInfo *ClsName, + const ObjCInterfaceDecl *ID, + const ObjCMethodDecl *MD, QualType RetTy, + ObjCMethodSummariesTy &CachedSummaries) { // Look up a summary in our summary cache. - const RetainSummary *Summ = ObjCMethodSummaries.find(ID, ClsName, S); + const RetainSummary *Summ = CachedSummaries.find(ID, ClsName, S); if (!Summ) { - assert(ScratchArgs.isEmpty()); - - // "initXXX": pass-through for receiver. - if (cocoa::deriveNamingConvention(S, MD) == cocoa::InitRule) - Summ = getInitMethodSummary(RetTy); - else - Summ = getCommonMethodSummary(MD, S, RetTy); + Summ = getStandardMethodSummary(MD, S, RetTy); // Annotations override defaults. updateSummaryFromAnnotations(Summ, MD); // Memoize the summary. - ObjCMethodSummaries[ObjCSummaryKey(ID, ClsName, S)] = Summ; - } - - return Summ; -} - -const RetainSummary * -RetainSummaryManager::getClassMethodSummary(Selector S, IdentifierInfo *ClsName, - const ObjCInterfaceDecl *ID, - const ObjCMethodDecl *MD, - QualType RetTy) { - - assert(ClsName && "Class name must be specified."); - const RetainSummary *Summ = ObjCClassMethodSummaries.find(ID, ClsName, S); - - if (!Summ) { - Summ = getCommonMethodSummary(MD, S, RetTy); - - // Annotations override defaults. - updateSummaryFromAnnotations(Summ, MD); - - // Memoize the summary. - ObjCClassMethodSummaries[ObjCSummaryKey(ID, ClsName, S)] = Summ; + CachedSummaries[ObjCSummaryKey(ID, ClsName, S)] = Summ; } return Summ; @@ -1515,6 +1536,7 @@ void RetainSummaryManager::InitializeMethodSummaries() { // Don't track allocated autorelease pools yet, as it is okay to prematurely // exit a method. addClassMethSummary("NSAutoreleasePool", "alloc", NoTrackYet); + addClassMethSummary("NSAutoreleasePool", "allocWithZone", NoTrackYet, false); // Create summaries QCRenderer/QCView -createSnapShotImageOfType: addInstMethSummary("QCRenderer", AllocSumm, @@ -1561,13 +1583,13 @@ template<> struct ProgramStateTrait<AutoreleasePoolContents> } // end GR namespace } // end clang namespace -static SymbolRef GetCurrentAutoreleasePool(const ProgramState *state) { +static SymbolRef GetCurrentAutoreleasePool(ProgramStateRef state) { ARStack stack = state->get<AutoreleaseStack>(); return stack.isEmpty() ? SymbolRef() : stack.getHead(); } -static const ProgramState * -SendAutorelease(const ProgramState *state, +static ProgramStateRef +SendAutorelease(ProgramStateRef state, ARCounts::Factory &F, SymbolRef sym) { SymbolRef pool = GetCurrentAutoreleasePool(state); @@ -1598,7 +1620,7 @@ namespace { class CFRefBug : public BugType { protected: CFRefBug(StringRef name) - : BugType(name, "Memory (Core Foundation/Objective-C)") {} + : BugType(name, categories::MemoryCoreFoundationObjectiveC) {} public: // FIXME: Eventually remove. @@ -1698,7 +1720,7 @@ namespace { // Bug Reports. // //===---------===// - class CFRefReportVisitor : public BugReporterVisitor { + class CFRefReportVisitor : public BugReporterVisitorImpl<CFRefReportVisitor> { protected: SymbolRef Sym; const SummaryLogTy &SummaryLog; @@ -1733,6 +1755,15 @@ namespace { PathDiagnosticPiece *getEndPath(BugReporterContext &BRC, const ExplodedNode *N, BugReport &BR); + + virtual BugReporterVisitor *clone() const { + // The curiously-recurring template pattern only works for one level of + // subclassing. Rather than make a new template base for + // CFRefReportVisitor, we simply override clone() to do the right thing. + // This could be trouble someday if BugReporterVisitorImpl is ever + // used for something else besides a convenient implementation of clone(). + return new CFRefLeakReportVisitor(*this); + } }; class CFRefReport : public BugReport { @@ -1771,7 +1802,7 @@ namespace { public: CFRefLeakReport(CFRefBug &D, const LangOptions &LOpts, bool GCEnabled, const SummaryLogTy &Log, ExplodedNode *n, SymbolRef sym, - ExprEngine &Eng); + CheckerContext &Ctx); PathDiagnosticLocation getLocation(const SourceManager &SM) const { assert(Location.isValid()); @@ -1823,6 +1854,20 @@ static inline bool contains(const SmallVectorImpl<ArgEffect>& V, return false; } +static bool isPropertyAccess(const Stmt *S, ParentMap &PM) { + unsigned maxDepth = 4; + while (S && maxDepth) { + if (const PseudoObjectExpr *PO = dyn_cast<PseudoObjectExpr>(S)) { + if (!isa<ObjCMessageExpr>(PO->getSyntacticForm())) + return true; + return false; + } + S = PM.getParent(S); + --maxDepth; + } + return false; +} + PathDiagnosticPiece *CFRefReportVisitor::VisitNode(const ExplodedNode *N, const ExplodedNode *PrevN, BugReporterContext &BRC, @@ -1832,8 +1877,9 @@ PathDiagnosticPiece *CFRefReportVisitor::VisitNode(const ExplodedNode *N, return NULL; // Check if the type state has changed. - const ProgramState *PrevSt = PrevN->getState(); - const ProgramState *CurrSt = N->getState(); + ProgramStateRef PrevSt = PrevN->getState(); + ProgramStateRef CurrSt = N->getState(); + const LocationContext *LCtx = N->getLocationContext(); const RefVal* CurrT = CurrSt->get<RefBindings>(Sym); if (!CurrT) return NULL; @@ -1851,40 +1897,49 @@ PathDiagnosticPiece *CFRefReportVisitor::VisitNode(const ExplodedNode *N, if (!PrevT) { const Stmt *S = cast<StmtPoint>(N->getLocation()).getStmt(); - if (const CallExpr *CE = dyn_cast<CallExpr>(S)) { - // Get the name of the callee (if it is available). - SVal X = CurrSt->getSValAsScalarOrLoc(CE->getCallee()); - if (const FunctionDecl *FD = X.getAsFunctionDecl()) - os << "Call to function '" << *FD << '\''; - else - os << "function call"; + if (isa<ObjCArrayLiteral>(S)) { + os << "NSArray literal is an object with a +0 retain count"; } - else if (isa<ObjCMessageExpr>(S)) { - os << "Method"; - } else { - os << "Property"; + else if (isa<ObjCDictionaryLiteral>(S)) { + os << "NSDictionary literal is an object with a +0 retain count"; } + else { + if (const CallExpr *CE = dyn_cast<CallExpr>(S)) { + // Get the name of the callee (if it is available). + SVal X = CurrSt->getSValAsScalarOrLoc(CE->getCallee(), LCtx); + if (const FunctionDecl *FD = X.getAsFunctionDecl()) + os << "Call to function '" << *FD << '\''; + else + os << "function call"; + } + else { + assert(isa<ObjCMessageExpr>(S)); + // The message expression may have between written directly or as + // a property access. Lazily determine which case we are looking at. + os << (isPropertyAccess(S, N->getParentMap()) ? "Property" : "Method"); + } - if (CurrV.getObjKind() == RetEffect::CF) { - os << " returns a Core Foundation object with a "; - } - else { - assert (CurrV.getObjKind() == RetEffect::ObjC); - os << " returns an Objective-C object with a "; - } + if (CurrV.getObjKind() == RetEffect::CF) { + os << " returns a Core Foundation object with a "; + } + else { + assert (CurrV.getObjKind() == RetEffect::ObjC); + os << " returns an Objective-C object with a "; + } - if (CurrV.isOwned()) { - os << "+1 retain count"; + if (CurrV.isOwned()) { + os << "+1 retain count"; - if (GCEnabled) { - assert(CurrV.getObjKind() == RetEffect::CF); - os << ". " - "Core Foundation objects are not automatically garbage collected."; + if (GCEnabled) { + assert(CurrV.getObjKind() == RetEffect::CF); + os << ". " + "Core Foundation objects are not automatically garbage collected."; + } + } + else { + assert (CurrV.isNotOwned()); + os << "+0 retain count"; } - } - else { - assert (CurrV.isNotOwned()); - os << "+0 retain count"; } PathDiagnosticLocation Pos(S, BRC.getSourceManager(), @@ -1912,7 +1967,7 @@ PathDiagnosticPiece *CFRefReportVisitor::VisitNode(const ExplodedNode *N, // Retrieve the value of the argument. Is it the symbol // we are interested in? - if (CurrSt->getSValAsScalarOrLoc(*AI).getAsLocSymbol() != Sym) + if (CurrSt->getSValAsScalarOrLoc(*AI, LCtx).getAsLocSymbol() != Sym) continue; // We have an argument. Get the effect! @@ -1921,7 +1976,8 @@ PathDiagnosticPiece *CFRefReportVisitor::VisitNode(const ExplodedNode *N, } else if (const ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(S)) { if (const Expr *receiver = ME->getInstanceReceiver()) - if (CurrSt->getSValAsScalarOrLoc(receiver).getAsLocSymbol() == Sym) { + if (CurrSt->getSValAsScalarOrLoc(receiver, LCtx) + .getAsLocSymbol() == Sym) { // The symbol we are tracking is the receiver. AEffects.push_back(Summ->getReceiverEffect()); } @@ -1949,7 +2005,8 @@ PathDiagnosticPiece *CFRefReportVisitor::VisitNode(const ExplodedNode *N, if (contains(AEffects, MakeCollectable)) { // Get the name of the function. const Stmt *S = cast<StmtPoint>(N->getLocation()).getStmt(); - SVal X = CurrSt->getSValAsScalarOrLoc(cast<CallExpr>(S)->getCallee()); + SVal X = + CurrSt->getSValAsScalarOrLoc(cast<CallExpr>(S)->getCallee(), LCtx); const FunctionDecl *FD = X.getAsFunctionDecl(); if (GCEnabled) { @@ -2004,8 +2061,8 @@ PathDiagnosticPiece *CFRefReportVisitor::VisitNode(const ExplodedNode *N, if (PrevV.getKind() == RefVal::Released) { assert(GCEnabled && CurrV.getCount() > 0); - os << " The object is not eligible for garbage collection until the " - "retain count reaches 0 again."; + os << " The object is not eligible for garbage collection until " + "the retain count reaches 0 again."; } break; @@ -2015,8 +2072,12 @@ PathDiagnosticPiece *CFRefReportVisitor::VisitNode(const ExplodedNode *N, break; case RefVal::ReturnedOwned: - os << "Object returned to caller as an owning reference (single retain " - "count transferred to caller)"; + // Autoreleases can be applied after marking a node ReturnedOwned. + if (CurrV.getAutoreleaseCount()) + return NULL; + + os << "Object returned to caller as an owning reference (single " + "retain count transferred to caller)"; break; case RefVal::ReturnedNotOwned: @@ -2061,7 +2122,7 @@ PathDiagnosticPiece *CFRefReportVisitor::VisitNode(const ExplodedNode *N, for (Stmt::const_child_iterator I = S->child_begin(), E = S->child_end(); I!=E; ++I) if (const Expr *Exp = dyn_cast_or_null<Expr>(*I)) - if (CurrSt->getSValAsScalarOrLoc(Exp).getAsLocSymbol() == Sym) { + if (CurrSt->getSValAsScalarOrLoc(Exp, LCtx).getAsLocSymbol() == Sym) { P->addRange(Exp->getSourceRange()); break; } @@ -2069,62 +2130,42 @@ PathDiagnosticPiece *CFRefReportVisitor::VisitNode(const ExplodedNode *N, return P; } -namespace { - class FindUniqueBinding : - public StoreManager::BindingsHandler { - SymbolRef Sym; - const MemRegion* Binding; - bool First; - - public: - FindUniqueBinding(SymbolRef sym) : Sym(sym), Binding(0), First(true) {} - - bool HandleBinding(StoreManager& SMgr, Store store, const MemRegion* R, - SVal val) { - - SymbolRef SymV = val.getAsSymbol(); - if (!SymV || SymV != Sym) - return true; - - if (Binding) { - First = false; - return false; - } - else - Binding = R; - - return true; - } - - operator bool() { return First && Binding; } - const MemRegion* getRegion() { return Binding; } - }; -} - +// Find the first node in the current function context that referred to the +// tracked symbol and the memory location that value was stored to. Note, the +// value is only reported if the allocation occurred in the same function as +// the leak. static std::pair<const ExplodedNode*,const MemRegion*> GetAllocationSite(ProgramStateManager& StateMgr, const ExplodedNode *N, SymbolRef Sym) { - - // Find both first node that referred to the tracked symbol and the - // memory location that value was store to. const ExplodedNode *Last = N; const MemRegion* FirstBinding = 0; + const LocationContext *LeakContext = N->getLocationContext(); while (N) { - const ProgramState *St = N->getState(); + ProgramStateRef St = N->getState(); RefBindings B = St->get<RefBindings>(); if (!B.lookup(Sym)) break; - FindUniqueBinding FB(Sym); + StoreManager::FindUniqueBinding FB(Sym); StateMgr.iterBindings(St, FB); if (FB) FirstBinding = FB.getRegion(); - Last = N; + // Allocation node, is the last node in the current context in which the + // symbol was tracked. + if (N->getLocationContext() == LeakContext) + Last = N; + N = N->pred_empty() ? NULL : *(N->pred_begin()); } + // If allocation happened in a function different from the leak node context, + // do not report the binding. + if (N->getLocationContext() != LeakContext) { + FirstBinding = 0; + } + return std::make_pair(Last, FirstBinding); } @@ -2132,9 +2173,7 @@ PathDiagnosticPiece* CFRefReportVisitor::getEndPath(BugReporterContext &BRC, const ExplodedNode *EndN, BugReport &BR) { - // Tell the BugReporterContext to report cases when the tracked symbol is - // assigned to different variables, etc. - BRC.addNotableSymbol(Sym); + BR.markInteresting(Sym); return BugReporterVisitor::getDefaultEndPath(BRC, EndN, BR); } @@ -2145,7 +2184,7 @@ CFRefLeakReportVisitor::getEndPath(BugReporterContext &BRC, // Tell the BugReporterContext to report cases when the tracked symbol is // assigned to different variables, etc. - BRC.addNotableSymbol(Sym); + BR.markInteresting(Sym); // We are reporting a leak. Walk up the graph to get to the first node where // the symbol appeared, and also get the first VarDecl that tracked object @@ -2193,10 +2232,10 @@ CFRefLeakReportVisitor::getEndPath(BugReporterContext &BRC, } else { const FunctionDecl *FD = cast<FunctionDecl>(D); - os << " is return from a function whose name ('" - << FD->getNameAsString() + os << " is returned from a function whose name ('" + << *FD << "') does not contain 'Copy' or 'Create'. This violates the naming" - " convention rules given the Memory Management Guide for Core" + " convention rules given in the Memory Management Guide for Core" " Foundation"; } } @@ -2218,7 +2257,7 @@ CFRefLeakReportVisitor::getEndPath(BugReporterContext &BRC, CFRefLeakReport::CFRefLeakReport(CFRefBug &D, const LangOptions &LOpts, bool GCEnabled, const SummaryLogTy &Log, ExplodedNode *n, SymbolRef sym, - ExprEngine &Eng) + CheckerContext &Ctx) : CFRefReport(D, LOpts, GCEnabled, Log, n, sym, false) { // Most bug reports are cached at the location where they occurred. @@ -2231,10 +2270,10 @@ CFRefLeakReport::CFRefLeakReport(CFRefBug &D, const LangOptions &LOpts, // same SourceLocation. const ExplodedNode *AllocNode = 0; - const SourceManager& SMgr = Eng.getContext().getSourceManager(); + const SourceManager& SMgr = Ctx.getSourceManager(); llvm::tie(AllocNode, AllocBinding) = // Set AllocBinding. - GetAllocationSite(Eng.getStateManager(), getErrorNode(), sym); + GetAllocationSite(Ctx.getStateManager(), getErrorNode(), sym); // Get the SourceLocation for the allocation site. ProgramPoint P = AllocNode->getLocation(); @@ -2244,15 +2283,14 @@ CFRefLeakReport::CFRefLeakReport(CFRefBug &D, const LangOptions &LOpts, // Fill in the description of the bug. Description.clear(); llvm::raw_string_ostream os(Description); - unsigned AllocLine = SMgr.getExpansionLineNumber(AllocStmt->getLocStart()); os << "Potential leak "; if (GCEnabled) os << "(when using garbage collection) "; - os << "of an object allocated on line " << AllocLine; + os << "of an object"; // FIXME: AllocBinding doesn't get populated for RegionStore yet. if (AllocBinding) - os << " and stored into '" << AllocBinding->getString() << '\''; + os << " stored into '" << AllocBinding->getString() << '\''; addVisitor(new CFRefLeakReportVisitor(sym, GCEnabled, Log)); } @@ -2271,24 +2309,26 @@ class RetainCountChecker check::PostStmt<CastExpr>, check::PostStmt<CallExpr>, check::PostStmt<CXXConstructExpr>, + check::PostStmt<ObjCArrayLiteral>, + check::PostStmt<ObjCDictionaryLiteral>, check::PostObjCMessage, check::PreStmt<ReturnStmt>, check::RegionChanges, eval::Assume, eval::Call > { - mutable llvm::OwningPtr<CFRefBug> useAfterRelease, releaseNotOwned; - mutable llvm::OwningPtr<CFRefBug> deallocGC, deallocNotOwned; - mutable llvm::OwningPtr<CFRefBug> overAutorelease, returnNotOwnedForOwned; - mutable llvm::OwningPtr<CFRefBug> leakWithinFunction, leakAtReturn; - mutable llvm::OwningPtr<CFRefBug> leakWithinFunctionGC, leakAtReturnGC; + mutable OwningPtr<CFRefBug> useAfterRelease, releaseNotOwned; + mutable OwningPtr<CFRefBug> deallocGC, deallocNotOwned; + mutable OwningPtr<CFRefBug> overAutorelease, returnNotOwnedForOwned; + mutable OwningPtr<CFRefBug> leakWithinFunction, leakAtReturn; + mutable OwningPtr<CFRefBug> leakWithinFunctionGC, leakAtReturnGC; typedef llvm::DenseMap<SymbolRef, const SimpleProgramPointTag *> SymbolTagMap; // This map is only used to ensure proper deletion of any allocated tags. mutable SymbolTagMap DeadSymbolTags; - mutable llvm::OwningPtr<RetainSummaryManager> Summaries; - mutable llvm::OwningPtr<RetainSummaryManager> SummariesGC; + mutable OwningPtr<RetainSummaryManager> Summaries; + mutable OwningPtr<RetainSummaryManager> SummariesGC; mutable ARCounts::Factory ARCountFactory; @@ -2386,7 +2426,7 @@ public: bool GCEnabled) const { // FIXME: We don't support ARC being turned on and off during one analysis. // (nor, for that matter, do we support changing ASTContexts) - bool ARCEnabled = (bool)Ctx.getLangOptions().ObjCAutoRefCount; + bool ARCEnabled = (bool)Ctx.getLangOpts().ObjCAutoRefCount; if (GCEnabled) { if (!SummariesGC) SummariesGC.reset(new RetainSummaryManager(Ctx, true, ARCEnabled)); @@ -2406,7 +2446,7 @@ public: return getSummaryManager(C.getASTContext(), C.isObjCGCEnabled()); } - void printState(raw_ostream &Out, const ProgramState *State, + void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, const char *Sep) const; void checkBind(SVal loc, SVal val, const Stmt *S, CheckerContext &C) const; @@ -2415,66 +2455,72 @@ public: void checkPostStmt(const CallExpr *CE, CheckerContext &C) const; void checkPostStmt(const CXXConstructExpr *CE, CheckerContext &C) const; + void checkPostStmt(const ObjCArrayLiteral *AL, CheckerContext &C) const; + void checkPostStmt(const ObjCDictionaryLiteral *DL, CheckerContext &C) const; void checkPostObjCMessage(const ObjCMessage &Msg, CheckerContext &C) const; + void checkSummary(const RetainSummary &Summ, const CallOrObjCMessage &Call, CheckerContext &C) const; bool evalCall(const CallExpr *CE, CheckerContext &C) const; - const ProgramState *evalAssume(const ProgramState *state, SVal Cond, + ProgramStateRef evalAssume(ProgramStateRef state, SVal Cond, bool Assumption) const; - const ProgramState * - checkRegionChanges(const ProgramState *state, + ProgramStateRef + checkRegionChanges(ProgramStateRef state, const StoreManager::InvalidatedSymbols *invalidated, ArrayRef<const MemRegion *> ExplicitRegions, - ArrayRef<const MemRegion *> Regions) const; + ArrayRef<const MemRegion *> Regions, + const CallOrObjCMessage *Call) const; - bool wantsRegionChangeUpdate(const ProgramState *state) const { + bool wantsRegionChangeUpdate(ProgramStateRef state) const { return true; } void checkPreStmt(const ReturnStmt *S, CheckerContext &C) const; void checkReturnWithRetEffect(const ReturnStmt *S, CheckerContext &C, ExplodedNode *Pred, RetEffect RE, RefVal X, - SymbolRef Sym, const ProgramState *state) const; + SymbolRef Sym, ProgramStateRef state) const; void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; - void checkEndPath(EndOfFunctionNodeBuilder &Builder, ExprEngine &Eng) const; + void checkEndPath(CheckerContext &C) const; - const ProgramState *updateSymbol(const ProgramState *state, SymbolRef sym, + ProgramStateRef updateSymbol(ProgramStateRef state, SymbolRef sym, RefVal V, ArgEffect E, RefVal::Kind &hasErr, CheckerContext &C) const; - void processNonLeakError(const ProgramState *St, SourceRange ErrorRange, + void processNonLeakError(ProgramStateRef St, SourceRange ErrorRange, RefVal::Kind ErrorKind, SymbolRef Sym, CheckerContext &C) const; + + void processObjCLiterals(CheckerContext &C, const Expr *Ex) const; const ProgramPointTag *getDeadSymbolTag(SymbolRef sym) const; - const ProgramState *handleSymbolDeath(const ProgramState *state, + ProgramStateRef handleSymbolDeath(ProgramStateRef state, SymbolRef sid, RefVal V, SmallVectorImpl<SymbolRef> &Leaked) const; - std::pair<ExplodedNode *, const ProgramState *> - handleAutoreleaseCounts(const ProgramState *state, + std::pair<ExplodedNode *, ProgramStateRef > + handleAutoreleaseCounts(ProgramStateRef state, GenericNodeBuilderRefCount Bd, ExplodedNode *Pred, - ExprEngine &Eng, SymbolRef Sym, RefVal V) const; + CheckerContext &Ctx, SymbolRef Sym, RefVal V) const; - ExplodedNode *processLeaks(const ProgramState *state, + ExplodedNode *processLeaks(ProgramStateRef state, SmallVectorImpl<SymbolRef> &Leaked, GenericNodeBuilderRefCount &Builder, - ExprEngine &Eng, + CheckerContext &Ctx, ExplodedNode *Pred = 0) const; }; } // end anonymous namespace namespace { class StopTrackingCallback : public SymbolVisitor { - const ProgramState *state; + ProgramStateRef state; public: - StopTrackingCallback(const ProgramState *st) : state(st) {} - const ProgramState *getState() const { return state; } + StopTrackingCallback(ProgramStateRef st) : state(st) {} + ProgramStateRef getState() const { return state; } bool VisitSymbol(SymbolRef sym) { state = state->remove<RefBindings>(sym); @@ -2495,9 +2541,10 @@ void RetainCountChecker::checkPostStmt(const BlockExpr *BE, if (!BE->getBlockDecl()->hasCaptures()) return; - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); const BlockDataRegion *R = - cast<BlockDataRegion>(state->getSVal(BE).getAsRegion()); + cast<BlockDataRegion>(state->getSVal(BE, + C.getLocationContext()).getAsRegion()); BlockDataRegion::referenced_vars_iterator I = R->referenced_vars_begin(), E = R->referenced_vars_end(); @@ -2509,7 +2556,7 @@ void RetainCountChecker::checkPostStmt(const BlockExpr *BE, // via captured variables, even though captured variables result in a copy // and in implicit increment/decrement of a retain count. SmallVector<const MemRegion*, 10> Regions; - const LocationContext *LC = C.getPredecessor()->getLocationContext(); + const LocationContext *LC = C.getLocationContext(); MemRegionManager &MemMgr = C.getSValBuilder().getRegionManager(); for ( ; I != E; ++I) { @@ -2546,8 +2593,8 @@ void RetainCountChecker::checkPostStmt(const CastExpr *CE, break; } - const ProgramState *state = C.getState(); - SymbolRef Sym = state->getSVal(CE).getAsLocSymbol(); + ProgramStateRef state = C.getState(); + SymbolRef Sym = state->getSVal(CE, C.getLocationContext()).getAsLocSymbol(); if (!Sym) return; const RefVal* T = state->get<RefBindings>(Sym); @@ -2563,15 +2610,18 @@ void RetainCountChecker::checkPostStmt(const CastExpr *CE, return; } - C.generateNode(state); + C.addTransition(state); } void RetainCountChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const { + if (C.wasInlined) + return; + // Get the callee. - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); const Expr *Callee = CE->getCallee(); - SVal L = state->getSVal(Callee); + SVal L = state->getSVal(Callee, C.getLocationContext()); RetainSummaryManager &Summaries = getSummaryManager(C); const RetainSummary *Summ = 0; @@ -2591,7 +2641,7 @@ void RetainCountChecker::checkPostStmt(const CallExpr *CE, if (!Summ) Summ = Summaries.getDefaultSummary(); - checkSummary(*Summ, CallOrObjCMessage(CE, state), C); + checkSummary(*Summ, CallOrObjCMessage(CE, state, C.getLocationContext()), C); } void RetainCountChecker::checkPostStmt(const CXXConstructExpr *CE, @@ -2607,20 +2657,62 @@ void RetainCountChecker::checkPostStmt(const CXXConstructExpr *CE, if (!Summ) return; - const ProgramState *state = C.getState(); - checkSummary(*Summ, CallOrObjCMessage(CE, state), C); + ProgramStateRef state = C.getState(); + checkSummary(*Summ, CallOrObjCMessage(CE, state, C.getLocationContext()), C); +} + +void RetainCountChecker::processObjCLiterals(CheckerContext &C, + const Expr *Ex) const { + ProgramStateRef state = C.getState(); + const ExplodedNode *pred = C.getPredecessor(); + for (Stmt::const_child_iterator it = Ex->child_begin(), et = Ex->child_end() ; + it != et ; ++it) { + const Stmt *child = *it; + SVal V = state->getSVal(child, pred->getLocationContext()); + if (SymbolRef sym = V.getAsSymbol()) + if (const RefVal* T = state->get<RefBindings>(sym)) { + RefVal::Kind hasErr = (RefVal::Kind) 0; + state = updateSymbol(state, sym, *T, MayEscape, hasErr, C); + if (hasErr) { + processNonLeakError(state, child->getSourceRange(), hasErr, sym, C); + return; + } + } + } + + // Return the object as autoreleased. + // RetEffect RE = RetEffect::MakeNotOwned(RetEffect::ObjC); + if (SymbolRef sym = + state->getSVal(Ex, pred->getLocationContext()).getAsSymbol()) { + QualType ResultTy = Ex->getType(); + state = state->set<RefBindings>(sym, RefVal::makeNotOwned(RetEffect::ObjC, + ResultTy)); + } + + C.addTransition(state); +} + +void RetainCountChecker::checkPostStmt(const ObjCArrayLiteral *AL, + CheckerContext &C) const { + // Apply the 'MayEscape' to all values. + processObjCLiterals(C, AL); +} + +void RetainCountChecker::checkPostStmt(const ObjCDictionaryLiteral *DL, + CheckerContext &C) const { + // Apply the 'MayEscape' to all keys and values. + processObjCLiterals(C, DL); } void RetainCountChecker::checkPostObjCMessage(const ObjCMessage &Msg, CheckerContext &C) const { - const ProgramState *state = C.getState(); - ExplodedNode *Pred = C.getPredecessor(); + ProgramStateRef state = C.getState(); RetainSummaryManager &Summaries = getSummaryManager(C); const RetainSummary *Summ; if (Msg.isInstanceMessage()) { - const LocationContext *LC = Pred->getLocationContext(); + const LocationContext *LC = C.getLocationContext(); Summ = Summaries.getInstanceMethodSummary(Msg, state, LC); } else { Summ = Summaries.getClassMethodSummary(Msg); @@ -2630,7 +2722,7 @@ void RetainCountChecker::checkPostObjCMessage(const ObjCMessage &Msg, if (!Summ) return; - checkSummary(*Summ, CallOrObjCMessage(Msg, state), C); + checkSummary(*Summ, CallOrObjCMessage(Msg, state, C.getLocationContext()), C); } /// GetReturnType - Used to get the return type of a message expression or @@ -2664,7 +2756,7 @@ static QualType GetReturnType(const Expr *RetE, ASTContext &Ctx) { void RetainCountChecker::checkSummary(const RetainSummary &Summ, const CallOrObjCMessage &CallOrMsg, CheckerContext &C) const { - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); // Evaluate the effect of the arguments. RefVal::Kind hasErr = (RefVal::Kind) 0; @@ -2689,7 +2781,7 @@ void RetainCountChecker::checkSummary(const RetainSummary &Summ, // Evaluate the effect on the message receiver. bool ReceiverIsTracked = false; if (!hasErr && CallOrMsg.isObjCMessage()) { - const LocationContext *LC = C.getPredecessor()->getLocationContext(); + const LocationContext *LC = C.getLocationContext(); SVal Receiver = CallOrMsg.getInstanceMessageReceiver(LC); if (SymbolRef Sym = Receiver.getAsLocSymbol()) { if (const RefVal *T = state->get<RefBindings>(Sym)) { @@ -2722,7 +2814,7 @@ void RetainCountChecker::checkSummary(const RetainSummary &Summ, switch (RE.getKind()) { default: - llvm_unreachable("Unhandled RetEffect."); break; + llvm_unreachable("Unhandled RetEffect."); case RetEffect::NoRet: // No work necessary. @@ -2730,7 +2822,8 @@ void RetainCountChecker::checkSummary(const RetainSummary &Summ, case RetEffect::OwnedAllocatedSymbol: case RetEffect::OwnedSymbol: { - SymbolRef Sym = state->getSVal(CallOrMsg.getOriginExpr()).getAsSymbol(); + SymbolRef Sym = state->getSVal(CallOrMsg.getOriginExpr(), + C.getLocationContext()).getAsSymbol(); if (!Sym) break; @@ -2757,7 +2850,7 @@ void RetainCountChecker::checkSummary(const RetainSummary &Summ, case RetEffect::ARCNotOwnedSymbol: case RetEffect::NotOwnedSymbol: { const Expr *Ex = CallOrMsg.getOriginExpr(); - SymbolRef Sym = state->getSVal(Ex).getAsSymbol(); + SymbolRef Sym = state->getSVal(Ex, C.getLocationContext()).getAsSymbol(); if (!Sym) break; @@ -2776,7 +2869,7 @@ void RetainCountChecker::checkSummary(const RetainSummary &Summ, if (state == C.getState()) { NewNode = C.getPredecessor(); } else { - NewNode = C.generateNode(state); + NewNode = C.addTransition(state); } // Annotate the node with summary we used. @@ -2791,15 +2884,15 @@ void RetainCountChecker::checkSummary(const RetainSummary &Summ, } -const ProgramState * -RetainCountChecker::updateSymbol(const ProgramState *state, SymbolRef sym, +ProgramStateRef +RetainCountChecker::updateSymbol(ProgramStateRef state, SymbolRef sym, RefVal V, ArgEffect E, RefVal::Kind &hasErr, CheckerContext &C) const { // In GC mode [... release] and [... retain] do nothing. // In ARC mode they shouldn't exist at all, but we just ignore them. bool IgnoreRetainMsg = C.isObjCGCEnabled(); if (!IgnoreRetainMsg) - IgnoreRetainMsg = (bool)C.getASTContext().getLangOptions().ObjCAutoRefCount; + IgnoreRetainMsg = (bool)C.getASTContext().getLangOpts().ObjCAutoRefCount; switch (E) { default: break; @@ -2822,7 +2915,6 @@ RetainCountChecker::updateSymbol(const ProgramState *state, SymbolRef sym, case IncRefMsg: case MakeCollectable: llvm_unreachable("DecRefMsg/IncRefMsg/MakeCollectable already converted"); - return state; case Dealloc: // Any use of -dealloc in GC is *bad*. @@ -2835,7 +2927,6 @@ RetainCountChecker::updateSymbol(const ProgramState *state, SymbolRef sym, switch (V.getKind()) { default: llvm_unreachable("Invalid RefVal state for an explicit dealloc."); - break; case RefVal::Owned: // The object immediately transitions to the released state. V = V ^ RefVal::Released; @@ -2879,7 +2970,6 @@ RetainCountChecker::updateSymbol(const ProgramState *state, SymbolRef sym, switch (V.getKind()) { default: llvm_unreachable("Invalid RefVal state for a retain."); - break; case RefVal::Owned: case RefVal::NotOwned: V = V + 1; @@ -2901,7 +2991,6 @@ RetainCountChecker::updateSymbol(const ProgramState *state, SymbolRef sym, default: // case 'RefVal::Released' handled above. llvm_unreachable("Invalid RefVal state for a release."); - break; case RefVal::Owned: assert(V.getCount() > 0); @@ -2932,7 +3021,7 @@ RetainCountChecker::updateSymbol(const ProgramState *state, SymbolRef sym, return state->set<RefBindings>(sym, V); } -void RetainCountChecker::processNonLeakError(const ProgramState *St, +void RetainCountChecker::processNonLeakError(ProgramStateRef St, SourceRange ErrorRange, RefVal::Kind ErrorKind, SymbolRef Sym, @@ -2945,7 +3034,6 @@ void RetainCountChecker::processNonLeakError(const ProgramState *St, switch (ErrorKind) { default: llvm_unreachable("Unhandled error."); - return; case RefVal::ErrorUseAfterRelease: if (!useAfterRelease) useAfterRelease.reset(new UseAfterRelease()); @@ -2969,7 +3057,7 @@ void RetainCountChecker::processNonLeakError(const ProgramState *St, } assert(BT); - CFRefReport *report = new CFRefReport(*BT, C.getASTContext().getLangOptions(), + CFRefReport *report = new CFRefReport(*BT, C.getASTContext().getLangOpts(), C.isObjCGCEnabled(), SummaryLog, N, Sym); report->addRange(ErrorRange); @@ -2982,11 +3070,8 @@ void RetainCountChecker::processNonLeakError(const ProgramState *St, bool RetainCountChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { // Get the callee. We're only interested in simple C functions. - const ProgramState *state = C.getState(); - const Expr *Callee = CE->getCallee(); - SVal L = state->getSVal(Callee); - - const FunctionDecl *FD = L.getAsFunctionDecl(); + ProgramStateRef state = C.getState(); + const FunctionDecl *FD = C.getCalleeDecl(CE); if (!FD) return false; @@ -3008,7 +3093,7 @@ bool RetainCountChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { // See if it's one of the specific functions we know how to eval. bool canEval = false; - QualType ResultTy = FD->getResultType(); + QualType ResultTy = CE->getCallReturnType(); if (ResultTy->isObjCIdType()) { // Handle: id NSMakeCollectable(CFTypeRef) canEval = II->isStr("NSMakeCollectable"); @@ -3026,14 +3111,15 @@ bool RetainCountChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { return false; // Bind the return value. - SVal RetVal = state->getSVal(CE->getArg(0)); + const LocationContext *LCtx = C.getLocationContext(); + SVal RetVal = state->getSVal(CE->getArg(0), LCtx); if (RetVal.isUnknown()) { // If the receiver is unknown, conjure a return value. SValBuilder &SVB = C.getSValBuilder(); unsigned Count = C.getCurrentBlockCount(); - SVal RetVal = SVB.getConjuredSymbolVal(0, CE, ResultTy, Count); + SVal RetVal = SVB.getConjuredSymbolVal(0, CE, LCtx, ResultTy, Count); } - state = state->BindExpr(CE, RetVal, false); + state = state->BindExpr(CE, LCtx, RetVal, false); // FIXME: This should not be necessary, but otherwise the argument seems to be // considered alive during the next statement. @@ -3046,7 +3132,7 @@ bool RetainCountChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { // Invalidate the argument region. unsigned Count = C.getCurrentBlockCount(); - state = state->invalidateRegions(ArgRegion, CE, Count); + state = state->invalidateRegions(ArgRegion, CE, Count, LCtx); // Restore the refcount status of the argument. if (Binding) @@ -3061,14 +3147,30 @@ bool RetainCountChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { // Handle return statements. //===----------------------------------------------------------------------===// +// Return true if the current LocationContext has no caller context. +static bool inTopFrame(CheckerContext &C) { + const LocationContext *LC = C.getLocationContext(); + return LC->getParent() == 0; +} + void RetainCountChecker::checkPreStmt(const ReturnStmt *S, CheckerContext &C) const { + + // Only adjust the reference count if this is the top-level call frame, + // and not the result of inlining. In the future, we should do + // better checking even for inlined calls, and see if they match + // with their expected semantics (e.g., the method should return a retained + // object, etc.). + if (!inTopFrame(C)) + return; + const Expr *RetE = S->getRetValue(); if (!RetE) return; - const ProgramState *state = C.getState(); - SymbolRef Sym = state->getSValAsScalarOrLoc(RetE).getAsLocSymbol(); + ProgramStateRef state = C.getState(); + SymbolRef Sym = + state->getSValAsScalarOrLoc(RetE, C.getLocationContext()).getAsLocSymbol(); if (!Sym) return; @@ -3107,7 +3209,7 @@ void RetainCountChecker::checkPreStmt(const ReturnStmt *S, // Update the binding. state = state->set<RefBindings>(Sym, X); - ExplodedNode *Pred = C.generateNode(state); + ExplodedNode *Pred = C.addTransition(state); // At this point we have updated the state properly. // Everything after this is merely checking to see if the return value has @@ -3121,8 +3223,7 @@ void RetainCountChecker::checkPreStmt(const ReturnStmt *S, static SimpleProgramPointTag AutoreleaseTag("RetainCountChecker : Autorelease"); GenericNodeBuilderRefCount Bd(C, &AutoreleaseTag); - llvm::tie(Pred, state) = handleAutoreleaseCounts(state, Bd, Pred, - C.getEngine(), Sym, X); + llvm::tie(Pred, state) = handleAutoreleaseCounts(state, Bd, Pred, C, Sym, X); // Did we cache out? if (!Pred) @@ -3159,7 +3260,7 @@ void RetainCountChecker::checkReturnWithRetEffect(const ReturnStmt *S, ExplodedNode *Pred, RetEffect RE, RefVal X, SymbolRef Sym, - const ProgramState *state) const { + ProgramStateRef state) const { // Any leaks or other errors? if (X.isReturnedOwned() && X.getCount() == 0) { if (RE.getKind() != RetEffect::NoRet) { @@ -3186,14 +3287,14 @@ void RetainCountChecker::checkReturnWithRetEffect(const ReturnStmt *S, static SimpleProgramPointTag ReturnOwnLeakTag("RetainCountChecker : ReturnsOwnLeak"); - ExplodedNode *N = C.generateNode(state, Pred, &ReturnOwnLeakTag); + ExplodedNode *N = C.addTransition(state, Pred, &ReturnOwnLeakTag); if (N) { - const LangOptions &LOpts = C.getASTContext().getLangOptions(); + const LangOptions &LOpts = C.getASTContext().getLangOpts(); bool GCEnabled = C.isObjCGCEnabled(); CFRefReport *report = new CFRefLeakReport(*getLeakAtReturnBug(LOpts, GCEnabled), LOpts, GCEnabled, SummaryLog, - N, Sym, C.getEngine()); + N, Sym, C); C.EmitReport(report); } } @@ -3206,14 +3307,14 @@ void RetainCountChecker::checkReturnWithRetEffect(const ReturnStmt *S, static SimpleProgramPointTag ReturnNotOwnedTag("RetainCountChecker : ReturnNotOwnedForOwned"); - ExplodedNode *N = C.generateNode(state, Pred, &ReturnNotOwnedTag); + ExplodedNode *N = C.addTransition(state, Pred, &ReturnNotOwnedTag); if (N) { if (!returnNotOwnedForOwned) returnNotOwnedForOwned.reset(new ReturnedNotOwnedForOwned()); CFRefReport *report = new CFRefReport(*returnNotOwnedForOwned, - C.getASTContext().getLangOptions(), + C.getASTContext().getLangOpts(), C.isObjCGCEnabled(), SummaryLog, N, Sym); C.EmitReport(report); } @@ -3236,7 +3337,7 @@ void RetainCountChecker::checkBind(SVal loc, SVal val, const Stmt *S, // (2) we are binding to a memregion that does not have stack storage // (3) we are binding to a memregion with stack storage that the store // does not understand. - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); if (loc::MemRegionVal *regionLoc = dyn_cast<loc::MemRegionVal>(&loc)) { escapes = !regionLoc->getRegion()->hasStackStorage(); @@ -3247,6 +3348,12 @@ void RetainCountChecker::checkBind(SVal loc, SVal val, const Stmt *S, // the binding). escapes = (state == (state->bindLoc(*regionLoc, val))); } + if (!escapes) { + // Case 4: We do not currently model what happens when a symbol is + // assigned to a struct field, so be conservative here and let the symbol + // go. TODO: This could definitely be improved upon. + escapes = !isa<VarRegion>(regionLoc->getRegion()); + } } // If our store can represent the binding and we aren't storing to something @@ -3261,7 +3368,7 @@ void RetainCountChecker::checkBind(SVal loc, SVal val, const Stmt *S, C.addTransition(state); } -const ProgramState *RetainCountChecker::evalAssume(const ProgramState *state, +ProgramStateRef RetainCountChecker::evalAssume(ProgramStateRef state, SVal Cond, bool Assumption) const { @@ -3294,11 +3401,12 @@ const ProgramState *RetainCountChecker::evalAssume(const ProgramState *state, return state; } -const ProgramState * -RetainCountChecker::checkRegionChanges(const ProgramState *state, +ProgramStateRef +RetainCountChecker::checkRegionChanges(ProgramStateRef state, const StoreManager::InvalidatedSymbols *invalidated, ArrayRef<const MemRegion *> ExplicitRegions, - ArrayRef<const MemRegion *> Regions) const { + ArrayRef<const MemRegion *> Regions, + const CallOrObjCMessage *Call) const { if (!invalidated) return state; @@ -3324,10 +3432,11 @@ RetainCountChecker::checkRegionChanges(const ProgramState *state, // Handle dead symbols and end-of-path. //===----------------------------------------------------------------------===// -std::pair<ExplodedNode *, const ProgramState *> -RetainCountChecker::handleAutoreleaseCounts(const ProgramState *state, +std::pair<ExplodedNode *, ProgramStateRef > +RetainCountChecker::handleAutoreleaseCounts(ProgramStateRef state, GenericNodeBuilderRefCount Bd, - ExplodedNode *Pred, ExprEngine &Eng, + ExplodedNode *Pred, + CheckerContext &Ctx, SymbolRef Sym, RefVal V) const { unsigned ACnt = V.getAutoreleaseCount(); @@ -3335,7 +3444,7 @@ RetainCountChecker::handleAutoreleaseCounts(const ProgramState *state, if (!ACnt) return std::make_pair(Pred, state); - assert(!Eng.isObjCGCEnabled() && "Autorelease counts in GC mode?"); + assert(!Ctx.isObjCGCEnabled() && "Autorelease counts in GC mode?"); unsigned Cnt = V.getCount(); // FIXME: Handle sending 'autorelease' to already released object. @@ -3366,10 +3475,8 @@ RetainCountChecker::handleAutoreleaseCounts(const ProgramState *state, V = V ^ RefVal::ErrorOverAutorelease; state = state->set<RefBindings>(Sym, V); - if (ExplodedNode *N = Bd.MakeNode(state, Pred)) { - N->markAsSink(); - - llvm::SmallString<128> sbuf; + if (ExplodedNode *N = Bd.MakeNode(state, Pred, true)) { + SmallString<128> sbuf; llvm::raw_svector_ostream os(sbuf); os << "Object over-autoreleased: object was sent -autorelease "; if (V.getAutoreleaseCount() > 1) @@ -3379,18 +3486,18 @@ RetainCountChecker::handleAutoreleaseCounts(const ProgramState *state, if (!overAutorelease) overAutorelease.reset(new OverAutorelease()); - const LangOptions &LOpts = Eng.getContext().getLangOptions(); + const LangOptions &LOpts = Ctx.getASTContext().getLangOpts(); CFRefReport *report = new CFRefReport(*overAutorelease, LOpts, /* GCEnabled = */ false, SummaryLog, N, Sym, os.str()); - Eng.getBugReporter().EmitReport(report); + Ctx.EmitReport(report); } - return std::make_pair((ExplodedNode *)0, (const ProgramState *)0); + return std::make_pair((ExplodedNode *)0, (ProgramStateRef )0); } -const ProgramState * -RetainCountChecker::handleSymbolDeath(const ProgramState *state, +ProgramStateRef +RetainCountChecker::handleSymbolDeath(ProgramStateRef state, SymbolRef sid, RefVal V, SmallVectorImpl<SymbolRef> &Leaked) const { bool hasLeak = false; @@ -3407,10 +3514,11 @@ RetainCountChecker::handleSymbolDeath(const ProgramState *state, } ExplodedNode * -RetainCountChecker::processLeaks(const ProgramState *state, +RetainCountChecker::processLeaks(ProgramStateRef state, SmallVectorImpl<SymbolRef> &Leaked, GenericNodeBuilderRefCount &Builder, - ExprEngine &Eng, ExplodedNode *Pred) const { + CheckerContext &Ctx, + ExplodedNode *Pred) const { if (Leaked.empty()) return Pred; @@ -3421,51 +3529,58 @@ RetainCountChecker::processLeaks(const ProgramState *state, for (SmallVectorImpl<SymbolRef>::iterator I = Leaked.begin(), E = Leaked.end(); I != E; ++I) { - const LangOptions &LOpts = Eng.getContext().getLangOptions(); - bool GCEnabled = Eng.isObjCGCEnabled(); + const LangOptions &LOpts = Ctx.getASTContext().getLangOpts(); + bool GCEnabled = Ctx.isObjCGCEnabled(); CFRefBug *BT = Pred ? getLeakWithinFunctionBug(LOpts, GCEnabled) : getLeakAtReturnBug(LOpts, GCEnabled); assert(BT && "BugType not initialized."); CFRefLeakReport *report = new CFRefLeakReport(*BT, LOpts, GCEnabled, - SummaryLog, N, *I, Eng); - Eng.getBugReporter().EmitReport(report); + SummaryLog, N, *I, Ctx); + Ctx.EmitReport(report); } } return N; } -void RetainCountChecker::checkEndPath(EndOfFunctionNodeBuilder &Builder, - ExprEngine &Eng) const { - const ProgramState *state = Builder.getState(); - GenericNodeBuilderRefCount Bd(Builder); +void RetainCountChecker::checkEndPath(CheckerContext &Ctx) const { + ProgramStateRef state = Ctx.getState(); + GenericNodeBuilderRefCount Bd(Ctx); RefBindings B = state->get<RefBindings>(); - ExplodedNode *Pred = Builder.getPredecessor(); + ExplodedNode *Pred = Ctx.getPredecessor(); for (RefBindings::iterator I = B.begin(), E = B.end(); I != E; ++I) { - llvm::tie(Pred, state) = handleAutoreleaseCounts(state, Bd, Pred, Eng, + llvm::tie(Pred, state) = handleAutoreleaseCounts(state, Bd, Pred, Ctx, I->first, I->second); if (!state) return; } + // If the current LocationContext has a parent, don't check for leaks. + // We will do that later. + // FIXME: we should instead check for imblances of the retain/releases, + // and suggest annotations. + if (Ctx.getLocationContext()->getParent()) + return; + B = state->get<RefBindings>(); SmallVector<SymbolRef, 10> Leaked; for (RefBindings::iterator I = B.begin(), E = B.end(); I != E; ++I) state = handleSymbolDeath(state, I->first, I->second, Leaked); - processLeaks(state, Leaked, Bd, Eng, Pred); + processLeaks(state, Leaked, Bd, Ctx, Pred); } const ProgramPointTag * RetainCountChecker::getDeadSymbolTag(SymbolRef sym) const { const SimpleProgramPointTag *&tag = DeadSymbolTags[sym]; if (!tag) { - llvm::SmallString<64> buf; + SmallString<64> buf; llvm::raw_svector_ostream out(buf); - out << "RetainCountChecker : Dead Symbol : " << sym->getSymbolID(); + out << "RetainCountChecker : Dead Symbol : "; + sym->dumpToStream(out); tag = new SimpleProgramPointTag(out.str()); } return tag; @@ -3473,10 +3588,9 @@ RetainCountChecker::getDeadSymbolTag(SymbolRef sym) const { void RetainCountChecker::checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const { - ExprEngine &Eng = C.getEngine(); ExplodedNode *Pred = C.getPredecessor(); - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); RefBindings B = state->get<RefBindings>(); // Update counts from autorelease pools @@ -3487,7 +3601,7 @@ void RetainCountChecker::checkDeadSymbols(SymbolReaper &SymReaper, // Use the symbol as the tag. // FIXME: This might not be as unique as we would like. GenericNodeBuilderRefCount Bd(C, getDeadSymbolTag(Sym)); - llvm::tie(Pred, state) = handleAutoreleaseCounts(state, Bd, Pred, Eng, + llvm::tie(Pred, state) = handleAutoreleaseCounts(state, Bd, Pred, C, Sym, *T); if (!state) return; @@ -3505,7 +3619,7 @@ void RetainCountChecker::checkDeadSymbols(SymbolReaper &SymReaper, { GenericNodeBuilderRefCount Bd(C, this); - Pred = processLeaks(state, Leaked, Bd, Eng, Pred); + Pred = processLeaks(state, Leaked, Bd, C, Pred); } // Did we cache out? @@ -3520,7 +3634,7 @@ void RetainCountChecker::checkDeadSymbols(SymbolReaper &SymReaper, B = F.remove(B, *I); state = state->set<RefBindings>(B); - C.generateNode(state, Pred); + C.addTransition(state, Pred); } //===----------------------------------------------------------------------===// @@ -3528,10 +3642,10 @@ void RetainCountChecker::checkDeadSymbols(SymbolReaper &SymReaper, //===----------------------------------------------------------------------===// static void PrintPool(raw_ostream &Out, SymbolRef Sym, - const ProgramState *State) { + ProgramStateRef State) { Out << ' '; if (Sym) - Out << Sym->getSymbolID(); + Sym->dumpToStream(Out); else Out << "<pool>"; Out << ":{"; @@ -3544,14 +3658,14 @@ static void PrintPool(raw_ostream &Out, SymbolRef Sym, Out << '}'; } -static bool UsesAutorelease(const ProgramState *state) { +static bool UsesAutorelease(ProgramStateRef state) { // A state uses autorelease if it allocated an autorelease pool or if it has // objects in the caller's autorelease pool. return !state->get<AutoreleaseStack>().isEmpty() || state->get<AutoreleasePoolContents>(SymbolRef()); } -void RetainCountChecker::printState(raw_ostream &Out, const ProgramState *State, +void RetainCountChecker::printState(raw_ostream &Out, ProgramStateRef State, const char *NL, const char *Sep) const { RefBindings B = State->get<RefBindings>(); diff --git a/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp b/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp index e761bff..6e56593 100644 --- a/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp @@ -25,7 +25,7 @@ using namespace ento; namespace { class ReturnPointerRangeChecker : public Checker< check::PreStmt<ReturnStmt> > { - mutable llvm::OwningPtr<BuiltinBug> BT; + mutable OwningPtr<BuiltinBug> BT; public: void checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const; }; @@ -33,13 +33,13 @@ public: void ReturnPointerRangeChecker::checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const { - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); const Expr *RetE = RS->getRetValue(); if (!RetE) return; - SVal V = state->getSVal(RetE); + SVal V = state->getSVal(RetE, C.getLocationContext()); const MemRegion *R = V.getAsRegion(); const ElementRegion *ER = dyn_cast_or_null<ElementRegion>(R); @@ -58,8 +58,8 @@ void ReturnPointerRangeChecker::checkPreStmt(const ReturnStmt *RS, = C.getStoreManager().getSizeInElements(state, ER->getSuperRegion(), ER->getValueType()); - const ProgramState *StInBound = state->assumeInBound(Idx, NumElements, true); - const ProgramState *StOutBound = state->assumeInBound(Idx, NumElements, false); + ProgramStateRef StInBound = state->assumeInBound(Idx, NumElements, true); + ProgramStateRef StOutBound = state->assumeInBound(Idx, NumElements, false); if (StOutBound && !StInBound) { ExplodedNode *N = C.generateSink(StOutBound); diff --git a/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp b/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp index e8c8d90..7b1f0b1 100644 --- a/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp @@ -25,7 +25,7 @@ using namespace ento; namespace { class ReturnUndefChecker : public Checker< check::PreStmt<ReturnStmt> > { - mutable llvm::OwningPtr<BuiltinBug> BT; + mutable OwningPtr<BuiltinBug> BT; public: void checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const; }; @@ -38,7 +38,7 @@ void ReturnUndefChecker::checkPreStmt(const ReturnStmt *RS, if (!RetE) return; - if (!C.getState()->getSVal(RetE).isUndef()) + if (!C.getState()->getSVal(RetE, C.getLocationContext()).isUndef()) return; ExplodedNode *N = C.generateSink(); @@ -54,7 +54,8 @@ void ReturnUndefChecker::checkPreStmt(const ReturnStmt *RS, new BugReport(*BT, BT->getDescription(), N); report->addRange(RetE->getSourceRange()); - report->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, RetE)); + report->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, RetE, + report)); C.EmitReport(report); } diff --git a/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp b/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp index 91c4b96..54cf569 100644 --- a/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp @@ -26,12 +26,12 @@ using namespace ento; namespace { class StackAddrEscapeChecker : public Checker< check::PreStmt<ReturnStmt>, check::EndPath > { - mutable llvm::OwningPtr<BuiltinBug> BT_stackleak; - mutable llvm::OwningPtr<BuiltinBug> BT_returnstack; + mutable OwningPtr<BuiltinBug> BT_stackleak; + mutable OwningPtr<BuiltinBug> BT_returnstack; public: void checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const; - void checkEndPath(EndOfFunctionNodeBuilder &B, ExprEngine &Eng) const; + void checkEndPath(CheckerContext &Ctx) const; private: void EmitStackError(CheckerContext &C, const MemRegion *R, const Expr *RetE) const; @@ -100,7 +100,7 @@ void StackAddrEscapeChecker::EmitStackError(CheckerContext &C, const MemRegion * new BuiltinBug("Return of address to stack-allocated memory")); // Generate a report for this bug. - llvm::SmallString<512> buf; + SmallString<512> buf; llvm::raw_svector_ostream os(buf); SourceRange range = GenName(os, R, C.getSourceManager()); os << " returned to caller"; @@ -113,45 +113,53 @@ void StackAddrEscapeChecker::EmitStackError(CheckerContext &C, const MemRegion * } void StackAddrEscapeChecker::checkPreStmt(const ReturnStmt *RS, - CheckerContext &C) const { + CheckerContext &C) const { const Expr *RetE = RS->getRetValue(); if (!RetE) return; - SVal V = C.getState()->getSVal(RetE); + SVal V = C.getState()->getSVal(RetE, C.getLocationContext()); const MemRegion *R = V.getAsRegion(); - if (!R || !R->hasStackStorage()) - return; + if (!R) + return; - if (R->hasStackStorage()) { - // Automatic reference counting automatically copies blocks. - if (C.getASTContext().getLangOptions().ObjCAutoRefCount && - isa<BlockDataRegion>(R)) - return; + const StackSpaceRegion *SS = + dyn_cast_or_null<StackSpaceRegion>(R->getMemorySpace()); + + if (!SS) + return; - EmitStackError(C, R, RetE); + // Return stack memory in an ancestor stack frame is fine. + const StackFrameContext *SFC = SS->getStackFrame(); + if (SFC != C.getLocationContext()->getCurrentStackFrame()) + return; + + // Automatic reference counting automatically copies blocks. + if (C.getASTContext().getLangOpts().ObjCAutoRefCount && + isa<BlockDataRegion>(R)) return; - } -} -void StackAddrEscapeChecker::checkEndPath(EndOfFunctionNodeBuilder &B, - ExprEngine &Eng) const { + EmitStackError(C, R, RetE); +} - const ProgramState *state = B.getState(); +void StackAddrEscapeChecker::checkEndPath(CheckerContext &Ctx) const { + ProgramStateRef state = Ctx.getState(); // Iterate over all bindings to global variables and see if it contains // a memory region in the stack space. class CallBack : public StoreManager::BindingsHandler { private: - ExprEngine &Eng; + CheckerContext &Ctx; const StackFrameContext *CurSFC; public: SmallVector<std::pair<const MemRegion*, const MemRegion*>, 10> V; - CallBack(ExprEngine &Eng, const LocationContext *LCtx) - : Eng(Eng), CurSFC(LCtx->getCurrentStackFrame()) {} + CallBack(CheckerContext &CC) : + Ctx(CC), + CurSFC(CC.getLocationContext()->getCurrentStackFrame()) + {} bool HandleBinding(StoreManager &SMgr, Store store, const MemRegion *region, SVal val) { @@ -165,7 +173,7 @@ void StackAddrEscapeChecker::checkEndPath(EndOfFunctionNodeBuilder &B, // Under automated retain release, it is okay to assign a block // directly to a global variable. - if (Eng.getContext().getLangOptions().ObjCAutoRefCount && + if (Ctx.getASTContext().getLangOpts().ObjCAutoRefCount && isa<BlockDataRegion>(vR)) return true; @@ -181,14 +189,14 @@ void StackAddrEscapeChecker::checkEndPath(EndOfFunctionNodeBuilder &B, } }; - CallBack cb(Eng, B.getPredecessor()->getLocationContext()); + CallBack cb(Ctx); state->getStateManager().getStoreManager().iterBindings(state->getStore(),cb); if (cb.V.empty()) return; // Generate an error node. - ExplodedNode *N = B.generateNode(state); + ExplodedNode *N = Ctx.addTransition(state); if (!N) return; @@ -201,10 +209,10 @@ void StackAddrEscapeChecker::checkEndPath(EndOfFunctionNodeBuilder &B, for (unsigned i = 0, e = cb.V.size(); i != e; ++i) { // Generate a report for this bug. - llvm::SmallString<512> buf; + SmallString<512> buf; llvm::raw_svector_ostream os(buf); SourceRange range = GenName(os, cb.V[i].second, - Eng.getContext().getSourceManager()); + Ctx.getSourceManager()); os << " is still referred to by the global variable '"; const VarRegion *VR = cast<VarRegion>(cb.V[i].first->getBaseRegion()); os << *VR->getDecl() @@ -213,7 +221,7 @@ void StackAddrEscapeChecker::checkEndPath(EndOfFunctionNodeBuilder &B, if (range.isValid()) report->addRange(range); - Eng.getBugReporter().EmitReport(report); + Ctx.EmitReport(report); } } diff --git a/lib/StaticAnalyzer/Checkers/StreamChecker.cpp b/lib/StaticAnalyzer/Checkers/StreamChecker.cpp index 1d14e9e..3745d4a 100644 --- a/lib/StaticAnalyzer/Checkers/StreamChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/StreamChecker.cpp @@ -64,7 +64,7 @@ class StreamChecker : public Checker<eval::Call, *II_fwrite, *II_fseek, *II_ftell, *II_rewind, *II_fgetpos, *II_fsetpos, *II_clearerr, *II_feof, *II_ferror, *II_fileno; - mutable llvm::OwningPtr<BuiltinBug> BT_nullfp, BT_illegalwhence, + mutable OwningPtr<BuiltinBug> BT_nullfp, BT_illegalwhence, BT_doubleclose, BT_ResourceLeak; public: @@ -75,7 +75,7 @@ public: bool evalCall(const CallExpr *CE, CheckerContext &C) const; void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; - void checkEndPath(EndOfFunctionNodeBuilder &B, ExprEngine &Eng) const; + void checkEndPath(CheckerContext &Ctx) const; void checkPreStmt(const ReturnStmt *S, CheckerContext &C) const; private: @@ -96,9 +96,9 @@ private: void OpenFileAux(CheckerContext &C, const CallExpr *CE) const; - const ProgramState *CheckNullStream(SVal SV, const ProgramState *state, + ProgramStateRef CheckNullStream(SVal SV, ProgramStateRef state, CheckerContext &C) const; - const ProgramState *CheckDoubleClose(const CallExpr *CE, const ProgramState *state, + ProgramStateRef CheckDoubleClose(const CallExpr *CE, ProgramStateRef state, CheckerContext &C) const; }; @@ -115,10 +115,7 @@ namespace ento { } bool StreamChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { - const ProgramState *state = C.getState(); - const Expr *Callee = CE->getCallee(); - SVal L = state->getSVal(Callee); - const FunctionDecl *FD = L.getAsFunctionDecl(); + const FunctionDecl *FD = C.getCalleeDecl(CE); if (!FD) return false; @@ -221,17 +218,18 @@ void StreamChecker::Tmpfile(CheckerContext &C, const CallExpr *CE) const { } void StreamChecker::OpenFileAux(CheckerContext &C, const CallExpr *CE) const { - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); unsigned Count = C.getCurrentBlockCount(); SValBuilder &svalBuilder = C.getSValBuilder(); + const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); DefinedSVal RetVal = - cast<DefinedSVal>(svalBuilder.getConjuredSymbolVal(0, CE, Count)); - state = state->BindExpr(CE, RetVal); + cast<DefinedSVal>(svalBuilder.getConjuredSymbolVal(0, CE, LCtx, Count)); + state = state->BindExpr(CE, C.getLocationContext(), RetVal); ConstraintManager &CM = C.getConstraintManager(); // Bifurcate the state into two: one with a valid FILE* pointer, the other // with a NULL. - const ProgramState *stateNotNull, *stateNull; + ProgramStateRef stateNotNull, stateNull; llvm::tie(stateNotNull, stateNull) = CM.assumeDual(state, RetVal); if (SymbolRef Sym = RetVal.getAsSymbol()) { @@ -247,29 +245,32 @@ void StreamChecker::OpenFileAux(CheckerContext &C, const CallExpr *CE) const { } void StreamChecker::Fclose(CheckerContext &C, const CallExpr *CE) const { - const ProgramState *state = CheckDoubleClose(CE, C.getState(), C); + ProgramStateRef state = CheckDoubleClose(CE, C.getState(), C); if (state) C.addTransition(state); } void StreamChecker::Fread(CheckerContext &C, const CallExpr *CE) const { - const ProgramState *state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(3)), state, C)) + ProgramStateRef state = C.getState(); + if (!CheckNullStream(state->getSVal(CE->getArg(3), C.getLocationContext()), + state, C)) return; } void StreamChecker::Fwrite(CheckerContext &C, const CallExpr *CE) const { - const ProgramState *state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(3)), state, C)) + ProgramStateRef state = C.getState(); + if (!CheckNullStream(state->getSVal(CE->getArg(3), C.getLocationContext()), + state, C)) return; } void StreamChecker::Fseek(CheckerContext &C, const CallExpr *CE) const { - const ProgramState *state = C.getState(); - if (!(state = CheckNullStream(state->getSVal(CE->getArg(0)), state, C))) + ProgramStateRef state = C.getState(); + if (!(state = CheckNullStream(state->getSVal(CE->getArg(0), + C.getLocationContext()), state, C))) return; // Check the legality of the 'whence' argument of 'fseek'. - SVal Whence = state->getSVal(CE->getArg(2)); + SVal Whence = state->getSVal(CE->getArg(2), C.getLocationContext()); const nonloc::ConcreteInt *CI = dyn_cast<nonloc::ConcreteInt>(&Whence); if (!CI) @@ -279,7 +280,7 @@ void StreamChecker::Fseek(CheckerContext &C, const CallExpr *CE) const { if (x >= 0 && x <= 2) return; - if (ExplodedNode *N = C.generateNode(state)) { + if (ExplodedNode *N = C.addTransition(state)) { if (!BT_illegalwhence) BT_illegalwhence.reset(new BuiltinBug("Illegal whence argument", "The whence argument to fseek() should be " @@ -291,61 +292,69 @@ void StreamChecker::Fseek(CheckerContext &C, const CallExpr *CE) const { } void StreamChecker::Ftell(CheckerContext &C, const CallExpr *CE) const { - const ProgramState *state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(0)), state, C)) + ProgramStateRef state = C.getState(); + if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), + state, C)) return; } void StreamChecker::Rewind(CheckerContext &C, const CallExpr *CE) const { - const ProgramState *state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(0)), state, C)) + ProgramStateRef state = C.getState(); + if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), + state, C)) return; } void StreamChecker::Fgetpos(CheckerContext &C, const CallExpr *CE) const { - const ProgramState *state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(0)), state, C)) + ProgramStateRef state = C.getState(); + if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), + state, C)) return; } void StreamChecker::Fsetpos(CheckerContext &C, const CallExpr *CE) const { - const ProgramState *state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(0)), state, C)) + ProgramStateRef state = C.getState(); + if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), + state, C)) return; } void StreamChecker::Clearerr(CheckerContext &C, const CallExpr *CE) const { - const ProgramState *state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(0)), state, C)) + ProgramStateRef state = C.getState(); + if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), + state, C)) return; } void StreamChecker::Feof(CheckerContext &C, const CallExpr *CE) const { - const ProgramState *state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(0)), state, C)) + ProgramStateRef state = C.getState(); + if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), + state, C)) return; } void StreamChecker::Ferror(CheckerContext &C, const CallExpr *CE) const { - const ProgramState *state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(0)), state, C)) + ProgramStateRef state = C.getState(); + if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), + state, C)) return; } void StreamChecker::Fileno(CheckerContext &C, const CallExpr *CE) const { - const ProgramState *state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(0)), state, C)) + ProgramStateRef state = C.getState(); + if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), + state, C)) return; } -const ProgramState *StreamChecker::CheckNullStream(SVal SV, const ProgramState *state, +ProgramStateRef StreamChecker::CheckNullStream(SVal SV, ProgramStateRef state, CheckerContext &C) const { const DefinedSVal *DV = dyn_cast<DefinedSVal>(&SV); if (!DV) return 0; ConstraintManager &CM = C.getConstraintManager(); - const ProgramState *stateNotNull, *stateNull; + ProgramStateRef stateNotNull, stateNull; llvm::tie(stateNotNull, stateNull) = CM.assumeDual(state, *DV); if (!stateNotNull && stateNull) { @@ -361,10 +370,11 @@ const ProgramState *StreamChecker::CheckNullStream(SVal SV, const ProgramState * return stateNotNull; } -const ProgramState *StreamChecker::CheckDoubleClose(const CallExpr *CE, - const ProgramState *state, +ProgramStateRef StreamChecker::CheckDoubleClose(const CallExpr *CE, + ProgramStateRef state, CheckerContext &C) const { - SymbolRef Sym = state->getSVal(CE->getArg(0)).getAsSymbol(); + SymbolRef Sym = + state->getSVal(CE->getArg(0), C.getLocationContext()).getAsSymbol(); if (!Sym) return state; @@ -399,7 +409,7 @@ void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, for (SymbolReaper::dead_iterator I = SymReaper.dead_begin(), E = SymReaper.dead_end(); I != E; ++I) { SymbolRef Sym = *I; - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); const StreamState *SS = state->get<StreamState>(Sym); if (!SS) return; @@ -418,23 +428,22 @@ void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, } } -void StreamChecker::checkEndPath(EndOfFunctionNodeBuilder &B, - ExprEngine &Eng) const { - const ProgramState *state = B.getState(); +void StreamChecker::checkEndPath(CheckerContext &Ctx) const { + ProgramStateRef state = Ctx.getState(); typedef llvm::ImmutableMap<SymbolRef, StreamState> SymMap; SymMap M = state->get<StreamState>(); for (SymMap::iterator I = M.begin(), E = M.end(); I != E; ++I) { StreamState SS = I->second; if (SS.isOpened()) { - ExplodedNode *N = B.generateNode(state); + ExplodedNode *N = Ctx.addTransition(state); if (N) { if (!BT_ResourceLeak) BT_ResourceLeak.reset(new BuiltinBug("Resource Leak", "Opened File never closed. Potential Resource leak.")); BugReport *R = new BugReport(*BT_ResourceLeak, BT_ResourceLeak->getDescription(), N); - Eng.getBugReporter().EmitReport(R); + Ctx.EmitReport(R); } } } @@ -445,8 +454,8 @@ void StreamChecker::checkPreStmt(const ReturnStmt *S, CheckerContext &C) const { if (!RetE) return; - const ProgramState *state = C.getState(); - SymbolRef Sym = state->getSVal(RetE).getAsSymbol(); + ProgramStateRef state = C.getState(); + SymbolRef Sym = state->getSVal(RetE, C.getLocationContext()).getAsSymbol(); if (!Sym) return; diff --git a/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp b/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp new file mode 100644 index 0000000..1133682 --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp @@ -0,0 +1,62 @@ +//== TaintTesterChecker.cpp ----------------------------------- -*- C++ -*--=// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This checker can be used for testing how taint data is propagated. +// +//===----------------------------------------------------------------------===// +#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" + +using namespace clang; +using namespace ento; + +namespace { +class TaintTesterChecker : public Checker< check::PostStmt<Expr> > { + + mutable OwningPtr<BugType> BT; + void initBugType() const; + + /// Given a pointer argument, get the symbol of the value it contains + /// (points to). + SymbolRef getPointedToSymbol(CheckerContext &C, + const Expr* Arg, + bool IssueWarning = true) const; + +public: + void checkPostStmt(const Expr *E, CheckerContext &C) const; +}; +} + +inline void TaintTesterChecker::initBugType() const { + if (!BT) + BT.reset(new BugType("Tainted data", "General")); +} + +void TaintTesterChecker::checkPostStmt(const Expr *E, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + if (!State) + return; + + if (State->isTainted(E, C.getLocationContext())) { + if (ExplodedNode *N = C.addTransition()) { + initBugType(); + BugReport *report = new BugReport(*BT, "tainted",N); + report->addRange(E->getSourceRange()); + C.EmitReport(report); + } + } +} + +void ento::registerTaintTesterChecker(CheckerManager &mgr) { + mgr.registerChecker<TaintTesterChecker>(); +} diff --git a/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp b/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp index b860b34..a30f6d5 100644 --- a/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp @@ -24,12 +24,14 @@ using namespace ento; namespace { class UndefBranchChecker : public Checker<check::BranchCondition> { - mutable llvm::OwningPtr<BuiltinBug> BT; + mutable OwningPtr<BuiltinBug> BT; struct FindUndefExpr { - const ProgramState *St; + ProgramStateRef St; + const LocationContext *LCtx; - FindUndefExpr(const ProgramState *S) : St(S) {} + FindUndefExpr(ProgramStateRef S, const LocationContext *L) + : St(S), LCtx(L) {} const Expr *FindExpr(const Expr *Ex) { if (!MatchesCriteria(Ex)) @@ -45,25 +47,25 @@ class UndefBranchChecker : public Checker<check::BranchCondition> { return Ex; } - bool MatchesCriteria(const Expr *Ex) { return St->getSVal(Ex).isUndef(); } + bool MatchesCriteria(const Expr *Ex) { + return St->getSVal(Ex, LCtx).isUndef(); + } }; public: - void checkBranchCondition(const Stmt *Condition, BranchNodeBuilder &Builder, - ExprEngine &Eng) const; + void checkBranchCondition(const Stmt *Condition, CheckerContext &Ctx) const; }; } void UndefBranchChecker::checkBranchCondition(const Stmt *Condition, - BranchNodeBuilder &Builder, - ExprEngine &Eng) const { - const ProgramState *state = Builder.getState(); - SVal X = state->getSVal(Condition); + CheckerContext &Ctx) const { + SVal X = Ctx.getState()->getSVal(Condition, Ctx.getLocationContext()); if (X.isUndef()) { - ExplodedNode *N = Builder.generateNode(Condition, state); + // Generate a sink node, which implicitly marks both outgoing branches as + // infeasible. + ExplodedNode *N = Ctx.generateSink(); if (N) { - N->markAsSink(); if (!BT) BT.reset( new BuiltinBug("Branch condition evaluates to a garbage value")); @@ -86,25 +88,22 @@ void UndefBranchChecker::checkBranchCondition(const Stmt *Condition, const Expr *Ex = cast<Expr>(Condition); ExplodedNode *PrevN = *N->pred_begin(); ProgramPoint P = PrevN->getLocation(); - const ProgramState *St = N->getState(); + ProgramStateRef St = N->getState(); if (PostStmt *PS = dyn_cast<PostStmt>(&P)) if (PS->getStmt() == Ex) St = PrevN->getState(); - FindUndefExpr FindIt(St); + FindUndefExpr FindIt(St, Ctx.getLocationContext()); Ex = FindIt.FindExpr(Ex); // Emit the bug report. BugReport *R = new BugReport(*BT, BT->getDescription(), N); - R->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, Ex)); + R->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, Ex, R)); R->addRange(Ex->getSourceRange()); - Eng.getBugReporter().EmitReport(R); + Ctx.EmitReport(R); } - - Builder.markInfeasible(true); - Builder.markInfeasible(false); } } diff --git a/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp b/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp index 2aebed9..d57767e 100644 --- a/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp @@ -17,6 +17,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "llvm/ADT/SmallString.h" #include "llvm/Support/raw_ostream.h" using namespace clang; @@ -25,23 +26,23 @@ using namespace ento; namespace { class UndefCapturedBlockVarChecker : public Checker< check::PostStmt<BlockExpr> > { - mutable llvm::OwningPtr<BugType> BT; + mutable OwningPtr<BugType> BT; public: void checkPostStmt(const BlockExpr *BE, CheckerContext &C) const; }; } // end anonymous namespace -static const BlockDeclRefExpr *FindBlockDeclRefExpr(const Stmt *S, - const VarDecl *VD){ - if (const BlockDeclRefExpr *BR = dyn_cast<BlockDeclRefExpr>(S)) +static const DeclRefExpr *FindBlockDeclRefExpr(const Stmt *S, + const VarDecl *VD) { + if (const DeclRefExpr *BR = dyn_cast<DeclRefExpr>(S)) if (BR->getDecl() == VD) return BR; for (Stmt::const_child_iterator I = S->child_begin(), E = S->child_end(); I!=E; ++I) if (const Stmt *child = *I) { - const BlockDeclRefExpr *BR = FindBlockDeclRefExpr(child, VD); + const DeclRefExpr *BR = FindBlockDeclRefExpr(child, VD); if (BR) return BR; } @@ -55,9 +56,10 @@ UndefCapturedBlockVarChecker::checkPostStmt(const BlockExpr *BE, if (!BE->getBlockDecl()->hasCaptures()) return; - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); const BlockDataRegion *R = - cast<BlockDataRegion>(state->getSVal(BE).getAsRegion()); + cast<BlockDataRegion>(state->getSVal(BE, + C.getLocationContext()).getAsRegion()); BlockDataRegion::referenced_vars_iterator I = R->referenced_vars_begin(), E = R->referenced_vars_end(); @@ -72,7 +74,7 @@ UndefCapturedBlockVarChecker::checkPostStmt(const BlockExpr *BE, continue; // Get the VarRegion associated with VD in the local stack frame. - const LocationContext *LC = C.getPredecessor()->getLocationContext(); + const LocationContext *LC = C.getLocationContext(); VR = C.getSValBuilder().getRegionManager().getVarRegion(VD, LC); SVal VRVal = state->getSVal(VR); @@ -82,7 +84,7 @@ UndefCapturedBlockVarChecker::checkPostStmt(const BlockExpr *BE, BT.reset(new BuiltinBug("uninitialized variable captured by block")); // Generate a bug report. - llvm::SmallString<128> buf; + SmallString<128> buf; llvm::raw_svector_ostream os(buf); os << "Variable '" << VD->getName() diff --git a/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp b/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp index 7ae9668..c3c9ed7 100644 --- a/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp @@ -18,6 +18,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" +#include "llvm/ADT/SmallString.h" using namespace clang; using namespace ento; @@ -26,7 +27,7 @@ namespace { class UndefResultChecker : public Checker< check::PostStmt<BinaryOperator> > { - mutable llvm::OwningPtr<BugType> BT; + mutable OwningPtr<BugType> BT; public: void checkPostStmt(const BinaryOperator *B, CheckerContext &C) const; @@ -35,8 +36,9 @@ public: void UndefResultChecker::checkPostStmt(const BinaryOperator *B, CheckerContext &C) const { - const ProgramState *state = C.getState(); - if (state->getSVal(B).isUndef()) { + ProgramStateRef state = C.getState(); + const LocationContext *LCtx = C.getLocationContext(); + if (state->getSVal(B, LCtx).isUndef()) { // Generate an error node. ExplodedNode *N = C.generateSink(); if (!N) @@ -45,16 +47,16 @@ void UndefResultChecker::checkPostStmt(const BinaryOperator *B, if (!BT) BT.reset(new BuiltinBug("Result of operation is garbage or undefined")); - llvm::SmallString<256> sbuf; + SmallString<256> sbuf; llvm::raw_svector_ostream OS(sbuf); const Expr *Ex = NULL; bool isLeft = true; - if (state->getSVal(B->getLHS()).isUndef()) { + if (state->getSVal(B->getLHS(), LCtx).isUndef()) { Ex = B->getLHS()->IgnoreParenCasts(); isLeft = true; } - else if (state->getSVal(B->getRHS()).isUndef()) { + else if (state->getSVal(B->getRHS(), LCtx).isUndef()) { Ex = B->getRHS()->IgnoreParenCasts(); isLeft = false; } @@ -74,10 +76,12 @@ void UndefResultChecker::checkPostStmt(const BinaryOperator *B, BugReport *report = new BugReport(*BT, OS.str(), N); if (Ex) { report->addRange(Ex->getSourceRange()); - report->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, Ex)); + report->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, Ex, + report)); } else - report->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, B)); + report->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, B, + report)); C.EmitReport(report); } } diff --git a/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp b/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp index bb6831b..0297c4e 100644 --- a/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp @@ -24,7 +24,7 @@ using namespace ento; namespace { class UndefinedArraySubscriptChecker : public Checker< check::PreStmt<ArraySubscriptExpr> > { - mutable llvm::OwningPtr<BugType> BT; + mutable OwningPtr<BugType> BT; public: void checkPreStmt(const ArraySubscriptExpr *A, CheckerContext &C) const; @@ -34,7 +34,7 @@ public: void UndefinedArraySubscriptChecker::checkPreStmt(const ArraySubscriptExpr *A, CheckerContext &C) const { - if (C.getState()->getSVal(A->getIdx()).isUndef()) { + if (C.getState()->getSVal(A->getIdx(), C.getLocationContext()).isUndef()) { if (ExplodedNode *N = C.generateSink()) { if (!BT) BT.reset(new BuiltinBug("Array subscript is undefined")); @@ -43,7 +43,8 @@ UndefinedArraySubscriptChecker::checkPreStmt(const ArraySubscriptExpr *A, BugReport *R = new BugReport(*BT, BT->getName(), N); R->addRange(A->getIdx()->getSourceRange()); R->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, - A->getIdx())); + A->getIdx(), + R)); C.EmitReport(R); } } diff --git a/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp b/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp index 5ca4a9f..78f7fa6 100644 --- a/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp @@ -24,7 +24,7 @@ using namespace ento; namespace { class UndefinedAssignmentChecker : public Checker<check::Bind> { - mutable llvm::OwningPtr<BugType> BT; + mutable OwningPtr<BugType> BT; public: void checkBind(SVal location, SVal val, const Stmt *S, @@ -54,8 +54,8 @@ void UndefinedAssignmentChecker::checkBind(SVal location, SVal val, while (StoreE) { if (const BinaryOperator *B = dyn_cast<BinaryOperator>(StoreE)) { if (B->isCompoundAssignmentOp()) { - const ProgramState *state = C.getState(); - if (state->getSVal(B->getLHS()).isUndef()) { + ProgramStateRef state = C.getState(); + if (state->getSVal(B->getLHS(), C.getLocationContext()).isUndef()) { str = "The left expression of the compound assignment is an " "uninitialized value. The computed value will also be garbage"; ex = B->getLHS(); @@ -78,7 +78,7 @@ void UndefinedAssignmentChecker::checkBind(SVal location, SVal val, BugReport *R = new BugReport(*BT, str, N); if (ex) { R->addRange(ex->getSourceRange()); - R->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, ex)); + R->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, ex, R)); } C.EmitReport(R); } diff --git a/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp b/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp index cec286d..60e665fe 100644 --- a/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp @@ -19,6 +19,8 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/Basic/TargetInfo.h" #include "llvm/ADT/Optional.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringSwitch.h" #include <fcntl.h> @@ -28,7 +30,7 @@ using llvm::Optional; namespace { class UnixAPIChecker : public Checker< check::PreStmt<CallExpr> > { - mutable llvm::OwningPtr<BugType> BT_open, BT_pthreadOnce, BT_mallocZero; + mutable OwningPtr<BugType> BT_open, BT_pthreadOnce, BT_mallocZero; mutable Optional<uint64_t> Val_O_CREAT; public: @@ -36,10 +38,24 @@ public: void CheckOpen(CheckerContext &C, const CallExpr *CE) const; void CheckPthreadOnce(CheckerContext &C, const CallExpr *CE) const; + void CheckCallocZero(CheckerContext &C, const CallExpr *CE) const; void CheckMallocZero(CheckerContext &C, const CallExpr *CE) const; + void CheckReallocZero(CheckerContext &C, const CallExpr *CE) const; + void CheckAllocaZero(CheckerContext &C, const CallExpr *CE) const; + void CheckVallocZero(CheckerContext &C, const CallExpr *CE) const; typedef void (UnixAPIChecker::*SubChecker)(CheckerContext &, const CallExpr *) const; +private: + bool ReportZeroByteAllocation(CheckerContext &C, + ProgramStateRef falseState, + const Expr *arg, + const char *fn_name) const; + void BasicAllocationCheck(CheckerContext &C, + const CallExpr *CE, + const unsigned numArgs, + const unsigned sizeArg, + const char *fn) const; }; } //end anonymous namespace @@ -47,11 +63,11 @@ public: // Utility functions. //===----------------------------------------------------------------------===// -static inline void LazyInitialize(llvm::OwningPtr<BugType> &BT, +static inline void LazyInitialize(OwningPtr<BugType> &BT, const char *name) { if (BT) return; - BT.reset(new BugType(name, "Unix API")); + BT.reset(new BugType(name, categories::UnixAPI)); } //===----------------------------------------------------------------------===// @@ -74,7 +90,7 @@ void UnixAPIChecker::CheckOpen(CheckerContext &C, const CallExpr *CE) const { } // Look at the 'oflags' argument for the O_CREAT flag. - const ProgramState *state = C.getState(); + ProgramStateRef state = C.getState(); if (CE->getNumArgs() < 2) { // The frontend should issue a warning for this case, so this is a sanity @@ -84,7 +100,7 @@ void UnixAPIChecker::CheckOpen(CheckerContext &C, const CallExpr *CE) const { // Now check if oflags has O_CREAT set. const Expr *oflagsEx = CE->getArg(1); - const SVal V = state->getSVal(oflagsEx); + const SVal V = state->getSVal(oflagsEx, C.getLocationContext()); if (!isa<NonLoc>(V)) { // The case where 'V' can be a location can only be due to a bad header, // so in this case bail out. @@ -102,7 +118,7 @@ void UnixAPIChecker::CheckOpen(CheckerContext &C, const CallExpr *CE) const { DefinedSVal maskedFlags = cast<DefinedSVal>(maskedFlagsUC); // Check if maskedFlags is non-zero. - const ProgramState *trueState, *falseState; + ProgramStateRef trueState, falseState; llvm::tie(trueState, falseState) = state->assume(maskedFlags); // Only emit an error if the value of 'maskedFlags' is properly @@ -141,8 +157,9 @@ void UnixAPIChecker::CheckPthreadOnce(CheckerContext &C, // Check if the first argument is stack allocated. If so, issue a warning // because that's likely to be bad news. - const ProgramState *state = C.getState(); - const MemRegion *R = state->getSVal(CE->getArg(0)).getAsRegion(); + ProgramStateRef state = C.getState(); + const MemRegion *R = + state->getSVal(CE->getArg(0), C.getLocationContext()).getAsRegion(); if (!R || !isa<StackSpaceRegion>(R->getMemorySpace())) return; @@ -150,7 +167,7 @@ void UnixAPIChecker::CheckPthreadOnce(CheckerContext &C, if (!N) return; - llvm::SmallString<256> S; + SmallString<256> S; llvm::raw_svector_ostream os(S); os << "Call to 'pthread_once' uses"; if (const VarRegion *VR = dyn_cast<VarRegion>(R)) @@ -170,78 +187,157 @@ void UnixAPIChecker::CheckPthreadOnce(CheckerContext &C, } //===----------------------------------------------------------------------===// -// "malloc" with allocation size 0 +// "calloc", "malloc", "realloc", "alloca" and "valloc" with allocation size 0 //===----------------------------------------------------------------------===// +// FIXME: Eventually these should be rolled into the MallocChecker, but right now +// they're more basic and valuable for widespread use. + +// Returns true if we try to do a zero byte allocation, false otherwise. +// Fills in trueState and falseState. +static bool IsZeroByteAllocation(ProgramStateRef state, + const SVal argVal, + ProgramStateRef *trueState, + ProgramStateRef *falseState) { + llvm::tie(*trueState, *falseState) = + state->assume(cast<DefinedSVal>(argVal)); + + return (*falseState && !*trueState); +} -// FIXME: Eventually this should be rolled into the MallocChecker, but this -// check is more basic and is valuable for widespread use. -void UnixAPIChecker::CheckMallocZero(CheckerContext &C, - const CallExpr *CE) const { +// Generates an error report, indicating that the function whose name is given +// will perform a zero byte allocation. +// Returns false if an error occured, true otherwise. +bool UnixAPIChecker::ReportZeroByteAllocation(CheckerContext &C, + ProgramStateRef falseState, + const Expr *arg, + const char *fn_name) const { + ExplodedNode *N = C.generateSink(falseState); + if (!N) + return false; + + LazyInitialize(BT_mallocZero, + "Undefined allocation of 0 bytes (CERT MEM04-C; CWE-131)"); + + SmallString<256> S; + llvm::raw_svector_ostream os(S); + os << "Call to '" << fn_name << "' has an allocation size of 0 bytes"; + BugReport *report = new BugReport(*BT_mallocZero, os.str(), N); - // Sanity check that malloc takes one argument. - if (CE->getNumArgs() != 1) + report->addRange(arg->getSourceRange()); + report->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, arg, + report)); + C.EmitReport(report); + + return true; +} + +// Does a basic check for 0-sized allocations suitable for most of the below +// functions (modulo "calloc") +void UnixAPIChecker::BasicAllocationCheck(CheckerContext &C, + const CallExpr *CE, + const unsigned numArgs, + const unsigned sizeArg, + const char *fn) const { + // Sanity check for the correct number of arguments + if (CE->getNumArgs() != numArgs) return; // Check if the allocation size is 0. - const ProgramState *state = C.getState(); - SVal argVal = state->getSVal(CE->getArg(0)); + ProgramStateRef state = C.getState(); + ProgramStateRef trueState = NULL, falseState = NULL; + const Expr *arg = CE->getArg(sizeArg); + SVal argVal = state->getSVal(arg, C.getLocationContext()); if (argVal.isUnknownOrUndef()) return; - - const ProgramState *trueState, *falseState; - llvm::tie(trueState, falseState) = state->assume(cast<DefinedSVal>(argVal)); - + // Is the value perfectly constrained to zero? - if (falseState && !trueState) { - ExplodedNode *N = C.generateSink(falseState); - if (!N) - return; - - // FIXME: Add reference to CERT advisory, and/or C99 standard in bug - // output. + if (IsZeroByteAllocation(state, argVal, &trueState, &falseState)) { + (void) ReportZeroByteAllocation(C, falseState, arg, fn); + return; + } + // Assume the the value is non-zero going forward. + assert(trueState); + if (trueState != state) + C.addTransition(trueState); +} - LazyInitialize(BT_mallocZero, "Undefined allocation of 0 bytes"); - - BugReport *report = - new BugReport(*BT_mallocZero, "Call to 'malloc' has an allocation" - " size of 0 bytes", N); - report->addRange(CE->getArg(0)->getSourceRange()); - report->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, - CE->getArg(0))); - C.EmitReport(report); +void UnixAPIChecker::CheckCallocZero(CheckerContext &C, + const CallExpr *CE) const { + unsigned int nArgs = CE->getNumArgs(); + if (nArgs != 2) return; + + ProgramStateRef state = C.getState(); + ProgramStateRef trueState = NULL, falseState = NULL; + + unsigned int i; + for (i = 0; i < nArgs; i++) { + const Expr *arg = CE->getArg(i); + SVal argVal = state->getSVal(arg, C.getLocationContext()); + if (argVal.isUnknownOrUndef()) { + if (i == 0) + continue; + else + return; + } + + if (IsZeroByteAllocation(state, argVal, &trueState, &falseState)) { + if (ReportZeroByteAllocation(C, falseState, arg, "calloc")) + return; + else if (i == 0) + continue; + else + return; + } } + // Assume the the value is non-zero going forward. assert(trueState); - if (trueState != state) { + if (trueState != state) C.addTransition(trueState); - } } - + +void UnixAPIChecker::CheckMallocZero(CheckerContext &C, + const CallExpr *CE) const { + BasicAllocationCheck(C, CE, 1, 0, "malloc"); +} + +void UnixAPIChecker::CheckReallocZero(CheckerContext &C, + const CallExpr *CE) const { + BasicAllocationCheck(C, CE, 2, 1, "realloc"); +} + +void UnixAPIChecker::CheckAllocaZero(CheckerContext &C, + const CallExpr *CE) const { + BasicAllocationCheck(C, CE, 1, 0, "alloca"); +} + +void UnixAPIChecker::CheckVallocZero(CheckerContext &C, + const CallExpr *CE) const { + BasicAllocationCheck(C, CE, 1, 0, "valloc"); +} + + //===----------------------------------------------------------------------===// // Central dispatch function. //===----------------------------------------------------------------------===// -void UnixAPIChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const { - // Get the callee. All the functions we care about are C functions - // with simple identifiers. - const ProgramState *state = C.getState(); - const Expr *Callee = CE->getCallee(); - const FunctionDecl *Fn = state->getSVal(Callee).getAsFunctionDecl(); - - if (!Fn) - return; - - const IdentifierInfo *FI = Fn->getIdentifier(); - if (!FI) +void UnixAPIChecker::checkPreStmt(const CallExpr *CE, + CheckerContext &C) const { + StringRef FName = C.getCalleeName(CE); + if (FName.empty()) return; SubChecker SC = - llvm::StringSwitch<SubChecker>(FI->getName()) + llvm::StringSwitch<SubChecker>(FName) .Case("open", &UnixAPIChecker::CheckOpen) .Case("pthread_once", &UnixAPIChecker::CheckPthreadOnce) + .Case("calloc", &UnixAPIChecker::CheckCallocZero) .Case("malloc", &UnixAPIChecker::CheckMallocZero) + .Case("realloc", &UnixAPIChecker::CheckReallocZero) + .Cases("alloca", "__builtin_alloca", &UnixAPIChecker::CheckAllocaZero) + .Case("valloc", &UnixAPIChecker::CheckVallocZero) .Default(NULL); if (SC) diff --git a/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp b/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp index 459ee65..5a13ed0 100644 --- a/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp @@ -24,7 +24,7 @@ #include "clang/AST/ParentMap.h" #include "clang/Basic/Builtins.h" #include "clang/Basic/SourceManager.h" -#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallSet.h" // The number of CFGBlock pointers we want to reserve memory for. This is used // once for each function we analyze. @@ -54,10 +54,11 @@ void UnreachableCodeChecker::checkEndAnalysis(ExplodedGraph &G, BugReporter &B, ExprEngine &Eng) const { CFGBlocksSet reachable, visited; - + if (Eng.hasWorkRemaining()) return; + const Decl *D = 0; CFG *C = 0; ParentMap *PM = 0; const LocationContext *LC = 0; @@ -67,9 +68,11 @@ void UnreachableCodeChecker::checkEndAnalysis(ExplodedGraph &G, const ProgramPoint &P = I->getLocation(); LC = P.getLocationContext(); + if (!D) + D = LC->getAnalysisDeclContext()->getDecl(); // Save the CFG if we don't have it already if (!C) - C = LC->getAnalysisContext()->getUnoptimizedCFG(); + C = LC->getAnalysisDeclContext()->getUnoptimizedCFG(); if (!PM) PM = &LC->getParentMap(); @@ -80,10 +83,15 @@ void UnreachableCodeChecker::checkEndAnalysis(ExplodedGraph &G, } // Bail out if we didn't get the CFG or the ParentMap. - if (!C || !PM) + if (!D || !C || !PM) return; - - ASTContext &Ctx = B.getContext(); + + // Don't do anything for template instantiations. Proving that code + // in a template instantiation is unreachable means proving that it is + // unreachable in all instantiations. + if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) + if (FD->isTemplateInstantiation()) + return; // Find CFGBlocks that were not covered by any node for (CFG::const_iterator I = C->begin(), E = C->end(); I != E; ++I) { @@ -108,6 +116,14 @@ void UnreachableCodeChecker::checkEndAnalysis(ExplodedGraph &G, if (CB->size() > 0 && isInvalidPath(CB, *PM)) continue; + // It is good practice to always have a "default" label in a "switch", even + // if we should never get there. It can be used to detect errors, for + // instance. Unreachable code directly under a "default" label is therefore + // likely to be a false positive. + if (const Stmt *label = CB->getLabel()) + if (label->getStmtClass() == Stmt::DefaultStmtClass) + continue; + // Special case for __builtin_unreachable. // FIXME: This should be extended to include other unreachable markers, // such as llvm_unreachable. @@ -117,7 +133,7 @@ void UnreachableCodeChecker::checkEndAnalysis(ExplodedGraph &G, ci != ce; ++ci) { if (const CFGStmt *S = (*ci).getAs<CFGStmt>()) if (const CallExpr *CE = dyn_cast<CallExpr>(S->getStmt())) { - if (CE->isBuiltinCall(Ctx) == Builtin::BI__builtin_unreachable) { + if (CE->isBuiltinCall() == Builtin::BI__builtin_unreachable) { foundUnreachable = true; break; } @@ -146,8 +162,8 @@ void UnreachableCodeChecker::checkEndAnalysis(ExplodedGraph &G, if (SM.isInSystemHeader(SL) || SM.isInExternCSystemHeader(SL)) continue; - B.EmitBasicReport("Unreachable code", "Dead code", "This statement is never" - " executed", DL, SR); + B.EmitBasicReport(D, "Unreachable code", "Dead code", + "This statement is never executed", DL, SR); } } diff --git a/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp b/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp index b34b97c..38c9cc1 100644 --- a/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp @@ -20,20 +20,61 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/AST/CharUnits.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/STLExtras.h" using namespace clang; using namespace ento; namespace { class VLASizeChecker : public Checker< check::PreStmt<DeclStmt> > { - mutable llvm::OwningPtr<BugType> BT_zero; - mutable llvm::OwningPtr<BugType> BT_undef; - + mutable OwningPtr<BugType> BT; + enum VLASize_Kind { VLA_Garbage, VLA_Zero, VLA_Tainted }; + + void reportBug(VLASize_Kind Kind, + const Expr *SizeE, + ProgramStateRef State, + CheckerContext &C) const; public: void checkPreStmt(const DeclStmt *DS, CheckerContext &C) const; }; } // end anonymous namespace +void VLASizeChecker::reportBug(VLASize_Kind Kind, + const Expr *SizeE, + ProgramStateRef State, + CheckerContext &C) const { + // Generate an error node. + ExplodedNode *N = C.generateSink(State); + if (!N) + return; + + if (!BT) + BT.reset(new BuiltinBug("Dangerous variable-length array (VLA) declaration")); + + SmallString<256> buf; + llvm::raw_svector_ostream os(buf); + os << "Declared variable-length array (VLA) "; + switch (Kind) { + case VLA_Garbage: + os << "uses a garbage value as its size"; + break; + case VLA_Zero: + os << "has zero size"; + break; + case VLA_Tainted: + os << "has tainted size"; + break; + } + + BugReport *report = new BugReport(*BT, os.str(), N); + report->addRange(SizeE->getSourceRange()); + report->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, SizeE, + report)); + C.EmitReport(report); + return; +} + void VLASizeChecker::checkPreStmt(const DeclStmt *DS, CheckerContext &C) const { if (!DS->isSingleDecl()) return; @@ -49,24 +90,11 @@ void VLASizeChecker::checkPreStmt(const DeclStmt *DS, CheckerContext &C) const { // FIXME: Handle multi-dimensional VLAs. const Expr *SE = VLA->getSizeExpr(); - const ProgramState *state = C.getState(); - SVal sizeV = state->getSVal(SE); + ProgramStateRef state = C.getState(); + SVal sizeV = state->getSVal(SE, C.getLocationContext()); if (sizeV.isUndef()) { - // Generate an error node. - ExplodedNode *N = C.generateSink(); - if (!N) - return; - - if (!BT_undef) - BT_undef.reset(new BuiltinBug("Declared variable-length array (VLA) " - "uses a garbage value as its size")); - - BugReport *report = - new BugReport(*BT_undef, BT_undef->getName(), N); - report->addRange(SE->getSourceRange()); - report->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, SE)); - C.EmitReport(report); + reportBug(VLA_Garbage, SE, state, C); return; } @@ -75,23 +103,20 @@ void VLASizeChecker::checkPreStmt(const DeclStmt *DS, CheckerContext &C) const { if (sizeV.isUnknown()) return; + // Check if the size is tainted. + if (state->isTainted(sizeV)) { + reportBug(VLA_Tainted, SE, 0, C); + return; + } + // Check if the size is zero. DefinedSVal sizeD = cast<DefinedSVal>(sizeV); - const ProgramState *stateNotZero, *stateZero; + ProgramStateRef stateNotZero, stateZero; llvm::tie(stateNotZero, stateZero) = state->assume(sizeD); if (stateZero && !stateNotZero) { - ExplodedNode *N = C.generateSink(stateZero); - if (!BT_zero) - BT_zero.reset(new BuiltinBug("Declared variable-length array (VLA) has " - "zero size")); - - BugReport *report = - new BugReport(*BT_zero, BT_zero->getName(), N); - report->addRange(SE->getSourceRange()); - report->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, SE)); - C.EmitReport(report); + reportBug(VLA_Zero, SE, stateZero, C); return; } @@ -117,7 +142,7 @@ void VLASizeChecker::checkPreStmt(const DeclStmt *DS, CheckerContext &C) const { cast<NonLoc>(EleSizeVal), SizeTy); // Finally, assume that the array's extent matches the given size. - const LocationContext *LC = C.getPredecessor()->getLocationContext(); + const LocationContext *LC = C.getLocationContext(); DefinedOrUnknownSVal Extent = state->getRegion(VD, LC)->getExtent(svalBuilder); DefinedOrUnknownSVal ArraySize = cast<DefinedOrUnknownSVal>(ArraySizeVal); diff --git a/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp b/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp new file mode 100644 index 0000000..f7c7c0c --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp @@ -0,0 +1,241 @@ +//=======- VirtualCallChecker.cpp --------------------------------*- C++ -*-==// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines a checker that checks virtual function calls during +// construction or destruction of C++ objects. +// +//===----------------------------------------------------------------------===// + +#include "ClangSACheckers.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/StmtVisitor.h" +#include "llvm/Support/SaveAndRestore.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" +#include "llvm/ADT/SmallString.h" + +using namespace clang; +using namespace ento; + +namespace { + +class WalkAST : public StmtVisitor<WalkAST> { + BugReporter &BR; + AnalysisDeclContext *AC; + + 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. */ + } K; + + /// 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(BugReporter &br, AnalysisDeclContext *ac) + : BR(br), + AC(ac), + visitingCallExpr(0) {} + + 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); + } + + /// This method returns an item from the worklist without removing it. + WorkListUnit Dequeue() { + assert(!WList.empty()); + return WList.back(); + } + + 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(); + } + } + + // 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); + +}; +} // end anonymous namespace + +//===----------------------------------------------------------------------===// +// AST walking. +//===----------------------------------------------------------------------===// + +void WalkAST::VisitChildren(Stmt *S) { + for (Stmt::child_iterator I = S->child_begin(), E = S->child_end(); I!=E; ++I) + if (Stmt *child = *I) + Visit(child); +} + +void WalkAST::VisitCallExpr(CallExpr *CE) { + VisitChildren(CE); + Enqueue(CE); +} + +void WalkAST::VisitCXXMemberCallExpr(CallExpr *CE) { + VisitChildren(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 (CME->getQualifier()) + callIsNonVirtual = true; + + // Elide analyzing the call entirely if the base pointer is not 'this'. + if (Expr *base = CME->getBase()->IgnoreImpCasts()) + if (!isa<CXXThisExpr>(base)) + return; + } + + // Get the callee. + const CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(CE->getDirectCallee()); + if (MD && MD->isVirtual() && !callIsNonVirtual) + ReportVirtualCall(CE, MD->isPure()); + + Enqueue(CE); +} + +void WalkAST::ReportVirtualCall(const CallExpr *CE, bool isPure) { + SmallString<100> buf; + llvm::raw_svector_ostream os(buf); + + os << "Call Path : "; + // Name of current visiting CallExpr. + os << *CE->getDirectCallee(); + + // Name of the CallExpr whose body is current walking. + 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; + } + + PathDiagnosticLocation CELoc = + PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC); + SourceRange R = CE->getCallee()->getSourceRange(); + + if (isPure) { + os << "\n" << "Call pure virtual functions during construction or " + << "destruction may leads undefined behaviour"; + BR.EmitBasicReport(AC->getDecl(), + "Call pure virtual function during construction or " + "Destruction", + "Cplusplus", + os.str(), CELoc, &R, 1); + return; + } + else { + os << "\n" << "Call virtual functions during construction or " + << "destruction will never go to a more derived class"; + BR.EmitBasicReport(AC->getDecl(), + "Call virtual function during construction or " + "Destruction", + "Cplusplus", + os.str(), CELoc, &R, 1); + return; + } +} + +//===----------------------------------------------------------------------===// +// VirtualCallChecker +//===----------------------------------------------------------------------===// + +namespace { +class VirtualCallChecker : public Checker<check::ASTDecl<CXXRecordDecl> > { +public: + void checkASTDecl(const CXXRecordDecl *RD, AnalysisManager& mgr, + BugReporter &BR) const { + WalkAST walker(BR, mgr.getAnalysisDeclContext(RD)); + + // Check the constructors. + for (CXXRecordDecl::ctor_iterator I = RD->ctor_begin(), E = RD->ctor_end(); + I != E; ++I) { + if (!I->isCopyOrMoveConstructor()) + if (Stmt *Body = I->getBody()) { + walker.Visit(Body); + walker.Execute(); + } + } + + // Check the destructor. + if (CXXDestructorDecl *DD = RD->getDestructor()) + if (Stmt *Body = DD->getBody()) { + walker.Visit(Body); + walker.Execute(); + } + } +}; +} + +void ento::registerVirtualCallChecker(CheckerManager &mgr) { + mgr.registerChecker<VirtualCallChecker>(); +} |