From b6f1adc7038af27414786b4220d6aedb5c66169a Mon Sep 17 00:00:00 2001 From: Jan Voung Date: Mon, 16 Mar 2026 14:16:30 +0000 Subject: [PATCH 1/5] [clang][FlowSensitive] Do a quick check and bail early for massive CFGs Bail out early if the visiting each reachable basic block once would have exceeded the MaxBlockVisits limit. If that is the case, then actually visiting and doing the dataflow analysis would hit the limit, but we would have wasted a lot of time and possibly OOMed. We've seen example of CFGs with # of blocks that are 2-8x the visit limit. Those examples also tend to have lots of `Locs`, which we track in maps for each BB. Since the maps do not share memory across BBs, this leads to non-linear memory usage and OOMing before hitting the max visit limit. --- .../TypeErasedDataflowAnalysis.cpp | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp b/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp index 02982274093cb..0b2a45c963141 100644 --- a/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp +++ b/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp @@ -11,6 +11,7 @@ // //===----------------------------------------------------------------------===// +#include #include #include #include @@ -34,6 +35,7 @@ #include "clang/Basic/LLVM.h" #include "clang/Support/Compiler.h" #include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/BitVector.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/STLExtras.h" @@ -479,6 +481,27 @@ transferCFGBlock(const CFGBlock &Block, AnalysisContext &AC, return State; } +// Returns the number of blocks that would be visited if we only visit each +// reachable block once. This provides a lower bound on the number of block +// visits. This is a light version of the main analysis loop (keep in sync). +size_t NumBlockVisitsIfVisitEachReachableOnce(const CFG &CFG) { + PostOrderCFGView POV(&CFG); + ForwardDataflowWorklist Worklist(CFG, &POV); + llvm::BitVector VisitedBlocks(CFG.size()); + const CFGBlock &Entry = CFG.getEntry(); + Worklist.enqueueSuccessors(&Entry); + while (const CFGBlock *Block = Worklist.dequeue()) { + if (VisitedBlocks[Block->getBlockID()]) + continue; + // Do not add unreachable successor blocks to `Worklist`. + if (Block->hasNoReturnElement()) + continue; + VisitedBlocks[Block->getBlockID()] = true; + Worklist.enqueueSuccessors(Block); + } + return VisitedBlocks.count(); +} + llvm::Expected>> runTypeErasedDataflowAnalysis( const AdornedCFG &ACFG, TypeErasedDataflowAnalysis &Analysis, @@ -496,6 +519,14 @@ runTypeErasedDataflowAnalysis( MaybeStartingEnv ? *MaybeStartingEnv : InitEnv; const clang::CFG &CFG = ACFG.getCFG(); + if (CFG.size() > MaxBlockVisits) { + if (CFG.size() > NumBlockVisitsIfVisitEachReachableOnce(CFG)) { + return llvm::createStringError( + std::errc::timed_out, "number of blocks in cfg will lead to " + "exceeding maximum number of block visits"); + } + } + PostOrderCFGView POV(&CFG); ForwardDataflowWorklist Worklist(CFG, &POV); llvm::SmallDenseSet NonStructLoopBackedgeNodes = From c19a65a2ed2caee56e7e072160f17c3acf1e1f40 Mon Sep 17 00:00:00 2001 From: Jan Voung Date: Mon, 16 Mar 2026 14:39:25 +0000 Subject: [PATCH 2/5] Swap lines --- clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp b/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp index 0b2a45c963141..ece0875ef3962 100644 --- a/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp +++ b/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp @@ -493,10 +493,10 @@ size_t NumBlockVisitsIfVisitEachReachableOnce(const CFG &CFG) { while (const CFGBlock *Block = Worklist.dequeue()) { if (VisitedBlocks[Block->getBlockID()]) continue; + VisitedBlocks[Block->getBlockID()] = true; // Do not add unreachable successor blocks to `Worklist`. if (Block->hasNoReturnElement()) continue; - VisitedBlocks[Block->getBlockID()] = true; Worklist.enqueueSuccessors(Block); } return VisitedBlocks.count(); From a62a45447f5789612dfa850f9282342f904b8c74 Mon Sep 17 00:00:00 2001 From: Jan Voung Date: Mon, 16 Mar 2026 14:46:13 +0000 Subject: [PATCH 3/5] warning --- clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp b/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp index ece0875ef3962..004341f205c9d 100644 --- a/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp +++ b/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp @@ -519,7 +519,7 @@ runTypeErasedDataflowAnalysis( MaybeStartingEnv ? *MaybeStartingEnv : InitEnv; const clang::CFG &CFG = ACFG.getCFG(); - if (CFG.size() > MaxBlockVisits) { + if (CFG.size() > static_cast(MaxBlockVisits)) { if (CFG.size() > NumBlockVisitsIfVisitEachReachableOnce(CFG)) { return llvm::createStringError( std::errc::timed_out, "number of blocks in cfg will lead to " From 724ab377aa5606e3a18855a7da5b32dfb64e79f1 Mon Sep 17 00:00:00 2001 From: Jan Voung Date: Wed, 18 Mar 2026 14:43:32 +0000 Subject: [PATCH 4/5] Fix comparison and add a test. Add comments. --- .../TypeErasedDataflowAnalysis.cpp | 16 ++++++--- .../TypeErasedDataflowAnalysisTest.cpp | 33 +++++++++++++++++-- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp b/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp index 004341f205c9d..33767eeec2cd7 100644 --- a/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp +++ b/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp @@ -481,10 +481,10 @@ transferCFGBlock(const CFGBlock &Block, AnalysisContext &AC, return State; } -// Returns the number of blocks that would be visited if we only visit each -// reachable block once. This provides a lower bound on the number of block -// visits. This is a light version of the main analysis loop (keep in sync). -size_t NumBlockVisitsIfVisitEachReachableOnce(const CFG &CFG) { +// Returns the number of reachable blocks (would be visited if we only visit +// each reachable block once). This is a light version of the main fixpoint loop +// (keep in sync). +size_t NumBlockVisitsIfVisitOnce(const CFG &CFG) { PostOrderCFGView POV(&CFG); ForwardDataflowWorklist Worklist(CFG, &POV); llvm::BitVector VisitedBlocks(CFG.size()); @@ -519,8 +519,14 @@ runTypeErasedDataflowAnalysis( MaybeStartingEnv ? *MaybeStartingEnv : InitEnv; const clang::CFG &CFG = ACFG.getCFG(); + + // Bail out if the number of reachable blocks is already beyond the maximum + // number of block visits to save time and avoid runnign out of memory for + // massive CFGs. Note: CFG.size() could have unreachable blocks, + // but it is a faster to compare that to MaxBlockVisits first before doing the + // reachable block count. if (CFG.size() > static_cast(MaxBlockVisits)) { - if (CFG.size() > NumBlockVisitsIfVisitEachReachableOnce(CFG)) { + if (NumBlockVisitsIfVisitOnce(CFG) > static_cast(MaxBlockVisits)) { return llvm::createStringError( std::errc::timed_out, "number of blocks in cfg will lead to " "exceeding maximum number of block visits"); diff --git a/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp b/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp index 3a64bbdf52702..c5d88464acacd 100644 --- a/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp @@ -58,7 +58,8 @@ class DataflowAnalysisTest : public Test { template llvm::Expected>>> - runAnalysis(llvm::StringRef Code, AnalysisT (*MakeAnalysis)(ASTContext &)) { + runAnalysis(llvm::StringRef Code, AnalysisT (*MakeAnalysis)(ASTContext &), + int32_t MaxBlockVisits = MaxBlockVisitsInAnalysis) { AST = tooling::buildASTFromCodeWithArgs(Code, {"-std=c++11"}); auto *Func = selectFirst( @@ -75,7 +76,7 @@ class DataflowAnalysisTest : public Test { std::make_unique()); Environment Env(*DACtx, *Func); - return runDataflowAnalysis(*ACFG, Analysis, Env); + return runDataflowAnalysis(*ACFG, Analysis, Env, {}, MaxBlockVisits); } /// Returns the `CFGBlock` containing `S` (and asserts that it exists). @@ -450,6 +451,34 @@ TEST_F(DataflowAnalysisTest, NonConvergingAnalysis) { "maximum number of blocks processed"); } +TEST_F(DataflowAnalysisTest, MaxBlockVisitsWithUnreachableBlocks) { + std::string Code = R"( + void target() { + if (false) { + (void)0; + } + } + )"; + + // There are 4 blocks in the CFG: + // entry, if-header, then-body, and exit. + // But, only 2 of them are reachable, if we do not count the entry block, + // which is how we count BlockVisits. So: + // - MaxBlockVisits = 2 should still succeed (being less than CFG.size() = 4, + // shouldn't prevent the analysis from succeeding) + // - MaxBlockVisits = 1 should fail, as it is not enough to cover all of the + // reachable blocks. + EXPECT_THAT_EXPECTED( + runAnalysis( + Code, [](ASTContext &C) { return NoopAnalysis(C); }, 2), + llvm::Succeeded()); + EXPECT_THAT_EXPECTED( + runAnalysis( + Code, [](ASTContext &C) { return NoopAnalysis(C); }, 1), + llvm::FailedWithMessage("number of blocks in cfg will lead to exceeding " + "maximum number of block visits")); +} + // Regression test for joins of bool-typed lvalue expressions. The first loop // results in two passes through the code that follows. Each pass results in a // different `StorageLocation` for the pointee of `v`. Then, the second loop From f651577c4dfe32b6973741be3fe5241b39a5a2ad Mon Sep 17 00:00:00 2001 From: Jan Voung Date: Wed, 18 Mar 2026 20:40:32 +0000 Subject: [PATCH 5/5] Review comments and rename helper --- .../FlowSensitive/TypeErasedDataflowAnalysis.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp b/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp index 33767eeec2cd7..9e0fd8122c890 100644 --- a/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp +++ b/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp @@ -484,7 +484,7 @@ transferCFGBlock(const CFGBlock &Block, AnalysisContext &AC, // Returns the number of reachable blocks (would be visited if we only visit // each reachable block once). This is a light version of the main fixpoint loop // (keep in sync). -size_t NumBlockVisitsIfVisitOnce(const CFG &CFG) { +size_t NumReachableBlocks(const CFG &CFG) { PostOrderCFGView POV(&CFG); ForwardDataflowWorklist Worklist(CFG, &POV); llvm::BitVector VisitedBlocks(CFG.size()); @@ -521,16 +521,15 @@ runTypeErasedDataflowAnalysis( const clang::CFG &CFG = ACFG.getCFG(); // Bail out if the number of reachable blocks is already beyond the maximum - // number of block visits to save time and avoid runnign out of memory for + // number of block visits to save time and avoid running out of memory for // massive CFGs. Note: CFG.size() could have unreachable blocks, // but it is a faster to compare that to MaxBlockVisits first before doing the // reachable block count. - if (CFG.size() > static_cast(MaxBlockVisits)) { - if (NumBlockVisitsIfVisitOnce(CFG) > static_cast(MaxBlockVisits)) { - return llvm::createStringError( - std::errc::timed_out, "number of blocks in cfg will lead to " - "exceeding maximum number of block visits"); - } + if (CFG.size() > static_cast(MaxBlockVisits) && + NumReachableBlocks(CFG) > static_cast(MaxBlockVisits)) { + return llvm::createStringError(std::errc::timed_out, + "number of blocks in cfg will lead to " + "exceeding maximum number of block visits"); } PostOrderCFGView POV(&CFG);