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