[clang] [LifetimeSafety] Fix crash on ternary operator when one of the expressions is `throw` (PR #190345)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Apr 7 06:59:36 PDT 2026
https://github.com/NeKon69 updated https://github.com/llvm/llvm-project/pull/190345
>From 813689c7f7392ec5c44d90ec8a030a9ba102985d Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Fri, 3 Apr 2026 16:32:34 +0300
Subject: [PATCH 1/7] [LifetimeSafety] Apply the fix
---
.../Analysis/LifetimeSafety/FactsGenerator.cpp | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 75f2978d848b7..6108d34275092 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -94,6 +94,10 @@ static const Loan *createLoan(FactManager &FactMgr,
return FactMgr.getLoanMgr().createLoan(Path, MTE);
}
+static bool producesConditionalResult(const Expr *E) {
+ return isa<CXXThrowExpr>(E->IgnoreParenImpCasts());
+}
+
void FactsGenerator::run() {
llvm::TimeTraceScope TimeProfile("FactGenerator");
const CFG &Cfg = *AC.getCFG();
@@ -404,8 +408,18 @@ void FactsGenerator::VisitConditionalOperator(const ConditionalOperator *CO) {
if (hasOrigins(CO)) {
// Merge origins from both branches of the conditional operator.
// We kill to clear the initial state and merge both origins into it.
- killAndFlowOrigin(*CO, *CO->getTrueExpr());
- flowOrigin(*CO, *CO->getFalseExpr());
+ const Expr *TrueExpr = CO->getTrueExpr();
+ const Expr *FalseExpr = CO->getFalseExpr();
+ bool Initialized = false;
+ for (const Expr *Branch : {TrueExpr, FalseExpr}) {
+ if (producesConditionalResult(Branch))
+ continue;
+ if (!Initialized) {
+ killAndFlowOrigin(*CO, *Branch);
+ Initialized = true;
+ } else
+ flowOrigin(*CO, *Branch);
+ }
}
}
>From c43162e8d4de5a033ddc84a275f6fe3a44db78f7 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Fri, 3 Apr 2026 16:41:36 +0300
Subject: [PATCH 2/7] add a test file
---
.../test/Sema/warn-lifetime-safety-conditional-throw.cpp | 9 +++++++++
1 file changed, 9 insertions(+)
create mode 100644 clang/test/Sema/warn-lifetime-safety-conditional-throw.cpp
diff --git a/clang/test/Sema/warn-lifetime-safety-conditional-throw.cpp b/clang/test/Sema/warn-lifetime-safety-conditional-throw.cpp
new file mode 100644
index 0000000000000..2437618d131cd
--- /dev/null
+++ b/clang/test/Sema/warn-lifetime-safety-conditional-throw.cpp
@@ -0,0 +1,9 @@
+// RUN: %clang_cc1 -fsyntax-only -fcxx-exceptions -Wlifetime-safety -Wno-dangling -verify %s
+
+// expected-no-diagnostics
+
+void conditional_throw_branches(bool cond, int *value) {
+ (void)(cond ? throw 1 : value);
+ (void)(cond ? value : throw 1);
+ (void)(cond ? throw 1 : throw 2);
+}
>From ac67df326082b1a1ed7237742789b85bafaff45e Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Fri, 3 Apr 2026 16:43:14 +0300
Subject: [PATCH 3/7] update functions body to represent what it actually does
---
clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 6108d34275092..5eb2fcd150576 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -95,7 +95,7 @@ static const Loan *createLoan(FactManager &FactMgr,
}
static bool producesConditionalResult(const Expr *E) {
- return isa<CXXThrowExpr>(E->IgnoreParenImpCasts());
+ return !isa<CXXThrowExpr>(E->IgnoreParenImpCasts());
}
void FactsGenerator::run() {
@@ -412,7 +412,7 @@ void FactsGenerator::VisitConditionalOperator(const ConditionalOperator *CO) {
const Expr *FalseExpr = CO->getFalseExpr();
bool Initialized = false;
for (const Expr *Branch : {TrueExpr, FalseExpr}) {
- if (producesConditionalResult(Branch))
+ if (!producesConditionalResult(Branch))
continue;
if (!Initialized) {
killAndFlowOrigin(*CO, *Branch);
>From 4c89bd55fcb8d8a41c45962b4943358959325d33 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sun, 5 Apr 2026 14:01:44 +0300
Subject: [PATCH 4/7] [LifetimeSafety] Improve ternary handling to ignore
[[noreturn]] arms
---
.../Analyses/LifetimeSafety/FactsGenerator.h | 2 +
.../LifetimeSafety/FactsGenerator.cpp | 42 ++++++++++++-------
...warn-lifetime-safety-conditional-throw.cpp | 41 ++++++++++++++++--
3 files changed, 66 insertions(+), 19 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 8fe2436b04086..c17b603c00ddb 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -66,6 +66,8 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
void handlePointerArithmetic(const BinaryOperator *BO);
+ void handleTernaryOperator(const ConditionalOperator *CO);
+
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 5eb2fcd150576..617c3d53fd508 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -94,8 +94,8 @@ static const Loan *createLoan(FactManager &FactMgr,
return FactMgr.getLoanMgr().createLoan(Path, MTE);
}
-static bool producesConditionalResult(const Expr *E) {
- return !isa<CXXThrowExpr>(E->IgnoreParenImpCasts());
+static bool isThrowExpr(const Expr *E) {
+ return isa<CXXThrowExpr>(E->IgnoreParenImpCasts());
}
void FactsGenerator::run() {
@@ -404,22 +404,32 @@ void FactsGenerator::VisitBinaryOperator(const BinaryOperator *BO) {
// TODO: Handle assignments involving dereference like `*p = q`.
}
+void FactsGenerator::handleTernaryOperator(const ConditionalOperator *CO) {
+ const auto *Map = AC.getCFGStmtMap();
+ const Expr *TrueExpr = CO->getTrueExpr();
+ const Expr *FalseExpr = CO->getFalseExpr();
+ bool TBHasConditionResult =
+ Map->getBlock(TrueExpr)->hasNoReturnElement() || isThrowExpr(TrueExpr);
+ bool FBHasConditionResult =
+ Map->getBlock(FalseExpr)->hasNoReturnElement() || isThrowExpr(FalseExpr);
+ bool FirstFlow = true;
+ auto HandleFlow = [&](const Expr *E, bool HasNoReturn) {
+ if (HasNoReturn)
+ return;
+ if (FirstFlow) {
+ killAndFlowOrigin(*CO, *E);
+ FirstFlow = false;
+ } else {
+ flowOrigin(*CO, *E);
+ }
+ };
+ HandleFlow(TrueExpr, TBHasConditionResult);
+ HandleFlow(FalseExpr, FBHasConditionResult);
+}
+
void FactsGenerator::VisitConditionalOperator(const ConditionalOperator *CO) {
if (hasOrigins(CO)) {
- // Merge origins from both branches of the conditional operator.
- // We kill to clear the initial state and merge both origins into it.
- const Expr *TrueExpr = CO->getTrueExpr();
- const Expr *FalseExpr = CO->getFalseExpr();
- bool Initialized = false;
- for (const Expr *Branch : {TrueExpr, FalseExpr}) {
- if (!producesConditionalResult(Branch))
- continue;
- if (!Initialized) {
- killAndFlowOrigin(*CO, *Branch);
- Initialized = true;
- } else
- flowOrigin(*CO, *Branch);
- }
+ handleTernaryOperator(CO);
}
}
diff --git a/clang/test/Sema/warn-lifetime-safety-conditional-throw.cpp b/clang/test/Sema/warn-lifetime-safety-conditional-throw.cpp
index 2437618d131cd..874d885c4bce5 100644
--- a/clang/test/Sema/warn-lifetime-safety-conditional-throw.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-conditional-throw.cpp
@@ -1,9 +1,44 @@
// RUN: %clang_cc1 -fsyntax-only -fcxx-exceptions -Wlifetime-safety -Wno-dangling -verify %s
-// expected-no-diagnostics
-
-void conditional_throw_branches(bool cond, int *value) {
+void throw_branches(bool cond, int *value) {
(void)(cond ? throw 1 : value);
(void)(cond ? value : throw 1);
(void)(cond ? throw 1 : throw 2);
}
+
+int *f(int *p [[clang::lifetimebound]]);
+[[noreturn]] int *noret_f(int *p [[clang::lifetimebound]]);
+
+
+constexpr bool kTrue = true;
+constexpr bool kFalse = false;
+
+int *constexpr_dead_false(int *num) {
+ int local = 0;
+ return kTrue ? num : f(&local);
+}
+
+int *constexpr_dead_true(int *num) {
+ int local = 0;
+ return kFalse ? f(&local) : num;
+}
+
+int *constexpr_live_false(int *num) {
+ int local = 0;
+ return kFalse ? num : f(&local); // expected-warning {{address of stack memory is returned later}} // expected-note {{returned here}}
+}
+
+int *constexpr_live_true(int *num) {
+ int local = 0;
+ return kTrue ? f(&local) : num; // expected-warning {{address of stack memory is returned later}} // expected-note {{returned here}}
+}
+
+int *noreturn_dead_false(bool cond, int *num) {
+ int local = 0;
+ return cond ? num : noret_f(&local);
+}
+
+int *noreturn_dead_true(bool cond, int *num) {
+ int local = 0;
+ return cond ? noret_f(&local) : num;
+}
>From 0f1052c3d855559a2ac424ac36923dd09a4d63a0 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Mon, 6 Apr 2026 22:35:49 +0300
Subject: [PATCH 5/7] [LifetimeSafety] use isInevitablySinking to handle
ternary, update tests to include nested ones
---
.../LifetimeSafety/FactsGenerator.cpp | 16 ++++++++--------
...warn-lifetime-safety-conditional-throw.cpp | 19 +++++++++++--------
2 files changed, 19 insertions(+), 16 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 617c3d53fd508..7c61e590037df 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -406,12 +406,13 @@ void FactsGenerator::VisitBinaryOperator(const BinaryOperator *BO) {
void FactsGenerator::handleTernaryOperator(const ConditionalOperator *CO) {
const auto *Map = AC.getCFGStmtMap();
+
const Expr *TrueExpr = CO->getTrueExpr();
const Expr *FalseExpr = CO->getFalseExpr();
- bool TBHasConditionResult =
- Map->getBlock(TrueExpr)->hasNoReturnElement() || isThrowExpr(TrueExpr);
- bool FBHasConditionResult =
- Map->getBlock(FalseExpr)->hasNoReturnElement() || isThrowExpr(FalseExpr);
+
+ bool TBIsSinking = Map->getBlock(TrueExpr)->isInevitablySinking();
+ bool FBIsSinking = Map->getBlock(FalseExpr)->isInevitablySinking();
+
bool FirstFlow = true;
auto HandleFlow = [&](const Expr *E, bool HasNoReturn) {
if (HasNoReturn)
@@ -419,12 +420,11 @@ void FactsGenerator::handleTernaryOperator(const ConditionalOperator *CO) {
if (FirstFlow) {
killAndFlowOrigin(*CO, *E);
FirstFlow = false;
- } else {
+ } else
flowOrigin(*CO, *E);
- }
};
- HandleFlow(TrueExpr, TBHasConditionResult);
- HandleFlow(FalseExpr, FBHasConditionResult);
+ HandleFlow(TrueExpr, TBIsSinking);
+ HandleFlow(FalseExpr, FBIsSinking);
}
void FactsGenerator::VisitConditionalOperator(const ConditionalOperator *CO) {
diff --git a/clang/test/Sema/warn-lifetime-safety-conditional-throw.cpp b/clang/test/Sema/warn-lifetime-safety-conditional-throw.cpp
index 874d885c4bce5..5e3a9f01d89b2 100644
--- a/clang/test/Sema/warn-lifetime-safety-conditional-throw.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-conditional-throw.cpp
@@ -2,10 +2,14 @@
void throw_branches(bool cond, int *value) {
(void)(cond ? throw 1 : value);
- (void)(cond ? value : throw 1);
(void)(cond ? throw 1 : throw 2);
}
+void nested_throw_branches(bool cond, bool cond2, int *value) {
+ (void)(cond ? (cond2 ? throw 1 : value) : throw 2);
+ (void)(cond ? throw 1 : (cond2 ? value : throw 2));
+}
+
int *f(int *p [[clang::lifetimebound]]);
[[noreturn]] int *noret_f(int *p [[clang::lifetimebound]]);
@@ -18,9 +22,9 @@ int *constexpr_dead_false(int *num) {
return kTrue ? num : f(&local);
}
-int *constexpr_dead_true(int *num) {
+int *constexpr_dead_nested(int *num) {
int local = 0;
- return kFalse ? f(&local) : num;
+ return kTrue ? (kTrue ? num : f(&local)) : num;
}
int *constexpr_live_false(int *num) {
@@ -28,17 +32,16 @@ int *constexpr_live_false(int *num) {
return kFalse ? num : f(&local); // expected-warning {{address of stack memory is returned later}} // expected-note {{returned here}}
}
-int *constexpr_live_true(int *num) {
+int *constexpr_live_nested(int *num) {
int local = 0;
- return kTrue ? f(&local) : num; // expected-warning {{address of stack memory is returned later}} // expected-note {{returned here}}
-}
+ return kTrue ? (kFalse ? num : f(&local)) : num; } // expected-warning {{address of stack memory is returned later}} // expected-note {{returned here}}
int *noreturn_dead_false(bool cond, int *num) {
int local = 0;
return cond ? num : noret_f(&local);
}
-int *noreturn_dead_true(bool cond, int *num) {
+int *noreturn_dead_nested(bool cond, bool cond2, int *num) {
int local = 0;
- return cond ? noret_f(&local) : num;
+ return cond ? (cond2 ? num : noret_f(&local)) : num;
}
>From c6f0ed8ac1c56dfecfedc9c08f8c16c0a2128fe9 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Mon, 6 Apr 2026 22:53:28 +0300
Subject: [PATCH 6/7] delete unused function
---
clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 4 ----
1 file changed, 4 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 7c61e590037df..1fb83ef0d0288 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -94,10 +94,6 @@ static const Loan *createLoan(FactManager &FactMgr,
return FactMgr.getLoanMgr().createLoan(Path, MTE);
}
-static bool isThrowExpr(const Expr *E) {
- return isa<CXXThrowExpr>(E->IgnoreParenImpCasts());
-}
-
void FactsGenerator::run() {
llvm::TimeTraceScope TimeProfile("FactGenerator");
const CFG &Cfg = *AC.getCFG();
>From 10f358965cb967b0ae41b34d2fae93fddd914295 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Tue, 7 Apr 2026 16:59:17 +0300
Subject: [PATCH 7/7] push test code
---
.../Analysis/LifetimeSafety/FactsGenerator.cpp | 17 +++++++++++------
1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 1fb83ef0d0288..62d2c0db45572 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -402,16 +402,21 @@ void FactsGenerator::VisitBinaryOperator(const BinaryOperator *BO) {
void FactsGenerator::handleTernaryOperator(const ConditionalOperator *CO) {
const auto *Map = AC.getCFGStmtMap();
+ const auto *const COBlock = Map->getBlock(CO);
const Expr *TrueExpr = CO->getTrueExpr();
const Expr *FalseExpr = CO->getFalseExpr();
- bool TBIsSinking = Map->getBlock(TrueExpr)->isInevitablySinking();
- bool FBIsSinking = Map->getBlock(FalseExpr)->isInevitablySinking();
+ COBlock->dump();
+ const auto *TI = COBlock->succ_begin();
+ const auto *FI = TI + 1;
+
+ bool TBHasEdge = TI->getReachableBlock() != nullptr;
+ bool FBHasEdge = FI->getReachableBlock() != nullptr;
bool FirstFlow = true;
- auto HandleFlow = [&](const Expr *E, bool HasNoReturn) {
- if (HasNoReturn)
+ auto HandleFlow = [&](const Expr *E, bool HasAnEdge) {
+ if (!HasAnEdge)
return;
if (FirstFlow) {
killAndFlowOrigin(*CO, *E);
@@ -419,8 +424,8 @@ void FactsGenerator::handleTernaryOperator(const ConditionalOperator *CO) {
} else
flowOrigin(*CO, *E);
};
- HandleFlow(TrueExpr, TBIsSinking);
- HandleFlow(FalseExpr, FBIsSinking);
+ HandleFlow(TrueExpr, TBHasEdge);
+ HandleFlow(FalseExpr, FBHasEdge);
}
void FactsGenerator::VisitConditionalOperator(const ConditionalOperator *CO) {
More information about the cfe-commits
mailing list