[clang] b2dda8c - [LifetimeSafety] Add placement new support (#194030)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Apr 30 03:12:45 PDT 2026
Author: NeKon69
Date: 2026-04-30T15:42:40+05:30
New Revision: b2dda8cd34c39cd899b5fed1c6a6e56ad39906c6
URL: https://github.com/llvm/llvm-project/commit/b2dda8cd34c39cd899b5fed1c6a6e56ad39906c6
DIFF: https://github.com/llvm/llvm-project/commit/b2dda8cd34c39cd899b5fed1c6a6e56ad39906c6.diff
LOG: [LifetimeSafety] Add placement new support (#194030)
Allows flow from placement new closely resembling standard library form.
Comes as part of the completion of #164963.
Added:
Modified:
clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
clang/test/Sema/warn-lifetime-safety.cpp
Removed:
################################################################################
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 2c7cc6b38b3e1..7835ac8e0ff23 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
diff er 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,51 @@ 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()));
+ 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.
+ // 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()) {
+ // Use the placement argument before the implicit conversion to void*, so
+ // inner origins are still available.
+ const Expr *PlacementArg = NE->getPlacementArg(0);
+ if (const auto *ICE = dyn_cast<ImplicitCastExpr>(PlacementArg);
+ ICE && ICE->getCastKind() == CK_BitCast &&
+ PlacementArg->getType()->isVoidPointerType())
+ PlacementArg = ICE->getSubExpr();
+ OriginList *PlacementList = getOriginsList(*PlacementArg);
+ // FIXME: General placement arguments need separate handling to overwrite
+ // the right origins.
+
+ // The pointer returned by placement new comes from the placement
+ // argument.
+ if (PlacementList)
+ 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();
- if (!NewList || !NE->getInitializer())
+ if (!NewList || !Init)
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);
+ if (OriginList *InitList = getOriginsList(*Init); InitList)
+ flow(NewList, InitList, 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 fc2aef6024a42..30b450c333fbd 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,209 @@ 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}}
+}
+
+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
+
+// FIXME: Currently this is not diagnosed because placement new does not overwrite the placement argument's origins.
+namespace placement_new_argument {
+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);
+ }
+ storage->use();
+}
+
+struct ViewPointerFieldHolder {
+ View *Ptr;
+};
+
+void placement_new_pointer_field_from_dead_scope() {
+ ViewPointerFieldHolder h{new View};
+ {
+ MyObj obj;
+ new (h.Ptr) View(obj);
+ }
+ h.Ptr->use();
+}
+
+void placement_new_array_subscript_from_dead_scope() {
+ View *slots[1] = {new View};
+ {
+ MyObj obj;
+ new (slots[0]) View(obj);
+ }
+ slots[0]->use();
+}
+
+void placement_new_pointer_reference_from_dead_scope() {
+ View *storage = new View;
+ View *&ref = storage;
+ {
+ MyObj obj;
+ new (ref) View(obj);
+ }
+ storage->use();
+}
+
+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]]);
+
+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]];
+};
+
+void placement_new_member_function_pointer_from_dead_scope() {
+ ViewStorage storage;
+ {
+ MyObj obj;
+ new (storage.get()) View(obj);
+ }
+ storage.Storage.use();
+}
+
+void placement_new_addressof_from_dead_scope() {
+ View storage;
+ {
+ MyObj obj;
+ new (&storage) View(obj);
+ }
+ storage.use();
+}
+
+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_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_argument
+
namespace method_call_uses_field_origins {
int GLOBAL_INT;
std::string GLOBAL_STRING{"123"};
More information about the cfe-commits
mailing list