#include "TestingSupport.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Analysis/FlowSensitive/DataflowAnalysis.h" #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" #include "clang/Analysis/FlowSensitive/DataflowLattice.h" #include "llvm/Testing/Support/Error.h" #include "gtest/gtest.h" namespace clang::dataflow::test { namespace { using testing::HasSubstr; struct TestLattice { int Elements = 0; int Branches = 0; int Joins = 0; LatticeJoinEffect join(const TestLattice &Other) { if (Joins < 3) { ++Joins; Elements += Other.Elements; Branches += Other.Branches; return LatticeJoinEffect::Changed; } return LatticeJoinEffect::Unchanged; } friend bool operator==(const TestLattice &LHS, const TestLattice &RHS) { return std::tie(LHS.Elements, LHS.Branches, LHS.Joins) == std::tie(RHS.Elements, RHS.Branches, RHS.Joins); } }; class TestAnalysis : public DataflowAnalysis { public: using DataflowAnalysis::DataflowAnalysis; static TestLattice initialElement() { return TestLattice{}; } void transfer(const CFGElement &, TestLattice &L, Environment &E) { E.getDataflowAnalysisContext().getOptions().Log->log( [](llvm::raw_ostream &OS) { OS << "transfer()"; }); ++L.Elements; } void transferBranch(bool Branch, const Stmt *S, TestLattice &L, Environment &E) { E.getDataflowAnalysisContext().getOptions().Log->log( [&](llvm::raw_ostream &OS) { OS << "transferBranch(" << Branch << ")"; }); ++L.Branches; } }; class TestLogger : public Logger { public: TestLogger(std::string &S) : OS(S) {} private: llvm::raw_string_ostream OS; void beginAnalysis(const AdornedCFG &, TypeErasedDataflowAnalysis &) override { logText("beginAnalysis()"); } void endAnalysis() override { logText("\nendAnalysis()"); } void enterBlock(const CFGBlock &B, bool PostVisit) override { OS << "\nenterBlock(" << B.BlockID << ", " << (PostVisit ? "true" : "false") << ")\n"; } void enterElement(const CFGElement &E) override { // we don't want the trailing \n std::string S; llvm::raw_string_ostream SS(S); E.dumpToStream(SS); OS << "enterElement(" << llvm::StringRef(S).trim() << ")\n"; } void recordState(TypeErasedDataflowAnalysisState &S) override { const TestLattice &L = llvm::any_cast(S.Lattice.Value); OS << "recordState(Elements=" << L.Elements << ", Branches=" << L.Branches << ", Joins=" << L.Joins << ")\n"; } /// Records that the analysis state for the current block is now final. void blockConverged() override { logText("blockConverged()"); } void logText(llvm::StringRef Text) override { OS << Text << "\n"; } }; AnalysisInputs makeInputs() { const char *Code = R"cpp( int target(bool b, int p, int q) { return b ? p : q; } )cpp"; static const std::vector Args = { "-fsyntax-only", "-fno-delayed-template-parsing", "-std=c++17"}; auto Inputs = AnalysisInputs( Code, ast_matchers::hasName("target"), [](ASTContext &C, Environment &) { return TestAnalysis(C); }); Inputs.ASTBuildArgs = Args; return Inputs; } TEST(LoggerTest, Sequence) { auto Inputs = makeInputs(); std::string Log; TestLogger Logger(Log); Inputs.BuiltinOptions.Log = &Logger; ASSERT_THAT_ERROR(checkDataflow(std::move(Inputs), [](const AnalysisOutputs &) {}), llvm::Succeeded()); EXPECT_EQ(Log, R"(beginAnalysis() enterBlock(4, false) recordState(Elements=0, Branches=0, Joins=0) enterElement(b) transfer() recordState(Elements=1, Branches=0, Joins=0) enterElement(b (ImplicitCastExpr, LValueToRValue, _Bool)) transfer() recordState(Elements=2, Branches=0, Joins=0) recordState(Elements=2, Branches=0, Joins=0) enterBlock(2, false) transferBranch(1) recordState(Elements=2, Branches=1, Joins=0) enterElement(p) transfer() recordState(Elements=3, Branches=1, Joins=0) enterBlock(3, false) transferBranch(0) recordState(Elements=2, Branches=1, Joins=0) enterElement(q) transfer() recordState(Elements=3, Branches=1, Joins=0) enterBlock(1, false) recordState(Elements=6, Branches=2, Joins=1) enterElement(b ? p : q) transfer() recordState(Elements=7, Branches=2, Joins=1) enterElement(b ? p : q (ImplicitCastExpr, LValueToRValue, int)) transfer() recordState(Elements=8, Branches=2, Joins=1) enterElement(return b ? p : q;) transfer() recordState(Elements=9, Branches=2, Joins=1) enterBlock(0, false) recordState(Elements=9, Branches=2, Joins=1) endAnalysis() )"); } TEST(LoggerTest, HTML) { auto Inputs = makeInputs(); std::vector Logs; auto Logger = Logger::html([&]() { Logs.emplace_back(); return std::make_unique(Logs.back()); }); Inputs.BuiltinOptions.Log = Logger.get(); ASSERT_THAT_ERROR(checkDataflow(std::move(Inputs), [](const AnalysisOutputs &) {}), llvm::Succeeded()); // Simple smoke tests: we can't meaningfully test the behavior. ASSERT_THAT(Logs, testing::SizeIs(1)); EXPECT_THAT(Logs[0], HasSubstr("function updateSelection")) << "embeds JS"; EXPECT_THAT(Logs[0], HasSubstr("html {")) << "embeds CSS"; EXPECT_THAT(Logs[0], HasSubstr("b (ImplicitCastExpr")) << "has CFG elements"; EXPECT_THAT(Logs[0], HasSubstr("\"B3:1_B3.1\":")) << "has analysis point state"; EXPECT_THAT(Logs[0], HasSubstr("transferBranch(0)")) << "has analysis logs"; EXPECT_THAT(Logs[0], HasSubstr("LocToVal")) << "has built-in lattice dump"; EXPECT_THAT(Logs[0], HasSubstr("\"type\": \"int\"")) << "has value dump"; } } // namespace } // namespace clang::dataflow::test