[clang] [LifetimeSafety] Add placement new support (PR #194030)

via cfe-commits cfe-commits at lists.llvm.org
Sat Apr 25 04:32:17 PDT 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang-temporal-safety

Author: NeKon69

<details>
<summary>Changes</summary>

Allows flow from placement new closely resembling standard library form.

Comes as part of the completion of #<!-- -->164963.

---
Full diff: https://github.com/llvm/llvm-project/pull/194030.diff


2 Files Affected:

- (modified) clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp (+31-3) 
- (modified) clang/test/Sema/warn-lifetime-safety.cpp (+110-11) 


``````````diff
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index efdb1a1691ae3..b44b7655e38f3 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;
   }
@@ -626,9 +632,31 @@ void FactsGenerator::VisitArraySubscriptExpr(const ArraySubscriptExpr *ASE) {
 void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
   OriginList *NewList = getOriginsList(*NE);
 
-  const Loan *L = createLoan(FactMgr, NE);
-  CurrentBlockFacts.push_back(
-      FactMgr.createFact<IssueFact>(L->getID(), NewList->getOuterOriginID()));
+  // 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()) {
+      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));
+    }
+  } else {
+    const Loan *L = createLoan(FactMgr, NE);
+    CurrentBlockFacts.push_back(
+        FactMgr.createFact<IssueFact>(L->getID(), NewList->getOuterOriginID()));
+  }
 
   NewList = NewList->peelOuterOrigin();
 
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index fc2aef6024a42..a507d7c058358 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,116 @@ 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}}
+}
+
+// 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;
+
+  void bad_store_after_placement_new() {
+    {
+      MyObj obj;
+      new (&V) View(obj);
+    }
+    V.use();
+  }
+};
+
+void placement_new_member_call_from_dead_scope() {
+  View *storage = new View;
+  {
+    MyObj obj;
+    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() {
+  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"};

``````````

</details>


https://github.com/llvm/llvm-project/pull/194030


More information about the cfe-commits mailing list