xref: /llvm-project/clang/unittests/Analysis/FlowSensitive/MatchSwitchTest.cpp (revision 9cbdef610339e2b762ba5fa98a4db044bba19d0d)
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 #include "clang/Analysis/FlowSensitive/MatchSwitch.h"
10 #include "TestingSupport.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/AST/Decl.h"
13 #include "clang/AST/Expr.h"
14 #include "clang/AST/Stmt.h"
15 #include "clang/ASTMatchers/ASTMatchFinder.h"
16 #include "clang/ASTMatchers/ASTMatchers.h"
17 #include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
18 #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
19 #include "clang/Analysis/FlowSensitive/DataflowLattice.h"
20 #include "clang/Analysis/FlowSensitive/MapLattice.h"
21 #include "clang/Tooling/Tooling.h"
22 #include "llvm/ADT/StringRef.h"
23 #include "llvm/ADT/Twine.h"
24 #include "llvm/Support/Error.h"
25 #include "llvm/Testing/ADT/StringMapEntry.h"
26 #include "llvm/Testing/Support/Error.h"
27 #include "gmock/gmock.h"
28 #include "gtest/gtest.h"
29 #include <cstdint>
30 #include <memory>
31 #include <ostream>
32 #include <string>
33 #include <utility>
34 
35 using namespace clang;
36 using namespace dataflow;
37 
38 namespace {
39 using ::llvm::IsStringMapEntry;
40 using ::testing::UnorderedElementsAre;
41 
42 class BooleanLattice {
43 public:
44   BooleanLattice() : Value(false) {}
45   explicit BooleanLattice(bool B) : Value(B) {}
46 
47   static BooleanLattice bottom() { return BooleanLattice(false); }
48 
49   static BooleanLattice top() { return BooleanLattice(true); }
50 
51   LatticeJoinEffect join(BooleanLattice Other) {
52     auto Prev = Value;
53     Value = Value || Other.Value;
54     return Prev == Value ? LatticeJoinEffect::Unchanged
55                          : LatticeJoinEffect::Changed;
56   }
57 
58   friend bool operator==(BooleanLattice LHS, BooleanLattice RHS) {
59     return LHS.Value == RHS.Value;
60   }
61 
62   friend std::ostream &operator<<(std::ostream &Os, const BooleanLattice &B) {
63     Os << B.Value;
64     return Os;
65   }
66 
67   bool value() const { return Value; }
68 
69 private:
70   bool Value;
71 };
72 } // namespace
73 
74 MATCHER_P(Holds, m,
75           ((negation ? "doesn't hold" : "holds") +
76            llvm::StringRef(" a lattice element that ") +
77            ::testing::DescribeMatcher<BooleanLattice>(m, negation))
78               .str()) {
79   return ExplainMatchResult(m, arg.Lattice, result_listener);
80 }
81 
82 void TransferSetTrue(const DeclRefExpr *,
83                      const ast_matchers::MatchFinder::MatchResult &,
84                      TransferState<BooleanLattice> &State) {
85   State.Lattice = BooleanLattice(true);
86 }
87 
88 void TransferSetFalse(const Stmt *,
89                       const ast_matchers::MatchFinder::MatchResult &,
90                       TransferState<BooleanLattice> &State) {
91   State.Lattice = BooleanLattice(false);
92 }
93 
94 class TestAnalysis : public DataflowAnalysis<TestAnalysis, BooleanLattice> {
95   MatchSwitch<TransferState<BooleanLattice>> TransferSwitch;
96 
97 public:
98   explicit TestAnalysis(ASTContext &Context)
99       : DataflowAnalysis<TestAnalysis, BooleanLattice>(Context) {
100     using namespace ast_matchers;
101     TransferSwitch =
102         MatchSwitchBuilder<TransferState<BooleanLattice>>()
103             .CaseOf<DeclRefExpr>(declRefExpr(to(varDecl(hasName("X")))),
104                                  TransferSetTrue)
105             .CaseOf<Stmt>(callExpr(callee(functionDecl(hasName("Foo")))),
106                           TransferSetFalse)
107             .Build();
108   }
109 
110   static BooleanLattice initialElement() { return BooleanLattice::bottom(); }
111 
112   void transfer(const Stmt *S, BooleanLattice &L, Environment &Env) {
113     TransferState<BooleanLattice> State(L, Env);
114     TransferSwitch(*S, getASTContext(), State);
115   }
116 };
117 
118 template <typename Matcher>
119 void RunDataflow(llvm::StringRef Code, Matcher Expectations) {
120   using namespace ast_matchers;
121   using namespace test;
122   ASSERT_THAT_ERROR(
123       checkDataflow<TestAnalysis>(
124           AnalysisInputs<TestAnalysis>(
125               Code, hasName("fun"),
126               [](ASTContext &C, Environment &) { return TestAnalysis(C); })
127               .withASTBuildArgs({"-fsyntax-only", "-std=c++17"}),
128           /*VerifyResults=*/
129           [&Expectations](
130               const llvm::StringMap<
131                   DataflowAnalysisState<TestAnalysis::Lattice>> &Results,
132               const AnalysisOutputs &) { EXPECT_THAT(Results, Expectations); }),
133       llvm::Succeeded());
134 }
135 
136 TEST(MatchSwitchTest, JustX) {
137   std::string Code = R"(
138     void fun() {
139       int X = 1;
140       (void)X;
141       // [[p]]
142     }
143   )";
144   RunDataflow(Code, UnorderedElementsAre(
145                         IsStringMapEntry("p", Holds(BooleanLattice(true)))));
146 }
147 
148 TEST(MatchSwitchTest, JustFoo) {
149   std::string Code = R"(
150     void Foo();
151     void fun() {
152       Foo();
153       // [[p]]
154     }
155   )";
156   RunDataflow(Code, UnorderedElementsAre(
157                         IsStringMapEntry("p", Holds(BooleanLattice(false)))));
158 }
159 
160 TEST(MatchSwitchTest, XThenFoo) {
161   std::string Code = R"(
162     void Foo();
163     void fun() {
164       int X = 1;
165       (void)X;
166       Foo();
167       // [[p]]
168     }
169   )";
170   RunDataflow(Code, UnorderedElementsAre(
171                         IsStringMapEntry("p", Holds(BooleanLattice(false)))));
172 }
173 
174 TEST(MatchSwitchTest, FooThenX) {
175   std::string Code = R"(
176     void Foo();
177     void fun() {
178       Foo();
179       int X = 1;
180       (void)X;
181       // [[p]]
182     }
183   )";
184   RunDataflow(Code, UnorderedElementsAre(
185                         IsStringMapEntry("p", Holds(BooleanLattice(true)))));
186 }
187 
188 TEST(MatchSwitchTest, Neither) {
189   std::string Code = R"(
190     void Bar();
191     void fun(bool b) {
192       Bar();
193       // [[p]]
194     }
195   )";
196   RunDataflow(Code, UnorderedElementsAre(
197                         IsStringMapEntry("p", Holds(BooleanLattice(false)))));
198 }
199 
200 TEST(MatchSwitchTest, ReturnNonVoid) {
201   using namespace ast_matchers;
202 
203   auto Unit =
204       tooling::buildASTFromCode("void f() { int x = 42; }", "input.cc",
205                                 std::make_shared<PCHContainerOperations>());
206   auto &Context = Unit->getASTContext();
207   const auto *S =
208       selectFirst<FunctionDecl>(
209           "f",
210           match(functionDecl(isDefinition(), hasName("f")).bind("f"), Context))
211           ->getBody();
212 
213   MatchSwitch<const int, std::vector<int>> Switch =
214       MatchSwitchBuilder<const int, std::vector<int>>()
215           .CaseOf<Stmt>(stmt(),
216                         [](const Stmt *, const MatchFinder::MatchResult &,
217                            const int &State) -> std::vector<int> {
218                           return {1, State, 3};
219                         })
220           .Build();
221   std::vector<int> Actual = Switch(*S, Context, 7);
222   std::vector<int> Expected{1, 7, 3};
223   EXPECT_EQ(Actual, Expected);
224 }
225