Hi, Jordan. Thanx for the review!
Attached is the new version of the patch with all the comments
addressed. Also added support for directly called operator new()/new[]()
and operator delete()
There is currently one problem with handling of operator delete(). The
following test
void testDeleteOp1() {
int *p = (int *)malloc(sizeof(int));
operator delete(p); // FIXME: should complain "Argument to operator
delete() was allocated by malloc(), not operator new"
}
produce no warnings as attached RefState seem to be missing at the point
when checkPostStmt(const CallExpr *CE, CheckerContext &C) callback is
called for operator delete(p).
I haven't investigated the problem deeply yet, intend to address it
parallel with the review.
+ if (NE->getNumPlacementArgs())
+ return;
+ // skip placement new operators as they may not allocate memory
Two comments here:
- Please make sure all comments are complete, capitalized, and
punctuated sentences. (This has the important one, "complete"....just
missing capitalization and punctuation.)
I'll try. Unfortunately I am not as good in English as I want to be, so
sorry for my grammar, syntax, and punctuation.
--
Anton
Index: lib/StaticAnalyzer/Checkers/MallocChecker.cpp
===================================================================
--- lib/StaticAnalyzer/Checkers/MallocChecker.cpp (revision 174894)
+++ lib/StaticAnalyzer/Checkers/MallocChecker.cpp (working copy)
@@ -35,40 +35,70 @@
namespace {
+// Possible deallocator kinds.
+// Numeration is added for convenient mapping to RefState::Kind
+enum DeallocatorKind {
+ D_unknown = 0,
+ D_free = 0x4,
+ D_delete = 0x8,
+ D_deleteArray = 0xC
+};
+
class RefState {
+ // First two bits of Kind represent memory kind
+ static const int K_MASK = 0x3;
+ // Next two bits represent deallocator kind (mapped from DeallocatorKind)
+ static const int D_MASK = 0xC;
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;
+ Relinquished,
+
+ // Mapped DeallocatorKind.
+ // Expected kind of a deallocator; used to check if a real
+ // kind of a deallocator matches expected one
+ Free = 0x4,
+ Delete = 0x8,
+ DeleteArray = 0xC
+ } K;
const Stmt *S;
+ const FunctionDecl *CalleeDecl;
+ RefState(Kind k, const Stmt *s, const FunctionDecl *calleeDecl)
+ : K(k), S(s), CalleeDecl(calleeDecl) {}
public:
- RefState(Kind k, const Stmt *s) : K(k), S(s) {}
+ bool isAllocated() const { return (K & K_MASK) == Allocated; }
+ bool isReleased() const { return (K & K_MASK) == Released; }
+ bool isRelinquished() const { return (K & K_MASK) == Relinquished; }
+ DeallocatorKind getDeallocKind() const {
+ return (DeallocatorKind)(K & D_MASK);
+ }
- bool isAllocated() const { return K == Allocated; }
- bool isReleased() const { return K == Released; }
- bool isRelinquished() const { return K == Relinquished; }
-
const Stmt *getStmt() const { return S; }
+ const FunctionDecl *getCalleeDecl() const { return CalleeDecl; }
bool operator==(const RefState &X) const {
- return K == X.K && S == X.S;
+ return K == X.K && S == X.S && CalleeDecl == X.CalleeDecl;
}
- static RefState getAllocated(const Stmt *s) {
- return RefState(Allocated, s);
+ static RefState getAllocated(DeallocatorKind dKind, const Stmt *s,
+ const FunctionDecl *calleeDecl) {
+ return RefState((Kind)(Allocated | dKind), s, calleeDecl);
}
- static RefState getReleased(const Stmt *s) { return RefState(Released, s); }
+ static RefState getReleased(const Stmt *s) {
+ return RefState(Released, s, 0);
+ }
static RefState getRelinquished(const Stmt *s) {
- return RefState(Relinquished, s);
+ return RefState(Relinquished, s, 0);
}
void Profile(llvm::FoldingSetNodeID &ID) const {
ID.AddInteger(K);
ID.AddPointer(S);
+ ID.AddPointer(CalleeDecl);
}
void dump(raw_ostream &OS) const {
@@ -77,7 +107,7 @@
"Released",
"Relinquished"
};
- OS << Table[(unsigned) K];
+ OS << Table[(unsigned) K & K_MASK];
}
LLVM_ATTRIBUTE_USED void dump() const {
@@ -120,6 +150,8 @@
check::PreStmt<ReturnStmt>,
check::PreStmt<CallExpr>,
check::PostStmt<CallExpr>,
+ check::PostStmt<CXXNewExpr>,
+ check::PreStmt<CXXDeleteExpr>,
check::PostStmt<BlockExpr>,
check::PostObjCMessage,
check::Location,
@@ -148,6 +180,8 @@
void checkPreStmt(const CallExpr *S, CheckerContext &C) const;
void checkPostStmt(const CallExpr *CE, CheckerContext &C) const;
+ void checkPostStmt(const CXXNewExpr *NE, CheckerContext &C) const;
+ void checkPreStmt(const CXXDeleteExpr *DE, CheckerContext &C) const;
void checkPostObjCMessage(const ObjCMethodCall &Call, CheckerContext &C) const;
void checkPostStmt(const BlockExpr *BE, CheckerContext &C) const;
void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
@@ -168,31 +202,46 @@
private:
void initIdentifierInfo(ASTContext &C) const;
+ /// Auxiliary functions that return kind and print names of
+ /// allocators/deallocators
+ DeallocatorKind GetDeallocKind(CheckerContext &C, const Expr *E) const;
+ void PrintAllocDeallocName(raw_ostream &os, CheckerContext &C,
+ const Expr *E) const;
+ void PrintAllocDeallocName(raw_ostream &os, CheckerContext &C,
+ const RefState *RS) const;
+ void PrintExpectedAllocName(raw_ostream &os, CheckerContext &C,
+ const Expr *DeallocExpr) 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;
+ bool isDefaultNonptrplacementNewDelete(const FunctionDecl *FD,
+ CheckerContext &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,
- ProgramStateRef state) {
+ ProgramStateRef state,
+ DeallocatorKind dKind = D_free) {
return MallocMemAux(C, CE,
state->getSVal(SizeEx, C.getLocationContext()),
- Init, state);
+ Init, state, dKind);
}
static ProgramStateRef MallocMemAux(CheckerContext &C, const CallExpr *CE,
SVal SizeEx, SVal Init,
- ProgramStateRef state);
+ ProgramStateRef state,
+ DeallocatorKind dKind = D_free);
/// Update the RefState to reflect the new memory allocation.
static ProgramStateRef MallocUpdateRefState(CheckerContext &C,
- const CallExpr *CE,
- ProgramStateRef state);
+ const Expr *E,
+ ProgramStateRef state,
+ DeallocatorKind dKind = D_free);
ProgramStateRef FreeMemAttr(CheckerContext &C, const CallExpr *CE,
const OwnershipAttr* Att) const;
@@ -225,8 +274,11 @@
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 ReportOffsetFree(CheckerContext &C, SVal ArgVal, SourceRange Range)const;
+ void ReportBadFree(CheckerContext &C, SVal ArgVal, SourceRange range,
+ const Expr *DeallocExpr, const RefState *RS = 0) const;
+ void ReportOffsetFree(CheckerContext &C, SVal ArgVal, SourceRange Range,
+ const Expr *DeallocExpr,
+ const RefState *RS = 0) const;
/// Find the location of the allocation for Sym on the path leading to the
/// exploded node N.
@@ -439,6 +491,32 @@
return false;
}
+bool MallocChecker::isDefaultNonptrplacementNewDelete(const FunctionDecl *FD,
+ CheckerContext &C) const {
+ if (!FD)
+ return false;
+
+ if (FD->getDeclName().getNameKind() != DeclarationName::CXXOperatorName)
+ return false;
+
+ OverloadedOperatorKind kind = FD->getDeclName().getCXXOverloadedOperator();
+ if (kind != OO_New && kind != OO_Array_New &&
+ kind != OO_Delete && kind != OO_Array_Delete)
+ return false;
+
+ // Skip custom new operators
+ if (!FD->isImplicit() &&
+ !C.getSourceManager().isInSystemHeader(FD->getLocStart()))
+ return false;
+
+ if (FD->isReservedGlobalPlacementOperator())
+ return false;
+
+ // One of the standard new/new[]/delete/delete[] operators including placement
+ // nothrow versions
+ return true;
+}
+
void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const {
if (C.wasInlined)
return;
@@ -470,6 +548,16 @@
State = MallocUpdateRefState(C, CE, State);
} else if (FunI == II_strndup) {
State = MallocUpdateRefState(C, CE, State);
+ } else if (isDefaultNonptrplacementNewDelete(FD, C)) {
+ OverloadedOperatorKind K = FD->getDeclName().getCXXOverloadedOperator();
+ if (K == OO_New)
+ State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State,
+ D_delete);
+ else if (K == OO_Array_New)
+ State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State,
+ D_deleteArray);
+ else
+ State = FreeMemAux(C, CE, State, 0, false, ReleasedAllocatedMemory);
}
}
@@ -495,6 +583,52 @@
C.addTransition(State);
}
+void MallocChecker::checkPostStmt(const CXXNewExpr *NE,
+ CheckerContext &C) const {
+
+ FunctionDecl *OperatorNew = NE->getOperatorNew();
+ if (!OperatorNew)
+ return;
+
+ // Skip custom new operators
+ if (!OperatorNew->isImplicit() &&
+ !C.getSourceManager().isInSystemHeader(OperatorNew->getLocStart()) &&
+ !NE->isGlobalNew())
+ return;
+
+ // Skip standard global placement operator new/new[](std::size_t, void * p);
+ // process all other standard new/new[] operators including placement
+ // operators new/new[](std::size_t, const std::nothrow_t&)
+ if (OperatorNew->isReservedGlobalPlacementOperator())
+ return;
+
+ ProgramStateRef State = C.getState();
+ // the return value from operator new is already bound and we don't want to
+ // break this binding so we call MallocUpdateRefState instead of MallocMemAux
+ State = MallocUpdateRefState(C, NE, State, NE->isArray() ? D_deleteArray
+ : D_delete);
+ C.addTransition(State);
+}
+
+void MallocChecker::checkPreStmt(const CXXDeleteExpr *DE,
+ CheckerContext &C) const {
+ FunctionDecl *OperatorDelete = DE->getOperatorDelete();
+ if (!OperatorDelete)
+ return;
+
+ // Skip custom delete operators
+ if (!OperatorDelete->isImplicit() &&
+ !C.getSourceManager().isInSystemHeader(OperatorDelete->getLocStart()))
+ return;
+
+ ProgramStateRef State = C.getState();
+ bool ReleasedAllocated = false;
+ State = FreeMemAux(C, DE->getArgument(), DE, State,
+ /*Hold*/false, ReleasedAllocated);
+
+ C.addTransition(State);
+}
+
static bool isFreeWhenDoneSetToZero(const ObjCMethodCall &Call) {
Selector S = Call.getSelector();
for (unsigned i = 1; i < S.getNumArgs(); ++i)
@@ -514,7 +648,6 @@
// 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.
bool ReleasedAllocatedMemory = false;
Selector S = Call.getSelector();
if ((S.getNameForSlot(0) == "dataWithBytesNoCopy" ||
@@ -547,7 +680,8 @@
ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C,
const CallExpr *CE,
SVal Size, SVal Init,
- ProgramStateRef state) {
+ ProgramStateRef state,
+ DeallocatorKind dKind) {
// 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
@@ -582,14 +716,15 @@
assert(state);
}
- return MallocUpdateRefState(C, CE, state);
+ return MallocUpdateRefState(C, CE, state, dKind);
}
ProgramStateRef MallocChecker::MallocUpdateRefState(CheckerContext &C,
- const CallExpr *CE,
- ProgramStateRef state) {
+ const Expr *E,
+ ProgramStateRef state,
+ DeallocatorKind dKind) {
// Get the return value.
- SVal retVal = state->getSVal(CE, C.getLocationContext());
+ SVal retVal = state->getSVal(E, C.getLocationContext());
// We expect the malloc functions to return a pointer.
if (!isa<Loc>(retVal))
@@ -598,8 +733,12 @@
SymbolRef Sym = retVal.getAsLocSymbol();
assert(Sym);
+ const FunctionDecl *FD = 0;
+ if (const CallExpr *CE = dyn_cast<CallExpr>(E))
+ FD = C.getCalleeDecl(CE);
+
// Set the symbol's state to Allocated.
- return state->set<RegionState>(Sym, RefState::getAllocated(CE));
+ return state->set<RegionState>(Sym, RefState::getAllocated(dKind, E, FD));
}
@@ -652,6 +791,99 @@
return false;
}
+DeallocatorKind MallocChecker::GetDeallocKind(CheckerContext &C,
+ const Expr *E) const {
+ if (!E)
+ return D_unknown;
+
+ if (const CallExpr *CE = dyn_cast<CallExpr>(E)) {
+ const FunctionDecl *FD = C.getCalleeDecl(CE);
+ ASTContext &Ctx = C.getASTContext();
+
+ if (isFreeFunction(FD, Ctx))
+ return D_free;
+
+ if (isDefaultNonptrplacementNewDelete(FD, C)) {
+ OverloadedOperatorKind kind =
+ FD->getDeclName().getCXXOverloadedOperator();
+ if (kind == OO_New)
+ return D_delete;
+ else if (kind == OO_Array_New)
+ return D_deleteArray;
+ }
+
+ return D_unknown;
+ }
+
+ if (const CXXDeleteExpr *DE = dyn_cast_or_null<CXXDeleteExpr>(E))
+ return DE->isArrayForm() ? D_deleteArray : D_delete;
+
+ return D_unknown;
+}
+
+void MallocChecker::PrintAllocDeallocName(raw_ostream &os, CheckerContext &C,
+ const Expr *E) const {
+
+ if (!E)
+ return;
+
+ // get the exact name of an allocation function
+ if (const CallExpr *CE = dyn_cast<CallExpr>(E)) {
+ if (const FunctionDecl *FD = CE->getDirectCallee()) {
+ if (FD->getKind() == Decl::Function) {
+ os << *FD;
+ if (FD->getDeclName().getNameKind() != DeclarationName::CXXOperatorName)
+ os << "()";
+ return;
+ }
+ }
+ }
+
+ if (const CXXNewExpr *NE = dyn_cast<CXXNewExpr>(E)) {
+ os << "operator new" << (NE->isArray() ? "[]" : "");
+ return;
+ }
+
+ if (const CXXDeleteExpr *DE = dyn_cast<CXXDeleteExpr>(E)) {
+ os << "operator delete" << (DE->isArrayForm() ? "[]" : "");
+ return;
+ }
+
+ if (isa<ObjCMessageExpr>(E)) {
+ os << "Objective-C method";
+ return;
+ }
+
+ os << "unknown means";
+}
+
+void MallocChecker::PrintAllocDeallocName(raw_ostream &os, CheckerContext &C,
+ const RefState *RS) const {
+ if (const FunctionDecl *FD = RS->getCalleeDecl()) {
+ if (FD->getKind() == Decl::Function) {
+ os << *FD;
+ if (FD->getDeclName().getNameKind() != DeclarationName::CXXOperatorName)
+ os << "()";
+ return;
+ }
+ }
+
+ PrintAllocDeallocName(os, C, cast<Expr>(RS->getStmt()));
+}
+
+void MallocChecker::PrintExpectedAllocName(raw_ostream &os, CheckerContext &C,
+ const Expr *E) const {
+ DeallocatorKind dKind = GetDeallocKind(C, E);
+
+ switch(dKind) {
+ case D_free: os << "malloc()"; return;
+ case D_delete: os << "operator new"; return;
+ case D_deleteArray: os << "operator new[]"; return;
+ case D_unknown: os << "unknown means"; return;
+ default: assert(0 && "unhandled DeallocatorKind");
+ }
+}
+
ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C,
const Expr *ArgExpr,
const Expr *ParentExpr,
@@ -685,7 +917,7 @@
// 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;
}
@@ -693,13 +925,14 @@
// 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;
}
const MemSpaceRegion *MS = R->getMemorySpace();
- // Parameters, locals, statics, and globals shouldn't be freed.
+ // Parameters, locals, statics, globals, and memory returned by alloca()
+ // shouldn't be freed.
if (!(isa<UnknownSpaceRegion>(MS) || isa<HeapSpaceRegion>(MS))) {
// FIXME: at the time this code was written, malloc() regions were
// represented by conjured symbols, which are all in UnknownSpaceRegion.
@@ -709,7 +942,7 @@
// 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;
}
@@ -746,6 +979,14 @@
return 0;
}
+ // Check if an expected deallocation function matches real one
+ if (RsBase &&
+ RsBase->getDeallocKind() != D_unknown &&
+ RsBase->getDeallocKind() != GetDeallocKind(C, ParentExpr) ) {
+ ReportBadFree(C, ArgVal, 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();
@@ -753,7 +994,7 @@
Offset.isValid() &&
!Offset.hasSymbolicOffset() &&
Offset.getOffset() != 0) {
- ReportOffsetFree(C, ArgVal, ArgExpr->getSourceRange());
+ ReportOffsetFree(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr, RsBase);
return 0;
}
@@ -868,38 +1109,49 @@
}
}
-void MallocChecker::ReportBadFree(CheckerContext &C, SVal ArgVal,
- SourceRange range) const {
+void MallocChecker::ReportBadFree(CheckerContext &C, SVal ArgVal,
+ SourceRange range, const Expr *DeallocExpr,
+ const RefState *RS) const {
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()";
+
+ os << "Argument to ";
+ PrintAllocDeallocName(os, C, DeallocExpr);
+ if (RS) {
+ os << " was allocated by ";
+ PrintAllocDeallocName(os, C, RS);
+ os << ", not ";
+ } else {
+ if (MR) {
+ while (const ElementRegion *ER = dyn_cast<ElementRegion>(MR))
+ MR = ER->getSuperRegion();
+
+ // Special case for alloca()
+ if (isa<AllocaRegion>(MR))
+ os << " was allocated by alloca(), not ";
+ else {
+ os << " is ";
+ if (SummarizeRegion(os, MR))
+ os << ", which is not memory allocated by ";
+ else
+ os << "not memory allocated by ";
+ }
+ } else {
+ os << " is ";
+ if (SummarizeValue(os, ArgVal))
+ os << ", which is not memory allocated by ";
else
- os << "not memory allocated by malloc()";
+ os << "not memory allocated by ";
}
- } else {
- os << "Argument to free() is ";
- if (SummarizeValue(os, ArgVal))
- os << ", which is not memory allocated by malloc()";
- else
- os << "not memory allocated by malloc()";
}
-
+ PrintExpectedAllocName(os, C, DeallocExpr);
+
BugReport *R = new BugReport(*BT_BadFree, os.str(), N);
R->markInteresting(MR);
R->addRange(range);
@@ -908,7 +1160,8 @@
}
void MallocChecker::ReportOffsetFree(CheckerContext &C, SVal ArgVal,
- SourceRange Range) const {
+ SourceRange Range, const Expr *DeallocExpr,
+ const RefState *RS) const {
ExplodedNode *N = C.generateSink();
if (N == NULL)
return;
@@ -930,11 +1183,17 @@
int offsetBytes = Offset.getOffset() / C.getASTContext().getCharWidth();
- os << "Argument to free() is offset by "
+ os << "Argument to ";
+ PrintAllocDeallocName(os, C, DeallocExpr);
+ os << " is offset by "
<< offsetBytes
<< " "
<< ((abs(offsetBytes) > 1) ? "bytes" : "byte")
- << " from the start of memory allocated by malloc()";
+ << " from the start of memory allocated by ";
+ if (RS)
+ PrintAllocDeallocName(os, C, RS);
+ else
+ PrintExpectedAllocName(os, C, DeallocExpr);
BugReport *R = new BugReport(*BT_OffsetFree, os.str(), N);
R->markInteresting(MR->getBaseRegion());
@@ -1343,7 +1602,8 @@
if (RS->isReleased()) {
if (I.getData().Kind == RPToBeFreedAfterFailure)
state = state->set<RegionState>(ReallocSym,
- RefState::getAllocated(RS->getStmt()));
+ RefState::getAllocated(RS->getDeallocKind(), RS->getStmt(),
+ RS->getCalleeDecl()));
else if (I.getData().Kind == RPDoNotTrackAfterFailure)
state = state->remove<RegionState>(ReallocSym);
else
Index: test/Analysis/alloc-match-dealloc.cpp
===================================================================
--- test/Analysis/alloc-match-dealloc.cpp (revision 0)
+++ test/Analysis/alloc-match-dealloc.cpp (working copy)
@@ -0,0 +1,119 @@
+// RUN: %clang_cc1 -analyze -analyzer-checker=core,alpha.unix.MallocWithAnnotations -verify %s
+
+#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 delete expression
+void testDeleteExpr1() {
+ int *p = (int *)malloc(sizeof(int));
+ delete p; // expected-warning{{Argument to operator delete was allocated by malloc(), not operator new}}
+}
+
+void testDeleteExpr2() {
+ int *p = (int *)malloc(8);
+ int *q = (int *)realloc(p, 16);
+ delete q; // expected-warning{{Argument to operator delete was allocated by realloc(), not operator new}}
+}
+
+void testDeleteExpr3() {
+ int *p = (int *)calloc(1, sizeof(int));
+ delete p; // expected-warning{{Argument to operator delete was allocated by calloc(), not operator new}}
+}
+
+void testDeleteExpr4(const char *s) {
+ char *p = strdup(s);
+ delete p; // expected-warning{{Argument to operator delete was allocated by strdup(), not operator new}}
+}
+
+void testDeleteExpr5() {
+ int *p = (int *)my_malloc(sizeof(int));
+ delete p; // expected-warning{{Argument to operator delete was allocated by my_malloc(), not operator new}}
+}
+
+void testDeleteExpr6() {
+ int *p = (int *)__builtin_alloca(sizeof(int));
+ delete p; // expected-warning{{Argument to operator delete was allocated by alloca(), not operator new}}
+}
+
+void testDeleteExpr7() {
+ int *p = new int[1];
+ delete p; // expected-warning{{Argument to operator delete was allocated by operator new[], not operator new}}
+}
+
+void testDeleteExpr8() {
+ int *p = (int *)operator new[](0);
+ delete p; // expected-warning{{Argument to operator delete was allocated by operator new[], not operator new}}
+}
+
+//--------------- test operator delete
+void testDeleteOp1() {
+ int *p = (int *)malloc(sizeof(int));
+ operator delete(p); // FIXME: should complain "Argument to operator delete() was allocated by malloc(), not operator new"
+}
+
+//--------------- test delete[] expression
+void testDeleteArrayExpr1() {
+ int *p = (int *)malloc(sizeof(int));
+ delete[] p; // expected-warning{{Argument to operator delete[] was allocated by malloc(), not operator new[]}}
+}
+
+void testDeleteArrayExpr2() {
+ int *p = new int;
+ delete[] p; // expected-warning{{Argument to operator delete[] was allocated by operator new, not operator new[]}}
+}
+
+void testDeleteArrayExpr3() {
+ int *p = (int *)operator new(0);
+ delete[] p; // expected-warning{{Argument to operator delete[] was allocated by operator new, not operator new[]}}
+}
+
+//--------------- test operator delete[]
+void testDeleteArrayOp1() {
+ int *p = (int *)malloc(sizeof(int));
+ operator delete[](p); // FIXME: should complain "Argument to operator delete[]() was allocated by malloc(), not operator new"
+}
+
+//--------------- test free()
+void testFree1() {
+ int *p = new int;
+ free(p); // expected-warning{{Argument to free() was allocated by operator new, not malloc()}}
+}
+
+void testFree2() {
+ int *p = (int *)operator new(0);
+ free(p); // expected-warning{{Argument to free() was allocated by operator new, not malloc()}}
+}
+
+void testFree3() {
+ int *p = new int[1];
+ free(p); // expected-warning{{Argument to free() was allocated by operator new[], not malloc()}}
+}
+
+//--------------- test realloc()
+void testRealloc1() {
+ int *p = new int;
+ realloc(p, sizeof(long)); // expected-warning{{Argument to realloc() was allocated by operator new, not malloc()}}
+}
+
+void testRealloc2() {
+ int *p = (int *)operator new(0);
+ realloc(p, sizeof(long)); // expected-warning{{Argument to realloc() was allocated by operator new, not malloc()}}
+}
+
+void testRealloc3() {
+ int *p = new int[1];
+ realloc(p, sizeof(long)); // expected-warning{{Argument to realloc() was allocated by operator new[], not malloc()}}
+}
Index: test/Analysis/inline.cpp
===================================================================
--- test/Analysis/inline.cpp (revision 174894)
+++ test/Analysis/inline.cpp (working copy)
@@ -255,6 +255,7 @@
IntWrapper *obj = new IntWrapper(42);
// should be TRUE
clang_analyzer_eval(obj->value == 42); // expected-warning{{UNKNOWN}}
+ delete obj;
}
void testPlacement() {
Index: test/Analysis/Inputs/system-header-simulator-cxx.h
===================================================================
--- test/Analysis/Inputs/system-header-simulator-cxx.h (revision 174894)
+++ test/Analysis/Inputs/system-header-simulator-cxx.h (working copy)
@@ -59,4 +59,28 @@
return 0;
}
};
+
+ class bad_alloc : public exception {
+ public:
+ bad_alloc() throw();
+ bad_alloc(const bad_alloc&) throw();
+ bad_alloc& operator=(const bad_alloc&) throw();
+ virtual const char* what() const throw() {
+ return 0;
+ }
+ };
+
+ struct nothrow_t {};
+
+ extern const nothrow_t nothrow;
}
+
+void* operator new(std::size_t, const std::nothrow_t&) throw();
+void* operator new[](std::size_t, const std::nothrow_t&) throw();
+void operator delete(void*, const std::nothrow_t&) throw();
+void operator delete[](void*, const std::nothrow_t&) throw();
+
+void* operator new (std::size_t size, void* ptr) throw() { return ptr; };
+void* operator new[] (std::size_t size, void* ptr) throw() { return ptr; };
+void operator delete (void* ptr, void*) throw() {};
+void operator delete[] (void* ptr, void*) throw() {};
Index: test/Analysis/new.cpp
===================================================================
--- test/Analysis/new.cpp (revision 174894)
+++ test/Analysis/new.cpp (working copy)
@@ -1,4 +1,5 @@
// RUN: %clang_cc1 -analyze -analyzer-checker=core,unix.Malloc,debug.ExprInspection -analyzer-store region -std=c++11 -verify %s
+#include "Inputs/system-header-simulator-cxx.h"
void clang_analyzer_eval(bool);
@@ -19,13 +20,6 @@
clang_analyzer_eval(someGlobal == 0); // expected-warning{{TRUE}}
}
-
-// This is the standard placement new.
-inline void* operator new(size_t, void* __p) throw()
-{
- return __p;
-}
-
void *testPlacementNew() {
int *x = (int *)malloc(sizeof(int));
*x = 1;
@@ -73,7 +67,6 @@
clang_analyzer_eval(*n == 0); // expected-warning{{TRUE}}
}
-
struct PtrWrapper {
int *x;
@@ -85,7 +78,80 @@
return new PtrWrapper(static_cast<int *>(malloc(4)));
}
+//--------------------------------
+// unix.Malloc checks
+//--------------------------------
+void testGlobalExprNewBeforeOverload() {
+ int *p = new int;
+} // expected-warning{{Memory is never released; potential leak}}
+
+void testGlobalOpNewBeforeOverload() {
+ void *p = operator new(0);
+} // expected-warning{{Memory is never released; potential leak}}
+
+void *operator new(size_t);
+void *operator new(size_t, double d);
+void *operator new[](size_t);
+void *operator new[](size_t, double d);
+
+void testExprPlacementNew() {
+ int i;
+ int *p1 = new(&i) int; // no warn - standard placement new
+
+ int *p2 = new(1.0) int; // no warn - overloaded placement new
+
+ int *p3 = new (std::nothrow) int;
+} // expected-warning{{Memory is never released; potential leak}}
+
+void testExprPlacementNewArray() {
+ int i;
+ int *p1 = new(&i) int[1]; // no warn - standard placement new[]
+
+ int *p2 = new(1.0) int[1]; // no warn - overloaded placement new[]
+
+ int *p3 = new (std::nothrow) int[1];
+} // expected-warning{{Memory is never released; potential leak}}
+
+void testCustomOpNew() {
+ void *p = operator new(0);
+} // no warn - custom new
+
+void testGlobalExprNew() {
+ void *p = ::new int;
+} // expected-warning{{Memory is never released; potential leak}}
+
+void testCustomExprNew() {
+ int *p = new int;
+} // no warn - custom new
+
+void testGlobalExprNewArray() {
+ void *p = ::new int[1];
+} // expected-warning{{Memory is never released; potential leak}}
+
+void testOverloadedExprNewArray() {
+ int *p = new int[1];
+} // no warn - custom new[]
+
+// test if unix.Malloc processes operator delete
+void testExprDeleteArg() {
+ int i;
+ delete &i; // expected-warning{{Argument to operator delete is the address of the local variable 'i', which is not memory allocated by operator new}}
+}
+
+// test if unix.Malloc handles operator delete[]
+void testExprDeleteArrArg() {
+ int i;
+ delete[] &i; // expected-warning{{Argument to operator delete[] is the address of the local variable 'i', which is not memory allocated by operator new[]}}
+}
+
+// test for proper allocator/deallocator names in the warning
+void testAllocDeallocNames() {
+ int *p = ::new int[1];
+ p += 1;
+ delete[] (p); // expected-warning{{Argument to operator delete[] is offset by 4 bytes from the start of memory allocated by operator new[]}}
+}
+
//--------------------------------
// Incorrectly-modelled behavior
//--------------------------------
@@ -95,8 +161,10 @@
// Should warn that *n is uninitialized.
if (*n) { // no-warning
+ delete n;
return 0;
}
+ delete n;
return 1;
}
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits