clang-tools 17.0.0git
RedundantStringCStrCheck.cpp
Go to the documentation of this file.
1//===- RedundantStringCStrCheck.cpp - Check for redundant c_str calls -----===//
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// This file implements a check for redundant calls of c_str() on strings.
10//
11//===----------------------------------------------------------------------===//
12
14#include "../utils/Matchers.h"
15#include "../utils/OptionsUtils.h"
16#include "clang/Lex/Lexer.h"
17#include "clang/Tooling/FixIt.h"
18
19using namespace clang::ast_matchers;
20
22
23namespace {
24
25// Return true if expr needs to be put in parens when it is an argument of a
26// prefix unary operator, e.g. when it is a binary or ternary operator
27// syntactically.
28bool needParensAfterUnaryOperator(const Expr &ExprNode) {
29 if (isa<clang::BinaryOperator>(&ExprNode) ||
30 isa<clang::ConditionalOperator>(&ExprNode)) {
31 return true;
32 }
33 if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(&ExprNode)) {
34 return Op->getNumArgs() == 2 && Op->getOperator() != OO_PlusPlus &&
35 Op->getOperator() != OO_MinusMinus && Op->getOperator() != OO_Call &&
36 Op->getOperator() != OO_Subscript;
37 }
38 return false;
39}
40
41// Format a pointer to an expression: prefix with '*' but simplify
42// when it already begins with '&'. Return empty string on failure.
43std::string
44formatDereference(const ast_matchers::MatchFinder::MatchResult &Result,
45 const Expr &ExprNode) {
46 if (const auto *Op = dyn_cast<clang::UnaryOperator>(&ExprNode)) {
47 if (Op->getOpcode() == UO_AddrOf) {
48 // Strip leading '&'.
49 return std::string(tooling::fixit::getText(
50 *Op->getSubExpr()->IgnoreParens(), *Result.Context));
51 }
52 }
53 StringRef Text = tooling::fixit::getText(ExprNode, *Result.Context);
54
55 if (Text.empty())
56 return std::string();
57
58 // Remove remaining '->' from overloaded operator call
59 Text.consume_back("->");
60
61 // Add leading '*'.
62 if (needParensAfterUnaryOperator(ExprNode)) {
63 return (llvm::Twine("*(") + Text + ")").str();
64 }
65 return (llvm::Twine("*") + Text).str();
66}
67
68AST_MATCHER(MaterializeTemporaryExpr, isBoundToLValue) {
69 return Node.isBoundToLvalueReference();
70}
71
72} // end namespace
73
75 ClangTidyContext *Context)
76 : ClangTidyCheck(Name, Context),
77 StringParameterFunctions(utils::options::parseStringList(
78 Options.get("StringParameterFunctions", ""))) {
79 if (getLangOpts().CPlusPlus20)
80 StringParameterFunctions.push_back("::std::format");
81 if (getLangOpts().CPlusPlus23)
82 StringParameterFunctions.push_back("::std::print");
83}
84
86 ast_matchers::MatchFinder *Finder) {
87 // Match expressions of type 'string' or 'string*'.
88 const auto StringDecl = type(hasUnqualifiedDesugaredType(recordType(
89 hasDeclaration(cxxRecordDecl(hasName("::std::basic_string"))))));
90 const auto StringExpr =
91 expr(anyOf(hasType(StringDecl), hasType(qualType(pointsTo(StringDecl)))));
92
93 // Match string constructor.
94 const auto StringConstructorExpr = expr(anyOf(
95 cxxConstructExpr(argumentCountIs(1),
96 hasDeclaration(cxxMethodDecl(hasName("basic_string")))),
97 cxxConstructExpr(
98 argumentCountIs(2),
99 hasDeclaration(cxxMethodDecl(hasName("basic_string"))),
100 // If present, the second argument is the alloc object which must not
101 // be present explicitly.
102 hasArgument(1, cxxDefaultArgExpr()))));
103
104 // Match string constructor.
105 const auto StringViewConstructorExpr = cxxConstructExpr(
106 argumentCountIs(1),
107 hasDeclaration(cxxMethodDecl(hasName("basic_string_view"))));
108
109 // Match a call to the string 'c_str()' method.
110 const auto StringCStrCallExpr =
111 cxxMemberCallExpr(on(StringExpr.bind("arg")),
112 callee(memberExpr().bind("member")),
113 callee(cxxMethodDecl(hasAnyName("c_str", "data"))))
114 .bind("call");
115 const auto HasRValueTempParent =
116 hasParent(materializeTemporaryExpr(unless(isBoundToLValue())));
117 // Detect redundant 'c_str()' calls through a string constructor.
118 // If CxxConstructExpr is the part of some CallExpr we need to
119 // check that matched ParamDecl of the ancestor CallExpr is not rvalue.
120 Finder->addMatcher(
121 traverse(
122 TK_AsIs,
123 cxxConstructExpr(
124 anyOf(StringConstructorExpr, StringViewConstructorExpr),
125 hasArgument(0, StringCStrCallExpr),
126 unless(anyOf(HasRValueTempParent, hasParent(cxxBindTemporaryExpr(
127 HasRValueTempParent)))))),
128 this);
129
130 // Detect: 's == str.c_str()' -> 's == str'
131 Finder->addMatcher(
132 cxxOperatorCallExpr(
133 hasAnyOverloadedOperatorName("<", ">", ">=", "<=", "!=", "==", "+"),
134 anyOf(allOf(hasArgument(0, StringExpr),
135 hasArgument(1, StringCStrCallExpr)),
136 allOf(hasArgument(0, StringCStrCallExpr),
137 hasArgument(1, StringExpr)))),
138 this);
139
140 // Detect: 'dst += str.c_str()' -> 'dst += str'
141 // Detect: 's = str.c_str()' -> 's = str'
142 Finder->addMatcher(
143 cxxOperatorCallExpr(hasAnyOverloadedOperatorName("=", "+="),
144 hasArgument(0, StringExpr),
145 hasArgument(1, StringCStrCallExpr)),
146 this);
147
148 // Detect: 'dst.append(str.c_str())' -> 'dst.append(str)'
149 Finder->addMatcher(
150 cxxMemberCallExpr(on(StringExpr), callee(decl(cxxMethodDecl(hasAnyName(
151 "append", "assign", "compare")))),
152 argumentCountIs(1), hasArgument(0, StringCStrCallExpr)),
153 this);
154
155 // Detect: 'dst.compare(p, n, str.c_str())' -> 'dst.compare(p, n, str)'
156 Finder->addMatcher(
157 cxxMemberCallExpr(on(StringExpr),
158 callee(decl(cxxMethodDecl(hasName("compare")))),
159 argumentCountIs(3), hasArgument(2, StringCStrCallExpr)),
160 this);
161
162 // Detect: 'dst.find(str.c_str())' -> 'dst.find(str)'
163 Finder->addMatcher(
164 cxxMemberCallExpr(on(StringExpr),
165 callee(decl(cxxMethodDecl(hasAnyName(
166 "find", "find_first_not_of", "find_first_of",
167 "find_last_not_of", "find_last_of", "rfind")))),
168 anyOf(argumentCountIs(1), argumentCountIs(2)),
169 hasArgument(0, StringCStrCallExpr)),
170 this);
171
172 // Detect: 'dst.insert(pos, str.c_str())' -> 'dst.insert(pos, str)'
173 Finder->addMatcher(
174 cxxMemberCallExpr(on(StringExpr),
175 callee(decl(cxxMethodDecl(hasName("insert")))),
176 argumentCountIs(2), hasArgument(1, StringCStrCallExpr)),
177 this);
178
179 // Detect redundant 'c_str()' calls through a StringRef constructor.
180 Finder->addMatcher(
181 traverse(
182 TK_AsIs,
183 cxxConstructExpr(
184 // Implicit constructors of these classes are overloaded
185 // wrt. string types and they internally make a StringRef
186 // referring to the argument. Passing a string directly to
187 // them is preferred to passing a char pointer.
188 hasDeclaration(cxxMethodDecl(hasAnyName(
189 "::llvm::StringRef::StringRef", "::llvm::Twine::Twine"))),
190 argumentCountIs(1),
191 // The only argument must have the form x.c_str() or p->c_str()
192 // where the method is string::c_str(). StringRef also has
193 // a constructor from string which is more efficient (avoids
194 // strlen), so we can construct StringRef from the string
195 // directly.
196 hasArgument(0, StringCStrCallExpr))),
197 this);
198
199 if (!StringParameterFunctions.empty()) {
200 // Detect redundant 'c_str()' calls in parameters passed to std::format in
201 // C++20 onwards and std::print in C++23 onwards.
202 Finder->addMatcher(
203 traverse(TK_AsIs,
204 callExpr(callee(functionDecl(matchers::matchesAnyListedName(
205 StringParameterFunctions))),
206 forEachArgumentWithParam(StringCStrCallExpr,
207 parmVarDecl()))),
208 this);
209 }
210}
211
212void RedundantStringCStrCheck::check(const MatchFinder::MatchResult &Result) {
213 const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call");
214 const auto *Arg = Result.Nodes.getNodeAs<Expr>("arg");
215 const auto *Member = Result.Nodes.getNodeAs<MemberExpr>("member");
216 bool Arrow = Member->isArrow();
217 // Replace the "call" node with the "arg" node, prefixed with '*'
218 // if the call was using '->' rather than '.'.
219 std::string ArgText =
220 Arrow ? formatDereference(Result, *Arg)
221 : tooling::fixit::getText(*Arg, *Result.Context).str();
222 if (ArgText.empty())
223 return;
224
225 diag(Call->getBeginLoc(), "redundant call to %0")
226 << Member->getMemberDecl()
227 << FixItHint::CreateReplacement(Call->getSourceRange(), ArgText);
228}
229
230} // namespace clang::tidy::readability
std::string Text
Token Name
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
const LangOptions & getLangOpts() const
Returns the language options from the context.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
RedundantStringCStrCheck(StringRef Name, ClangTidyContext *Context)
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
inline ::clang::ast_matchers::internal::Matcher< NamedDecl > matchesAnyListedName(llvm::ArrayRef< StringRef > NameList)