diff --git a/clang/include/clang/Analysis/FlowSensitive/MatchSwitch.h b/clang/include/clang/Analysis/FlowSensitive/MatchSwitch.h --- a/clang/include/clang/Analysis/FlowSensitive/MatchSwitch.h +++ b/clang/include/clang/Analysis/FlowSensitive/MatchSwitch.h @@ -25,7 +25,9 @@ #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" +#include "llvm/ADT/DenseMap.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Support/ErrorHandling.h" #include #include #include @@ -49,6 +51,16 @@ template using MatchSwitch = std::function; +/// Options that determine the behavior of a `MatchSwitchBuilder` instance. +struct MatchSwitchBuildOptions { + /// If true, caches the action of a match for a given statement. As a result, + /// matchers will be invoked at most once for each unique statement. + /// + /// Note: This assumes that all invocations of `MatchSwitch` for a given + /// `Stmt` use the same `ASTContext`. + bool CacheActions = true; +}; + /// 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 @@ -91,26 +103,25 @@ return std::move(*this); } - MatchSwitch 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; - } - } - }; + MatchSwitch Build(MatchSwitchBuildOptions Options = {}) && { + if (!Options.CacheActions) { + return [Matcher = BuildMatcher(), Actions = std::move(Actions)]( + const Stmt &Stmt, ASTContext &Context, State &S) { + if (auto M = BuildMatcherForStmt(Matcher, Actions, Stmt, Context)) + M(S); + }; + } + return + [Cache = llvm::DenseMap>(), + Matcher = BuildMatcher(), Actions = std::move(Actions)]( + const Stmt &Stmt, ASTContext &Context, State &S) mutable { + auto Res = Cache.try_emplace(&Stmt, nullptr); + if (Res.second) + Res.first->second = + BuildMatcherForStmt(Matcher, Actions, Stmt, Context); + if (Res.first->second != nullptr) + Res.first->second(S); + }; } private: @@ -141,11 +152,48 @@ std::move(Matchers)); } + using Action = std::function; + + static std::function + BuildMatcherForStmt(const ast_matchers::internal::DynTypedMatcher &Matcher, + const std::vector &Actions, const Stmt &Stmt, + ASTContext &Context) { + return [&Stmt, &Context, &Matcher, + &Actions](State &S) -> std::function { + auto Results = ast_matchers::matchDynamic(Matcher, Stmt, Context); + if (Results.empty()) { + return nullptr; + } + + // 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 [&Actions, Index, &Stmt, Res = std::move(Results[0]), + &Context](State &S) { + Actions[Index]( + &Stmt, ast_matchers::MatchFinder::MatchResult(Res, &Context), + S); + }; + } + } + + llvm_unreachable("Unhandled result in MatchSwitch!"); + }; + } + std::vector Matchers; - std::vector> - Actions; + std::vector Actions; }; + } // namespace dataflow } // namespace clang + #endif // LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_MATCHSWITCH_H_ diff --git a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp --- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp +++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp @@ -284,9 +284,6 @@ } auto buildTransferMatchSwitch() { - // FIXME: Evaluate the efficiency of matchers. If using matchers results in a - // lot of duplicated work (e.g. string comparisons), consider providing APIs - // that avoid it through memoization. return MatchSwitchBuilder() // Attach a symbolic "has_value" state to optional values that we see for // the first time. diff --git a/clang/unittests/Analysis/FlowSensitive/MatchSwitchTest.cpp b/clang/unittests/Analysis/FlowSensitive/MatchSwitchTest.cpp --- a/clang/unittests/Analysis/FlowSensitive/MatchSwitchTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/MatchSwitchTest.cpp @@ -100,19 +100,26 @@ } class TestAnalysis : public DataflowAnalysis { + bool CacheMatchSwitchActions; MatchSwitch> TransferSwitch; public: + TestAnalysis(ASTContext &ASTCtx, bool CacheMatchSwitchActions) + : DataflowAnalysis(ASTCtx), + CacheMatchSwitchActions(CacheMatchSwitchActions) {} + explicit TestAnalysis(ASTContext &Context) : DataflowAnalysis(Context) { using namespace ast_matchers; + MatchSwitchBuildOptions BuildOptions; + BuildOptions.CacheActions = CacheMatchSwitchActions; TransferSwitch = MatchSwitchBuilder>() .CaseOf(declRefExpr(to(varDecl(hasName("X")))), TransferSetTrue) .CaseOf(callExpr(callee(functionDecl(hasName("Foo")))), TransferSetFalse) - .Build(); + .Build(std::move(BuildOptions)); } static BooleanLattice initialElement() { return BooleanLattice::bottom(); } @@ -123,7 +130,7 @@ } }; -class MatchSwitchTest : public ::testing::Test { +class MatchSwitchTest : public ::testing::TestWithParam { protected: template void RunDataflow(llvm::StringRef Code, Matcher Expectations) { @@ -141,7 +148,10 @@ } }; -TEST_F(MatchSwitchTest, JustX) { +INSTANTIATE_TEST_SUITE_P(MatchSwitchTestInst, MatchSwitchTest, + ::testing::Bool()); + +TEST_P(MatchSwitchTest, JustX) { std::string Code = R"( void fun() { int X = 1; @@ -153,7 +163,7 @@ UnorderedElementsAre(Pair("p", Holds(BooleanLattice(true))))); } -TEST_F(MatchSwitchTest, JustFoo) { +TEST_P(MatchSwitchTest, JustFoo) { std::string Code = R"( void Foo(); void fun() { @@ -165,7 +175,7 @@ UnorderedElementsAre(Pair("p", Holds(BooleanLattice(false))))); } -TEST_F(MatchSwitchTest, XThenFoo) { +TEST_P(MatchSwitchTest, XThenFoo) { std::string Code = R"( void Foo(); void fun() { @@ -179,7 +189,7 @@ UnorderedElementsAre(Pair("p", Holds(BooleanLattice(false))))); } -TEST_F(MatchSwitchTest, FooThenX) { +TEST_P(MatchSwitchTest, FooThenX) { std::string Code = R"( void Foo(); void fun() { @@ -193,7 +203,7 @@ UnorderedElementsAre(Pair("p", Holds(BooleanLattice(true))))); } -TEST_F(MatchSwitchTest, Neither) { +TEST_P(MatchSwitchTest, Neither) { std::string Code = R"( void Bar(); void fun(bool b) {