[clang] 846bfd7 - [LifetimeSafety] Avoid assert on variadic placement new (#199588)
via cfe-commits
cfe-commits at lists.llvm.org
Wed May 27 22:17:57 PDT 2026
Author: Zeyi Xu
Date: 2026-05-28T13:17:52+08:00
New Revision: 846bfd77ddc44ef76882fd165fa4d5b68b760456
URL: https://github.com/llvm/llvm-project/commit/846bfd77ddc44ef76882fd165fa4d5b68b760456
DIFF: https://github.com/llvm/llvm-project/commit/846bfd77ddc44ef76882fd165fa4d5b68b760456.diff
LOG: [LifetimeSafety] Avoid assert on variadic placement new (#199588)
Avoid assuming that a placement allocation function has a second
`ParmVarDecl` before checking whether that parameter is `void*`.
Variadic `operator new(size_t, ...)` can have a placement argument
matched by the ellipsis instead.
As of AI Usage: Codex is used to help rephrase part of the new comments.
Closes https://github.com/llvm/llvm-project/issues/199584
Added:
Modified:
clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
clang/test/Sema/warn-lifetime-safety.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 943bdd7199bf3..0b6d8c4fc026e 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -76,6 +76,8 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
void handlePointerArithmetic(const BinaryOperator *BO);
+ void handlePlacementNew(const CXXNewExpr *NE, OriginList *NewList);
+
void handleCXXCtorInitializer(const CXXCtorInitializer *CII);
void handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds);
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 323802c6a88d5..b34a04f1f3bda 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -631,38 +631,48 @@ void FactsGenerator::VisitArraySubscriptExpr(const ArraySubscriptExpr *ASE) {
Dst->getOuterOriginID(), Src->getOuterOriginID(), /*Kill=*/true));
}
+void 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;
+
+ const FunctionDecl *OperatorNew = NE->getOperatorNew();
+ if (OperatorNew->getNumParams() <= 1)
+ return;
+
+ const auto *Arg =
+ OperatorNew->getParamDecl(1)->getType()->getAs<PointerType>();
+ if (!Arg || !Arg->isVoidPointerType())
+ return;
+
+ // 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));
+}
+
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.
- // 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));
- }
+ handlePlacementNew(NE, NewList);
} else {
const Loan *L = createLoan(FactMgr, NE);
CurrentBlockFacts.push_back(
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index ae9d5d08e358b..87a9b6c330f73 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2982,6 +2982,17 @@ void placement_new_heap_then_delete_use_after_free() {
(void)*p; // expected-note {{later used here}}
}
+struct PlacementArg {};
+
+struct VariadicPlacementNew {
+ void *operator new(std::size_t, ...);
+};
+
+void variadic_placement_new() {
+ PlacementArg arg;
+ (void)new (arg) VariadicPlacementNew;
+}
+
int* foo(int* x [[clang::lifetimebound]], int* y [[clang::lifetimebound]]);
void placement_new_delete_result_of_lifetimebound_call() {
More information about the cfe-commits
mailing list