| 1 | //===--- UseAnyOfAllOfCheck.cpp - clang-tidy-------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "UseAnyOfAllOfCheck.h" |
| 10 | #include "clang/AST/ASTContext.h" |
| 11 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
| 12 | #include "clang/Analysis/Analyses/ExprMutationAnalyzer.h" |
| 13 | #include "clang/Frontend/CompilerInstance.h" |
| 14 | |
| 15 | using namespace clang::ast_matchers; |
| 16 | |
| 17 | namespace clang { |
| 18 | namespace { |
| 19 | /// Matches a Stmt whose parent is a CompoundStmt, and which is directly |
| 20 | /// followed by a Stmt matching the inner matcher. |
| 21 | AST_MATCHER_P(Stmt, nextStmt, ast_matchers::internal::Matcher<Stmt>, |
| 22 | InnerMatcher) { |
| 23 | DynTypedNodeList Parents = Finder->getASTContext().getParents(Node); |
| 24 | if (Parents.size() != 1) |
| 25 | return false; |
| 26 | |
| 27 | auto *C = Parents[0].get<CompoundStmt>(); |
| 28 | if (!C) |
| 29 | return false; |
| 30 | |
| 31 | const auto *I = llvm::find(Range: C->body(), Val: &Node); |
| 32 | assert(I != C->body_end() && "C is parent of Node" ); |
| 33 | if (++I == C->body_end()) |
| 34 | return false; // Node is last statement. |
| 35 | |
| 36 | return InnerMatcher.matches(Node: **I, Finder, Builder); |
| 37 | } |
| 38 | } // namespace |
| 39 | |
| 40 | namespace tidy::readability { |
| 41 | |
| 42 | void UseAnyOfAllOfCheck::registerMatchers(MatchFinder *Finder) { |
| 43 | auto Returns = [](bool V) { |
| 44 | return returnStmt(hasReturnValue(InnerMatcher: cxxBoolLiteral(equals(Value: V)))); |
| 45 | }; |
| 46 | |
| 47 | auto ReturnsButNotTrue = |
| 48 | returnStmt(hasReturnValue(InnerMatcher: unless(cxxBoolLiteral(equals(Value: true))))); |
| 49 | auto ReturnsButNotFalse = |
| 50 | returnStmt(hasReturnValue(InnerMatcher: unless(cxxBoolLiteral(equals(Value: false))))); |
| 51 | |
| 52 | Finder->addMatcher( |
| 53 | NodeMatch: cxxForRangeStmt( |
| 54 | nextStmt(InnerMatcher: Returns(false).bind(ID: "final_return" )), |
| 55 | hasBody(InnerMatcher: allOf(hasDescendant(Returns(true)), |
| 56 | unless(anyOf(hasDescendant(breakStmt()), |
| 57 | hasDescendant(gotoStmt()), |
| 58 | hasDescendant(ReturnsButNotTrue)))))) |
| 59 | .bind(ID: "any_of_loop" ), |
| 60 | Action: this); |
| 61 | |
| 62 | Finder->addMatcher( |
| 63 | NodeMatch: cxxForRangeStmt( |
| 64 | nextStmt(InnerMatcher: Returns(true).bind(ID: "final_return" )), |
| 65 | hasBody(InnerMatcher: allOf(hasDescendant(Returns(false)), |
| 66 | unless(anyOf(hasDescendant(breakStmt()), |
| 67 | hasDescendant(gotoStmt()), |
| 68 | hasDescendant(ReturnsButNotFalse)))))) |
| 69 | .bind(ID: "all_of_loop" ), |
| 70 | Action: this); |
| 71 | } |
| 72 | |
| 73 | static bool isViableLoop(const CXXForRangeStmt &S, ASTContext &Context) { |
| 74 | |
| 75 | ExprMutationAnalyzer Mutations(*S.getBody(), Context); |
| 76 | if (Mutations.isMutated(S.getLoopVariable())) |
| 77 | return false; |
| 78 | const auto Matches = |
| 79 | match(Matcher: findAll(Matcher: declRefExpr().bind(ID: "decl_ref" )), Node: *S.getBody(), Context); |
| 80 | |
| 81 | return llvm::none_of(Range: Matches, P: [&Mutations](auto &DeclRef) { |
| 82 | // TODO: allow modifications of loop-local variables |
| 83 | return Mutations.isMutated( |
| 84 | DeclRef.template getNodeAs<DeclRefExpr>("decl_ref" )->getDecl()); |
| 85 | }); |
| 86 | } |
| 87 | |
| 88 | void UseAnyOfAllOfCheck::check(const MatchFinder::MatchResult &Result) { |
| 89 | |
| 90 | if (const auto *S = Result.Nodes.getNodeAs<CXXForRangeStmt>(ID: "any_of_loop" )) { |
| 91 | if (!isViableLoop(S: *S, Context&: *Result.Context)) |
| 92 | return; |
| 93 | |
| 94 | diag(Loc: S->getForLoc(), Description: "replace loop by 'std%select{|::ranges}0::any_of()'" ) |
| 95 | << getLangOpts().CPlusPlus20; |
| 96 | } else if (const auto *S = |
| 97 | Result.Nodes.getNodeAs<CXXForRangeStmt>(ID: "all_of_loop" )) { |
| 98 | if (!isViableLoop(S: *S, Context&: *Result.Context)) |
| 99 | return; |
| 100 | |
| 101 | diag(Loc: S->getForLoc(), Description: "replace loop by 'std%select{|::ranges}0::all_of()'" ) |
| 102 | << getLangOpts().CPlusPlus20; |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | } // namespace tidy::readability |
| 107 | } // namespace clang |
| 108 | |