[clang] [analyzer] Fix a false memory leak reports involving placement new (PR #144341)
Arseniy Zaostrovnykh via cfe-commits
cfe-commits at lists.llvm.org
Mon Jun 16 05:48:05 PDT 2025
https://github.com/necto updated https://github.com/llvm/llvm-project/pull/144341
>From 44fe99cfc6ab394ba94301b94323be87f98c06dd Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <necto.ne at gmail.com>
Date: Fri, 13 Jun 2025 17:43:57 +0200
Subject: [PATCH 1/3] [analyzer] Fix a false memory leak report involving
placement new
Placement new does not allocate memory, so it should not be reported as
a memory leak. A recent MallocChecker refactor changed inlining of
placement-new calls with manual evaluation by MallocChecker.
https://github.com/llvm/llvm-project/commit/339282d49f5310a2837da45c0ccc19da15675554
This change avoids marking the value returned by placement new as
allocated and hence avoids the false leak reports.
---
CPP-6375
---
.../StaticAnalyzer/Checkers/MallocChecker.cpp | 21 ++++++++++
.../test/Analysis/NewDelete-checker-test.cpp | 41 ++++++++++++++++++-
2 files changed, 60 insertions(+), 2 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
index fef33509c0b6e..4a78f64797bba 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
@@ -1371,6 +1371,19 @@ void MallocChecker::checkIfFreeNameIndex(ProgramStateRef State,
C.addTransition(State);
}
+bool isVoidStar(QualType T) {
+ return !T.isNull() && T->isPointerType() && T->getPointeeType()->isVoidType();
+}
+
+const Expr* getPlacementNewBufferArg(const CallExpr *CE, const FunctionDecl *FD) {
+ if (CE->getNumArgs() == 1)
+ return nullptr;
+ // Second argument of placement new must be void*
+ if (!isVoidStar(FD->getParamDecl(1)->getType()))
+ return nullptr;
+ return CE->getArg(1);
+}
+
void MallocChecker::checkCXXNewOrCXXDelete(ProgramStateRef State,
const CallEvent &Call,
CheckerContext &C) const {
@@ -1386,6 +1399,14 @@ void MallocChecker::checkCXXNewOrCXXDelete(ProgramStateRef State,
// processed by the checkPostStmt callbacks for CXXNewExpr and
// CXXDeleteExpr.
const FunctionDecl *FD = C.getCalleeDecl(CE);
+ if (const auto *BufArg = getPlacementNewBufferArg(CE, FD)) {
+ // Placement new does not allocate memory
+ auto RetVal = State->getSVal(BufArg, Call.getLocationContext());
+ State = State->BindExpr(CE, C.getLocationContext(), RetVal);
+ C.addTransition(State);
+ return;
+ }
+
switch (FD->getOverloadedOperator()) {
case OO_New:
State = MallocMemAux(C, Call, CE->getArg(0), UndefinedVal(), State,
diff --git a/clang/test/Analysis/NewDelete-checker-test.cpp b/clang/test/Analysis/NewDelete-checker-test.cpp
index 06754f669b1e6..0820600a63559 100644
--- a/clang/test/Analysis/NewDelete-checker-test.cpp
+++ b/clang/test/Analysis/NewDelete-checker-test.cpp
@@ -26,9 +26,10 @@
// RUN: -analyzer-checker=cplusplus.NewDeleteLeaks
//
// RUN: %clang_analyze_cc1 -std=c++17 -fblocks -verify %s \
-// RUN: -verify=expected,leak \
+// RUN: -verify=expected,leak,inspection \
// RUN: -analyzer-checker=core \
-// RUN: -analyzer-checker=cplusplus.NewDeleteLeaks
+// RUN: -analyzer-checker=cplusplus.NewDeleteLeaks \
+// RUN: -analyzer-checker=debug.ExprInspection
#include "Inputs/system-header-simulator-cxx.h"
@@ -63,6 +64,42 @@ void testGlobalNoThrowPlacementExprNewBeforeOverload() {
int *p = new(std::nothrow) int;
} // leak-warning{{Potential leak of memory pointed to by 'p'}}
+//----- Standard pointer placement operators
+void testGlobalPointerPlacementNew() {
+ int i;
+ void *p1 = operator new(0, &i); // no warn
+
+ void *p2 = operator new[](0, &i); // no warn
+
+ int *p3 = new(&i) int; // no warn
+
+ int *p4 = new(&i) int[0]; // no warn
+}
+
+template<typename T>
+void clang_analyzer_dump(T x);
+
+void testPlacementNewBufValue() {
+ int i = 10;
+ int *p = new(&i) int;
+ clang_analyzer_dump(p); // inspection-warning{{&i}}
+ clang_analyzer_dump(*p); // inspection-warning{{10}}
+}
+
+void testPlacementNewBufValueExplicitOp() {
+ int i = 10;
+ int *p = (int*)operator new(sizeof(int), &i);
+ clang_analyzer_dump(p); // inspection-warning{{&i}}
+ clang_analyzer_dump(*p); // inspection-warning{{10}}
+}
+
+void testPlacementArrNewBufValueExplicitArrOp() {
+ int i = 10;
+ int *p = (int*)operator new[](sizeof(int), &i);
+ clang_analyzer_dump(p); // inspection-warning{{&i}}
+ clang_analyzer_dump(*p); // inspection-warning{{10}}
+}
+
//----- Other cases
void testNewMemoryIsInHeap() {
int *p = new int;
>From b18957fbc53bdb68245ea87ce09441f31eb9c809 Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <necto.ne at gmail.com>
Date: Mon, 16 Jun 2025 14:38:15 +0200
Subject: [PATCH 2/3] [NFC] Fix formatting
---
clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
index 4a78f64797bba..c0b08d9a28ffa 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
@@ -1375,7 +1375,8 @@ bool isVoidStar(QualType T) {
return !T.isNull() && T->isPointerType() && T->getPointeeType()->isVoidType();
}
-const Expr* getPlacementNewBufferArg(const CallExpr *CE, const FunctionDecl *FD) {
+const Expr *getPlacementNewBufferArg(const CallExpr *CE,
+ const FunctionDecl *FD) {
if (CE->getNumArgs() == 1)
return nullptr;
// Second argument of placement new must be void*
>From 2b861b6bf1f01ac2376dffe2bdedf32d823797dd Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <necto.ne at gmail.com>
Date: Mon, 16 Jun 2025 14:47:21 +0200
Subject: [PATCH 3/3] Use isVoidPointerType() and state the checked signature
---
clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
index c0b08d9a28ffa..ebd98c27a8001 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
@@ -1371,16 +1371,15 @@ void MallocChecker::checkIfFreeNameIndex(ProgramStateRef State,
C.addTransition(State);
}
-bool isVoidStar(QualType T) {
- return !T.isNull() && T->isPointerType() && T->getPointeeType()->isVoidType();
-}
-
const Expr *getPlacementNewBufferArg(const CallExpr *CE,
const FunctionDecl *FD) {
- if (CE->getNumArgs() == 1)
+ // Checking for signature:
+ // void* operator new ( std::size_t count, void* ptr );
+ // void* operator new[]( std::size_t count, void* ptr );
+ if (CE->getNumArgs() != 2)
return nullptr;
- // Second argument of placement new must be void*
- if (!isVoidStar(FD->getParamDecl(1)->getType()))
+ auto BuffType = FD->getParamDecl(1)->getType();
+ if (BuffType.isNull() || !BuffType->isVoidPointerType())
return nullptr;
return CE->getArg(1);
}
More information about the cfe-commits
mailing list