https://github.com/zeyi2 updated 
https://github.com/llvm/llvm-project/pull/202286

>From 453a3e2eca70dbbeb2d2233724965762569412b1 Mon Sep 17 00:00:00 2001
From: Zeyi Xu <[email protected]>
Date: Mon, 8 Jun 2026 16:12:52 +0800
Subject: [PATCH 1/2] [LifetimeSafety] Diagnose UAF for aligned and nothrow new
 expressions

---
 .../Analyses/LifetimeSafety/FactsGenerator.h  |  2 +-
 .../LifetimeSafety/FactsGenerator.cpp         | 22 ++++++---
 clang/test/Sema/Inputs/lifetime-analysis.h    |  6 +++
 clang/test/Sema/warn-lifetime-safety.cpp      | 46 ++++++++++++++++++-
 4 files changed, 67 insertions(+), 9 deletions(-)

diff --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 8f0670728bae9..2c961bd305fac 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -76,7 +76,7 @@ class FactsGenerator : public 
ConstStmtVisitor<FactsGenerator> {
 
   void handlePointerArithmetic(const BinaryOperator *BO);
 
-  void handlePlacementNew(const CXXNewExpr *NE, OriginList *NewList);
+  bool handlePlacementNew(const CXXNewExpr *NE, OriginList *NewList);
 
   void handleCXXCtorInitializer(const CXXCtorInitializer *CII);
 
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 633f9ae57930b..9fbfaf8ae606b 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -631,23 +631,23 @@ void FactsGenerator::VisitArraySubscriptExpr(const 
ArraySubscriptExpr *ASE) {
       Dst->getOuterOriginID(), Src->getOuterOriginID(), /*Kill=*/true));
 }
 
-void FactsGenerator::handlePlacementNew(const CXXNewExpr *NE,
+bool FactsGenerator::handlePlacementNew(const CXXNewExpr *NE,
                                         OriginList *NewList) {
   // Model only the standard single-argument placement new form, where the
   // placement argument corresponds to a void* allocation-function parameter.
   // Other placement forms, such as std::nothrow, are not modeled as providing
   // storage for the returned pointer.
   if (NE->getNumPlacementArgs() != 1)
-    return;
+    return false;
 
   const FunctionDecl *OperatorNew = NE->getOperatorNew();
   if (OperatorNew->getNumParams() <= 1)
-    return;
+    return false;
 
   const auto *Arg =
       OperatorNew->getParamDecl(1)->getType()->getAs<PointerType>();
   if (!Arg || !Arg->isVoidPointerType())
-    return;
+    return false;
 
   // Use the placement argument before the implicit conversion to void*, so
   // inner origins are still available.
@@ -665,15 +665,23 @@ void FactsGenerator::handlePlacementNew(const CXXNewExpr 
*NE,
   if (PlacementList)
     CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
         NewList->getOuterOriginID(), PlacementList->getOuterOriginID(), true));
+  return true;
 }
 
 void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
   OriginList *NewList = getOriginsList(*NE);
   const Expr *Init = NE->getInitializer();
 
-  if (NE->getNumPlacementArgs() == 1) {
-    handlePlacementNew(NE, NewList);
-  } else {
+  bool HandledAsPlacementNew = false;
+  if (NE->getNumPlacementArgs() == 1)
+    HandledAsPlacementNew = handlePlacementNew(NE, NewList);
+
+  // Treat ordinary new and replaceable global allocation forms as heap
+  // allocations.
+  const FunctionDecl *OperatorNew = NE->getOperatorNew();
+  if (!HandledAsPlacementNew &&
+      (NE->getNumPlacementArgs() == 0 ||
+       (OperatorNew && OperatorNew->isReplaceableGlobalAllocationFunction()))) 
{
     const Loan *L = createLoan(FactMgr, NE);
     CurrentBlockFacts.push_back(
         FactMgr.createFact<IssueFact>(L->getID(), 
NewList->getOuterOriginID()));
diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h 
b/clang/test/Sema/Inputs/lifetime-analysis.h
index 4d727ae9499d6..024c3c2bc51b7 100644
--- a/clang/test/Sema/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/Inputs/lifetime-analysis.h
@@ -53,6 +53,9 @@ T *begin(T (&array)[N]);
 
 using size_t = decltype(sizeof(0));
 using nullptr_t = decltype(nullptr);
+enum class align_val_t : size_t {};
+struct nothrow_t {};
+extern const nothrow_t nothrow;
 
 template<typename T>
 struct initializer_list {
@@ -356,3 +359,6 @@ class function<R(Args...)> {
 
 void *operator new(std::size_t, void *) noexcept;
 void *operator new[](std::size_t, void *) noexcept;
+void *operator new(std::size_t, const std::nothrow_t &) noexcept;
+void *operator new(std::size_t, std::align_val_t,
+                   const std::nothrow_t &) noexcept;
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index 5aaa389b63ccd..efff00517fcaf 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2751,6 +2751,24 @@ void new_int_braces() {
   (void)*p;           // expected-note {{later used here}}
 }
 
+void new_int_aligned() {
+  int *p = new (std::align_val_t(sizeof(int))) int{}; // expected-warning 
{{allocated object does not live long enough}}
+  delete p;                                           // expected-note {{freed 
here}}
+  (void)*p;                                           // expected-note {{later 
used here}}
+}
+
+void new_int_nothrow() {
+  int *p = new (std::nothrow) int{}; // expected-warning {{allocated object 
does not live long enough}}
+  delete p;                          // expected-note {{freed here}}
+  (void)*p;                          // expected-note {{later used here}}
+}
+
+void new_int_aligned_nothrow() {
+  int *p = new (std::align_val_t(sizeof(int)), std::nothrow) int{}; // 
expected-warning {{allocated object does not live long enough}}
+  delete p;                                                         // 
expected-note {{freed here}}
+  (void)*p;                                                         // 
expected-note {{later used here}}
+}
+
 void conditional_delete(bool cond) {
   int *p1 = new int;       // expected-warning {{allocated object does not 
live long enough}}
   int *p2 = new int;       // expected-warning {{allocated object does not 
live long enough}}
@@ -3009,6 +3027,32 @@ void variadic_placement_new() {
   (void)new (arg) VariadicPlacementNew;
 }
 
+struct Arena {};
+
+struct CustomPlacementNew {
+  int X;
+  void *operator new(std::size_t, Arena &, int);
+  void operator delete(void *);
+};
+
+struct SingleArgCustomPlacementNew {
+  int X;
+  void *operator new(std::size_t, Arena &);
+  void operator delete(void *);
+};
+
+void custom_placement_new_not_heap(Arena &A) {
+  CustomPlacementNew *p = new (A, 0) CustomPlacementNew;
+  delete p;
+  (void)p->X;
+}
+
+void single_arg_custom_placement_new_not_heap(Arena &A) {
+  SingleArgCustomPlacementNew *p = new (A) SingleArgCustomPlacementNew;
+  delete p;
+  (void)p->X;
+}
+
 int* foo(int* x [[clang::lifetimebound]], int* y [[clang::lifetimebound]]);
 
 void placement_new_delete_result_of_lifetimebound_call() {
@@ -3594,4 +3638,4 @@ void capturing_multiple_locals() {
         setCaptureBy(v, local2);    // expected-warning{{local variable 
'local2' does not live long enough}}
     }                               // expected-note 2 {{destroyed here}} 
     (void)v;                        // expected-note 2 {{later used here}}
-}
\ No newline at end of file
+}

>From caef02d356a4bbee651e14aa8c4383d1391308e9 Mon Sep 17 00:00:00 2001
From: Zeyi Xu <[email protected]>
Date: Mon, 8 Jun 2026 23:37:00 +0800
Subject: [PATCH 2/2] add tests

---
 clang/test/Sema/warn-lifetime-safety.cpp | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index efff00517fcaf..f6dab33f8476c 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2928,6 +2928,18 @@ void class_specific_operator_delete_use_after_free() {
   (void)p->X;                                       // expected-note {{later 
used here}}
 }
 
+struct ClassSpecificNew {
+  int X;
+  static void *operator new(std::size_t);
+  static void operator delete(void *);
+};
+
+void class_specific_operator_new_use_after_free() {
+  ClassSpecificNew *p = new ClassSpecificNew; // expected-warning {{allocated 
object does not live long enough}}
+  delete p;                                   // expected-note {{freed here}}
+  (void)p->X;                                 // expected-note {{later used 
here}}
+}
+
 struct PointerFieldHolder {
   MyObj *Ptr;
 };

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to