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