Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
//
//===----------------------------------------------------------------------===//

#include <cstddef>
#include <optional>
#include <system_error>
#include <utility>
Expand All @@ -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"
Expand Down Expand Up @@ -479,6 +481,27 @@ transferCFGBlock(const CFGBlock &Block, AnalysisContext &AC,
return State;
}

// 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());
const CFGBlock &Entry = CFG.getEntry();
Worklist.enqueueSuccessors(&Entry);
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;
Worklist.enqueueSuccessors(Block);
}
return VisitedBlocks.count();
}

llvm::Expected<std::vector<std::optional<TypeErasedDataflowAnalysisState>>>
runTypeErasedDataflowAnalysis(
const AdornedCFG &ACFG, TypeErasedDataflowAnalysis &Analysis,
Expand All @@ -496,6 +519,20 @@ 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

// 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<size_t>(MaxBlockVisits)) {
if (NumBlockVisitsIfVisitOnce(CFG) > static_cast<size_t>(MaxBlockVisits)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: use && instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

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<const CFGBlock *> NonStructLoopBackedgeNodes =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ class DataflowAnalysisTest : public Test {
template <typename AnalysisT>
llvm::Expected<std::vector<
std::optional<DataflowAnalysisState<typename AnalysisT::Lattice>>>>
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<FunctionDecl>(
Expand All @@ -75,7 +76,7 @@ class DataflowAnalysisTest : public Test {
std::make_unique<WatchedLiteralsSolver>());
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).
Expand Down Expand Up @@ -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<NoopAnalysis>(
Code, [](ASTContext &C) { return NoopAnalysis(C); }, 2),
llvm::Succeeded());
EXPECT_THAT_EXPECTED(
runAnalysis<NoopAnalysis>(
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
Expand Down