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