[analyzer] For now assume all standard global 'operator new' functions allocate memor...
authorAnton Yartsev <anton.yartsev@gmail.com>
Thu, 28 Mar 2013 16:10:38 +0000 (16:10 +0000)
committerAnton Yartsev <anton.yartsev@gmail.com>
Thu, 28 Mar 2013 16:10:38 +0000 (16:10 +0000)
+ Improved test coverage for cplusplus.NewDelete checker.

llvm-svn: 178244

clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
clang/test/Analysis/NewDelete-checker-test.mm
clang/test/Analysis/NewDelete-custom.cpp [new file with mode: 0644]
clang/test/Analysis/NewDelete-variadic.cpp [new file with mode: 0644]

index 068f9ce..2598445 100644 (file)
@@ -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<CXXMethodDecl>(FD))
     return false;
 
   // Return true if tested operator is a standard placement nothrow operator.
index 0fedf25..c1dd6b2 100644 (file)
@@ -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<CXXMethodDecl>(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<CXXAllocatorCall> 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<FunctionProtoType>())
index 417e977..64e9c82 100644 (file)
@@ -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/clang/test/Analysis/NewDelete-custom.cpp b/clang/test/Analysis/NewDelete-custom.cpp
new file mode 100644 (file)
index 0000000..47105ff
--- /dev/null
@@ -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/clang/test/Analysis/NewDelete-variadic.cpp b/clang/test/Analysis/NewDelete-variadic.cpp
new file mode 100644 (file)
index 0000000..4b02403
--- /dev/null
@@ -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
+}