//===----------------------------------------------------------------------===// // // 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 "UseAnyOfAllOfCheck.h" #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Analysis/Analyses/ExprMutationAnalyzer.h" #include "clang/Frontend/CompilerInstance.h" using namespace clang::ast_matchers; namespace clang { namespace { /// Matches a Stmt whose parent is a CompoundStmt, and which is directly /// followed by a Stmt matching the inner matcher. AST_MATCHER_P(Stmt, nextStmt, ast_matchers::internal::Matcher, InnerMatcher) { const DynTypedNodeList Parents = Finder->getASTContext().getParents(Node); if (Parents.size() != 1) return false; auto *C = Parents[0].get(); if (!C) return false; const auto *I = llvm::find(C->body(), &Node); assert(I != C->body_end() && "C is parent of Node"); if (++I == C->body_end()) return false; // Node is last statement. return InnerMatcher.matches(**I, Finder, Builder); } AST_MATCHER(Expr, isUnsafeTemporaryRangeInit) { return Node.IgnoreParenCasts()->isPRValue(); } } // namespace namespace tidy::readability { void UseAnyOfAllOfCheck::registerMatchers(MatchFinder *Finder) { auto Returns = [](bool V) { return returnStmt(hasReturnValue(cxxBoolLiteral(equals(V)))); }; const auto ReturnsButNotTrue = returnStmt(hasReturnValue(unless(cxxBoolLiteral(equals(true))))); const auto ReturnsButNotFalse = returnStmt(hasReturnValue(unless(cxxBoolLiteral(equals(false))))); const auto RangeInitMatcher = optionally(expr(isUnsafeTemporaryRangeInit()).bind("unsafe_range_init")); Finder->addMatcher( cxxForRangeStmt( hasRangeInit(RangeInitMatcher), nextStmt(Returns(false).bind("final_return")), hasBody(allOf(hasDescendant(Returns(true)), unless(anyOf(hasDescendant(breakStmt()), hasDescendant(gotoStmt()), hasDescendant(ReturnsButNotTrue)))))) .bind("any_of_loop"), this); Finder->addMatcher( cxxForRangeStmt( hasRangeInit(RangeInitMatcher), nextStmt(Returns(true).bind("final_return")), hasBody(allOf(hasDescendant(Returns(false)), unless(anyOf(hasDescendant(breakStmt()), hasDescendant(gotoStmt()), hasDescendant(ReturnsButNotFalse)))))) .bind("all_of_loop"), this); } static bool isViableLoop(const CXXForRangeStmt &S, ASTContext &Context) { ExprMutationAnalyzer Mutations(*S.getBody(), Context); if (Mutations.isMutated(S.getLoopVariable())) return false; const auto Matches = match(findAll(declRefExpr().bind("decl_ref")), *S.getBody(), Context); return llvm::none_of(Matches, [&Mutations](auto &DeclRef) { // TODO: allow modifications of loop-local variables return Mutations.isMutated( DeclRef.template getNodeAs("decl_ref")->getDecl()); }); } void UseAnyOfAllOfCheck::check(const MatchFinder::MatchResult &Result) { const auto *AnyOfS = Result.Nodes.getNodeAs("any_of_loop"); const auto *AllOfS = Result.Nodes.getNodeAs("all_of_loop"); const CXXForRangeStmt *S = AnyOfS ? AnyOfS : AllOfS; if (!S || !isViableLoop(*S, *Result.Context)) return; const bool IsAnyOf = (AnyOfS != nullptr); diag(S->getForLoc(), "replace loop by 'std%select{|::ranges}0::%select{all_of|any_of}1()'") << getLangOpts().CPlusPlus20 << IsAnyOf; if (const auto *Init = Result.Nodes.getNodeAs("unsafe_range_init")) { if (getLangOpts().CPlusPlus20) diag(Init->getExprLoc(), "reusing the temporary range directly in the replacement may be " "unsafe; consider materializing it in a local variable first, or " "use 'std::ranges' algorithms which handle temporary ranges safely", DiagnosticIDs::Note); else diag(Init->getExprLoc(), "reusing the temporary range directly in the replacement may be " "unsafe; consider materializing it in a local variable first", DiagnosticIDs::Note); } } } // namespace tidy::readability } // namespace clang