[clang] owner-constructor-inference (PR #223635)
Utkarsh Saxena via cfe-commits
cfe-commits at lists.llvm.org
Tue Sep 15 02:40:15 PDT 2026
https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/223635
>From 1bc809f69e21b952dc4ac19b292cbf25e83efcef Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Tue, 15 Sep 2026 08:56:31 +0000
Subject: [PATCH] owner-constructor-inference
---
.../LifetimeSafety/LifetimeAnnotations.h | 5 +++
clang/lib/Analysis/LifetimeSafety/Checker.cpp | 8 +++-
.../LifetimeSafety/LifetimeAnnotations.cpp | 5 +++
clang/test/Sema/LifetimeSafety/safety.cpp | 38 +++++++++++++++++++
4 files changed, 54 insertions(+), 2 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
index b7ee742445654..9097bbb267124 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
@@ -113,6 +113,11 @@ bool isGslPointerType(QualType QT);
bool isGslOwnerType(QualType QT);
bool isGslOwnerType(const CXXRecordDecl *RD);
+// Tells whether the given constructor belongs to an Owner type and the
+// parameter is of pointer type. This is useful to disable inference on owning
+// pointers being captured by owners.
+bool isOwnerPtrCtor(const CXXConstructorDecl *Ctor, const ParmVarDecl *PVD);
+
// Returns true if the given method is std::unique_ptr::release().
// This is treated as a move in lifetime analysis to avoid false-positives
// when ownership is manually transferred.
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index a358215a295d1..c49b9102ef869 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -153,8 +153,12 @@ class LifetimeChecker {
if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF))
AnnotationWarningsMap.try_emplace(PVD, ReturnEsc->getReturnExpr());
else if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(OEF);
- FieldEsc && isa<CXXConstructorDecl>(FD))
- AnnotationWarningsMap.try_emplace(PVD, FieldEsc->getFieldDecl());
+ FieldEsc && isa<CXXConstructorDecl>(FD)) {
+ // Disable inference for pointers being captured by an owner type,
+ // as owners typically consume these pointers rather than borrow them.
+ if (!isOwnerPtrCtor(dyn_cast<CXXConstructorDecl>(FD), PVD))
+ AnnotationWarningsMap.try_emplace(PVD, FieldEsc->getFieldDecl());
+ }
}
// TODO: Suggest lifetime_capture_by(this) for parameter escaping to a
// field!
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
index 725e6dcd5ebdd..98b2a5a867752 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -387,6 +387,11 @@ bool isGslOwnerType(const CXXRecordDecl *RD) {
return isRecordWithAttr<OwnerAttr>(RD);
}
+bool isOwnerPtrCtor(const CXXConstructorDecl *Ctor, const ParmVarDecl *PVD) {
+ return Ctor && PVD->getType()->isPointerType() &&
+ isGslOwnerType(Ctor->getParent());
+}
+
static StringRef getName(const CXXRecordDecl &RD) {
if (const auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(&RD))
return CTSD->getSpecializedTemplate()->getName();
diff --git a/clang/test/Sema/LifetimeSafety/safety.cpp b/clang/test/Sema/LifetimeSafety/safety.cpp
index 851f558c85dcd..c87acc9c58aeb 100644
--- a/clang/test/Sema/LifetimeSafety/safety.cpp
+++ b/clang/test/Sema/LifetimeSafety/safety.cpp
@@ -4283,3 +4283,41 @@ void test_cyclic_cfg(int n) {
} // expected-note {{local variable 'a' is destroyed here}}
v.use(); // expected-note {{later used here}}
}
+
+namespace TakeOwnershipTests {
+std::unique_ptr<int> takeOwnership(int* i) { return std::unique_ptr<int>(i); }
+
+void doubleFree() {
+ std::unique_ptr<int> up;
+ {
+ int a = 42;
+ // No use-after-scope warning here.
+ // This is a double-free due to multiple ownership which is currently not supported.
+ up = takeOwnership(&a);
+ }
+ (void)up.get();
+}
+
+void ok() {
+ std::unique_ptr<int> up;
+ {
+ int* a = new int(42);
+ up = takeOwnership(a); // Ok.
+ }
+ (void)up.get();
+}
+
+void take(std::unique_ptr<int> o);
+
+void foo() {
+ int* p;
+ std::unique_ptr<int> up;
+ {
+ std::unique_ptr<int> o = std::unique_ptr<int>(new int(42));
+ p = o.get(); // expected-warning {{local variable 'o' may not live long enough}} \
+ // expected-note {{result of call to 'get' aliases the storage of local variable 'o' because the implicit object parameter is inferred as lifetimebound}}
+ up = std::move(o); // expected-note {{potentially moved here}}
+ } // expected-note {{local variable 'o' is destroyed here}}
+ (void)*p; // expected-note {{later used here}}
+}
+} // namespace TakeOwnershipTests
More information about the cfe-commits
mailing list