[clang] [analyzer] Fix false positive on `UnreachableCode` with CTU mode (PR #219225)
Marco Milanese via cfe-commits
cfe-commits at lists.llvm.org
Thu Aug 27 08:08:46 PDT 2026
https://github.com/marco-milanese-sonarsource created https://github.com/llvm/llvm-project/pull/219225
This PR fixes a false positive on `UnreachableCodeChecker` occurring when STU exploration exhausts the steps budget and the CTU second phase completes its exploration (i.e., has no more work to do).
There are two code pieces to consider:
* When we transition from STU exploration to CTU exploration ([`CoreEngine.h:173`](https://github.com/llvm/llvm-project/blob/main/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp#L173)) we move `CTUWList` to `WList`:
```c++
if (CTUWList) {
NumSTUSteps += STUSteps;
const unsigned MinCTUSteps =
this->ExprEng.getAnalysisManager().options.CTUMaxNodesMin;
const unsigned Pct =
this->ExprEng.getAnalysisManager().options.CTUMaxNodesPercentage;
unsigned MaxCTUSteps = std::max(STUSteps * Pct / 100, MinCTUSteps);
WList = std::move(CTUWList); // remaining STU work gets discarded
const unsigned CTUSteps = ProcessWList(MaxCTUSteps);
NumCTUSteps += CTUSteps;
}
```
* At the end of the analysis `UnreachableCodeChecker` checks for code that was not explored. This is corresponds to actual dead code only as long as the exploration is complete, thus (correctly) the checker calls `hasWorkRemaining` ([`CoreEngine.h:149`](https://github.com/llvm/llvm-project/blob/main/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h#L149-L151)). If such predicate is true it immediately bails out as any finding may be due to the uncomplete exploration.
* `hasWorkRemaining` relies on `WList` being empty or not to determine if there is work remaining (i.e., complete/uncomplete exploration):
```c++
bool hasWorkRemaining() const { return wasBlocksExhausted() ||
WList->hasWork() ||
wasBlockAborted(); }
```
Now, consider the scenario where:
1. STU exploration was *not* exhaustive (i.e., some code was not analyzed because we did not have enough steps budget).
2. CTU exploration was exhaustive.
In this case `hasWorkRemaining` should return `true` as STU exploration had more work to do, but because we override it with the CTU worklist (and the latter is fully emptied), then `hasWorkRemaining` will return `false`, raising the false positive.
This can be simulated in the regression test by setting a very low STU budget and enabling CTU mode. Actually we don't
need to do any CTU work, supply extra TUs; this is because the switch to the CTU worklist (empty) is always done when CTU mode is enabled.
>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] 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;
+}
More information about the cfe-commits
mailing list