[clang][dataflow] Generalise match switch utility to other AST types and add a `CFGMatchSwitch` which currently handles `CFGStmt` and `CFGInitializer`.

`MatchSwitch` currently takes in matchers and functions for the `Stmt` class.

This patch generalises the match switch utility (renamed to `ASTMatchSwitch`) to work for different AST node types by introducing a template argument which is the base type for the AST nodes that the match switch will handle.

A `CFGMatchSwitch` is introduced as a wrapper around multiple `ASTMatchSwitch`s for different base types. It works by unwrapping `CFGElement`s into their contained AST nodes and passing the nodes to the relevant `ASTMatchSwitch`. The `CFGMatchSwitch` currently only handles `CFGStmt` and `CFGInitializer`.

Reviewed By: gribozavr2, sgatev

Differential Revision: https://reviews.llvm.org/D131616
This commit is contained in:
Wei Yi Tee 2022-09-01 08:39:59 +00:00
parent 849df8f6f0
commit d931ac9e27
5 changed files with 273 additions and 36 deletions

View File

@ -0,0 +1,98 @@
//===---- CFGMatchSwitch.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 `CFGMatchSwitch` abstraction for building a "switch"
// statement for control flow graph elements. Each case of the switch is
// defined by an ASTMatcher which is applied on the AST node contained in the
// input `CFGElement`.
//
// Currently, the `CFGMatchSwitch` only handles `CFGElement`s of
// `Kind::Statement` and `Kind::Initializer`.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_CFGMATCHSWITCH_H_
#define LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_CFGMATCHSWITCH_H_
#include "clang/AST/ASTContext.h"
#include "clang/AST/Stmt.h"
#include "clang/Analysis/CFG.h"
#include "clang/Analysis/FlowSensitive/MatchSwitch.h"
#include <functional>
#include <utility>
namespace clang {
namespace dataflow {
template <typename State, typename Result = void>
using CFGMatchSwitch =
std::function<Result(const CFGElement &, 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 an AST node
/// contained in a CFG element.
template <typename State, typename Result = void> class CFGMatchSwitchBuilder {
public:
/// Registers an action `A` for `CFGStmt`s that will be triggered by the match
/// of the pattern `M` against the `Stmt` contained in the input `CFGStmt`.
///
/// Requirements:
///
/// `NodeT` should be derived from `Stmt`.
template <typename NodeT>
CFGMatchSwitchBuilder &&
CaseOfCFGStmt(MatchSwitchMatcher<Stmt> M,
MatchSwitchAction<NodeT, State, Result> A) && {
std::move(StmtBuilder).template CaseOf<NodeT>(M, A);
return std::move(*this);
}
/// Registers an action `A` for `CFGInitializer`s that will be triggered by
/// the match of the pattern `M` against the `CXXCtorInitializer` contained in
/// the input `CFGInitializer`.
///
/// Requirements:
///
/// `NodeT` should be derived from `CXXCtorInitializer`.
template <typename NodeT>
CFGMatchSwitchBuilder &&
CaseOfCFGInit(MatchSwitchMatcher<CXXCtorInitializer> M,
MatchSwitchAction<NodeT, State, Result> A) && {
std::move(InitBuilder).template CaseOf<NodeT>(M, A);
return std::move(*this);
}
CFGMatchSwitch<State, Result> Build() && {
return [StmtMS = std::move(StmtBuilder).Build(),
InitMS = std::move(InitBuilder).Build()](const CFGElement &Element,
ASTContext &Context,
State &S) -> Result {
switch (Element.getKind()) {
case CFGElement::Initializer:
return InitMS(*Element.castAs<CFGInitializer>().getInitializer(),
Context, S);
case CFGElement::Statement:
case CFGElement::Constructor:
case CFGElement::CXXRecordTypedCall:
return StmtMS(*Element.castAs<CFGStmt>().getStmt(), Context, S);
default:
// FIXME: Handle other kinds of CFGElement.
return Result();
}
};
}
private:
ASTMatchSwitchBuilder<Stmt, State, Result> StmtBuilder;
ASTMatchSwitchBuilder<CXXCtorInitializer, State, Result> InitBuilder;
};
} // namespace dataflow
} // namespace clang
#endif // LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_CFGMATCHSWITCH_H_

View File

@ -16,6 +16,9 @@
// library may be generalized and moved to ASTMatchers.
//
//===----------------------------------------------------------------------===//
//
// FIXME: Rename to ASTMatchSwitch.h and update documentation when all usages of
// `MatchSwitch` are updated to `ASTMatchSwitch<Stmt>`
#ifndef LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_MATCHSWITCH_H_
#define LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_MATCHSWITCH_H_
@ -28,6 +31,7 @@
#include "llvm/ADT/StringRef.h"
#include <functional>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
@ -44,23 +48,35 @@ template <typename LatticeT> struct TransferState {
Environment &Env;
};
/// Matches against `Stmt` and, based on its structure, dispatches to an
/// appropriate handler.
template <typename T>
using MatchSwitchMatcher = ast_matchers::internal::Matcher<T>;
template <typename T, typename State, typename Result = void>
using MatchSwitchAction = std::function<Result(
const T *, const ast_matchers::MatchFinder::MatchResult &, State &)>;
template <typename BaseT, typename State, typename Result = void>
using ASTMatchSwitch =
std::function<Result(const BaseT &, ASTContext &, State &)>;
// FIXME: Remove this alias when all usages of `MatchSwitch` are updated to
// `ASTMatchSwitch<Stmt>`.
template <typename State, typename Result = void>
using MatchSwitch = std::function<Result(const Stmt &, ASTContext &, State &)>;
using MatchSwitch = ASTMatchSwitch<Stmt, State, Result>;
/// 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.
/// callbacks, which together define a switch that can be applied to a node
/// whose type derives from `BaseT`. 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.
/// can define the `ASTMatchSwitch` 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>>()
/// ASTMatchSwitch<Stmt, TransferState<MyLattice> BuildSwitch() {
/// return ASTMatchSwitchBuilder<TransferState<MyLattice>>()
/// .CaseOf(callExpr(callee(functionDecl(hasName("foo")))), TransferFooCall)
/// .CaseOf(callExpr(argumentCountIs(2),
/// callee(functionDecl(hasName("bar")))),
@ -68,35 +84,35 @@ using MatchSwitch = std::function<Result(const Stmt &, ASTContext &, State &)>;
/// .Build();
/// }
/// \endcode
template <typename State, typename Result = void> class MatchSwitchBuilder {
template <typename BaseT, typename State, typename Result = void>
class ASTMatchSwitchBuilder {
public:
/// Registers an action that will be triggered by the match of a pattern
/// against the input statement.
///
/// Requirements:
///
/// `Node` should be a subclass of `Stmt`.
template <typename Node>
MatchSwitchBuilder &&
CaseOf(ast_matchers::internal::Matcher<Stmt> M,
std::function<Result(const Node *,
const ast_matchers::MatchFinder::MatchResult &,
State &)>
A) && {
/// `NodeT` should be derived from `BaseT`.
template <typename NodeT>
ASTMatchSwitchBuilder &&CaseOf(MatchSwitchMatcher<BaseT> M,
MatchSwitchAction<NodeT, State, Result> A) && {
static_assert(std::is_base_of<BaseT, NodeT>::value,
"NodeT must be derived from BaseT.");
Matchers.push_back(std::move(M));
Actions.push_back(
[A = std::move(A)](const Stmt *Stmt,
[A = std::move(A)](const BaseT *Node,
const ast_matchers::MatchFinder::MatchResult &R,
State &S) { return A(cast<Node>(Stmt), R, S); });
State &S) { return A(cast<NodeT>(Node), R, S); });
return std::move(*this);
}
MatchSwitch<State, Result> Build() && {
ASTMatchSwitch<BaseT, State, Result> Build() && {
return [Matcher = BuildMatcher(), Actions = std::move(Actions)](
const Stmt &Stmt, ASTContext &Context, State &S) -> Result {
auto Results = ast_matchers::matchDynamic(Matcher, Stmt, Context);
if (Results.empty())
const BaseT &Node, ASTContext &Context, State &S) -> Result {
auto Results = ast_matchers::matchDynamic(Matcher, Node, Context);
if (Results.empty()) {
return Result();
}
// 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()) {
@ -105,7 +121,7 @@ public:
if (ID.consume_front("Tag") && !ID.getAsInteger(10, Index) &&
Index < Actions.size()) {
return Actions[Index](
&Stmt,
&Node,
ast_matchers::MatchFinder::MatchResult(Results[0], &Context), S);
}
}
@ -137,15 +153,19 @@ private:
// 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>(),
DynTypedMatcher::VO_AnyOf, ASTNodeKind::getFromNodeKind<BaseT>(),
std::move(Matchers));
}
std::vector<ast_matchers::internal::DynTypedMatcher> Matchers;
std::vector<std::function<Result(
const Stmt *, const ast_matchers::MatchFinder::MatchResult &, State &)>>
Actions;
std::vector<MatchSwitchAction<BaseT, State, Result>> Actions;
};
// FIXME: Remove this alias when all usages of `MatchSwitchBuilder` are updated
// to `ASTMatchSwitchBuilder<Stmt>`.
template <typename State, typename Result = void>
using MatchSwitchBuilder = ASTMatchSwitchBuilder<Stmt, State, Result>;
} // namespace dataflow
} // namespace clang
#endif // LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_MATCHSWITCH_H_

View File

@ -0,0 +1,124 @@
//===- unittests/Analysis/FlowSensitive/CFGMatchSwitchTest.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
//
//===----------------------------------------------------------------------===//
#include "clang/Analysis/FlowSensitive/CFGMatchSwitch.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/Stmt.h"
#include "clang/Analysis/CFG.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/ADT/StringRef.h"
#include "gtest/gtest.h"
using namespace clang;
using namespace dataflow;
using namespace ast_matchers;
namespace {
// State for tracking the number of matches on each kind of CFGElement by the
// CFGMatchSwitch. Currently only tracks CFGStmt and CFGInitializer.
struct CFGElementMatches {
unsigned StmtMatches = 0;
unsigned InitializerMatches = 0;
};
// Returns a match switch that counts the number of local variables
// (singly-declared) and fields initialized to the integer literal 42.
auto buildCFGMatchSwitch() {
return CFGMatchSwitchBuilder<CFGElementMatches>()
.CaseOfCFGStmt<DeclStmt>(
declStmt(hasSingleDecl(
varDecl(hasInitializer(integerLiteral(equals(42)))))),
[](const DeclStmt *, const MatchFinder::MatchResult &,
CFGElementMatches &Counter) { Counter.StmtMatches++; })
.CaseOfCFGInit<CXXCtorInitializer>(
cxxCtorInitializer(withInitializer(integerLiteral(equals(42)))),
[](const CXXCtorInitializer *, const MatchFinder::MatchResult &,
CFGElementMatches &Counter) { Counter.InitializerMatches++; })
.Build();
}
// Runs the match switch `MS` on the control flow graph generated from `Code`,
// tracking information in state `S`. For simplicity, this test utility is
// restricted to CFGs with a single control flow block (excluding entry and
// exit blocks) - generated by `Code` with sequential flow (i.e. no branching).
//
// Requirements:
//
// `Code` must contain a function named `f`, the body of this function will be
// used to generate the CFG.
template <typename State>
void applySwitchToCode(CFGMatchSwitch<State> &MS, State &S,
llvm::StringRef Code) {
auto Unit = tooling::buildASTFromCodeWithArgs(Code, {"-Wno-unused-value"});
auto &Ctx = Unit->getASTContext();
const auto *F = selectFirst<FunctionDecl>(
"f", match(functionDecl(isDefinition(), hasName("f")).bind("f"), Ctx));
CFG::BuildOptions BO;
BO.AddInitializers = true;
auto CFG = CFG::buildCFG(F, F->getBody(), &Ctx, BO);
auto CFGBlock = *CFG->getEntry().succ_begin();
for (auto &Elt : CFGBlock->Elements) {
MS(Elt, Ctx, S);
}
}
TEST(CFGMatchSwitchTest, NoInitializationTo42) {
CFGMatchSwitch<CFGElementMatches> Switch = buildCFGMatchSwitch();
CFGElementMatches Counter;
applySwitchToCode(Switch, Counter, R"(
void f() {
42;
}
)");
EXPECT_EQ(Counter.StmtMatches, 0u);
EXPECT_EQ(Counter.InitializerMatches, 0u);
}
TEST(CFGMatchSwitchTest, SingleLocalVarInitializationTo42) {
CFGMatchSwitch<CFGElementMatches> Switch = buildCFGMatchSwitch();
CFGElementMatches Counter;
applySwitchToCode(Switch, Counter, R"(
void f() {
int i = 42;
}
)");
EXPECT_EQ(Counter.StmtMatches, 1u);
EXPECT_EQ(Counter.InitializerMatches, 0u);
}
TEST(CFGMatchSwitchTest, SingleFieldInitializationTo42) {
CFGMatchSwitch<CFGElementMatches> Switch = buildCFGMatchSwitch();
CFGElementMatches Counter;
applySwitchToCode(Switch, Counter, R"(
struct f {
int i;
f(): i(42) {}
};
)");
EXPECT_EQ(Counter.StmtMatches, 0u);
EXPECT_EQ(Counter.InitializerMatches, 1u);
}
TEST(CFGMatchSwitchTest, LocalVarAndFieldInitializationTo42) {
CFGMatchSwitch<CFGElementMatches> Switch = buildCFGMatchSwitch();
CFGElementMatches Counter;
applySwitchToCode(Switch, Counter, R"(
struct f {
int i;
f(): i(42) {
int j = 42;
}
};
)");
EXPECT_EQ(Counter.StmtMatches, 1u);
EXPECT_EQ(Counter.InitializerMatches, 1u);
}
} // namespace

View File

@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
)
add_clang_unittest(ClangAnalysisFlowSensitiveTests
CFGMatchSwitchTest.cpp
ChromiumCheckModelTest.cpp
DataflowAnalysisContextTest.cpp
DataflowEnvironmentTest.cpp

View File

@ -5,12 +5,6 @@
// 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"