From 697462881c4b9b704c7859f4bab0a6116c684bb1 Mon Sep 17 00:00:00 2001 From: Anton Yartsev Date: Thu, 28 Mar 2013 16:10:38 +0000 Subject: [PATCH] [analyzer] For now assume all standard global 'operator new' functions allocate memory in heap. + Improved test coverage for cplusplus.NewDelete checker. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@178244 91177308-0d34-0410-b5e6-96231b3b80d8 --- lib/StaticAnalyzer/Checkers/MallocChecker.cpp | 9 +- lib/StaticAnalyzer/Core/ExprEngineCXX.cpp | 30 +++++- test/Analysis/NewDelete-checker-test.mm | 99 ++++++++++--------- test/Analysis/NewDelete-custom.cpp | 57 +++++++++++ test/Analysis/NewDelete-variadic.cpp | 19 ++++ 5 files changed, 158 insertions(+), 56 deletions(-) create mode 100644 test/Analysis/NewDelete-custom.cpp create mode 100644 test/Analysis/NewDelete-variadic.cpp diff --git a/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index 068f9ce822..2598445866 100644 --- a/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -457,6 +457,10 @@ bool MallocChecker::isFreeFunction(const FunctionDecl *FD, ASTContext &C) const return false; } +// Tells if the callee is one of the following: +// 1) A global non-placement new/delete operator function. +// 2) A global placement operator function with the single placement argument +// of type std::nothrow_t. bool MallocChecker::isStandardNewDelete(const FunctionDecl *FD, ASTContext &C) const { if (!FD) @@ -467,9 +471,8 @@ bool MallocChecker::isStandardNewDelete(const FunctionDecl *FD, Kind != OO_Delete && Kind != OO_Array_Delete) return false; - // Skip custom new operators. - if (!FD->isImplicit() && - !C.getSourceManager().isInSystemHeader(FD->getLocStart())) + // Skip all operator new/delete methods. + if (isa(FD)) return false; // Return true if tested operator is a standard placement nothrow operator. diff --git a/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp index 0fedf255d6..c1dd6b2220 100644 --- a/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp +++ b/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp @@ -278,11 +278,32 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred, unsigned blockCount = currBldrCtx->blockCount(); const LocationContext *LCtx = Pred->getLocationContext(); - DefinedOrUnknownSVal symVal = svalBuilder.conjureSymbolVal(0, CNE, LCtx, - CNE->getType(), - blockCount); - ProgramStateRef State = Pred->getState(); + DefinedOrUnknownSVal symVal = UnknownVal(); + FunctionDecl *FD = CNE->getOperatorNew(); + + bool IsStandardGlobalOpNewFunction = false; + if (FD && !isa(FD) && !FD->isVariadic()) { + if (FD->getNumParams() == 2) { + QualType T = FD->getParamDecl(1)->getType(); + if (const IdentifierInfo *II = T.getBaseTypeIdentifier()) + // NoThrow placement new behaves as a standard new. + IsStandardGlobalOpNewFunction = II->getName().equals("nothrow_t"); + } + else + // Placement forms are considered non-standard. + IsStandardGlobalOpNewFunction = (FD->getNumParams() == 1); + } + + // We assume all standard global 'operator new' functions allocate memory in + // heap. We realize this is an approximation that might not correctly model + // a custom global allocator. + if (IsStandardGlobalOpNewFunction) + symVal = svalBuilder.getConjuredHeapSymbolVal(CNE, LCtx, blockCount); + else + symVal = svalBuilder.conjureSymbolVal(0, CNE, LCtx, CNE->getType(), + blockCount); + ProgramStateRef State = Pred->getState(); CallEventManager &CEMgr = getStateManager().getCallEventManager(); CallEventRef Call = CEMgr.getCXXAllocatorCall(CNE, State, LCtx); @@ -296,7 +317,6 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred, // is not declared as non-throwing, failures /must/ be signalled by // exceptions, and thus the return value will never be NULL. // C++11 [basic.stc.dynamic.allocation]p3. - FunctionDecl *FD = CNE->getOperatorNew(); if (FD && getContext().getLangOpts().CXXExceptions) { QualType Ty = FD->getType(); if (const FunctionProtoType *ProtoType = Ty->getAs()) diff --git a/test/Analysis/NewDelete-checker-test.mm b/test/Analysis/NewDelete-checker-test.mm index 417e9770b8..64e9c82bb9 100644 --- a/test/Analysis/NewDelete-checker-test.mm +++ b/test/Analysis/NewDelete-checker-test.mm @@ -11,68 +11,63 @@ 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; +//----- Standard non-placement operators +void testGlobalOpNew() { + void *p = operator new(0); } // expected-warning{{Memory is never released; potential leak}} -void testGlobalOpNewBeforeOverload() { - void *p = operator new(0); +void testGlobalOpNewArray() { + void *p = operator new[](0); } // expected-warning{{Memory is never released; potential leak}} -void testMemIsOnHeap() { +void testGlobalNewExpr() { 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 +void testGlobalNewExprArray() { + int *p = new int[0]; +} // expected-warning{{Memory is never released; potential leak}} - int *p2 = new(1.0) int; // no warn - overloaded placement new +//----- Standard nothrow placement operators +void testGlobalNoThrowPlacementOpNewBeforeOverload() { + void *p = operator new(0, std::nothrow); +} // expected-warning{{Memory is never released; potential leak}} - int *p3 = new (std::nothrow) int; +void testGlobalNoThrowPlacementExprNewBeforeOverload() { + int *p = new(std::nothrow) int; } // expected-warning{{Memory is never released; potential leak}} -void testExprPlacementNewArray() { + +//----- Standard pointer placement operators +void testGlobalPointerPlacementNew() { 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[] + void *p1 = operator new(0, &i); // no warn - int *p3 = new (std::nothrow) int[1]; -} // expected-warning{{Memory is never released; potential leak}} + void *p2 = operator new[](0, &i); // no warn -void testCustomOpNew() { - void *p = operator new(0); // no warn - call to a custom new -} + int *p3 = new(&i) int; // no warn -void testGlobalExprNew() { - void *p = ::new int; // no warn - call to a custom new + int *p4 = new(&i) int[0]; // no warn } -void testCustomExprNew() { - int *p = new int; // no warn - call to a custom new +//----- Other cases +void testNewMemoryIsInHeap() { + int *p = new int; + if (global != p) // condition is always true as 'p' wraps a heap region that + // is different from a region wrapped by 'global' + global = p; // pointer escapes } -void testGlobalExprNewArray() { - void *p = ::new int[1]; // no warn - call to a custom new -} +struct PtrWrapper { + int *x; + + PtrWrapper(int *input) : x(input) {} +}; -void testOverloadedExprNewArray() { - int *p = new int[1]; // no warn - call to a custom new +void testNewInvalidationPlacement(PtrWrapper *w) { + // Ensure that we don't consider this a leak. + new (w) PtrWrapper(new int); // no warn } //--------------- @@ -121,22 +116,30 @@ void testAllocDeallocNames() { // malloc()/free() are subjects of unix.Malloc and unix.MallocWithAnnotations void testMallocFreeNoWarn() { int i; - free(&i); // no-warning + free(&i); // no warn int *p1 = (int *)malloc(sizeof(int)); - free(++p1); // no-warning + free(++p1); // no warn int *p2 = (int *)malloc(sizeof(int)); free(p2); - free(p2); // no-warning + free(p2); // no warn - int *p3 = (int *)malloc(sizeof(int)); // no-warning + int *p3 = (int *)malloc(sizeof(int)); // no warn } -void testFreeNewed() { +//----- Test free standard new +void testFreeOpNew() { + void *p = operator new(0); + free(p); +} // expected-warning{{Memory is never released; potential leak}} +// FIXME: Pointer should escape + +void testFreeNewExpr() { int *p = new int; - free(p); // pointer escaped, no-warning -} + free(p); +} // expected-warning{{Memory is never released; potential leak}} +// FIXME: Pointer should escape void testObjcFreeNewed() { int *p = new int; diff --git a/test/Analysis/NewDelete-custom.cpp b/test/Analysis/NewDelete-custom.cpp new file mode 100644 index 0000000000..47105ff4ef --- /dev/null +++ b/test/Analysis/NewDelete-custom.cpp @@ -0,0 +1,57 @@ +// 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" + +void *allocator(std::size_t size); + +void *operator new[](std::size_t size) throw() { return allocator(size); } +void *operator new(std::size_t size) throw() { return allocator(size); } +void *operator new(std::size_t size, std::nothrow_t& nothrow) throw() { return allocator(size); } +void *operator new(std::size_t, double d); + +class C { +public: + void *operator new(std::size_t); +}; + +void testNewMethod() { + void *p1 = C::operator new(0); // no warn + + C *p2 = new C; // no warn + + C *c3 = ::new C; +} // expected-warning{{Memory is never released; potential leak}} + +void testOpNewArray() { + void *p = operator new[](0); +} //FIXME: expected 'Memory is never released; potential leak' + +void testNewExprArray() { + int *p = new int[0]; +} // expected-warning{{Memory is never released; potential leak}} + +//----- Custom non-placement operators +void testOpNew() { + void *p = operator new(0); +} //FIXME: expected 'Memory is never released; potential leak' + +void testNewExpr() { + int *p = new int; +} // expected-warning{{Memory is never released; potential leak}} + +//----- Custom NoThrow placement operators +void testOpNewNoThrow() { + void *p = operator new(0, std::nothrow); +} // expected-warning{{Memory is never released; potential leak}} + +void testNewExprNoThrow() { + int *p = new(std::nothrow) int; +} // expected-warning{{Memory is never released; potential leak}} + +//----- Custom placement operators +void testOpNewPlacement() { + void *p = operator new(0, 0.1); // no warn +} + +void testNewExprPlacement() { + int *p = new(0.1) int; // no warn +} diff --git a/test/Analysis/NewDelete-variadic.cpp b/test/Analysis/NewDelete-variadic.cpp new file mode 100644 index 0000000000..4b02403c70 --- /dev/null +++ b/test/Analysis/NewDelete-variadic.cpp @@ -0,0 +1,19 @@ +// RUN: %clang_cc1 -analyze -analyzer-checker=core,cplusplus.NewDelete -analyzer-store region -std=c++11 -fblocks -verify %s +// expected-no-diagnostics + +namespace std { + typedef __typeof__(sizeof(int)) size_t; +} + +void *operator new(std::size_t, ...); +void *operator new[](std::size_t, ...); + +void testGlobalCustomVariadicNew() { + void *p1 = operator new(0); // no warn + + void *p2 = operator new[](0); // no warn + + int *p3 = new int; // no warn + + int *p4 = new int[0]; // no warn +} -- 2.40.0