123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375 |
- //===--- 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 {
- // 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.startswith("::")) {
- if (FullNameTrimmed == Pattern)
- return true;
- } else if (FullNameTrimmedRef.endswith(Pattern) &&
- FullNameTrimmedRef.drop_back(Pattern.size()).endswith("::")) {
- 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 (Node.getMethodDecl()->isFunctionTemplateSpecialization())
- return Node.getNumArgs() == Node.getMethodDecl()
- ->getPrimaryTemplate()
- ->getTemplatedDecl()
- ->getNumParams();
- return Node.getNumArgs() == Node.getMethodDecl()->getNumParams();
- }
- AST_MATCHER(DeclRefExpr, hasExplicitTemplateArgs) {
- return Node.hasExplicitTemplateArgs();
- }
- 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 = cxxMemberCallExpr(
- hasDeclaration(functionDecl(hasName("push_back"))),
- on(hasType(hasCanonicalType(
- hasDeclaration(cxxRecordDecl(hasAnyName(ContainersWithPushBack)))))));
- auto CallPush =
- cxxMemberCallExpr(hasDeclaration(functionDecl(hasName("push"))),
- on(hasType(hasCanonicalType(hasDeclaration(
- cxxRecordDecl(hasAnyName(ContainersWithPush)))))));
- auto CallPushFront = cxxMemberCallExpr(
- hasDeclaration(functionDecl(hasName("push_front"))),
- on(hasType(hasCanonicalType(hasDeclaration(
- cxxRecordDecl(hasAnyName(ContainersWithPushFront)))))));
- auto CallEmplacy = cxxMemberCallExpr(
- hasDeclaration(
- functionDecl(hasAnyNameIgnoringTemplates(EmplacyFunctions))),
- on(hasType(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(cxxConstructExpr(isListInitialization())));
- // 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));
- 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,
- has(cxxFunctionalCastExpr(HasConstructExpr))));
- auto HasConstructExprWithValueTypeType =
- has(ignoringImplicit(cxxConstructExpr(
- SoughtConstructExpr, hasType(type(hasUnqualifiedDesugaredType(
- type(equalsBoundNode("value_type"))))))));
- auto HasConstructExprWithValueTypeTypeAsLastArgument =
- hasLastArgument(materializeTemporaryExpr(anyOf(
- HasConstructExprWithValueTypeType,
- has(cxxFunctionalCastExpr(HasConstructExprWithValueTypeType)))));
- 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 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(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;
- const SourceLocation ExprBegin =
- CtorCall ? CtorCall->getExprLoc() : MakeCall->getExprLoc();
- // Range for constructor name and opening brace.
- const auto ParamCallSourceRange =
- CharSourceRange::getTokenRange(ExprBegin, CallParensRange.getBegin());
- Diag << FixItHint::CreateRemoval(ParamCallSourceRange)
- << FixItHint::CreateRemoval(CharSourceRange::getTokenRange(
- CallParensRange.getEnd(), CallParensRange.getEnd()));
- 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
|