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>
hasAnyNameStdString(std::vector<std::string> Names)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>
removeNamespaces(const std::vector<std::string> & Names)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 *
getConstructExpr(const CXXCtorInitializer & CtorInit)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>
getConstructExprArgRange(const CXXConstructExpr & Construct)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
RedundantStringInitCheck(StringRef Name,ClangTidyContext * Context)64 RedundantStringInitCheck::RedundantStringInitCheck(StringRef Name,
65 ClangTidyContext *Context)
66 : ClangTidyCheck(Name, Context),
67 StringNames(utils::options::parseStringList(
68 Options.get("StringNames", DefaultStringNames))) {}
69
storeOptions(ClangTidyOptions::OptionMap & Opts)70 void RedundantStringInitCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
71 Options.store(Opts, "StringNames", DefaultStringNames);
72 }
73
registerMatchers(MatchFinder * Finder)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 =
101 traverse(ast_type_traits::TK_AsIs, 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 traverse(ast_type_traits::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
check(const MatchFinder::MatchResult & Result)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