================ @@ -0,0 +1,419 @@ +//===----------------------------------------------------------------------===// +// +// 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 "UseStructuredBindingCheck.h" +#include "../utils/DeclRefExprUtils.h" +#include "../utils/OptionsUtils.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::modernize { +namespace { +constexpr const char *DefaultPairTypes = "std::pair"; +constexpr llvm::StringLiteral PairDeclName = "PairVarD"; +constexpr llvm::StringLiteral PairVarTypeName = "PairVarType"; +constexpr llvm::StringLiteral FirstVarDeclName = "FirstVarDecl"; +constexpr llvm::StringLiteral SecondVarDeclName = "SecondVarDecl"; +constexpr llvm::StringLiteral FirstDeclStmtName = "FirstDeclStmt"; +constexpr llvm::StringLiteral SecondDeclStmtName = "SecondDeclStmt"; +constexpr llvm::StringLiteral FirstTypeName = "FirstType"; +constexpr llvm::StringLiteral SecondTypeName = "SecondType"; +constexpr llvm::StringLiteral ScopeBlockName = "ScopeBlock"; +constexpr llvm::StringLiteral StdTieAssignStmtName = "StdTieAssign"; +constexpr llvm::StringLiteral StdTieExprName = "StdTieExpr"; +constexpr llvm::StringLiteral ForRangeStmtName = "ForRangeStmt"; + +/// What qualifiers and specifiers are used to create structured binding +/// declaration, it only supports the following four cases now. +enum TransferType : uint8_t { + TT_ByVal, + TT_ByConstVal, + TT_ByRef, + TT_ByConstRef +}; + +/// Try to match exactly two VarDecl inside two DeclStmts, and set binding for +/// the used DeclStmts. +bool matchTwoVarDecl(const DeclStmt *DS1, const DeclStmt *DS2, + ast_matchers::internal::Matcher<VarDecl> InnerMatcher1, + ast_matchers::internal::Matcher<VarDecl> InnerMatcher2, + internal::ASTMatchFinder *Finder, + internal::BoundNodesTreeBuilder *Builder) { + SmallVector<std::pair<const VarDecl *, const DeclStmt *>, 2> Vars; + auto CollectVarsInDeclStmt = [&Vars](const DeclStmt *DS) -> bool { + if (!DS) + return true; + + for (const auto *VD : DS->decls()) { + if (Vars.size() == 2) + return false; + + if (const auto *Var = dyn_cast<VarDecl>(VD)) + Vars.emplace_back(Var, DS); + else + return false; + } + + return true; + }; + + if (!CollectVarsInDeclStmt(DS1) || !CollectVarsInDeclStmt(DS2)) + return false; + + if (Vars.size() != 2) + return false; + + if (InnerMatcher1.matches(*Vars[0].first, Finder, Builder) && + InnerMatcher2.matches(*Vars[1].first, Finder, Builder)) { + Builder->setBinding(FirstDeclStmtName, + clang::DynTypedNode::create(*Vars[0].second)); + if (Vars[0].second != Vars[1].second) + Builder->setBinding(SecondDeclStmtName, + clang::DynTypedNode::create(*Vars[1].second)); + return true; + } + + return false; +} + +/// Matches a Stmt whose parent is a CompoundStmt, and which is directly +/// following two VarDecls matching the inner matcher, at the same time set +/// binding for the CompoundStmt. +AST_MATCHER_P2(Stmt, hasPreTwoVarDecl, ast_matchers::internal::Matcher<VarDecl>, + InnerMatcher1, ast_matchers::internal::Matcher<VarDecl>, + InnerMatcher2) { + DynTypedNodeList Parents = Finder->getASTContext().getParents(Node); + if (Parents.size() != 1) + return false; + + auto *C = Parents[0].get<CompoundStmt>(); + if (!C) + return false; + + const auto I = + llvm::find(llvm::make_range(C->body_rbegin(), C->body_rend()), &Node); + assert(I != C->body_rend() && "C is parent of Node"); + if ((I + 1) == C->body_rend()) + return false; + + const auto *DS2 = dyn_cast<DeclStmt>(*(I + 1)); + if (!DS2) + return false; + + const DeclStmt *DS1 = (!DS2->isSingleDecl() || ((I + 2) == C->body_rend()) + ? nullptr + : dyn_cast<DeclStmt>(*(I + 2))); + + if (matchTwoVarDecl(DS1, DS2, InnerMatcher1, InnerMatcher2, Finder, + Builder)) { + Builder->setBinding(ScopeBlockName, clang::DynTypedNode::create(*C)); + return true; + } + + return false; +} + +/// Matches a Stmt whose parent is a CompoundStmt, and which is directly +/// followed by two VarDecls matching the inner matcher, at the same time set +/// binding for the CompoundStmt. +AST_MATCHER_P2(Stmt, hasNextTwoVarDecl, + ast_matchers::internal::Matcher<VarDecl>, InnerMatcher1, + ast_matchers::internal::Matcher<VarDecl>, InnerMatcher2) { + const DynTypedNodeList Parents = Finder->getASTContext().getParents(Node); + if (Parents.size() != 1) + return false; + + auto *C = Parents[0].get<CompoundStmt>(); + if (!C) + return false; + + const auto *I = llvm::find(C->body(), &Node); + assert(I != C->body_end() && "C is parent of Node"); + if ((I + 1) == C->body_end()) + return false; + + if (matchTwoVarDecl( + dyn_cast<DeclStmt>(*(I + 1)), + ((I + 2) == C->body_end() ? nullptr : dyn_cast<DeclStmt>(*(I + 2))), + InnerMatcher1, InnerMatcher2, Finder, Builder)) { + Builder->setBinding(ScopeBlockName, clang::DynTypedNode::create(*C)); + return true; + } + + return false; +} + +/// Matches a CompoundStmt which has two VarDecls +/// matching the inner matcher in the beginning. +AST_MATCHER_P2(CompoundStmt, hasFirstTwoVarDecl, + ast_matchers::internal::Matcher<VarDecl>, InnerMatcher1, + ast_matchers::internal::Matcher<VarDecl>, InnerMatcher2) { + const auto *I = Node.body_begin(); + if ((I) == Node.body_end()) + return false; + + return matchTwoVarDecl( + dyn_cast<DeclStmt>(*(I)), + ((I + 1) == Node.body_end() ? nullptr : dyn_cast<DeclStmt>(*(I + 1))), + InnerMatcher1, InnerMatcher2, Finder, Builder); +} + +/// It's not very common to have specifiers for variables used to decompose +/// a pair, so we ignore these cases. +AST_MATCHER(VarDecl, hasAnySpecifiersShouldBeIgnored) { + return Node.isStaticLocal() || Node.isConstexpr() || Node.hasAttrs() || + Node.isInlineSpecified(); +} + +// Ignore nodes inside macros. +AST_POLYMORPHIC_MATCHER(isInMarco, + AST_POLYMORPHIC_SUPPORTED_TYPES(Stmt, Decl)) { + return Node.getBeginLoc().isMacroID() || Node.getEndLoc().isMacroID(); +} + +AST_MATCHER_P(Expr, ignoringCopyCtorAndImplicitCast, + ast_matchers::internal::Matcher<Expr>, InnerMatcher) { + if (const auto *CtorE = dyn_cast<CXXConstructExpr>(&Node)) { + if (const auto *CtorD = CtorE->getConstructor(); + CtorD->isCopyConstructor() && CtorE->getNumArgs() == 1) { + return InnerMatcher.matches(*CtorE->getArg(0)->IgnoreImpCasts(), Finder, + Builder); + } + } + + return InnerMatcher.matches(*Node.IgnoreImpCasts(), Finder, Builder); +} + +} // namespace + +UseStructuredBindingCheck::UseStructuredBindingCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + PairTypes(utils::options::parseStringList( + Options.get("PairTypes", DefaultPairTypes))) { + ; +} + +static auto getVarInitWithMemberMatcher(StringRef PairName, + StringRef MemberName, + StringRef TypeName, + StringRef BindingName) { + return varDecl( + unless(hasAnySpecifiersShouldBeIgnored()), unless(isInMarco()), + hasInitializer( + ignoringImpCasts(ignoringCopyCtorAndImplicitCast(memberExpr( + hasObjectExpression(ignoringImpCasts(declRefExpr( + to(equalsBoundNode(std::string(PairName)))))), + member(fieldDecl(hasName(MemberName), + hasType(qualType().bind(TypeName))))))))) + .bind(BindingName); +} + +void UseStructuredBindingCheck::registerMatchers(MatchFinder *Finder) { + auto PairType = + qualType(unless(isVolatileQualified()), + hasUnqualifiedDesugaredType(recordType( + hasDeclaration(cxxRecordDecl(hasAnyName(PairTypes)))))); + + auto VarInitWithFirstMember = getVarInitWithMemberMatcher( + PairDeclName, "first", FirstTypeName, FirstVarDeclName); + auto VarInitWithSecondMember = getVarInitWithMemberMatcher( + PairDeclName, "second", SecondTypeName, SecondVarDeclName); + + // X x; + // Y y; + // std::tie(x, y) = ...; + Finder->addMatcher( + exprWithCleanups( + unless(isInMarco()), + has(cxxOperatorCallExpr( + hasOverloadedOperatorName("="), + hasLHS(ignoringImplicit( + callExpr( + callee( + functionDecl(isInStdNamespace(), hasName("tie"))), + hasArgument( + 0, + declRefExpr(to( + varDecl( + unless(hasAnySpecifiersShouldBeIgnored()), + unless(isInMarco())) + .bind(FirstVarDeclName)))), + hasArgument( + 1, + declRefExpr(to( + varDecl( + unless(hasAnySpecifiersShouldBeIgnored()), + unless(isInMarco())) + .bind(SecondVarDeclName))))) + .bind(StdTieExprName))), + hasRHS(expr(hasType(PairType)))) + .bind(StdTieAssignStmtName)), + hasPreTwoVarDecl( + varDecl(equalsBoundNode(std::string(FirstVarDeclName))), + varDecl(equalsBoundNode(std::string(SecondVarDeclName))))), + this); + + // pair<X, Y> p = ...; + // X x = p.first; + // Y y = p.second; + Finder->addMatcher( + declStmt( + unless(isInMarco()), + hasSingleDecl( + varDecl(unless(hasAnySpecifiersShouldBeIgnored()), + hasType(qualType(anyOf(PairType, lValueReferenceType( + pointee(PairType)))) + .bind(PairVarTypeName)), + hasInitializer(expr())) + .bind(PairDeclName)), + hasNextTwoVarDecl(VarInitWithFirstMember, VarInitWithSecondMember)), + this); + + // for (pair<X, Y> p : map) { + // X x = p.first; + // Y y = p.second; + // } + Finder->addMatcher( + cxxForRangeStmt( + unless(isInMarco()), + hasLoopVariable( + varDecl(hasType(qualType(anyOf(PairType, lValueReferenceType( + pointee(PairType)))) + .bind(PairVarTypeName)), + hasInitializer(expr())) + .bind(PairDeclName)), + hasBody(compoundStmt(hasFirstTwoVarDecl(VarInitWithFirstMember, + VarInitWithSecondMember)) + .bind(ScopeBlockName))) + .bind(ForRangeStmtName), + this); +} + +static std::optional<TransferType> getTransferType(const ASTContext &Ctx, + QualType ResultType, + QualType OriginType) { + ResultType = ResultType.getCanonicalType(); + OriginType = OriginType.getCanonicalType(); + + if (ResultType == Ctx.getLValueReferenceType(OriginType.withConst())) + return TT_ByConstRef; + + if (ResultType == Ctx.getLValueReferenceType(OriginType)) + return TT_ByRef; + + if (ResultType == OriginType.withConst()) + return TT_ByConstVal; + + if (ResultType == OriginType) + return TT_ByVal; + + return std::nullopt; +} + +void UseStructuredBindingCheck::check(const MatchFinder::MatchResult &Result) { + const auto *FirstVar = Result.Nodes.getNodeAs<VarDecl>(FirstVarDeclName); + const auto *SecondVar = Result.Nodes.getNodeAs<VarDecl>(SecondVarDeclName); + + const auto *DS1 = Result.Nodes.getNodeAs<DeclStmt>(FirstDeclStmtName); + const auto *DS2 = Result.Nodes.getNodeAs<DeclStmt>(SecondDeclStmtName); + const auto *ScopeBlock = Result.Nodes.getNodeAs<CompoundStmt>(ScopeBlockName); + + // Captured structured bindings are a C++20 extension + if (!Result.Context->getLangOpts().CPlusPlus20) { + if (auto Matchers = match( + compoundStmt( + hasDescendant(lambdaExpr(hasAnyCapture(capturesVar(varDecl( + anyOf(equalsNode(FirstVar), equalsNode(SecondVar)))))))), + *ScopeBlock, *Result.Context); + !Matchers.empty()) + return; + } + + const auto *CFRS = Result.Nodes.getNodeAs<CXXForRangeStmt>(ForRangeStmtName); + auto DiagAndFix = [&](SourceLocation DiagLoc, SourceRange ReplaceRange, ---------------- flovent wrote:
I choose explicit capture in the end, we don't have `TransferType`'s declaration in header. [e4ffb48](https://github.com/llvm/llvm-project/pull/158462/commits/e4ffb48e8c9cb84b9b3c7b14f2e1a32c00228905) https://github.com/llvm/llvm-project/pull/158462 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits