[clang] [analyzer] Fix false positive on `UnreachableCode` with CTU mode (PR #219225)
Marco Milanese via cfe-commits
cfe-commits at lists.llvm.org
Tue Sep 1 02:05:41 PDT 2026
https://github.com/marco-milanese-sonarsource updated https://github.com/llvm/llvm-project/pull/219225
>From 59f08b836ae562a43939d8317b5855bafed5e2c0 Mon Sep 17 00:00:00 2001
From: Marco Milanese <marco.milanese at sonarsource.com>
Date: Thu, 27 Aug 2026 16:42:47 +0200
Subject: [PATCH 1/5] Add regression test
---
clang/test/Analysis/ctu/stu-workremaining.cpp | 21 +++++++++++++++++++
1 file changed, 21 insertions(+)
create mode 100644 clang/test/Analysis/ctu/stu-workremaining.cpp
diff --git a/clang/test/Analysis/ctu/stu-workremaining.cpp b/clang/test/Analysis/ctu/stu-workremaining.cpp
new file mode 100644
index 0000000000000..1524a98ec9a93
--- /dev/null
+++ b/clang/test/Analysis/ctu/stu-workremaining.cpp
@@ -0,0 +1,21 @@
+// RUN: %clang_analyze_cc1 -std=c++20 \
+// RUN: -analyzer-checker=core,alpha.deadcode.UnreachableCode \
+// RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \
+// RUN: -analyzer-config max-nodes=10 \
+// RUN: -verify=ctu-on %s
+// ctu-on-no-diagnostics
+
+// RUN: %clang_analyze_cc1 -std=c++20 \
+// RUN: -analyzer-checker=core,alpha.deadcode.UnreachableCode \
+// RUN: -analyzer-config experimental-enable-naive-ctu-analysis=false \
+// RUN: -analyzer-config max-nodes=10 \
+// RUN: -verify=ctu-off %s
+// ctu-off-no-diagnostics
+
+#define NOP ((void)0)
+
+void entrypoint(int x) {
+ NOP; NOP; NOP; NOP; NOP;
+ NOP; NOP; NOP; NOP; NOP;
+ if (x) NOP;
+}
>From 0cad94e9c5d5918005f1d45a057b7641acd224a0 Mon Sep 17 00:00:00 2001
From: Marco Milanese <marco.milanese at sonarsource.com>
Date: Thu, 27 Aug 2026 17:47:59 +0200
Subject: [PATCH 2/5] Track whether STU finished with work remaining
---
.../clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h | 5 +++++
clang/lib/StaticAnalyzer/Core/CoreEngine.cpp | 1 +
2 files changed, 6 insertions(+)
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h
index d26c0d9257b0f..a5c24dd97e3d2 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h
@@ -82,6 +82,10 @@ class CoreEngine {
/// usually because it could not reason about something.
BlocksAborted blocksAborted;
+ /// Whether the single-TU phase ran out of budget with work left over.
+ /// The CTU phase replaces \c WList, so this has to be remembered separately.
+ bool STUHadWorkRemaining = false;
+
/// The information about functions shared by the whole translation unit.
/// (This data is owned by AnalysisConsumer.)
FunctionSummariesTy *FunctionSummaries;
@@ -148,6 +152,7 @@ class CoreEngine {
bool wasBlocksExhausted() const { return !blocksExhausted.empty(); }
bool hasWorkRemaining() const { return wasBlocksExhausted() ||
WList->hasWork() ||
+ STUHadWorkRemaining ||
wasBlockAborted(); }
/// Inform the CoreEngine that a basic block was aborted because
diff --git a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp
index 307c96b23b206..85fddd16057a9 100644
--- a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp
@@ -161,6 +161,7 @@ bool CoreEngine::ExecuteWorkList(const StackFrame *SF, unsigned MaxSteps,
return MaxSteps - Steps;
};
const unsigned STUSteps = ProcessWList(MaxSteps);
+ STUHadWorkRemaining = WList->hasWork();
if (CTUWList) {
NumSTUSteps += STUSteps;
>From 098a10f514f865b557e1cd5ccf9a799ec61dab4b Mon Sep 17 00:00:00 2001
From: Marco Milanese <marco.milanese at sonarsource.com>
Date: Fri, 28 Aug 2026 08:52:26 +0200
Subject: [PATCH 3/5] Fix formatting issue
---
.../clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h
index a5c24dd97e3d2..46399793306b8 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h
@@ -150,10 +150,10 @@ class CoreEngine {
// Functions for external checking of whether we have unfinished work.
bool wasBlockAborted() const { return !blocksAborted.empty(); }
bool wasBlocksExhausted() const { return !blocksExhausted.empty(); }
- bool hasWorkRemaining() const { return wasBlocksExhausted() ||
- WList->hasWork() ||
- STUHadWorkRemaining ||
- wasBlockAborted(); }
+ bool hasWorkRemaining() const {
+ return wasBlocksExhausted() || WList->hasWork() || STUHadWorkRemaining ||
+ wasBlockAborted();
+ }
/// Inform the CoreEngine that a basic block was aborted because
/// it could not be completely analyzed.
>From b75c75cade2164039968f091b1e191dfb58a0b66 Mon Sep 17 00:00:00 2001
From: Marco Milanese <marco.milanese at sonarsource.com>
Date: Fri, 28 Aug 2026 14:28:05 +0200
Subject: [PATCH 4/5] Improve regression test
---
.../ctu/stu-workremaining-with-ctu-calls.cpp | 70 +++++++++++++++++++
clang/test/Analysis/ctu/stu-workremaining.cpp | 14 ++--
2 files changed, 80 insertions(+), 4 deletions(-)
create mode 100644 clang/test/Analysis/ctu/stu-workremaining-with-ctu-calls.cpp
diff --git a/clang/test/Analysis/ctu/stu-workremaining-with-ctu-calls.cpp b/clang/test/Analysis/ctu/stu-workremaining-with-ctu-calls.cpp
new file mode 100644
index 0000000000000..7a288ea4bc66d
--- /dev/null
+++ b/clang/test/Analysis/ctu/stu-workremaining-with-ctu-calls.cpp
@@ -0,0 +1,70 @@
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+// RUN: split-file %s %t
+
+// RUN: %clang_cc1 -std=c++20 -emit-pch -o %t/other.cpp.ast %t/other.cpp
+
+// RUN: %clang_extdef_map %t/other.cpp -- -std=c++20 >> %t/externalDefMap.tmp.txt
+// On windows, absolute paths generated by extdef_map are not recognized,
+// so CSA prepends the workdir path to them. Force relative paths to work
+// around this issue.
+// RUN: sed -e 's| .*other\.cpp| other.cpp.ast|' \
+// RUN: %t/externalDefMap.tmp.txt > %t/externalDefMap.txt
+
+// RUN: %clang_analyze_cc1 -std=c++20 \
+// RUN: -analyzer-checker=core,alpha.deadcode.UnreachableCode \
+// RUN: -analyzer-config experimental-enable-naive-ctu-analysis=false \
+// RUN: -analyzer-config max-nodes=50 \
+// RUN: -verify=ctu-off %t/main.cpp
+
+// RUN: %clang_analyze_cc1 -std=c++20 \
+// RUN: -analyzer-checker=core,alpha.deadcode.UnreachableCode \
+// RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \
+// RUN: -analyzer-config ctu-dir=%t \
+// RUN: -analyzer-config max-nodes=50 \
+// RUN: -analyzer-config ctu-phase1-inlining=none \
+// RUN: -verify=ctu-on %t/main.cpp
+
+//--- main.cpp
+
+#define NOP ((void)0)
+
+extern void other(void);
+
+void tp_no_cross_call(int x) {
+ // ctu-on-warning at +4{{This statement is never executed}}
+ // ctu-on-warning at +3{{self-comparison always evaluates to false}}
+ // ctu-off-warning at +2{{This statement is never executed}}
+ // ctu-off-warning at +1{{self-comparison always evaluates to false}}
+ if (x != x) NOP;
+}
+
+void tp_cross_call(int x) {
+ other();
+ // ctu-on-warning at +4{{This statement is never executed}}
+ // ctu-on-warning at +3{{self-comparison always evaluates to false}}
+ // ctu-off-warning at +2{{This statement is never executed}}
+ // ctu-off-warning at +1{{self-comparison always evaluates to false}}
+ if (x != x) NOP;
+}
+
+void fp_no_cross_call(int x) {
+ NOP; NOP; NOP; NOP; NOP; NOP; NOP; NOP;
+ NOP; NOP; NOP; NOP; NOP; NOP; NOP; NOP;
+ NOP; NOP; NOP; NOP; NOP; NOP; NOP; NOP;
+ NOP; NOP; NOP; NOP; NOP; NOP; NOP; NOP;
+ if (x) NOP;
+}
+
+void fp_cross_call(int x) {
+ other();
+ NOP; NOP; NOP; NOP; NOP; NOP; NOP; NOP;
+ NOP; NOP; NOP; NOP; NOP; NOP; NOP; NOP;
+ NOP; NOP; NOP; NOP; NOP; NOP; NOP; NOP;
+ NOP; NOP; NOP; NOP; NOP; NOP; NOP; NOP;
+ if (x) NOP;
+}
+
+//--- other.cpp
+
+void other() { return; }
diff --git a/clang/test/Analysis/ctu/stu-workremaining.cpp b/clang/test/Analysis/ctu/stu-workremaining.cpp
index 1524a98ec9a93..2fb1e609e3b1d 100644
--- a/clang/test/Analysis/ctu/stu-workremaining.cpp
+++ b/clang/test/Analysis/ctu/stu-workremaining.cpp
@@ -3,19 +3,25 @@
// RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \
// RUN: -analyzer-config max-nodes=10 \
// RUN: -verify=ctu-on %s
-// ctu-on-no-diagnostics
// RUN: %clang_analyze_cc1 -std=c++20 \
// RUN: -analyzer-checker=core,alpha.deadcode.UnreachableCode \
// RUN: -analyzer-config experimental-enable-naive-ctu-analysis=false \
// RUN: -analyzer-config max-nodes=10 \
// RUN: -verify=ctu-off %s
-// ctu-off-no-diagnostics
#define NOP ((void)0)
-void entrypoint(int x) {
+void tp(int x) {
+ // ctu-on-warning at +4{{This statement is never executed}}
+ // ctu-on-warning at +3{{self-comparison always evaluates to false}}
+ // ctu-off-warning at +2{{This statement is never executed}}
+ // ctu-off-warning at +1{{self-comparison always evaluates to false}}
+ if (x != x) NOP;
+}
+
+void fp(int x) {
NOP; NOP; NOP; NOP; NOP;
NOP; NOP; NOP; NOP; NOP;
- if (x) NOP;
+ if (x) NOP; // no-warning: the true branch might be alive even in CTU
}
>From 9a581d75ecee0cc0fc60856ef4e32cc915b3268f Mon Sep 17 00:00:00 2001
From: Marco Milanese <marco.milanese at sonarsource.com>
Date: Tue, 1 Sep 2026 11:01:47 +0200
Subject: [PATCH 5/5] Improve naming
---
.../clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h | 6 +++---
.../clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h | 2 +-
.../lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp | 2 +-
clang/lib/StaticAnalyzer/Core/CoreEngine.cpp | 2 +-
4 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h
index 46399793306b8..67bd1504d5ffb 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h
@@ -84,7 +84,7 @@ class CoreEngine {
/// Whether the single-TU phase ran out of budget with work left over.
/// The CTU phase replaces \c WList, so this has to be remembered separately.
- bool STUHadWorkRemaining = false;
+ bool exploredAllSTUPaths = false;
/// The information about functions shared by the whole translation unit.
/// (This data is owned by AnalysisConsumer.)
@@ -150,8 +150,8 @@ class CoreEngine {
// Functions for external checking of whether we have unfinished work.
bool wasBlockAborted() const { return !blocksAborted.empty(); }
bool wasBlocksExhausted() const { return !blocksExhausted.empty(); }
- bool hasWorkRemaining() const {
- return wasBlocksExhausted() || WList->hasWork() || STUHadWorkRemaining ||
+ bool hasExploredAllPaths() const {
+ return wasBlocksExhausted() || WList->hasWork() || exploredAllSTUPaths ||
wasBlockAborted();
}
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
index 64a2ebe5149e4..195d63b0e0936 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
@@ -467,7 +467,7 @@ class ExprEngine {
// Functions for external checking of whether we have unfinished work.
bool wasBlocksExhausted() const { return Engine.wasBlocksExhausted(); }
bool hasEmptyWorkList() const { return !Engine.getWorkList()->hasWork(); }
- bool hasWorkRemaining() const { return Engine.hasWorkRemaining(); }
+ bool hasExploredAllPaths() const { return Engine.hasExploredAllPaths(); }
const CoreEngine &getCoreEngine() const { return Engine; }
diff --git a/clang/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp
index 0ab282feb67bb..57b079e40e6c4 100644
--- a/clang/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp
@@ -51,7 +51,7 @@ void UnreachableCodeChecker::checkEndAnalysis(ExplodedGraph &G,
ExprEngine &Eng) const {
CFGBlocksSet reachable, visited;
- if (Eng.hasWorkRemaining())
+ if (Eng.hasExploredAllPaths())
return;
const Decl *D = nullptr;
diff --git a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp
index 85fddd16057a9..593346b2354a1 100644
--- a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp
@@ -161,7 +161,7 @@ bool CoreEngine::ExecuteWorkList(const StackFrame *SF, unsigned MaxSteps,
return MaxSteps - Steps;
};
const unsigned STUSteps = ProcessWList(MaxSteps);
- STUHadWorkRemaining = WList->hasWork();
+ exploredAllSTUPaths = WList->hasWork();
if (CTUWList) {
NumSTUSteps += STUSteps;
More information about the cfe-commits
mailing list