[clang] 41c566e - [Clang] eliminate -Winvalid-noreturn false positive after throw + unreachable try/catch blocks (#175443)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Feb 13 10:25:14 PST 2026
Author: Oleksandr Tarasiuk
Date: 2026-02-13T20:25:09+02:00
New Revision: 41c566e9e16489d599a9cf45e32a02aace925785
URL: https://github.com/llvm/llvm-project/commit/41c566e9e16489d599a9cf45e32a02aace925785
DIFF: https://github.com/llvm/llvm-project/commit/41c566e9e16489d599a9cf45e32a02aace925785.diff
LOG: [Clang] eliminate -Winvalid-noreturn false positive after throw + unreachable try/catch blocks (#175443)
Fixes #174822
---
This PR fixes a false `-Winvalid-noreturn` positive when an
unconditional `throw` is followed by an unreachable `try`/`catch` block.
The false positive occurred because reachability analysis marked
`try`/`catch` regions as live even when unreachable via normal control
flow. The following logic
https://github.com/llvm/llvm-project/blob/1c0c9aeae681dbed90fcb19edd8a41e10a17f867/clang/lib/Sema/AnalysisBasedWarnings.cpp#L573-L589
that adjusted for missing `EH` call edges, treated disconnected `try` as
reachable.
CFG construction now keeps the `try` dispatch block as the join point of
the `try` statement and adds a successor for the `try` body. If the
`try` body is empty, an empty block is created and connected to the
successor. This prevents the `try` dispatch from becoming disconnected
and ensures that return paths in _function-try-blocks_
https://github.com/llvm/llvm-project/blob/1c0c9aeae681dbed90fcb19edd8a41e10a17f867/clang/test/SemaCXX/return-noreturn.cpp#L249-L264
Also, unreachable try/catch code after an unconditional `throw` is no
longer treated as reachable and no longer triggers a false positive
`-Winvalid-noreturn` warning.
Added:
Modified:
clang/docs/ReleaseNotes.rst
clang/lib/Analysis/CFG.cpp
clang/lib/Sema/AnalysisBasedWarnings.cpp
clang/test/Analysis/auto-obj-dtors-cfg-output.cpp
clang/test/Analysis/misc-ps-region-store.cpp
clang/test/SemaCXX/return-noreturn.cpp
Removed:
################################################################################
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 2268622af0a0d..dd11ee3646922 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -258,14 +258,13 @@ Bug Fixes in This Version
- Fixed atomic boolean compound assignment; the conversion back to atomic bool would be miscompiled. (#GH33210)
- Fixed a failed assertion in the preprocessor when ``__has_embed`` parameters are missing parentheses. (#GH175088)
-
- Fix lifetime extension of temporaries in for-range-initializers in templates. (#GH165182)
- Fixed a preprocessor crash in ``__has_cpp_attribute`` on incomplete scoped attributes. (#GH178098)
- Fixes an assertion failure when evaluating ``__underlying_type`` on enum redeclarations. (#GH177943)
- Fixed an assertion failure caused by nested macro expansion during header-name lexing (``__has_embed(__has_include)``). (#GH178635)
-
- Clang now outputs relative paths of embeds for dependency output. (#GH161950)
- Fixed an assertion failure when evaluating ``_Countof`` on invalid ``void``-typed operands. (#GH180893)
+- Fixed a ``-Winvalid-noreturn`` false positive for unreachable ``try`` blocks following an unconditional ``throw``. (#GH174822)
Bug Fixes to Compiler Builtins
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp
index 8001a67a5e158..1b6cfc716cf93 100644
--- a/clang/lib/Analysis/CFG.cpp
+++ b/clang/lib/Analysis/CFG.cpp
@@ -4748,11 +4748,19 @@ CFGBlock *CFGBuilder::VisitCXXTryStmt(CXXTryStmt *Terminator) {
// Save the current "try" context.
SaveAndRestore SaveTry(TryTerminatedBlock, NewTryTerminatedBlock);
- cfg->addTryDispatchBlock(TryTerminatedBlock);
+ cfg->addTryDispatchBlock(NewTryTerminatedBlock);
assert(Terminator->getTryBlock() && "try must contain a non-NULL body");
Block = nullptr;
- return addStmt(Terminator->getTryBlock());
+
+ if (CFGBlock *TryBodyEntry = addStmt(Terminator->getTryBlock())) {
+ addSuccessor(NewTryTerminatedBlock, TryBodyEntry);
+ } else {
+ CFGBlock *EmptyTryBody = createBlock(/*add_successor=*/false);
+ addSuccessor(NewTryTerminatedBlock, EmptyTryBody);
+ addSuccessor(EmptyTryBody, TrySuccessor);
+ }
+ return NewTryTerminatedBlock;
}
CFGBlock *CFGBuilder::VisitCXXCatchStmt(CXXCatchStmt *CS) {
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 4f04bc3999a24..8c7bff27718de 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -573,26 +573,7 @@ static ControlFlowKind CheckFallThrough(AnalysisDeclContext &AC) {
// The CFG leaves in dead things, and we don't want the dead code paths to
// confuse us, so we mark all live things first.
llvm::BitVector live(cfg->getNumBlockIDs());
- unsigned count = reachable_code::ScanReachableFromBlock(&cfg->getEntry(),
- live);
-
- bool AddEHEdges = AC.getAddEHEdges();
- if (!AddEHEdges && count != cfg->getNumBlockIDs())
- // When there are things remaining dead, and we didn't add EH edges
- // from CallExprs to the catch clauses, we have to go back and
- // mark them as live.
- for (const auto *B : *cfg) {
- if (!live[B->getBlockID()]) {
- if (B->preds().empty()) {
- const Stmt *Term = B->getTerminatorStmt();
- if (isa_and_nonnull<CXXTryStmt>(Term))
- // When not adding EH edges from calls, catch clauses
- // can otherwise seem dead. Avoid noting them as dead.
- count += reachable_code::ScanReachableFromBlock(B, live);
- continue;
- }
- }
- }
+ reachable_code::ScanReachableFromBlock(&cfg->getEntry(), live);
// Now we know what is live, we check the live precessors of the exit block
// and look for fall through paths, being careful to ignore normal returns,
diff --git a/clang/test/Analysis/auto-obj-dtors-cfg-output.cpp b/clang/test/Analysis/auto-obj-dtors-cfg-output.cpp
index 96b9a5508cc08..882f30a7120a3 100644
--- a/clang/test/Analysis/auto-obj-dtors-cfg-output.cpp
+++ b/clang/test/Analysis/auto-obj-dtors-cfg-output.cpp
@@ -1296,17 +1296,21 @@ void test_for_inc_conditional() {
(void)0;
}
-// CHECK: [B3 (ENTRY)]
-// CHECK-NEXT: Succs (1): B0
+// CHECK: [B4 (ENTRY)]
+// CHECK-NEXT: Succs (1): B1
// CHECK: [B1]
// CHECK-NEXT: T: try ...
-// CHECK-NEXT: Succs (2): B2 B0
+// CHECK-NEXT: Preds (1): B4
+// CHECK-NEXT: Succs (3): B2 B0 B3
// CHECK: [B2]
// CHECK-NEXT: catch (const A &e):
// CHECK-NEXT: 1: catch (const A &e) {
// CHECK-NEXT: }
// CHECK-NEXT: Preds (1): B1
// CHECK-NEXT: Succs (1): B0
+// CHECK: [B3]
+// CHECK-NEXT: Preds (1): B1
+// CHECK-NEXT: Succs (1): B0
// CHECK: [B0 (EXIT)]
// CHECK-NEXT: Preds (3): B2 B1 B3
void test_catch_const_ref() {
@@ -1315,11 +1319,12 @@ void test_catch_const_ref() {
}
}
-// CHECK: [B3 (ENTRY)]
-// CHECK-NEXT: Succs (1): B0
+// CHECK: [B4 (ENTRY)]
+// CHECK-NEXT: Succs (1): B1
// CHECK: [B1]
// CHECK-NEXT: T: try ...
-// CHECK-NEXT: Succs (2): B2 B0
+// CHECK-NEXT: Preds (1): B4
+// CHECK-NEXT: Succs (3): B2 B0 B3
// CHECK: [B2]
// CHECK-NEXT: catch (A e):
// CHECK-NEXT: 1: catch (A e) {
@@ -1327,6 +1332,9 @@ void test_catch_const_ref() {
// CHECK-NEXT: 2: [B2.1].~A() (Implicit destructor)
// CHECK-NEXT: Preds (1): B1
// CHECK-NEXT: Succs (1): B0
+// CHECK: [B3]
+// CHECK-NEXT: Preds (1): B1
+// CHECK-NEXT: Succs (1): B0
// CHECK: [B0 (EXIT)]
// CHECK-NEXT: Preds (3): B2 B1 B3
void test_catch_copy() {
diff --git a/clang/test/Analysis/misc-ps-region-store.cpp b/clang/test/Analysis/misc-ps-region-store.cpp
index 958ad5ea40ea5..c0b7d648f78ee 100644
--- a/clang/test/Analysis/misc-ps-region-store.cpp
+++ b/clang/test/Analysis/misc-ps-region-store.cpp
@@ -525,8 +525,7 @@ MyEnum rdar10892489_positive() {
throw MyEnumValue;
} catch (MyEnum e) {
int *p = 0;
- // FALSE NEGATIVE
- *p = 0xDEADBEEF; // {{null}}
+ *p = 0xDEADBEEF; // expected-warning {{Dereference of null pointer (loaded from variable 'p')}}
return e;
}
return MyEnumValue;
@@ -551,8 +550,7 @@ void PR11545_positive() {
catch (...)
{
int *p = 0;
- // FALSE NEGATIVE
- *p = 0xDEADBEEF; // {{null}}
+ *p = 0xDEADBEEF; // expected-warning {{Dereference of null pointer (loaded from variable 'p')}}
}
}
diff --git a/clang/test/SemaCXX/return-noreturn.cpp b/clang/test/SemaCXX/return-noreturn.cpp
index 873e4c7e12f23..9137534c5149b 100644
--- a/clang/test/SemaCXX/return-noreturn.cpp
+++ b/clang/test/SemaCXX/return-noreturn.cpp
@@ -262,3 +262,10 @@ int functionTryBlock3(int s) try {
} catch (...) {
return 0;
} // ok, both paths return.
+
+namespace GH174822 {
+[[noreturn]] void t() {
+ throw 1;
+ try {} catch(...) {}
+}
+}
More information about the cfe-commits
mailing list