[clang] [LifetimeSafety] Add support for conditional operators (PR #167240)
Utkarsh Saxena via cfe-commits
cfe-commits at lists.llvm.org
Sun Nov 9 12:24:06 PST 2025
https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/167240
>From b90459cc39afd208fc4b019c38f704ff305567d2 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Sun, 9 Nov 2025 18:10:42 +0000
Subject: [PATCH] lifetime-safety-cxx-conditional
---
.../Analyses/LifetimeSafety/FactsGenerator.h | 1 +
.../LifetimeSafety/FactsGenerator.cpp | 8 +++
.../Sema/warn-lifetime-safety-dataflow.cpp | 17 ++++++
clang/test/Sema/warn-lifetime-safety.cpp | 53 +++++++++++++++++++
.../unittests/Analysis/LifetimeSafetyTest.cpp | 3 +-
5 files changed, 80 insertions(+), 2 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 5e58abee2bbb3..4c8ab3f859a49 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -43,6 +43,7 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
void VisitUnaryOperator(const UnaryOperator *UO);
void VisitReturnStmt(const ReturnStmt *RS);
void VisitBinaryOperator(const BinaryOperator *BO);
+ void VisitConditionalOperator(const ConditionalOperator *CO);
void VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE);
void VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr *FCE);
void VisitInitListExpr(const InitListExpr *ILE);
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index bec8e1dabb0b5..087a00b1b0d2d 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -176,6 +176,14 @@ void FactsGenerator::VisitBinaryOperator(const BinaryOperator *BO) {
handleAssignment(BO->getLHS(), BO->getRHS());
}
+void FactsGenerator::VisitConditionalOperator(const ConditionalOperator *CO) {
+ if (hasOrigin(CO)) {
+ // Merge origins from both branches of the conditional operator.
+ killAndFlowOrigin(*CO, *CO->getTrueExpr());
+ flowOrigin(*CO, *CO->getFalseExpr());
+ }
+}
+
void FactsGenerator::VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE) {
// Assignment operators have special "kill-then-propagate" semantics
// and are handled separately.
diff --git a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
index 31148b990d6bd..e9515b5d61006 100644
--- a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
@@ -414,3 +414,20 @@ void test_use_lifetimebound_call() {
// CHECK: Expire ([[L_Y]] (Path: y))
// CHECK: Expire ([[L_X]] (Path: x))
}
+// CHECK-LABEL: Function: test_conditional_operator
+void test_conditional_operator(bool cond) {
+ MyObj x, y;
+ MyObj *p = cond ? &x : &y;
+// CHECK: Block B{{[0-9]+}}:
+// 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))
+// CHECK: Block B{{[0-9]+}}:
+// CHECK: Issue ([[L_Y:[0-9]+]] (Path: y), ToOrigin: [[O_DRE_Y:[0-9]+]] (Expr: DeclRefExpr))
+// CHECK: OriginFlow (Dest: [[O_ADDR_Y:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_Y]] (Expr: DeclRefExpr))
+// CHECK: Block B{{[0-9]+}}:
+// CHECK: OriginFlow (Dest: [[O_COND_OP:[0-9]+]] (Expr: ConditionalOperator), Src: [[O_ADDR_X]] (Expr: UnaryOperator))
+// CHECK: OriginFlow (Dest: [[O_COND_OP]] (Expr: ConditionalOperator), Src: [[O_ADDR_Y]] (Expr: UnaryOperator), Merge)
+// CHECK: OriginFlow (Dest: [[O_P:[0-9]+]] (Decl: p), Src: [[O_COND_OP]] (Expr: ConditionalOperator))
+// CHECK: Expire ([[L_Y]] (Path: y))
+// CHECK: Expire ([[L_X]] (Path: x))
+}
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 4f234f0ac6e2d..3057ac9385736 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -440,6 +440,7 @@ void no_error_loan_from_current_iteration(bool cond) {
//===----------------------------------------------------------------------===//
View Identity(View v [[clang::lifetimebound]]);
+MyObj* Identity(MyObj* v [[clang::lifetimebound]]);
View Choose(bool cond, View a [[clang::lifetimebound]], View b [[clang::lifetimebound]]);
MyObj* GetPointer(const MyObj& obj [[clang::lifetimebound]]);
@@ -582,3 +583,55 @@ void lifetimebound_ctor() {
}
(void)v;
}
+
+// Conditional operator.
+void conditional_operator(bool cond) {
+ MyObj safe;
+ MyObj* p = &safe;
+ {
+ MyObj temp;
+ p = cond ? &temp // expected-warning {{object whose reference is captured may not live long enough}}
+ : &safe;
+ } // expected-note {{destroyed here}}
+ if (cond) p = &safe;
+ (void)*p; // expected-note {{later used here}}
+
+ {
+ MyObj a, b;
+ p = cond ? &a // expected-warning {{object whose reference is captured does not live long enough}}
+ : &b; // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note 2 {{destroyed here}}
+ (void)*p; // expected-note 2 {{later used here}}
+
+ {
+ MyObj a, b, c, d;
+ p = cond ? cond ? &a // expected-warning {{object whose reference is captured does not live long enough}}.
+ : &b // expected-warning {{object whose reference is captured does not live long enough}}.
+ : cond ? &c // expected-warning {{object whose reference is captured does not live long enough}}.
+ : &d; // expected-warning {{object whose reference is captured does not live long enough}}.
+ } // expected-note 4 {{destroyed here}}
+ (void)*p; // expected-note 4 {{later used here}}
+
+ {
+ MyObj a, b;
+ p = Identity(cond ? &a // expected-warning {{object whose reference is captured does not live long enough}}
+ : &b); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note 2 {{destroyed here}}
+ (void)*p; // expected-note 2 {{later used here}}
+
+ {
+ MyObj a, b;
+ p = Identity(cond ? Identity(&a) // expected-warning {{object whose reference is captured does not live long enough}}
+ : Identity(&b)); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note 2 {{destroyed here}}
+ (void)*p; // expected-note 2 {{later used here}}
+
+ {
+ MyObj a, b, c, d;
+ p = Identity(cond ? Identity(cond ? &a // expected-warning {{object whose reference is captured does not live long enough}}
+ : &b) // expected-warning {{object whose reference is captured does not live long enough}}
+ : Identity(cond ? &c // expected-warning {{object whose reference is captured does not live long enough}}
+ : &d)); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note 4 {{destroyed here}}
+ (void)*p; // expected-note 4 {{later used here}}
+}
diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
index 34af476843c0d..9d61d56e078e3 100644
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -689,7 +689,6 @@ TEST_F(LifetimeAnalysisTest, GslPointerConstructFromView) {
EXPECT_THAT(Origin("q"), HasLoansTo({"a"}, "p1"));
}
-// FIXME: Handle loans in ternary operator!
TEST_F(LifetimeAnalysisTest, GslPointerInConditionalOperator) {
SetupTest(R"(
void target(bool cond) {
@@ -698,7 +697,7 @@ TEST_F(LifetimeAnalysisTest, GslPointerInConditionalOperator) {
POINT(p1);
}
)");
- EXPECT_THAT(Origin("v"), HasLoansTo({}, "p1"));
+ EXPECT_THAT(Origin("v"), HasLoansTo({"a", "b"}, "p1"));
}
// FIXME: Handle temporaries.
More information about the cfe-commits
mailing list