[clang] [LifetimeSafety] Detect expiry of loans to trivially destructed types (PR #168855)
Kashika Akhouri via cfe-commits
cfe-commits at lists.llvm.org
Fri Nov 21 01:21:04 PST 2025
https://github.com/kashika0112 updated https://github.com/llvm/llvm-project/pull/168855
>From 24c7b4b1ccb7dceca06938e23dadea8b11ebb051 Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <akhourik at google.com>
Date: Thu, 20 Nov 2025 11:08:26 +0000
Subject: [PATCH 1/3] Support trivial desctructors
---
.../Analyses/LifetimeSafety/FactsGenerator.h | 1 +
.../LifetimeSafety/FactsGenerator.cpp | 18 +++--
clang/lib/Sema/AnalysisBasedWarnings.cpp | 1 +
.../Sema/warn-lifetime-safety-dataflow.cpp | 24 ++++++-
clang/test/Sema/warn-lifetime-safety.cpp | 44 +++++++++++--
.../unittests/Analysis/LifetimeSafetyTest.cpp | 66 +++++++++++++++++++
6 files changed, 143 insertions(+), 11 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 8ea37259c570b..85192fdff5067 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -51,6 +51,7 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
private:
void handleDestructor(const CFGAutomaticObjDtor &DtorOpt);
+ void handleTrivialDestructors(const CFGLifetimeEnds &LifetimeEnds);
void handleGSLPointerConstruction(const CXXConstructExpr *CCE);
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index cb9a202b08968..fc92aba773187 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -66,6 +66,9 @@ void FactsGenerator::run() {
else if (std::optional<CFGAutomaticObjDtor> DtorOpt =
Element.getAs<CFGAutomaticObjDtor>())
handleDestructor(*DtorOpt);
+ else if (std::optional<CFGLifetimeEnds> LifetimeEnds =
+ Element.getAs<CFGLifetimeEnds>())
+ handleTrivialDestructors(*LifetimeEnds);
}
CurrentBlockFacts.append(EscapesInCurrentBlock.begin(),
EscapesInCurrentBlock.end());
@@ -230,11 +233,7 @@ void FactsGenerator::VisitMaterializeTemporaryExpr(
}
void FactsGenerator::handleDestructor(const CFGAutomaticObjDtor &DtorOpt) {
- /// TODO: Also handle trivial destructors (e.g., for `int`
- /// variables) which will never have a CFGAutomaticObjDtor node.
/// TODO: Handle loans to temporaries.
- /// TODO: Consider using clang::CFG::BuildOptions::AddLifetime to reuse the
- /// lifetime ends.
const VarDecl *DestructedVD = DtorOpt.getVarDecl();
if (!DestructedVD)
return;
@@ -251,6 +250,17 @@ void FactsGenerator::handleDestructor(const CFGAutomaticObjDtor &DtorOpt) {
}
}
+void FactsGenerator::handleTrivialDestructors(
+ const CFGLifetimeEnds &LifetimeEnds) {
+ for (const auto &Loan : FactMgr.getLoanMgr().getLoans()) {
+ if (Loan.Path.D == LifetimeEnds.getVarDecl()) {
+ CurrentBlockFacts.push_back(FactMgr.createFact<ExpireFact>(
+ Loan.ID, LifetimeEnds.getTriggerStmt()->getEndLoc()));
+ break;
+ }
+ }
+}
+
void FactsGenerator::handleGSLPointerConstruction(const CXXConstructExpr *CCE) {
assert(isGslPointerType(CCE->getType()));
if (CCE->getNumArgs() != 1)
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 0fa75a24db4f3..fd6abc2874cb7 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2987,6 +2987,7 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
AC.getCFGBuildOptions().AddInitializers = true;
AC.getCFGBuildOptions().AddImplicitDtors = true;
AC.getCFGBuildOptions().AddTemporaryDtors = true;
+ AC.getCFGBuildOptions().AddLifetime = true;
AC.getCFGBuildOptions().AddCXXNewAllocator = false;
AC.getCFGBuildOptions().AddCXXDefaultInitExprInCtors = true;
diff --git a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
index 11d3b836db3e7..4d9d88fbc294d 100644
--- a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
@@ -59,6 +59,7 @@ int return_int_val() {
int x = 10;
// CHECK: Block B{{[0-9]+}}:
// CHECK: Issue ([[L_X:[0-9]+]] (Path: x), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr))
+// CHECK: Expire (0 (Path: x))
return x;
}
// CHECK-NEXT: End of Block
@@ -77,7 +78,6 @@ void loan_expires_cpp() {
}
-// FIXME: No expire for Trivial Destructors
// CHECK-LABEL: Function: loan_expires_trivial
void loan_expires_trivial() {
int trivial_obj = 1;
@@ -86,11 +86,29 @@ void loan_expires_trivial() {
// CHECK: OriginFlow (Dest: [[O_ADDR_TRIVIAL_OBJ:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_TRIVIAL]] (Expr: DeclRefExpr))
int* pTrivialObj = &trivial_obj;
// CHECK: OriginFlow (Dest: {{[0-9]+}} (Decl: pTrivialObj), Src: [[O_ADDR_TRIVIAL_OBJ]] (Expr: UnaryOperator))
-// CHECK-NOT: Expire
+// CHECK: Expire (0 (Path: trivial_obj))
// CHECK-NEXT: End of Block
- // FIXME: Add check for Expire once trivial destructors are handled for expiration.
}
+// Trivial Destructors
+// CHECK-LABEL: Function: return_int_pointer
+int* return_int_pointer() {
+ int* ptr;
+// CHECK: Block B{{[0-9]+}}:
+ int x = 1;
+// CHECK: Issue ([[L_X:[0-9]+]] (Path: x), ToOrigin: [[O_DRE_X:[0-9]+]] (Expr: DeclRefExpr))
+// CHECK: OriginFlow (Dest: [[O_ADDR_X:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_X]] (Expr: DeclRefExpr))
+ ptr = &x;
+// CHECK: Use ([[O_PTR:[0-9]+]] (Decl: ptr), Write)
+// CHECK: OriginFlow (Dest: [[O_PTR]] (Decl: ptr), Src: [[O_ADDR_X]] (Expr: UnaryOperator))
+// CHECK: Use ([[O_PTR]] (Decl: ptr), Read)
+// CHECK: OriginFlow (Dest: [[O_RET_VAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_PTR]] (Decl: ptr))
+// CHECK: Expire ([[L_X]] (Path: x))
+// CHECK: OriginEscapes ([[O_RET_VAL]] (Expr: ImplicitCastExpr))
+ return ptr;
+}
+// CHECK-NEXT: End of Block
+
// CHECK-LABEL: Function: conditional
void conditional(bool condition) {
int a = 5;
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 2803e73b5aee2..88c8d5076e5b5 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -12,6 +12,10 @@ struct [[gsl::Pointer()]] View {
void use() const;
};
+class S {
+ View a, b;
+};
+
//===----------------------------------------------------------------------===//
// Basic Definite Use-After-Free (-W...permissive)
// These are cases where the pointer is guaranteed to be dangling at the use site.
@@ -396,6 +400,24 @@ void loan_from_previous_iteration(MyObj safe, bool condition) {
} // expected-note {{destroyed here}}
}
+void trivial_uaf(){
+ int * a;
+ {
+ int b = 1;
+ a = &b; // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)*a; // expected-note {{later used here}}
+}
+
+void trivial_class_uaf() {
+ S* ptr;
+ {
+ S s;
+ ptr = &s; // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)ptr; // expected-note {{later used here}}
+}
+
//===----------------------------------------------------------------------===//
// Basic Definite Use-After-Return (Return-Stack-Address) (-W...permissive)
// These are cases where the pointer is guaranteed to be dangling at the use site.
@@ -493,6 +515,20 @@ MyObj& reference_return_of_local() {
// expected-note at -1 {{returned here}}
}
+int* trivial_uar() {
+ int *a;
+ int b = 1;
+ a = &b; // expected-warning {{address of stack memory is returned later}}
+ return a; // expected-note {{returned here}}
+}
+
+S* trivial_class_uar () {
+ S *ptr;
+ S s;
+ ptr = &s; // expected-warning {{address of stack memory is returned later}}
+ return ptr; // expected-note {{returned here}}
+}
+
//===----------------------------------------------------------------------===//
// Use-After-Scope & Use-After-Return (Return-Stack-Address) Combined
// These are cases where the diagnostic kind is determined by location
@@ -696,10 +732,10 @@ void lifetimebound_return_reference() {
{
MyObj obj;
View temp_v = obj;
- const MyObj& ref = GetObject(temp_v);
- ptr = &ref;
- }
- (void)*ptr;
+ const MyObj& ref = GetObject(temp_v);
+ ptr = &ref; // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)*ptr; // expected-note {{later used here}}
}
// FIXME: No warning for non gsl::Pointer types. Origin tracking is only supported for pointer types.
diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
index 558a22af72572..a895475013c98 100644
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -59,6 +59,7 @@ class LifetimeTestRunner {
BuildOptions.setAllAlwaysAdd();
BuildOptions.AddImplicitDtors = true;
BuildOptions.AddTemporaryDtors = true;
+ BuildOptions.AddLifetime = true;
// Run the main analysis.
Analysis = std::make_unique<LifetimeSafetyAnalysis>(*AnalysisCtx, nullptr);
@@ -1307,6 +1308,42 @@ TEST_F(LifetimeAnalysisTest, LivenessOutsideLoop) {
EXPECT_THAT(Origins({"p"}), MaybeLiveAt("p1"));
}
+TEST_F(LifetimeAnalysisTest, TrivialDestructorsUAF) {
+ SetupTest(R"(
+ void target() {
+ int *ptr;
+ {
+ int s = 1;
+ ptr = &s;
+ }
+ POINT(p1);
+ (void)*ptr;
+ }
+ )");
+ EXPECT_THAT(Origin("ptr"), HasLoansTo({"s"}, "p1"));
+ EXPECT_THAT(Origins({"ptr"}), MustBeLiveAt("p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, TrivialClassDestructorsUAF) {
+ SetupTest(R"(
+ class S {
+ View a, b;
+ };
+
+ void target() {
+ S* ptr;
+ {
+ S s;
+ ptr = &s;
+ }
+ POINT(p1);
+ (void)ptr;
+ }
+ )");
+ EXPECT_THAT(Origin("ptr"), HasLoansTo({"s"}, "p1"));
+ EXPECT_THAT(Origins({"ptr"}), MustBeLiveAt("p1"));
+}
+
TEST_F(LifetimeAnalysisTest, SimpleReturnStackAddress) {
SetupTest(R"(
MyObj* target() {
@@ -1506,5 +1543,34 @@ TEST_F(LifetimeAnalysisTest, ReturnBeforeUseAfterScope) {
EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p1"));
}
+TEST_F(LifetimeAnalysisTest, TrivialDestructorsUAR) {
+ SetupTest(R"(
+ int* target() {
+ int s = 10;
+ int* p = &s;
+ POINT(p1);
+ return p;
+ }
+ )");
+ EXPECT_THAT("s", HasLiveLoanAtExpiry("p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, TrivialClassDestructorsUAR) {
+ SetupTest(R"(
+ class S {
+ View a, b;
+ };
+
+ S* target() {
+ S *ptr;
+ S s;
+ ptr = &s;
+ POINT(p1);
+ return ptr;
+ }
+ )");
+ EXPECT_THAT("s", HasLiveLoanAtExpiry("p1"));
+}
+
} // anonymous namespace
} // namespace clang::lifetimes::internal
>From a88ac13cb38b7ceffa95d0ca97ed1404072423fe Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <akhourik at google.com>
Date: Thu, 20 Nov 2025 16:16:51 +0000
Subject: [PATCH 2/3] add EnableLifetimeSafetyAnalysis check
---
clang/lib/Sema/AnalysisBasedWarnings.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index fd6abc2874cb7..25799c20eb55e 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2987,7 +2987,6 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
AC.getCFGBuildOptions().AddInitializers = true;
AC.getCFGBuildOptions().AddImplicitDtors = true;
AC.getCFGBuildOptions().AddTemporaryDtors = true;
- AC.getCFGBuildOptions().AddLifetime = true;
AC.getCFGBuildOptions().AddCXXNewAllocator = false;
AC.getCFGBuildOptions().AddCXXDefaultInitExprInCtors = true;
@@ -3003,6 +3002,7 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
P.enableConsumedAnalysis || EnableLifetimeSafetyAnalysis) {
// Unreachable code analysis and thread safety require a linearized CFG.
AC.getCFGBuildOptions().setAllAlwaysAdd();
+ AC.getCFGBuildOptions().AddLifetime = true;
} else {
AC.getCFGBuildOptions()
.setAlwaysAdd(Stmt::BinaryOperatorClass)
>From 7df3c505cf12b5dd5280938e23ddf38260f8f272 Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <akhourik at google.com>
Date: Fri, 21 Nov 2025 09:20:19 +0000
Subject: [PATCH 3/3] Address comments and remove handleDestructor
---
.../Analyses/LifetimeSafety/FactsGenerator.h | 3 +-
.../LifetimeSafety/FactsGenerator.cpp | 27 +-----------
.../Sema/warn-lifetime-safety-dataflow.cpp | 4 +-
clang/test/Sema/warn-lifetime-safety.cpp | 44 ++++++++++++++-----
4 files changed, 39 insertions(+), 39 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 85192fdff5067..878cb90b685f9 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -50,8 +50,7 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE);
private:
- void handleDestructor(const CFGAutomaticObjDtor &DtorOpt);
- void handleTrivialDestructors(const CFGLifetimeEnds &LifetimeEnds);
+ void handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds);
void handleGSLPointerConstruction(const CXXConstructExpr *CCE);
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index fc92aba773187..7116bece1fe9f 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -63,12 +63,9 @@ void FactsGenerator::run() {
const CFGElement &Element = Block->Elements[I];
if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>())
Visit(CS->getStmt());
- else if (std::optional<CFGAutomaticObjDtor> DtorOpt =
- Element.getAs<CFGAutomaticObjDtor>())
- handleDestructor(*DtorOpt);
else if (std::optional<CFGLifetimeEnds> LifetimeEnds =
Element.getAs<CFGLifetimeEnds>())
- handleTrivialDestructors(*LifetimeEnds);
+ handleLifetimeEnds(*LifetimeEnds);
}
CurrentBlockFacts.append(EscapesInCurrentBlock.begin(),
EscapesInCurrentBlock.end());
@@ -232,31 +229,11 @@ void FactsGenerator::VisitMaterializeTemporaryExpr(
killAndFlowOrigin(*MTE, *MTE->getSubExpr());
}
-void FactsGenerator::handleDestructor(const CFGAutomaticObjDtor &DtorOpt) {
- /// TODO: Handle loans to temporaries.
- const VarDecl *DestructedVD = DtorOpt.getVarDecl();
- if (!DestructedVD)
- return;
- // Iterate through all loans to see if any expire.
- /// TODO(opt): Do better than a linear search to find loans associated with
- /// 'DestructedVD'.
- for (const Loan &L : FactMgr.getLoanMgr().getLoans()) {
- const AccessPath &LoanPath = L.Path;
- // Check if the loan is for a stack variable and if that variable
- // is the one being destructed.
- if (LoanPath.D == DestructedVD)
- CurrentBlockFacts.push_back(FactMgr.createFact<ExpireFact>(
- L.ID, DtorOpt.getTriggerStmt()->getEndLoc()));
- }
-}
-
-void FactsGenerator::handleTrivialDestructors(
- const CFGLifetimeEnds &LifetimeEnds) {
+void FactsGenerator::handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds) {
for (const auto &Loan : FactMgr.getLoanMgr().getLoans()) {
if (Loan.Path.D == LifetimeEnds.getVarDecl()) {
CurrentBlockFacts.push_back(FactMgr.createFact<ExpireFact>(
Loan.ID, LifetimeEnds.getTriggerStmt()->getEndLoc()));
- break;
}
}
}
diff --git a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
index 4d9d88fbc294d..7b6fc9201af6d 100644
--- a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
@@ -59,7 +59,7 @@ int return_int_val() {
int x = 10;
// CHECK: Block B{{[0-9]+}}:
// CHECK: Issue ([[L_X:[0-9]+]] (Path: x), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr))
-// CHECK: Expire (0 (Path: x))
+// CHECK: Expire ([[L_X:[0-9]+]] (Path: x))
return x;
}
// CHECK-NEXT: End of Block
@@ -86,7 +86,7 @@ void loan_expires_trivial() {
// CHECK: OriginFlow (Dest: [[O_ADDR_TRIVIAL_OBJ:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_TRIVIAL]] (Expr: DeclRefExpr))
int* pTrivialObj = &trivial_obj;
// CHECK: OriginFlow (Dest: {{[0-9]+}} (Decl: pTrivialObj), Src: [[O_ADDR_TRIVIAL_OBJ]] (Expr: UnaryOperator))
-// CHECK: Expire (0 (Path: trivial_obj))
+// CHECK: Expire ([[L_TRIVIAL_OBJ:[0-9]+]] (Path: trivial_obj))
// CHECK-NEXT: End of Block
}
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 88c8d5076e5b5..8a04a4686d667 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -12,7 +12,7 @@ struct [[gsl::Pointer()]] View {
void use() const;
};
-class S {
+class TriviallyDestructedClass {
View a, b;
};
@@ -400,7 +400,7 @@ void loan_from_previous_iteration(MyObj safe, bool condition) {
} // expected-note {{destroyed here}}
}
-void trivial_uaf(){
+void trivial_int_uaf(){
int * a;
{
int b = 1;
@@ -410,9 +410,9 @@ void trivial_uaf(){
}
void trivial_class_uaf() {
- S* ptr;
+ TriviallyDestructedClass* ptr;
{
- S s;
+ TriviallyDestructedClass s;
ptr = &s; // expected-warning {{object whose reference is captured does not live long enough}}
} // expected-note {{destroyed here}}
(void)ptr; // expected-note {{later used here}}
@@ -515,20 +515,43 @@ MyObj& reference_return_of_local() {
// expected-note at -1 {{returned here}}
}
-int* trivial_uar() {
+int* trivial_int_uar() {
int *a;
int b = 1;
a = &b; // expected-warning {{address of stack memory is returned later}}
return a; // expected-note {{returned here}}
}
-S* trivial_class_uar () {
- S *ptr;
- S s;
+TriviallyDestructedClass* trivial_class_uar () {
+ TriviallyDestructedClass *ptr;
+ TriviallyDestructedClass s;
ptr = &s; // expected-warning {{address of stack memory is returned later}}
return ptr; // expected-note {{returned here}}
}
+// FIXME: No lifetime warning for this as no loans are issued for paramters
+const int& return_parameter(int a) {
+ return a;
+}
+
+// FIXME: No lifetime warning for this as no loans are issued for paramters
+int* return_pointer_to_parameter(int a) {
+ return &a;
+}
+
+const int& return_reference_to_parameter(int a)
+{
+ const int &b = a;
+ return b; // expected-warning {{address of stack memory is returned later}}
+ // expected-note at -1 {{returned here}}
+}
+
+const int& get_ref_to_local() {
+ int local_var = 42;
+ return local_var; // expected-warning {{address of stack memory is returned later}}
+ // expected-note at -1 {{returned here}}
+}
+
//===----------------------------------------------------------------------===//
// Use-After-Scope & Use-After-Return (Return-Stack-Address) Combined
// These are cases where the diagnostic kind is determined by location
@@ -724,7 +747,8 @@ void lifetimebound_partial_safety(bool cond) {
v.use(); // expected-note {{later used here}}
}
-// FIXME: Creating reference from lifetimebound call doesn't propagate loans.
+// FIXME: Warning should be on the 'GetObject' call, not the assignment to 'ptr'.
+// The loan from the lifetimebound argument is not propagated to the call expression itself.
const MyObj& GetObject(View v [[clang::lifetimebound]]);
void lifetimebound_return_reference() {
View v;
@@ -732,7 +756,7 @@ void lifetimebound_return_reference() {
{
MyObj obj;
View temp_v = obj;
- const MyObj& ref = GetObject(temp_v);
+ const MyObj& ref = GetObject(temp_v);
ptr = &ref; // expected-warning {{object whose reference is captured does not live long enough}}
} // expected-note {{destroyed here}}
(void)*ptr; // expected-note {{later used here}}
More information about the cfe-commits
mailing list