[clang] c88deef - [clang][dataflow] Add `MatchSwitch` utility library.

Yitzhak Mandelbaum via cfe-commits cfe-commits at lists.llvm.org
Fri Mar 4 09:20:04 PST 2022


Author: Yitzhak Mandelbaum
Date: 2022-03-04T17:19:51Z
New Revision: c88deef0a7218dd5c30500e7d3f58fc23283d3e5

URL: https://github.com/llvm/llvm-project/commit/c88deef0a7218dd5c30500e7d3f58fc23283d3e5
DIFF: https://github.com/llvm/llvm-project/commit/c88deef0a7218dd5c30500e7d3f58fc23283d3e5.diff

LOG: [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

Added: 
    clang/include/clang/Analysis/FlowSensitive/MatchSwitch.h
    clang/unittests/Analysis/FlowSensitive/MatchSwitchTest.cpp

Modified: 
    clang/unittests/Analysis/FlowSensitive/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Analysis/FlowSensitive/MatchSwitch.h b/clang/include/clang/Analysis/FlowSensitive/MatchSwitch.h
new file mode 100644
index 0000000000000..b319360627911
--- /dev/null
+++ b/clang/include/clang/Analysis/FlowSensitive/MatchSwitch.h
@@ -0,0 +1,153 @@
+//===---- MatchSwitch.h -----------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+//  This file defines the `MatchSwitch` abstraction for building 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.
+//
+//  Currently, the design is catered towards simplifying the implementation of
+//  `DataflowAnalysis` transfer functions. Based on experience here, this
+//  library may be generalized and moved to ASTMatchers.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_MATCHSWITCH_H_
+#define LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_MATCHSWITCH_H_
+
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Stmt.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
+#include "llvm/ADT/StringRef.h"
+#include <functional>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace clang {
+namespace dataflow {
+
+/// A common form of state shared between the cases of a transfer function.
+template <typename LatticeT> struct TransferState {
+  TransferState(LatticeT &Lattice, Environment &Env)
+      : Lattice(Lattice), Env(Env) {}
+
+  /// Current lattice element.
+  LatticeT &Lattice;
+  Environment &Env;
+};
+
+/// Matches against `Stmt` and, based on its structure, dispatches to an
+/// appropriate handler.
+template <typename State>
+using MatchSwitch = std::function<void(const Stmt &, ASTContext &, State &)>;
+
+/// Collects cases of a "match switch": a collection of matchers paired with
+/// callbacks, which together define a switch that can be applied to a
+/// `Stmt`. This structure can simplify the definition of `transfer` functions
+/// that rely on pattern-matching.
+///
+/// For example, consider an analysis that handles particular function calls. It
+/// can define the `MatchSwitch` once, in the constructor of the analysis, and
+/// then reuse it each time that `transfer` is called, with a fresh state value.
+///
+/// \code
+/// MatchSwitch<TransferState<MyLattice> BuildSwitch() {
+///   return MatchSwitchBuilder<TransferState<MyLattice>>()
+///     .CaseOf(callExpr(callee(functionDecl(hasName("foo")))), TransferFooCall)
+///     .CaseOf(callExpr(argumentCountIs(2),
+///                      callee(functionDecl(hasName("bar")))),
+///             TransferBarCall)
+///     .Build();
+/// }
+/// \endcode
+template <typename State> class MatchSwitchBuilder {
+public:
+  // An action is triggered by the match of a pattern against the input
+  // statement. For generality, actions take both the matched statement and the
+  // set of bindings produced by the match.
+  using Action = std::function<void(
+      const Stmt *, const ast_matchers::MatchFinder::MatchResult &, State &)>;
+
+  MatchSwitchBuilder &&CaseOf(ast_matchers::internal::Matcher<Stmt> M,
+                              Action A) && {
+    Matchers.push_back(std::move(M));
+    Actions.push_back(std::move(A));
+    return std::move(*this);
+  }
+
+  // Convenience function for the common case, where bound nodes are not
+  // needed. `Node` should be a subclass of `Stmt`.
+  template <typename Node>
+  MatchSwitchBuilder &&CaseOf(ast_matchers::internal::Matcher<Stmt> M,
+                              void (*Action)(const Node *, State &)) && {
+    Matchers.push_back(std::move(M));
+    Actions.push_back([Action](const Stmt *Stmt,
+                               const ast_matchers::MatchFinder::MatchResult &,
+                               State &S) { Action(cast<Node>(Stmt), S); });
+    return std::move(*this);
+  }
+
+  MatchSwitch<State> Build() && {
+    return [Matcher = BuildMatcher(), Actions = std::move(Actions)](
+               const Stmt &Stmt, ASTContext &Context, State &S) {
+      auto Results = ast_matchers::matchDynamic(Matcher, Stmt, Context);
+      if (Results.empty())
+        return;
+      // Look through the map for the first binding of the form "TagN..." use
+      // that to select the action.
+      for (const auto &Element : Results[0].getMap()) {
+        llvm::StringRef ID(Element.first);
+        size_t Index = 0;
+        if (ID.consume_front("Tag") && !ID.getAsInteger(10, Index) &&
+            Index < Actions.size()) {
+          Actions[Index](
+              &Stmt,
+              ast_matchers::MatchFinder::MatchResult(Results[0], &Context), S);
+          return;
+        }
+      }
+    };
+  }
+
+private:
+  ast_matchers::internal::DynTypedMatcher BuildMatcher() {
+    using ast_matchers::anything;
+    using ast_matchers::stmt;
+    using ast_matchers::unless;
+    using ast_matchers::internal::DynTypedMatcher;
+    if (Matchers.empty())
+      return stmt(unless(anything()));
+    for (int I = 0, N = Matchers.size(); I < N; ++I) {
+      std::string Tag = ("Tag" + llvm::Twine(I)).str();
+      // Many matchers are not bindable, so ensure that tryBind will work.
+      Matchers[I].setAllowBind(true);
+      auto M = *Matchers[I].tryBind(Tag);
+      // Each anyOf explicitly controls the traversal kind. The anyOf itself is
+      // set to `TK_AsIs` to ensure no nodes are skipped, thereby deferring to
+      // the kind of the branches. Then, each branch is either left as is, if
+      // the kind is already set, or explicitly set to `TK_AsIs`. We choose this
+      // setting because it is the default interpretation of matchers.
+      Matchers[I] =
+          !M.getTraversalKind() ? M.withTraversalKind(TK_AsIs) : std::move(M);
+    }
+    // The matcher type on the cases ensures that `Expr` kind is compatible with
+    // all of the matchers.
+    return DynTypedMatcher::constructVariadic(
+        DynTypedMatcher::VO_AnyOf, ASTNodeKind::getFromNodeKind<Stmt>(),
+        std::move(Matchers));
+  }
+
+  std::vector<ast_matchers::internal::DynTypedMatcher> Matchers;
+  std::vector<Action> Actions;
+};
+} // namespace dataflow
+} // namespace clang
+#endif // LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_MATCHSWITCH_H_

diff  --git a/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt b/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt
index 4dc442ba61722..d2608503a5396 100644
--- a/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt
+++ b/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt
@@ -7,6 +7,7 @@ add_clang_unittest(ClangAnalysisFlowSensitiveTests
   DataflowAnalysisContextTest.cpp
   DataflowEnvironmentTest.cpp
   MapLatticeTest.cpp
+  MatchSwitchTest.cpp
   MultiVarConstantPropagationTest.cpp
   SingleVarConstantPropagationTest.cpp
   SourceLocationsLatticeTest.cpp

diff  --git a/clang/unittests/Analysis/FlowSensitive/MatchSwitchTest.cpp b/clang/unittests/Analysis/FlowSensitive/MatchSwitchTest.cpp
new file mode 100644
index 0000000000000..b99e5c6e1c0b0
--- /dev/null
+++ b/clang/unittests/Analysis/FlowSensitive/MatchSwitchTest.cpp
@@ -0,0 +1,204 @@
+//===- unittests/Analysis/FlowSensitive/MatchSwitchTest.cpp ---------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+//  This file defines a simplistic version of Constant Propagation as an example
+//  of a forward, monotonic dataflow analysis. The analysis tracks all
+//  variables in the scope, but lacks escape analysis.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/FlowSensitive/MatchSwitch.h"
+#include "TestingSupport.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/Stmt.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
+#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
+#include "clang/Analysis/FlowSensitive/DataflowLattice.h"
+#include "clang/Analysis/FlowSensitive/MapLattice.h"
+#include "clang/Tooling/Tooling.h"
+#include "llvm/ADT/None.h"
+#include "llvm/ADT/Optional.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/Twine.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Testing/Support/Annotations.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include <cstdint>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <utility>
+
+using namespace clang;
+using namespace dataflow;
+
+namespace {
+using ::testing::Pair;
+using ::testing::UnorderedElementsAre;
+
+class BooleanLattice {
+public:
+  BooleanLattice() : Value(false) {}
+  explicit BooleanLattice(bool B) : Value(B) {}
+
+  static BooleanLattice bottom() { return BooleanLattice(false); }
+
+  static BooleanLattice top() { return BooleanLattice(true); }
+
+  LatticeJoinEffect join(BooleanLattice Other) {
+    auto Prev = Value;
+    Value = Value || Other.Value;
+    return Prev == Value ? LatticeJoinEffect::Unchanged
+                         : LatticeJoinEffect::Changed;
+  }
+
+  friend bool operator==(BooleanLattice LHS, BooleanLattice RHS) {
+    return LHS.Value == RHS.Value;
+  }
+
+  friend std::ostream &operator<<(std::ostream &Os, const BooleanLattice &B) {
+    Os << B.Value;
+    return Os;
+  }
+
+  bool value() const { return Value; }
+
+private:
+  bool Value;
+};
+} // namespace
+
+MATCHER_P(Holds, m,
+          ((negation ? "doesn't hold" : "holds") +
+           llvm::StringRef(" a lattice element that ") +
+           ::testing::DescribeMatcher<BooleanLattice>(m, negation))
+              .str()) {
+  return ExplainMatchResult(m, arg.Lattice, result_listener);
+}
+
+void TransferSetTrue(const DeclRefExpr *,
+                     TransferState<BooleanLattice> &State) {
+  State.Lattice = BooleanLattice(true);
+}
+
+void TransferSetFalse(const Stmt *,
+                      const ast_matchers::MatchFinder::MatchResult &,
+                      TransferState<BooleanLattice> &State) {
+  State.Lattice = BooleanLattice(false);
+}
+
+class TestAnalysis : public DataflowAnalysis<TestAnalysis, BooleanLattice> {
+  MatchSwitch<TransferState<BooleanLattice>> TransferSwitch;
+
+public:
+  explicit TestAnalysis(ASTContext &Context)
+      : DataflowAnalysis<TestAnalysis, BooleanLattice>(Context) {
+    using namespace ast_matchers;
+    TransferSwitch =
+        MatchSwitchBuilder<TransferState<BooleanLattice>>()
+            .CaseOf(declRefExpr(to(varDecl(hasName("X")))), TransferSetTrue)
+            .CaseOf(callExpr(callee(functionDecl(hasName("Foo")))),
+                    TransferSetFalse)
+            .Build();
+  }
+
+  static BooleanLattice initialElement() { return BooleanLattice::bottom(); }
+
+  void transfer(const Stmt *S, BooleanLattice &L, Environment &Env) {
+    TransferState<BooleanLattice> State(L, Env);
+    TransferSwitch(*S, getASTContext(), State);
+  }
+};
+
+class MatchSwitchTest : public ::testing::Test {
+protected:
+  template <typename Matcher>
+  void RunDataflow(llvm::StringRef Code, Matcher Expectations) {
+    ASSERT_THAT_ERROR(
+        test::checkDataflow<TestAnalysis>(
+            Code, "fun",
+            [](ASTContext &C, Environment &) { return TestAnalysis(C); },
+            [&Expectations](
+                llvm::ArrayRef<std::pair<
+                    std::string, DataflowAnalysisState<TestAnalysis::Lattice>>>
+                    Results,
+                ASTContext &) { EXPECT_THAT(Results, Expectations); },
+            {"-fsyntax-only", "-std=c++17"}),
+        llvm::Succeeded());
+  }
+};
+
+TEST_F(MatchSwitchTest, JustX) {
+  std::string Code = R"(
+    void fun() {
+      int X = 1;
+      (void)X;
+      // [[p]]
+    }
+  )";
+  RunDataflow(Code,
+              UnorderedElementsAre(Pair("p", Holds(BooleanLattice(true)))));
+}
+
+TEST_F(MatchSwitchTest, JustFoo) {
+  std::string Code = R"(
+    void Foo();
+    void fun() {
+      Foo();
+      // [[p]]
+    }
+  )";
+  RunDataflow(Code,
+              UnorderedElementsAre(Pair("p", Holds(BooleanLattice(false)))));
+}
+
+TEST_F(MatchSwitchTest, XThenFoo) {
+  std::string Code = R"(
+    void Foo();
+    void fun() {
+      int X = 1;
+      (void)X;
+      Foo();
+      // [[p]]
+    }
+  )";
+  RunDataflow(Code,
+              UnorderedElementsAre(Pair("p", Holds(BooleanLattice(false)))));
+}
+
+TEST_F(MatchSwitchTest, FooThenX) {
+  std::string Code = R"(
+    void Foo();
+    void fun() {
+      Foo();
+      int X = 1;
+      (void)X;
+      // [[p]]
+    }
+  )";
+  RunDataflow(Code,
+              UnorderedElementsAre(Pair("p", Holds(BooleanLattice(true)))));
+}
+
+TEST_F(MatchSwitchTest, Neither) {
+  std::string Code = R"(
+    void Bar();
+    void fun(bool b) {
+      Bar();
+      // [[p]]
+    }
+  )";
+  RunDataflow(Code,
+              UnorderedElementsAre(Pair("p", Holds(BooleanLattice(false)))));
+}


        


More information about the cfe-commits mailing list