[clang] [LifetimeSafety] Flow origins from lifetimebound args in `gsl::Pointer` construction (PR #189907)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Apr 10 09:54:50 PDT 2026
https://github.com/NeKon69 updated https://github.com/llvm/llvm-project/pull/189907
>From 4f237572faa6ba6320b95ef5e6095ed4076e22c0 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Tue, 31 Mar 2026 11:56:30 +0300
Subject: [PATCH 1/8] [LifetimeSafety] apply basic fix and update the test
---
clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 5 +++++
clang/test/Sema/warn-lifetime-analysis-nocfg.cpp | 12 +++++-------
2 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 861e1f6fa8a33..b55eeb59ac34d 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -712,6 +712,11 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
CallList->getOuterOriginID(), ArgList->getOuterOriginID(),
KillSrc));
KillSrc = false;
+ } else if (IsArgLifetimeBound(I)) {
+ CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
+ CallList->getOuterOriginID(), ArgList->getOuterOriginID(),
+ KillSrc));
+ KillSrc = false;
}
} else if (shouldTrackPointerImplicitObjectArg(I)) {
assert(ArgList->getLength() >= 2 &&
diff --git a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
index d58f23e4b554c..0ed151b9db136 100644
--- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
+++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
@@ -931,13 +931,11 @@ struct [[gsl::Pointer]] Pointer {
Pointer(const Bar & bar [[clang::lifetimebound]]);
};
Pointer test3(Bar bar) {
- // FIXME: Detect this using the CFG-based lifetime analysis (constructor of a pointer).
- // https://github.com/llvm/llvm-project/issues/175898
- Pointer p = Pointer(Bar()); // expected-warning {{temporary}}
- use(p);
- p = Pointer(Bar()); // expected-warning {{object backing}}
- use(p);
- return bar; // expected-warning {{address of stack}}
+ Pointer p = Pointer(Bar()); // expected-warning {{temporary}} cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}}
+ use(p); // cfg-note {{later used here}}
+ p = Pointer(Bar()); // expected-warning {{object backing}} cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}}
+ use(p); // cfg-note {{later used here}}
+ return bar; // expected-warning {{address of stack}} cfg-warning {{address of stack memory is returned later}} cfg-note {{returned here}}
}
template<typename T>
>From 610a9a48d4ccc0da8a293d4e60a3391b288caafe Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Wed, 1 Apr 2026 10:58:02 +0300
Subject: [PATCH 2/8] combine checks into one, update the comment
---
.../lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 13 ++++---------
1 file changed, 4 insertions(+), 9 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index b55eeb59ac34d..b314ac45c5760 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -704,15 +704,10 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
assert(!Args[I]->isGLValue() || ArgList->getLength() >= 2);
ArgList = getRValueOrigins(Args[I], ArgList);
}
- if (isGslOwnerType(Args[I]->getType())) {
- // The constructed gsl::Pointer borrows from the Owner's storage, not
- // from what the Owner itself borrows, so only the outermost origin is
- // needed.
- CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
- CallList->getOuterOriginID(), ArgList->getOuterOriginID(),
- KillSrc));
- KillSrc = false;
- } else if (IsArgLifetimeBound(I)) {
+ if (isGslOwnerType(Args[I]->getType()) || IsArgLifetimeBound(I)) {
+ // The constructed gsl::Pointer is dependent on the argument itself, not
+ // on anything the argument is dependent on, so only the outermost
+ // origin is needed.
CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
CallList->getOuterOriginID(), ArgList->getOuterOriginID(),
KillSrc));
>From 9e5130e89e4bcf3f2302ed05f133f4ae40cdd1d7 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Wed, 1 Apr 2026 19:28:22 +0300
Subject: [PATCH 3/8] move check to a different branch
---
.../lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index b314ac45c5760..75185be0ca203 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -704,14 +704,20 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
assert(!Args[I]->isGLValue() || ArgList->getLength() >= 2);
ArgList = getRValueOrigins(Args[I], ArgList);
}
- if (isGslOwnerType(Args[I]->getType()) || IsArgLifetimeBound(I)) {
- // The constructed gsl::Pointer is dependent on the argument itself, not
- // on anything the argument is dependent on, so only the outermost
- // origin is needed.
+ if (isGslOwnerType(Args[I]->getType())) {
+ // The constructed gsl::Pointer borrows from the Owner's storage, not
+ // from what the Owner itself borrows, so only the outermost origin is
+ // needed.
CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
CallList->getOuterOriginID(), ArgList->getOuterOriginID(),
KillSrc));
KillSrc = false;
+ } else if (IsArgLifetimeBound(I)) {
+ // A lifetimebound argument may affect the result at every origin level,
+ // so we propagate the whole OriginList instead of only the outermost
+ // origin.
+ flow(CallList, ArgList, KillSrc);
+ KillSrc = false;
}
} else if (shouldTrackPointerImplicitObjectArg(I)) {
assert(ArgList->getLength() >= 2 &&
>From 1ce95bb35ac2c7cba6d8323d227eeb0792362ed4 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Thu, 2 Apr 2026 11:52:35 +0300
Subject: [PATCH 4/8] add a few more tests
---
clang/test/Sema/warn-lifetime-safety.cpp | 46 ++++++++++++++++++++++++
1 file changed, 46 insertions(+)
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 88a502491e6c0..c18c852eb0703 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2413,3 +2413,49 @@ void owner_outlives_lifetimebound_source() {
}
} // namespace track_origins_for_lifetimebound_record_type
+
+namespace gslpointer_construction_from_lifetimebound {
+
+struct Bar {};
+template <typename T> struct [[gsl::Pointer]] Pointer {
+ Pointer();
+ Pointer(const T &bar [[clang::lifetimebound]]);
+ Pointer(const Pointer<T> &p [[clang::lifetimebound]]);
+ const T &operator*() const [[clang::lifetimebound]];
+};
+
+template <typename T> void use(T);
+
+void local_pointer() {
+ Pointer<int> p;
+ {
+ int v{};
+ p = Pointer(v); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ use(*p); // expected-note {{later used here}}
+}
+
+void nested_local_pointer() {
+ Pointer<Pointer<Pointer<Bar>>> ppp;
+ Pointer<Pointer<Bar>> pp;
+ Pointer<Bar> p;
+ {
+ Bar v{};
+ p = Pointer(v); // expected-warning {{object whose reference is captured does not live long enough}}
+ pp = Pointer(p);
+ ppp = Pointer(pp);
+ } // expected-note {{destroyed here}}
+ use(***ppp); // expected-note {{later used here}}
+}
+
+struct PFieldFromParam {
+ Pointer<Bar> value; // function-note {{this field dangles}}
+ PFieldFromParam(Bar bar) : value(bar) {} // function-warning {{address of stack memory escapes to a field}}
+};
+
+struct PFieldFromTemp {
+ Pointer<Bar> value; // function-note {{this field dangles}}
+ PFieldFromTemp() : value(Bar{}) {} // function-warning {{address of stack memory escapes to a field}}
+};
+
+} // namespace gslpointer_construction_from_lifetimebound
>From b3a8a1e6804431f5990d35b31308e18948fb577d Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Thu, 2 Apr 2026 11:53:48 +0300
Subject: [PATCH 5/8] add github link to the issue
---
clang/test/Sema/warn-lifetime-safety.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index c18c852eb0703..5603ff81966e6 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2415,7 +2415,7 @@ void owner_outlives_lifetimebound_source() {
} // namespace track_origins_for_lifetimebound_record_type
namespace gslpointer_construction_from_lifetimebound {
-
+// https://github.com/llvm/llvm-project/issues/175898
struct Bar {};
template <typename T> struct [[gsl::Pointer]] Pointer {
Pointer();
>From ab04bd2952eea7bc166415f5806c496034c93bcc Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Fri, 3 Apr 2026 09:33:43 +0300
Subject: [PATCH 6/8] remove annotations from copy ctor
---
clang/test/Sema/warn-lifetime-safety.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 5603ff81966e6..20357e68dd7ad 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2420,7 +2420,7 @@ struct Bar {};
template <typename T> struct [[gsl::Pointer]] Pointer {
Pointer();
Pointer(const T &bar [[clang::lifetimebound]]);
- Pointer(const Pointer<T> &p [[clang::lifetimebound]]);
+ Pointer(const Pointer<T> &p);
const T &operator*() const [[clang::lifetimebound]];
};
>From 19ef050533ee7b8bc2ef17698ef896da24a7fc33 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Tue, 7 Apr 2026 22:22:31 +0300
Subject: [PATCH 7/8] delete initializer list
---
clang/test/Sema/warn-lifetime-safety.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 20357e68dd7ad..4b245e23dcf4c 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2429,7 +2429,7 @@ template <typename T> void use(T);
void local_pointer() {
Pointer<int> p;
{
- int v{};
+ int v;
p = Pointer(v); // expected-warning {{object whose reference is captured does not live long enough}}
} // expected-note {{destroyed here}}
use(*p); // expected-note {{later used here}}
@@ -2440,7 +2440,7 @@ void nested_local_pointer() {
Pointer<Pointer<Bar>> pp;
Pointer<Bar> p;
{
- Bar v{};
+ Bar v;
p = Pointer(v); // expected-warning {{object whose reference is captured does not live long enough}}
pp = Pointer(p);
ppp = Pointer(pp);
>From 3e72ca9bb995e95999d178c39ce1ce45dab31126 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Wed, 8 Apr 2026 19:37:26 +0300
Subject: [PATCH 8/8] update the comment and only flwo the outer origin
---
.../lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 75185be0ca203..14828ddb24c24 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -713,10 +713,15 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
KillSrc));
KillSrc = false;
} else if (IsArgLifetimeBound(I)) {
- // A lifetimebound argument may affect the result at every origin level,
- // so we propagate the whole OriginList instead of only the outermost
- // origin.
- flow(CallList, ArgList, KillSrc);
+ // Only flow the outer origin here. For lifetimebound args in
+ // gsl::Pointer construction, we do not have enough information to
+ // safely match inner origins, so the source and
+ // destination origin lists may have different lengths.
+ // FIXME: Handle origin-shape mismatches gracefully so we can also flow
+ // inner origins.
+ CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
+ CallList->getOuterOriginID(), ArgList->getOuterOriginID(),
+ KillSrc));
KillSrc = false;
}
} else if (shouldTrackPointerImplicitObjectArg(I)) {
More information about the cfe-commits
mailing list