forked from OSchip/llvm-project
[analyzer] MallocChecker: implement pessimistic version of the checker,
which allows values to escape through unknown calls. Assumes all calls but the malloc family are unknown. Also, catch a use-after-free when a pointer is passed to a function after a call to free (previously, you had to explicitly dereference the pointer value). llvm-svn: 150112
This commit is contained in:
parent
cd37bf4ec8
commit
a1b227b6a7
|
@ -131,6 +131,10 @@ private:
|
||||||
void ReallocMem(CheckerContext &C, const CallExpr *CE) const;
|
void ReallocMem(CheckerContext &C, const CallExpr *CE) const;
|
||||||
static void CallocMem(CheckerContext &C, const CallExpr *CE);
|
static void 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;
|
||||||
|
|
||||||
static bool SummarizeValue(raw_ostream &os, SVal V);
|
static bool SummarizeValue(raw_ostream &os, SVal V);
|
||||||
static bool SummarizeRegion(raw_ostream &os, const MemRegion *MR);
|
static bool SummarizeRegion(raw_ostream &os, const MemRegion *MR);
|
||||||
void ReportBadFree(CheckerContext &C, SVal ArgVal, SourceRange range) const;
|
void ReportBadFree(CheckerContext &C, SVal ArgVal, SourceRange range) const;
|
||||||
|
@ -186,6 +190,7 @@ void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Filter.CMallocOptimistic)
|
||||||
// Check all the attributes, if there are any.
|
// Check all the attributes, if there are any.
|
||||||
// There can be multiple of these attributes.
|
// There can be multiple of these attributes.
|
||||||
if (FD->hasAttrs()) {
|
if (FD->hasAttrs()) {
|
||||||
|
@ -206,6 +211,22 @@ void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Filter.CMallocPessimistic) {
|
||||||
|
ProgramStateRef State = C.getState();
|
||||||
|
// The pointer might escape through a function call.
|
||||||
|
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)
|
||||||
|
return;
|
||||||
|
checkEscape(Sym, A, C);
|
||||||
|
checkUseAfterFree(Sym, C, A);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MallocChecker::MallocMem(CheckerContext &C, const CallExpr *CE) {
|
void MallocChecker::MallocMem(CheckerContext &C, const CallExpr *CE) {
|
||||||
|
@ -642,32 +663,36 @@ void MallocChecker::checkEndPath(CheckerContext &Ctx) const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MallocChecker::checkPreStmt(const ReturnStmt *S, CheckerContext &C) const {
|
bool MallocChecker::checkEscape(SymbolRef Sym, const Stmt *S,
|
||||||
const Expr *retExpr = S->getRetValue();
|
CheckerContext &C) const {
|
||||||
if (!retExpr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
ProgramStateRef state = C.getState();
|
ProgramStateRef state = C.getState();
|
||||||
|
const RefState *RS = state->get<RegionState>(Sym);
|
||||||
|
if (!RS)
|
||||||
|
return false;
|
||||||
|
|
||||||
SymbolRef Sym = state->getSVal(retExpr, C.getLocationContext()).getAsSymbol();
|
if (RS->isAllocated()) {
|
||||||
|
state = state->set<RegionState>(Sym, RefState::getEscaped(S));
|
||||||
|
C.addTransition(state);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MallocChecker::checkPreStmt(const ReturnStmt *S, CheckerContext &C) const {
|
||||||
|
const Expr *E = S->getRetValue();
|
||||||
|
if (!E)
|
||||||
|
return;
|
||||||
|
SymbolRef Sym = C.getState()->getSVal(E, C.getLocationContext()).getAsSymbol();
|
||||||
if (!Sym)
|
if (!Sym)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const RefState *RS = state->get<RegionState>(Sym);
|
checkEscape(Sym, S, C);
|
||||||
if (!RS)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// FIXME: check other cases.
|
|
||||||
if (RS->isAllocated())
|
|
||||||
state = state->set<RegionState>(Sym, RefState::getEscaped(S));
|
|
||||||
|
|
||||||
C.addTransition(state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ProgramStateRef MallocChecker::evalAssume(ProgramStateRef state,
|
ProgramStateRef MallocChecker::evalAssume(ProgramStateRef state,
|
||||||
SVal Cond,
|
SVal Cond,
|
||||||
bool Assumption) const {
|
bool Assumption) const {
|
||||||
// If a symblic region is assumed to NULL, set its state to AllocateFailed.
|
// If a symbolic region is assumed to NULL, set its state to AllocateFailed.
|
||||||
// FIXME: should also check symbols assumed to non-null.
|
// FIXME: should also check symbols assumed to non-null.
|
||||||
|
|
||||||
RegionStateTy RS = state->get<RegionState>();
|
RegionStateTy RS = state->get<RegionState>();
|
||||||
|
@ -681,24 +706,32 @@ ProgramStateRef MallocChecker::evalAssume(ProgramStateRef state,
|
||||||
return state;
|
return 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.addTransition()) {
|
||||||
|
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);
|
||||||
|
if (S)
|
||||||
|
R->addRange(S->getSourceRange());
|
||||||
|
C.EmitReport(R);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the location is a freed symbolic region.
|
// Check if the location is a freed symbolic region.
|
||||||
void MallocChecker::checkLocation(SVal l, bool isLoad, const Stmt *S,
|
void MallocChecker::checkLocation(SVal l, bool isLoad, const Stmt *S,
|
||||||
CheckerContext &C) const {
|
CheckerContext &C) const {
|
||||||
SymbolRef Sym = l.getLocSymbolInBase();
|
SymbolRef Sym = l.getLocSymbolInBase();
|
||||||
if (Sym) {
|
if (Sym)
|
||||||
const RefState *RS = C.getState()->get<RegionState>(Sym);
|
checkUseAfterFree(Sym, C);
|
||||||
if (RS && RS->isReleased()) {
|
|
||||||
if (ExplodedNode *N = C.addTransition()) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MallocChecker::checkBind(SVal location, SVal val,
|
void MallocChecker::checkBind(SVal location, SVal val,
|
||||||
|
|
|
@ -4,23 +4,9 @@ void *malloc(size_t);
|
||||||
void free(void *);
|
void free(void *);
|
||||||
void *realloc(void *ptr, size_t size);
|
void *realloc(void *ptr, size_t size);
|
||||||
void *calloc(size_t nmemb, size_t size);
|
void *calloc(size_t nmemb, size_t size);
|
||||||
void __attribute((ownership_returns(malloc))) *my_malloc(size_t);
|
|
||||||
void __attribute((ownership_takes(malloc, 1))) my_free(void *);
|
|
||||||
void __attribute((ownership_returns(malloc, 1))) *my_malloc2(size_t);
|
|
||||||
void __attribute((ownership_holds(malloc, 1))) my_hold(void *);
|
|
||||||
|
|
||||||
// Duplicate attributes are silly, but not an error.
|
void myfoo(int *p);
|
||||||
// Duplicate attribute has no extra effect.
|
void myfooint(int p);
|
||||||
// If two are of different kinds, that is an error and reported as such.
|
|
||||||
void __attribute((ownership_holds(malloc, 1)))
|
|
||||||
__attribute((ownership_holds(malloc, 1)))
|
|
||||||
__attribute((ownership_holds(malloc, 3))) my_hold2(void *, void *, void *);
|
|
||||||
void *my_malloc3(size_t);
|
|
||||||
void *myglobalpointer;
|
|
||||||
struct stuff {
|
|
||||||
void *somefield;
|
|
||||||
};
|
|
||||||
struct stuff myglobalstuff;
|
|
||||||
|
|
||||||
void f1() {
|
void f1() {
|
||||||
int *p = malloc(12);
|
int *p = malloc(12);
|
||||||
|
@ -44,107 +30,6 @@ void f2_realloc_1() {
|
||||||
int *q = realloc(p,0); // no-warning
|
int *q = realloc(p,0); // no-warning
|
||||||
}
|
}
|
||||||
|
|
||||||
// ownership attributes tests
|
|
||||||
void naf1() {
|
|
||||||
int *p = my_malloc3(12);
|
|
||||||
return; // no-warning
|
|
||||||
}
|
|
||||||
|
|
||||||
void n2af1() {
|
|
||||||
int *p = my_malloc2(12);
|
|
||||||
return; // expected-warning{{Allocated memory never released. Potential memory leak.}}
|
|
||||||
}
|
|
||||||
|
|
||||||
void af1() {
|
|
||||||
int *p = my_malloc(12);
|
|
||||||
return; // expected-warning{{Allocated memory never released. Potential memory leak.}}
|
|
||||||
}
|
|
||||||
|
|
||||||
void af1_b() {
|
|
||||||
int *p = my_malloc(12); // expected-warning{{Allocated memory never released. Potential memory leak.}}
|
|
||||||
}
|
|
||||||
|
|
||||||
void af1_c() {
|
|
||||||
myglobalpointer = my_malloc(12); // no-warning
|
|
||||||
}
|
|
||||||
|
|
||||||
void af1_d() {
|
|
||||||
struct stuff mystuff;
|
|
||||||
mystuff.somefield = my_malloc(12); // expected-warning{{Allocated memory never released. Potential memory leak.}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that we can pass out allocated memory via pointer-to-pointer.
|
|
||||||
void af1_e(void **pp) {
|
|
||||||
*pp = my_malloc(42); // no-warning
|
|
||||||
}
|
|
||||||
|
|
||||||
void af1_f(struct stuff *somestuff) {
|
|
||||||
somestuff->somefield = my_malloc(12); // no-warning
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allocating memory for a field via multiple indirections to our arguments is OK.
|
|
||||||
void af1_g(struct stuff **pps) {
|
|
||||||
*pps = my_malloc(sizeof(struct stuff)); // no-warning
|
|
||||||
(*pps)->somefield = my_malloc(42); // no-warning
|
|
||||||
}
|
|
||||||
|
|
||||||
void af2() {
|
|
||||||
int *p = my_malloc(12);
|
|
||||||
my_free(p);
|
|
||||||
free(p); // expected-warning{{Try to free a memory block that has been released}}
|
|
||||||
}
|
|
||||||
|
|
||||||
void af2b() {
|
|
||||||
int *p = my_malloc(12);
|
|
||||||
free(p);
|
|
||||||
my_free(p); // expected-warning{{Try to free a memory block that has been released}}
|
|
||||||
}
|
|
||||||
|
|
||||||
void af2c() {
|
|
||||||
int *p = my_malloc(12);
|
|
||||||
free(p);
|
|
||||||
my_hold(p); // expected-warning{{Try to free a memory block that has been released}}
|
|
||||||
}
|
|
||||||
|
|
||||||
void af2d() {
|
|
||||||
int *p = my_malloc(12);
|
|
||||||
free(p);
|
|
||||||
my_hold2(0, 0, p); // expected-warning{{Try to free a memory block that has been released}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No leak if malloc returns null.
|
|
||||||
void af2e() {
|
|
||||||
int *p = my_malloc(12);
|
|
||||||
if (!p)
|
|
||||||
return; // no-warning
|
|
||||||
free(p); // no-warning
|
|
||||||
}
|
|
||||||
|
|
||||||
// This case would inflict a double-free elsewhere.
|
|
||||||
// However, this case is considered an analyzer bug since it causes false-positives.
|
|
||||||
void af3() {
|
|
||||||
int *p = my_malloc(12);
|
|
||||||
my_hold(p);
|
|
||||||
free(p); // no-warning
|
|
||||||
}
|
|
||||||
|
|
||||||
// This case would inflict a double-free elsewhere.
|
|
||||||
// However, this case is considered an analyzer bug since it causes false-positives.
|
|
||||||
int * af4() {
|
|
||||||
int *p = my_malloc(12);
|
|
||||||
my_free(p);
|
|
||||||
return p; // no-warning
|
|
||||||
}
|
|
||||||
|
|
||||||
// This case is (possibly) ok, be conservative
|
|
||||||
int * af5() {
|
|
||||||
int *p = my_malloc(12);
|
|
||||||
my_hold(p);
|
|
||||||
return p; // no-warning
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// This case tests that storing malloc'ed memory to a static variable which is
|
// This case tests that storing malloc'ed memory to a static variable which is
|
||||||
// then returned is not leaked. In the absence of known contracts for functions
|
// then returned is not leaked. In the absence of known contracts for functions
|
||||||
// or inter-procedural analysis, this is a conservative answer.
|
// or inter-procedural analysis, this is a conservative answer.
|
||||||
|
@ -261,3 +146,97 @@ char callocZeroesBad () {
|
||||||
}
|
}
|
||||||
return result; // expected-warning{{never released}}
|
return result; // expected-warning{{never released}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void nullFree() {
|
||||||
|
int *p = 0;
|
||||||
|
free(p); // no warning - a nop
|
||||||
|
}
|
||||||
|
|
||||||
|
void paramFree(int *p) {
|
||||||
|
myfoo(p);
|
||||||
|
free(p); // no warning
|
||||||
|
myfoo(p); // TODO: This should be a warning.
|
||||||
|
}
|
||||||
|
|
||||||
|
int* mallocEscapeRet() {
|
||||||
|
int *p = malloc(12);
|
||||||
|
return p; // no warning
|
||||||
|
}
|
||||||
|
|
||||||
|
void mallocEscapeFoo() {
|
||||||
|
int *p = malloc(12);
|
||||||
|
myfoo(p);
|
||||||
|
return; // no warning
|
||||||
|
}
|
||||||
|
|
||||||
|
void mallocEscapeFree() {
|
||||||
|
int *p = malloc(12);
|
||||||
|
myfoo(p);
|
||||||
|
free(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mallocEscapeFreeFree() {
|
||||||
|
int *p = malloc(12);
|
||||||
|
myfoo(p);
|
||||||
|
free(p);
|
||||||
|
free(p); // expected-warning{{Try to free a memory block that has been released}}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mallocEscapeFreeUse() {
|
||||||
|
int *p = malloc(12);
|
||||||
|
myfoo(p);
|
||||||
|
free(p);
|
||||||
|
myfoo(p); // expected-warning{{Use dynamically allocated memory after it is freed.}}
|
||||||
|
}
|
||||||
|
|
||||||
|
int *myalloc();
|
||||||
|
void myalloc2(int **p);
|
||||||
|
|
||||||
|
void mallocEscapeFreeCustomAlloc() {
|
||||||
|
int *p = malloc(12);
|
||||||
|
myfoo(p);
|
||||||
|
free(p);
|
||||||
|
p = myalloc();
|
||||||
|
free(p); // no warning
|
||||||
|
}
|
||||||
|
|
||||||
|
void mallocEscapeFreeCustomAlloc2() {
|
||||||
|
int *p = malloc(12);
|
||||||
|
myfoo(p);
|
||||||
|
free(p);
|
||||||
|
myalloc2(&p);
|
||||||
|
free(p); // no warning
|
||||||
|
}
|
||||||
|
|
||||||
|
void mallocBindFreeUse() {
|
||||||
|
int *x = malloc(12);
|
||||||
|
int *y = x;
|
||||||
|
free(y);
|
||||||
|
myfoo(x); // expected-warning{{Use dynamically allocated memory after it is freed.}}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mallocEscapeMalloc() {
|
||||||
|
int *p = malloc(12);
|
||||||
|
myfoo(p);
|
||||||
|
p = malloc(12); // expected-warning{{Allocated memory never released. Potential memory leak.}}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mallocMalloc() {
|
||||||
|
int *p = malloc(12);
|
||||||
|
p = malloc(12); // expected-warning{{Allocated memory never released. Potential memory leak}}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mallocFreeMalloc() {
|
||||||
|
int *p = malloc(12);
|
||||||
|
free(p);
|
||||||
|
p = malloc(12);
|
||||||
|
free(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MallocFreeUse_params() {
|
||||||
|
int *p = malloc(12);
|
||||||
|
free(p);
|
||||||
|
myfoo(p); //expected-warning{{Use dynamically allocated memory after it is freed}}
|
||||||
|
myfooint(*p); //expected-warning{{Use dynamically allocated memory after it is freed}}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue