1 //===- RedundantStringInitCheck.cpp - clang-tidy ----------------*- C++ -*-===// 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-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "RedundantStringInitCheck.h" 10 #include "../utils/Matchers.h" 11 #include "../utils/OptionsUtils.h" 12 #include "clang/ASTMatchers/ASTMatchers.h" 13 14 using namespace clang::ast_matchers; 15 using namespace clang::tidy::matchers; 16 17 namespace clang { 18 namespace tidy { 19 namespace readability { 20 21 const char DefaultStringNames[] = 22 "::std::basic_string_view;::std::basic_string"; 23 24 static ast_matchers::internal::Matcher<NamedDecl> 25 hasAnyNameStdString(std::vector<std::string> Names) { 26 return ast_matchers::internal::Matcher<NamedDecl>( 27 new ast_matchers::internal::HasNameMatcher(std::move(Names))); 28 } 29 30 static std::vector<std::string> 31 removeNamespaces(const std::vector<std::string> &Names) { 32 std::vector<std::string> Result; 33 Result.reserve(Names.size()); 34 for (const std::string &Name : Names) { 35 std::string::size_type ColonPos = Name.rfind(':'); 36 Result.push_back( 37 Name.substr(ColonPos == std::string::npos ? 0 : ColonPos + 1)); 38 } 39 return Result; 40 } 41 42 static const CXXConstructExpr * 43 getConstructExpr(const CXXCtorInitializer &CtorInit) { 44 const Expr *InitExpr = CtorInit.getInit(); 45 if (const auto *CleanUpExpr = dyn_cast<ExprWithCleanups>(InitExpr)) 46 InitExpr = CleanUpExpr->getSubExpr(); 47 return dyn_cast<CXXConstructExpr>(InitExpr); 48 } 49 50 static llvm::Optional<SourceRange> 51 getConstructExprArgRange(const CXXConstructExpr &Construct) { 52 SourceLocation B, E; 53 for (const Expr *Arg : Construct.arguments()) { 54 if (B.isInvalid()) 55 B = Arg->getBeginLoc(); 56 if (Arg->getEndLoc().isValid()) 57 E = Arg->getEndLoc(); 58 } 59 if (B.isInvalid() || E.isInvalid()) 60 return llvm::None; 61 return SourceRange(B, E); 62 } 63 64 RedundantStringInitCheck::RedundantStringInitCheck(StringRef Name, 65 ClangTidyContext *Context) 66 : ClangTidyCheck(Name, Context), 67 StringNames(utils::options::parseStringList( 68 Options.get("StringNames", DefaultStringNames))) {} 69 70 void RedundantStringInitCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { 71 Options.store(Opts, "StringNames", DefaultStringNames); 72 } 73 74 void RedundantStringInitCheck::registerMatchers(MatchFinder *Finder) { 75 const auto HasStringTypeName = hasAnyNameStdString(StringNames); 76 const auto HasStringCtorName = 77 hasAnyNameStdString(removeNamespaces(StringNames)); 78 79 // Match string constructor. 80 const auto StringConstructorExpr = expr( 81 anyOf(cxxConstructExpr(argumentCountIs(1), 82 hasDeclaration(cxxMethodDecl(HasStringCtorName))), 83 // If present, the second argument is the alloc object which must 84 // not be present explicitly. 85 cxxConstructExpr(argumentCountIs(2), 86 hasDeclaration(cxxMethodDecl(HasStringCtorName)), 87 hasArgument(1, cxxDefaultArgExpr())))); 88 89 // Match a string constructor expression with an empty string literal. 90 const auto EmptyStringCtorExpr = cxxConstructExpr( 91 StringConstructorExpr, 92 hasArgument(0, ignoringParenImpCasts(stringLiteral(hasSize(0))))); 93 94 const auto EmptyStringCtorExprWithTemporaries = 95 cxxConstructExpr(StringConstructorExpr, 96 hasArgument(0, ignoringImplicit(EmptyStringCtorExpr))); 97 98 const auto StringType = hasType(hasUnqualifiedDesugaredType( 99 recordType(hasDeclaration(cxxRecordDecl(HasStringTypeName))))); 100 const auto EmptyStringInit = traverse( 101 TK_AsIs, expr(ignoringImplicit(anyOf( 102 EmptyStringCtorExpr, EmptyStringCtorExprWithTemporaries)))); 103 104 // Match a variable declaration with an empty string literal as initializer. 105 // Examples: 106 // string foo = ""; 107 // string bar(""); 108 Finder->addMatcher( 109 traverse(TK_AsIs, 110 namedDecl(varDecl(StringType, hasInitializer(EmptyStringInit)) 111 .bind("vardecl"), 112 unless(parmVarDecl()))), 113 this); 114 // Match a field declaration with an empty string literal as initializer. 115 Finder->addMatcher( 116 namedDecl(fieldDecl(StringType, hasInClassInitializer(EmptyStringInit)) 117 .bind("fieldDecl")), 118 this); 119 // Matches Constructor Initializers with an empty string literal as 120 // initializer. 121 // Examples: 122 // Foo() : SomeString("") {} 123 Finder->addMatcher( 124 cxxCtorInitializer( 125 isWritten(), 126 forField(allOf(StringType, optionally(hasInClassInitializer( 127 EmptyStringInit.bind("empty_init"))))), 128 withInitializer(EmptyStringInit)) 129 .bind("ctorInit"), 130 this); 131 } 132 133 void RedundantStringInitCheck::check(const MatchFinder::MatchResult &Result) { 134 if (const auto *VDecl = Result.Nodes.getNodeAs<VarDecl>("vardecl")) { 135 // VarDecl's getSourceRange() spans 'string foo = ""' or 'string bar("")'. 136 // So start at getLocation() to span just 'foo = ""' or 'bar("")'. 137 SourceRange ReplaceRange(VDecl->getLocation(), VDecl->getEndLoc()); 138 diag(VDecl->getLocation(), "redundant string initialization") 139 << FixItHint::CreateReplacement(ReplaceRange, VDecl->getName()); 140 } 141 if (const auto *FDecl = Result.Nodes.getNodeAs<FieldDecl>("fieldDecl")) { 142 // FieldDecl's getSourceRange() spans 'string foo = ""'. 143 // So start at getLocation() to span just 'foo = ""'. 144 SourceRange ReplaceRange(FDecl->getLocation(), FDecl->getEndLoc()); 145 diag(FDecl->getLocation(), "redundant string initialization") 146 << FixItHint::CreateReplacement(ReplaceRange, FDecl->getName()); 147 } 148 if (const auto *CtorInit = 149 Result.Nodes.getNodeAs<CXXCtorInitializer>("ctorInit")) { 150 if (const FieldDecl *Member = CtorInit->getMember()) { 151 if (!Member->hasInClassInitializer() || 152 Result.Nodes.getNodeAs<Expr>("empty_init")) { 153 // The String isn't declared in the class with an initializer or its 154 // declared with a redundant initializer, which will be removed. Either 155 // way the string will be default initialized, therefore we can remove 156 // the constructor initializer entirely. 157 diag(CtorInit->getMemberLocation(), "redundant string initialization") 158 << FixItHint::CreateRemoval(CtorInit->getSourceRange()); 159 return; 160 } 161 } 162 const CXXConstructExpr *Construct = getConstructExpr(*CtorInit); 163 if (!Construct) 164 return; 165 if (llvm::Optional<SourceRange> RemovalRange = 166 getConstructExprArgRange(*Construct)) 167 diag(CtorInit->getMemberLocation(), "redundant string initialization") 168 << FixItHint::CreateRemoval(*RemovalRange); 169 } 170 } 171 172 } // namespace readability 173 } // namespace tidy 174 } // namespace clang 175