| //===--- 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 |