================ @@ -0,0 +1,320 @@ +//===----------------------------------------------------------------------===// +// +// 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 "InefficientContainerAssignmentCheck.h" +#include "../utils/OptionsUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/ParentMapContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringExtras.h" +#include <optional> +#include <string> + +using namespace clang::ast_matchers; + +namespace clang::tidy::performance { + +namespace { + +/// How the assignment can be rewritten without the temporary. The order +/// matches the %select in the diagnostic. +enum class Rewrite { + Assign, ///< lhs.assign(args...) + AssignRange, ///< lhs.assign_range(range) + ClearResize, ///< lhs.clear(); lhs.resize(count) + InitList, ///< lhs = {...} + Direct, ///< lhs = source +}; + +} // namespace + +/// Collects the variables and members named by \p E so that arguments +/// referring back to the destination can be detected. +static void +collectReferencedDecls(const Expr &E, ASTContext &Ctx, + llvm::SmallPtrSetImpl<const ValueDecl *> &Decls) { + for (const BoundNodes &N : match(findAll(declRefExpr().bind("ref")), E, Ctx)) + Decls.insert(N.getNodeAs<DeclRefExpr>("ref")->getDecl()); + for (const BoundNodes &N : + match(findAll(memberExpr().bind("member")), E, Ctx)) + Decls.insert(N.getNodeAs<MemberExpr>("member")->getMemberDecl()); +} + +static bool +refersToAnyOf(const Expr &E, ASTContext &Ctx, + const llvm::SmallPtrSetImpl<const ValueDecl *> &Decls) { + llvm::SmallPtrSet<const ValueDecl *, 8> Referenced; + collectReferencedDecls(E, Ctx, Referenced); + return llvm::any_of( + Referenced, [&Decls](const ValueDecl *D) { return Decls.contains(D); }); +} + +/// Returns true if one of the first \p NumArgs parameters of \p Ctor has the +/// type of a template argument of the container other than its element type. +/// That is what the allocator parameter of the standard containers looks +/// like, and an explicitly passed allocator has no counterpart in 'assign'. +static bool passesAllocator(const CXXConstructorDecl &Ctor, unsigned NumArgs, + const ASTContext &Ctx) { + const auto *Spec = + dyn_cast<ClassTemplateSpecializationDecl>(Ctor.getParent()); + if (!Spec) + return false; + const TemplateArgumentList &TemplateArgs = Spec->getTemplateArgs(); + for (unsigned I = 0, E = std::min(NumArgs, Ctor.getNumParams()); I < E; ++I) { + const QualType ParamType = + Ctor.getParamDecl(I)->getType().getNonReferenceType(); + if (!ParamType->isRecordType()) + continue; + for (unsigned J = 1; J < TemplateArgs.size(); ++J) { + const TemplateArgument &Arg = TemplateArgs[J]; + if (Arg.getKind() == TemplateArgument::Type && + ASTContext::hasSameUnqualifiedType(Arg.getAsType(), ParamType)) + return true; + } + } + return false; +} + +static bool isFromRangeTag(QualType T) { + const CXXRecordDecl *RD = T.getNonReferenceType()->getAsCXXRecordDecl(); + return RD && RD->isInStdNamespace() && RD->getName() == "from_range_t"; +} + +/// A postfix expression can take a member access directly; anything else has +/// to be parenthesized first. +static bool needsParens(const Expr &E) { + const Expr *Inner = E.IgnoreImpCasts(); + if (const auto *Call = dyn_cast<CXXOperatorCallExpr>(Inner)) + return Call->getOperator() != OO_Subscript && + Call->getOperator() != OO_Call; + return !isa<DeclRefExpr, MemberExpr, ArraySubscriptExpr, CallExpr, ParenExpr>( + Inner); +} + +/// Returns the statement that directly contains \p E, looking through the +/// implicit cleanup wrapper and parentheses, or nullptr if \p E is a +/// subexpression or an initializer. +static const Stmt *getEnclosingStatement(const Expr &E, ASTContext &Ctx) { + DynTypedNodeList Parents = Ctx.getParents(E); + while (Parents.size() == 1) { + const auto *S = Parents[0].get<Stmt>(); + if (!S) + return nullptr; + if (!isa<ExprWithCleanups, ParenExpr>(S)) + return S; + Parents = Ctx.getParents(*S); + } + return nullptr; +} + +InefficientContainerAssignmentCheck::InefficientContainerAssignmentCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + ContainerClasses(utils::options::parseStringList(Options.get( + "ContainerClasses", "::std::vector;::std::deque;::std::list;" + "::std::forward_list;::std::basic_string"))) {} + +void InefficientContainerAssignmentCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "ContainerClasses", + utils::options::serializeStringList(ContainerClasses)); +} + +void InefficientContainerAssignmentCheck::registerMatchers( + MatchFinder *Finder) { + if (ContainerClasses.empty()) + return; + + const auto Container = cxxRecordDecl(hasAnyName(ContainerClasses)); + const auto Temporary = + cxxConstructExpr(hasDeclaration(cxxConstructorDecl(ofClass(Container)))) ---------------- gaul wrote:
I tried it, but `hasType` sees the type as written, so for `std::string` it matched the typedef rather than `basic_string`, and no string assignment was diagnosed. I kept matching on the constructor's class. https://github.com/llvm/llvm-project/pull/222159 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
