[analyzer] These implements unix.MismatchedDeallocatorChecker checker.
+ Improved display names for allocators and deallocators The checker checks if a deallocation function matches allocation one. ('free' for 'malloc', 'delete' for 'new' etc.) llvm-svn: 178250
This commit is contained in:
parent
ddbf4f47dc
commit
0578959981
|
@ -290,6 +290,10 @@ def MallocPessimistic : Checker<"Malloc">,
|
|||
def MallocSizeofChecker : Checker<"MallocSizeof">,
|
||||
HelpText<"Check for dubious malloc arguments involving sizeof">,
|
||||
DescFile<"MallocSizeofChecker.cpp">;
|
||||
|
||||
def MismatchedDeallocatorChecker : Checker<"MismatchedDeallocator">,
|
||||
HelpText<"Check for mismatched deallocators.">,
|
||||
DescFile<"MallocChecker.cpp">;
|
||||
|
||||
} // end "unix"
|
||||
|
||||
|
@ -531,4 +535,3 @@ def ExprInspectionChecker : Checker<"ExprInspection">,
|
|||
DescFile<"ExprInspectionChecker.cpp">;
|
||||
|
||||
} // end "debug"
|
||||
|
||||
|
|
|
@ -35,6 +35,14 @@ using namespace ento;
|
|||
|
||||
namespace {
|
||||
|
||||
// Used to check correspondence between allocators and deallocators.
|
||||
enum AllocationFamily {
|
||||
AF_None,
|
||||
AF_Malloc,
|
||||
AF_CXXNew,
|
||||
AF_CXXNewArray
|
||||
};
|
||||
|
||||
class RefState {
|
||||
enum Kind { // Reference to allocated memory.
|
||||
Allocated,
|
||||
|
@ -42,33 +50,42 @@ class RefState {
|
|||
Released,
|
||||
// The responsibility for freeing resources has transfered from
|
||||
// this reference. A relinquished symbol should not be freed.
|
||||
Relinquished } K;
|
||||
Relinquished };
|
||||
|
||||
const Stmt *S;
|
||||
unsigned K : 2; // Kind enum, but stored as a bitfield.
|
||||
unsigned Family : 30; // Rest of 32-bit word, currently just an allocation
|
||||
// family.
|
||||
|
||||
RefState(Kind k, const Stmt *s, unsigned family)
|
||||
: K(k), S(s), Family(family) {}
|
||||
public:
|
||||
RefState(Kind k, const Stmt *s) : K(k), S(s) {}
|
||||
|
||||
bool isAllocated() const { return K == Allocated; }
|
||||
bool isReleased() const { return K == Released; }
|
||||
bool isRelinquished() const { return K == Relinquished; }
|
||||
|
||||
AllocationFamily getAllocationFamily() const {
|
||||
return (AllocationFamily)Family;
|
||||
}
|
||||
const Stmt *getStmt() const { return S; }
|
||||
|
||||
bool operator==(const RefState &X) const {
|
||||
return K == X.K && S == X.S;
|
||||
return K == X.K && S == X.S && Family == X.Family;
|
||||
}
|
||||
|
||||
static RefState getAllocated(const Stmt *s) {
|
||||
return RefState(Allocated, s);
|
||||
static RefState getAllocated(unsigned family, const Stmt *s) {
|
||||
return RefState(Allocated, s, family);
|
||||
}
|
||||
static RefState getReleased(const Stmt *s) { return RefState(Released, s); }
|
||||
static RefState getRelinquished(const Stmt *s) {
|
||||
return RefState(Relinquished, s);
|
||||
static RefState getReleased(unsigned family, const Stmt *s) {
|
||||
return RefState(Released, s, family);
|
||||
}
|
||||
static RefState getRelinquished(unsigned family, const Stmt *s) {
|
||||
return RefState(Relinquished, s, family);
|
||||
}
|
||||
|
||||
void Profile(llvm::FoldingSetNodeID &ID) const {
|
||||
ID.AddInteger(K);
|
||||
ID.AddPointer(S);
|
||||
ID.AddInteger(Family);
|
||||
}
|
||||
|
||||
void dump(raw_ostream &OS) const {
|
||||
|
@ -131,6 +148,7 @@ class MallocChecker : public Checker<check::DeadSymbols,
|
|||
mutable OwningPtr<BugType> BT_Leak;
|
||||
mutable OwningPtr<BugType> BT_UseFree;
|
||||
mutable OwningPtr<BugType> BT_BadFree;
|
||||
mutable OwningPtr<BugType> BT_BadDealloc;
|
||||
mutable OwningPtr<BugType> BT_OffsetFree;
|
||||
mutable IdentifierInfo *II_malloc, *II_free, *II_realloc, *II_calloc,
|
||||
*II_valloc, *II_reallocf, *II_strndup, *II_strdup;
|
||||
|
@ -145,6 +163,7 @@ public:
|
|||
DefaultBool CMallocPessimistic;
|
||||
DefaultBool CMallocOptimistic;
|
||||
DefaultBool CNewDeleteChecker;
|
||||
DefaultBool CMismatchedDeallocatorChecker;
|
||||
};
|
||||
|
||||
ChecksFilter Filter;
|
||||
|
@ -173,6 +192,23 @@ public:
|
|||
private:
|
||||
void initIdentifierInfo(ASTContext &C) const;
|
||||
|
||||
/// \brief Determine family of a deallocation expression.
|
||||
AllocationFamily getAllocationFamily(CheckerContext &C, const Expr *E) const;
|
||||
|
||||
/// \brief Print names of allocators and deallocators.
|
||||
///
|
||||
/// \returns true on success.
|
||||
bool printAllocDeallocName(raw_ostream &os, CheckerContext &C,
|
||||
const Expr *E) const;
|
||||
|
||||
/// \brief Print expected name of an allocator based on the deallocator's
|
||||
/// family derived from the DeallocExpr.
|
||||
void printExpectedAllocName(raw_ostream &os, CheckerContext &C,
|
||||
const Expr *DeallocExpr) const;
|
||||
/// \brief Print expected name of a deallocator based on the allocator's
|
||||
/// family.
|
||||
void printExpectedDeallocName(raw_ostream &os, AllocationFamily Family) const;
|
||||
|
||||
///@{
|
||||
/// Check if this is one of the functions which can allocate/reallocate memory
|
||||
/// pointed to by one of its arguments.
|
||||
|
@ -186,20 +222,22 @@ private:
|
|||
const OwnershipAttr* Att);
|
||||
static ProgramStateRef MallocMemAux(CheckerContext &C, const CallExpr *CE,
|
||||
const Expr *SizeEx, SVal Init,
|
||||
ProgramStateRef state) {
|
||||
ProgramStateRef State,
|
||||
AllocationFamily Family = AF_Malloc) {
|
||||
return MallocMemAux(C, CE,
|
||||
state->getSVal(SizeEx, C.getLocationContext()),
|
||||
Init, state);
|
||||
State->getSVal(SizeEx, C.getLocationContext()),
|
||||
Init, State, Family);
|
||||
}
|
||||
|
||||
static ProgramStateRef MallocMemAux(CheckerContext &C, const CallExpr *CE,
|
||||
SVal SizeEx, SVal Init,
|
||||
ProgramStateRef state);
|
||||
ProgramStateRef State,
|
||||
AllocationFamily Family = AF_Malloc);
|
||||
|
||||
/// Update the RefState to reflect the new memory allocation.
|
||||
static ProgramStateRef MallocUpdateRefState(CheckerContext &C,
|
||||
const Expr *E,
|
||||
ProgramStateRef state);
|
||||
static ProgramStateRef
|
||||
MallocUpdateRefState(CheckerContext &C, const Expr *E, ProgramStateRef State,
|
||||
AllocationFamily Family = AF_Malloc);
|
||||
|
||||
ProgramStateRef FreeMemAttr(CheckerContext &C, const CallExpr *CE,
|
||||
const OwnershipAttr* Att) const;
|
||||
|
@ -234,10 +272,13 @@ private:
|
|||
|
||||
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;
|
||||
void ReportBadFree(CheckerContext &C, SVal ArgVal, SourceRange Range,
|
||||
const Expr *DeallocExpr) const;
|
||||
void ReportBadDealloc(CheckerContext &C, SourceRange Range,
|
||||
const Expr *DeallocExpr, const RefState *RS) const;
|
||||
void ReportOffsetFree(CheckerContext &C, SVal ArgVal, SourceRange Range)const;
|
||||
void ReportOffsetFree(CheckerContext &C, SVal ArgVal, SourceRange Range,
|
||||
const Expr *DeallocExpr,
|
||||
const Expr *AllocExpr = 0) const;
|
||||
void ReportUseAfterFree(CheckerContext &C, SourceRange Range,
|
||||
SymbolRef Sym) const;
|
||||
void ReportDoubleFree(CheckerContext &C, SourceRange Range, bool Released,
|
||||
|
@ -505,7 +546,8 @@ void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const {
|
|||
initIdentifierInfo(C.getASTContext());
|
||||
IdentifierInfo *FunI = FD->getIdentifier();
|
||||
|
||||
if (Filter.CMallocOptimistic || Filter.CMallocPessimistic) {
|
||||
if (Filter.CMallocOptimistic || Filter.CMallocPessimistic ||
|
||||
Filter.CMismatchedDeallocatorChecker) {
|
||||
if (FunI == II_malloc || FunI == II_valloc) {
|
||||
if (CE->getNumArgs() < 1)
|
||||
return;
|
||||
|
@ -525,7 +567,7 @@ void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const {
|
|||
}
|
||||
}
|
||||
|
||||
if (Filter.CNewDeleteChecker) {
|
||||
if (Filter.CNewDeleteChecker || Filter.CMismatchedDeallocatorChecker) {
|
||||
if (isStandardNewDelete(FD, C.getASTContext())) {
|
||||
// Process direct calls to operator new/new[]/delete/delete[] functions
|
||||
// as distinct from new/new[]/delete/delete[] expressions that are
|
||||
|
@ -533,9 +575,11 @@ void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const {
|
|||
// CXXDeleteExpr.
|
||||
OverloadedOperatorKind K = FD->getOverloadedOperator();
|
||||
if (K == OO_New)
|
||||
State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State);
|
||||
State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State,
|
||||
AF_CXXNew);
|
||||
else if (K == OO_Array_New)
|
||||
State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State);
|
||||
State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State,
|
||||
AF_CXXNewArray);
|
||||
else if (K == OO_Delete || K == OO_Array_Delete)
|
||||
State = FreeMemAux(C, CE, State, 0, false, ReleasedAllocatedMemory);
|
||||
else
|
||||
|
@ -544,7 +588,7 @@ void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const {
|
|||
}
|
||||
}
|
||||
|
||||
if (Filter.CMallocOptimistic) {
|
||||
if (Filter.CMallocOptimistic || Filter.CMismatchedDeallocatorChecker) {
|
||||
// Check all the attributes, if there are any.
|
||||
// There can be multiple of these attributes.
|
||||
if (FD->hasAttrs())
|
||||
|
@ -575,7 +619,7 @@ void MallocChecker::checkPostStmt(const CXXNewExpr *NE,
|
|||
if (SymbolRef Sym = C.getSVal(*I).getAsSymbol())
|
||||
checkUseAfterFree(Sym, C, *I);
|
||||
|
||||
if (!Filter.CNewDeleteChecker)
|
||||
if (!Filter.CNewDeleteChecker && !Filter.CMismatchedDeallocatorChecker)
|
||||
return;
|
||||
|
||||
if (!isStandardNewDelete(NE->getOperatorNew(), C.getASTContext()))
|
||||
|
@ -586,19 +630,20 @@ void MallocChecker::checkPostStmt(const CXXNewExpr *NE,
|
|||
// value (if any) and we don't want to loose this value. So we call
|
||||
// MallocUpdateRefState() instead of MallocMemAux() which breakes the
|
||||
// existing binding.
|
||||
State = MallocUpdateRefState(C, NE, State);
|
||||
State = MallocUpdateRefState(C, NE, State, NE->isArray() ? AF_CXXNewArray
|
||||
: AF_CXXNew);
|
||||
C.addTransition(State);
|
||||
}
|
||||
|
||||
void MallocChecker::checkPreStmt(const CXXDeleteExpr *DE,
|
||||
CheckerContext &C) const {
|
||||
|
||||
if (!Filter.CNewDeleteChecker) {
|
||||
if (!Filter.CNewDeleteChecker)
|
||||
if (SymbolRef Sym = C.getSVal(DE->getArgument()).getAsSymbol())
|
||||
checkUseAfterFree(Sym, C, DE->getArgument());
|
||||
|
||||
if (!Filter.CNewDeleteChecker && !Filter.CMismatchedDeallocatorChecker)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isStandardNewDelete(DE->getOperatorDelete(), C.getASTContext()))
|
||||
return;
|
||||
|
@ -674,7 +719,8 @@ ProgramStateRef MallocChecker::MallocMemReturnsAttr(CheckerContext &C,
|
|||
ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C,
|
||||
const CallExpr *CE,
|
||||
SVal Size, SVal Init,
|
||||
ProgramStateRef state) {
|
||||
ProgramStateRef State,
|
||||
AllocationFamily Family) {
|
||||
|
||||
// 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
|
||||
|
@ -684,14 +730,14 @@ ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C,
|
|||
const LocationContext *LCtx = C.getPredecessor()->getLocationContext();
|
||||
DefinedSVal RetVal = svalBuilder.getConjuredHeapSymbolVal(CE, LCtx, Count)
|
||||
.castAs<DefinedSVal>();
|
||||
state = state->BindExpr(CE, C.getLocationContext(), RetVal);
|
||||
State = State->BindExpr(CE, C.getLocationContext(), RetVal);
|
||||
|
||||
// We expect the malloc functions to return a pointer.
|
||||
if (!RetVal.getAs<Loc>())
|
||||
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 =
|
||||
|
@ -703,20 +749,21 @@ ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C,
|
|||
SValBuilder &svalBuilder = C.getSValBuilder();
|
||||
DefinedOrUnknownSVal Extent = R->getExtent(svalBuilder);
|
||||
DefinedOrUnknownSVal extentMatchesSize =
|
||||
svalBuilder.evalEQ(state, Extent, *DefinedSize);
|
||||
svalBuilder.evalEQ(State, Extent, *DefinedSize);
|
||||
|
||||
state = state->assume(extentMatchesSize, true);
|
||||
assert(state);
|
||||
State = State->assume(extentMatchesSize, true);
|
||||
assert(State);
|
||||
}
|
||||
|
||||
return MallocUpdateRefState(C, CE, state);
|
||||
return MallocUpdateRefState(C, CE, State, Family);
|
||||
}
|
||||
|
||||
ProgramStateRef MallocChecker::MallocUpdateRefState(CheckerContext &C,
|
||||
const Expr *E,
|
||||
ProgramStateRef state) {
|
||||
ProgramStateRef State,
|
||||
AllocationFamily Family) {
|
||||
// Get the return value.
|
||||
SVal retVal = state->getSVal(E, C.getLocationContext());
|
||||
SVal retVal = State->getSVal(E, C.getLocationContext());
|
||||
|
||||
// We expect the malloc functions to return a pointer.
|
||||
if (!retVal.getAs<Loc>())
|
||||
|
@ -726,8 +773,7 @@ ProgramStateRef MallocChecker::MallocUpdateRefState(CheckerContext &C,
|
|||
assert(Sym);
|
||||
|
||||
// Set the symbol's state to Allocated.
|
||||
return state->set<RegionState>(Sym, RefState::getAllocated(E));
|
||||
|
||||
return State->set<RegionState>(Sym, RefState::getAllocated(Family, E));
|
||||
}
|
||||
|
||||
ProgramStateRef MallocChecker::FreeMemAttr(CheckerContext &C,
|
||||
|
@ -779,6 +825,100 @@ static bool didPreviousFreeFail(ProgramStateRef State,
|
|||
return false;
|
||||
}
|
||||
|
||||
AllocationFamily MallocChecker::getAllocationFamily(CheckerContext &C,
|
||||
const Expr *E) const {
|
||||
if (!E)
|
||||
return AF_None;
|
||||
|
||||
if (const CallExpr *CE = dyn_cast<CallExpr>(E)) {
|
||||
const FunctionDecl *FD = C.getCalleeDecl(CE);
|
||||
ASTContext &Ctx = C.getASTContext();
|
||||
|
||||
if (isFreeFunction(FD, Ctx))
|
||||
return AF_Malloc;
|
||||
|
||||
if (isStandardNewDelete(FD, Ctx)) {
|
||||
OverloadedOperatorKind Kind = FD->getOverloadedOperator();
|
||||
if (Kind == OO_Delete)
|
||||
return AF_CXXNew;
|
||||
else if (Kind == OO_Array_Delete)
|
||||
return AF_CXXNewArray;
|
||||
}
|
||||
|
||||
return AF_None;
|
||||
}
|
||||
|
||||
if (const CXXDeleteExpr *DE = dyn_cast<CXXDeleteExpr>(E))
|
||||
return DE->isArrayForm() ? AF_CXXNewArray : AF_CXXNew;
|
||||
|
||||
if (isa<ObjCMessageExpr>(E))
|
||||
return AF_Malloc;
|
||||
|
||||
return AF_None;
|
||||
}
|
||||
|
||||
bool MallocChecker::printAllocDeallocName(raw_ostream &os, CheckerContext &C,
|
||||
const Expr *E) const {
|
||||
if (const CallExpr *CE = dyn_cast<CallExpr>(E)) {
|
||||
// FIXME: This doesn't handle indirect calls.
|
||||
const FunctionDecl *FD = CE->getDirectCallee();
|
||||
if (!FD)
|
||||
return false;
|
||||
|
||||
os << *FD;
|
||||
if (!FD->isOverloadedOperator())
|
||||
os << "()";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (const ObjCMessageExpr *Msg = dyn_cast<ObjCMessageExpr>(E)) {
|
||||
if (Msg->isInstanceMessage())
|
||||
os << "-";
|
||||
else
|
||||
os << "+";
|
||||
os << Msg->getSelector().getAsString();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (const CXXNewExpr *NE = dyn_cast<CXXNewExpr>(E)) {
|
||||
os << "'"
|
||||
<< getOperatorSpelling(NE->getOperatorNew()->getOverloadedOperator())
|
||||
<< "'";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (const CXXDeleteExpr *DE = dyn_cast<CXXDeleteExpr>(E)) {
|
||||
os << "'"
|
||||
<< getOperatorSpelling(DE->getOperatorDelete()->getOverloadedOperator())
|
||||
<< "'";
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MallocChecker::printExpectedAllocName(raw_ostream &os, CheckerContext &C,
|
||||
const Expr *E) const {
|
||||
AllocationFamily Family = getAllocationFamily(C, E);
|
||||
|
||||
switch(Family) {
|
||||
case AF_Malloc: os << "malloc()"; return;
|
||||
case AF_CXXNew: os << "'new'"; return;
|
||||
case AF_CXXNewArray: os << "'new[]'"; return;
|
||||
case AF_None: llvm_unreachable("not a deallocation expression");
|
||||
}
|
||||
}
|
||||
|
||||
void MallocChecker::printExpectedDeallocName(raw_ostream &os,
|
||||
AllocationFamily Family) const {
|
||||
switch(Family) {
|
||||
case AF_Malloc: os << "free()"; return;
|
||||
case AF_CXXNew: os << "'delete'"; return;
|
||||
case AF_CXXNewArray: os << "'delete[]'"; return;
|
||||
case AF_None: llvm_unreachable("suspicious AF_None argument");
|
||||
}
|
||||
}
|
||||
|
||||
ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C,
|
||||
const Expr *ArgExpr,
|
||||
const Expr *ParentExpr,
|
||||
|
@ -812,7 +952,7 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C,
|
|||
// Nonlocs can't be freed, of course.
|
||||
// Non-region locations (labels and fixed addresses) also shouldn't be freed.
|
||||
if (!R) {
|
||||
ReportBadFree(C, ArgVal, ArgExpr->getSourceRange());
|
||||
ReportBadFree(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -820,7 +960,7 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C,
|
|||
|
||||
// Blocks might show up as heap data, but should not be free()d
|
||||
if (isa<BlockDataRegion>(R)) {
|
||||
ReportBadFree(C, ArgVal, ArgExpr->getSourceRange());
|
||||
ReportBadFree(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -837,7 +977,7 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C,
|
|||
// function, so UnknownSpaceRegion is always a possibility.
|
||||
// False negatives are better than false positives.
|
||||
|
||||
ReportBadFree(C, ArgVal, ArgExpr->getSourceRange());
|
||||
ReportBadFree(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -860,6 +1000,14 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C,
|
|||
return 0;
|
||||
}
|
||||
|
||||
// Check if an expected deallocation function matches the real one.
|
||||
if (RsBase &&
|
||||
RsBase->getAllocationFamily() != AF_None &&
|
||||
RsBase->getAllocationFamily() != getAllocationFamily(C, ParentExpr) ) {
|
||||
ReportBadDealloc(C, ArgExpr->getSourceRange(), ParentExpr, RsBase);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check if the memory location being freed is the actual location
|
||||
// allocated, or an offset.
|
||||
RegionOffset Offset = R->getAsOffset();
|
||||
|
@ -867,7 +1015,9 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C,
|
|||
Offset.isValid() &&
|
||||
!Offset.hasSymbolicOffset() &&
|
||||
Offset.getOffset() != 0) {
|
||||
ReportOffsetFree(C, ArgVal, ArgExpr->getSourceRange());
|
||||
const Expr *AllocExpr = cast<Expr>(RsBase->getStmt());
|
||||
ReportOffsetFree(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr,
|
||||
AllocExpr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -887,12 +1037,15 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C,
|
|||
}
|
||||
}
|
||||
|
||||
AllocationFamily Family = RsBase ? RsBase->getAllocationFamily() : AF_None;
|
||||
// Normal free.
|
||||
if (Hold) {
|
||||
if (Hold)
|
||||
return State->set<RegionState>(SymBase,
|
||||
RefState::getRelinquished(ParentExpr));
|
||||
}
|
||||
return State->set<RegionState>(SymBase, RefState::getReleased(ParentExpr));
|
||||
RefState::getRelinquished(Family,
|
||||
ParentExpr));
|
||||
|
||||
return State->set<RegionState>(SymBase,
|
||||
RefState::getReleased(Family, ParentExpr));
|
||||
}
|
||||
|
||||
bool MallocChecker::SummarizeValue(raw_ostream &os, SVal V) {
|
||||
|
@ -982,38 +1135,43 @@ bool MallocChecker::SummarizeRegion(raw_ostream &os,
|
|||
}
|
||||
}
|
||||
|
||||
void MallocChecker::ReportBadFree(CheckerContext &C, SVal ArgVal,
|
||||
SourceRange Range) const {
|
||||
void MallocChecker::ReportBadFree(CheckerContext &C, SVal ArgVal,
|
||||
SourceRange Range,
|
||||
const Expr *DeallocExpr) const {
|
||||
|
||||
if (!Filter.CMallocOptimistic && !Filter.CMallocPessimistic &&
|
||||
!Filter.CNewDeleteChecker)
|
||||
return;
|
||||
|
||||
if (ExplodedNode *N = C.generateSink()) {
|
||||
if (!BT_BadFree)
|
||||
BT_BadFree.reset(new BugType("Bad free", "Memory Error"));
|
||||
|
||||
SmallString<100> buf;
|
||||
llvm::raw_svector_ostream os(buf);
|
||||
|
||||
|
||||
const MemRegion *MR = ArgVal.getAsRegion();
|
||||
if (MR) {
|
||||
while (const ElementRegion *ER = dyn_cast<ElementRegion>(MR))
|
||||
MR = ER->getSuperRegion();
|
||||
|
||||
// Special case for alloca()
|
||||
if (isa<AllocaRegion>(MR))
|
||||
os << "Argument to free() was allocated by alloca(), not malloc()";
|
||||
else {
|
||||
os << "Argument to free() is ";
|
||||
if (SummarizeRegion(os, MR))
|
||||
os << ", which is not memory allocated by malloc()";
|
||||
else
|
||||
os << "not memory allocated by malloc()";
|
||||
}
|
||||
} else {
|
||||
os << "Argument to free() is ";
|
||||
if (SummarizeValue(os, ArgVal))
|
||||
os << ", which is not memory allocated by malloc()";
|
||||
while (const ElementRegion *ER = dyn_cast_or_null<ElementRegion>(MR))
|
||||
MR = ER->getSuperRegion();
|
||||
|
||||
if (MR && isa<AllocaRegion>(MR))
|
||||
os << "Memory allocated by alloca() should not be deallocated";
|
||||
else {
|
||||
os << "Argument to ";
|
||||
if (!printAllocDeallocName(os, C, DeallocExpr))
|
||||
os << "deallocator";
|
||||
|
||||
os << " is ";
|
||||
bool Summarized = MR ? SummarizeRegion(os, MR)
|
||||
: SummarizeValue(os, ArgVal);
|
||||
if (Summarized)
|
||||
os << ", which is not memory allocated by ";
|
||||
else
|
||||
os << "not memory allocated by malloc()";
|
||||
os << "not memory allocated by ";
|
||||
|
||||
printExpectedAllocName(os, C, DeallocExpr);
|
||||
}
|
||||
|
||||
|
||||
BugReport *R = new BugReport(*BT_BadFree, os.str(), N);
|
||||
R->markInteresting(MR);
|
||||
R->addRange(Range);
|
||||
|
@ -1021,8 +1179,50 @@ void MallocChecker::ReportBadFree(CheckerContext &C, SVal ArgVal,
|
|||
}
|
||||
}
|
||||
|
||||
void MallocChecker::ReportBadDealloc(CheckerContext &C, SourceRange Range,
|
||||
const Expr *DeallocExpr,
|
||||
const RefState *RS) const {
|
||||
|
||||
if (!Filter.CMismatchedDeallocatorChecker)
|
||||
return;
|
||||
|
||||
if (ExplodedNode *N = C.generateSink()) {
|
||||
if (!BT_BadDealloc)
|
||||
BT_BadDealloc.reset(new BugType("Bad deallocator", "Memory Error"));
|
||||
|
||||
SmallString<100> buf;
|
||||
llvm::raw_svector_ostream os(buf);
|
||||
|
||||
const Expr *AllocExpr = cast<Expr>(RS->getStmt());
|
||||
SmallString<20> AllocBuf;
|
||||
llvm::raw_svector_ostream AllocOs(AllocBuf);
|
||||
SmallString<20> DeallocBuf;
|
||||
llvm::raw_svector_ostream DeallocOs(DeallocBuf);
|
||||
|
||||
os << "Memory";
|
||||
if (printAllocDeallocName(AllocOs, C, AllocExpr))
|
||||
os << " allocated by " << AllocOs.str();
|
||||
|
||||
os << " should be deallocated by ";
|
||||
printExpectedDeallocName(os, RS->getAllocationFamily());
|
||||
|
||||
if (printAllocDeallocName(DeallocOs, C, DeallocExpr))
|
||||
os << ", not " << DeallocOs.str();
|
||||
|
||||
BugReport *R = new BugReport(*BT_BadDealloc, os.str(), N);
|
||||
R->addRange(Range);
|
||||
C.emitReport(R);
|
||||
}
|
||||
}
|
||||
|
||||
void MallocChecker::ReportOffsetFree(CheckerContext &C, SVal ArgVal,
|
||||
SourceRange Range) const {
|
||||
SourceRange Range, const Expr *DeallocExpr,
|
||||
const Expr *AllocExpr) const {
|
||||
|
||||
if (!Filter.CMallocOptimistic && !Filter.CMallocPessimistic &&
|
||||
!Filter.CNewDeleteChecker)
|
||||
return;
|
||||
|
||||
ExplodedNode *N = C.generateSink();
|
||||
if (N == NULL)
|
||||
return;
|
||||
|
@ -1032,6 +1232,8 @@ void MallocChecker::ReportOffsetFree(CheckerContext &C, SVal ArgVal,
|
|||
|
||||
SmallString<100> buf;
|
||||
llvm::raw_svector_ostream os(buf);
|
||||
SmallString<20> AllocNameBuf;
|
||||
llvm::raw_svector_ostream AllocNameOs(AllocNameBuf);
|
||||
|
||||
const MemRegion *MR = ArgVal.getAsRegion();
|
||||
assert(MR && "Only MemRegion based symbols can have offset free errors");
|
||||
|
@ -1044,11 +1246,18 @@ void MallocChecker::ReportOffsetFree(CheckerContext &C, SVal ArgVal,
|
|||
|
||||
int offsetBytes = Offset.getOffset() / C.getASTContext().getCharWidth();
|
||||
|
||||
os << "Argument to free() is offset by "
|
||||
os << "Argument to ";
|
||||
if (!printAllocDeallocName(os, C, DeallocExpr))
|
||||
os << "deallocator";
|
||||
os << " is offset by "
|
||||
<< offsetBytes
|
||||
<< " "
|
||||
<< ((abs(offsetBytes) > 1) ? "bytes" : "byte")
|
||||
<< " from the start of memory allocated by malloc()";
|
||||
<< " from the start of ";
|
||||
if (AllocExpr && printAllocDeallocName(AllocNameOs, C, AllocExpr))
|
||||
os << "memory allocated by " << AllocNameOs.str();
|
||||
else
|
||||
os << "allocated memory";
|
||||
|
||||
BugReport *R = new BugReport(*BT_OffsetFree, os.str(), N);
|
||||
R->markInteresting(MR->getBaseRegion());
|
||||
|
@ -1059,6 +1268,10 @@ void MallocChecker::ReportOffsetFree(CheckerContext &C, SVal ArgVal,
|
|||
void MallocChecker::ReportUseAfterFree(CheckerContext &C, SourceRange Range,
|
||||
SymbolRef Sym) const {
|
||||
|
||||
if (!Filter.CMallocOptimistic && !Filter.CMallocPessimistic &&
|
||||
!Filter.CNewDeleteChecker)
|
||||
return;
|
||||
|
||||
if (ExplodedNode *N = C.generateSink()) {
|
||||
if (!BT_UseFree)
|
||||
BT_UseFree.reset(new BugType("Use-after-free", "Memory Error"));
|
||||
|
@ -1077,6 +1290,10 @@ void MallocChecker::ReportDoubleFree(CheckerContext &C, SourceRange Range,
|
|||
bool Released, SymbolRef Sym,
|
||||
SymbolRef PrevSym) const {
|
||||
|
||||
if (!Filter.CMallocOptimistic && !Filter.CMallocPessimistic &&
|
||||
!Filter.CNewDeleteChecker)
|
||||
return;
|
||||
|
||||
if (ExplodedNode *N = C.generateSink()) {
|
||||
if (!BT_DoubleFree)
|
||||
BT_DoubleFree.reset(new BugType("Double free", "Memory Error"));
|
||||
|
@ -1248,6 +1465,11 @@ MallocChecker::getAllocationSite(const ExplodedNode *N, SymbolRef Sym,
|
|||
|
||||
void MallocChecker::reportLeak(SymbolRef Sym, ExplodedNode *N,
|
||||
CheckerContext &C) const {
|
||||
|
||||
if (!Filter.CMallocOptimistic && !Filter.CMallocPessimistic &&
|
||||
!Filter.CNewDeleteChecker)
|
||||
return;
|
||||
|
||||
assert(N);
|
||||
if (!BT_Leak) {
|
||||
BT_Leak.reset(new BugType("Memory leak", "Memory Error"));
|
||||
|
@ -1491,7 +1713,7 @@ ProgramStateRef MallocChecker::evalAssume(ProgramStateRef state,
|
|||
if (RS->isReleased()) {
|
||||
if (I.getData().Kind == RPToBeFreedAfterFailure)
|
||||
state = state->set<RegionState>(ReallocSym,
|
||||
RefState::getAllocated(RS->getStmt()));
|
||||
RefState::getAllocated(RS->getAllocationFamily(), RS->getStmt()));
|
||||
else if (I.getData().Kind == RPDoNotTrackAfterFailure)
|
||||
state = state->remove<RegionState>(ReallocSym);
|
||||
else
|
||||
|
@ -1796,3 +2018,4 @@ void ento::register##name(CheckerManager &mgr) {\
|
|||
REGISTER_CHECKER(MallocPessimistic)
|
||||
REGISTER_CHECKER(MallocOptimistic)
|
||||
REGISTER_CHECKER(NewDeleteChecker)
|
||||
REGISTER_CHECKER(MismatchedDeallocatorChecker)
|
||||
|
|
|
@ -84,7 +84,7 @@ void testUseAfterDelete() {
|
|||
|
||||
void testDeleteAlloca() {
|
||||
int *p = (int *)__builtin_alloca(sizeof(int));
|
||||
delete p; // expected-warning{{Argument to free() was allocated by alloca(), not malloc()}}
|
||||
delete p; // expected-warning{{Memory allocated by alloca() should not be deallocated}}
|
||||
}
|
||||
|
||||
void testDoubleDelete() {
|
||||
|
@ -95,18 +95,18 @@ void testDoubleDelete() {
|
|||
|
||||
void testExprDeleteArg() {
|
||||
int i;
|
||||
delete &i; // expected-warning{{Argument to free() is the address of the local variable 'i', which is not memory allocated by malloc()}}
|
||||
} // FIXME: 'free()' -> 'delete'; 'malloc()' -> 'new'
|
||||
delete &i; // expected-warning{{Argument to 'delete' is the address of the local variable 'i', which is not memory allocated by 'new'}}
|
||||
}
|
||||
|
||||
void testExprDeleteArrArg() {
|
||||
int i;
|
||||
delete[] &i; // expected-warning{{Argument to free() is the address of the local variable 'i', which is not memory allocated by malloc()}}
|
||||
} // FIXME: 'free()' -> 'delete[]'; 'malloc()' -> 'new[]'
|
||||
delete[] &i; // expected-warning{{Argument to 'delete[]' is the address of the local variable 'i', which is not memory allocated by 'new[]'}}
|
||||
}
|
||||
|
||||
void testAllocDeallocNames() {
|
||||
int *p = new(std::nothrow) int[1];
|
||||
delete[] (++p); // expected-warning{{Argument to free() is offset by 4 bytes from the start of memory allocated by malloc()}}
|
||||
} // FIXME: 'free()' -> 'delete[]'; 'malloc()' -> 'new[]'
|
||||
delete[] (++p); // expected-warning{{Argument to 'delete[]' is offset by 4 bytes from the start of memory allocated by 'new[]'}}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Check for intersections with unix.Malloc and unix.MallocWithAnnotations
|
||||
|
@ -143,8 +143,9 @@ void testFreeNewExpr() {
|
|||
|
||||
void testObjcFreeNewed() {
|
||||
int *p = new int;
|
||||
NSData *nsdata = [NSData dataWithBytesNoCopy:p length:sizeof(int) freeWhenDone:1]; // pointer escaped, no-warning
|
||||
NSData *nsdata = [NSData dataWithBytesNoCopy:p length:sizeof(int) freeWhenDone:1]; // expected-warning{{Memory is never released; potential leak}}
|
||||
}
|
||||
// FIXME: Pointer should escape
|
||||
|
||||
void testFreeAfterDelete() {
|
||||
int *p = new int;
|
||||
|
|
|
@ -0,0 +1,187 @@
|
|||
// RUN: %clang_cc1 -analyze -analyzer-checker=core,unix.MismatchedDeallocator -fblocks -verify %s
|
||||
|
||||
#include "Inputs/system-header-simulator-objc.h"
|
||||
#include "Inputs/system-header-simulator-cxx.h"
|
||||
|
||||
typedef __typeof__(sizeof(int)) size_t;
|
||||
void *malloc(size_t);
|
||||
void *realloc(void *ptr, size_t size);
|
||||
void *calloc(size_t nmemb, size_t size);
|
||||
char *strdup(const char *s);
|
||||
void __attribute((ownership_returns(malloc))) *my_malloc(size_t);
|
||||
|
||||
void free(void *);
|
||||
void __attribute((ownership_takes(malloc, 1))) my_free(void *);
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Test if an allocation function matches deallocation function
|
||||
//---------------------------------------------------------------
|
||||
|
||||
//--------------- test malloc family
|
||||
void testMalloc1() {
|
||||
int *p = (int *)malloc(sizeof(int));
|
||||
delete p; // expected-warning{{Memory allocated by malloc() should be deallocated by free(), not 'delete'}}
|
||||
}
|
||||
|
||||
void testMalloc2() {
|
||||
int *p = (int *)malloc(8);
|
||||
int *q = (int *)realloc(p, 16);
|
||||
delete q; // expected-warning{{Memory allocated by realloc() should be deallocated by free(), not 'delete'}}
|
||||
}
|
||||
|
||||
void testMalloc3() {
|
||||
int *p = (int *)calloc(1, sizeof(int));
|
||||
delete p; // expected-warning{{Memory allocated by calloc() should be deallocated by free(), not 'delete'}}
|
||||
}
|
||||
|
||||
void testMalloc4(const char *s) {
|
||||
char *p = strdup(s);
|
||||
delete p; // expected-warning{{Memory allocated by strdup() should be deallocated by free(), not 'delete'}}
|
||||
}
|
||||
|
||||
void testMalloc5() {
|
||||
int *p = (int *)my_malloc(sizeof(int));
|
||||
delete p; // expected-warning{{Memory allocated by my_malloc() should be deallocated by free(), not 'delete'}}
|
||||
}
|
||||
|
||||
void testMalloc6() {
|
||||
int *p = (int *)malloc(sizeof(int));
|
||||
operator delete(p); // expected-warning{{Memory allocated by malloc() should be deallocated by free(), not operator delete}}
|
||||
}
|
||||
|
||||
void testMalloc7() {
|
||||
int *p = (int *)malloc(sizeof(int));
|
||||
delete[] p; // expected-warning{{Memory allocated by malloc() should be deallocated by free(), not 'delete[]'}}
|
||||
}
|
||||
|
||||
void testMalloc8() {
|
||||
int *p = (int *)malloc(sizeof(int));
|
||||
operator delete[](p); // expected-warning{{Memory allocated by malloc() should be deallocated by free(), not operator delete[]}}
|
||||
}
|
||||
|
||||
//--------------- test new family
|
||||
void testNew1() {
|
||||
int *p = new int;
|
||||
free(p); // expected-warning{{Memory allocated by 'new' should be deallocated by 'delete', not free()}}
|
||||
}
|
||||
|
||||
void testNew2() {
|
||||
int *p = (int *)operator new(0);
|
||||
free(p); // expected-warning{{Memory allocated by operator new should be deallocated by 'delete', not free()}}
|
||||
}
|
||||
|
||||
void testNew3() {
|
||||
int *p = new int[1];
|
||||
free(p); // expected-warning{{Memory allocated by 'new[]' should be deallocated by 'delete[]', not free()}}
|
||||
}
|
||||
|
||||
void testNew4() {
|
||||
int *p = new int;
|
||||
realloc(p, sizeof(long)); // expected-warning{{Memory allocated by 'new' should be deallocated by 'delete', not realloc()}}
|
||||
}
|
||||
|
||||
void testNew5() {
|
||||
int *p = (int *)operator new(0);
|
||||
realloc(p, sizeof(long)); // expected-warning{{Memory allocated by operator new should be deallocated by 'delete', not realloc()}}
|
||||
}
|
||||
|
||||
void testNew6() {
|
||||
int *p = new int[1];
|
||||
realloc(p, sizeof(long)); // expected-warning{{Memory allocated by 'new[]' should be deallocated by 'delete[]', not realloc()}}
|
||||
}
|
||||
|
||||
void testNew7() {
|
||||
int *p = new int;
|
||||
delete[] p; // expected-warning{{Memory allocated by 'new' should be deallocated by 'delete', not 'delete[]'}}
|
||||
}
|
||||
|
||||
void testNew8() {
|
||||
int *p = (int *)operator new(0);
|
||||
delete[] p; // expected-warning{{Memory allocated by operator new should be deallocated by 'delete', not 'delete[]'}}
|
||||
}
|
||||
|
||||
void testNew9() {
|
||||
int *p = new int[1];
|
||||
delete p; // expected-warning{{Memory allocated by 'new[]' should be deallocated by 'delete[]', not 'delete'}}
|
||||
}
|
||||
|
||||
void testNew10() {
|
||||
int *p = (int *)operator new[](0);
|
||||
delete p; // expected-warning{{Memory allocated by operator new[] should be deallocated by 'delete[]', not 'delete'}}
|
||||
}
|
||||
|
||||
void testNew11(NSUInteger dataLength) {
|
||||
int *p = new int;
|
||||
NSData *d = [NSData dataWithBytesNoCopy:p length:sizeof(int) freeWhenDone:1]; // expected-warning{{Memory allocated by 'new' should be deallocated by 'delete', not +dataWithBytesNoCopy:length:freeWhenDone:}}
|
||||
// FIXME: should be "+dataWithBytesNoCopy:length:freeWhenDone: cannot take ownership of memory allocated by 'new'."
|
||||
}
|
||||
|
||||
//-------------------------------------------------------
|
||||
// Check for intersection with unix.Malloc bounded with
|
||||
// unix.MismatchedDeallocator
|
||||
//-------------------------------------------------------
|
||||
|
||||
// new/delete oparators are subjects of cplusplus.NewDelete.
|
||||
void testNewDeleteNoWarn() {
|
||||
int i;
|
||||
delete &i; // no-warning
|
||||
|
||||
int *p1 = new int;
|
||||
delete ++p1; // no-warning
|
||||
|
||||
int *p2 = new int;
|
||||
delete p2;
|
||||
delete p2; // no-warning
|
||||
|
||||
int *p3 = new int; // no-warning
|
||||
}
|
||||
|
||||
void testDeleteOpAfterFree() {
|
||||
int *p = (int *)malloc(sizeof(int));
|
||||
free(p);
|
||||
operator delete(p); // no-warning
|
||||
}
|
||||
|
||||
void testDeleteAfterFree() {
|
||||
int *p = (int *)malloc(sizeof(int));
|
||||
free(p);
|
||||
delete p; // no-warning
|
||||
}
|
||||
|
||||
void testStandardPlacementNewAfterFree() {
|
||||
int *p = (int *)malloc(sizeof(int));
|
||||
free(p);
|
||||
p = new(p) int; // no-warning
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Check for intersection with cplusplus.NewDelete bounded with
|
||||
// unix.MismatchedDeallocator
|
||||
//---------------------------------------------------------------
|
||||
|
||||
// malloc()/free() are subjects of unix.Malloc and unix.MallocWithAnnotations
|
||||
void testMallocFreeNoWarn() {
|
||||
int i;
|
||||
free(&i); // no-warning
|
||||
|
||||
int *p1 = (int *)malloc(sizeof(int));
|
||||
free(++p1); // no-warning
|
||||
|
||||
int *p2 = (int *)malloc(sizeof(int));
|
||||
free(p2);
|
||||
free(p2); // no-warning
|
||||
|
||||
int *p3 = (int *)malloc(sizeof(int)); // no-warning
|
||||
}
|
||||
|
||||
void testFreeAfterDelete() {
|
||||
int *p = new int;
|
||||
delete p;
|
||||
free(p); // no-warning
|
||||
}
|
||||
|
||||
void testStandardPlacementNewAfterDelete() {
|
||||
int *p = new int;
|
||||
delete p;
|
||||
p = new(p) int; // no-warning
|
||||
}
|
|
@ -50,7 +50,7 @@ void t10 () {
|
|||
|
||||
void t11 () {
|
||||
char *p = (char*)__builtin_alloca(2);
|
||||
free(p); // expected-warning {{Argument to free() was allocated by alloca(), not malloc()}}
|
||||
free(p); // expected-warning {{Memory allocated by alloca() should not be deallocated}}
|
||||
}
|
||||
|
||||
void t12 () {
|
||||
|
|
|
@ -68,7 +68,7 @@ void testNSStringFreeWhenDoneNO2(NSUInteger dataLength) {
|
|||
|
||||
void testOffsetFree() {
|
||||
int *p = (int *)malloc(sizeof(int));
|
||||
NSData *nsdata = [NSData dataWithBytesNoCopy:++p length:sizeof(int) freeWhenDone:1]; // expected-warning{{Argument to free() is offset by 4 bytes from the start of memory allocated by malloc()}}
|
||||
NSData *nsdata = [NSData dataWithBytesNoCopy:++p length:sizeof(int) freeWhenDone:1]; // expected-warning{{Argument to +dataWithBytesNoCopy:length:freeWhenDone: is offset by 4 bytes from the start of memory allocated by malloc()}}
|
||||
}
|
||||
|
||||
void testRelinquished1() {
|
||||
|
|
Loading…
Reference in New Issue