xref: /llvm-project/clang/unittests/Analysis/FlowSensitive/LoggerTest.cpp (revision ccf1e322bd5828c74ac5213dc2e05ab506da25bd)
1 #include "TestingSupport.h"
2 #include "clang/ASTMatchers/ASTMatchers.h"
3 #include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
4 #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
5 #include "clang/Analysis/FlowSensitive/DataflowLattice.h"
6 #include "llvm/Testing/Support/Error.h"
7 #include "gtest/gtest.h"
8 #include <optional>
9 
10 namespace clang::dataflow::test {
11 namespace {
12 using testing::HasSubstr;
13 
14 struct TestLattice {
15   int Elements = 0;
16   int Branches = 0;
17   int Joins = 0;
18 
19   LatticeJoinEffect join(const TestLattice &Other) {
20     if (Joins < 3) {
21       ++Joins;
22       Elements += Other.Elements;
23       Branches += Other.Branches;
24       return LatticeJoinEffect::Changed;
25     }
26     return LatticeJoinEffect::Unchanged;
27   }
28   friend bool operator==(const TestLattice &LHS, const TestLattice &RHS) {
29     return std::tie(LHS.Elements, LHS.Branches, LHS.Joins) ==
30            std::tie(RHS.Elements, RHS.Branches, RHS.Joins);
31   }
32 };
33 
34 class TestAnalysis : public DataflowAnalysis<TestAnalysis, TestLattice> {
35 public:
36   using DataflowAnalysis::DataflowAnalysis;
37 
38   static TestLattice initialElement() { return TestLattice{}; }
39   void transfer(const CFGElement &, TestLattice &L, Environment &E) {
40     E.getDataflowAnalysisContext().getOptions().Log->log(
41         [](llvm::raw_ostream &OS) { OS << "transfer()"; });
42     ++L.Elements;
43   }
44   void transferBranch(bool Branch, const Stmt *S, TestLattice &L,
45                       Environment &E) {
46     E.getDataflowAnalysisContext().getOptions().Log->log(
47         [&](llvm::raw_ostream &OS) {
48           OS << "transferBranch(" << Branch << ")";
49         });
50     ++L.Branches;
51   }
52 };
53 
54 class TestLogger : public Logger {
55 public:
56   TestLogger(std::string &S) : OS(S) {}
57 
58 private:
59   llvm::raw_string_ostream OS;
60 
61   void beginAnalysis(const ControlFlowContext &,
62                      TypeErasedDataflowAnalysis &) override {
63     logText("beginAnalysis()");
64   }
65   void endAnalysis() override { logText("\nendAnalysis()"); }
66 
67   void enterBlock(const CFGBlock &B, bool PostVisit) override {
68     OS << "\nenterBlock(" << B.BlockID << ", " << (PostVisit ? "true" : "false")
69        << ")\n";
70   }
71   void enterElement(const CFGElement &E) override {
72     // we don't want the trailing \n
73     std::string S;
74     llvm::raw_string_ostream SS(S);
75     E.dumpToStream(SS);
76 
77     OS << "enterElement(" << llvm::StringRef(S).trim() << ")\n";
78   }
79   void recordState(TypeErasedDataflowAnalysisState &S) override {
80     const TestLattice &L = llvm::any_cast<TestLattice>(S.Lattice.Value);
81     OS << "recordState(Elements=" << L.Elements << ", Branches=" << L.Branches
82        << ", Joins=" << L.Joins << ")\n";
83   }
84   /// Records that the analysis state for the current block is now final.
85   void blockConverged() override { logText("blockConverged()"); }
86 
87   void logText(llvm::StringRef Text) override { OS << Text << "\n"; }
88 };
89 
90 AnalysisInputs<TestAnalysis> makeInputs() {
91   const char *Code = R"cpp(
92 int target(bool b, int p, int q) {
93   return b ? p : q;
94 }
95 )cpp";
96   static const std::vector<std::string> Args = {
97       "-fsyntax-only", "-fno-delayed-template-parsing", "-std=c++17"};
98 
99   auto Inputs = AnalysisInputs<TestAnalysis>(
100       Code, ast_matchers::hasName("target"),
101       [](ASTContext &C, Environment &) { return TestAnalysis(C); });
102   Inputs.ASTBuildArgs = Args;
103   return Inputs;
104 }
105 
106 TEST(LoggerTest, Sequence) {
107   auto Inputs = makeInputs();
108   std::string Log;
109   TestLogger Logger(Log);
110   Inputs.BuiltinOptions.Log = &Logger;
111 
112   ASSERT_THAT_ERROR(checkDataflow<TestAnalysis>(std::move(Inputs),
113                                                 [](const AnalysisOutputs &) {}),
114                     llvm::Succeeded());
115 
116   EXPECT_EQ(Log, R"(beginAnalysis()
117 
118 enterBlock(4, false)
119 recordState(Elements=0, Branches=0, Joins=0)
120 enterElement(b)
121 transfer()
122 recordState(Elements=1, Branches=0, Joins=0)
123 enterElement(b (ImplicitCastExpr, LValueToRValue, _Bool))
124 transfer()
125 recordState(Elements=2, Branches=0, Joins=0)
126 recordState(Elements=2, Branches=0, Joins=0)
127 
128 enterBlock(3, false)
129 transferBranch(0)
130 recordState(Elements=2, Branches=1, Joins=0)
131 enterElement(q)
132 transfer()
133 recordState(Elements=3, Branches=1, Joins=0)
134 
135 enterBlock(2, false)
136 transferBranch(1)
137 recordState(Elements=2, Branches=1, Joins=0)
138 enterElement(p)
139 transfer()
140 recordState(Elements=3, Branches=1, Joins=0)
141 
142 enterBlock(1, false)
143 recordState(Elements=6, Branches=2, Joins=1)
144 enterElement(b ? p : q)
145 transfer()
146 recordState(Elements=7, Branches=2, Joins=1)
147 enterElement(b ? p : q (ImplicitCastExpr, LValueToRValue, int))
148 transfer()
149 recordState(Elements=8, Branches=2, Joins=1)
150 enterElement(return b ? p : q;)
151 transfer()
152 recordState(Elements=9, Branches=2, Joins=1)
153 
154 enterBlock(0, false)
155 recordState(Elements=9, Branches=2, Joins=1)
156 
157 endAnalysis()
158 )");
159 }
160 
161 TEST(LoggerTest, HTML) {
162   auto Inputs = makeInputs();
163   std::vector<std::string> Logs;
164   auto Logger = Logger::html([&]() {
165     Logs.emplace_back();
166     return std::make_unique<llvm::raw_string_ostream>(Logs.back());
167   });
168   Inputs.BuiltinOptions.Log = Logger.get();
169 
170   ASSERT_THAT_ERROR(checkDataflow<TestAnalysis>(std::move(Inputs),
171                                                 [](const AnalysisOutputs &) {}),
172                     llvm::Succeeded());
173 
174   // Simple smoke tests: we can't meaningfully test the behavior.
175   ASSERT_THAT(Logs, testing::SizeIs(1));
176   EXPECT_THAT(Logs[0], HasSubstr("function updateSelection")) << "embeds JS";
177   EXPECT_THAT(Logs[0], HasSubstr("html {")) << "embeds CSS";
178   EXPECT_THAT(Logs[0], HasSubstr("b (ImplicitCastExpr")) << "has CFG elements";
179   EXPECT_THAT(Logs[0], HasSubstr("\"B3:1_B3.1\":"))
180       << "has analysis point state";
181   EXPECT_THAT(Logs[0], HasSubstr("transferBranch(0)")) << "has analysis logs";
182   EXPECT_THAT(Logs[0], HasSubstr("LocToVal")) << "has built-in lattice dump";
183   EXPECT_THAT(Logs[0], HasSubstr("\"type\": \"int\"")) << "has value dump";
184 }
185 
186 } // namespace
187 } // namespace clang::dataflow::test
188