diff --git a/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp b/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp --- a/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "SimplifyBooleanExprCheck.h" +#include "../utils/VisitorTrace.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/Lex/Lexer.h" #include "llvm/Support/SaveAndRestore.h" @@ -291,7 +292,8 @@ return true; } - bool VisitBinaryOperator(const BinaryOperator *Op) const { + bool VisitBinaryOperator(const BinaryOperator *Op) { + auto Trace = Tracer.enter(Op); Check->reportBinOp(Context, Op); return true; } @@ -354,6 +356,7 @@ } bool VisitIfStmt(IfStmt *If) { + auto Trace = Tracer.enter(If); // Skip any if's that have a condition var or an init statement. if (If->hasInitStorage() || If->hasVarStorage()) return true; @@ -433,6 +436,7 @@ } bool VisitConditionalOperator(ConditionalOperator *Cond) { + auto Trace = Tracer.enter(Cond); /* * Condition ? true : false; -> Condition * Condition ? false : true; -> !Condition; @@ -449,6 +453,7 @@ } bool VisitCompoundStmt(CompoundStmt *CS) { + auto Trace = Tracer.enter(CS); if (CS->size() < 2) return true; bool CurIf = false, PrevIf = false; @@ -547,6 +552,7 @@ } bool TraverseUnaryOperator(UnaryOperator *Op) { + auto Trace = Tracer.enter(Op); if (!Check->SimplifyDeMorgan || Op->getOpcode() != UO_LNot) return Base::TraverseUnaryOperator(Op); Expr *SubImp = Op->getSubExpr()->IgnoreImplicit(); @@ -577,6 +583,7 @@ SimplifyBooleanExprCheck *Check; SmallVector StmtStack; ASTContext &Context; + VisitorTrace Tracer{*Check, Context}; }; SimplifyBooleanExprCheck::SimplifyBooleanExprCheck(StringRef Name, diff --git a/clang-tools-extra/clang-tidy/utils/CMakeLists.txt b/clang-tools-extra/clang-tidy/utils/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/utils/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/utils/CMakeLists.txt @@ -21,6 +21,7 @@ TransformerClangTidyCheck.cpp TypeTraits.cpp UsingInserter.cpp + VisitorTrace.cpp LINK_LIBS clangTidy diff --git a/clang-tools-extra/clang-tidy/utils/VisitorTrace.h b/clang-tools-extra/clang-tidy/utils/VisitorTrace.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/utils/VisitorTrace.h @@ -0,0 +1,69 @@ +//===---------- VisitorTrace.h - clang-tidy -------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_VISITORTRACE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_VISITORTRACE_H + +#include "../ClangTidyCheck.h" +#include "clang/AST/ASTContext.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Compiler.h" +#include "llvm/Support/PrettyStackTrace.h" + +namespace clang { +namespace tidy { + +class VisitorTrace : public llvm::PrettyStackTraceEntry { + // These 3 types would cover pretty much all uses. + using PtrTy = llvm::PointerUnion; + // Most uses in tidy would only ever need to store one item in here. + using StackTy = llvm::SmallVector; + +public: + VisitorTrace(llvm::StringLiteral ID, const ASTContext &Ctx) + : ID(ID), Ctx(Ctx) {} + VisitorTrace(ClangTidyCheck &Check, const ASTContext &Ctx) + // FIXME: This cast makes no sense, why does ClangTidyCheck hide the + // virtual getID call? + : ID(static_cast(Check) + .getID()), + Ctx(Ctx) {} + + struct LLVM_NODISCARD PushStateRAII { + friend class VisitorTrace; + + private: + PushStateRAII(StackTy &Stack, PtrTy Ty) : Stack(Stack) { + assert(!Ty.isNull()); + Stack.push_back(Ty); + } + + public: + ~PushStateRAII() { Stack.pop_back(); } + + StackTy &Stack; + }; + + PushStateRAII enter(const Decl *D) { return {Stack, D}; } + + PushStateRAII enter(const Stmt *S) { return {Stack, S}; } + + PushStateRAII enter(const Type *T) { return {Stack, T}; } + + void print(llvm::raw_ostream &OS) const override; + +private: + llvm::StringRef ID; + const ASTContext &Ctx; + StackTy Stack; +}; + +} // namespace tidy +} // namespace clang + +#endif diff --git a/clang-tools-extra/clang-tidy/utils/VisitorTrace.cpp b/clang-tools-extra/clang-tidy/utils/VisitorTrace.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/utils/VisitorTrace.cpp @@ -0,0 +1,56 @@ +//===---------- VisitorTrace.cpp - clang-tidy -----------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "VisitorTrace.h" +#include "llvm/Support/raw_ostream.h" + +using namespace clang; +using namespace clang::tidy; + +static void printStackEntry(llvm::raw_ostream &OS, const Decl *D, + const ASTContext &Ctx) { + OS << D->getDeclKindName() << "Decl "; + if (const auto *ND = llvm::dyn_cast(D)) { + ND->printQualifiedName(OS); + OS << " - "; + } else + OS << "- "; + D->getSourceRange().print(OS, Ctx.getSourceManager()); +} + +static void printStackEntry(llvm::raw_ostream &OS, const Stmt *S, + const ASTContext &Ctx) { + OS << S->getStmtClassName() << " - "; + S->getSourceRange().print(OS, Ctx.getSourceManager()); +} + +static void printStackEntry(llvm::raw_ostream &OS, const Type *T, + const ASTContext &Ctx) { + OS << T->getTypeClassName() << "Type - "; + QualType(T, 0).print(OS, Ctx.getPrintingPolicy()); +} + +void VisitorTrace::print(llvm::raw_ostream &OS) const { + OS << "Running AST visitor for '" << ID << "'\n"; + if (Stack.empty()) { + OS << "No children visited\n"; + return; + } + unsigned Idx = 0; + OS << "Visiting:"; + for (const PtrTy &Item : llvm::reverse(Stack)) { + OS << "\n #" << ++Idx << ": "; + if (const auto *D = Item.dyn_cast()) + printStackEntry(OS, D, Ctx); + else if (const auto *S = Item.dyn_cast()) + printStackEntry(OS, S, Ctx); + else + printStackEntry(OS, Item.get(), Ctx); + } + OS << "\n"; +} diff --git a/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt b/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt --- a/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt +++ b/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt @@ -33,6 +33,7 @@ UsingInserterTest.cpp ReadabilityModuleTest.cpp TransformerClangTidyCheckTest.cpp + VisitorTraceTest.cpp ) clang_target_link_libraries(ClangTidyTests diff --git a/clang-tools-extra/unittests/clang-tidy/VisitorTraceTest.cpp b/clang-tools-extra/unittests/clang-tidy/VisitorTraceTest.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/unittests/clang-tidy/VisitorTraceTest.cpp @@ -0,0 +1,103 @@ +//===------- VisitorTraceTest.cpp - clang-tidy ----------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "utils/VisitorTrace.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/ASTMatchers/ASTMatchersInternal.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/raw_ostream.h" +#include "gtest/gtest.h" +#include +#include + +namespace clang { +namespace tidy { + +constexpr llvm::StringLiteral Bind = "Bound"; + +using namespace clang::ast_matchers; + +namespace { +struct ASTAndTracer { + std::unique_ptr AST; + VisitorTrace Tracer; + + ASTAndTracer(std::unique_ptr AST) + : AST(std::move(AST)), Tracer("Test", this->AST->getASTContext()) {} + + ASTAndTracer(StringRef Code) + : ASTAndTracer(tooling::buildASTFromCodeWithArgs(Code, {"-std=c++11"})) {} + + template + const NodeT *getNode(internal::BindableMatcher Matcher) { + return selectFirst(Bind, + match(Matcher.bind(Bind), AST->getASTContext())); + } + + template + const NodeT *getNode(StringRef ID, internal::Matcher Matcher) { + return selectFirst(ID, match(Matcher, AST->getASTContext())); + } + + std::string dumpVisitor() { + std::string S; + llvm::raw_string_ostream OS(S); + Tracer.print(OS); + return S; + } +}; +} // namespace + +TEST(VisitorTrace, TestOutput) { + ASTAndTracer Item("void Foo(){}"); + + StringRef EmptyState = R"(Running AST visitor for 'Test' +No children visited +)"; + + StringRef State1 = R"(Running AST visitor for 'Test' +Visiting: + #1: FunctionDecl Foo - +)"; + + StringRef State2 = R"(Running AST visitor for 'Test' +Visiting: + #1: BuiltinType - void + #2: FunctionDecl Foo - +)"; + + StringRef State3 = R"(Running AST visitor for 'Test' +Visiting: + #1: CompoundStmt - + #2: BuiltinType - void + #3: FunctionDecl Foo - +)"; + + EXPECT_EQ(Item.dumpVisitor(), EmptyState); + { + auto State = Item.Tracer.enter(Item.getNode(functionDecl(hasName("Foo")))); + EXPECT_EQ(Item.dumpVisitor(), State1); + { + auto State = Item.Tracer.enter( + Item.getNode(Bind, functionDecl(returns(type().bind(Bind))))); + EXPECT_EQ(Item.dumpVisitor(), State2); + { + auto State = Item.Tracer.enter(Item.getNode(compoundStmt())); + EXPECT_EQ(Item.dumpVisitor(), State3); + } + EXPECT_EQ(Item.dumpVisitor(), State2); + } + EXPECT_EQ(Item.dumpVisitor(), State1); + } + EXPECT_EQ(Item.dumpVisitor(), EmptyState); +} + +} // namespace tidy +} // namespace clang