diff --git a/clang/include/clang/AST/ASTNodeTraverser.h b/clang/include/clang/AST/ASTNodeTraverser.h --- a/clang/include/clang/AST/ASTNodeTraverser.h +++ b/clang/include/clang/AST/ASTNodeTraverser.h @@ -85,6 +85,9 @@ TraversalKind GetTraversalKind() const { return Traversal; } void Visit(const Decl *D) { + if (Traversal != TK_AsIs && D->isImplicit()) + return; + getNodeDelegate().AddChild([=] { getNodeDelegate().Visit(D); if (!D) @@ -144,7 +147,8 @@ if (isa(S) || isa(S)) return; - if (isa(S) && Traversal == TK_IgnoreUnlessSpelledInSource) + if (Traversal == TK_IgnoreUnlessSpelledInSource && + isa(S)) return; for (const Stmt *SubStmt : S->children()) @@ -185,6 +189,8 @@ } void Visit(const CXXCtorInitializer *Init) { + if (Traversal != TK_AsIs && !Init->isWritten()) + return; getNodeDelegate().AddChild([=] { getNodeDelegate().Visit(Init); Visit(Init->getInit()); @@ -401,6 +407,9 @@ if (const Expr *TRC = D->getTrailingRequiresClause()) Visit(TRC); + if (Traversal != TK_AsIs && D->isDefaulted()) + return; + if (const auto *C = dyn_cast(D)) for (const auto *I : C->inits()) Visit(I); @@ -417,6 +426,9 @@ } void VisitVarDecl(const VarDecl *D) { + if (Traversal != TK_AsIs && D->isCXXForRangeDecl()) + return; + if (D->hasInit()) Visit(D->getInit()); } @@ -717,6 +729,26 @@ Visit(CatchParam); } + void VisitCXXForRangeStmt(const CXXForRangeStmt *Node) { + if (Traversal != TK_AsIs) { + Visit(Node->getInit()); + Visit(Node->getLoopVariable()); + Visit(Node->getRangeInit()); + Visit(Node->getBody()); + } + } + + void VisitCallExpr(const CallExpr *Node) { + for (auto Child : + make_filter_range(Node->children(), [this](const Stmt *Child) { + if (Traversal == TK_AsIs) + return false; + return !isa(Child); + })) { + Visit(Child); + } + } + void VisitExpressionTemplateArgument(const TemplateArgument &TA) { Visit(TA.getAsExpr()); } diff --git a/clang/include/clang/ASTMatchers/ASTMatchersInternal.h b/clang/include/clang/ASTMatchers/ASTMatchersInternal.h --- a/clang/include/clang/ASTMatchers/ASTMatchersInternal.h +++ b/clang/include/clang/ASTMatchers/ASTMatchersInternal.h @@ -1060,7 +1060,7 @@ virtual ASTContext &getASTContext() const = 0; - virtual bool IsMatchingInTemplateInstantiationNotSpelledInSource() const = 0; + virtual bool IsMatchingInASTNodeNotSpelledInSource() const = 0; protected: virtual bool matchesChildOf(const DynTypedNode &Node, ASTContext &Ctx, diff --git a/clang/lib/ASTMatchers/ASTMatchFinder.cpp b/clang/lib/ASTMatchers/ASTMatchFinder.cpp --- a/clang/lib/ASTMatchers/ASTMatchFinder.cpp +++ b/clang/lib/ASTMatchers/ASTMatchFinder.cpp @@ -95,9 +95,12 @@ // matching the descendants. MatchChildASTVisitor(const DynTypedMatcher *Matcher, ASTMatchFinder *Finder, BoundNodesTreeBuilder *Builder, int MaxDepth, - TraversalKind Traversal, ASTMatchFinder::BindKind Bind) + TraversalKind Traversal, bool IgnoreImplicitChildren, + ASTMatchFinder::BindKind Bind) : Matcher(Matcher), Finder(Finder), Builder(Builder), CurrentDepth(0), - MaxDepth(MaxDepth), Traversal(Traversal), Bind(Bind), Matches(false) {} + MaxDepth(MaxDepth), Traversal(Traversal), + IgnoreImplicitChildren(IgnoreImplicitChildren), Bind(Bind), + Matches(false) {} // Returns true if a match is found in the subtree rooted at the // given AST node. This is done via a set of mutually recursive @@ -145,6 +148,12 @@ // They are public only to allow CRTP to work. They are *not *part // of the public API of this class. bool TraverseDecl(Decl *DeclNode) { + + if (DeclNode && DeclNode->isImplicit() && + Finder->getASTContext().getParentMapContext().getTraversalKind() == + TK_IgnoreUnlessSpelledInSource) + return baseTraverse(*DeclNode); + ScopedIncrement ScopedDepth(&CurrentDepth); return (DeclNode == nullptr) || traverse(*DeclNode); } @@ -178,6 +187,10 @@ Stmt *StmtToTraverse = getStmtToTraverse(StmtNode); if (!StmtToTraverse) return true; + + if (IgnoreImplicitChildren && isa(StmtNode)) + return true; + if (!match(*StmtToTraverse)) return false; return VisitorBase::TraverseStmt(StmtToTraverse, Queue); @@ -268,7 +281,7 @@ } bool shouldVisitTemplateInstantiations() const { return true; } - bool shouldVisitImplicitCode() const { return true; } + bool shouldVisitImplicitCode() const { return !IgnoreImplicitChildren; } private: // Used for updating the depth during traversal. @@ -363,6 +376,7 @@ int CurrentDepth; const int MaxDepth; const TraversalKind Traversal; + const bool IgnoreImplicitChildren; const ASTMatchFinder::BindKind Bind; bool Matches; }; @@ -500,19 +514,21 @@ const DynTypedMatcher &Matcher, BoundNodesTreeBuilder *Builder, int MaxDepth, TraversalKind Traversal, BindKind Bind) { - bool ScopedTraversal = TraversingTemplateInstantiationNotSpelledInSource; + bool ScopedTraversal = TraversingASTNodeNotSpelledInSource || + TraversingASTChildrenNotSpelledInSource; - if (const auto *CTSD = Node.get()) { - int SK = CTSD->getSpecializationKind(); - if (SK == TSK_ExplicitInstantiationDeclaration || - SK == TSK_ExplicitInstantiationDefinition) + bool IgnoreImplicitChildren = false; + + if (getASTContext().getParentMapContext().getTraversalKind() != TK_AsIs) { + IgnoreImplicitChildren = true; + if (Node.get()) ScopedTraversal = true; } - TemplateInstantiationNotSpelledInSourceScope RAII(this, ScopedTraversal); + ASTNodeNotSpelledInSourceScope RAII(this, ScopedTraversal); - MatchChildASTVisitor Visitor( - &Matcher, this, Builder, MaxDepth, Traversal, Bind); + MatchChildASTVisitor Visitor(&Matcher, this, Builder, MaxDepth, Traversal, + IgnoreImplicitChildren, Bind); return Visitor.findMatch(Node); } @@ -595,38 +611,53 @@ bool shouldVisitTemplateInstantiations() const { return true; } bool shouldVisitImplicitCode() const { return true; } - bool IsMatchingInTemplateInstantiationNotSpelledInSource() const override { - return TraversingTemplateInstantiationNotSpelledInSource; + bool IsMatchingInASTNodeNotSpelledInSource() const override { + return TraversingASTNodeNotSpelledInSource; } bool TraverseTemplateInstantiations(ClassTemplateDecl *D) { - TemplateInstantiationNotSpelledInSourceScope RAII(this, true); + ASTNodeNotSpelledInSourceScope RAII(this, true); return RecursiveASTVisitor::TraverseTemplateInstantiations( D); } bool TraverseTemplateInstantiations(VarTemplateDecl *D) { - TemplateInstantiationNotSpelledInSourceScope RAII(this, true); + ASTNodeNotSpelledInSourceScope RAII(this, true); return RecursiveASTVisitor::TraverseTemplateInstantiations( D); } bool TraverseTemplateInstantiations(FunctionTemplateDecl *D) { - TemplateInstantiationNotSpelledInSourceScope RAII(this, true); + ASTNodeNotSpelledInSourceScope RAII(this, true); return RecursiveASTVisitor::TraverseTemplateInstantiations( D); } private: - bool TraversingTemplateInstantiationNotSpelledInSource = false; + bool TraversingASTNodeNotSpelledInSource = false; + bool TraversingASTChildrenNotSpelledInSource = false; - struct TemplateInstantiationNotSpelledInSourceScope { - TemplateInstantiationNotSpelledInSourceScope(MatchASTVisitor *V, bool B) - : MV(V), MB(V->TraversingTemplateInstantiationNotSpelledInSource) { - V->TraversingTemplateInstantiationNotSpelledInSource = B; + struct ASTNodeNotSpelledInSourceScope { + ASTNodeNotSpelledInSourceScope(MatchASTVisitor *V, bool B) + : MV(V), MB(V->TraversingASTNodeNotSpelledInSource) { + V->TraversingASTNodeNotSpelledInSource = B; } - ~TemplateInstantiationNotSpelledInSourceScope() { - MV->TraversingTemplateInstantiationNotSpelledInSource = MB; + ~ASTNodeNotSpelledInSourceScope() { + MV->TraversingASTNodeNotSpelledInSource = MB; + } + + private: + MatchASTVisitor *MV; + bool MB; + }; + + struct ASTChildrenNotSpelledInSource { + ASTChildrenNotSpelledInSource(MatchASTVisitor *V, bool B) + : MV(V), MB(V->TraversingASTChildrenNotSpelledInSource) { + V->TraversingASTChildrenNotSpelledInSource = B; + } + ~ASTChildrenNotSpelledInSource() { + MV->TraversingASTChildrenNotSpelledInSource = MB; } private: @@ -1053,6 +1084,24 @@ if (!DeclNode) { return true; } + + auto ScopedTraversal = + TraversingASTNodeNotSpelledInSource || DeclNode->isImplicit(); + auto ScopedChildren = TraversingASTChildrenNotSpelledInSource; + + if (const auto *CTSD = dyn_cast(DeclNode)) { + auto SK = CTSD->getSpecializationKind(); + if (SK == TSK_ExplicitInstantiationDeclaration || + SK == TSK_ExplicitInstantiationDefinition) + ScopedChildren = true; + } else if (const auto *FD = dyn_cast(DeclNode)) { + if (FD->isDefaulted()) + ScopedChildren = true; + } + + ASTNodeNotSpelledInSourceScope RAII1(this, ScopedTraversal); + ASTChildrenNotSpelledInSource RAII2(this, ScopedChildren); + match(*DeclNode); return RecursiveASTVisitor::TraverseDecl(DeclNode); } @@ -1061,6 +1110,10 @@ if (!StmtNode) { return true; } + auto ScopedTraversal = TraversingASTNodeNotSpelledInSource || + TraversingASTChildrenNotSpelledInSource; + + ASTNodeNotSpelledInSourceScope RAII(this, ScopedTraversal); match(*StmtNode); return RecursiveASTVisitor::TraverseStmt(StmtNode, Queue); } @@ -1106,6 +1159,14 @@ if (!CtorInit) return true; + auto ScopedTraversal = TraversingASTNodeNotSpelledInSource || + TraversingASTChildrenNotSpelledInSource; + + if (!CtorInit->isWritten()) + ScopedTraversal = true; + + ASTNodeNotSpelledInSourceScope RAII1(this, ScopedTraversal); + match(*CtorInit); return RecursiveASTVisitor::TraverseConstructorInitializer( diff --git a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp --- a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp +++ b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp @@ -286,7 +286,7 @@ if (Finder->getASTContext().getParentMapContext().getTraversalKind() == TK_IgnoreUnlessSpelledInSource && - Finder->IsMatchingInTemplateInstantiationNotSpelledInSource()) + Finder->IsMatchingInASTNodeNotSpelledInSource()) return false; auto N = @@ -311,7 +311,7 @@ if (Finder->getASTContext().getParentMapContext().getTraversalKind() == TK_IgnoreUnlessSpelledInSource && - Finder->IsMatchingInTemplateInstantiationNotSpelledInSource()) + Finder->IsMatchingInASTNodeNotSpelledInSource()) return false; auto N = diff --git a/clang/unittests/AST/ASTTraverserTest.cpp b/clang/unittests/AST/ASTTraverserTest.cpp --- a/clang/unittests/AST/ASTTraverserTest.cpp +++ b/clang/unittests/AST/ASTTraverserTest.cpp @@ -35,6 +35,10 @@ } void Visit(const Stmt *S) { + if (!S) { + OS << "<<>>"; + return; + } OS << S->getStmtClassName(); if (auto *E = dyn_cast(S)) { OS << " '" << E->getDecl()->getDeclName() << "'"; @@ -51,7 +55,14 @@ OS << C->getCommentKindName(); } - void Visit(const CXXCtorInitializer *Init) { OS << "CXXCtorInitializer"; } + void Visit(const CXXCtorInitializer *Init) { + OS << "CXXCtorInitializer"; + if (const auto *F = Init->getAnyMember()) { + OS << " '" << F->getNameAsString() << "'"; + } else if (auto const *TSI = Init->getTypeSourceInfo()) { + OS << " '" << TSI->getType().getAsString() << "'"; + } + } void Visit(const Attr *A) { switch (A->getKind()) { @@ -226,7 +237,7 @@ verifyWithDynNode(Init, R"cpp( -CXXCtorInitializer +CXXCtorInitializer 'm_number' `-IntegerLiteral )cpp"); @@ -1050,6 +1061,438 @@ } } +TEST(Traverse, IgnoreUnlessSpelledInSourceImplicit) { + { + auto AST = buildASTFromCode(R"cpp( +int i = 0; +)cpp"); + const auto *TUDecl = AST->getASTContext().getTranslationUnitDecl(); + +#if _WIN32 + EXPECT_EQ(dumpASTString(TK_AsIs, TUDecl), + R"cpp( +TranslationUnitDecl +|-CXXRecordDecl '_GUID' +| `-TypeVisibilityAttr +|-TypedefDecl '__int128_t' +| `-BuiltinType +|-TypedefDecl '__uint128_t' +| `-BuiltinType +|-TypedefDecl '__NSConstantString' +| `-RecordType +|-CXXRecordDecl 'type_info' +| `-TypeVisibilityAttr +|-TypedefDecl 'size_t' +| `-BuiltinType +|-TypedefDecl '__builtin_ms_va_list' +| `-PointerType +| `-BuiltinType +|-TypedefDecl '__builtin_va_list' +| `-PointerType +| `-BuiltinType +`-VarDecl 'i' + `-IntegerLiteral +)cpp"); +#else + EXPECT_EQ(dumpASTString(TK_AsIs, TUDecl), + R"cpp( +TranslationUnitDecl +|-TypedefDecl '__int128_t' +| `-BuiltinType +|-TypedefDecl '__uint128_t' +| `-BuiltinType +|-TypedefDecl '__NSConstantString' +| `-RecordType +|-TypedefDecl '__builtin_ms_va_list' +| `-PointerType +| `-BuiltinType +|-TypedefDecl '__builtin_va_list' +| `-ConstantArrayType +| `-RecordType +`-VarDecl 'i' + `-IntegerLiteral +)cpp"); +#endif + + EXPECT_EQ(dumpASTString(TK_IgnoreUnlessSpelledInSource, TUDecl), + R"cpp( +TranslationUnitDecl +`-VarDecl 'i' + `-IntegerLiteral +)cpp"); + } + + auto AST2 = buildASTFromCodeWithArgs(R"cpp( +struct Simple { +}; +struct Other { +}; + +struct Record : Simple, Other { + Record() : Simple(), m_i(42) {} +private: + int m_i; + int m_i2 = 42; + Simple m_s; +}; + +struct NonTrivial { + NonTrivial() {} + NonTrivial(NonTrivial&) {} + NonTrivial& operator=(NonTrivial&) { return *this; } + + ~NonTrivial() {} +}; + +struct ContainsArray { + NonTrivial arr[2]; + int irr[2]; + ContainsArray& operator=(ContainsArray &) = default; +}; + +void copyIt() +{ + ContainsArray ca; + ContainsArray ca2; + ca2 = ca; +} + +void forLoop() +{ + int arr[2]; + for (auto i : arr) + { + + } + for (auto& a = arr; auto i : a) + { + + } +} + +struct DefaultedAndDeleted { + NonTrivial nt; + DefaultedAndDeleted() = default; + ~DefaultedAndDeleted() = default; + DefaultedAndDeleted(DefaultedAndDeleted &) = default; + DefaultedAndDeleted& operator=(DefaultedAndDeleted &) = default; + DefaultedAndDeleted(DefaultedAndDeleted &&) = delete; + DefaultedAndDeleted& operator=(DefaultedAndDeleted &&) = delete; +}; + +void copyIt2() +{ + DefaultedAndDeleted ca; + DefaultedAndDeleted ca2; + ca2 = ca; +} + +void hasDefaultArg(int i, int j = 0) +{ +} +void callDefaultArg() +{ + hasDefaultArg(42); +} +)cpp", + {"-std=c++20"}); + + { + auto BN = ast_matchers::match( + cxxRecordDecl(hasName("Record"), unless(isImplicit())).bind("rec"), + AST2->getASTContext()); + EXPECT_EQ(BN.size(), 1u); + + EXPECT_EQ(dumpASTString(TK_AsIs, BN[0].getNodeAs("rec")), + R"cpp( +CXXRecordDecl 'Record' +|-CXXRecordDecl 'Record' +|-CXXConstructorDecl 'Record' +| |-CXXCtorInitializer 'struct Simple' +| | `-CXXConstructExpr +| |-CXXCtorInitializer 'struct Other' +| | `-CXXConstructExpr +| |-CXXCtorInitializer 'm_i' +| | `-IntegerLiteral +| |-CXXCtorInitializer 'm_i2' +| | `-CXXDefaultInitExpr +| |-CXXCtorInitializer 'm_s' +| | `-CXXConstructExpr +| `-CompoundStmt +|-AccessSpecDecl +|-FieldDecl 'm_i' +|-FieldDecl 'm_i2' +| `-IntegerLiteral +`-FieldDecl 'm_s' +)cpp"); + + EXPECT_EQ(dumpASTString(TK_IgnoreUnlessSpelledInSource, + BN[0].getNodeAs("rec")), + R"cpp( +CXXRecordDecl 'Record' +|-CXXConstructorDecl 'Record' +| |-CXXCtorInitializer 'struct Simple' +| | `-CXXConstructExpr +| |-CXXCtorInitializer 'm_i' +| | `-IntegerLiteral +| `-CompoundStmt +|-AccessSpecDecl +|-FieldDecl 'm_i' +|-FieldDecl 'm_i2' +| `-IntegerLiteral +`-FieldDecl 'm_s' +)cpp"); + } + { + auto BN = ast_matchers::match( + cxxRecordDecl(hasName("ContainsArray"), unless(isImplicit())) + .bind("rec"), + AST2->getASTContext()); + EXPECT_EQ(BN.size(), 1u); + + EXPECT_EQ(dumpASTString(TK_AsIs, BN[0].getNodeAs("rec")), + R"cpp( +CXXRecordDecl 'ContainsArray' +|-CXXRecordDecl 'ContainsArray' +|-FieldDecl 'arr' +|-FieldDecl 'irr' +|-CXXMethodDecl 'operator=' +| |-ParmVarDecl '' +| `-CompoundStmt +| |-ForStmt +| | |-DeclStmt +| | | `-VarDecl '__i0' +| | | `-IntegerLiteral +| | |-<<>> +| | |-BinaryOperator +| | | |-ImplicitCastExpr +| | | | `-DeclRefExpr '__i0' +| | | `-IntegerLiteral +| | |-UnaryOperator +| | | `-DeclRefExpr '__i0' +| | `-CXXMemberCallExpr +| | |-MemberExpr +| | | `-ArraySubscriptExpr +| | | |-ImplicitCastExpr +| | | | `-MemberExpr +| | | | `-CXXThisExpr +| | | `-ImplicitCastExpr +| | | `-DeclRefExpr '__i0' +| | `-ArraySubscriptExpr +| | |-ImplicitCastExpr +| | | `-MemberExpr +| | | `-DeclRefExpr '' +| | `-ImplicitCastExpr +| | `-DeclRefExpr '__i0' +| |-CallExpr +| | |-ImplicitCastExpr +| | | `-DeclRefExpr '__builtin_memcpy' +| | |-ImplicitCastExpr +| | | `-UnaryOperator +| | | `-MemberExpr +| | | `-CXXThisExpr +| | |-ImplicitCastExpr +| | | `-UnaryOperator +| | | `-MemberExpr +| | | `-DeclRefExpr '' +| | `-IntegerLiteral +| `-ReturnStmt +| `-UnaryOperator +| `-CXXThisExpr +|-CXXConstructorDecl 'ContainsArray' +| `-ParmVarDecl '' +|-CXXDestructorDecl '~ContainsArray' +| `-CompoundStmt +`-CXXConstructorDecl 'ContainsArray' + |-CXXCtorInitializer 'arr' + | `-CXXConstructExpr + `-CompoundStmt +)cpp"); + + EXPECT_EQ(dumpASTString(TK_IgnoreUnlessSpelledInSource, + BN[0].getNodeAs("rec")), + R"cpp( +CXXRecordDecl 'ContainsArray' +|-FieldDecl 'arr' +|-FieldDecl 'irr' +`-CXXMethodDecl 'operator=' + `-ParmVarDecl '' +)cpp"); + } + { + auto BN = ast_matchers::match(functionDecl(hasName("forLoop")).bind("func"), + AST2->getASTContext()); + EXPECT_EQ(BN.size(), 1u); + + EXPECT_EQ(dumpASTString(TK_AsIs, BN[0].getNodeAs("func")), + R"cpp( +FunctionDecl 'forLoop' +`-CompoundStmt + |-DeclStmt + | `-VarDecl 'arr' + |-CXXForRangeStmt + | |-<<>> + | |-DeclStmt + | | `-VarDecl '__range1' + | | `-DeclRefExpr 'arr' + | |-DeclStmt + | | `-VarDecl '__begin1' + | | `-ImplicitCastExpr + | | `-DeclRefExpr '__range1' + | |-DeclStmt + | | `-VarDecl '__end1' + | | `-BinaryOperator + | | |-ImplicitCastExpr + | | | `-DeclRefExpr '__range1' + | | `-IntegerLiteral + | |-BinaryOperator + | | |-ImplicitCastExpr + | | | `-DeclRefExpr '__begin1' + | | `-ImplicitCastExpr + | | `-DeclRefExpr '__end1' + | |-UnaryOperator + | | `-DeclRefExpr '__begin1' + | |-DeclStmt + | | `-VarDecl 'i' + | | `-ImplicitCastExpr + | | `-UnaryOperator + | | `-ImplicitCastExpr + | | `-DeclRefExpr '__begin1' + | `-CompoundStmt + `-CXXForRangeStmt + |-DeclStmt + | `-VarDecl 'a' + | `-DeclRefExpr 'arr' + |-DeclStmt + | `-VarDecl '__range1' + | `-DeclRefExpr 'a' + |-DeclStmt + | `-VarDecl '__begin1' + | `-ImplicitCastExpr + | `-DeclRefExpr '__range1' + |-DeclStmt + | `-VarDecl '__end1' + | `-BinaryOperator + | |-ImplicitCastExpr + | | `-DeclRefExpr '__range1' + | `-IntegerLiteral + |-BinaryOperator + | |-ImplicitCastExpr + | | `-DeclRefExpr '__begin1' + | `-ImplicitCastExpr + | `-DeclRefExpr '__end1' + |-UnaryOperator + | `-DeclRefExpr '__begin1' + |-DeclStmt + | `-VarDecl 'i' + | `-ImplicitCastExpr + | `-UnaryOperator + | `-ImplicitCastExpr + | `-DeclRefExpr '__begin1' + `-CompoundStmt +)cpp"); + + EXPECT_EQ(dumpASTString(TK_IgnoreUnlessSpelledInSource, + BN[0].getNodeAs("func")), + R"cpp( +FunctionDecl 'forLoop' +`-CompoundStmt + |-DeclStmt + | `-VarDecl 'arr' + |-CXXForRangeStmt + | |-<<>> + | |-VarDecl 'i' + | |-DeclRefExpr 'arr' + | `-CompoundStmt + `-CXXForRangeStmt + |-DeclStmt + | `-VarDecl 'a' + | `-DeclRefExpr 'arr' + |-VarDecl 'i' + |-DeclRefExpr 'a' + `-CompoundStmt +)cpp"); + } + { + auto BN = ast_matchers::match( + cxxRecordDecl(hasName("DefaultedAndDeleted"), unless(isImplicit())) + .bind("rec"), + AST2->getASTContext()); + EXPECT_EQ(BN.size(), 1u); + + EXPECT_EQ(dumpASTString(TK_AsIs, BN[0].getNodeAs("rec")), + R"cpp( +CXXRecordDecl 'DefaultedAndDeleted' +|-CXXRecordDecl 'DefaultedAndDeleted' +|-FieldDecl 'nt' +|-CXXConstructorDecl 'DefaultedAndDeleted' +| |-CXXCtorInitializer 'nt' +| | `-CXXConstructExpr +| `-CompoundStmt +|-CXXDestructorDecl '~DefaultedAndDeleted' +| `-CompoundStmt +|-CXXConstructorDecl 'DefaultedAndDeleted' +| `-ParmVarDecl '' +|-CXXMethodDecl 'operator=' +| |-ParmVarDecl '' +| `-CompoundStmt +| |-CXXMemberCallExpr +| | |-MemberExpr +| | | `-MemberExpr +| | | `-CXXThisExpr +| | `-MemberExpr +| | `-DeclRefExpr '' +| `-ReturnStmt +| `-UnaryOperator +| `-CXXThisExpr +|-CXXConstructorDecl 'DefaultedAndDeleted' +| `-ParmVarDecl '' +`-CXXMethodDecl 'operator=' + `-ParmVarDecl '' +)cpp"); + + EXPECT_EQ(dumpASTString(TK_IgnoreUnlessSpelledInSource, + BN[0].getNodeAs("rec")), + R"cpp( +CXXRecordDecl 'DefaultedAndDeleted' +|-FieldDecl 'nt' +|-CXXConstructorDecl 'DefaultedAndDeleted' +|-CXXDestructorDecl '~DefaultedAndDeleted' +|-CXXConstructorDecl 'DefaultedAndDeleted' +| `-ParmVarDecl '' +|-CXXMethodDecl 'operator=' +| `-ParmVarDecl '' +|-CXXConstructorDecl 'DefaultedAndDeleted' +| `-ParmVarDecl '' +`-CXXMethodDecl 'operator=' + `-ParmVarDecl '' +)cpp"); + } + { + auto BN = ast_matchers::match( + callExpr(callee(functionDecl(hasName("hasDefaultArg")))) + .bind("funcCall"), + AST2->getASTContext()); + EXPECT_EQ(BN.size(), 1u); + + EXPECT_EQ(dumpASTString(TK_AsIs, BN[0].getNodeAs("funcCall")), + R"cpp( +CallExpr +|-ImplicitCastExpr +| `-DeclRefExpr 'hasDefaultArg' +|-IntegerLiteral +`-CXXDefaultArgExpr +)cpp"); + EXPECT_EQ(dumpASTString(TK_IgnoreUnlessSpelledInSource, + BN[0].getNodeAs("funcCall")), + R"cpp( +CallExpr +|-DeclRefExpr 'hasDefaultArg' +`-IntegerLiteral +)cpp"); + } +} + TEST(Traverse, IgnoreUnlessSpelledInSourceTemplateInstantiations) { auto AST = buildASTFromCode(R"cpp( @@ -1108,7 +1551,6 @@ ClassTemplateDecl 'TemplStruct' |-TemplateTypeParmDecl 'T' `-CXXRecordDecl 'TemplStruct' - |-CXXRecordDecl 'TemplStruct' |-CXXConstructorDecl 'TemplStruct' | `-CompoundStmt |-CXXDestructorDecl '~TemplStruct' @@ -1216,7 +1658,6 @@ ClassTemplateSpecializationDecl 'TemplStruct' |-TemplateArgument type _Bool | `-BuiltinType -|-CXXRecordDecl 'TemplStruct' |-CXXConstructorDecl 'TemplStruct' | `-CompoundStmt |-CXXDestructorDecl '~TemplStruct' diff --git a/clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp b/clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp --- a/clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp +++ b/clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp @@ -2331,6 +2331,255 @@ } } +TEST(Traversal, traverseNoImplicit) { + StringRef Code = R"cpp( +struct NonTrivial { + NonTrivial() {} + NonTrivial(NonTrivial&) {} + NonTrivial& operator=(NonTrivial&) { return *this; } + + ~NonTrivial() {} +}; + +struct NoSpecialMethods { + NonTrivial nt; +}; + +struct ContainsArray { + NonTrivial arr[2]; + ContainsArray& operator=(ContainsArray &other) = default; +}; + +void copyIt() +{ + NoSpecialMethods nc1; + NoSpecialMethods nc2(nc1); + nc2 = nc1; + + ContainsArray ca; + ContainsArray ca2; + ca2 = ca; +} + +struct HasCtorInits : NoSpecialMethods, NonTrivial +{ + int m_i; + NonTrivial m_nt; + HasCtorInits() : NoSpecialMethods(), m_i(42) {} +}; + +)cpp"; + { + auto M = cxxRecordDecl(hasName("NoSpecialMethods"), + has(cxxRecordDecl(hasName("NoSpecialMethods")))); + EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M))); + EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M))); + + M = cxxRecordDecl(hasName("NoSpecialMethods"), + has(cxxConstructorDecl(isCopyConstructor()))); + EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M))); + EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M))); + + M = cxxRecordDecl(hasName("NoSpecialMethods"), + has(cxxMethodDecl(isCopyAssignmentOperator()))); + EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M))); + EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M))); + + M = cxxRecordDecl(hasName("NoSpecialMethods"), + has(cxxConstructorDecl(isDefaultConstructor()))); + EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M))); + EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M))); + + M = cxxRecordDecl(hasName("NoSpecialMethods"), has(cxxDestructorDecl())); + EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M))); + EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M))); + } + { + // Compiler generates a forStmt to copy the array + EXPECT_TRUE(matches(Code, traverse(TK_AsIs, forStmt()))); + EXPECT_FALSE( + matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, forStmt()))); + } + { + // The defaulted method declaration can be matched, but not its + // definition, in IgnoreUnlessSpelledInSource mode + auto MDecl = cxxMethodDecl(ofClass(cxxRecordDecl(hasName("ContainsArray"))), + isCopyAssignmentOperator(), isDefaulted()); + + EXPECT_TRUE(matches(Code, traverse(TK_AsIs, MDecl))); + EXPECT_TRUE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, MDecl))); + + auto MDef = cxxMethodDecl(MDecl, has(compoundStmt())); + + EXPECT_TRUE(matches(Code, traverse(TK_AsIs, MDef))); + EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, MDef))); + + // The parameter of the defaulted method can still be matched. + auto MParm = + cxxMethodDecl(MDecl, hasParameter(0, parmVarDecl(hasName("other")))); + + EXPECT_TRUE(matches(Code, traverse(TK_AsIs, MParm))); + EXPECT_TRUE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, MParm))); + } + { + auto M = + cxxConstructorDecl(hasName("HasCtorInits"), + has(cxxCtorInitializer(forField(hasName("m_i"))))); + EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M))); + EXPECT_TRUE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M))); + } + { + auto M = + cxxConstructorDecl(hasName("HasCtorInits"), + has(cxxCtorInitializer(forField(hasName("m_nt"))))); + EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M))); + EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M))); + } + { + auto M = cxxConstructorDecl( + hasName("HasCtorInits"), + has(cxxCtorInitializer(withInitializer(cxxConstructExpr(hasDeclaration( + cxxConstructorDecl(hasName("NoSpecialMethods")))))))); + EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M))); + EXPECT_TRUE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M))); + } + { + auto M = cxxConstructorDecl( + hasName("HasCtorInits"), + has(cxxCtorInitializer(withInitializer(cxxConstructExpr( + hasDeclaration(cxxConstructorDecl(hasName("NonTrivial")))))))); + EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M))); + EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M))); + } + { + auto M = cxxCtorInitializer(forField(hasName("m_nt"))); + EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M))); + EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M))); + } + + Code = R"cpp( + void rangeFor() + { + int arr[2]; + for (auto i : arr) + { + + } + } + )cpp"; + { + auto M = cxxForRangeStmt(has(binaryOperator(hasOperatorName("!=")))); + EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M))); + EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M))); + } + { + auto M = + cxxForRangeStmt(hasDescendant(binaryOperator(hasOperatorName("+")))); + EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M))); + EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M))); + } + { + auto M = + cxxForRangeStmt(hasDescendant(unaryOperator(hasOperatorName("++")))); + EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M))); + EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M))); + } + { + auto M = cxxForRangeStmt(has(declStmt())); + EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M))); + EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M))); + } + { + auto M = + cxxForRangeStmt(hasLoopVariable(varDecl(hasName("i"))), + hasRangeInit(declRefExpr(to(varDecl(hasName("arr")))))); + EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M))); + EXPECT_TRUE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M))); + } + { + auto M = cxxForRangeStmt(unless(hasInitStatement(stmt()))); + EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M))); + EXPECT_TRUE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M))); + } + + Code = R"cpp( + void rangeFor() + { + int arr[2]; + for (auto& a = arr; auto i : a) + { + + } + } + )cpp"; + { + auto M = cxxForRangeStmt(has(binaryOperator(hasOperatorName("!=")))); + EXPECT_TRUE( + matchesConditionally(Code, traverse(TK_AsIs, M), true, {"-std=c++20"})); + EXPECT_FALSE( + matchesConditionally(Code, traverse(TK_IgnoreUnlessSpelledInSource, M), + true, {"-std=c++20"})); + } + { + auto M = + cxxForRangeStmt(hasDescendant(binaryOperator(hasOperatorName("+")))); + EXPECT_TRUE( + matchesConditionally(Code, traverse(TK_AsIs, M), true, {"-std=c++20"})); + EXPECT_FALSE( + matchesConditionally(Code, traverse(TK_IgnoreUnlessSpelledInSource, M), + true, {"-std=c++20"})); + } + { + auto M = + cxxForRangeStmt(hasDescendant(unaryOperator(hasOperatorName("++")))); + EXPECT_TRUE( + matchesConditionally(Code, traverse(TK_AsIs, M), true, {"-std=c++20"})); + EXPECT_FALSE( + matchesConditionally(Code, traverse(TK_IgnoreUnlessSpelledInSource, M), + true, {"-std=c++20"})); + } + { + auto M = cxxForRangeStmt(has(declStmt())); + EXPECT_TRUE( + matchesConditionally(Code, traverse(TK_AsIs, M), true, {"-std=c++20"})); + EXPECT_FALSE( + matchesConditionally(Code, traverse(TK_IgnoreUnlessSpelledInSource, M), + true, {"-std=c++20"})); + } + { + auto M = cxxForRangeStmt( + hasInitStatement(declStmt(hasSingleDecl(varDecl( + hasName("a"), + hasInitializer(declRefExpr(to(varDecl(hasName("arr"))))))))), + hasLoopVariable(varDecl(hasName("i"))), + hasRangeInit(declRefExpr(to(varDecl(hasName("a")))))); + EXPECT_TRUE( + matchesConditionally(Code, traverse(TK_AsIs, M), true, {"-std=c++20"})); + EXPECT_TRUE( + matchesConditionally(Code, traverse(TK_IgnoreUnlessSpelledInSource, M), + true, {"-std=c++20"})); + } + Code = R"cpp( +void hasDefaultArg(int i, int j = 0) +{ +} +void callDefaultArg() +{ + hasDefaultArg(42); +} +)cpp"; + { + auto M = callExpr(has(integerLiteral(equals(42)))); + EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M))); + EXPECT_TRUE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M))); + } + { + auto M = callExpr(has(cxxDefaultArgExpr())); + EXPECT_TRUE(matches(Code, traverse(TK_AsIs, M))); + EXPECT_FALSE(matches(Code, traverse(TK_IgnoreUnlessSpelledInSource, M))); + } +} + template bool matcherTemplateWithBinding(StringRef Code, const MatcherT &M) { return matchAndVerifyResultTrue( diff --git a/clang/unittests/Tooling/TransformerTest.cpp b/clang/unittests/Tooling/TransformerTest.cpp --- a/clang/unittests/Tooling/TransformerTest.cpp +++ b/clang/unittests/Tooling/TransformerTest.cpp @@ -132,6 +132,13 @@ compareSnippets(Expected, rewrite(Input)); } + template void testRuleFailure(R Rule, StringRef Input) { + Transformers.push_back( + std::make_unique(std::move(Rule), consumer())); + Transformers.back()->registerMatchers(&MatchFinder); + ASSERT_FALSE(rewrite(Input)) << "Expected failure to rewrite code"; + } + // Transformers are referenced by MatchFinder. std::vector> Transformers; clang::ast_matchers::MatchFinder MatchFinder; @@ -1067,6 +1074,179 @@ EXPECT_EQ(ErrorCount, 0); } +TEST_F(TransformerTest, ImplicitNodes_ConstructorDecl) { + + std::string OtherStructPrefix = R"cpp( +struct Other { +)cpp"; + std::string OtherStructSuffix = "};"; + + std::string CopyableStructName = "struct Copyable"; + std::string BrokenStructName = "struct explicit Copyable"; + + std::string CodeSuffix = R"cpp( +{ + Other m_i; + Copyable(); +}; +)cpp"; + + std::string CopyCtor = "Other(const Other&) = default;"; + std::string ExplicitCopyCtor = "explicit Other(const Other&) = default;"; + std::string BrokenExplicitCopyCtor = + "explicit explicit explicit Other(const Other&) = default;"; + + std::string RewriteInput = OtherStructPrefix + CopyCtor + OtherStructSuffix + + CopyableStructName + CodeSuffix; + std::string ExpectedRewriteOutput = OtherStructPrefix + ExplicitCopyCtor + + OtherStructSuffix + CopyableStructName + + CodeSuffix; + std::string BrokenRewriteOutput = OtherStructPrefix + BrokenExplicitCopyCtor + + OtherStructSuffix + BrokenStructName + + CodeSuffix; + + auto MatchedRecord = + cxxConstructorDecl(isCopyConstructor()).bind("copyConstructor"); + + auto RewriteRule = + changeTo(before(node("copyConstructor")), cat("explicit ")); + + testRule(makeRule(traverse(TK_IgnoreUnlessSpelledInSource, MatchedRecord), + RewriteRule), + RewriteInput, ExpectedRewriteOutput); + + testRule(makeRule(traverse(TK_AsIs, MatchedRecord), RewriteRule), + RewriteInput, BrokenRewriteOutput); +} + +TEST_F(TransformerTest, ImplicitNodes_RangeFor) { + + std::string CodePrefix = R"cpp( +struct Container +{ + int* begin() const; + int* end() const; + int* cbegin() const; + int* cend() const; +}; + +void foo() +{ + const Container c; +)cpp"; + + std::string BeginCallBefore = " c.begin();"; + std::string BeginCallAfter = " c.cbegin();"; + + std::string ForLoop = "for (auto i : c)"; + std::string BrokenForLoop = "for (auto i :.cbegin() c)"; + + std::string CodeSuffix = R"cpp( + { + } +} +)cpp"; + + std::string RewriteInput = + CodePrefix + BeginCallBefore + ForLoop + CodeSuffix; + std::string ExpectedRewriteOutput = + CodePrefix + BeginCallAfter + ForLoop + CodeSuffix; + std::string BrokenRewriteOutput = + CodePrefix + BeginCallAfter + BrokenForLoop + CodeSuffix; + + auto MatchedRecord = + cxxMemberCallExpr(on(expr(hasType(qualType(isConstQualified(), + hasDeclaration(cxxRecordDecl( + hasName("Container")))))) + .bind("callTarget")), + callee(cxxMethodDecl(hasName("begin")))) + .bind("constBeginCall"); + + auto RewriteRule = + changeTo(node("constBeginCall"), cat(name("callTarget"), ".cbegin()")); + + testRule(makeRule(traverse(TK_IgnoreUnlessSpelledInSource, MatchedRecord), + RewriteRule), + RewriteInput, ExpectedRewriteOutput); + + testRule(makeRule(traverse(TK_AsIs, MatchedRecord), RewriteRule), + RewriteInput, BrokenRewriteOutput); +} + +TEST_F(TransformerTest, ImplicitNodes_ForStmt) { + + std::string CodePrefix = R"cpp( +struct NonTrivial { + NonTrivial() {} + NonTrivial(NonTrivial&) {} + NonTrivial& operator=(NonTrivial const&) { return *this; } + + ~NonTrivial() {} +}; + +struct ContainsArray { + NonTrivial arr[2]; + ContainsArray& operator=(ContainsArray const&) = default; +}; + +void testIt() +{ + ContainsArray ca1; + ContainsArray ca2; + ca2 = ca1; +)cpp"; + + auto CodeSuffix = "}"; + + auto LoopBody = R"cpp( + { + + } +)cpp"; + + auto RawLoop = "for (auto i = 0; i != 5; ++i)"; + + auto RangeLoop = "for (auto i : boost::irange(5))"; + + // Expect to rewrite the raw loop to the ranged loop. + // This works in TK_IgnoreUnlessSpelledInSource mode, but TK_AsIs + // mode also matches the hidden for loop generated in the copy assignment + // operator of ContainsArray. Transformer then fails to transform the code at + // all. + + auto RewriteInput = + CodePrefix + RawLoop + LoopBody + RawLoop + LoopBody + CodeSuffix; + + auto RewriteOutput = + CodePrefix + RangeLoop + LoopBody + RangeLoop + LoopBody + CodeSuffix; + { + auto MatchedLoop = forStmt( + has(declStmt( + hasSingleDecl(varDecl(hasInitializer(integerLiteral(equals(0)))) + .bind("loopVar")))), + has(binaryOperator(hasOperatorName("!="), + hasLHS(ignoringImplicit(declRefExpr( + to(varDecl(equalsBoundNode("loopVar")))))), + hasRHS(expr().bind("upperBoundExpr")))), + has(unaryOperator(hasOperatorName("++"), + hasUnaryOperand(declRefExpr( + to(varDecl(equalsBoundNode("loopVar")))))) + .bind("incrementOp"))); + + auto RewriteRule = + changeTo(transformer::enclose(node("loopVar"), node("incrementOp")), + cat("auto ", name("loopVar"), " : boost::irange(", + node("upperBoundExpr"), ")")); + + testRule(makeRule(traverse(TK_IgnoreUnlessSpelledInSource, MatchedLoop), + RewriteRule), + RewriteInput, RewriteOutput); + + testRuleFailure(makeRule(traverse(TK_AsIs, MatchedLoop), RewriteRule), + RewriteInput); + } +} + TEST_F(TransformerTest, TemplateInstantiation) { std::string NonTemplatesInput = R"cpp(