[clang] [clang] Fix NewDeleteLeaks false negative for heap fields (PR #221587)
Peiqi Li via cfe-commits
cfe-commits at lists.llvm.org
Sun Sep 6 08:54:30 PDT 2026
https://github.com/voyager-jhk created https://github.com/llvm/llvm-project/pull/221587
Pointers stored in heap fields are incorrectly considered escaped, causing leaks to go unreported.
Fixes #214226
>From a4998e0693f07b00ac7f40dcd4154f1ccb53e43e Mon Sep 17 00:00:00 2001
From: voyager-jhk <voyager.lpq at gmail.com>
Date: Sun, 6 Sep 2026 23:52:43 +0800
Subject: [PATCH] [clang] Fix NewDeleteLeaks false negative for heap fields
Pointers stored in heap fields are incorrectly considered escaped,
causing leaks to go unreported.
Fixes #214226
---
clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 6 +++--
clang/test/Analysis/NewDeleteLeaks.cpp | 23 ++++++++++++++++++++
clang/test/Analysis/malloc.c | 9 +++++++-
3 files changed, 35 insertions(+), 3 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index f0f7d78fc5d50..69b091650c9f5 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -3442,7 +3442,8 @@ void ExprEngine::VisitAtomicExpr(const AtomicExpr *AE, ExplodedNode *Pred,
// A value escapes in four possible cases:
// (1) We are binding to something that is not a memory region.
-// (2) We are binding to a MemRegion that does not have stack storage.
+// (2) We are binding to a MemRegion that is not in stack, static-local,
+// or heap space.
// (3) We are binding to a top-level parameter region with a non-trivial
// destructor. We won't see the destructor during analysis, but it's there.
// (4) We are binding to a MemRegion with stack storage that the store
@@ -3455,7 +3456,8 @@ ProgramStateRef ExprEngine::processPointerEscapedOnBind(
// Cases (1) and (2).
const MemRegion *MR = LocAndVal.first.getAsRegion();
const MemSpaceRegion *Space = MR ? MR->getMemorySpace(State) : nullptr;
- if (!MR || !isa<StackSpaceRegion, StaticGlobalSpaceRegion>(Space)) {
+ if (!MR || !isa<StackSpaceRegion, StaticGlobalSpaceRegion, HeapSpaceRegion>(
+ Space)) {
Escaped.push_back(LocAndVal.second);
continue;
}
diff --git a/clang/test/Analysis/NewDeleteLeaks.cpp b/clang/test/Analysis/NewDeleteLeaks.cpp
index d9c4b77d1e6fc..e133a653873c7 100644
--- a/clang/test/Analysis/NewDeleteLeaks.cpp
+++ b/clang/test/Analysis/NewDeleteLeaks.cpp
@@ -251,3 +251,26 @@ void validate_system_header() {
}
} // namespace protobuf_leak
+
+// GH214226: Do not treat pointers stored in heap fields as escaped.
+namespace heap_field_bind {
+
+struct Owner {
+ int *member;
+ Owner() : member(new int(42)) {} // expected-note {{Memory is allocated}}
+};
+
+void leak_member_when_owner_deleted() {
+ Owner *owner = new Owner; // expected-note {{Calling default constructor for 'Owner'}}
+ // expected-note at -1 {{Returning from default constructor for 'Owner'}}
+ delete owner;
+} // expected-warning {{Potential leak of memory pointed to by field 'member'}}
+// expected-note at -1 {{Potential leak of memory pointed to by field 'member'}}
+
+void member_released_before_owner() {
+ Owner *owner = new Owner;
+ delete owner->member;
+ delete owner;
+} // no-warning
+
+} // namespace heap_field_bind
diff --git a/clang/test/Analysis/malloc.c b/clang/test/Analysis/malloc.c
index 6c3dadfd16021..0e8a84447c952 100644
--- a/clang/test/Analysis/malloc.c
+++ b/clang/test/Analysis/malloc.c
@@ -1771,7 +1771,14 @@ void testMallocIntoMalloc(void) {
StructWithPtr *s = malloc(sizeof(StructWithPtr));
s->memP = malloc(sizeof(int));
free(s);
-} // FIXME: should warn here
+} // expected-warning {{Potential leak of memory pointed to by field 'memP'}}
+
+void testMallocIntoMallocThenFreeMember(void) {
+ StructWithPtr *s = malloc(sizeof(StructWithPtr));
+ s->memP = malloc(sizeof(int));
+ free(s->memP);
+ free(s);
+} // no-warning
int conjure(void);
void testExtent(void) {
More information about the cfe-commits
mailing list