frederic-tingaud-sonarsource created this revision.
frederic-tingaud-sonarsource added a reviewer: dcoughlin.
Herald added subscribers: manas, ASDenysPetrov, martong, dkrupp, donat.nagy, 
Szelethus, a.sidorin, baloghadamsoftware.
Herald added a project: All.
frederic-tingaud-sonarsource requested review of this revision.
Herald added a project: clang.
Herald added a subscriber: cfe-commits.

Currently, when encountering an overridden new operator, the StaticAnalyzer 
will inline it when possible, while an overridden delete operator will never 
be, which leads us to false positives like the following:

  struct CustomOperators {
    void *operator new(size_t count) {
      return malloc(count);
    }
  
    void operator delete(void *addr) {
      free(addr);
    }
  };
  
  void compliant() {
    auto *a = new CustomOperators();
    delete a; // warning{{Potential leak of memory pointed to by 'a'}}
  }

This patch restores the symmetry between how operator new and operator delete 
are handled by also inlining the content of operator delete when possible.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D124845

Files:
  clang/include/clang/Analysis/ConstructionContext.h
  clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
  clang/lib/StaticAnalyzer/Core/CallEvent.cpp
  clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
  clang/test/Analysis/cxxnewexpr-callback-inline.cpp
  clang/test/Analysis/cxxnewexpr-callback-noinline.cpp
  clang/test/Analysis/dtor.cpp

Index: clang/test/Analysis/dtor.cpp
===================================================================
--- clang/test/Analysis/dtor.cpp
+++ clang/test/Analysis/dtor.cpp
@@ -570,3 +570,32 @@
   // no-crash
 }
 } // namespace dtor_over_loc_concrete_int
+
+// Test overriden new/delete operators
+struct CustomOperators {
+  void *operator new(size_t count) {
+    return malloc(count);
+  }
+
+  void operator delete(void *addr) {
+    free(addr);
+  }
+
+private:
+  int i;
+};
+
+void compliant() {
+  auto *a = new CustomOperators();
+  delete a;
+}
+
+void overrideLeak() {
+  auto *a = new CustomOperators();
+} // expected-warning{{Potential leak of memory pointed to by 'a'}}
+
+void overrideDoubleDelete() {
+  auto *a = new CustomOperators();
+  delete a;
+  delete a; // expected-warning@581 {{Attempt to free released memory}}
+}
Index: clang/test/Analysis/cxxnewexpr-callback-noinline.cpp
===================================================================
--- clang/test/Analysis/cxxnewexpr-callback-noinline.cpp
+++ clang/test/Analysis/cxxnewexpr-callback-noinline.cpp
@@ -3,13 +3,16 @@
 #include "Inputs/system-header-simulator-cxx.h"
 
 namespace std {
-  void *malloc(size_t);
-}
+void *malloc(size_t);
+void free(void *);
+} // namespace std
 
 void *operator new(size_t size) { return std::malloc(size); }
+void operator delete(void *ptr) { std::free(ptr); }
 
 struct S {
   S() {}
+  ~S() {}
 };
 
 void foo();
@@ -17,13 +20,35 @@
 void test() {
   S *s = new S();
   foo();
+  delete s;
 }
 
+/*
+void test() {
+  S *s = new S();
 // CHECK:      PreCall (S::S)
 // CHECK-NEXT: PostCall (S::S)
 // CHECK-NEXT: PreStmt<CXXNewExpr>
 // CHECK-NEXT: PostStmt<CXXNewExpr>
+  foo();
 // CHECK-NEXT: PreCall (foo)
 // CHECK-NEXT: PostCall (foo)
+  delete s;
+// CHECK-NEXT: PreCall (S::~S)
+// CHECK-NEXT: PostCall (S::~S)
+// CHECK-NEXT: PreCall (operator delete)
+// CHECK-NEXT: PostCall (operator delete)
+}
+
+void operator delete(void *ptr) {
+  std::free(ptr);
+// CHECK-NEXT: PreCall (std::free)
+// CHECK-NEXT: PostCall (std::free)
+}
+
+void *operator new(size_t size) {
+  return std::malloc(size);
 // CHECK-NEXT: PreCall (std::malloc)
 // CHECK-NEXT: PostCall (std::malloc)
+}
+*/
Index: clang/test/Analysis/cxxnewexpr-callback-inline.cpp
===================================================================
--- clang/test/Analysis/cxxnewexpr-callback-inline.cpp
+++ clang/test/Analysis/cxxnewexpr-callback-inline.cpp
@@ -4,12 +4,15 @@
 
 namespace std {
   void *malloc(size_t);
+  void free(void *);
 }
 
 void *operator new(size_t size) { return std::malloc(size); }
+void operator delete(void *ptr) { std::free(ptr); }
 
 struct S {
   S() {}
+  ~S() {}
 };
 
 void foo();
@@ -17,8 +20,12 @@
 void test() {
   S *s = new S();
   foo();
+  delete s;
 }
 
+/*
+void test() {
+  S *s = new S();
 // CHECK:      PreCall (operator new)
 // CHECK-NEXT: PreCall (std::malloc)
 // CHECK-NEXT: PostCall (std::malloc)
@@ -28,5 +35,15 @@
 // CHECK-NEXT: PostCall (S::S)
 // CHECK-NEXT: PreStmt<CXXNewExpr>
 // CHECK-NEXT: PostStmt<CXXNewExpr>
+  foo();
 // CHECK-NEXT: PreCall (foo)
 // CHECK-NEXT: PostCall (foo)
+  delete s;
+// CHECK-NEXT: PreCall (S::~S)
+// CHECK-NEXT: PostCall (S::~S)
+// CHECK-NEXT: PreCall (operator delete)
+// CHECK-NEXT: PreCall (std::free)
+// CHECK-NEXT: PostCall (std::free)
+// CHECK-NEXT: PostCall (operator delete)
+}
+*/
Index: clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
===================================================================
--- clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
+++ clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
@@ -945,8 +945,17 @@
 
   ExplodedNodeSet DstPreCall;
   getCheckerManager().runCheckersForPreCall(DstPreCall, Pred, *Call, *this);
+  ExplodedNodeSet DstPostCall;
 
-  getCheckerManager().runCheckersForPostCall(Dst, DstPreCall, *Call, *this);
+  if (AMgr.getAnalyzerOptions().MayInlineCXXAllocator) {
+    StmtNodeBuilder Bldr(DstPreCall, DstPostCall, *currBldrCtx);
+    for (ExplodedNode *I : DstPreCall) {
+      defaultEvalCall(Bldr, I, *Call);
+    }
+  } else {
+    DstPostCall = DstPreCall;
+  }
+  getCheckerManager().runCheckersForPostCall(Dst, DstPostCall, *Call, *this);
 }
 
 void ExprEngine::VisitCXXCatchStmt(const CXXCatchStmt *CS, ExplodedNode *Pred,
Index: clang/lib/StaticAnalyzer/Core/CallEvent.cpp
===================================================================
--- clang/lib/StaticAnalyzer/Core/CallEvent.cpp
+++ clang/lib/StaticAnalyzer/Core/CallEvent.cpp
@@ -1408,6 +1408,8 @@
     return getSimpleCall(CE, State, LC);
   } else if (const auto *NE = dyn_cast<CXXNewExpr>(S)) {
     return getCXXAllocatorCall(NE, State, LC);
+  } else if (const auto *DE = dyn_cast<CXXDeleteExpr>(S)) {
+    return getCXXDeallocatorCall(DE, State, LC);
   } else if (const auto *ME = dyn_cast<ObjCMessageExpr>(S)) {
     return getObjCMethodCall(ME, State, LC);
   } else {
Index: clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
===================================================================
--- clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
+++ clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
@@ -1276,7 +1276,7 @@
   getCaller(const StackFrameContext *CalleeCtx, ProgramStateRef State);
 
   /// Gets a call event for a function call, Objective-C method call,
-  /// or a 'new' call.
+  /// a 'new', or a 'delete' call.
   CallEventRef<>
   getCall(const Stmt *S, ProgramStateRef State,
           const LocationContext *LC);
Index: clang/include/clang/Analysis/ConstructionContext.h
===================================================================
--- clang/include/clang/Analysis/ConstructionContext.h
+++ clang/include/clang/Analysis/ConstructionContext.h
@@ -120,7 +120,8 @@
   ConstructionContextItem(const Expr *E, unsigned Index)
       : Data(E), Kind(ArgumentKind), Index(Index) {
     assert(isa<CallExpr>(E) || isa<CXXConstructExpr>(E) ||
-           isa<CXXInheritedCtorInitExpr>(E) || isa<ObjCMessageExpr>(E));
+           isa<CXXDeleteExpr>(E) || isa<CXXInheritedCtorInitExpr>(E) ||
+           isa<ObjCMessageExpr>(E));
   }
 
   ConstructionContextItem(const CXXCtorInitializer *Init)
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to