From 2de19edab6001d2c17720d02fe0760b9b452192a Mon Sep 17 00:00:00 2001 From: Anton Yartsev Date: Mon, 25 Mar 2013 01:35:45 +0000 Subject: [PATCH] [analyzer] Adds cplusplus.NewDelete checker that check for memory leaks, double free, and use-after-free problems of memory managed by new/delete. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@177849 91177308-0d34-0410-b5e6-96231b3b80d8 --- lib/Driver/Tools.cpp | 3 + lib/StaticAnalyzer/Checkers/Checkers.td | 12 +- lib/StaticAnalyzer/Checkers/MallocChecker.cpp | 159 +++++++-- .../Inputs/system-header-simulator-cxx.h | 24 ++ test/Analysis/NewDelete-checker-test.mm | 156 +++++++++ test/Analysis/NewDelete-path-notes.cpp | 323 ++++++++++++++++++ test/Analysis/inline.cpp | 1 + test/Analysis/new.cpp | 61 +++- utils/analyzer/SATestBuild.py | 2 +- 9 files changed, 705 insertions(+), 36 deletions(-) create mode 100644 test/Analysis/NewDelete-checker-test.mm create mode 100644 test/Analysis/NewDelete-path-notes.cpp diff --git a/lib/Driver/Tools.cpp b/lib/Driver/Tools.cpp index 2581ae36f7..bff8848db0 100644 --- a/lib/Driver/Tools.cpp +++ b/lib/Driver/Tools.cpp @@ -1911,6 +1911,9 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, 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"); diff --git a/lib/StaticAnalyzer/Checkers/Checkers.td b/lib/StaticAnalyzer/Checkers/Checkers.td index bbcf9aa438..83727cbd35 100644 --- a/lib/StaticAnalyzer/Checkers/Checkers.td +++ b/lib/StaticAnalyzer/Checkers/Checkers.td @@ -166,6 +166,14 @@ def ReturnUndefChecker : Checker<"UndefReturn">, // 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 @@ def UnixAPIChecker : Checker<"API">, 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 @@ def ChrootChecker : Checker<"Chroot">, 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">, diff --git a/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index e88322ddf5..068f9ce822 100644 --- a/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -120,6 +120,8 @@ class MallocChecker : public Checker, check::PreStmt, check::PostStmt, + check::PostStmt, + check::PreStmt, check::PostStmt, check::PostObjCMessage, check::Location, @@ -142,12 +144,15 @@ public: 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 @@ private: 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 @@ private: /// 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 @@ private: ///\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. @@ -281,14 +286,14 @@ private: inline bool isAllocated(const RefState *S, const RefState *SPrev, const Stmt *Stmt) { // Did not track -> allocated. Other state (released) -> allocated. - return (Stmt && isa(Stmt) && + return (Stmt && (isa(Stmt) || isa(Stmt)) && (S && S->isAllocated()) && (!SPrev || !SPrev->isAllocated())); } inline bool isReleased(const RefState *S, const RefState *SPrev, const Stmt *Stmt) { // Did not track -> released. Other state (allocated) -> released. - return (Stmt && isa(Stmt) && + return (Stmt && (isa(Stmt) || isa(Stmt)) && (S && S->isReleased()) && (!SPrev || !SPrev->isReleased())); } @@ -398,6 +403,9 @@ bool MallocChecker::isMemFunction(const FunctionDecl *FD, ASTContext &C) const { if (isAllocationFunction(FD, C)) return true; + if (isStandardNewDelete(FD, C)) + return true; + return false; } @@ -449,6 +457,36 @@ bool MallocChecker::isFreeFunction(const FunctionDecl *FD, ASTContext &C) const 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,22 +502,42 @@ void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const { 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"); + } } } @@ -505,6 +563,51 @@ void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const { 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 +710,10 @@ ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C, } 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()) @@ -620,7 +723,7 @@ ProgramStateRef MallocChecker::MallocUpdateRefState(CheckerContext &C, assert(Sym); // Set the symbol's state to Allocated. - return state->set(Sym, RefState::getAllocated(CE)); + return state->set(Sym, RefState::getAllocated(E)); } @@ -1244,7 +1347,12 @@ void MallocChecker::checkDeadSymbols(SymbolReaper &SymReaper, 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. @@ -1684,3 +1792,4 @@ void ento::register##name(CheckerManager &mgr) {\ REGISTER_CHECKER(MallocPessimistic) REGISTER_CHECKER(MallocOptimistic) +REGISTER_CHECKER(NewDeleteChecker) diff --git a/test/Analysis/Inputs/system-header-simulator-cxx.h b/test/Analysis/Inputs/system-header-simulator-cxx.h index faca0b41aa..03de527a02 100644 --- a/test/Analysis/Inputs/system-header-simulator-cxx.h +++ b/test/Analysis/Inputs/system-header-simulator-cxx.h @@ -59,4 +59,28 @@ namespace std { 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() {}; diff --git a/test/Analysis/NewDelete-checker-test.mm b/test/Analysis/NewDelete-checker-test.mm new file mode 100644 index 0000000000..417e9770b8 --- /dev/null +++ b/test/Analysis/NewDelete-checker-test.mm @@ -0,0 +1,156 @@ +// 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 *); +int *global; + +//------------------ +// 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 testMemIsOnHeap() { + int *p = new int; + if (global != p) + global = p; +} // expected-warning{{Memory is never released; potential leak}} +//FIXME: currently a memory region for 'new' is not a heap region, that lead to +//false-positive 'memory leak' ('global != p' is not evaluated to true and 'p' +//does not escape) + +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}} +} diff --git a/test/Analysis/NewDelete-path-notes.cpp b/test/Analysis/NewDelete-path-notes.cpp new file mode 100644 index 0000000000..296170a541 --- /dev/null +++ b/test/Analysis/NewDelete-path-notes.cpp @@ -0,0 +1,323 @@ +// RUN: %clang_cc1 -analyze -analyzer-checker=cplusplus.NewDelete -analyzer-output=text -verify %s +// RUN: %clang_cc1 -analyze -analyzer-checker=cplusplus.NewDelete -analyzer-output=plist %s -o %t.plist +// RUN: FileCheck --input-file=%t.plist %s + +void test() { + int *p = new int; + // expected-note@-1 {{Memory is allocated}} + if (p) + // expected-note@-1 {{Assuming 'p' is non-null}} + // expected-note@-2 {{Taking true branch}} + delete p; + // expected-note@-1 {{Memory is released}} + + delete p; // expected-warning {{Attempt to free released memory}} + // expected-note@-1 {{Attempt to free released memory}} +} + +// CHECK: diagnostics +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: path +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: kindcontrol +// CHECK-NEXT: edges +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: start +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line6 +// CHECK-NEXT: col3 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line6 +// CHECK-NEXT: col5 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: end +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line6 +// CHECK-NEXT: col12 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line6 +// CHECK-NEXT: col14 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: kindevent +// CHECK-NEXT: location +// CHECK-NEXT: +// CHECK-NEXT: line6 +// CHECK-NEXT: col12 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: ranges +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line6 +// CHECK-NEXT: col12 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line6 +// CHECK-NEXT: col18 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: depth0 +// CHECK-NEXT: extended_message +// CHECK-NEXT: Memory is allocated +// CHECK-NEXT: message +// CHECK-NEXT: Memory is allocated +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: kindcontrol +// CHECK-NEXT: edges +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: start +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line6 +// CHECK-NEXT: col12 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line6 +// CHECK-NEXT: col14 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: end +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line8 +// CHECK-NEXT: col3 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line8 +// CHECK-NEXT: col4 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: kindcontrol +// CHECK-NEXT: edges +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: start +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line8 +// CHECK-NEXT: col3 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line8 +// CHECK-NEXT: col4 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: end +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line8 +// CHECK-NEXT: col7 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line8 +// CHECK-NEXT: col7 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: kindevent +// CHECK-NEXT: location +// CHECK-NEXT: +// CHECK-NEXT: line8 +// CHECK-NEXT: col7 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: ranges +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line8 +// CHECK-NEXT: col7 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line8 +// CHECK-NEXT: col7 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: depth0 +// CHECK-NEXT: extended_message +// CHECK-NEXT: Assuming 'p' is non-null +// CHECK-NEXT: message +// CHECK-NEXT: Assuming 'p' is non-null +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: kindcontrol +// CHECK-NEXT: edges +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: start +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line8 +// CHECK-NEXT: col7 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line8 +// CHECK-NEXT: col7 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: end +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line11 +// CHECK-NEXT: col5 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line11 +// CHECK-NEXT: col10 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: kindevent +// CHECK-NEXT: location +// CHECK-NEXT: +// CHECK-NEXT: line11 +// CHECK-NEXT: col5 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: ranges +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line11 +// CHECK-NEXT: col5 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line11 +// CHECK-NEXT: col12 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: depth0 +// CHECK-NEXT: extended_message +// CHECK-NEXT: Memory is released +// CHECK-NEXT: message +// CHECK-NEXT: Memory is released +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: kindcontrol +// CHECK-NEXT: edges +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: start +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line11 +// CHECK-NEXT: col5 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line11 +// CHECK-NEXT: col10 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: end +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line14 +// CHECK-NEXT: col3 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line14 +// CHECK-NEXT: col8 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: kindevent +// CHECK-NEXT: location +// CHECK-NEXT: +// CHECK-NEXT: line14 +// CHECK-NEXT: col3 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: ranges +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line14 +// CHECK-NEXT: col3 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: line14 +// CHECK-NEXT: col10 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: depth0 +// CHECK-NEXT: extended_message +// CHECK-NEXT: Attempt to free released memory +// CHECK-NEXT: message +// CHECK-NEXT: Attempt to free released memory +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: descriptionAttempt to free released memory +// CHECK-NEXT: categoryMemory Error +// CHECK-NEXT: typeDouble free +// CHECK-NEXT: issue_context_kindfunction +// CHECK-NEXT: issue_contexttest +// CHECK-NEXT: issue_hash9 +// CHECK-NEXT: location +// CHECK-NEXT: +// CHECK-NEXT: line14 +// CHECK-NEXT: col3 +// CHECK-NEXT: file0 +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: diff --git a/test/Analysis/inline.cpp b/test/Analysis/inline.cpp index 27853dc2aa..a16fa00d12 100644 --- a/test/Analysis/inline.cpp +++ b/test/Analysis/inline.cpp @@ -288,6 +288,7 @@ namespace OperatorNew { IntWrapper *obj = new IntWrapper(42); // should be TRUE clang_analyzer_eval(obj->value == 42); // expected-warning{{UNKNOWN}} + delete obj; } void testPlacement() { diff --git a/test/Analysis/new.cpp b/test/Analysis/new.cpp index fdd16da3dc..701ac8bc55 100644 --- a/test/Analysis/new.cpp +++ b/test/Analysis/new.cpp @@ -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 @@ void testImplicitlyDeclaredGlobalNew() { 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 @@ void testScalarInitialization() { clang_analyzer_eval(*n == 0); // expected-warning{{TRUE}} } - struct PtrWrapper { int *x; @@ -85,6 +79,55 @@ PtrWrapper *testNewInvalidation() { return new PtrWrapper(static_cast(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 @@ int testNoInitialization() { // Should warn that *n is uninitialized. if (*n) { // no-warning + delete n; return 0; } + delete n; return 1; } diff --git a/utils/analyzer/SATestBuild.py b/utils/analyzer/SATestBuild.py index 067be162e2..3d8f7c0a14 100755 --- a/utils/analyzer/SATestBuild.py +++ b/utils/analyzer/SATestBuild.py @@ -169,7 +169,7 @@ SBOutputDirReferencePrefix = "Ref" # 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 -- 2.40.0