Skip to content

Commit c88deef

Browse files
committed
[clang][dataflow] Add MatchSwitch utility library.
Adds `MatchSwitch`, a library for simplifying implementation of transfer functions. `MatchSwitch` supports constructing a "switch" statement, where each case of the switch is defined by an AST matcher. The cases are considered in order, like pattern matching in functional languages. Differential Revision: https://reviews.llvm.org/D120900
1 parent 4e817b3 commit c88deef

File tree

3 files changed

+358
-0
lines changed

3 files changed

+358
-0
lines changed
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
//===---- MatchSwitch.h -----------------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This file defines the `MatchSwitch` abstraction for building a "switch"
10+
// statement, where each case of the switch is defined by an AST matcher. The
11+
// cases are considered in order, like pattern matching in functional
12+
// languages.
13+
//
14+
// Currently, the design is catered towards simplifying the implementation of
15+
// `DataflowAnalysis` transfer functions. Based on experience here, this
16+
// library may be generalized and moved to ASTMatchers.
17+
//
18+
//===----------------------------------------------------------------------===//
19+
20+
#ifndef LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_MATCHSWITCH_H_
21+
#define LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_MATCHSWITCH_H_
22+
23+
#include "clang/AST/ASTContext.h"
24+
#include "clang/AST/Stmt.h"
25+
#include "clang/ASTMatchers/ASTMatchFinder.h"
26+
#include "clang/ASTMatchers/ASTMatchers.h"
27+
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
28+
#include "llvm/ADT/StringRef.h"
29+
#include <functional>
30+
#include <string>
31+
#include <utility>
32+
#include <vector>
33+
34+
namespace clang {
35+
namespace dataflow {
36+
37+
/// A common form of state shared between the cases of a transfer function.
38+
template <typename LatticeT> struct TransferState {
39+
TransferState(LatticeT &Lattice, Environment &Env)
40+
: Lattice(Lattice), Env(Env) {}
41+
42+
/// Current lattice element.
43+
LatticeT &Lattice;
44+
Environment &Env;
45+
};
46+
47+
/// Matches against `Stmt` and, based on its structure, dispatches to an
48+
/// appropriate handler.
49+
template <typename State>
50+
using MatchSwitch = std::function<void(const Stmt &, ASTContext &, State &)>;
51+
52+
/// Collects cases of a "match switch": a collection of matchers paired with
53+
/// callbacks, which together define a switch that can be applied to a
54+
/// `Stmt`. This structure can simplify the definition of `transfer` functions
55+
/// that rely on pattern-matching.
56+
///
57+
/// For example, consider an analysis that handles particular function calls. It
58+
/// can define the `MatchSwitch` once, in the constructor of the analysis, and
59+
/// then reuse it each time that `transfer` is called, with a fresh state value.
60+
///
61+
/// \code
62+
/// MatchSwitch<TransferState<MyLattice> BuildSwitch() {
63+
/// return MatchSwitchBuilder<TransferState<MyLattice>>()
64+
/// .CaseOf(callExpr(callee(functionDecl(hasName("foo")))), TransferFooCall)
65+
/// .CaseOf(callExpr(argumentCountIs(2),
66+
/// callee(functionDecl(hasName("bar")))),
67+
/// TransferBarCall)
68+
/// .Build();
69+
/// }
70+
/// \endcode
71+
template <typename State> class MatchSwitchBuilder {
72+
public:
73+
// An action is triggered by the match of a pattern against the input
74+
// statement. For generality, actions take both the matched statement and the
75+
// set of bindings produced by the match.
76+
using Action = std::function<void(
77+
const Stmt *, const ast_matchers::MatchFinder::MatchResult &, State &)>;
78+
79+
MatchSwitchBuilder &&CaseOf(ast_matchers::internal::Matcher<Stmt> M,
80+
Action A) && {
81+
Matchers.push_back(std::move(M));
82+
Actions.push_back(std::move(A));
83+
return std::move(*this);
84+
}
85+
86+
// Convenience function for the common case, where bound nodes are not
87+
// needed. `Node` should be a subclass of `Stmt`.
88+
template <typename Node>
89+
MatchSwitchBuilder &&CaseOf(ast_matchers::internal::Matcher<Stmt> M,
90+
void (*Action)(const Node *, State &)) && {
91+
Matchers.push_back(std::move(M));
92+
Actions.push_back([Action](const Stmt *Stmt,
93+
const ast_matchers::MatchFinder::MatchResult &,
94+
State &S) { Action(cast<Node>(Stmt), S); });
95+
return std::move(*this);
96+
}
97+
98+
MatchSwitch<State> Build() && {
99+
return [Matcher = BuildMatcher(), Actions = std::move(Actions)](
100+
const Stmt &Stmt, ASTContext &Context, State &S) {
101+
auto Results = ast_matchers::matchDynamic(Matcher, Stmt, Context);
102+
if (Results.empty())
103+
return;
104+
// Look through the map for the first binding of the form "TagN..." use
105+
// that to select the action.
106+
for (const auto &Element : Results[0].getMap()) {
107+
llvm::StringRef ID(Element.first);
108+
size_t Index = 0;
109+
if (ID.consume_front("Tag") && !ID.getAsInteger(10, Index) &&
110+
Index < Actions.size()) {
111+
Actions[Index](
112+
&Stmt,
113+
ast_matchers::MatchFinder::MatchResult(Results[0], &Context), S);
114+
return;
115+
}
116+
}
117+
};
118+
}
119+
120+
private:
121+
ast_matchers::internal::DynTypedMatcher BuildMatcher() {
122+
using ast_matchers::anything;
123+
using ast_matchers::stmt;
124+
using ast_matchers::unless;
125+
using ast_matchers::internal::DynTypedMatcher;
126+
if (Matchers.empty())
127+
return stmt(unless(anything()));
128+
for (int I = 0, N = Matchers.size(); I < N; ++I) {
129+
std::string Tag = ("Tag" + llvm::Twine(I)).str();
130+
// Many matchers are not bindable, so ensure that tryBind will work.
131+
Matchers[I].setAllowBind(true);
132+
auto M = *Matchers[I].tryBind(Tag);
133+
// Each anyOf explicitly controls the traversal kind. The anyOf itself is
134+
// set to `TK_AsIs` to ensure no nodes are skipped, thereby deferring to
135+
// the kind of the branches. Then, each branch is either left as is, if
136+
// the kind is already set, or explicitly set to `TK_AsIs`. We choose this
137+
// setting because it is the default interpretation of matchers.
138+
Matchers[I] =
139+
!M.getTraversalKind() ? M.withTraversalKind(TK_AsIs) : std::move(M);
140+
}
141+
// The matcher type on the cases ensures that `Expr` kind is compatible with
142+
// all of the matchers.
143+
return DynTypedMatcher::constructVariadic(
144+
DynTypedMatcher::VO_AnyOf, ASTNodeKind::getFromNodeKind<Stmt>(),
145+
std::move(Matchers));
146+
}
147+
148+
std::vector<ast_matchers::internal::DynTypedMatcher> Matchers;
149+
std::vector<Action> Actions;
150+
};
151+
} // namespace dataflow
152+
} // namespace clang
153+
#endif // LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_MATCHSWITCH_H_

clang/unittests/Analysis/FlowSensitive/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ add_clang_unittest(ClangAnalysisFlowSensitiveTests
77
DataflowAnalysisContextTest.cpp
88
DataflowEnvironmentTest.cpp
99
MapLatticeTest.cpp
10+
MatchSwitchTest.cpp
1011
MultiVarConstantPropagationTest.cpp
1112
SingleVarConstantPropagationTest.cpp
1213
SourceLocationsLatticeTest.cpp
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
//===- unittests/Analysis/FlowSensitive/MatchSwitchTest.cpp ---------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This file defines a simplistic version of Constant Propagation as an example
10+
// of a forward, monotonic dataflow analysis. The analysis tracks all
11+
// variables in the scope, but lacks escape analysis.
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
#include "clang/Analysis/FlowSensitive/MatchSwitch.h"
16+
#include "TestingSupport.h"
17+
#include "clang/AST/ASTContext.h"
18+
#include "clang/AST/Decl.h"
19+
#include "clang/AST/Expr.h"
20+
#include "clang/AST/Stmt.h"
21+
#include "clang/ASTMatchers/ASTMatchFinder.h"
22+
#include "clang/ASTMatchers/ASTMatchers.h"
23+
#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
24+
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
25+
#include "clang/Analysis/FlowSensitive/DataflowLattice.h"
26+
#include "clang/Analysis/FlowSensitive/MapLattice.h"
27+
#include "clang/Tooling/Tooling.h"
28+
#include "llvm/ADT/None.h"
29+
#include "llvm/ADT/Optional.h"
30+
#include "llvm/ADT/StringRef.h"
31+
#include "llvm/ADT/Twine.h"
32+
#include "llvm/Support/Error.h"
33+
#include "llvm/Testing/Support/Annotations.h"
34+
#include "llvm/Testing/Support/Error.h"
35+
#include "gmock/gmock.h"
36+
#include "gtest/gtest.h"
37+
#include <cstdint>
38+
#include <memory>
39+
#include <ostream>
40+
#include <string>
41+
#include <utility>
42+
43+
using namespace clang;
44+
using namespace dataflow;
45+
46+
namespace {
47+
using ::testing::Pair;
48+
using ::testing::UnorderedElementsAre;
49+
50+
class BooleanLattice {
51+
public:
52+
BooleanLattice() : Value(false) {}
53+
explicit BooleanLattice(bool B) : Value(B) {}
54+
55+
static BooleanLattice bottom() { return BooleanLattice(false); }
56+
57+
static BooleanLattice top() { return BooleanLattice(true); }
58+
59+
LatticeJoinEffect join(BooleanLattice Other) {
60+
auto Prev = Value;
61+
Value = Value || Other.Value;
62+
return Prev == Value ? LatticeJoinEffect::Unchanged
63+
: LatticeJoinEffect::Changed;
64+
}
65+
66+
friend bool operator==(BooleanLattice LHS, BooleanLattice RHS) {
67+
return LHS.Value == RHS.Value;
68+
}
69+
70+
friend std::ostream &operator<<(std::ostream &Os, const BooleanLattice &B) {
71+
Os << B.Value;
72+
return Os;
73+
}
74+
75+
bool value() const { return Value; }
76+
77+
private:
78+
bool Value;
79+
};
80+
} // namespace
81+
82+
MATCHER_P(Holds, m,
83+
((negation ? "doesn't hold" : "holds") +
84+
llvm::StringRef(" a lattice element that ") +
85+
::testing::DescribeMatcher<BooleanLattice>(m, negation))
86+
.str()) {
87+
return ExplainMatchResult(m, arg.Lattice, result_listener);
88+
}
89+
90+
void TransferSetTrue(const DeclRefExpr *,
91+
TransferState<BooleanLattice> &State) {
92+
State.Lattice = BooleanLattice(true);
93+
}
94+
95+
void TransferSetFalse(const Stmt *,
96+
const ast_matchers::MatchFinder::MatchResult &,
97+
TransferState<BooleanLattice> &State) {
98+
State.Lattice = BooleanLattice(false);
99+
}
100+
101+
class TestAnalysis : public DataflowAnalysis<TestAnalysis, BooleanLattice> {
102+
MatchSwitch<TransferState<BooleanLattice>> TransferSwitch;
103+
104+
public:
105+
explicit TestAnalysis(ASTContext &Context)
106+
: DataflowAnalysis<TestAnalysis, BooleanLattice>(Context) {
107+
using namespace ast_matchers;
108+
TransferSwitch =
109+
MatchSwitchBuilder<TransferState<BooleanLattice>>()
110+
.CaseOf(declRefExpr(to(varDecl(hasName("X")))), TransferSetTrue)
111+
.CaseOf(callExpr(callee(functionDecl(hasName("Foo")))),
112+
TransferSetFalse)
113+
.Build();
114+
}
115+
116+
static BooleanLattice initialElement() { return BooleanLattice::bottom(); }
117+
118+
void transfer(const Stmt *S, BooleanLattice &L, Environment &Env) {
119+
TransferState<BooleanLattice> State(L, Env);
120+
TransferSwitch(*S, getASTContext(), State);
121+
}
122+
};
123+
124+
class MatchSwitchTest : public ::testing::Test {
125+
protected:
126+
template <typename Matcher>
127+
void RunDataflow(llvm::StringRef Code, Matcher Expectations) {
128+
ASSERT_THAT_ERROR(
129+
test::checkDataflow<TestAnalysis>(
130+
Code, "fun",
131+
[](ASTContext &C, Environment &) { return TestAnalysis(C); },
132+
[&Expectations](
133+
llvm::ArrayRef<std::pair<
134+
std::string, DataflowAnalysisState<TestAnalysis::Lattice>>>
135+
Results,
136+
ASTContext &) { EXPECT_THAT(Results, Expectations); },
137+
{"-fsyntax-only", "-std=c++17"}),
138+
llvm::Succeeded());
139+
}
140+
};
141+
142+
TEST_F(MatchSwitchTest, JustX) {
143+
std::string Code = R"(
144+
void fun() {
145+
int X = 1;
146+
(void)X;
147+
// [[p]]
148+
}
149+
)";
150+
RunDataflow(Code,
151+
UnorderedElementsAre(Pair("p", Holds(BooleanLattice(true)))));
152+
}
153+
154+
TEST_F(MatchSwitchTest, JustFoo) {
155+
std::string Code = R"(
156+
void Foo();
157+
void fun() {
158+
Foo();
159+
// [[p]]
160+
}
161+
)";
162+
RunDataflow(Code,
163+
UnorderedElementsAre(Pair("p", Holds(BooleanLattice(false)))));
164+
}
165+
166+
TEST_F(MatchSwitchTest, XThenFoo) {
167+
std::string Code = R"(
168+
void Foo();
169+
void fun() {
170+
int X = 1;
171+
(void)X;
172+
Foo();
173+
// [[p]]
174+
}
175+
)";
176+
RunDataflow(Code,
177+
UnorderedElementsAre(Pair("p", Holds(BooleanLattice(false)))));
178+
}
179+
180+
TEST_F(MatchSwitchTest, FooThenX) {
181+
std::string Code = R"(
182+
void Foo();
183+
void fun() {
184+
Foo();
185+
int X = 1;
186+
(void)X;
187+
// [[p]]
188+
}
189+
)";
190+
RunDataflow(Code,
191+
UnorderedElementsAre(Pair("p", Holds(BooleanLattice(true)))));
192+
}
193+
194+
TEST_F(MatchSwitchTest, Neither) {
195+
std::string Code = R"(
196+
void Bar();
197+
void fun(bool b) {
198+
Bar();
199+
// [[p]]
200+
}
201+
)";
202+
RunDataflow(Code,
203+
UnorderedElementsAre(Pair("p", Holds(BooleanLattice(false)))));
204+
}

0 commit comments

Comments
 (0)