From b4ad3c3891e550b04db3aca73e1f34007e94eaa8 Mon Sep 17 00:00:00 2001 From: Nathan James Date: Tue, 5 Apr 2022 21:47:06 +0100 Subject: [PATCH] Reland "[ASTMatchers] Output currently matching node on crash" Extend D120185 to also log the node being matched on in case of a crash. This can help if a matcher is causing a crash or there are not enough interesting nodes bound. Reviewed By: aaron.ballman Differential Revision: https://reviews.llvm.org/D122529 This reverts commit 61d67c8eecd2547bc690f9a6bba61b9ba814c71b. This relands commit 6e33e45b943061f79ab6de1b60d04d4c6f32f8f9. Fixing the build issue on 32bit machines due to not enough free bits in the PointerUnion. --- clang/lib/ASTMatchers/ASTMatchFinder.cpp | 236 ++++++++++++++---- .../ASTMatchers/ASTMatchersInternalTest.cpp | 30 ++- 2 files changed, 207 insertions(+), 59 deletions(-) diff --git a/clang/lib/ASTMatchers/ASTMatchFinder.cpp b/clang/lib/ASTMatchers/ASTMatchFinder.cpp index 70598460151a..5d245d3fc9b7 100644 --- a/clang/lib/ASTMatchers/ASTMatchFinder.cpp +++ b/clang/lib/ASTMatchers/ASTMatchFinder.cpp @@ -761,53 +761,189 @@ public: D); } +private: + bool TraversingASTNodeNotSpelledInSource = false; + bool TraversingASTNodeNotAsIs = false; + bool TraversingASTChildrenNotSpelledInSource = false; + + class CurMatchData { +// We don't have enough free low bits in 32bit builds to discriminate 8 pointer +// types in PointerUnion. so split the union in 2 using a free bit from the +// callback pointer. +#define CMD_TYPES_0 \ + const QualType *, const TypeLoc *, const NestedNameSpecifier *, \ + const NestedNameSpecifierLoc * +#define CMD_TYPES_1 \ + const CXXCtorInitializer *, const TemplateArgumentLoc *, const Attr *, \ + const DynTypedNode * + +#define IMPL(Index) \ + template \ + typename std::enable_if_t< \ + llvm::is_one_of::value> \ + SetCallbackAndRawNode(const MatchCallback *CB, const NodeType &N) { \ + assertEmpty(); \ + Callback.setPointerAndInt(CB, Index); \ + Node##Index = &N; \ + } \ + \ + template \ + typename std::enable_if_t< \ + llvm::is_one_of::value, const T *> \ + getNode() const { \ + assertHoldsState(); \ + return Callback.getInt() == (Index) ? Node##Index.dyn_cast() \ + : nullptr; \ + } + + public: + CurMatchData() : Node0(nullptr) {} + + IMPL(0) + IMPL(1) + + const MatchCallback *getCallback() const { return Callback.getPointer(); } + + void SetBoundNodes(const BoundNodes &BN) { + assertHoldsState(); + BNodes = &BN; + } + + void clearBoundNodes() { + assertHoldsState(); + BNodes = nullptr; + } + + const BoundNodes *getBoundNodes() const { + assertHoldsState(); + return BNodes; + } + + void reset() { + assertHoldsState(); + Callback.setPointerAndInt(nullptr, 0); + Node0 = nullptr; + } + + private: + void assertHoldsState() const { + assert(Callback.getPointer() != nullptr && !Node0.isNull()); + } + + void assertEmpty() const { + assert(Callback.getPointer() == nullptr && Node0.isNull() && + BNodes == nullptr); + } + + llvm::PointerIntPair Callback; + union { + llvm::PointerUnion Node0; + llvm::PointerUnion Node1; + }; + const BoundNodes *BNodes = nullptr; + +#undef CMD_TYPES_0 +#undef CMD_TYPES_1 +#undef IMPL + } CurMatchState; + + struct CurMatchRAII { + template + CurMatchRAII(MatchASTVisitor &MV, const MatchCallback *CB, + const NodeType &NT) + : MV(MV) { + MV.CurMatchState.SetCallbackAndRawNode(CB, NT); + } + + ~CurMatchRAII() { MV.CurMatchState.reset(); } + + private: + MatchASTVisitor &MV; + }; + +public: class TraceReporter : llvm::PrettyStackTraceEntry { + static void dumpNode(const ASTContext &Ctx, const DynTypedNode &Node, + raw_ostream &OS) { + if (const auto *D = Node.get()) { + OS << D->getDeclKindName() << "Decl "; + if (const auto *ND = dyn_cast(D)) { + ND->printQualifiedName(OS); + OS << " : "; + } else + OS << ": "; + D->getSourceRange().print(OS, Ctx.getSourceManager()); + } else if (const auto *S = Node.get()) { + OS << S->getStmtClassName() << " : "; + S->getSourceRange().print(OS, Ctx.getSourceManager()); + } else if (const auto *T = Node.get()) { + OS << T->getTypeClassName() << "Type : "; + QualType(T, 0).print(OS, Ctx.getPrintingPolicy()); + } else if (const auto *QT = Node.get()) { + OS << "QualType : "; + QT->print(OS, Ctx.getPrintingPolicy()); + } else { + OS << Node.getNodeKind().asStringRef() << " : "; + Node.getSourceRange().print(OS, Ctx.getSourceManager()); + } + } + + static void dumpNodeFromState(const ASTContext &Ctx, + const CurMatchData &State, raw_ostream &OS) { + if (const DynTypedNode *MatchNode = State.getNode()) { + dumpNode(Ctx, *MatchNode, OS); + } else if (const auto *QT = State.getNode()) { + dumpNode(Ctx, DynTypedNode::create(*QT), OS); + } else if (const auto *TL = State.getNode()) { + dumpNode(Ctx, DynTypedNode::create(*TL), OS); + } else if (const auto *NNS = State.getNode()) { + dumpNode(Ctx, DynTypedNode::create(*NNS), OS); + } else if (const auto *NNSL = State.getNode()) { + dumpNode(Ctx, DynTypedNode::create(*NNSL), OS); + } else if (const auto *CtorInit = State.getNode()) { + dumpNode(Ctx, DynTypedNode::create(*CtorInit), OS); + } else if (const auto *TAL = State.getNode()) { + dumpNode(Ctx, DynTypedNode::create(*TAL), OS); + } else if (const auto *At = State.getNode()) { + dumpNode(Ctx, DynTypedNode::create(*At), OS); + } + } + public: TraceReporter(const MatchASTVisitor &MV) : MV(MV) {} void print(raw_ostream &OS) const override { - if (!MV.CurMatched) { + const CurMatchData &State = MV.CurMatchState; + const MatchCallback *CB = State.getCallback(); + if (!CB) { OS << "ASTMatcher: Not currently matching\n"; return; } + assert(MV.ActiveASTContext && "ActiveASTContext should be set if there is a matched callback"); - OS << "ASTMatcher: Processing '" << MV.CurMatched->getID() << "'\n"; - const BoundNodes::IDToNodeMap &Map = MV.CurBoundNodes->getMap(); - if (Map.empty()) { - OS << "No bound nodes\n"; - return; - } - OS << "--- Bound Nodes Begin ---\n"; - for (const auto &Item : Map) { - OS << " " << Item.first << " - { "; - if (const auto *D = Item.second.get()) { - OS << D->getDeclKindName() << "Decl "; - if (const auto *ND = dyn_cast(D)) { - ND->printQualifiedName(OS); - OS << " : "; - } else - OS << ": "; - D->getSourceRange().print(OS, - MV.ActiveASTContext->getSourceManager()); - } else if (const auto *S = Item.second.get()) { - OS << S->getStmtClassName() << " : "; - S->getSourceRange().print(OS, - MV.ActiveASTContext->getSourceManager()); - } else if (const auto *T = Item.second.get()) { - OS << T->getTypeClassName() << "Type : "; - QualType(T, 0).print(OS, MV.ActiveASTContext->getPrintingPolicy()); - } else if (const auto *QT = Item.second.get()) { - OS << "QualType : "; - QT->print(OS, MV.ActiveASTContext->getPrintingPolicy()); - } else { - OS << Item.second.getNodeKind().asStringRef() << " : "; - Item.second.getSourceRange().print( - OS, MV.ActiveASTContext->getSourceManager()); + ASTContext &Ctx = MV.getASTContext(); + + if (const BoundNodes *Nodes = State.getBoundNodes()) { + OS << "ASTMatcher: Processing '" << CB->getID() << "' against:\n\t"; + dumpNodeFromState(Ctx, State, OS); + const BoundNodes::IDToNodeMap &Map = Nodes->getMap(); + if (Map.empty()) { + OS << "\nNo bound nodes\n"; + return; } - OS << " }\n"; + OS << "\n--- Bound Nodes Begin ---\n"; + for (const auto &Item : Map) { + OS << " " << Item.first << " - { "; + dumpNode(Ctx, Item.second, OS); + OS << " }\n"; + } + OS << "--- Bound Nodes End ---\n"; + } else { + OS << "ASTMatcher: Matching '" << CB->getID() << "' against:\n\t"; + dumpNodeFromState(Ctx, State, OS); + OS << '\n'; } - OS << "--- Bound Nodes End ---\n"; } private: @@ -815,13 +951,6 @@ public: }; private: - bool TraversingASTNodeNotSpelledInSource = false; - bool TraversingASTNodeNotAsIs = false; - bool TraversingASTChildrenNotSpelledInSource = false; - - const MatchCallback *CurMatched = nullptr; - const BoundNodes *CurBoundNodes = nullptr; - struct ASTNodeNotSpelledInSourceScope { ASTNodeNotSpelledInSourceScope(MatchASTVisitor *V, bool B) : MV(V), MB(V->TraversingASTNodeNotSpelledInSource) { @@ -887,6 +1016,7 @@ private: if (EnableCheckProfiling) Timer.setBucket(&TimeByBucket[MP.second->getID()]); BoundNodesTreeBuilder Builder; + CurMatchRAII RAII(*this, MP.second, Node); if (MP.first.matches(Node, this, &Builder)) { MatchVisitor Visitor(*this, ActiveASTContext, MP.second); Builder.visitMatches(&Visitor); @@ -919,6 +1049,7 @@ private: continue; } + CurMatchRAII RAII(*this, MP.second, DynNode); if (MP.first.matches(DynNode, this, &Builder)) { MatchVisitor Visitor(*this, ActiveASTContext, MP.second); Builder.visitMatches(&Visitor); @@ -1107,35 +1238,30 @@ private: // the aggregated bound nodes for each match. class MatchVisitor : public BoundNodesTreeBuilder::Visitor { struct CurBoundScope { - CurBoundScope(MatchASTVisitor &MV, const BoundNodes &BN) : MV(MV) { - assert(MV.CurMatched && !MV.CurBoundNodes); - MV.CurBoundNodes = &BN; + CurBoundScope(MatchASTVisitor::CurMatchData &State, const BoundNodes &BN) + : State(State) { + State.SetBoundNodes(BN); } - ~CurBoundScope() { MV.CurBoundNodes = nullptr; } + ~CurBoundScope() { State.clearBoundNodes(); } private: - MatchASTVisitor &MV; + MatchASTVisitor::CurMatchData &State; }; public: MatchVisitor(MatchASTVisitor &MV, ASTContext *Context, MatchFinder::MatchCallback *Callback) - : MV(MV), Context(Context), Callback(Callback) { - assert(!MV.CurMatched && !MV.CurBoundNodes); - MV.CurMatched = Callback; - } - - ~MatchVisitor() { MV.CurMatched = nullptr; } + : State(MV.CurMatchState), Context(Context), Callback(Callback) {} void visitMatch(const BoundNodes& BoundNodesView) override { TraversalKindScope RAII(*Context, Callback->getCheckTraversalKind()); - CurBoundScope RAII2(MV, BoundNodesView); + CurBoundScope RAII2(State, BoundNodesView); Callback->run(MatchFinder::MatchResult(BoundNodesView, Context)); } private: - MatchASTVisitor &MV; + MatchASTVisitor::CurMatchData &State; ASTContext* Context; MatchFinder::MatchCallback* Callback; }; diff --git a/clang/unittests/ASTMatchers/ASTMatchersInternalTest.cpp b/clang/unittests/ASTMatchers/ASTMatchersInternalTest.cpp index b86917c36dcb..6573461c9fc1 100644 --- a/clang/unittests/ASTMatchers/ASTMatchersInternalTest.cpp +++ b/clang/unittests/ASTMatchers/ASTMatchersInternalTest.cpp @@ -39,10 +39,24 @@ TEST(HasNameDeathTest, DiesOnEmptyPattern) { // FIXME: Figure out why back traces aren't being generated on clang builds on // windows. #if ENABLE_BACKTRACES && (!defined(_MSC_VER) || !defined(__clang__)) + +AST_MATCHER(Decl, causeCrash) { + abort(); + return true; +} + +TEST(MatcherCrashDeathTest, CrashOnMatcherDump) { + llvm::EnablePrettyStackTrace(); + auto Matcher = testing::HasSubstr( + "ASTMatcher: Matching '' against:\n\tFunctionDecl foo : " + ""); + ASSERT_DEATH(matches("void foo();", functionDecl(causeCrash())), Matcher); +} + template static void crashTestNodeDump(MatcherT Matcher, ArrayRef MatchedNodes, - StringRef Code) { + StringRef Against, StringRef Code) { llvm::EnablePrettyStackTrace(); MatchFinder Finder; @@ -58,7 +72,9 @@ static void crashTestNodeDump(MatcherT Matcher, ASSERT_DEATH(tooling::runToolOnCode( newFrontendActionFactory(&Finder)->create(), Code), testing::HasSubstr( - "ASTMatcher: Processing 'CrashTester'\nNo bound nodes")); + ("ASTMatcher: Processing 'CrashTester' against:\n\t" + + Against + "\nNo bound nodes") + .str())); } else { std::vector>> @@ -69,7 +85,9 @@ static void crashTestNodeDump(MatcherT Matcher, } auto CrashMatcher = testing::AllOf( testing::HasSubstr( - "ASTMatcher: Processing 'CrashTester'\n--- Bound Nodes Begin ---"), + ("ASTMatcher: Processing 'CrashTester' against:\n\t" + Against + + "\n--- Bound Nodes Begin ---") + .str()), testing::HasSubstr("--- Bound Nodes End ---"), testing::AllOfArray(Matchers)); @@ -79,7 +97,8 @@ static void crashTestNodeDump(MatcherT Matcher, } } TEST(MatcherCrashDeathTest, CrashOnCallbackDump) { - crashTestNodeDump(forStmt(), {}, "void foo() { for(;;); }"); + crashTestNodeDump(forStmt(), {}, "ForStmt : ", + "void foo() { for(;;); }"); crashTestNodeDump( forStmt(hasLoopInit(declStmt(hasSingleDecl( varDecl(hasType(qualType().bind("QT")), @@ -94,6 +113,7 @@ TEST(MatcherCrashDeathTest, CrashOnCallbackDump) { "IL - { IntegerLiteral : }", "QT - { QualType : int }", "T - { BuiltinType : int }", "VD - { VarDecl I : }"}, + "ForStmt : ", R"cpp( void foo() { for (int I = 0; I < 5; ++I) { @@ -106,12 +126,14 @@ TEST(MatcherCrashDeathTest, CrashOnCallbackDump) { {"Unnamed - { CXXRecordDecl (anonymous) : }", "Op+ - { CXXMethodDecl (anonymous struct)::operator+ : }"}, + "CXXRecordDecl (anonymous) : ", "struct { int operator+(int) const; } Unnamed;"); crashTestNodeDump( cxxRecordDecl(hasMethod(cxxConstructorDecl(isDefaulted()).bind("Ctor")), hasMethod(cxxDestructorDecl(isDefaulted()).bind("Dtor"))), {"Ctor - { CXXConstructorDecl Foo::Foo : }", "Dtor - { CXXDestructorDecl Foo::~Foo : }"}, + "CXXRecordDecl Foo : ", "struct Foo { Foo() = default; ~Foo() = default; };"); } #endif // ENABLE_BACKTRACES