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

source code of clang/unittests/Analysis/FlowSensitive/LoggerTest.cpp