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