[analyzer] Adds cplusplus.NewDelete checker that check for memory leaks, double free...
authorAnton Yartsev <anton.yartsev@gmail.com>
Mon, 25 Mar 2013 01:35:45 +0000 (01:35 +0000)
committerAnton Yartsev <anton.yartsev@gmail.com>
Mon, 25 Mar 2013 01:35:45 +0000 (01:35 +0000)
llvm-svn: 177849

clang/lib/Driver/Tools.cpp
clang/lib/StaticAnalyzer/Checkers/Checkers.td
clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
clang/test/Analysis/Inputs/system-header-simulator-cxx.h
clang/test/Analysis/NewDelete-checker-test.mm [new file with mode: 0644]
clang/test/Analysis/NewDelete-path-notes.cpp [new file with mode: 0644]
clang/test/Analysis/inline.cpp
clang/test/Analysis/new.cpp
clang/utils/analyzer/SATestBuild.py

index 2581ae3..bff8848 100644 (file)
@@ -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");
index bbcf9aa..83727cb 100644 (file)
@@ -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">,
index e88322d..068f9ce 100644 (file)
@@ -120,6 +120,8 @@ class MallocChecker : public Checker<check::DeadSymbols,
                                      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 @@ 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<CallExpr>(Stmt) &&
+      return (Stmt && (isa<CallExpr>(Stmt) || isa<CXXNewExpr>(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<CallExpr>(Stmt) &&
+      return (Stmt && (isa<CallExpr>(Stmt) || isa<CXXDeleteExpr>(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<Loc>())
@@ -620,7 +723,7 @@ ProgramStateRef MallocChecker::MallocUpdateRefState(CheckerContext &C,
   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,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)
index faca0b4..03de527 100644 (file)
@@ -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/clang/test/Analysis/NewDelete-checker-test.mm b/clang/test/Analysis/NewDelete-checker-test.mm
new file mode 100644 (file)
index 0000000..417e977
--- /dev/null
@@ -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/clang/test/Analysis/NewDelete-path-notes.cpp b/clang/test/Analysis/NewDelete-path-notes.cpp
new file mode 100644 (file)
index 0000000..296170a
--- /dev/null
@@ -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:  <key>diagnostics</key>
+// CHECK-NEXT:<array>
+// CHECK-NEXT: <dict>
+// CHECK-NEXT:  <key>path</key>
+// CHECK-NEXT:  <array>
+// CHECK-NEXT:   <dict>
+// CHECK-NEXT:    <key>kind</key><string>control</string>
+// CHECK-NEXT:    <key>edges</key>
+// CHECK-NEXT:     <array>
+// CHECK-NEXT:      <dict>
+// CHECK-NEXT:       <key>start</key>
+// CHECK-NEXT:        <array>
+// CHECK-NEXT:         <dict>
+// CHECK-NEXT:          <key>line</key><integer>6</integer>
+// CHECK-NEXT:          <key>col</key><integer>3</integer>
+// CHECK-NEXT:          <key>file</key><integer>0</integer>
+// CHECK-NEXT:         </dict>
+// CHECK-NEXT:         <dict>
+// CHECK-NEXT:          <key>line</key><integer>6</integer>
+// CHECK-NEXT:          <key>col</key><integer>5</integer>
+// CHECK-NEXT:          <key>file</key><integer>0</integer>
+// CHECK-NEXT:         </dict>
+// CHECK-NEXT:        </array>
+// CHECK-NEXT:       <key>end</key>
+// CHECK-NEXT:        <array>
+// CHECK-NEXT:         <dict>
+// CHECK-NEXT:          <key>line</key><integer>6</integer>
+// CHECK-NEXT:          <key>col</key><integer>12</integer>
+// CHECK-NEXT:          <key>file</key><integer>0</integer>
+// CHECK-NEXT:         </dict>
+// CHECK-NEXT:         <dict>
+// CHECK-NEXT:          <key>line</key><integer>6</integer>
+// CHECK-NEXT:          <key>col</key><integer>14</integer>
+// CHECK-NEXT:          <key>file</key><integer>0</integer>
+// CHECK-NEXT:         </dict>
+// CHECK-NEXT:        </array>
+// CHECK-NEXT:      </dict>
+// CHECK-NEXT:     </array>
+// CHECK-NEXT:   </dict>
+// CHECK-NEXT:   <dict>
+// CHECK-NEXT:    <key>kind</key><string>event</string>
+// CHECK-NEXT:    <key>location</key>
+// CHECK-NEXT:    <dict>
+// CHECK-NEXT:     <key>line</key><integer>6</integer>
+// CHECK-NEXT:     <key>col</key><integer>12</integer>
+// CHECK-NEXT:     <key>file</key><integer>0</integer>
+// CHECK-NEXT:    </dict>
+// CHECK-NEXT:    <key>ranges</key>
+// CHECK-NEXT:    <array>
+// CHECK-NEXT:      <array>
+// CHECK-NEXT:       <dict>
+// CHECK-NEXT:        <key>line</key><integer>6</integer>
+// CHECK-NEXT:        <key>col</key><integer>12</integer>
+// CHECK-NEXT:        <key>file</key><integer>0</integer>
+// CHECK-NEXT:       </dict>
+// CHECK-NEXT:       <dict>
+// CHECK-NEXT:        <key>line</key><integer>6</integer>
+// CHECK-NEXT:        <key>col</key><integer>18</integer>
+// CHECK-NEXT:        <key>file</key><integer>0</integer>
+// CHECK-NEXT:       </dict>
+// CHECK-NEXT:      </array>
+// CHECK-NEXT:    </array>
+// CHECK-NEXT:    <key>depth</key><integer>0</integer>
+// CHECK-NEXT:    <key>extended_message</key>
+// CHECK-NEXT:    <string>Memory is allocated</string>
+// CHECK-NEXT:    <key>message</key>
+// CHECK-NEXT:    <string>Memory is allocated</string>
+// CHECK-NEXT:   </dict>
+// CHECK-NEXT:   <dict>
+// CHECK-NEXT:    <key>kind</key><string>control</string>
+// CHECK-NEXT:    <key>edges</key>
+// CHECK-NEXT:     <array>
+// CHECK-NEXT:      <dict>
+// CHECK-NEXT:       <key>start</key>
+// CHECK-NEXT:        <array>
+// CHECK-NEXT:         <dict>
+// CHECK-NEXT:          <key>line</key><integer>6</integer>
+// CHECK-NEXT:          <key>col</key><integer>12</integer>
+// CHECK-NEXT:          <key>file</key><integer>0</integer>
+// CHECK-NEXT:         </dict>
+// CHECK-NEXT:         <dict>
+// CHECK-NEXT:          <key>line</key><integer>6</integer>
+// CHECK-NEXT:          <key>col</key><integer>14</integer>
+// CHECK-NEXT:          <key>file</key><integer>0</integer>
+// CHECK-NEXT:         </dict>
+// CHECK-NEXT:        </array>
+// CHECK-NEXT:       <key>end</key>
+// CHECK-NEXT:        <array>
+// CHECK-NEXT:         <dict>
+// CHECK-NEXT:          <key>line</key><integer>8</integer>
+// CHECK-NEXT:          <key>col</key><integer>3</integer>
+// CHECK-NEXT:          <key>file</key><integer>0</integer>
+// CHECK-NEXT:         </dict>
+// CHECK-NEXT:         <dict>
+// CHECK-NEXT:          <key>line</key><integer>8</integer>
+// CHECK-NEXT:          <key>col</key><integer>4</integer>
+// CHECK-NEXT:          <key>file</key><integer>0</integer>
+// CHECK-NEXT:         </dict>
+// CHECK-NEXT:        </array>
+// CHECK-NEXT:      </dict>
+// CHECK-NEXT:     </array>
+// CHECK-NEXT:   </dict>
+// CHECK-NEXT:   <dict>
+// CHECK-NEXT:    <key>kind</key><string>control</string>
+// CHECK-NEXT:    <key>edges</key>
+// CHECK-NEXT:     <array>
+// CHECK-NEXT:      <dict>
+// CHECK-NEXT:       <key>start</key>
+// CHECK-NEXT:        <array>
+// CHECK-NEXT:         <dict>
+// CHECK-NEXT:          <key>line</key><integer>8</integer>
+// CHECK-NEXT:          <key>col</key><integer>3</integer>
+// CHECK-NEXT:          <key>file</key><integer>0</integer>
+// CHECK-NEXT:         </dict>
+// CHECK-NEXT:         <dict>
+// CHECK-NEXT:          <key>line</key><integer>8</integer>
+// CHECK-NEXT:          <key>col</key><integer>4</integer>
+// CHECK-NEXT:          <key>file</key><integer>0</integer>
+// CHECK-NEXT:         </dict>
+// CHECK-NEXT:        </array>
+// CHECK-NEXT:       <key>end</key>
+// CHECK-NEXT:        <array>
+// CHECK-NEXT:         <dict>
+// CHECK-NEXT:          <key>line</key><integer>8</integer>
+// CHECK-NEXT:          <key>col</key><integer>7</integer>
+// CHECK-NEXT:          <key>file</key><integer>0</integer>
+// CHECK-NEXT:         </dict>
+// CHECK-NEXT:         <dict>
+// CHECK-NEXT:          <key>line</key><integer>8</integer>
+// CHECK-NEXT:          <key>col</key><integer>7</integer>
+// CHECK-NEXT:          <key>file</key><integer>0</integer>
+// CHECK-NEXT:         </dict>
+// CHECK-NEXT:        </array>
+// CHECK-NEXT:      </dict>
+// CHECK-NEXT:     </array>
+// CHECK-NEXT:   </dict>
+// CHECK-NEXT:   <dict>
+// CHECK-NEXT:    <key>kind</key><string>event</string>
+// CHECK-NEXT:    <key>location</key>
+// CHECK-NEXT:    <dict>
+// CHECK-NEXT:     <key>line</key><integer>8</integer>
+// CHECK-NEXT:     <key>col</key><integer>7</integer>
+// CHECK-NEXT:     <key>file</key><integer>0</integer>
+// CHECK-NEXT:    </dict>
+// CHECK-NEXT:    <key>ranges</key>
+// CHECK-NEXT:    <array>
+// CHECK-NEXT:      <array>
+// CHECK-NEXT:       <dict>
+// CHECK-NEXT:        <key>line</key><integer>8</integer>
+// CHECK-NEXT:        <key>col</key><integer>7</integer>
+// CHECK-NEXT:        <key>file</key><integer>0</integer>
+// CHECK-NEXT:       </dict>
+// CHECK-NEXT:       <dict>
+// CHECK-NEXT:        <key>line</key><integer>8</integer>
+// CHECK-NEXT:        <key>col</key><integer>7</integer>
+// CHECK-NEXT:        <key>file</key><integer>0</integer>
+// CHECK-NEXT:       </dict>
+// CHECK-NEXT:      </array>
+// CHECK-NEXT:    </array>
+// CHECK-NEXT:    <key>depth</key><integer>0</integer>
+// CHECK-NEXT:    <key>extended_message</key>
+// CHECK-NEXT:    <string>Assuming &apos;p&apos; is non-null</string>
+// CHECK-NEXT:    <key>message</key>
+// CHECK-NEXT:    <string>Assuming &apos;p&apos; is non-null</string>
+// CHECK-NEXT:   </dict>
+// CHECK-NEXT:   <dict>
+// CHECK-NEXT:    <key>kind</key><string>control</string>
+// CHECK-NEXT:    <key>edges</key>
+// CHECK-NEXT:     <array>
+// CHECK-NEXT:      <dict>
+// CHECK-NEXT:       <key>start</key>
+// CHECK-NEXT:        <array>
+// CHECK-NEXT:         <dict>
+// CHECK-NEXT:          <key>line</key><integer>8</integer>
+// CHECK-NEXT:          <key>col</key><integer>7</integer>
+// CHECK-NEXT:          <key>file</key><integer>0</integer>
+// CHECK-NEXT:         </dict>
+// CHECK-NEXT:         <dict>
+// CHECK-NEXT:          <key>line</key><integer>8</integer>
+// CHECK-NEXT:          <key>col</key><integer>7</integer>
+// CHECK-NEXT:          <key>file</key><integer>0</integer>
+// CHECK-NEXT:         </dict>
+// CHECK-NEXT:        </array>
+// CHECK-NEXT:       <key>end</key>
+// CHECK-NEXT:        <array>
+// CHECK-NEXT:         <dict>
+// CHECK-NEXT:          <key>line</key><integer>11</integer>
+// CHECK-NEXT:          <key>col</key><integer>5</integer>
+// CHECK-NEXT:          <key>file</key><integer>0</integer>
+// CHECK-NEXT:         </dict>
+// CHECK-NEXT:         <dict>
+// CHECK-NEXT:          <key>line</key><integer>11</integer>
+// CHECK-NEXT:          <key>col</key><integer>10</integer>
+// CHECK-NEXT:          <key>file</key><integer>0</integer>
+// CHECK-NEXT:         </dict>
+// CHECK-NEXT:        </array>
+// CHECK-NEXT:      </dict>
+// CHECK-NEXT:     </array>
+// CHECK-NEXT:   </dict>
+// CHECK-NEXT:   <dict>
+// CHECK-NEXT:    <key>kind</key><string>event</string>
+// CHECK-NEXT:    <key>location</key>
+// CHECK-NEXT:    <dict>
+// CHECK-NEXT:     <key>line</key><integer>11</integer>
+// CHECK-NEXT:     <key>col</key><integer>5</integer>
+// CHECK-NEXT:     <key>file</key><integer>0</integer>
+// CHECK-NEXT:    </dict>
+// CHECK-NEXT:    <key>ranges</key>
+// CHECK-NEXT:    <array>
+// CHECK-NEXT:      <array>
+// CHECK-NEXT:       <dict>
+// CHECK-NEXT:        <key>line</key><integer>11</integer>
+// CHECK-NEXT:        <key>col</key><integer>5</integer>
+// CHECK-NEXT:        <key>file</key><integer>0</integer>
+// CHECK-NEXT:       </dict>
+// CHECK-NEXT:       <dict>
+// CHECK-NEXT:        <key>line</key><integer>11</integer>
+// CHECK-NEXT:        <key>col</key><integer>12</integer>
+// CHECK-NEXT:        <key>file</key><integer>0</integer>
+// CHECK-NEXT:       </dict>
+// CHECK-NEXT:      </array>
+// CHECK-NEXT:    </array>
+// CHECK-NEXT:    <key>depth</key><integer>0</integer>
+// CHECK-NEXT:    <key>extended_message</key>
+// CHECK-NEXT:    <string>Memory is released</string>
+// CHECK-NEXT:    <key>message</key>
+// CHECK-NEXT:    <string>Memory is released</string>
+// CHECK-NEXT:   </dict>
+// CHECK-NEXT:   <dict>
+// CHECK-NEXT:    <key>kind</key><string>control</string>
+// CHECK-NEXT:    <key>edges</key>
+// CHECK-NEXT:     <array>
+// CHECK-NEXT:      <dict>
+// CHECK-NEXT:       <key>start</key>
+// CHECK-NEXT:        <array>
+// CHECK-NEXT:         <dict>
+// CHECK-NEXT:          <key>line</key><integer>11</integer>
+// CHECK-NEXT:          <key>col</key><integer>5</integer>
+// CHECK-NEXT:          <key>file</key><integer>0</integer>
+// CHECK-NEXT:         </dict>
+// CHECK-NEXT:         <dict>
+// CHECK-NEXT:          <key>line</key><integer>11</integer>
+// CHECK-NEXT:          <key>col</key><integer>10</integer>
+// CHECK-NEXT:          <key>file</key><integer>0</integer>
+// CHECK-NEXT:         </dict>
+// CHECK-NEXT:        </array>
+// CHECK-NEXT:       <key>end</key>
+// CHECK-NEXT:        <array>
+// CHECK-NEXT:         <dict>
+// CHECK-NEXT:          <key>line</key><integer>14</integer>
+// CHECK-NEXT:          <key>col</key><integer>3</integer>
+// CHECK-NEXT:          <key>file</key><integer>0</integer>
+// CHECK-NEXT:         </dict>
+// CHECK-NEXT:         <dict>
+// CHECK-NEXT:          <key>line</key><integer>14</integer>
+// CHECK-NEXT:          <key>col</key><integer>8</integer>
+// CHECK-NEXT:          <key>file</key><integer>0</integer>
+// CHECK-NEXT:         </dict>
+// CHECK-NEXT:        </array>
+// CHECK-NEXT:      </dict>
+// CHECK-NEXT:     </array>
+// CHECK-NEXT:   </dict>
+// CHECK-NEXT:   <dict>
+// CHECK-NEXT:    <key>kind</key><string>event</string>
+// CHECK-NEXT:    <key>location</key>
+// CHECK-NEXT:    <dict>
+// CHECK-NEXT:     <key>line</key><integer>14</integer>
+// CHECK-NEXT:     <key>col</key><integer>3</integer>
+// CHECK-NEXT:     <key>file</key><integer>0</integer>
+// CHECK-NEXT:    </dict>
+// CHECK-NEXT:    <key>ranges</key>
+// CHECK-NEXT:    <array>
+// CHECK-NEXT:      <array>
+// CHECK-NEXT:       <dict>
+// CHECK-NEXT:        <key>line</key><integer>14</integer>
+// CHECK-NEXT:        <key>col</key><integer>3</integer>
+// CHECK-NEXT:        <key>file</key><integer>0</integer>
+// CHECK-NEXT:       </dict>
+// CHECK-NEXT:       <dict>
+// CHECK-NEXT:        <key>line</key><integer>14</integer>
+// CHECK-NEXT:        <key>col</key><integer>10</integer>
+// CHECK-NEXT:        <key>file</key><integer>0</integer>
+// CHECK-NEXT:       </dict>
+// CHECK-NEXT:      </array>
+// CHECK-NEXT:    </array>
+// CHECK-NEXT:    <key>depth</key><integer>0</integer>
+// CHECK-NEXT:    <key>extended_message</key>
+// CHECK-NEXT:    <string>Attempt to free released memory</string>
+// CHECK-NEXT:    <key>message</key>
+// CHECK-NEXT:    <string>Attempt to free released memory</string>
+// CHECK-NEXT:   </dict>
+// CHECK-NEXT:  </array>
+// CHECK-NEXT:  <key>description</key><string>Attempt to free released memory</string>
+// CHECK-NEXT:  <key>category</key><string>Memory Error</string>
+// CHECK-NEXT:  <key>type</key><string>Double free</string>
+// CHECK-NEXT: <key>issue_context_kind</key><string>function</string>
+// CHECK-NEXT: <key>issue_context</key><string>test</string>
+// CHECK-NEXT: <key>issue_hash</key><string>9</string>
+// CHECK-NEXT: <key>location</key>
+// CHECK-NEXT: <dict>
+// CHECK-NEXT:  <key>line</key><integer>14</integer>
+// CHECK-NEXT:  <key>col</key><integer>3</integer>
+// CHECK-NEXT:  <key>file</key><integer>0</integer>
+// CHECK-NEXT: </dict>
+// CHECK-NEXT: </dict>
+// CHECK-NEXT:</array>
index 27853dc..a16fa00 100644 (file)
@@ -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() {
index fdd16da..701ac8b 100644 (file)
@@ -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<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 @@ int testNoInitialization() {
 
   // Should warn that *n is uninitialized.
   if (*n) { // no-warning
+    delete n;
     return 0;
   }
+  delete n;
   return 1;
 }
 
index 067be16..3d8f7c0 100755 (executable)
@@ -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