https://github.com/NeKon69 updated https://github.com/llvm/llvm-project/pull/190345
>From 813689c7f7392ec5c44d90ec8a030a9ba102985d Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Fri, 3 Apr 2026 16:32:34 +0300 Subject: [PATCH 01/16] [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 <[email protected]> Date: Fri, 3 Apr 2026 16:41:36 +0300 Subject: [PATCH 02/16] 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 <[email protected]> Date: Fri, 3 Apr 2026 16:43:14 +0300 Subject: [PATCH 03/16] 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 <[email protected]> Date: Sun, 5 Apr 2026 14:01:44 +0300 Subject: [PATCH 04/16] [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 <[email protected]> Date: Mon, 6 Apr 2026 22:35:49 +0300 Subject: [PATCH 05/16] [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 <[email protected]> Date: Mon, 6 Apr 2026 22:53:28 +0300 Subject: [PATCH 06/16] 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 <[email protected]> Date: Tue, 7 Apr 2026 16:59:17 +0300 Subject: [PATCH 07/16] 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) { >From 8f7a0615d38eb78e34be7c662ce8cd0ab24293bf Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Tue, 7 Apr 2026 20:48:46 +0300 Subject: [PATCH 08/16] [LifetimeSafety] implement unpolished logic --- .../Analyses/LifetimeSafety/FactsGenerator.h | 1 + .../LifetimeSafety/FactsGenerator.cpp | 47 ++++++++++++++----- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h index c17b603c00ddb..b15e0bf69884f 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h @@ -136,6 +136,7 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> { // corresponding to the left-hand side is updated to be a "write", thereby // exempting it from the check. llvm::DenseMap<const Expr *, UseFact *> UseFacts; + const CFGBlock *CurrentBlock; }; } // namespace clang::lifetimes::internal diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index 62d2c0db45572..c9ebee2b8307d 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -103,6 +103,7 @@ void FactsGenerator::run() { for (const CFGBlock *Block : *AC.getAnalysis<PostOrderCFGView>()) { CurrentBlockFacts.clear(); EscapesInCurrentBlock.clear(); + CurrentBlock = Block; if (Block == &Cfg.getEntry()) CurrentBlockFacts.append(PlaceholderLoanFacts.begin(), PlaceholderLoanFacts.end()); @@ -401,18 +402,42 @@ 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(); - - COBlock->dump(); - const auto *TI = COBlock->succ_begin(); - const auto *FI = TI + 1; + const Expr *TrueExpr = CO->getTrueExpr()->IgnoreParenImpCasts(); + const Expr *FalseExpr = CO->getFalseExpr()->IgnoreParenImpCasts(); + + const auto Preds = CurrentBlock->preds(); + auto PredHasStmt = [](const CFGBlock::AdjacentBlock &Pred, const Stmt *S) { + return llvm::any_of(*Pred, [S](const CFGElement &Elt) { + if (auto CS = Elt.getAs<CFGStmt>()) { + // S->dump(); + // CS->getStmt()->dump(); + return CS->getStmt() == S; + } + return false; + }); + }; - bool TBHasEdge = TI->getReachableBlock() != nullptr; - bool FBHasEdge = FI->getReachableBlock() != nullptr; + bool TBHasEdge = true; + bool FBHasEdge = true; + + // CurrentBlock->dump(); + switch (CurrentBlock->pred_size()) { + case 0: + return; + case 1: + TBHasEdge = PredHasStmt(*Preds.begin(), TrueExpr); + FBHasEdge = !TBHasEdge; + break; + case 2: { + const auto *It = Preds.begin(); + TBHasEdge = It->isReachable(); + FBHasEdge = (++It)->isReachable(); + break; + } + default: + llvm_unreachable("expected at most 2 successors"); + return; + } bool FirstFlow = true; auto HandleFlow = [&](const Expr *E, bool HasAnEdge) { >From da3ed0ce9a88feb086d23acf85a41b118300fb53 Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Tue, 7 Apr 2026 22:18:30 +0300 Subject: [PATCH 09/16] fix test crashes --- clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index c9ebee2b8307d..6f9bff839c44c 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -402,15 +402,13 @@ void FactsGenerator::VisitBinaryOperator(const BinaryOperator *BO) { } void FactsGenerator::handleTernaryOperator(const ConditionalOperator *CO) { - const Expr *TrueExpr = CO->getTrueExpr()->IgnoreParenImpCasts(); - const Expr *FalseExpr = CO->getFalseExpr()->IgnoreParenImpCasts(); + const Expr *TrueExpr = CO->getTrueExpr(); + const Expr *FalseExpr = CO->getFalseExpr(); const auto Preds = CurrentBlock->preds(); auto PredHasStmt = [](const CFGBlock::AdjacentBlock &Pred, const Stmt *S) { return llvm::any_of(*Pred, [S](const CFGElement &Elt) { if (auto CS = Elt.getAs<CFGStmt>()) { - // S->dump(); - // CS->getStmt()->dump(); return CS->getStmt() == S; } return false; @@ -420,12 +418,11 @@ void FactsGenerator::handleTernaryOperator(const ConditionalOperator *CO) { bool TBHasEdge = true; bool FBHasEdge = true; - // CurrentBlock->dump(); switch (CurrentBlock->pred_size()) { case 0: return; case 1: - TBHasEdge = PredHasStmt(*Preds.begin(), TrueExpr); + TBHasEdge = PredHasStmt(*Preds.begin(), TrueExpr->IgnoreParenImpCasts()); FBHasEdge = !TBHasEdge; break; case 2: { >From 3eae67bdbfb79d08871a7f4650b30213c9709294 Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Tue, 7 Apr 2026 22:19:58 +0300 Subject: [PATCH 10/16] rename test file --- ...al-throw.cpp => warn-lifetime-safety-conditional-operator.cpp} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename clang/test/Sema/{warn-lifetime-safety-conditional-throw.cpp => warn-lifetime-safety-conditional-operator.cpp} (100%) diff --git a/clang/test/Sema/warn-lifetime-safety-conditional-throw.cpp b/clang/test/Sema/warn-lifetime-safety-conditional-operator.cpp similarity index 100% rename from clang/test/Sema/warn-lifetime-safety-conditional-throw.cpp rename to clang/test/Sema/warn-lifetime-safety-conditional-operator.cpp >From 175b883a3e7b9df6e51b856f9c0fb28c0c5378c6 Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Wed, 8 Apr 2026 23:02:27 +0300 Subject: [PATCH 11/16] update assert message --- clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index 6f9bff839c44c..7b5ee28e32c56 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -432,7 +432,7 @@ void FactsGenerator::handleTernaryOperator(const ConditionalOperator *CO) { break; } default: - llvm_unreachable("expected at most 2 successors"); + llvm_unreachable("expected at most 2 predecessors"); return; } >From 19ec7d421419087889469b5fc3f8bcc30038e422 Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Wed, 8 Apr 2026 23:04:40 +0300 Subject: [PATCH 12/16] fix formatting in one of the tests --- clang/test/Sema/warn-lifetime-safety-conditional-operator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/test/Sema/warn-lifetime-safety-conditional-operator.cpp b/clang/test/Sema/warn-lifetime-safety-conditional-operator.cpp index 5e3a9f01d89b2..b6800b5e39809 100644 --- a/clang/test/Sema/warn-lifetime-safety-conditional-operator.cpp +++ b/clang/test/Sema/warn-lifetime-safety-conditional-operator.cpp @@ -34,8 +34,8 @@ int *constexpr_live_false(int *num) { int *constexpr_live_nested(int *num) { int local = 0; - return kTrue ? (kFalse ? num : 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); >From 6a5124c2e1b360babf1c79b9507342a27921665e Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Thu, 9 Apr 2026 20:33:49 +0300 Subject: [PATCH 13/16] address review suggestions --- .../Analyses/LifetimeSafety/FactsGenerator.h | 2 - .../LifetimeSafety/FactsGenerator.cpp | 98 +++++++++---------- clang/test/Sema/warn-lifetime-safety.cpp | 55 +++++++++++ 3 files changed, 103 insertions(+), 52 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h index b15e0bf69884f..2dbadb27981a7 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h @@ -66,8 +66,6 @@ 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 7b5ee28e32c56..5a821f37e3ad4 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -401,58 +401,56 @@ void FactsGenerator::VisitBinaryOperator(const BinaryOperator *BO) { // TODO: Handle assignments involving dereference like `*p = q`. } -void FactsGenerator::handleTernaryOperator(const ConditionalOperator *CO) { - const Expr *TrueExpr = CO->getTrueExpr(); - const Expr *FalseExpr = CO->getFalseExpr(); - - const auto Preds = CurrentBlock->preds(); - auto PredHasStmt = [](const CFGBlock::AdjacentBlock &Pred, const Stmt *S) { - return llvm::any_of(*Pred, [S](const CFGElement &Elt) { - if (auto CS = Elt.getAs<CFGStmt>()) { - return CS->getStmt() == S; - } - return false; - }); - }; - - bool TBHasEdge = true; - bool FBHasEdge = true; - - switch (CurrentBlock->pred_size()) { - case 0: - return; - case 1: - TBHasEdge = PredHasStmt(*Preds.begin(), TrueExpr->IgnoreParenImpCasts()); - FBHasEdge = !TBHasEdge; - break; - case 2: { - const auto *It = Preds.begin(); - TBHasEdge = It->isReachable(); - FBHasEdge = (++It)->isReachable(); - break; - } - default: - llvm_unreachable("expected at most 2 predecessors"); - return; - } - - bool FirstFlow = true; - auto HandleFlow = [&](const Expr *E, bool HasAnEdge) { - if (!HasAnEdge) - return; - if (FirstFlow) { - killAndFlowOrigin(*CO, *E); - FirstFlow = false; - } else - flowOrigin(*CO, *E); - }; - HandleFlow(TrueExpr, TBHasEdge); - HandleFlow(FalseExpr, FBHasEdge); -} - void FactsGenerator::VisitConditionalOperator(const ConditionalOperator *CO) { if (hasOrigins(CO)) { - handleTernaryOperator(CO); + const Expr *TrueExpr = CO->getTrueExpr(); + const Expr *FalseExpr = CO->getFalseExpr(); + + const auto Preds = CurrentBlock->preds(); + auto PredHasStmt = [](const CFGBlock::AdjacentBlock &Pred, const Stmt *S) { + return llvm::any_of(*Pred, [S](const CFGElement &Elt) { + if (auto CS = Elt.getAs<CFGStmt>()) + return CS->getStmt() == S; + return false; + }); + }; + + // Skip origin flow from conditional operator arms that cannot produce the + // result value: throw arms and calls to noreturn functions. + bool TBHasEdge = true; + bool FBHasEdge = true; + + switch (CurrentBlock->pred_size()) { + case 0: + return; + case 1: + TBHasEdge = PredHasStmt(*Preds.begin(), TrueExpr->IgnoreParenImpCasts()); + FBHasEdge = !TBHasEdge; + break; + case 2: { + const auto *It = Preds.begin(); + TBHasEdge = It->isReachable(); + FBHasEdge = (++It)->isReachable(); + break; + } + default: + llvm_unreachable("expected at most 2 predecessors"); + return; + } + + bool FirstFlow = true; + auto HandleFlow = [&](const Expr *E) { + if (FirstFlow) { + killAndFlowOrigin(*CO, *E); + FirstFlow = false; + } else + flowOrigin(*CO, *E); + }; + + if (TBHasEdge) + HandleFlow(TrueExpr); + if (FBHasEdge) + HandleFlow(FalseExpr); } } diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp index 9b867e0fe1567..46e366744c798 100644 --- a/clang/test/Sema/warn-lifetime-safety.cpp +++ b/clang/test/Sema/warn-lifetime-safety.cpp @@ -2418,3 +2418,58 @@ void owner_outlives_lifetimebound_source() { } } // namespace track_origins_for_lifetimebound_record_type + +namespace conditional_operator_control_flow { +// https://github.com/llvm/llvm-project/issues/183895 + +#ifdef __cpp_exceptions + +void throw_branches(bool cond, int *value) { + (void)(cond ? throw 1 : value); + (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)); +} + +#endif + +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_nested(int *num) { + int local = 0; + return kTrue ? (kTrue ? num : 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_nested(int *num) { + int local = 0; + 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_nested(bool cond, bool cond2, int *num) { + int local = 0; + return cond ? (cond2 ? num : noret_f(&local)) : num; +} + +} // namespace conditional_operator_control_flow >From 70f9fa421a74d8fc436b8ba320b90c16f5fcbc99 Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Thu, 9 Apr 2026 20:34:26 +0300 Subject: [PATCH 14/16] delete old test file --- ...n-lifetime-safety-conditional-operator.cpp | 47 ------------------- 1 file changed, 47 deletions(-) delete mode 100644 clang/test/Sema/warn-lifetime-safety-conditional-operator.cpp diff --git a/clang/test/Sema/warn-lifetime-safety-conditional-operator.cpp b/clang/test/Sema/warn-lifetime-safety-conditional-operator.cpp deleted file mode 100644 index b6800b5e39809..0000000000000 --- a/clang/test/Sema/warn-lifetime-safety-conditional-operator.cpp +++ /dev/null @@ -1,47 +0,0 @@ -// RUN: %clang_cc1 -fsyntax-only -fcxx-exceptions -Wlifetime-safety -Wno-dangling -verify %s - -void throw_branches(bool cond, int *value) { - (void)(cond ? throw 1 : value); - (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]]); - - -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_nested(int *num) { - int local = 0; - return kTrue ? (kTrue ? num : 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_nested(int *num) { - int local = 0; - 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_nested(bool cond, bool cond2, int *num) { - int local = 0; - return cond ? (cond2 ? num : noret_f(&local)) : num; -} >From 303be90b8c21d4a555bdedd0f86f37ba16e81b50 Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Thu, 9 Apr 2026 22:36:58 +0300 Subject: [PATCH 15/16] return if no origins exist, update the test with additional run with enabled exceptions, set PruneTriviallyFalseEdges to true. --- .../LifetimeSafety/FactsGenerator.cpp | 97 ++++++++++--------- clang/lib/Sema/AnalysisBasedWarnings.cpp | 2 +- clang/test/Sema/warn-lifetime-safety.cpp | 1 + 3 files changed, 51 insertions(+), 49 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index 5a821f37e3ad4..ffe47d999736d 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -402,56 +402,57 @@ void FactsGenerator::VisitBinaryOperator(const BinaryOperator *BO) { } void FactsGenerator::VisitConditionalOperator(const ConditionalOperator *CO) { - if (hasOrigins(CO)) { - const Expr *TrueExpr = CO->getTrueExpr(); - const Expr *FalseExpr = CO->getFalseExpr(); - - const auto Preds = CurrentBlock->preds(); - auto PredHasStmt = [](const CFGBlock::AdjacentBlock &Pred, const Stmt *S) { - return llvm::any_of(*Pred, [S](const CFGElement &Elt) { - if (auto CS = Elt.getAs<CFGStmt>()) - return CS->getStmt() == S; - return false; - }); - }; - - // Skip origin flow from conditional operator arms that cannot produce the - // result value: throw arms and calls to noreturn functions. - bool TBHasEdge = true; - bool FBHasEdge = true; - - switch (CurrentBlock->pred_size()) { - case 0: - return; - case 1: - TBHasEdge = PredHasStmt(*Preds.begin(), TrueExpr->IgnoreParenImpCasts()); - FBHasEdge = !TBHasEdge; - break; - case 2: { - const auto *It = Preds.begin(); - TBHasEdge = It->isReachable(); - FBHasEdge = (++It)->isReachable(); - break; - } - default: - llvm_unreachable("expected at most 2 predecessors"); - return; - } + if (!hasOrigins(CO)) + return; + + const Expr *TrueExpr = CO->getTrueExpr(); + const Expr *FalseExpr = CO->getFalseExpr(); - bool FirstFlow = true; - auto HandleFlow = [&](const Expr *E) { - if (FirstFlow) { - killAndFlowOrigin(*CO, *E); - FirstFlow = false; - } else - flowOrigin(*CO, *E); - }; - - if (TBHasEdge) - HandleFlow(TrueExpr); - if (FBHasEdge) - HandleFlow(FalseExpr); + const auto Preds = CurrentBlock->preds(); + auto PredHasStmt = [](const CFGBlock::AdjacentBlock &Pred, const Stmt *S) { + return llvm::any_of(*Pred, [S](const CFGElement &Elt) { + if (auto CS = Elt.getAs<CFGStmt>()) + return CS->getStmt() == S; + return false; + }); + }; + + // Skip origin flow from conditional operator arms that cannot produce the + // result value: throw arms and calls to noreturn functions. + bool TBHasEdge = true; + bool FBHasEdge = true; + + switch (CurrentBlock->pred_size()) { + case 0: + return; + case 1: + TBHasEdge = PredHasStmt(*Preds.begin(), TrueExpr->IgnoreParenImpCasts()); + FBHasEdge = !TBHasEdge; + break; + case 2: { + const auto *It = Preds.begin(); + TBHasEdge = It->isReachable(); + FBHasEdge = (++It)->isReachable(); + break; + } + default: + llvm_unreachable("expected at most 2 predecessors"); + return; } + + bool FirstFlow = true; + auto HandleFlow = [&](const Expr *E) { + if (FirstFlow) { + killAndFlowOrigin(*CO, *E); + FirstFlow = false; + } else + flowOrigin(*CO, *E); + }; + + if (TBHasEdge) + HandleFlow(TrueExpr); + if (FBHasEdge) + HandleFlow(FalseExpr); } void FactsGenerator::VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE) { diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index 37ed7488bb927..48957caf61e5a 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -2922,7 +2922,7 @@ LifetimeSafetyTUAnalysis(Sema &S, TranslationUnitDecl *TU, if (!FD) continue; AnalysisDeclContext AC(nullptr, FD); - AC.getCFGBuildOptions().PruneTriviallyFalseEdges = false; + AC.getCFGBuildOptions().PruneTriviallyFalseEdges = true; AC.getCFGBuildOptions().AddLifetime = true; AC.getCFGBuildOptions().AddParameterLifetimes = true; AC.getCFGBuildOptions().setAllAlwaysAdd(); diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp index 46e366744c798..b45fa2a60a2ea 100644 --- a/clang/test/Sema/warn-lifetime-safety.cpp +++ b/clang/test/Sema/warn-lifetime-safety.cpp @@ -1,5 +1,6 @@ // RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -verify=expected,function %s // RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety -Wno-dangling -verify=expected,tu %s +// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -fcxx-exceptions -verify=expected,function,exception %s #include "Inputs/lifetime-analysis.h" >From e8fa6af82bd8e2a8176af259d86180f39901b7a8 Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Fri, 10 Apr 2026 19:08:47 +0300 Subject: [PATCH 16/16] address review comments --- .../LifetimeSafety/FactsGenerator.cpp | 21 ++++++++++--------- clang/test/Sema/warn-lifetime-safety.cpp | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index ffe47d999736d..9d386803ca22d 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -409,13 +409,6 @@ void FactsGenerator::VisitConditionalOperator(const ConditionalOperator *CO) { const Expr *FalseExpr = CO->getFalseExpr(); const auto Preds = CurrentBlock->preds(); - auto PredHasStmt = [](const CFGBlock::AdjacentBlock &Pred, const Stmt *S) { - return llvm::any_of(*Pred, [S](const CFGElement &Elt) { - if (auto CS = Elt.getAs<CFGStmt>()) - return CS->getStmt() == S; - return false; - }); - }; // Skip origin flow from conditional operator arms that cannot produce the // result value: throw arms and calls to noreturn functions. @@ -425,10 +418,17 @@ void FactsGenerator::VisitConditionalOperator(const ConditionalOperator *CO) { switch (CurrentBlock->pred_size()) { case 0: return; - case 1: - TBHasEdge = PredHasStmt(*Preds.begin(), TrueExpr->IgnoreParenImpCasts()); + case 1: { + TBHasEdge = llvm::any_of(**Preds.begin(), + [ExpectedStmt = TrueExpr->IgnoreParenImpCasts()]( + const CFGElement &Elt) { + if (auto CS = Elt.getAs<CFGStmt>()) + return CS->getStmt() == ExpectedStmt; + return false; + }); FBHasEdge = !TBHasEdge; break; + } case 2: { const auto *It = Preds.begin(); TBHasEdge = It->isReachable(); @@ -445,8 +445,9 @@ void FactsGenerator::VisitConditionalOperator(const ConditionalOperator *CO) { if (FirstFlow) { killAndFlowOrigin(*CO, *E); FirstFlow = false; - } else + } else { flowOrigin(*CO, *E); + } }; if (TBHasEdge) diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp index b45fa2a60a2ea..0251a1d37ee59 100644 --- a/clang/test/Sema/warn-lifetime-safety.cpp +++ b/clang/test/Sema/warn-lifetime-safety.cpp @@ -1,6 +1,6 @@ // RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -verify=expected,function %s // RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety -Wno-dangling -verify=expected,tu %s -// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -fcxx-exceptions -verify=expected,function,exception %s +// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -fcxx-exceptions -verify=expected,function %s #include "Inputs/lifetime-analysis.h" _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
