summaryrefslogtreecommitdiffstats
path: root/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/StaticAnalyzer/Checkers/MallocChecker.cpp')
-rw-r--r--lib/StaticAnalyzer/Checkers/MallocChecker.cpp626
1 files changed, 373 insertions, 253 deletions
diff --git a/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
index 8bce88a..dfcedf6 100644
--- a/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
@@ -18,7 +18,7 @@
#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/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
@@ -34,15 +34,21 @@ using namespace ento;
namespace {
class RefState {
- enum Kind { AllocateUnchecked, AllocateFailed, Released, Escaped,
+ enum Kind { // Reference to allocated memory.
+ Allocated,
+ // Reference to released/freed memory.
+ Released,
+ // The responsibility for freeing resources has transfered from
+ // this reference. A relinquished symbol should not be freed.
Relinquished } K;
const Stmt *S;
public:
RefState(Kind k, const Stmt *s) : K(k), S(s) {}
- bool isAllocated() const { return K == AllocateUnchecked; }
+ bool isAllocated() const { return K == Allocated; }
bool isReleased() const { return K == Released; }
+ bool isRelinquished() const { return K == Relinquished; }
const Stmt *getStmt() const { return S; }
@@ -50,14 +56,10 @@ public:
return K == X.K && S == X.S;
}
- static RefState getAllocateUnchecked(const Stmt *s) {
- return RefState(AllocateUnchecked, s);
- }
- static RefState getAllocateFailed() {
- return RefState(AllocateFailed, 0);
+ static RefState getAllocated(const Stmt *s) {
+ return RefState(Allocated, s);
}
static RefState getReleased(const Stmt *s) { return RefState(Released, s); }
- static RefState getEscaped(const Stmt *s) { return RefState(Escaped, s); }
static RefState getRelinquished(const Stmt *s) {
return RefState(Relinquished, s);
}
@@ -90,6 +92,7 @@ class MallocChecker : public Checker<check::DeadSymbols,
check::PreStmt<CallExpr>,
check::PostStmt<CallExpr>,
check::PostStmt<BlockExpr>,
+ check::PreObjCMessage,
check::Location,
check::Bind,
eval::Assume,
@@ -117,6 +120,7 @@ public:
void checkPreStmt(const CallExpr *S, CheckerContext &C) const;
void checkPostStmt(const CallExpr *CE, CheckerContext &C) const;
+ void checkPreObjCMessage(const ObjCMethodCall &Call, CheckerContext &C) const;
void checkPostStmt(const BlockExpr *BE, CheckerContext &C) const;
void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
void checkEndPath(CheckerContext &C) const;
@@ -132,17 +136,22 @@ public:
const StoreManager::InvalidatedSymbols *invalidated,
ArrayRef<const MemRegion *> ExplicitRegions,
ArrayRef<const MemRegion *> Regions,
- const CallOrObjCMessage *Call) const;
+ const CallEvent *Call) const;
bool wantsRegionChangeUpdate(ProgramStateRef state) const {
return true;
}
+ void printState(raw_ostream &Out, ProgramStateRef State,
+ const char *NL, const char *Sep) const;
+
private:
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;
+ bool isFreeFunction(const FunctionDecl *FD, ASTContext &C) const;
+ bool isAllocationFunction(const FunctionDecl *FD, ASTContext &C) const;
static ProgramStateRef MallocMemReturnsAttr(CheckerContext &C,
const CallExpr *CE,
@@ -167,20 +176,26 @@ private:
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 state, unsigned Num,
+ bool Hold) const;
+ ProgramStateRef FreeMemAux(CheckerContext &C, const Expr *Arg,
+ const Expr *ParentExpr,
+ ProgramStateRef state,
+ 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;
+ ///\brief Check if the memory associated with this symbol was released.
+ bool isReleased(SymbolRef Sym, CheckerContext &C) const;
+
bool checkUseAfterFree(SymbolRef Sym, CheckerContext &C,
const Stmt *S = 0) 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,
+ bool doesNotFreeMemory(const CallEvent *Call,
ProgramStateRef State) const;
static bool SummarizeValue(raw_ostream &os, SVal V);
@@ -207,15 +222,17 @@ private:
// 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;
+ // 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;
+ // A symbol from when the primary region should have been reallocated.
+ SymbolRef FailedReallocSymbol;
- public:
- MallocBugVisitor(SymbolRef S)
- : Sym(S), Mode(Normal), FailedReallocSymbol(0) {}
+ bool IsLeak;
+
+ public:
+ MallocBugVisitor(SymbolRef S, bool isLeak = false)
+ : Sym(S), Mode(Normal), FailedReallocSymbol(0), IsLeak(isLeak) {}
virtual ~MallocBugVisitor() {}
@@ -239,6 +256,15 @@ private:
(S && S->isReleased()) && (!SPrev || !SPrev->isReleased()));
}
+ inline bool isRelinquished(const RefState *S, const RefState *SPrev,
+ const Stmt *Stmt) {
+ // Did not track -> relinquished. Other state (allocated) -> relinquished.
+ return (Stmt && (isa<CallExpr>(Stmt) || isa<ObjCMessageExpr>(Stmt) ||
+ isa<ObjCPropertyRefExpr>(Stmt)) &&
+ (S && S->isRelinquished()) &&
+ (!SPrev || !SPrev->isRelinquished()));
+ }
+
inline bool isReallocFailedCheck(const RefState *S, const RefState *SPrev,
const Stmt *Stmt) {
// If the expression is not a call, and the state change is
@@ -253,6 +279,20 @@ private:
const ExplodedNode *PrevN,
BugReporterContext &BRC,
BugReport &BR);
+
+ PathDiagnosticPiece* getEndPath(BugReporterContext &BRC,
+ const ExplodedNode *EndPathNode,
+ BugReport &BR) {
+ if (!IsLeak)
+ return 0;
+
+ PathDiagnosticLocation L =
+ PathDiagnosticLocation::createEndOfPath(EndPathNode,
+ BRC.getSourceManager());
+ // Do not add the statement itself as a range in case of leak.
+ return new PathDiagnosticEventPiece(L, BR.getDescription(), false);
+ }
+
private:
class StackHintGeneratorForReallocationFailed
: public StackHintGeneratorForSymbol {
@@ -315,44 +355,73 @@ public:
} // end anonymous namespace
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 (II_malloc)
+ return;
+ II_malloc = &Ctx.Idents.get("malloc");
+ II_free = &Ctx.Idents.get("free");
+ II_realloc = &Ctx.Idents.get("realloc");
+ II_reallocf = &Ctx.Idents.get("reallocf");
+ II_calloc = &Ctx.Idents.get("calloc");
+ II_valloc = &Ctx.Idents.get("valloc");
+ II_strdup = &Ctx.Idents.get("strdup");
+ II_strndup = &Ctx.Idents.get("strndup");
}
bool MallocChecker::isMemFunction(const FunctionDecl *FD, ASTContext &C) const {
+ if (isFreeFunction(FD, C))
+ return true;
+
+ if (isAllocationFunction(FD, C))
+ return true;
+
+ return false;
+}
+
+bool MallocChecker::isAllocationFunction(const FunctionDecl *FD,
+ ASTContext &C) const {
if (!FD)
return false;
- IdentifierInfo *FunI = FD->getIdentifier();
- if (!FunI)
- return false;
- initIdentifierInfo(C);
+ if (FD->getKind() == Decl::Function) {
+ IdentifierInfo *FunI = FD->getIdentifier();
+ initIdentifierInfo(C);
- 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 (FunI == II_malloc || FunI == II_realloc ||
+ FunI == II_reallocf || FunI == II_calloc || FunI == II_valloc ||
+ FunI == II_strdup || FunI == II_strndup)
+ return true;
+ }
- if (Filter.CMallocOptimistic && FD->hasAttrs() &&
- FD->specific_attr_begin<OwnershipAttr>() !=
- FD->specific_attr_end<OwnershipAttr>())
- return true;
+ if (Filter.CMallocOptimistic && FD->hasAttrs())
+ for (specific_attr_iterator<OwnershipAttr>
+ i = FD->specific_attr_begin<OwnershipAttr>(),
+ e = FD->specific_attr_end<OwnershipAttr>();
+ i != e; ++i)
+ if ((*i)->getOwnKind() == OwnershipAttr::Returns)
+ return true;
+ return false;
+}
+
+bool MallocChecker::isFreeFunction(const FunctionDecl *FD, ASTContext &C) const {
+ if (!FD)
+ return false;
+
+ if (FD->getKind() == Decl::Function) {
+ IdentifierInfo *FunI = FD->getIdentifier();
+ initIdentifierInfo(C);
+ if (FunI == II_free || FunI == II_realloc || FunI == II_reallocf)
+ return true;
+ }
+ if (Filter.CMallocOptimistic && FD->hasAttrs())
+ for (specific_attr_iterator<OwnershipAttr>
+ i = FD->specific_attr_begin<OwnershipAttr>(),
+ e = FD->specific_attr_end<OwnershipAttr>();
+ i != e; ++i)
+ if ((*i)->getOwnKind() == OwnershipAttr::Takes ||
+ (*i)->getOwnKind() == OwnershipAttr::Holds)
+ return true;
return false;
}
@@ -361,29 +430,32 @@ void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const {
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) {
+
+ if (FD->getKind() == Decl::Function) {
+ initIdentifierInfo(C.getASTContext());
+ IdentifierInfo *FunI = FD->getIdentifier();
+
+ 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, State, 0, false);
+ } else if (FunI == II_strdup) {
+ State = MallocUpdateRefState(C, CE, State);
+ } else if (FunI == II_strndup) {
+ State = MallocUpdateRefState(C, CE, State);
+ }
+ }
+
+ if (Filter.CMallocOptimistic) {
// Check all the attributes, if there are any.
// There can be multiple of these attributes.
if (FD->hasAttrs())
@@ -405,6 +477,34 @@ void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const {
C.addTransition(State);
}
+static bool isFreeWhenDoneSetToZero(const ObjCMethodCall &Call) {
+ Selector S = Call.getSelector();
+ for (unsigned i = 1; i < S.getNumArgs(); ++i)
+ if (S.getNameForSlot(i).equals("freeWhenDone"))
+ if (Call.getArgSVal(i).isConstant(0))
+ return true;
+
+ return false;
+}
+
+void MallocChecker::checkPreObjCMessage(const ObjCMethodCall &Call,
+ CheckerContext &C) const {
+ // If the first selector is dataWithBytesNoCopy, assume that the memory will
+ // be released with 'free' by the new object.
+ // Ex: [NSData dataWithBytesNoCopy:bytes length:10];
+ // Unless 'freeWhenDone' param set to 0.
+ // TODO: Check that the memory was allocated with malloc.
+ Selector S = Call.getSelector();
+ if ((S.getNameForSlot(0) == "dataWithBytesNoCopy" ||
+ S.getNameForSlot(0) == "initWithBytesNoCopy" ||
+ S.getNameForSlot(0) == "initWithCharactersNoCopy") &&
+ !isFreeWhenDoneSetToZero(Call)){
+ unsigned int argIdx = 0;
+ C.addTransition(FreeMemAux(C, Call.getArgExpr(argIdx),
+ Call.getOriginExpr(), C.getState(), true));
+ }
+}
+
ProgramStateRef MallocChecker::MallocMemReturnsAttr(CheckerContext &C,
const CallExpr *CE,
const OwnershipAttr* Att) {
@@ -422,19 +522,27 @@ ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C,
const CallExpr *CE,
SVal Size, SVal Init,
ProgramStateRef state) {
- // Get the return value.
- SVal retVal = state->getSVal(CE, C.getLocationContext());
+
+ // Bind the return value to the symbolic value from the heap region.
+ // TODO: We could rewrite post visit to eval call; 'malloc' does not have
+ // side effects other than what we model here.
+ unsigned Count = C.getCurrentBlockCount();
+ SValBuilder &svalBuilder = C.getSValBuilder();
+ const LocationContext *LCtx = C.getPredecessor()->getLocationContext();
+ DefinedSVal RetVal =
+ cast<DefinedSVal>(svalBuilder.getConjuredHeapSymbolVal(CE, LCtx, Count));
+ state = state->BindExpr(CE, C.getLocationContext(), RetVal);
// We expect the malloc functions to return a pointer.
- if (!isa<Loc>(retVal))
+ if (!isa<Loc>(RetVal))
return 0;
// Fill the region with the initialization value.
- state = state->bindDefault(retVal, Init);
+ state = state->bindDefault(RetVal, Init);
// Set the region's extent equal to the Size parameter.
const SymbolicRegion *R =
- dyn_cast_or_null<SymbolicRegion>(retVal.getAsRegion());
+ dyn_cast_or_null<SymbolicRegion>(RetVal.getAsRegion());
if (!R)
return 0;
if (isa<DefinedOrUnknownSVal>(Size)) {
@@ -465,7 +573,7 @@ ProgramStateRef MallocChecker::MallocUpdateRefState(CheckerContext &C,
assert(Sym);
// Set the symbol's state to Allocated.
- return state->set<RegionState>(Sym, RefState::getAllocateUnchecked(CE));
+ return state->set<RegionState>(Sym, RefState::getAllocated(CE));
}
@@ -495,7 +603,15 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C,
if (CE->getNumArgs() < (Num + 1))
return 0;
- const Expr *ArgExpr = CE->getArg(Num);
+ return FreeMemAux(C, CE->getArg(Num), CE, state, Hold);
+}
+
+ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C,
+ const Expr *ArgExpr,
+ const Expr *ParentExpr,
+ ProgramStateRef state,
+ bool Hold) const {
+
SVal ArgVal = state->getSVal(ArgExpr, C.getLocationContext());
if (!isa<DefinedOrUnknownSVal>(ArgVal))
return 0;
@@ -558,20 +674,15 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C,
SymbolRef Sym = SR->getSymbol();
const RefState *RS = state->get<RegionState>(Sym);
- // If the symbol has not been tracked, return. This is possible when free() is
- // called on a pointer that does not get its pointee directly from malloc().
- // Full support of this requires inter-procedural analysis.
- if (!RS)
- return 0;
-
// Check double free.
- if (RS->isReleased()) {
+ if (RS && (RS->isReleased() || RS->isRelinquished())) {
if (ExplodedNode *N = C.generateSink()) {
if (!BT_DoubleFree)
BT_DoubleFree.reset(
new BugType("Double free", "Memory Error"));
BugReport *R = new BugReport(*BT_DoubleFree,
- "Attempt to free released memory", N);
+ (RS->isReleased() ? "Attempt to free released memory" :
+ "Attempt to free non-owned memory"), N);
R->addRange(ArgExpr->getSourceRange());
R->markInteresting(Sym);
R->addVisitor(new MallocBugVisitor(Sym));
@@ -582,8 +693,8 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C,
// Normal free.
if (Hold)
- return state->set<RegionState>(Sym, RefState::getRelinquished(CE));
- return state->set<RegionState>(Sym, RefState::getReleased(CE));
+ return state->set<RegionState>(Sym, RefState::getRelinquished(ParentExpr));
+ return state->set<RegionState>(Sym, RefState::getReleased(ParentExpr));
}
bool MallocChecker::SummarizeValue(raw_ostream &os, SVal V) {
@@ -780,10 +891,8 @@ ProgramStateRef MallocChecker::ReallocMem(CheckerContext &C,
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);
+ // to free() is returned. We just free the input pointer and do not add
+ // any constrains on the output pointer.
return stateFree;
}
@@ -851,8 +960,10 @@ MallocChecker::getAllocationSite(const ExplodedNode *N, SymbolRef Sym,
ProgramPoint P = AllocNode->getLocation();
const Stmt *AllocationStmt = 0;
- if (isa<StmtPoint>(P))
- AllocationStmt = cast<StmtPoint>(P).getStmt();
+ if (CallExitEnd *Exit = dyn_cast<CallExitEnd>(&P))
+ AllocationStmt = Exit->getCalleeContext()->getCallSite();
+ else if (StmtPoint *SP = dyn_cast<StmtPoint>(&P))
+ AllocationStmt = SP->getStmt();
return LeakInfo(AllocationStmt, ReferenceRegion);
}
@@ -884,15 +995,15 @@ void MallocChecker::reportLeak(SymbolRef Sym, ExplodedNode *N,
SmallString<200> buf;
llvm::raw_svector_ostream os(buf);
os << "Memory is never released; potential leak";
- if (Region) {
+ if (Region && Region->canPrintPretty()) {
os << " of memory pointed to by '";
- Region->dumpPretty(os);
- os <<'\'';
+ Region->printPretty(os);
+ os << '\'';
}
BugReport *R = new BugReport(*BT_Leak, os.str(), N, LocUsedForUniqueing);
R->markInteresting(Sym);
- R->addVisitor(new MallocBugVisitor(Sym));
+ R->addVisitor(new MallocBugVisitor(Sym, true));
C.EmitReport(R);
}
@@ -960,23 +1071,9 @@ void MallocChecker::checkEndPath(CheckerContext &C) const {
}
}
-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()))
+ // We will check for double free in the post visit.
+ if (isFreeFunction(C.getCalleeDecl(CE), C.getASTContext()))
return;
// Check use after free, when a freed pointer is passed to a call.
@@ -1000,7 +1097,8 @@ void MallocChecker::checkPreStmt(const ReturnStmt *S, CheckerContext &C) const {
return;
// Check if we are returning a symbol.
- SVal RetVal = C.getState()->getSVal(E, C.getLocationContext());
+ ProgramStateRef State = C.getState();
+ SVal RetVal = State->getSVal(E, C.getLocationContext());
SymbolRef Sym = RetVal.getAsSymbol();
if (!Sym)
// If we are returning a field of the allocated struct or an array element,
@@ -1011,16 +1109,18 @@ void MallocChecker::checkPreStmt(const ReturnStmt *S, CheckerContext &C) const {
if (const SymbolicRegion *BMR =
dyn_cast<SymbolicRegion>(MR->getBaseRegion()))
Sym = BMR->getSymbol();
- if (!Sym)
- return;
// Check if we are returning freed memory.
- if (checkUseAfterFree(Sym, C, E))
- return;
+ if (Sym)
+ if (checkUseAfterFree(Sym, C, E))
+ return;
- // If this function body is not inlined, check if the symbol is escaping.
- if (C.getLocationContext()->getParent() == 0)
- checkEscape(Sym, E, C);
+ // If this function body is not inlined, stop tracking any returned symbols.
+ if (C.getLocationContext()->getParent() == 0) {
+ State =
+ State->scanReachableSymbols<StopTrackingCallback>(RetVal).getState();
+ C.addTransition(State);
+ }
}
// TODO: Blocks should be either inlined or should call invalidate regions
@@ -1063,11 +1163,15 @@ void MallocChecker::checkPostStmt(const BlockExpr *BE,
C.addTransition(state);
}
-bool MallocChecker::checkUseAfterFree(SymbolRef Sym, CheckerContext &C,
- const Stmt *S) const {
+bool MallocChecker::isReleased(SymbolRef Sym, CheckerContext &C) const {
assert(Sym);
const RefState *RS = C.getState()->get<RegionState>(Sym);
- if (RS && RS->isReleased()) {
+ return (RS && RS->isReleased());
+}
+
+bool MallocChecker::checkUseAfterFree(SymbolRef Sym, CheckerContext &C,
+ const Stmt *S) const {
+ if (isReleased(Sym, C)) {
if (ExplodedNode *N = C.generateSink()) {
if (!BT_UseFree)
BT_UseFree.reset(new BugType("Use-after-free", "Memory Error"));
@@ -1090,7 +1194,7 @@ void MallocChecker::checkLocation(SVal l, bool isLoad, const Stmt *S,
CheckerContext &C) const {
SymbolRef Sym = l.getLocSymbolInBase();
if (Sym)
- checkUseAfterFree(Sym, C);
+ checkUseAfterFree(Sym, C, S);
}
//===----------------------------------------------------------------------===//
@@ -1118,13 +1222,11 @@ void MallocChecker::checkBind(SVal loc, SVal val, const Stmt *S,
// 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());
+ // Do this only if we know that the store is not supposed to generate the
+ // same state.
+ SVal StoredVal = state->getSVal(regionLoc->getRegion());
+ if (StoredVal != val)
+ escapes = (state == (state->bindLoc(*regionLoc, val)));
}
}
@@ -1165,7 +1267,7 @@ ProgramStateRef MallocChecker::evalAssume(ProgramStateRef state,
if (RS) {
if (RS->isReleased() && ! I.getData().IsFreeOnFailure)
state = state->set<RegionState>(ReallocSym,
- RefState::getAllocateUnchecked(RS->getStmt()));
+ RefState::getAllocated(RS->getStmt()));
}
state = state->remove<ReallocPairs>(I.getKey());
}
@@ -1175,118 +1277,30 @@ ProgramStateRef MallocChecker::evalAssume(ProgramStateRef state,
}
// Check if the function is known to us. So, for example, we could
-// conservatively assume it can free/reallocate it's pointer arguments.
+// conservatively assume it can free/reallocate its 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,
+bool MallocChecker::doesNotFreeMemory(const CallEvent *Call,
ProgramStateRef State) const {
- if (!Call)
- return false;
+ assert(Call);
// 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;
-
- const Decl *D = Call->getDecl();
- if (!D)
+ if (!(isa<FunctionCall>(Call) || isa<ObjCMethodCall>(Call)))
return false;
- 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;
-
- // 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;
- }
- }
- return false;
- }
-
- // PR12101
- // Many CoreFoundation and CoreGraphics might allow a tracked object
- // to escape.
- if (Call->isCFCGAllowingEscape(FName))
+ // Check Objective-C messages by selector name.
+ if (const ObjCMethodCall *Msg = dyn_cast<ObjCMethodCall>(Call)) {
+ // If it's not a framework call, or if it takes a callback, assume it
+ // can free memory.
+ if (!Call->isInSystemHeader() || Call->hasNonZeroCallbackArg())
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();
+ Selector S = Msg->getSelector();
- // White list the ObjC functions which do free memory.
+ // Whitelist the ObjC methods which do free memory.
// - Anything containing 'freeWhenDone' param set to 1.
// Ex: dataWithBytesNoCopy:length:freeWhenDone.
for (unsigned i = 1; i < S.getNumArgs(); ++i) {
@@ -1299,20 +1313,111 @@ bool MallocChecker::doesNotFreeMemory(const CallOrObjCMessage *Call,
}
// If the first selector ends with NoCopy, assume that the ownership is
- // transfered as well.
+ // transferred as well.
// Ex: [NSData dataWithBytesNoCopy:bytes length:10];
- if (S.getNameForSlot(0).endswith("NoCopy")) {
+ StringRef FirstSlot = S.getNameForSlot(0);
+ if (FirstSlot.endswith("NoCopy"))
+ return false;
+
+ // If the first selector starts with addPointer, insertPointer,
+ // or replacePointer, assume we are dealing with NSPointerArray or similar.
+ // This is similar to C++ containers (vector); we still might want to check
+ // that the pointers get freed by following the container itself.
+ if (FirstSlot.startswith("addPointer") ||
+ FirstSlot.startswith("insertPointer") ||
+ FirstSlot.startswith("replacePointer")) {
return false;
}
- // Otherwise, assume that the function does not free memory.
- // Most system calls, do not free the memory.
+ // Otherwise, assume that the method does not free memory.
+ // Most framework methods do not free memory.
return true;
}
- // Otherwise, assume that the function can free memory.
- return false;
+ // At this point the only thing left to handle is straight function calls.
+ const FunctionDecl *FD = cast<FunctionCall>(Call)->getDecl();
+ if (!FD)
+ return false;
+
+ ASTContext &ASTC = State->getStateManager().getContext();
+
+ // If it's one of the allocation functions we can reason about, we model
+ // its behavior explicitly.
+ if (isMemFunction(FD, ASTC))
+ return true;
+ // If it's not a system call, assume it frees memory.
+ if (!Call->isInSystemHeader())
+ return false;
+
+ // White list the system functions whose arguments escape.
+ const IdentifierInfo *II = FD->getIdentifier();
+ if (!II)
+ return false;
+ StringRef FName = II->getName();
+
+ // White list the 'XXXNoCopy' CoreFoundation functions.
+ // We specifically check these before
+ if (FName.endswith("NoCopy")) {
+ // Look for the deallocator argument. We know that the memory ownership
+ // is not transferred only if the deallocator argument is
+ // 'kCFAllocatorNull'.
+ for (unsigned i = 1; i < Call->getNumArgs(); ++i) {
+ const Expr *ArgE = Call->getArgExpr(i)->IgnoreParenCasts();
+ if (const DeclRefExpr *DE = dyn_cast<DeclRefExpr>(ArgE)) {
+ StringRef DeallocatorName = DE->getFoundDecl()->getName();
+ if (DeallocatorName == "kCFAllocatorNull")
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Associating streams with malloced buffers. The pointer can escape if
+ // 'closefn' is specified (and if that function does free memory),
+ // but it will not if closefn is not specified.
+ // Currently, we do not inspect the 'closefn' function (PR12101).
+ if (FName == "funopen")
+ if (Call->getNumArgs() >= 4 && Call->getArgSVal(4).isConstant(0))
+ return true;
+
+ // 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) {
+ const Expr *ArgE = Call->getArgExpr(0)->IgnoreParenCasts();
+ if (const DeclRefExpr *ArgDRE = dyn_cast<DeclRefExpr>(ArgE))
+ if (const VarDecl *D = dyn_cast<VarDecl>(ArgDRE->getDecl()))
+ if (D->getCanonicalDecl()->getName().find("std") != StringRef::npos)
+ return false;
+ }
+ }
+
+ // A bunch of other functions which either take ownership of a pointer or
+ // wrap the result up in a struct or object, meaning it can be freed later.
+ // (See RetainCountChecker.) 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 == "CGBitmapContextCreate" ||
+ FName == "CGBitmapContextCreateWithData" ||
+ FName == "CVPixelBufferCreateWithBytes" ||
+ FName == "CVPixelBufferCreateWithPlanarBytes" ||
+ FName == "OSAtomicEnqueue") {
+ return false;
+ }
+
+ // Handle cases where we know a buffer's /address/ can escape.
+ // Note that the above checks handle some special cases where we know that
+ // even though the address escapes, it's still our responsibility to free the
+ // buffer.
+ if (Call->argumentsMayEscape())
+ return false;
+
+ // Otherwise, assume that the function does not free memory.
+ // Most system calls do not free the memory.
+ return true;
}
// If the symbol we are tracking is invalidated, but not explicitly (ex: the &p
@@ -1323,7 +1428,7 @@ MallocChecker::checkRegionChanges(ProgramStateRef State,
const StoreManager::InvalidatedSymbols *invalidated,
ArrayRef<const MemRegion *> ExplicitRegions,
ArrayRef<const MemRegion *> Regions,
- const CallOrObjCMessage *Call) const {
+ const CallEvent *Call) const {
if (!invalidated || invalidated->empty())
return State;
llvm::SmallPtrSet<SymbolRef, 8> WhitelistedSymbols;
@@ -1345,9 +1450,13 @@ MallocChecker::checkRegionChanges(ProgramStateRef State,
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()));
+ // The symbol escaped. Note, we assume that if the symbol is released,
+ // passing it out will result in a use after free. We also keep tracking
+ // relinquished symbols.
+ if (const RefState *RS = State->get<RegionState>(sym)) {
+ if (RS->isAllocated())
+ State = State->remove<RegionState>(sym);
+ }
}
return State;
}
@@ -1377,7 +1486,7 @@ MallocChecker::MallocBugVisitor::VisitNode(const ExplodedNode *N,
const RefState *RS = state->get<RegionState>(Sym);
const RefState *RSPrev = statePrev->get<RegionState>(Sym);
- if (!RS && !RSPrev)
+ if (!RS)
return 0;
const Stmt *S = 0;
@@ -1386,17 +1495,22 @@ MallocChecker::MallocBugVisitor::VisitNode(const ExplodedNode *N,
// Retrieve the associated statement.
ProgramPoint ProgLoc = N->getLocation();
- if (isa<StmtPoint>(ProgLoc))
- S = cast<StmtPoint>(ProgLoc).getStmt();
+ if (StmtPoint *SP = dyn_cast<StmtPoint>(&ProgLoc))
+ S = SP->getStmt();
+ else if (CallExitEnd *Exit = dyn_cast<CallExitEnd>(&ProgLoc))
+ S = Exit->getCalleeContext()->getCallSite();
// 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();
+ else if (BlockEdge *Edge = dyn_cast<BlockEdge>(&ProgLoc)) {
+ const CFGBlock *srcBlk = Edge->getSrc();
S = srcBlk->getTerminator();
}
if (!S)
return 0;
+ // FIXME: We will eventually need to handle non-statement-based events
+ // (__attribute__((cleanup))).
+
// Find out if this is an interesting point and what is the kind.
if (Mode == Normal) {
if (isAllocated(RS, RSPrev, S)) {
@@ -1407,6 +1521,9 @@ MallocChecker::MallocBugVisitor::VisitNode(const ExplodedNode *N,
Msg = "Memory is released";
StackHint = new StackHintGeneratorForSymbol(Sym,
"Returned released memory");
+ } else if (isRelinquished(RS, RSPrev, S)) {
+ Msg = "Memory ownership is transfered";
+ StackHint = new StackHintGeneratorForSymbol(Sym, "");
} else if (isReallocFailedCheck(RS, RSPrev, S)) {
Mode = ReallocationFailed;
Msg = "Reallocation failed";
@@ -1428,11 +1545,6 @@ MallocChecker::MallocBugVisitor::VisitNode(const ExplodedNode *N,
// 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,
@@ -1452,6 +1564,14 @@ MallocChecker::MallocBugVisitor::VisitNode(const ExplodedNode *N,
return new PathDiagnosticEventPiece(Pos, Msg, true, StackHint);
}
+void MallocChecker::printState(raw_ostream &Out, ProgramStateRef State,
+ const char *NL, const char *Sep) const {
+
+ RegionStateTy RS = State->get<RegionState>();
+
+ if (!RS.isEmpty())
+ Out << "Has Malloc data" << NL;
+}
#define REGISTER_CHECKER(name) \
void ento::register##name(CheckerManager &mgr) {\
OpenPOWER on IntegriCloud