On 12.03.2013 22:06, Anna Zaks wrote:
Thanks Anton! The patch looks good overall. Have you evaluated it on
some real C++ codebases?
Not yet. Failed to launch scan-build with my Perl 5.16.2.
Below are some comments.
---
+ // 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() ?
AF_CXXNewArray
+ : AF_CXXNew);
Why is this different from handling malloc? MallocMemAux does break
the binding formed by default handling of malloc and forms a new
binding with more semantic information. (I am fine with addressing
this after the initial commit/commits.)
In case of 'new' the memory can be initialized to a non-default value
(e.g. int *p = new int(3); ). The existing logic of MallocMemAux()
breaks the binding and information about the initialization value is
lost. This causes several tests to fail.
Changed the comment to be more informative.
---
def MallocOptimistic : Checker<"MallocWithAnnotations">,
- HelpText<"Check for memory leaks, double free, and use-after-free
problems. Assumes that all user-defined functions which might free a
pointer are annotated.">,
+ HelpText<"Check for memory leaks, double free, and use-after-free
problems. Traces memory managed by malloc()/free(). Assumes that all
user-defined functions which might free a pointer are annotated.">,
Shouldn't the MallocWithAnnotations only check the functions which are
explicitly annotated? I think it might be better to change the code
rather than the comment.
Currently MallocWithAnnotations is designed as extended unix.Malloc and
it is not a single line change to make it distinct. Can we address this
after primary commits?
---
+ unsigned K : 2; // Kind enum, but stored as a bitfield.
+ unsigned Family : 30; // Rest of 32-bit word, currently just an
allocation
+ // family.
We usually add the comment on a line preceding the declaration, like this:
+ // Kind enum, but stored as a bitfield.
+ unsigned K : 2;
+ // Rest of 32-bit word, currently just an allocation family.
+ unsigned Family : 30;
---
+ // Check if an expected deallocation function matches the real one.
+ if (RsBase &&
+ RsBase->getAllocationFamily() != AF_None &&
+ RsBase->getAllocationFamily() != getAllocationFamily(C,
ParentExpr) ) {
Is it possible to have AF_None family here? Shouldn't "
RsBase->getAllocationFamily() != AF_None" be inside an assert?
It is possible. In the example below
initWithCharactersNoCopy:length:freeWhenDone takes ownership of memory
allocated by unknown means, RefState with AF_None is bound to the call
and after, when free() is processed, we have
RsBase->getAllocationFamily() == AF_None.
void test(unichar *characters) {
NSString *string = [[NSString alloc]
initWithCharactersNoCopy:characters length:12 freeWhenDone:1];
if (!string) {free(characters);}
}
---
+// Used to check correspondence between allocators and deallocators.
+enum AllocationFamily {
The comment should describe what family is. It is a central notion for
the checker and I do not think we explain it anywhere.
---
The patch is very long. Is it possible to split it up into smaller
chunks
(http://llvm.org/docs/DeveloperPolicy.html#incremental-development)?
Committed the initial refactoring as r176949.
Let's start! Attached is the cplusplus.NewDelete checker separated from
the patch.
Thanks,
Anna.
On Mar 12, 2013, at 8:56 AM, Anton Yartsev <[email protected]
<mailto:[email protected]>> wrote:
On 12.03.2013 2:24, Jordan Rose wrote:
Looking over this one last time...
- 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) {
+ SmallString<100> TempBuf;
+ llvm::raw_svector_ostream TempOs(TempBuf);
+ if (printAllocDeallocName(TempOs, C, AllocExpr))
+ os << "memory allocated by " << TempOs.str();
+ else
+ os << "allocated memory";
+ } else
+ printExpectedAllocName(os, C, DeallocExpr);
+
The way you've set it up, AllocExpr will never be NULL (which is
good, because otherwise you'd get "...from the start of malloc()"
rather than "from the start of memory allocated by malloc()").
Strange logic. Fixed.
+@interface Wrapper : NSData
+- (id)initWithBytesNoCopy:(void *)bytes length:(NSUInteger)len;
+@end
As I discovered with the rest of the ObjC patch, this isn't a great
test case because the analyzer doesn't consider it a system method.
However, I don't see you use it anywhere in the file anyway, so you
can probably just remove it.
+void testNew11(NSUInteger dataLength) {
+ int *data = new int;
+ NSData *nsdata = [NSData dataWithBytesNoCopy:data
length:sizeof(int) freeWhenDone:1]; // expected-warning{{Memory
allocated by 'new' should be deallocated by 'delete', not
+dataWithBytesNoCopy:length:freeWhenDone:}}
+}
Hm, that is rather unwieldy, but what bothers me more is that
+dataWithBytesNoCopy:length:freeWhenDone:/doesn't/ free the memory;
it just takes ownership of it. I guess it's okay to leave that as a
FIXME for now, but in the long run we should say something like
"+dataWithBytesNoCopy:length:freeWhenDone: cannot take ownership of
memory allocated by 'new'." (In the "hold" cases, most likely the
user wasn't intending to free
But, this doesn't have to block the patch; you/we can fix it
post-commit.
+ delete x; // FIXME: Shoud detect pointer escape and keep silent.
checkPointerEscape() is not currently invoked for delete.
Pedantic note: the real issue here is that we don't model delete at
all (see ExprEngine::VisitCXXDeleteExpr). The correct model won't
explicitly invoke checkPointerEscape, but it will construct a
CallEvent for the deletion operator and then try to evaluate that
call—or at least invalidate the arguments like VisitCXXNewExpr does
for placement args—which will cause the argument region to get
invalidated and checkPointerEscape to be triggered.
Jordan
Updated patch attached.
--
Anton
<MallocChecker_v11.patch>
--
Anton
Index: lib/Driver/Tools.cpp
===================================================================
--- lib/Driver/Tools.cpp (revision 176943)
+++ lib/Driver/Tools.cpp (working copy)
@@ -1890,6 +1890,9 @@
CmdArgs.push_back("-analyzer-checker=deadcode");
+ if (types::isCXX(Inputs[0].getType()))
+ CmdArgs.push_back("-analyzer-checker=cplusplus");
+
// Enable the following experimental checkers for testing.
CmdArgs.push_back("-analyzer-checker=security.insecureAPI.UncheckedReturn");
CmdArgs.push_back("-analyzer-checker=security.insecureAPI.getpw");
Index: lib/StaticAnalyzer/Checkers/Checkers.td
===================================================================
--- lib/StaticAnalyzer/Checkers/Checkers.td (revision 176943)
+++ lib/StaticAnalyzer/Checkers/Checkers.td (working copy)
@@ -166,6 +166,14 @@
// C++ checkers.
//===----------------------------------------------------------------------===//
+let ParentPackage = Cplusplus in {
+
+def NewDeleteChecker : Checker<"NewDelete">,
+ HelpText<"Check for memory leaks, double free, and use-after-free problems. Traces memory managed by new/delete.">,
+ DescFile<"MallocChecker.cpp">;
+
+} // end: "cplusplus"
+
let ParentPackage = CplusplusAlpha in {
def VirtualCallChecker : Checker<"VirtualCall">,
@@ -276,7 +284,7 @@
DescFile<"UnixAPIChecker.cpp">;
def MallocPessimistic : Checker<"Malloc">,
- HelpText<"Check for memory leaks, double free, and use-after-free problems.">,
+ HelpText<"Check for memory leaks, double free, and use-after-free problems. Traces memory managed by malloc()/free().">,
DescFile<"MallocChecker.cpp">;
def MallocSizeofChecker : Checker<"MallocSizeof">,
@@ -292,7 +300,7 @@
DescFile<"ChrootChecker.cpp">;
def MallocOptimistic : Checker<"MallocWithAnnotations">,
- HelpText<"Check for memory leaks, double free, and use-after-free problems. Assumes that all user-defined functions which might free a pointer are annotated.">,
+ HelpText<"Check for memory leaks, double free, and use-after-free problems. Traces memory managed by malloc()/free(). Assumes that all user-defined functions which might free a pointer are annotated.">,
DescFile<"MallocChecker.cpp">;
def PthreadLockChecker : Checker<"PthreadLock">,
Index: lib/StaticAnalyzer/Checkers/MallocChecker.cpp
===================================================================
--- lib/StaticAnalyzer/Checkers/MallocChecker.cpp (revision 176956)
+++ lib/StaticAnalyzer/Checkers/MallocChecker.cpp (working copy)
@@ -120,6 +120,8 @@
check::PreStmt<ReturnStmt>,
check::PreStmt<CallExpr>,
check::PostStmt<CallExpr>,
+ check::PostStmt<CXXNewExpr>,
+ check::PreStmt<CXXDeleteExpr>,
check::PostStmt<BlockExpr>,
check::PostObjCMessage,
check::Location,
@@ -142,12 +144,15 @@
struct ChecksFilter {
DefaultBool CMallocPessimistic;
DefaultBool CMallocOptimistic;
+ DefaultBool CNewDeleteChecker;
};
ChecksFilter Filter;
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;
@@ -174,6 +179,7 @@
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 isStandardNewDelete(const FunctionDecl *FD, ASTContext &C) const;
///@}
static ProgramStateRef MallocMemReturnsAttr(CheckerContext &C,
const CallExpr *CE,
@@ -192,7 +198,7 @@
/// Update the RefState to reflect the new memory allocation.
static ProgramStateRef MallocUpdateRefState(CheckerContext &C,
- const CallExpr *CE,
+ const Expr *E,
ProgramStateRef state);
ProgramStateRef FreeMemAttr(CheckerContext &C, const CallExpr *CE,
@@ -216,8 +222,7 @@
///\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;
+ bool checkUseAfterFree(SymbolRef Sym, CheckerContext &C, const Stmt *S) const;
/// Check if the function is known not to free memory, or if it is
/// "interesting" and should be modeled explicitly.
@@ -398,6 +403,9 @@
if (isAllocationFunction(FD, C))
return true;
+ if (isStandardNewDelete(FD, C))
+ return true;
+
return false;
}
@@ -449,6 +457,37 @@
return false;
}
+bool MallocChecker::isStandardNewDelete(const FunctionDecl *FD,
+ ASTContext &C) const {
+ if (!FD)
+ return false;
+
+ OverloadedOperatorKind Kind = FD->getOverloadedOperator();
+ 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;
+
+ // Return true if tested operator is a standard placement nothrow operator.
+ if (FD->getNumParams() == 2) {
+ QualType T = FD->getParamDecl(1)->getType();
+ if (const IdentifierInfo *II = T.getBaseTypeIdentifier())
+ return II->getName().equals("nothrow_t");
+ }
+
+ // Skip placement operators.
+ if (FD->getNumParams() != 1 || FD->isVariadic())
+ return false;
+
+ // One of the standard new/new[]/delete/delete[] non-placement operators.
+ return true;
+}
+
+
void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const {
if (C.wasInlined)
return;
@@ -464,23 +503,43 @@
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, ReleasedAllocatedMemory);
- } else if (FunI == II_strdup) {
- State = MallocUpdateRefState(C, CE, State);
- } else if (FunI == II_strndup) {
- State = MallocUpdateRefState(C, CE, State);
+ if (Filter.CMallocOptimistic || Filter.CMallocPessimistic) {
+ 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, ReleasedAllocatedMemory);
+ } else if (FunI == II_strdup) {
+ State = MallocUpdateRefState(C, CE, State);
+ } else if (FunI == II_strndup) {
+ State = MallocUpdateRefState(C, CE, State);
+ }
}
+
+ if (Filter.CNewDeleteChecker) {
+ 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
+ // processed by the checkPostStmt callbacks for CXXNewExpr and
+ // CXXDeleteExpr.
+ OverloadedOperatorKind K = FD->getOverloadedOperator();
+ if (K == OO_New)
+ State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State);
+ else if (K == OO_Array_New)
+ State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State);
+ else if (K == OO_Delete || K == OO_Array_Delete)
+ State = FreeMemAux(C, CE, State, 0, false, ReleasedAllocatedMemory);
+ else
+ llvm_unreachable("not a new/delete operator");
+ }
+ }
}
if (Filter.CMallocOptimistic) {
@@ -505,6 +564,51 @@
C.addTransition(State);
}
+void MallocChecker::checkPostStmt(const CXXNewExpr *NE,
+ CheckerContext &C) const {
+
+ if (NE->getNumPlacementArgs())
+ for (CXXNewExpr::const_arg_iterator I = NE->placement_arg_begin(),
+ E = NE->placement_arg_end(); I != E; ++I)
+ if (SymbolRef Sym = C.getSVal(*I).getAsSymbol())
+ checkUseAfterFree(Sym, C, *I);
+
+ if (!Filter.CNewDeleteChecker)
+ return;
+
+ if (!isStandardNewDelete(NE->getOperatorNew(), C.getASTContext()))
+ return;
+
+ ProgramStateRef State = C.getState();
+ // The return value from operator new is bound to a specified initialization
+ // 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);
+ C.addTransition(State);
+}
+
+void MallocChecker::checkPreStmt(const CXXDeleteExpr *DE,
+ CheckerContext &C) const {
+
+ if (!Filter.CNewDeleteChecker) {
+ if (SymbolRef Sym = C.getSVal(DE->getArgument()).getAsSymbol())
+ checkUseAfterFree(Sym, C, DE->getArgument());
+
+ return;
+ }
+
+ if (!isStandardNewDelete(DE->getOperatorDelete(), C.getASTContext()))
+ return;
+
+ ProgramStateRef State = C.getState();
+ bool ReleasedAllocated;
+ State = FreeMemAux(C, DE->getArgument(), DE, State,
+ /*Hold*/false, ReleasedAllocated);
+
+ C.addTransition(State);
+}
+
static bool isKnownDeallocObjCMethodName(const ObjCMethodCall &Call) {
// If the first selector piece is one of the names below, assume that the
// object takes ownership of the memory, promising to eventually deallocate it
@@ -607,10 +711,10 @@
}
ProgramStateRef MallocChecker::MallocUpdateRefState(CheckerContext &C,
- const CallExpr *CE,
+ const Expr *E,
ProgramStateRef state) {
// 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 (!retVal.getAs<Loc>())
@@ -620,7 +724,7 @@
assert(Sym);
// Set the symbol's state to Allocated.
- return state->set<RegionState>(Sym, RefState::getAllocated(CE));
+ return state->set<RegionState>(Sym, RefState::getAllocated(E));
}
@@ -1244,9 +1348,14 @@
void MallocChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const {
// We will check for double free in the post visit.
- if (isFreeFunction(C.getCalleeDecl(CE), C.getASTContext()))
+ if ((Filter.CMallocOptimistic || Filter.CMallocPessimistic) &&
+ isFreeFunction(C.getCalleeDecl(CE), C.getASTContext()))
return;
+ if (Filter.CNewDeleteChecker &&
+ isStandardNewDelete(C.getCalleeDecl(CE), C.getASTContext()))
+ return;
+
// Check use after free, when a freed pointer is passed to a call.
ProgramStateRef State = C.getState();
for (CallExpr::const_arg_iterator I = CE->arg_begin(),
@@ -1684,3 +1793,4 @@
REGISTER_CHECKER(MallocPessimistic)
REGISTER_CHECKER(MallocOptimistic)
+REGISTER_CHECKER(NewDeleteChecker)
Index: test/Analysis/inline.cpp
===================================================================
--- test/Analysis/inline.cpp (revision 176943)
+++ test/Analysis/inline.cpp (working copy)
@@ -288,6 +288,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 176943)
+++ 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 176943)
+++ test/Analysis/new.cpp (working copy)
@@ -1,9 +1,11 @@
// 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);
typedef __typeof__(sizeof(int)) size_t;
extern "C" void *malloc(size_t);
+extern "C" void free(void *);
int someGlobal;
void testImplicitlyDeclaredGlobalNew() {
@@ -19,13 +21,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 +68,6 @@
clang_analyzer_eval(*n == 0); // expected-warning{{TRUE}}
}
-
struct PtrWrapper {
int *x;
@@ -85,7 +79,56 @@
return new PtrWrapper(static_cast<int *>(malloc(4)));
}
+//--------------------------------------------------------------------
+// Check for intersection with other checkers from MallocChecker.cpp
+// bounded with unix.Malloc
+//--------------------------------------------------------------------
+// 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
+}
+
+// unix.Malloc does not know about operators new/delete.
+void testDeleteMallocked() {
+ int *x = (int *)malloc(sizeof(int));
+ delete x; // FIXME: Shoud detect pointer escape and keep silent after 'delete' is modeled properly.
+} // expected-warning{{Memory is never released; potential leak}}
+
+void testDeleteOpAfterFree() {
+ int *p = (int *)malloc(sizeof(int));
+ free(p);
+ operator delete(p); // expected-warning{{Use of memory after it is freed}}
+}
+
+void testDeleteAfterFree() {
+ int *p = (int *)malloc(sizeof(int));
+ free(p);
+ delete p; // expected-warning{{Use of memory after it is freed}}
+}
+
+void testStandardPlacementNewAfterFree() {
+ int *p = (int *)malloc(sizeof(int));
+ free(p);
+ p = new(p) int; // expected-warning{{Use of memory after it is freed}}
+}
+
+void testCustomPlacementNewAfterFree() {
+ int *p = (int *)malloc(sizeof(int));
+ free(p);
+ p = new(0, p) int; // expected-warning{{Use of memory after it is freed}}
+}
+
//--------------------------------
// Incorrectly-modelled behavior
//--------------------------------
@@ -95,8 +138,10 @@
// Should warn that *n is uninitialized.
if (*n) { // no-warning
+ delete n;
return 0;
}
+ delete n;
return 1;
}
Index: test/Analysis/NewDelete-checker-test.mm
===================================================================
--- test/Analysis/NewDelete-checker-test.mm (revision 0)
+++ test/Analysis/NewDelete-checker-test.mm (working copy)
@@ -0,0 +1,146 @@
+// RUN: %clang_cc1 -analyze -analyzer-checker=core,cplusplus.NewDelete -analyzer-store region -std=c++11 -fblocks -verify %s
+#include "Inputs/system-header-simulator-cxx.h"
+#include "Inputs/system-header-simulator-objc.h"
+
+typedef __typeof__(sizeof(int)) size_t;
+extern "C" void *malloc(size_t);
+extern "C" void free(void *);
+
+//------------------
+// check for leaks
+//------------------
+
+void testGlobalExprNewBeforeOverload1() {
+ int *p = new int;
+} // expected-warning{{Memory is never released; potential leak}}
+
+void testGlobalExprNewBeforeOverload2() {
+ 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(std::size_t);
+void *operator new(std::size_t, double d);
+void *operator new[](std::size_t);
+void *operator new[](std::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 - call to a custom new
+}
+
+void testGlobalExprNew() {
+ void *p = ::new int; // no warn - call to a custom new
+}
+
+void testCustomExprNew() {
+ int *p = new int; // no warn - call to a custom new
+}
+
+void testGlobalExprNewArray() {
+ void *p = ::new int[1]; // no warn - call to a custom new
+}
+
+void testOverloadedExprNewArray() {
+ int *p = new int[1]; // no warn - call to a custom new
+}
+
+//---------------
+// other checks
+//---------------
+
+void f(int *);
+
+void testUseAfterDelete() {
+ int *p = new int;
+ delete p;
+ f(p); // expected-warning{{Use of memory after it is freed}}
+}
+
+void testDeleteAlloca() {
+ int *p = (int *)__builtin_alloca(sizeof(int));
+ delete p; // expected-warning{{Argument to free() was allocated by alloca(), not malloc()}}
+}
+
+void testDoubleDelete() {
+ int *p = new int;
+ delete p;
+ delete p; // expected-warning{{Attempt to free released memory}}
+}
+
+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'
+
+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[]'
+
+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[]'
+
+//----------------------------------------------------------------------------
+// Check for intersections with unix.Malloc and unix.MallocWithAnnotations
+// checkers bounded with cplusplus.NewDelete.
+//----------------------------------------------------------------------------
+
+// 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 testFreeNewed() {
+ int *p = new int;
+ free(p); // pointer escaped, no-warning
+}
+
+void testObjcFreeNewed() {
+ int *p = new int;
+ NSData *nsdata = [NSData dataWithBytesNoCopy:p length:sizeof(int) freeWhenDone:1]; // pointer escaped, no-warning
+}
+
+void testFreeAfterDelete() {
+ int *p = new int;
+ delete p;
+ free(p); // expected-warning{{Use of memory after it is freed}}
+}
+
+void testStandardPlacementNewAfterDelete() {
+ int *p = new int;
+ delete p;
+ p = new(p) int; // expected-warning{{Use of memory after it is freed}}
+}
Index: utils/analyzer/SATestBuild.py
===================================================================
--- utils/analyzer/SATestBuild.py (revision 176943)
+++ utils/analyzer/SATestBuild.py (working copy)
@@ -169,7 +169,7 @@
# The list of checkers used during analyzes.
# Currently, consists of all the non experimental checkers.
-Checkers="alpha.unix.SimpleStream,alpha.security.taint,core,deadcode,security,unix,osx"
+Checkers="alpha.unix.SimpleStream,alpha.security.taint,core,cplusplus,deadcode,security,unix,osx"
Verbose = 1
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits