[clang] [LifetimeSafety] Diagnose UAF for aligned and nothrow new expressions (PR #202286)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Jun 8 01:51:19 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-analysis
Author: Zeyi Xu (zeyi2)
<details>
<summary>Changes</summary>
Closes https://github.com/llvm/llvm-project/issues/196208
---
Full diff: https://github.com/llvm/llvm-project/pull/202286.diff
4 Files Affected:
- (modified) clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h (+1-1)
- (modified) clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp (+15-7)
- (modified) clang/test/Sema/Inputs/lifetime-analysis.h (+6)
- (modified) clang/test/Sema/warn-lifetime-safety.cpp (+45-1)
``````````diff
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
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/202286
More information about the cfe-commits
mailing list