[clang] [LifetimeSafety] Add placement new support (PR #194030)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Apr 29 06:09:02 PDT 2026
https://github.com/NeKon69 updated https://github.com/llvm/llvm-project/pull/194030
>From 7b145c85107f012ea687a43022301a99ee55315c Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Fri, 24 Apr 2026 22:16:07 +0300
Subject: [PATCH 1/9] [LifetimeSafety] add placement new support
---
.../LifetimeSafety/FactsGenerator.cpp | 48 +++++--
clang/test/Sema/warn-lifetime-safety.cpp | 119 ++++++++++++++++--
2 files changed, 145 insertions(+), 22 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index efdb1a1691ae3..ec40ad4803c80 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -332,6 +332,12 @@ void FactsGenerator::VisitCastExpr(const CastExpr *CE) {
case CK_BuiltinFnToFnPtr:
// Ignore function-to-pointer decays.
return;
+ case CK_BitCast:
+ // OriginLists for Src and Dst may differ here. For example when casting
+ // from int** to void*
+ if (Src && Dest && Dest->getLength() == Src->getLength())
+ flow(Dest, Src, /*Kill=*/true);
+ return;
default:
return;
}
@@ -625,20 +631,40 @@ void FactsGenerator::VisitArraySubscriptExpr(const ArraySubscriptExpr *ASE) {
void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
OriginList *NewList = getOriginsList(*NE);
+ auto FlowTo = [&](OriginList *List) {
+ if (!List || !NE->getInitializer())
+ return;
- const Loan *L = createLoan(FactMgr, NE);
- CurrentBlockFacts.push_back(
- FactMgr.createFact<IssueFact>(L->getID(), NewList->getOuterOriginID()));
-
- NewList = NewList->peelOuterOrigin();
+ // FIXME: OriginList is null for `new[]` initializers. Remove this
+ // `Init` check once array origins are supported.
+ if (OriginList *Init = getOriginsList(*NE->getInitializer()); Init) {
+ flow(List, Init, true);
+ }
+ };
- if (!NewList || !NE->getInitializer())
- return;
+ // Check if we have a placement new where the second argument is void*, to
+ // avoid flowing from non-pointer parameters, such as std::nothrow.
+ // And that the placement parameter num is 1,
+ // that is to mostly limit to standard library placement new.
+ if (NE->getNumPlacementArgs() == 1) {
+ if (const auto *Arg = NE->getOperatorNew()
+ ->getParamDecl(1)
+ ->getType()
+ ->getAs<PointerType>();
+ Arg && Arg->isVoidPointerType()) {
+ OriginList *PlacementList = getOriginsList(*NE->getPlacementArg(0));
+ CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
+ NewList->getOuterOriginID(), PlacementList->getOuterOriginID(),
+ true));
+ }
+ } else {
+ const Loan *L = createLoan(FactMgr, NE);
+ CurrentBlockFacts.push_back(
+ FactMgr.createFact<IssueFact>(L->getID(), NewList->getOuterOriginID()));
+ }
- // FIXME: OriginList is null for `new[]` initializers. Remove this `Init`
- // check once array origins are supported.
- if (OriginList *Init = getOriginsList(*NE->getInitializer()); Init)
- flow(NewList, Init, true);
+ NewList = NewList->peelOuterOrigin();
+ FlowTo(NewList);
}
void FactsGenerator::VisitCXXDeleteExpr(const CXXDeleteExpr *DE) {
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index fc2aef6024a42..fcac827949cd0 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2859,17 +2859,6 @@ struct PointerFieldHolder {
MyObj *Ptr;
};
-// FIXME: https://github.com/llvm/llvm-project/issues/184344
-void placement_new_pointer_field_use_after_scope() {
- PointerFieldHolder h;
- PointerFieldHolder *p = &h;
- {
- MyObj obj;
- p = new (&h) PointerFieldHolder{&obj};
- }
- (void)p->Ptr->id;
-}
-
// FIXME: https://github.com/llvm/llvm-project/issues/184344
void delete_through_pointer_field() {
PointerFieldHolder h{new MyObj};
@@ -2898,6 +2887,114 @@ void allocate_void_ptr() {
} // namespace new_allocation
+namespace placement_new {
+
+void placement_new_int_basic() {
+ int *p;
+ {
+ int storage;
+ p = new (&storage) int; // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)*p; // expected-note {{later used here}}
+}
+
+void placement_new_view_from_dead_scope() {
+ View storage;
+ View *p = &storage;
+ {
+ MyObj obj;
+ p = new (&storage) View(obj); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ p->use(); // expected-note {{later used here}}
+}
+
+void placement_new_pointer_from_dead_object() {
+ MyObj *slot = nullptr;
+ MyObj **p = &slot;
+ {
+ MyObj obj;
+ p = new (&slot) MyObj *(&obj); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)**p; // expected-note {{later used here}}
+}
+
+void placement_new_array_basic() {
+ int *p;
+ {
+ int storage[2];
+ p = new (&storage) int[2]; // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)p[0]; // expected-note {{later used here}}
+}
+
+void placement_new_array_braces() {
+ int *p;
+ {
+ int storage[2];
+ p = new (&storage) int[2]{}; // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)p[0]; // expected-note {{later used here}}
+}
+
+struct PlacementNewInMethod {
+ View V; // expected-note {{this field dangles}}
+
+ void bad_store_after_placement_new() {
+ {
+ MyObj obj;
+ new (this) PlacementNewInMethod;
+ V = obj; // expected-warning {{address of stack memory escapes to a field}}
+ }
+ V.use();
+ }
+};
+
+// FIXME: Currently does not diagnose. We do not overwrite `storage`'s origins on placement new because we lose them on bitcast from `View*` to `void*`.
+void placement_new_member_call_from_dead_scope() {
+ View *storage = new View;
+ {
+ MyObj obj;
+ new (storage) View(obj);
+ }
+ storage->use();
+}
+
+void placement_new_heap_then_delete_use_after_free() {
+ int *storage = new int(7); // expected-warning {{allocated object does not live long enough}}
+ int *p = new (storage) int(42);
+ delete storage; // expected-note {{freed here}}
+ (void)*p; // expected-note {{later used here}}
+}
+
+int* foo(int* x [[clang::lifetimebound]], int* y [[clang::lifetimebound]]);
+
+void placement_new_delete_result_of_lifetimebound_call() {
+ int *x = new int(1); // expected-warning {{allocated object does not live long enough}}
+ int *y = new int(2); // expected-warning {{allocated object does not live long enough}}
+ int *slot = nullptr;
+ int **p = new (&slot) int *(foo(x, y));
+ delete foo(x, y); // expected-note 2 {{freed here}}
+ (void)**p; // expected-note 2 {{later used here}}
+}
+
+
+struct PointerFieldHolder {
+ MyObj *Ptr;
+};
+
+// FIXME: https://github.com/llvm/llvm-project/issues/184344
+void placement_new_pointer_field_use_after_scope() {
+ PointerFieldHolder h;
+ PointerFieldHolder *p = &h;
+ {
+ MyObj obj;
+ p = new (&h) PointerFieldHolder{&obj};
+ }
+ (void)p->Ptr->id;
+}
+
+} // namespace placement_new
+
namespace method_call_uses_field_origins {
int GLOBAL_INT;
std::string GLOBAL_STRING{"123"};
>From fc2f2f99cf27ed69ba48d4cf3cc005fd43edf776 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sat, 25 Apr 2026 13:21:55 +0300
Subject: [PATCH 2/9] update tests and impl
---
.../LifetimeSafety/FactsGenerator.cpp | 23 ++++++++++---------
clang/test/Sema/warn-lifetime-safety.cpp | 8 +++----
2 files changed, 16 insertions(+), 15 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index ec40ad4803c80..f1425b45b7a83 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -631,16 +631,6 @@ void FactsGenerator::VisitArraySubscriptExpr(const ArraySubscriptExpr *ASE) {
void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
OriginList *NewList = getOriginsList(*NE);
- auto FlowTo = [&](OriginList *List) {
- if (!List || !NE->getInitializer())
- return;
-
- // FIXME: OriginList is null for `new[]` initializers. Remove this
- // `Init` check once array origins are supported.
- if (OriginList *Init = getOriginsList(*NE->getInitializer()); Init) {
- flow(List, Init, true);
- }
- };
// Check if we have a placement new where the second argument is void*, to
// avoid flowing from non-pointer parameters, such as std::nothrow.
@@ -652,6 +642,10 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
->getType()
->getAs<PointerType>();
Arg && Arg->isVoidPointerType()) {
+ // FIXME: Flow from constructor expr to placement arg. To also support
+ // side effect placement new has. e.g.
+ // new(&p) MyObj(...);
+ // Should flow from constrcutor to &p.
OriginList *PlacementList = getOriginsList(*NE->getPlacementArg(0));
CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
NewList->getOuterOriginID(), PlacementList->getOuterOriginID(),
@@ -664,7 +658,14 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
}
NewList = NewList->peelOuterOrigin();
- FlowTo(NewList);
+
+ if (!NewList || !NE->getInitializer())
+ return;
+
+ // FIXME: OriginList is null for `new[]` initializers. Remove this `Init`
+ // check once array origins are supported.
+ if (OriginList *Init = getOriginsList(*NE->getInitializer()); Init)
+ flow(NewList, Init, true);
}
void FactsGenerator::VisitCXXDeleteExpr(const CXXDeleteExpr *DE) {
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index fcac827949cd0..ef3c54072a822 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2936,20 +2936,20 @@ void placement_new_array_braces() {
(void)p[0]; // expected-note {{later used here}}
}
+// FIXME: Currently does not diagnose. We do not overwrite `V`'s origins on placement new because we lose them on bitcast from `View*` to `void*`.
struct PlacementNewInMethod {
- View V; // expected-note {{this field dangles}}
+ View V;
void bad_store_after_placement_new() {
{
MyObj obj;
- new (this) PlacementNewInMethod;
- V = obj; // expected-warning {{address of stack memory escapes to a field}}
+ new (&V) View(obj);
}
V.use();
}
};
-// FIXME: Currently does not diagnose. We do not overwrite `storage`'s origins on placement new because we lose them on bitcast from `View*` to `void*`.
+// FIXME: same false-negative as above
void placement_new_member_call_from_dead_scope() {
View *storage = new View;
{
>From ea20cb21ac9d45364efec34308b908d1b595828d Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sat, 25 Apr 2026 13:23:25 +0300
Subject: [PATCH 3/9] update comment
---
clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index f1425b45b7a83..625e7f1aa1be2 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -642,10 +642,10 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
->getType()
->getAs<PointerType>();
Arg && Arg->isVoidPointerType()) {
- // FIXME: Flow from constructor expr to placement arg. To also support
+ // FIXME: Flow from constructor expr to placement arg to also support
// side effect placement new has. e.g.
// new(&p) MyObj(...);
- // Should flow from constrcutor to &p.
+ // Should flow from MyObj(...) to &p.
OriginList *PlacementList = getOriginsList(*NE->getPlacementArg(0));
CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
NewList->getOuterOriginID(), PlacementList->getOuterOriginID(),
>From 459da1dd261960d0dd5975f0c62ee716a61425d0 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sat, 25 Apr 2026 14:30:55 +0300
Subject: [PATCH 4/9] complete placement new
---
clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 11 ++++++-----
clang/test/Sema/warn-lifetime-safety.cpp | 12 +++++++-----
2 files changed, 13 insertions(+), 10 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 625e7f1aa1be2..b44b7655e38f3 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -642,11 +642,12 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
->getType()
->getAs<PointerType>();
Arg && Arg->isVoidPointerType()) {
- // FIXME: Flow from constructor expr to placement arg to also support
- // side effect placement new has. e.g.
- // new(&p) MyObj(...);
- // Should flow from MyObj(...) to &p.
- OriginList *PlacementList = getOriginsList(*NE->getPlacementArg(0));
+ const Expr *PlacementArg = NE->getPlacementArg(0)->IgnoreImpCasts();
+ OriginList *PlacementList =
+ getRValueOrigins(PlacementArg, getOriginsList(*PlacementArg));
+ if (PlacementList->peelOuterOrigin())
+ flow(PlacementList->peelOuterOrigin(),
+ getOriginsList(*NE->getInitializer()), true);
CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
NewList->getOuterOriginID(), PlacementList->getOuterOriginID(),
true));
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index ef3c54072a822..a507d7c058358 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2936,7 +2936,10 @@ void placement_new_array_braces() {
(void)p[0]; // expected-note {{later used here}}
}
-// FIXME: Currently does not diagnose. We do not overwrite `V`'s origins on placement new because we lose them on bitcast from `View*` to `void*`.
+// FIXME: Currently `&expr` creates a brand new origin instead of reusing origins
+// from the original expression. Because of that, writes through `&expr` cannot
+// overwrite the original expression's inner storage origins.
+// Related to https://github.com/llvm/llvm-project/issues/176291
struct PlacementNewInMethod {
View V;
@@ -2949,14 +2952,13 @@ struct PlacementNewInMethod {
}
};
-// FIXME: same false-negative as above
void placement_new_member_call_from_dead_scope() {
View *storage = new View;
{
MyObj obj;
- new (storage) View(obj);
- }
- storage->use();
+ new (storage) View(obj); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ storage->use(); // expected-note {{later used here}}
}
void placement_new_heap_then_delete_use_after_free() {
>From cf663a13bc279e19cf117698162f48dcbffdee87 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sat, 25 Apr 2026 21:03:21 +0300
Subject: [PATCH 5/9] replace checks
---
clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index b44b7655e38f3..15e0f562e1ba8 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -631,6 +631,7 @@ void FactsGenerator::VisitArraySubscriptExpr(const ArraySubscriptExpr *ASE) {
void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
OriginList *NewList = getOriginsList(*NE);
+ const Expr *Init = NE->getInitializer();
// Check if we have a placement new where the second argument is void*, to
// avoid flowing from non-pointer parameters, such as std::nothrow.
@@ -645,9 +646,8 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
const Expr *PlacementArg = NE->getPlacementArg(0)->IgnoreImpCasts();
OriginList *PlacementList =
getRValueOrigins(PlacementArg, getOriginsList(*PlacementArg));
- if (PlacementList->peelOuterOrigin())
- flow(PlacementList->peelOuterOrigin(),
- getOriginsList(*NE->getInitializer()), true);
+ if (Init)
+ flow(PlacementList->peelOuterOrigin(), getOriginsList(*Init), true);
CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
NewList->getOuterOriginID(), PlacementList->getOuterOriginID(),
true));
@@ -660,7 +660,7 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
NewList = NewList->peelOuterOrigin();
- if (!NewList || !NE->getInitializer())
+ if (!NewList || !Init)
return;
// FIXME: OriginList is null for `new[]` initializers. Remove this `Init`
>From cbd49e0278c9347ce580aa9208bcf0a0679083e2 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sat, 25 Apr 2026 21:23:29 +0300
Subject: [PATCH 6/9] add comments explaining flow
---
clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 15e0f562e1ba8..212e4cd9a36f5 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -643,11 +643,18 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
->getType()
->getAs<PointerType>();
Arg && Arg->isVoidPointerType()) {
+ // Use the placement argument before the implicit conversion to void*, so
+ // inner origins are still available.
const Expr *PlacementArg = NE->getPlacementArg(0)->IgnoreImpCasts();
+ // If the placement argument is a glvalue (such as DeclRefExpr), skip its
+ // outer storage origin so the list starts with the pointer origin.
OriginList *PlacementList =
getRValueOrigins(PlacementArg, getOriginsList(*PlacementArg));
+ // Placement new constructs the pointee of the placement pointer.
if (Init)
flow(PlacementList->peelOuterOrigin(), getOriginsList(*Init), true);
+ // The pointer returned by placement new comes from the placement
+ // argument.
CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
NewList->getOuterOriginID(), PlacementList->getOuterOriginID(),
true));
>From 0fdf10f8ed04c6c197a124a877d6effb35f9b16e Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sat, 25 Apr 2026 22:43:09 +0300
Subject: [PATCH 7/9] rename variables
---
clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 212e4cd9a36f5..d598dde81a52b 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -672,8 +672,8 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
// FIXME: OriginList is null for `new[]` initializers. Remove this `Init`
// check once array origins are supported.
- if (OriginList *Init = getOriginsList(*NE->getInitializer()); Init)
- flow(NewList, Init, true);
+ if (OriginList *InitList = getOriginsList(*Init); InitList)
+ flow(NewList, InitList, true);
}
void FactsGenerator::VisitCXXDeleteExpr(const CXXDeleteExpr *DE) {
>From 7ef4df706f1267d979b102f8e1902d59e752c117 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Tue, 28 Apr 2026 16:23:31 +0300
Subject: [PATCH 8/9] add a comment explaining current limitation and a few
more failing tests
---
.../LifetimeSafety/FactsGenerator.cpp | 8 ++
clang/test/Sema/warn-lifetime-safety.cpp | 94 +++++++++++++++++++
2 files changed, 102 insertions(+)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index d598dde81a52b..d55131381de39 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -643,6 +643,14 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
->getType()
->getAs<PointerType>();
Arg && Arg->isVoidPointerType()) {
+ // FIXME: This assumes the placement argument is a direct glvalue pointer
+ // expression with an origin list shaped like
+ // storage -> pointer value -> pointee object.
+ // The code below overwrites the pointee object origin. Since origin flow
+ // is one way, non-direct placement argument forms such as `storage.get()`
+ // or `&storage` need separate handling to find the actual storage object
+ // whose origins should be overwritten.
+
// Use the placement argument before the implicit conversion to void*, so
// inner origins are still available.
const Expr *PlacementArg = NE->getPlacementArg(0)->IgnoreImpCasts();
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index a507d7c058358..03a321cd215be 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2961,6 +2961,100 @@ void placement_new_member_call_from_dead_scope() {
storage->use(); // expected-note {{later used here}}
}
+struct ViewPointerFieldHolder {
+ View *Ptr;
+};
+
+// FIXME: Repeated field accesses do not share stable field origins.
+void placement_new_pointer_field_from_dead_scope() {
+ ViewPointerFieldHolder h{new View};
+ {
+ MyObj obj;
+ new (h.Ptr) View(obj);
+ }
+ h.Ptr->use();
+}
+
+// FIXME: Repeated array element accesses do not share stable element origins.
+void placement_new_array_subscript_from_dead_scope() {
+ View *slots[1] = {new View};
+ {
+ MyObj obj;
+ new (slots[0]) View(obj);
+ }
+ slots[0]->use();
+}
+
+// FIXME: Writes through references do not update the referred pointer's origins.
+void placement_new_pointer_reference_from_dead_scope() {
+ View *storage = new View;
+ View *&ref = storage;
+ {
+ MyObj obj;
+ new (ref) View(obj);
+ }
+ storage->use();
+}
+
+// FIXME: Writing through a conditional glvalue is not propagated to either arm.
+void placement_new_conditional_pointer_from_dead_scope(bool flag) {
+ View *x = new View;
+ View *y = new View;
+ {
+ MyObj obj;
+ new (flag ? x : y) View(obj);
+ }
+ x->use();
+ y->use();
+}
+
+View *identity(View *p [[clang::lifetimebound]]);
+
+// FIXME: Function call results are not mapped back to the argument they expose.
+void placement_new_function_pointer_from_dead_scope() {
+ View *storage = new View;
+ {
+ MyObj obj;
+ new (identity(storage)) View(obj);
+ }
+ storage->use();
+}
+
+struct ViewStorage {
+ View Storage;
+ View *get() [[clang::lifetimebound]];
+};
+
+// FIXME: Placement-new does not write back through the lifetimebound result of `get()`.
+void placement_new_member_function_pointer_from_dead_scope() {
+ ViewStorage storage;
+ {
+ MyObj obj;
+ new (storage.get()) View(obj);
+ }
+ storage.Storage.use();
+}
+
+// FIXME: Address-of placement arguments are not resolved to the addressed object.
+void placement_new_addressof_from_dead_scope() {
+ View storage;
+ {
+ MyObj obj;
+ new (&storage) View(obj);
+ }
+ storage.use();
+}
+
+// FIXME: Explicit casts to `void *` are not ignored for placement arguments.
+void placement_new_explicit_void_cast_from_dead_scope() {
+ View *storage = new View;
+ {
+ MyObj obj;
+ new ((void *)storage) View(obj);
+ }
+ storage->use();
+}
+
void placement_new_heap_then_delete_use_after_free() {
int *storage = new int(7); // expected-warning {{allocated object does not live long enough}}
int *p = new (storage) int(42);
>From 2c20253e10b4fc9b0a4704a57fd70cd035b7e1d7 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Wed, 29 Apr 2026 16:08:31 +0300
Subject: [PATCH 9/9] add another failing test and fix the code for arrays
---
.../lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 13 ++++++++-----
clang/test/Sema/warn-lifetime-safety.cpp | 9 +++++++++
2 files changed, 17 insertions(+), 5 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index d55131381de39..f21405f5bb7ce 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -658,14 +658,17 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
// outer storage origin so the list starts with the pointer origin.
OriginList *PlacementList =
getRValueOrigins(PlacementArg, getOriginsList(*PlacementArg));
- // Placement new constructs the pointee of the placement pointer.
- if (Init)
+ // If the placement argument only has a glvalue origin, there is no
+ // pointee object origin to overwrite.
+ if (Init && PlacementList)
+ // Placement new constructs the pointee of the placement pointer.
flow(PlacementList->peelOuterOrigin(), getOriginsList(*Init), true);
// The pointer returned by placement new comes from the placement
// argument.
- CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
- NewList->getOuterOriginID(), PlacementList->getOuterOriginID(),
- true));
+ if (PlacementList)
+ CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
+ NewList->getOuterOriginID(), PlacementList->getOuterOriginID(),
+ true));
}
} else {
const Loan *L = createLoan(FactMgr, NE);
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 03a321cd215be..b7276fb6e0ab5 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -3089,6 +3089,15 @@ void placement_new_pointer_field_use_after_scope() {
(void)p->Ptr->id;
}
+// FIXME: Stripping implicit casts also strips array to pointer decay, leaving only the array glvalue origin and no pointee origin to overwrite. Because of that, origins are not propagated here.
+void placement_new_direct_array_use_after_placement() {
+ char storage[sizeof(std::string)];
+ std::string* str1 = new (storage) std::string{"Old"};
+ auto p1 = str1->c_str();
+ new (storage) std::string{"New"};
+ (void)*p1;
+}
+
} // namespace placement_new
namespace method_call_uses_field_origins {
More information about the cfe-commits
mailing list