Skip to content

Commit 5a40df6

Browse files
committed
[clang][dataflow] Add framework for testing analyses.
Adds a general-purpose framework to support testing of dataflow analyses. Differential Revision: https://reviews.llvm.org/D115341
1 parent 9c244a3 commit 5a40df6

File tree

6 files changed

+540
-3
lines changed

6 files changed

+540
-3
lines changed

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ struct TypeErasedDataflowAnalysisState {
7878

7979
/// Transfers the state of a basic block by evaluating each of its statements in
8080
/// the context of `Analysis` and the states of its predecessors that are
81-
/// available in `BlockStates`.
81+
/// available in `BlockStates`. `HandleTransferredStmt` (if provided) will be
82+
/// applied to each statement in the block, after it is evaluated.
8283
///
8384
/// Requirements:
8485
///
@@ -88,7 +89,10 @@ struct TypeErasedDataflowAnalysisState {
8889
TypeErasedDataflowAnalysisState transferBlock(
8990
std::vector<llvm::Optional<TypeErasedDataflowAnalysisState>> &BlockStates,
9091
const CFGBlock &Block, const Environment &InitEnv,
91-
TypeErasedDataflowAnalysis &Analysis);
92+
TypeErasedDataflowAnalysis &Analysis,
93+
std::function<void(const CFGStmt &,
94+
const TypeErasedDataflowAnalysisState &)>
95+
HandleTransferredStmt = nullptr);
9296

9397
/// Performs dataflow analysis and returns a mapping from basic block IDs to
9498
/// dataflow analysis states that model the respective basic blocks. Indices

clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ static TypeErasedDataflowAnalysisState computeBlockInputState(
6666
TypeErasedDataflowAnalysisState transferBlock(
6767
std::vector<llvm::Optional<TypeErasedDataflowAnalysisState>> &BlockStates,
6868
const CFGBlock &Block, const Environment &InitEnv,
69-
TypeErasedDataflowAnalysis &Analysis) {
69+
TypeErasedDataflowAnalysis &Analysis,
70+
std::function<void(const CFGStmt &,
71+
const TypeErasedDataflowAnalysisState &)>
72+
HandleTransferredStmt) {
7073
TypeErasedDataflowAnalysisState State =
7174
computeBlockInputState(BlockStates, Block, InitEnv, Analysis);
7275
for (const CFGElement &Element : Block) {
@@ -79,6 +82,8 @@ TypeErasedDataflowAnalysisState transferBlock(
7982

8083
State.Lattice = Analysis.transferTypeErased(Stmt.getValue().getStmt(),
8184
State.Lattice, State.Env);
85+
if (HandleTransferredStmt != nullptr)
86+
HandleTransferredStmt(Stmt.getValue(), State);
8287
}
8388
return State;
8489
}

clang/unittests/Analysis/FlowSensitive/CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ set(LLVM_LINK_COMPONENTS
33
)
44

55
add_clang_unittest(ClangAnalysisFlowSensitiveTests
6+
TestingSupport.cpp
7+
TestingSupportTest.cpp
68
TypeErasedDataflowAnalysisTest.cpp
79
)
810

@@ -14,8 +16,13 @@ clang_target_link_libraries(ClangAnalysisFlowSensitiveTests
1416
clangASTMatchers
1517
clangBasic
1618
clangFrontend
19+
clangLex
1720
clangSerialization
1821
clangTesting
1922
clangTooling
2023
)
2124

25+
target_link_libraries(ClangAnalysisFlowSensitiveTests
26+
PRIVATE
27+
LLVMTestingSupport
28+
)
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
#include "TestingSupport.h"
2+
#include "clang/AST/ASTContext.h"
3+
#include "clang/AST/Decl.h"
4+
#include "clang/AST/Stmt.h"
5+
#include "clang/ASTMatchers/ASTMatchFinder.h"
6+
#include "clang/ASTMatchers/ASTMatchers.h"
7+
#include "clang/Analysis/CFG.h"
8+
#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
9+
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
10+
#include "clang/Basic/LLVM.h"
11+
#include "clang/Basic/LangOptions.h"
12+
#include "clang/Basic/SourceManager.h"
13+
#include "clang/Basic/TokenKinds.h"
14+
#include "clang/Lex/Lexer.h"
15+
#include "clang/Serialization/PCHContainerOperations.h"
16+
#include "clang/Tooling/ArgumentsAdjusters.h"
17+
#include "clang/Tooling/Tooling.h"
18+
#include "llvm/ADT/ArrayRef.h"
19+
#include "llvm/ADT/DenseMap.h"
20+
#include "llvm/ADT/Optional.h"
21+
#include "llvm/Support/Error.h"
22+
#include "llvm/Testing/Support/Annotations.h"
23+
#include "gtest/gtest.h"
24+
#include <functional>
25+
#include <memory>
26+
#include <string>
27+
#include <system_error>
28+
#include <utility>
29+
#include <vector>
30+
31+
using namespace clang;
32+
using namespace dataflow;
33+
34+
namespace {
35+
using ast_matchers::MatchFinder;
36+
37+
class FindTranslationUnitCallback : public MatchFinder::MatchCallback {
38+
public:
39+
explicit FindTranslationUnitCallback(
40+
std::function<void(ASTContext &)> Operation)
41+
: Operation{Operation} {}
42+
43+
void run(const MatchFinder::MatchResult &Result) override {
44+
const auto *TU = Result.Nodes.getNodeAs<TranslationUnitDecl>("tu");
45+
if (TU->getASTContext().getDiagnostics().getClient()->getNumErrors() != 0) {
46+
FAIL() << "Source file has syntax or type errors, they were printed to "
47+
"the test log";
48+
}
49+
Operation(TU->getASTContext());
50+
}
51+
52+
std::function<void(ASTContext &)> Operation;
53+
};
54+
} // namespace
55+
56+
static bool
57+
isAnnotationDirectlyAfterStatement(const Stmt *Stmt, unsigned AnnotationBegin,
58+
const SourceManager &SourceManager,
59+
const LangOptions &LangOptions) {
60+
auto NextToken =
61+
Lexer::findNextToken(Stmt->getEndLoc(), SourceManager, LangOptions);
62+
63+
while (NextToken.hasValue() &&
64+
SourceManager.getFileOffset(NextToken->getLocation()) <
65+
AnnotationBegin) {
66+
if (NextToken->isNot(tok::semi))
67+
return false;
68+
69+
NextToken = Lexer::findNextToken(NextToken->getEndLoc(), SourceManager,
70+
LangOptions);
71+
}
72+
73+
return true;
74+
}
75+
76+
llvm::Expected<llvm::DenseMap<const Stmt *, std::string>>
77+
clang::dataflow::testing::buildStatementToAnnotationMapping(
78+
const FunctionDecl *Func, llvm::Annotations AnnotatedCode) {
79+
llvm::DenseMap<const Stmt *, std::string> Result;
80+
81+
using namespace ast_matchers; // NOLINT: Too many names
82+
auto StmtMatcher =
83+
findAll(stmt(unless(anyOf(hasParent(expr()), hasParent(returnStmt()))))
84+
.bind("stmt"));
85+
86+
// This map should stay sorted because the binding algorithm relies on the
87+
// ordering of statement offsets
88+
std::map<unsigned, const Stmt *> Stmts;
89+
auto &Context = Func->getASTContext();
90+
auto &SourceManager = Context.getSourceManager();
91+
92+
for (auto &Match : match(StmtMatcher, *Func->getBody(), Context)) {
93+
const auto *S = Match.getNodeAs<Stmt>("stmt");
94+
unsigned Offset = SourceManager.getFileOffset(S->getEndLoc());
95+
Stmts[Offset] = S;
96+
}
97+
98+
unsigned I = 0;
99+
auto Annotations = AnnotatedCode.ranges();
100+
std::reverse(Annotations.begin(), Annotations.end());
101+
auto Code = AnnotatedCode.code();
102+
103+
for (auto OffsetAndStmt = Stmts.rbegin(); OffsetAndStmt != Stmts.rend();
104+
OffsetAndStmt++) {
105+
unsigned Offset = OffsetAndStmt->first;
106+
const Stmt *Stmt = OffsetAndStmt->second;
107+
108+
if (I < Annotations.size() && Annotations[I].Begin >= Offset) {
109+
auto Range = Annotations[I];
110+
111+
if (!isAnnotationDirectlyAfterStatement(Stmt, Range.Begin, SourceManager,
112+
Context.getLangOpts())) {
113+
return llvm::createStringError(
114+
std::make_error_code(std::errc::invalid_argument),
115+
"Annotation is not placed after a statement: %s",
116+
SourceManager.getLocForStartOfFile(SourceManager.getMainFileID())
117+
.getLocWithOffset(Offset)
118+
.printToString(SourceManager)
119+
.data());
120+
}
121+
122+
Result[Stmt] = Code.slice(Range.Begin, Range.End).str();
123+
I++;
124+
125+
if (I < Annotations.size() && Annotations[I].Begin >= Offset) {
126+
return llvm::createStringError(
127+
std::make_error_code(std::errc::invalid_argument),
128+
"Multiple annotations bound to the statement at the location: %s",
129+
Stmt->getBeginLoc().printToString(SourceManager).data());
130+
}
131+
}
132+
}
133+
134+
if (I < Annotations.size()) {
135+
return llvm::createStringError(
136+
std::make_error_code(std::errc::invalid_argument),
137+
"Not all annotations were bound to statements. Unbound annotation at: "
138+
"%s",
139+
SourceManager.getLocForStartOfFile(SourceManager.getMainFileID())
140+
.getLocWithOffset(Annotations[I].Begin)
141+
.printToString(SourceManager)
142+
.data());
143+
}
144+
145+
return Result;
146+
}
147+
148+
std::pair<const FunctionDecl *, std::unique_ptr<CFG>>
149+
clang::dataflow::testing::buildCFG(
150+
ASTContext &Context,
151+
ast_matchers::internal::Matcher<FunctionDecl> FuncMatcher) {
152+
CFG::BuildOptions Options;
153+
Options.PruneTriviallyFalseEdges = false;
154+
Options.AddInitializers = true;
155+
Options.AddImplicitDtors = true;
156+
Options.AddTemporaryDtors = true;
157+
Options.setAllAlwaysAdd();
158+
159+
const FunctionDecl *F = ast_matchers::selectFirst<FunctionDecl>(
160+
"target",
161+
ast_matchers::match(
162+
ast_matchers::functionDecl(ast_matchers::isDefinition(), FuncMatcher)
163+
.bind("target"),
164+
Context));
165+
if (F == nullptr)
166+
return std::make_pair(nullptr, nullptr);
167+
168+
return std::make_pair(
169+
F, clang::CFG::buildCFG(F, F->getBody(), &Context, Options));
170+
}

0 commit comments

Comments
 (0)