| 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 | |
| 9 | namespace clang::dataflow::test { |
| 10 | namespace { |
| 11 | using testing::HasSubstr; |
| 12 | |
| 13 | struct 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 | |
| 33 | class TestAnalysis : public DataflowAnalysis<TestAnalysis, TestLattice> { |
| 34 | public: |
| 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 | |
| 53 | class TestLogger : public Logger { |
| 54 | public: |
| 55 | TestLogger(std::string &S) : OS(S) {} |
| 56 | |
| 57 | private: |
| 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 | |
| 89 | AnalysisInputs<TestAnalysis> makeInputs() { |
| 90 | const char *Code = R"cpp( |
| 91 | int 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 | |
| 105 | TEST(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 | |
| 117 | enterBlock(4, false) |
| 118 | recordState(Elements=0, Branches=0, Joins=0) |
| 119 | enterElement(b) |
| 120 | transfer() |
| 121 | recordState(Elements=1, Branches=0, Joins=0) |
| 122 | enterElement(b (ImplicitCastExpr, LValueToRValue, _Bool)) |
| 123 | transfer() |
| 124 | recordState(Elements=2, Branches=0, Joins=0) |
| 125 | recordState(Elements=2, Branches=0, Joins=0) |
| 126 | |
| 127 | enterBlock(2, false) |
| 128 | transferBranch(1) |
| 129 | recordState(Elements=2, Branches=1, Joins=0) |
| 130 | enterElement(p) |
| 131 | transfer() |
| 132 | recordState(Elements=3, Branches=1, Joins=0) |
| 133 | |
| 134 | enterBlock(3, false) |
| 135 | transferBranch(0) |
| 136 | recordState(Elements=2, Branches=1, Joins=0) |
| 137 | enterElement(q) |
| 138 | transfer() |
| 139 | recordState(Elements=3, Branches=1, Joins=0) |
| 140 | |
| 141 | enterBlock(1, false) |
| 142 | recordState(Elements=6, Branches=2, Joins=1) |
| 143 | enterElement(b ? p : q) |
| 144 | transfer() |
| 145 | recordState(Elements=7, Branches=2, Joins=1) |
| 146 | enterElement(b ? p : q (ImplicitCastExpr, LValueToRValue, int)) |
| 147 | transfer() |
| 148 | recordState(Elements=8, Branches=2, Joins=1) |
| 149 | enterElement(return b ? p : q;) |
| 150 | transfer() |
| 151 | recordState(Elements=9, Branches=2, Joins=1) |
| 152 | |
| 153 | enterBlock(0, false) |
| 154 | recordState(Elements=9, Branches=2, Joins=1) |
| 155 | |
| 156 | endAnalysis() |
| 157 | )" ); |
| 158 | } |
| 159 | |
| 160 | TEST(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 | |