Skip to content

Commit f3dd8f1

Browse files
authored
[clang][dataflow] Make cap on block visits configurable by caller. (#77481)
Previously, we hard-coded the cap on block visits inside the framework. This patch enables the caller to specify the cap in the APIs for running an analysis.
1 parent 888f2a5 commit f3dd8f1

File tree

7 files changed

+49
-29
lines changed

7 files changed

+49
-29
lines changed

clang/include/clang/Analysis/FlowSensitive/DataflowAnalysis.h

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,12 @@ template <typename LatticeT> struct DataflowAnalysisState {
186186
/// the dataflow analysis cannot be performed successfully. Otherwise, calls
187187
/// `PostVisitCFG` on each CFG element with the final analysis results at that
188188
/// program point.
189+
///
190+
/// `MaxBlockVisits` caps the number of block visits during analysis. See
191+
/// `runTypeErasedDataflowAnalysis` for a full description. The default value is
192+
/// essentially arbitrary -- large enough to accommodate what seems like any
193+
/// reasonable CFG, but still small enough to limit the cost of hitting the
194+
/// limit.
189195
template <typename AnalysisT>
190196
llvm::Expected<std::vector<
191197
std::optional<DataflowAnalysisState<typename AnalysisT::Lattice>>>>
@@ -194,7 +200,8 @@ runDataflowAnalysis(
194200
const Environment &InitEnv,
195201
std::function<void(const CFGElement &, const DataflowAnalysisState<
196202
typename AnalysisT::Lattice> &)>
197-
PostVisitCFG = nullptr) {
203+
PostVisitCFG = nullptr,
204+
std::int32_t MaxBlockVisits = 20'000) {
198205
std::function<void(const CFGElement &,
199206
const TypeErasedDataflowAnalysisState &)>
200207
PostVisitCFGClosure = nullptr;
@@ -212,7 +219,7 @@ runDataflowAnalysis(
212219
}
213220

214221
auto TypeErasedBlockStates = runTypeErasedDataflowAnalysis(
215-
CFCtx, Analysis, InitEnv, PostVisitCFGClosure);
222+
CFCtx, Analysis, InitEnv, PostVisitCFGClosure, MaxBlockVisits);
216223
if (!TypeErasedBlockStates)
217224
return TypeErasedBlockStates.takeError();
218225

@@ -261,14 +268,19 @@ auto createAnalysis(ASTContext &ASTCtx, Environment &Env)
261268
/// iterations.
262269
/// - This limit is still low enough to keep runtimes acceptable (on typical
263270
/// machines) in cases where we hit the limit.
271+
///
272+
/// `MaxBlockVisits` caps the number of block visits during analysis. See
273+
/// `runDataflowAnalysis` for a full description and explanation of the default
274+
/// value.
264275
template <typename AnalysisT, typename Diagnostic>
265276
llvm::Expected<llvm::SmallVector<Diagnostic>> diagnoseFunction(
266277
const FunctionDecl &FuncDecl, ASTContext &ASTCtx,
267278
llvm::function_ref<llvm::SmallVector<Diagnostic>(
268279
const CFGElement &, ASTContext &,
269280
const TransferStateForDiagnostics<typename AnalysisT::Lattice> &)>
270281
Diagnoser,
271-
std::int64_t MaxSATIterations = 1'000'000'000) {
282+
std::int64_t MaxSATIterations = 1'000'000'000,
283+
std::int32_t MaxBlockVisits = 20'000) {
272284
llvm::Expected<ControlFlowContext> Context =
273285
ControlFlowContext::build(FuncDecl);
274286
if (!Context)
@@ -293,7 +305,8 @@ llvm::Expected<llvm::SmallVector<Diagnostic>> diagnoseFunction(
293305
State.Lattice.Value),
294306
State.Env));
295307
llvm::move(EltDiagnostics, std::back_inserter(Diagnostics));
296-
})
308+
},
309+
MaxBlockVisits)
297310
.takeError())
298311
return std::move(Err);
299312

clang/include/clang/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,13 +138,20 @@ struct TypeErasedDataflowAnalysisState {
138138
/// dataflow analysis cannot be performed successfully. Otherwise, calls
139139
/// `PostVisitCFG` on each CFG element with the final analysis results at that
140140
/// program point.
141+
///
142+
/// `MaxBlockVisits` caps the number of block visits during analysis. It doesn't
143+
/// distinguish between repeat visits to the same block and visits to distinct
144+
/// blocks. This parameter is a backstop to prevent infinite loops, in the case
145+
/// of bugs in the lattice and/or transfer functions that prevent the analysis
146+
/// from converging.
141147
llvm::Expected<std::vector<std::optional<TypeErasedDataflowAnalysisState>>>
142148
runTypeErasedDataflowAnalysis(
143149
const ControlFlowContext &CFCtx, TypeErasedDataflowAnalysis &Analysis,
144150
const Environment &InitEnv,
145151
std::function<void(const CFGElement &,
146152
const TypeErasedDataflowAnalysisState &)>
147-
PostVisitCFG = nullptr);
153+
PostVisitCFG,
154+
std::int32_t MaxBlockVisits);
148155

149156
} // namespace dataflow
150157
} // namespace clang

clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,8 @@ runTypeErasedDataflowAnalysis(
498498
const Environment &InitEnv,
499499
std::function<void(const CFGElement &,
500500
const TypeErasedDataflowAnalysisState &)>
501-
PostVisitCFG) {
501+
PostVisitCFG,
502+
std::int32_t MaxBlockVisits) {
502503
PrettyStackTraceAnalysis CrashInfo(CFCtx, "runTypeErasedDataflowAnalysis");
503504

504505
std::optional<Environment> MaybeStartingEnv;
@@ -524,27 +525,20 @@ runTypeErasedDataflowAnalysis(
524525

525526
AnalysisContext AC(CFCtx, Analysis, StartingEnv, BlockStates);
526527

527-
// Bugs in lattices and transfer functions can prevent the analysis from
528-
// converging. To limit the damage (infinite loops) that these bugs can cause,
529-
// limit the number of iterations.
530-
// FIXME: Consider making the maximum number of iterations configurable.
531-
// FIXME: Consider restricting the number of backedges followed, rather than
532-
// iterations.
533-
// FIXME: Set up statistics (see llvm/ADT/Statistic.h) to count average number
534-
// of iterations, number of functions that time out, etc.
535-
static constexpr uint32_t MaxAverageVisitsPerBlock = 4;
536-
static constexpr uint32_t AbsoluteMaxIterations = 1 << 16;
537-
const uint32_t RelativeMaxIterations =
528+
// FIXME: remove relative cap. There isn't really any good setting for
529+
// `MaxAverageVisitsPerBlock`, so it has no clear value over using
530+
// `MaxBlockVisits` directly.
531+
static constexpr std::int32_t MaxAverageVisitsPerBlock = 4;
532+
const std::int32_t RelativeMaxBlockVisits =
538533
MaxAverageVisitsPerBlock * BlockStates.size();
539-
const uint32_t MaxIterations =
540-
std::min(RelativeMaxIterations, AbsoluteMaxIterations);
541-
uint32_t Iterations = 0;
534+
MaxBlockVisits = std::min(RelativeMaxBlockVisits, MaxBlockVisits);
535+
std::int32_t BlockVisits = 0;
542536
while (const CFGBlock *Block = Worklist.dequeue()) {
543537
LLVM_DEBUG(llvm::dbgs()
544538
<< "Processing Block " << Block->getBlockID() << "\n");
545-
if (++Iterations > MaxIterations) {
539+
if (++BlockVisits > MaxBlockVisits) {
546540
return llvm::createStringError(std::errc::timed_out,
547-
"maximum number of iterations reached");
541+
"maximum number of blocks processed");
548542
}
549543

550544
const std::optional<TypeErasedDataflowAnalysisState> &OldBlockState =

clang/unittests/Analysis/FlowSensitive/TestingSupport.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "clang/AST/Stmt.h"
55
#include "clang/ASTMatchers/ASTMatchFinder.h"
66
#include "clang/ASTMatchers/ASTMatchers.h"
7+
#include "clang/Analysis/FlowSensitive/NoopAnalysis.h"
78
#include "clang/Basic/LLVM.h"
89
#include "clang/Basic/LangOptions.h"
910
#include "clang/Basic/SourceLocation.h"
@@ -18,7 +19,6 @@
1819
#include "gtest/gtest.h"
1920
#include <cassert>
2021
#include <functional>
21-
#include <memory>
2222
#include <string>
2323
#include <system_error>
2424
#include <utility>

clang/unittests/Analysis/FlowSensitive/TestingSupport.h

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
3434
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
3535
#include "clang/Analysis/FlowSensitive/MatchSwitch.h"
36-
#include "clang/Analysis/FlowSensitive/NoopAnalysis.h"
36+
#include "clang/Analysis/FlowSensitive/NoopLattice.h"
3737
#include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h"
3838
#include "clang/Basic/LLVM.h"
3939
#include "clang/Serialization/PCHContainerOperations.h"
@@ -62,6 +62,11 @@ std::ostream &operator<<(std::ostream &OS,
6262

6363
namespace test {
6464

65+
// Caps the number of block visits in any individual analysis. Given that test
66+
// code is typically quite small, we set a low number to help catch any problems
67+
// early. But, the choice is arbitrary.
68+
constexpr std::int32_t MaxBlockVisitsInAnalysis = 2'000;
69+
6570
/// Returns the environment at the program point marked with `Annotation` from
6671
/// the mapping of annotated program points to analysis state.
6772
///
@@ -277,8 +282,10 @@ checkDataflow(AnalysisInputs<AnalysisT> AI,
277282
// If successful, the dataflow analysis returns a mapping from block IDs to
278283
// the post-analysis states for the CFG blocks that have been evaluated.
279284
llvm::Expected<std::vector<std::optional<TypeErasedDataflowAnalysisState>>>
280-
MaybeBlockStates = runTypeErasedDataflowAnalysis(
281-
CFCtx, Analysis, InitEnv, TypeErasedPostVisitCFG);
285+
MaybeBlockStates =
286+
runTypeErasedDataflowAnalysis(CFCtx, Analysis, InitEnv,
287+
TypeErasedPostVisitCFG,
288+
MaxBlockVisitsInAnalysis);
282289
if (!MaybeBlockStates) return MaybeBlockStates.takeError();
283290
AO.BlockStates = *MaybeBlockStates;
284291

clang/unittests/Analysis/FlowSensitive/TransferTest.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,13 @@
1212
#include "clang/ASTMatchers/ASTMatchers.h"
1313
#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
1414
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
15+
#include "clang/Analysis/FlowSensitive/NoopAnalysis.h"
1516
#include "clang/Analysis/FlowSensitive/RecordOps.h"
1617
#include "clang/Analysis/FlowSensitive/StorageLocation.h"
1718
#include "clang/Analysis/FlowSensitive/Value.h"
1819
#include "clang/Basic/LangStandard.h"
19-
#include "llvm/ADT/ArrayRef.h"
2020
#include "llvm/ADT/SmallVector.h"
2121
#include "llvm/ADT/StringRef.h"
22-
#include "llvm/Support/Casting.h"
2322
#include "llvm/Testing/Support/Error.h"
2423
#include "gmock/gmock.h"
2524
#include "gtest/gtest.h"

clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ TEST_F(DataflowAnalysisTest, NonConvergingAnalysis) {
263263
auto Res = runAnalysis<NonConvergingAnalysis>(
264264
Code, [](ASTContext &C) { return NonConvergingAnalysis(C); });
265265
EXPECT_EQ(llvm::toString(Res.takeError()),
266-
"maximum number of iterations reached");
266+
"maximum number of blocks processed");
267267
}
268268

269269
// Regression test for joins of bool-typed lvalue expressions. The first loop

0 commit comments

Comments
 (0)