-
Notifications
You must be signed in to change notification settings - Fork 14k
[analyzer] Fix a false memory leak reports involving placement new #144341
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[analyzer] Fix a false memory leak reports involving placement new #144341
Conversation
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. llvm@339282d This change avoids marking the value returned by placement new as allocated and hence avoids the false leak reports. --- CPP-6375
@llvm/pr-subscribers-clang Author: Arseniy Zaostrovnykh (necto) ChangesPlacement 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. 339282d This change avoids marking the value returned by placement new as allocated and hence avoids the false leak reports. Note that the there are two syntaxes to invoke placement new: CPP-6375 Full diff: https://github.com/llvm/llvm-project/pull/144341.diff 2 Files Affected:
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;
|
@llvm/pr-subscribers-clang-static-analyzer-1 Author: Arseniy Zaostrovnykh (necto) ChangesPlacement 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. 339282d This change avoids marking the value returned by placement new as allocated and hence avoids the false leak reports. Note that the there are two syntaxes to invoke placement new: CPP-6375 Full diff: https://github.com/llvm/llvm-project/pull/144341.diff 2 Files Affected:
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;
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
Co-authored-by: Balázs Benics <108414871+balazs-benics-sonarsource@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, nice little patch 😄
…lvm#144341) 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. llvm@339282d This change avoids marking the value returned by placement new as allocated and hence avoids the false leak reports. Note that the there are two syntaxes to invoke placement new: `new (p) int` and an explicit operator call `operator new(sizeof(int), p)`. The first syntax was already properly handled by the engine. This change corrects handling of the second syntax. CPP-6375
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. 339282d
This change avoids marking the value returned by placement new as allocated and hence avoids the false leak reports.
Note that the there are two syntaxes to invoke placement new:
new (p) int
and an explicit operator calloperator new(sizeof(int), p)
. The first syntax was already properly handled by the engine. This change corrects handling of the second syntax.CPP-6375