|  | //===--- UseEmplaceCheck.cpp - clang-tidy----------------------------------===// | 
|  | // | 
|  | // 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 "UseEmplaceCheck.h" | 
|  | #include "../utils/OptionsUtils.h" | 
|  | using namespace clang::ast_matchers; | 
|  |  | 
|  | namespace clang::tidy::modernize { | 
|  |  | 
|  | namespace { | 
|  | AST_MATCHER_P(InitListExpr, initCountLeq, unsigned, N) { | 
|  | return Node.getNumInits() <= N; | 
|  | } | 
|  |  | 
|  | // Identical to hasAnyName, except it does not take template specifiers into | 
|  | // account. This is used to match the functions names as in | 
|  | // DefaultEmplacyFunctions below without caring about the template types of the | 
|  | // containers. | 
|  | AST_MATCHER_P(NamedDecl, hasAnyNameIgnoringTemplates, std::vector<StringRef>, | 
|  | Names) { | 
|  | const std::string FullName = "::" + Node.getQualifiedNameAsString(); | 
|  |  | 
|  | // This loop removes template specifiers by only keeping characters not within | 
|  | // template brackets. We keep a depth count to handle nested templates. For | 
|  | // example, it'll transform a::b<c<d>>::e<f> to simply a::b::e. | 
|  | std::string FullNameTrimmed; | 
|  | int Depth = 0; | 
|  | for (const auto &Character : FullName) { | 
|  | if (Character == '<') { | 
|  | ++Depth; | 
|  | } else if (Character == '>') { | 
|  | --Depth; | 
|  | } else if (Depth == 0) { | 
|  | FullNameTrimmed.append(1, Character); | 
|  | } | 
|  | } | 
|  |  | 
|  | // This loop is taken from HasNameMatcher::matchesNodeFullSlow in | 
|  | // clang/lib/ASTMatchers/ASTMatchersInternal.cpp and checks whether | 
|  | // FullNameTrimmed matches any of the given Names. | 
|  | const StringRef FullNameTrimmedRef = FullNameTrimmed; | 
|  | for (const StringRef Pattern : Names) { | 
|  | if (Pattern.starts_with("::")) { | 
|  | if (FullNameTrimmed == Pattern) | 
|  | return true; | 
|  | } else if (FullNameTrimmedRef.ends_with(Pattern) && | 
|  | FullNameTrimmedRef.drop_back(Pattern.size()).ends_with("::")) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Checks if the given matcher is the last argument of the given CallExpr. | 
|  | AST_MATCHER_P(CallExpr, hasLastArgument, | 
|  | clang::ast_matchers::internal::Matcher<Expr>, InnerMatcher) { | 
|  | if (Node.getNumArgs() == 0) | 
|  | return false; | 
|  |  | 
|  | return InnerMatcher.matches(*Node.getArg(Node.getNumArgs() - 1), Finder, | 
|  | Builder); | 
|  | } | 
|  |  | 
|  | // Checks if the given member call has the same number of arguments as the | 
|  | // function had parameters defined (this is useful to check if there is only one | 
|  | // variadic argument). | 
|  | AST_MATCHER(CXXMemberCallExpr, hasSameNumArgsAsDeclNumParams) { | 
|  | if (const FunctionTemplateDecl *Primary = | 
|  | Node.getMethodDecl()->getPrimaryTemplate()) | 
|  | return Node.getNumArgs() == Primary->getTemplatedDecl()->getNumParams(); | 
|  |  | 
|  | return Node.getNumArgs() == Node.getMethodDecl()->getNumParams(); | 
|  | } | 
|  |  | 
|  | AST_MATCHER(DeclRefExpr, hasExplicitTemplateArgs) { | 
|  | return Node.hasExplicitTemplateArgs(); | 
|  | } | 
|  |  | 
|  | // Helper Matcher which applies the given QualType Matcher either directly or by | 
|  | // resolving a pointer type to its pointee. Used to match v.push_back() as well | 
|  | // as p->push_back(). | 
|  | auto hasTypeOrPointeeType( | 
|  | const ast_matchers::internal::Matcher<QualType> &TypeMatcher) { | 
|  | return anyOf(hasType(TypeMatcher), | 
|  | hasType(pointerType(pointee(TypeMatcher)))); | 
|  | } | 
|  |  | 
|  | // Matches if the node has canonical type matching any of the given names. | 
|  | auto hasWantedType(llvm::ArrayRef<StringRef> TypeNames) { | 
|  | return hasCanonicalType(hasDeclaration(cxxRecordDecl(hasAnyName(TypeNames)))); | 
|  | } | 
|  |  | 
|  | // Matches member call expressions of the named method on the listed container | 
|  | // types. | 
|  | auto cxxMemberCallExprOnContainer( | 
|  | StringRef MethodName, llvm::ArrayRef<StringRef> ContainerNames) { | 
|  | return cxxMemberCallExpr( | 
|  | hasDeclaration(functionDecl(hasName(MethodName))), | 
|  | on(hasTypeOrPointeeType(hasWantedType(ContainerNames)))); | 
|  | } | 
|  |  | 
|  | const auto DefaultContainersWithPushBack = | 
|  | "::std::vector; ::std::list; ::std::deque"; | 
|  | const auto DefaultContainersWithPush = | 
|  | "::std::stack; ::std::queue; ::std::priority_queue"; | 
|  | const auto DefaultContainersWithPushFront = | 
|  | "::std::forward_list; ::std::list; ::std::deque"; | 
|  | const auto DefaultSmartPointers = | 
|  | "::std::shared_ptr; ::std::unique_ptr; ::std::auto_ptr; ::std::weak_ptr"; | 
|  | const auto DefaultTupleTypes = "::std::pair; ::std::tuple"; | 
|  | const auto DefaultTupleMakeFunctions = "::std::make_pair; ::std::make_tuple"; | 
|  | const auto DefaultEmplacyFunctions = | 
|  | "vector::emplace_back; vector::emplace;" | 
|  | "deque::emplace; deque::emplace_front; deque::emplace_back;" | 
|  | "forward_list::emplace_after; forward_list::emplace_front;" | 
|  | "list::emplace; list::emplace_back; list::emplace_front;" | 
|  | "set::emplace; set::emplace_hint;" | 
|  | "map::emplace; map::emplace_hint;" | 
|  | "multiset::emplace; multiset::emplace_hint;" | 
|  | "multimap::emplace; multimap::emplace_hint;" | 
|  | "unordered_set::emplace; unordered_set::emplace_hint;" | 
|  | "unordered_map::emplace; unordered_map::emplace_hint;" | 
|  | "unordered_multiset::emplace; unordered_multiset::emplace_hint;" | 
|  | "unordered_multimap::emplace; unordered_multimap::emplace_hint;" | 
|  | "stack::emplace; queue::emplace; priority_queue::emplace"; | 
|  | } // namespace | 
|  |  | 
|  | UseEmplaceCheck::UseEmplaceCheck(StringRef Name, ClangTidyContext *Context) | 
|  | : ClangTidyCheck(Name, Context), IgnoreImplicitConstructors(Options.get( | 
|  | "IgnoreImplicitConstructors", false)), | 
|  | ContainersWithPushBack(utils::options::parseStringList(Options.get( | 
|  | "ContainersWithPushBack", DefaultContainersWithPushBack))), | 
|  | ContainersWithPush(utils::options::parseStringList( | 
|  | Options.get("ContainersWithPush", DefaultContainersWithPush))), | 
|  | ContainersWithPushFront(utils::options::parseStringList(Options.get( | 
|  | "ContainersWithPushFront", DefaultContainersWithPushFront))), | 
|  | SmartPointers(utils::options::parseStringList( | 
|  | Options.get("SmartPointers", DefaultSmartPointers))), | 
|  | TupleTypes(utils::options::parseStringList( | 
|  | Options.get("TupleTypes", DefaultTupleTypes))), | 
|  | TupleMakeFunctions(utils::options::parseStringList( | 
|  | Options.get("TupleMakeFunctions", DefaultTupleMakeFunctions))), | 
|  | EmplacyFunctions(utils::options::parseStringList( | 
|  | Options.get("EmplacyFunctions", DefaultEmplacyFunctions))) {} | 
|  |  | 
|  | void UseEmplaceCheck::registerMatchers(MatchFinder *Finder) { | 
|  | // FIXME: Bunch of functionality that could be easily added: | 
|  | // + add handling of `insert` for stl associative container, but be careful | 
|  | // because this requires special treatment (it could cause performance | 
|  | // regression) | 
|  | // + match for emplace calls that should be replaced with insertion | 
|  | auto CallPushBack = | 
|  | cxxMemberCallExprOnContainer("push_back", ContainersWithPushBack); | 
|  | auto CallPush = cxxMemberCallExprOnContainer("push", ContainersWithPush); | 
|  | auto CallPushFront = | 
|  | cxxMemberCallExprOnContainer("push_front", ContainersWithPushFront); | 
|  |  | 
|  | auto CallEmplacy = cxxMemberCallExpr( | 
|  | hasDeclaration( | 
|  | functionDecl(hasAnyNameIgnoringTemplates(EmplacyFunctions))), | 
|  | on(hasTypeOrPointeeType(hasCanonicalType(hasDeclaration( | 
|  | has(typedefNameDecl(hasName("value_type"), | 
|  | hasType(type(hasUnqualifiedDesugaredType( | 
|  | recordType().bind("value_type"))))))))))); | 
|  |  | 
|  | // We can't replace push_backs of smart pointer because | 
|  | // if emplacement fails (f.e. bad_alloc in vector) we will have leak of | 
|  | // passed pointer because smart pointer won't be constructed | 
|  | // (and destructed) as in push_back case. | 
|  | auto IsCtorOfSmartPtr = | 
|  | hasDeclaration(cxxConstructorDecl(ofClass(hasAnyName(SmartPointers)))); | 
|  |  | 
|  | // Bitfields binds only to consts and emplace_back take it by universal ref. | 
|  | auto BitFieldAsArgument = hasAnyArgument( | 
|  | ignoringImplicit(memberExpr(hasDeclaration(fieldDecl(isBitField()))))); | 
|  |  | 
|  | // Initializer list can't be passed to universal reference. | 
|  | auto InitializerListAsArgument = hasAnyArgument( | 
|  | ignoringImplicit(allOf(cxxConstructExpr(isListInitialization()), | 
|  | unless(cxxTemporaryObjectExpr())))); | 
|  |  | 
|  | // We could have leak of resource. | 
|  | auto NewExprAsArgument = hasAnyArgument(ignoringImplicit(cxxNewExpr())); | 
|  | // We would call another constructor. | 
|  | auto ConstructingDerived = | 
|  | hasParent(implicitCastExpr(hasCastKind(CastKind::CK_DerivedToBase))); | 
|  |  | 
|  | // emplace_back can't access private or protected constructors. | 
|  | auto IsPrivateOrProtectedCtor = | 
|  | hasDeclaration(cxxConstructorDecl(anyOf(isPrivate(), isProtected()))); | 
|  |  | 
|  | auto HasInitList = anyOf(has(ignoringImplicit(initListExpr())), | 
|  | has(cxxStdInitializerListExpr())); | 
|  |  | 
|  | // FIXME: Discard 0/NULL (as nullptr), static inline const data members, | 
|  | // overloaded functions and template names. | 
|  | auto SoughtConstructExpr = | 
|  | cxxConstructExpr( | 
|  | unless(anyOf(IsCtorOfSmartPtr, HasInitList, BitFieldAsArgument, | 
|  | InitializerListAsArgument, NewExprAsArgument, | 
|  | ConstructingDerived, IsPrivateOrProtectedCtor))) | 
|  | .bind("ctor"); | 
|  | auto HasConstructExpr = has(ignoringImplicit(SoughtConstructExpr)); | 
|  |  | 
|  | // allow for T{} to be replaced, even if no CTOR is declared | 
|  | auto HasConstructInitListExpr = has(initListExpr( | 
|  | initCountLeq(1), anyOf(allOf(has(SoughtConstructExpr), | 
|  | has(cxxConstructExpr(argumentCountIs(0)))), | 
|  | has(cxxBindTemporaryExpr( | 
|  | has(SoughtConstructExpr), | 
|  | has(cxxConstructExpr(argumentCountIs(0)))))))); | 
|  | auto HasBracedInitListExpr = | 
|  | anyOf(has(cxxBindTemporaryExpr(HasConstructInitListExpr)), | 
|  | HasConstructInitListExpr); | 
|  |  | 
|  | auto MakeTuple = ignoringImplicit( | 
|  | callExpr(callee(expr(ignoringImplicit(declRefExpr( | 
|  | unless(hasExplicitTemplateArgs()), | 
|  | to(functionDecl(hasAnyName(TupleMakeFunctions)))))))) | 
|  | .bind("make")); | 
|  |  | 
|  | // make_something can return type convertible to container's element type. | 
|  | // Allow the conversion only on containers of pairs. | 
|  | auto MakeTupleCtor = ignoringImplicit(cxxConstructExpr( | 
|  | has(materializeTemporaryExpr(MakeTuple)), | 
|  | hasDeclaration(cxxConstructorDecl(ofClass(hasAnyName(TupleTypes)))))); | 
|  |  | 
|  | auto SoughtParam = | 
|  | materializeTemporaryExpr( | 
|  | anyOf(has(MakeTuple), has(MakeTupleCtor), HasConstructExpr, | 
|  | HasBracedInitListExpr, | 
|  | has(cxxFunctionalCastExpr(HasConstructExpr)), | 
|  | has(cxxFunctionalCastExpr(HasBracedInitListExpr)))) | 
|  | .bind("temporary_expr"); | 
|  |  | 
|  | auto HasConstructExprWithValueTypeType = | 
|  | has(ignoringImplicit(cxxConstructExpr( | 
|  | SoughtConstructExpr, hasType(type(hasUnqualifiedDesugaredType( | 
|  | type(equalsBoundNode("value_type")))))))); | 
|  |  | 
|  | auto HasBracedInitListWithValueTypeType = | 
|  | anyOf(allOf(HasConstructInitListExpr, | 
|  | has(initListExpr(hasType(type(hasUnqualifiedDesugaredType( | 
|  | type(equalsBoundNode("value_type")))))))), | 
|  | has(cxxBindTemporaryExpr( | 
|  | HasConstructInitListExpr, | 
|  | has(initListExpr(hasType(type(hasUnqualifiedDesugaredType( | 
|  | type(equalsBoundNode("value_type")))))))))); | 
|  |  | 
|  | auto HasConstructExprWithValueTypeTypeAsLastArgument = hasLastArgument( | 
|  | materializeTemporaryExpr( | 
|  | anyOf(HasConstructExprWithValueTypeType, | 
|  | HasBracedInitListWithValueTypeType, | 
|  | has(cxxFunctionalCastExpr(HasConstructExprWithValueTypeType)), | 
|  | has(cxxFunctionalCastExpr(HasBracedInitListWithValueTypeType)))) | 
|  | .bind("temporary_expr")); | 
|  |  | 
|  | Finder->addMatcher( | 
|  | traverse(TK_AsIs, cxxMemberCallExpr(CallPushBack, has(SoughtParam), | 
|  | unless(isInTemplateInstantiation())) | 
|  | .bind("push_back_call")), | 
|  | this); | 
|  |  | 
|  | Finder->addMatcher( | 
|  | traverse(TK_AsIs, cxxMemberCallExpr(CallPush, has(SoughtParam), | 
|  | unless(isInTemplateInstantiation())) | 
|  | .bind("push_call")), | 
|  | this); | 
|  |  | 
|  | Finder->addMatcher( | 
|  | traverse(TK_AsIs, cxxMemberCallExpr(CallPushFront, has(SoughtParam), | 
|  | unless(isInTemplateInstantiation())) | 
|  | .bind("push_front_call")), | 
|  | this); | 
|  |  | 
|  | Finder->addMatcher( | 
|  | traverse(TK_AsIs, | 
|  | cxxMemberCallExpr( | 
|  | CallEmplacy, HasConstructExprWithValueTypeTypeAsLastArgument, | 
|  | hasSameNumArgsAsDeclNumParams(), | 
|  | unless(isInTemplateInstantiation())) | 
|  | .bind("emplacy_call")), | 
|  | this); | 
|  |  | 
|  | Finder->addMatcher( | 
|  | traverse( | 
|  | TK_AsIs, | 
|  | cxxMemberCallExpr( | 
|  | CallEmplacy, | 
|  | on(hasType(cxxRecordDecl(has(typedefNameDecl( | 
|  | hasName("value_type"), | 
|  | hasType(type( | 
|  | hasUnqualifiedDesugaredType(recordType(hasDeclaration( | 
|  | cxxRecordDecl(hasAnyName(SmallVector<StringRef, 2>( | 
|  | TupleTypes.begin(), TupleTypes.end()))))))))))))), | 
|  | has(MakeTuple), hasSameNumArgsAsDeclNumParams(), | 
|  | unless(isInTemplateInstantiation())) | 
|  | .bind("emplacy_call")), | 
|  | this); | 
|  | } | 
|  |  | 
|  | void UseEmplaceCheck::check(const MatchFinder::MatchResult &Result) { | 
|  | const auto *PushBackCall = | 
|  | Result.Nodes.getNodeAs<CXXMemberCallExpr>("push_back_call"); | 
|  | const auto *PushCall = Result.Nodes.getNodeAs<CXXMemberCallExpr>("push_call"); | 
|  | const auto *PushFrontCall = | 
|  | Result.Nodes.getNodeAs<CXXMemberCallExpr>("push_front_call"); | 
|  | const auto *EmplacyCall = | 
|  | Result.Nodes.getNodeAs<CXXMemberCallExpr>("emplacy_call"); | 
|  | const auto *CtorCall = Result.Nodes.getNodeAs<CXXConstructExpr>("ctor"); | 
|  | const auto *MakeCall = Result.Nodes.getNodeAs<CallExpr>("make"); | 
|  | const auto *TemporaryExpr = | 
|  | Result.Nodes.getNodeAs<MaterializeTemporaryExpr>("temporary_expr"); | 
|  |  | 
|  | const CXXMemberCallExpr *Call = [&]() { | 
|  | if (PushBackCall) { | 
|  | return PushBackCall; | 
|  | } | 
|  | if (PushCall) { | 
|  | return PushCall; | 
|  | } | 
|  | if (PushFrontCall) { | 
|  | return PushFrontCall; | 
|  | } | 
|  | return EmplacyCall; | 
|  | }(); | 
|  |  | 
|  | assert(Call && "No call matched"); | 
|  | assert((CtorCall || MakeCall) && "No push_back parameter matched"); | 
|  |  | 
|  | if (IgnoreImplicitConstructors && CtorCall && CtorCall->getNumArgs() >= 1 && | 
|  | CtorCall->getArg(0)->getSourceRange() == CtorCall->getSourceRange()) | 
|  | return; | 
|  |  | 
|  | const auto FunctionNameSourceRange = CharSourceRange::getCharRange( | 
|  | Call->getExprLoc(), Call->getArg(0)->getExprLoc()); | 
|  |  | 
|  | auto Diag = | 
|  | EmplacyCall | 
|  | ? diag(TemporaryExpr ? TemporaryExpr->getBeginLoc() | 
|  | : CtorCall    ? CtorCall->getBeginLoc() | 
|  | : MakeCall->getBeginLoc(), | 
|  | "unnecessary temporary object created while calling %0") | 
|  | : diag(Call->getExprLoc(), "use emplace%select{|_back|_front}0 " | 
|  | "instead of push%select{|_back|_front}0"); | 
|  | if (EmplacyCall) | 
|  | Diag << Call->getMethodDecl()->getName(); | 
|  | else if (PushCall) | 
|  | Diag << 0; | 
|  | else if (PushBackCall) | 
|  | Diag << 1; | 
|  | else | 
|  | Diag << 2; | 
|  |  | 
|  | if (FunctionNameSourceRange.getBegin().isMacroID()) | 
|  | return; | 
|  |  | 
|  | if (PushBackCall) { | 
|  | const char *EmplacePrefix = MakeCall ? "emplace_back" : "emplace_back("; | 
|  | Diag << FixItHint::CreateReplacement(FunctionNameSourceRange, | 
|  | EmplacePrefix); | 
|  | } else if (PushCall) { | 
|  | const char *EmplacePrefix = MakeCall ? "emplace" : "emplace("; | 
|  | Diag << FixItHint::CreateReplacement(FunctionNameSourceRange, | 
|  | EmplacePrefix); | 
|  | } else if (PushFrontCall) { | 
|  | const char *EmplacePrefix = MakeCall ? "emplace_front" : "emplace_front("; | 
|  | Diag << FixItHint::CreateReplacement(FunctionNameSourceRange, | 
|  | EmplacePrefix); | 
|  | } | 
|  |  | 
|  | const SourceRange CallParensRange = | 
|  | MakeCall ? SourceRange(MakeCall->getCallee()->getEndLoc(), | 
|  | MakeCall->getRParenLoc()) | 
|  | : CtorCall->getParenOrBraceRange(); | 
|  |  | 
|  | // Finish if there is no explicit constructor call. | 
|  | if (CallParensRange.getBegin().isInvalid()) | 
|  | return; | 
|  |  | 
|  | // FIXME: Will there ever be a CtorCall, if there is no TemporaryExpr? | 
|  | const SourceLocation ExprBegin = TemporaryExpr ? TemporaryExpr->getExprLoc() | 
|  | : CtorCall    ? CtorCall->getExprLoc() | 
|  | : MakeCall->getExprLoc(); | 
|  |  | 
|  | // Range for constructor name and opening brace. | 
|  | const auto ParamCallSourceRange = | 
|  | CharSourceRange::getTokenRange(ExprBegin, CallParensRange.getBegin()); | 
|  |  | 
|  | // Range for constructor closing brace and end of temporary expr. | 
|  | const auto EndCallSourceRange = CharSourceRange::getTokenRange( | 
|  | CallParensRange.getEnd(), | 
|  | TemporaryExpr ? TemporaryExpr->getEndLoc() : CallParensRange.getEnd()); | 
|  |  | 
|  | Diag << FixItHint::CreateRemoval(ParamCallSourceRange) | 
|  | << FixItHint::CreateRemoval(EndCallSourceRange); | 
|  |  | 
|  | if (MakeCall && EmplacyCall) { | 
|  | // Remove extra left parenthesis | 
|  | Diag << FixItHint::CreateRemoval( | 
|  | CharSourceRange::getCharRange(MakeCall->getCallee()->getEndLoc(), | 
|  | MakeCall->getArg(0)->getBeginLoc())); | 
|  | } | 
|  | } | 
|  |  | 
|  | void UseEmplaceCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { | 
|  | Options.store(Opts, "IgnoreImplicitConstructors", IgnoreImplicitConstructors); | 
|  | Options.store(Opts, "ContainersWithPushBack", | 
|  | utils::options::serializeStringList(ContainersWithPushBack)); | 
|  | Options.store(Opts, "ContainersWithPush", | 
|  | utils::options::serializeStringList(ContainersWithPush)); | 
|  | Options.store(Opts, "ContainersWithPushFront", | 
|  | utils::options::serializeStringList(ContainersWithPushFront)); | 
|  | Options.store(Opts, "SmartPointers", | 
|  | utils::options::serializeStringList(SmartPointers)); | 
|  | Options.store(Opts, "TupleTypes", | 
|  | utils::options::serializeStringList(TupleTypes)); | 
|  | Options.store(Opts, "TupleMakeFunctions", | 
|  | utils::options::serializeStringList(TupleMakeFunctions)); | 
|  | Options.store(Opts, "EmplacyFunctions", | 
|  | utils::options::serializeStringList(EmplacyFunctions)); | 
|  | } | 
|  |  | 
|  | } // namespace clang::tidy::modernize |