brintos

brintos / llvm-project-archived public Read only

0
0
Text · 18.2 KiB · e585dd1 Raw
430 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "UseEmplaceCheck.h"10#include "../utils/OptionsUtils.h"11using namespace clang::ast_matchers;12 13namespace clang::tidy::modernize {14 15namespace {16AST_MATCHER_P(InitListExpr, initCountLeq, unsigned, N) {17  return Node.getNumInits() <= N;18}19 20// Identical to hasAnyName, except it does not take template specifiers into21// account. This is used to match the functions names as in22// DefaultEmplacyFunctions below without caring about the template types of the23// containers.24AST_MATCHER_P(NamedDecl, hasAnyNameIgnoringTemplates, std::vector<StringRef>,25              Names) {26  const std::string FullName = "::" + Node.getQualifiedNameAsString();27 28  // This loop removes template specifiers by only keeping characters not within29  // template brackets. We keep a depth count to handle nested templates. For30  // example, it'll transform a::b<c<d>>::e<f> to simply a::b::e.31  std::string FullNameTrimmed;32  int Depth = 0;33  for (const auto &Character : FullName) {34    if (Character == '<') {35      ++Depth;36    } else if (Character == '>') {37      --Depth;38    } else if (Depth == 0) {39      FullNameTrimmed.append(1, Character);40    }41  }42 43  // This loop is taken from HasNameMatcher::matchesNodeFullSlow in44  // clang/lib/ASTMatchers/ASTMatchersInternal.cpp and checks whether45  // FullNameTrimmed matches any of the given Names.46  const StringRef FullNameTrimmedRef = FullNameTrimmed;47  for (const StringRef Pattern : Names) {48    if (Pattern.starts_with("::")) {49      if (FullNameTrimmed == Pattern)50        return true;51    } else if (FullNameTrimmedRef.ends_with(Pattern) &&52               FullNameTrimmedRef.drop_back(Pattern.size()).ends_with("::")) {53      return true;54    }55  }56 57  return false;58}59 60// Checks if the given matcher is the last argument of the given CallExpr.61AST_MATCHER_P(CallExpr, hasLastArgument,62              clang::ast_matchers::internal::Matcher<Expr>, InnerMatcher) {63  if (Node.getNumArgs() == 0)64    return false;65 66  return InnerMatcher.matches(*Node.getArg(Node.getNumArgs() - 1), Finder,67                              Builder);68}69 70// Checks if the given member call has the same number of arguments as the71// function had parameters defined (this is useful to check if there is only one72// variadic argument).73AST_MATCHER(CXXMemberCallExpr, hasSameNumArgsAsDeclNumParams) {74  if (const FunctionTemplateDecl *Primary =75          Node.getMethodDecl()->getPrimaryTemplate())76    return Node.getNumArgs() == Primary->getTemplatedDecl()->getNumParams();77 78  return Node.getNumArgs() == Node.getMethodDecl()->getNumParams();79}80 81AST_MATCHER(DeclRefExpr, hasExplicitTemplateArgs) {82  return Node.hasExplicitTemplateArgs();83}84} // namespace85 86// Helper Matcher which applies the given QualType Matcher either directly or by87// resolving a pointer type to its pointee. Used to match v.push_back() as well88// as p->push_back().89static auto hasTypeOrPointeeType(90    const ast_matchers::internal::Matcher<QualType> &TypeMatcher) {91  return anyOf(hasType(TypeMatcher),92               hasType(pointerType(pointee(TypeMatcher))));93}94 95// Matches if the node has canonical type matching any of the given names.96static auto hasWantedType(llvm::ArrayRef<StringRef> TypeNames) {97  return hasCanonicalType(hasDeclaration(cxxRecordDecl(hasAnyName(TypeNames))));98}99 100// Matches member call expressions of the named method on the listed container101// types.102static auto103cxxMemberCallExprOnContainer(StringRef MethodName,104                             llvm::ArrayRef<StringRef> ContainerNames) {105  return cxxMemberCallExpr(106      hasDeclaration(functionDecl(hasName(MethodName))),107      on(hasTypeOrPointeeType(hasWantedType(ContainerNames))));108}109 110static const auto DefaultContainersWithPushBack =111    "::std::vector; ::std::list; ::std::deque";112static const auto DefaultContainersWithPush =113    "::std::stack; ::std::queue; ::std::priority_queue";114static const auto DefaultContainersWithPushFront =115    "::std::forward_list; ::std::list; ::std::deque";116static const auto DefaultSmartPointers =117    "::std::shared_ptr; ::std::unique_ptr; ::std::auto_ptr; ::std::weak_ptr";118static const auto DefaultTupleTypes = "::std::pair; ::std::tuple";119static const auto DefaultTupleMakeFunctions =120    "::std::make_pair; ::std::make_tuple";121static const auto DefaultEmplacyFunctions =122    "vector::emplace_back; vector::emplace;"123    "deque::emplace; deque::emplace_front; deque::emplace_back;"124    "forward_list::emplace_after; forward_list::emplace_front;"125    "list::emplace; list::emplace_back; list::emplace_front;"126    "set::emplace; set::emplace_hint;"127    "map::emplace; map::emplace_hint;"128    "multiset::emplace; multiset::emplace_hint;"129    "multimap::emplace; multimap::emplace_hint;"130    "unordered_set::emplace; unordered_set::emplace_hint;"131    "unordered_map::emplace; unordered_map::emplace_hint;"132    "unordered_multiset::emplace; unordered_multiset::emplace_hint;"133    "unordered_multimap::emplace; unordered_multimap::emplace_hint;"134    "stack::emplace; queue::emplace; priority_queue::emplace";135 136UseEmplaceCheck::UseEmplaceCheck(StringRef Name, ClangTidyContext *Context)137    : ClangTidyCheck(Name, Context), IgnoreImplicitConstructors(Options.get(138                                         "IgnoreImplicitConstructors", false)),139      ContainersWithPushBack(utils::options::parseStringList(Options.get(140          "ContainersWithPushBack", DefaultContainersWithPushBack))),141      ContainersWithPush(utils::options::parseStringList(142          Options.get("ContainersWithPush", DefaultContainersWithPush))),143      ContainersWithPushFront(utils::options::parseStringList(Options.get(144          "ContainersWithPushFront", DefaultContainersWithPushFront))),145      SmartPointers(utils::options::parseStringList(146          Options.get("SmartPointers", DefaultSmartPointers))),147      TupleTypes(utils::options::parseStringList(148          Options.get("TupleTypes", DefaultTupleTypes))),149      TupleMakeFunctions(utils::options::parseStringList(150          Options.get("TupleMakeFunctions", DefaultTupleMakeFunctions))),151      EmplacyFunctions(utils::options::parseStringList(152          Options.get("EmplacyFunctions", DefaultEmplacyFunctions))) {}153 154void UseEmplaceCheck::registerMatchers(MatchFinder *Finder) {155  // FIXME: Bunch of functionality that could be easily added:156  // + add handling of `insert` for stl associative container, but be careful157  // because this requires special treatment (it could cause performance158  // regression)159  // + match for emplace calls that should be replaced with insertion160  auto CallPushBack =161      cxxMemberCallExprOnContainer("push_back", ContainersWithPushBack);162  auto CallPush = cxxMemberCallExprOnContainer("push", ContainersWithPush);163  auto CallPushFront =164      cxxMemberCallExprOnContainer("push_front", ContainersWithPushFront);165 166  auto CallEmplacy = cxxMemberCallExpr(167      hasDeclaration(168          functionDecl(hasAnyNameIgnoringTemplates(EmplacyFunctions))),169      on(hasTypeOrPointeeType(170          hasCanonicalType(hasDeclaration(has(typedefNameDecl(171              hasName("value_type"),172              hasType(hasCanonicalType(recordType().bind("value_type"))))))))));173 174  // We can't replace push_backs of smart pointer because175  // if emplacement fails (f.e. bad_alloc in vector) we will have leak of176  // passed pointer because smart pointer won't be constructed177  // (and destructed) as in push_back case.178  auto IsCtorOfSmartPtr =179      hasDeclaration(cxxConstructorDecl(ofClass(hasAnyName(SmartPointers))));180 181  // Bitfields binds only to consts and emplace_back take it by universal ref.182  auto BitFieldAsArgument = hasAnyArgument(183      ignoringImplicit(memberExpr(hasDeclaration(fieldDecl(isBitField())))));184 185  // Initializer list can't be passed to universal reference.186  auto InitializerListAsArgument = hasAnyArgument(187      ignoringImplicit(allOf(cxxConstructExpr(isListInitialization()),188                             unless(cxxTemporaryObjectExpr()))));189 190  // We could have leak of resource.191  auto NewExprAsArgument = hasAnyArgument(ignoringImplicit(cxxNewExpr()));192  // We would call another constructor.193  auto ConstructingDerived =194      hasParent(implicitCastExpr(hasCastKind(CastKind::CK_DerivedToBase)));195 196  // emplace_back can't access private or protected constructors.197  auto IsPrivateOrProtectedCtor =198      hasDeclaration(cxxConstructorDecl(anyOf(isPrivate(), isProtected())));199 200  auto HasInitList = anyOf(has(ignoringImplicit(initListExpr())),201                           has(cxxStdInitializerListExpr()));202 203  // FIXME: Discard 0/NULL (as nullptr), static inline const data members,204  // overloaded functions and template names.205  auto SoughtConstructExpr =206      cxxConstructExpr(207          unless(anyOf(IsCtorOfSmartPtr, HasInitList, BitFieldAsArgument,208                       InitializerListAsArgument, NewExprAsArgument,209                       ConstructingDerived, IsPrivateOrProtectedCtor)))210          .bind("ctor");211  auto HasConstructExpr = has(ignoringImplicit(SoughtConstructExpr));212 213  // allow for T{} to be replaced, even if no CTOR is declared214  auto HasConstructInitListExpr = has(initListExpr(215      initCountLeq(1), anyOf(allOf(has(SoughtConstructExpr),216                                   has(cxxConstructExpr(argumentCountIs(0)))),217                             has(cxxBindTemporaryExpr(218                                 has(SoughtConstructExpr),219                                 has(cxxConstructExpr(argumentCountIs(0))))))));220  auto HasBracedInitListExpr =221      anyOf(has(cxxBindTemporaryExpr(HasConstructInitListExpr)),222            HasConstructInitListExpr);223 224  auto MakeTuple = ignoringImplicit(225      callExpr(callee(expr(ignoringImplicit(declRefExpr(226                   unless(hasExplicitTemplateArgs()),227                   to(functionDecl(hasAnyName(TupleMakeFunctions))))))))228          .bind("make"));229 230  // make_something can return type convertible to container's element type.231  // Allow the conversion only on containers of pairs.232  auto MakeTupleCtor = ignoringImplicit(cxxConstructExpr(233      has(materializeTemporaryExpr(MakeTuple)),234      hasDeclaration(cxxConstructorDecl(ofClass(hasAnyName(TupleTypes))))));235 236  auto SoughtParam =237      materializeTemporaryExpr(238          anyOf(has(MakeTuple), has(MakeTupleCtor), HasConstructExpr,239                HasBracedInitListExpr,240                has(cxxFunctionalCastExpr(HasConstructExpr)),241                has(cxxFunctionalCastExpr(HasBracedInitListExpr))))242          .bind("temporary_expr");243 244  auto HasConstructExprWithValueTypeType =245      has(ignoringImplicit(cxxConstructExpr(246          SoughtConstructExpr,247          hasType(hasCanonicalType(type(equalsBoundNode("value_type")))))));248 249  auto HasBracedInitListWithValueTypeType = anyOf(250      allOf(HasConstructInitListExpr,251            has(initListExpr(hasType(252                hasCanonicalType(type(equalsBoundNode("value_type"))))))),253      has(cxxBindTemporaryExpr(HasConstructInitListExpr,254                               has(initListExpr(hasType(hasCanonicalType(255                                   type(equalsBoundNode("value_type")))))))));256 257  auto HasConstructExprWithValueTypeTypeAsLastArgument = hasLastArgument(258      materializeTemporaryExpr(259          anyOf(HasConstructExprWithValueTypeType,260                HasBracedInitListWithValueTypeType,261                has(cxxFunctionalCastExpr(HasConstructExprWithValueTypeType)),262                has(cxxFunctionalCastExpr(HasBracedInitListWithValueTypeType))))263          .bind("temporary_expr"));264 265  Finder->addMatcher(266      traverse(TK_AsIs, cxxMemberCallExpr(CallPushBack, has(SoughtParam),267                                          unless(isInTemplateInstantiation()))268                            .bind("push_back_call")),269      this);270 271  Finder->addMatcher(272      traverse(TK_AsIs, cxxMemberCallExpr(CallPush, has(SoughtParam),273                                          unless(isInTemplateInstantiation()))274                            .bind("push_call")),275      this);276 277  Finder->addMatcher(278      traverse(TK_AsIs, cxxMemberCallExpr(CallPushFront, has(SoughtParam),279                                          unless(isInTemplateInstantiation()))280                            .bind("push_front_call")),281      this);282 283  Finder->addMatcher(284      traverse(TK_AsIs,285               cxxMemberCallExpr(286                   CallEmplacy, HasConstructExprWithValueTypeTypeAsLastArgument,287                   hasSameNumArgsAsDeclNumParams(),288                   unless(isInTemplateInstantiation()))289                   .bind("emplacy_call")),290      this);291 292  Finder->addMatcher(293      traverse(TK_AsIs,294               cxxMemberCallExpr(295                   CallEmplacy,296                   on(hasType(cxxRecordDecl(has(typedefNameDecl(297                       hasName("value_type"),298                       hasType(hasCanonicalType(recordType(hasDeclaration(299                           cxxRecordDecl(hasAnyName(SmallVector<StringRef, 2>(300                               TupleTypes.begin(), TupleTypes.end())))))))))))),301                   has(MakeTuple), hasSameNumArgsAsDeclNumParams(),302                   unless(isInTemplateInstantiation()))303                   .bind("emplacy_call")),304      this);305}306 307void UseEmplaceCheck::check(const MatchFinder::MatchResult &Result) {308  const auto *PushBackCall =309      Result.Nodes.getNodeAs<CXXMemberCallExpr>("push_back_call");310  const auto *PushCall = Result.Nodes.getNodeAs<CXXMemberCallExpr>("push_call");311  const auto *PushFrontCall =312      Result.Nodes.getNodeAs<CXXMemberCallExpr>("push_front_call");313  const auto *EmplacyCall =314      Result.Nodes.getNodeAs<CXXMemberCallExpr>("emplacy_call");315  const auto *CtorCall = Result.Nodes.getNodeAs<CXXConstructExpr>("ctor");316  const auto *MakeCall = Result.Nodes.getNodeAs<CallExpr>("make");317  const auto *TemporaryExpr =318      Result.Nodes.getNodeAs<MaterializeTemporaryExpr>("temporary_expr");319 320  const CXXMemberCallExpr *Call = [&]() {321    if (PushBackCall) {322      return PushBackCall;323    }324    if (PushCall) {325      return PushCall;326    }327    if (PushFrontCall) {328      return PushFrontCall;329    }330    return EmplacyCall;331  }();332 333  assert(Call && "No call matched");334  assert((CtorCall || MakeCall) && "No push_back parameter matched");335 336  if (IgnoreImplicitConstructors && CtorCall && CtorCall->getNumArgs() >= 1 &&337      CtorCall->getArg(0)->getSourceRange() == CtorCall->getSourceRange())338    return;339 340  const auto FunctionNameSourceRange = CharSourceRange::getCharRange(341      Call->getExprLoc(), Call->getArg(0)->getExprLoc());342 343  auto Diag =344      EmplacyCall345          ? diag(TemporaryExpr ? TemporaryExpr->getBeginLoc()346                 : CtorCall    ? CtorCall->getBeginLoc()347                               : MakeCall->getBeginLoc(),348                 "unnecessary temporary object created while calling %0")349          : diag(Call->getExprLoc(), "use emplace%select{|_back|_front}0 "350                                     "instead of push%select{|_back|_front}0");351  if (EmplacyCall)352    Diag << Call->getMethodDecl()->getName();353  else if (PushCall)354    Diag << 0;355  else if (PushBackCall)356    Diag << 1;357  else358    Diag << 2;359 360  if (FunctionNameSourceRange.getBegin().isMacroID())361    return;362 363  if (PushBackCall) {364    const char *EmplacePrefix = MakeCall ? "emplace_back" : "emplace_back(";365    Diag << FixItHint::CreateReplacement(FunctionNameSourceRange,366                                         EmplacePrefix);367  } else if (PushCall) {368    const char *EmplacePrefix = MakeCall ? "emplace" : "emplace(";369    Diag << FixItHint::CreateReplacement(FunctionNameSourceRange,370                                         EmplacePrefix);371  } else if (PushFrontCall) {372    const char *EmplacePrefix = MakeCall ? "emplace_front" : "emplace_front(";373    Diag << FixItHint::CreateReplacement(FunctionNameSourceRange,374                                         EmplacePrefix);375  }376 377  const SourceRange CallParensRange =378      MakeCall ? SourceRange(MakeCall->getCallee()->getEndLoc(),379                             MakeCall->getRParenLoc())380               : CtorCall->getParenOrBraceRange();381 382  // Finish if there is no explicit constructor call.383  if (CallParensRange.getBegin().isInvalid())384    return;385 386  // FIXME: Will there ever be a CtorCall, if there is no TemporaryExpr?387  const SourceLocation ExprBegin = TemporaryExpr ? TemporaryExpr->getExprLoc()388                                   : CtorCall    ? CtorCall->getExprLoc()389                                                 : MakeCall->getExprLoc();390 391  // Range for constructor name and opening brace.392  const auto ParamCallSourceRange =393      CharSourceRange::getTokenRange(ExprBegin, CallParensRange.getBegin());394 395  // Range for constructor closing brace and end of temporary expr.396  const auto EndCallSourceRange = CharSourceRange::getTokenRange(397      CallParensRange.getEnd(),398      TemporaryExpr ? TemporaryExpr->getEndLoc() : CallParensRange.getEnd());399 400  Diag << FixItHint::CreateRemoval(ParamCallSourceRange)401       << FixItHint::CreateRemoval(EndCallSourceRange);402 403  if (MakeCall && EmplacyCall) {404    // Remove extra left parenthesis405    Diag << FixItHint::CreateRemoval(406        CharSourceRange::getCharRange(MakeCall->getCallee()->getEndLoc(),407                                      MakeCall->getArg(0)->getBeginLoc()));408  }409}410 411void UseEmplaceCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {412  Options.store(Opts, "IgnoreImplicitConstructors", IgnoreImplicitConstructors);413  Options.store(Opts, "ContainersWithPushBack",414                utils::options::serializeStringList(ContainersWithPushBack));415  Options.store(Opts, "ContainersWithPush",416                utils::options::serializeStringList(ContainersWithPush));417  Options.store(Opts, "ContainersWithPushFront",418                utils::options::serializeStringList(ContainersWithPushFront));419  Options.store(Opts, "SmartPointers",420                utils::options::serializeStringList(SmartPointers));421  Options.store(Opts, "TupleTypes",422                utils::options::serializeStringList(TupleTypes));423  Options.store(Opts, "TupleMakeFunctions",424                utils::options::serializeStringList(TupleMakeFunctions));425  Options.store(Opts, "EmplacyFunctions",426                utils::options::serializeStringList(EmplacyFunctions));427}428 429} // namespace clang::tidy::modernize430