clang-tools 23.0.0git
UseStringViewCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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
10#include "../utils/Matchers.h"
12#include "clang/AST/ASTContext.h"
13#include "clang/AST/ASTDiagnostic.h"
14#include "clang/AST/Stmt.h"
15#include "clang/ASTMatchers/ASTMatchFinder.h"
16#include "clang/ASTMatchers/ASTMatchers.h"
17#include "clang/Basic/Diagnostic.h"
18#include "llvm/ADT/StringMap.h"
19
20using namespace clang::ast_matchers;
21
22namespace clang::tidy::modernize {
23
24namespace {
25AST_MATCHER(Expr, isStringLiteralOrTernary) {
26 const auto Matches = [](const auto &Self, const Expr &Expression) -> bool {
27 const Expr *Unwrapped = Expression.IgnoreParenImpCasts();
28 if (const auto *Ternary = dyn_cast<ConditionalOperator>(Unwrapped))
29 return Self(Self, *Ternary->getTrueExpr()) &&
30 Self(Self, *Ternary->getFalseExpr());
31 return isa<StringLiteral>(Unwrapped);
32 };
33 return Matches(Matches, Node);
34}
35
36AST_MATCHER(FunctionDecl, isOverloaded) {
37 const DeclarationName Name = Node.getDeclName();
38 // Sanity check
39 if (Name.isEmpty())
40 return false;
41 const DeclContext *DC = Node.getDeclContext();
42 auto LookupResult = DC->lookup(Name);
43 size_t UniqueSignatures = 0;
44 llvm::SmallPtrSet<const FunctionDecl *, 2> SeenFunctions;
45 for (NamedDecl *ND : LookupResult) {
46 const FunctionDecl *FD = nullptr;
47 if (const auto *Func = dyn_cast<FunctionDecl>(ND)) {
48 // Regular functions
49 FD = Func;
50 } else if (const auto *USD = dyn_cast<UsingShadowDecl>(ND)) {
51 // Overloads via "using ns::func_name"
52 FD = dyn_cast<FunctionDecl>(USD->getTargetDecl());
53 } else if (const auto *FTD = dyn_cast<FunctionTemplateDecl>(ND)) {
54 // Templated functions
55 FD = FTD->getTemplatedDecl();
56 }
57 if (FD && SeenFunctions.insert(FD->getCanonicalDecl()).second) {
58 UniqueSignatures++;
59 if (UniqueSignatures > 1)
60 return true;
61 }
62 }
63 return false;
64}
65} // namespace
66
67static constexpr StringRef StringViewClassKey = "string";
68static constexpr StringRef WStringViewClassKey = "wstring";
69static constexpr StringRef U8StringViewClassKey = "u8string";
70static constexpr StringRef U16StringViewClassKey = "u16string";
71static constexpr StringRef U32StringViewClassKey = "u32string";
72
73static auto getStringTypeMatcher(StringRef CharType) {
74 return hasCanonicalType(hasDeclaration(cxxRecordDecl(hasName(CharType))));
75}
76
77static void fixReturns(const FunctionDecl *FuncDecl,
78 const DiagnosticBuilder &Diag, ASTContext &Context) {
79 auto Matches = match(
80 findAll(returnStmt(hasReturnValue(ignoringParenImpCasts(
81 cxxTemporaryObjectExpr(argumentCountIs(0)).bind("temp_obj_expr"))))),
82 *FuncDecl->getBody(), Context);
83
84 for (const auto &Match : Matches)
85 if (const auto *TempObjExpr =
86 Match.getNodeAs<CXXTemporaryObjectExpr>("temp_obj_expr");
87 TempObjExpr && TempObjExpr->getSourceRange().isValid())
88 Diag << FixItHint::CreateReplacement(TempObjExpr->getSourceRange(), "{}");
89}
90
92 ClangTidyContext *Context)
93 : ClangTidyCheck(Name, Context),
94 CheckOverloadedFunctions(Options.get("CheckOverloadedFunctions", false)),
95 IgnoredFunctions(utils::options::parseStringList(
96 Options.get("IgnoredFunctions", "toString$;ToString$;to_string$"))) {
97 parseReplacementStringViewClass(
98 Options.get("ReplacementStringViewClass", ""));
99}
100
102 Options.store(Opts, "CheckOverloadedFunctions", CheckOverloadedFunctions);
103 Options.store(Opts, "IgnoredFunctions",
104 utils::options::serializeStringList(IgnoredFunctions));
105 Options.store(Opts, "ReplacementStringViewClass",
106 (Twine("") + StringViewClassKey + "=" + StringViewClass + ";" +
107 WStringViewClassKey + "=" + WStringViewClass + ";" +
108 U8StringViewClassKey + "=" + U8StringViewClass + ";" +
109 U16StringViewClassKey + "=" + U16StringViewClass + ";" +
110 U32StringViewClassKey + "=" + U32StringViewClass)
111 .str());
112}
113
114void UseStringViewCheck::registerMatchers(MatchFinder *Finder) {
115 const auto IsStdString = getStringTypeMatcher("::std::basic_string");
116 // TODO: also consider *StringViewClass types
117 const auto IsStdStringView = getStringTypeMatcher("::std::basic_string_view");
118 const auto IgnoredFunctionsMatcher =
119 matchers::matchesAnyListedRegexName(IgnoredFunctions);
120 const auto VirtualOrOperator =
121 cxxMethodDecl(anyOf(cxxConversionDecl(), isVirtual()));
122 const auto CheckOverloaded =
123 CheckOverloadedFunctions ? unless(anything()) : isOverloaded();
124 Finder->addMatcher(
125 functionDecl(
126 isDefinition(),
127 unless(anyOf(VirtualOrOperator, IgnoredFunctionsMatcher,
128 CheckOverloaded,
129 ast_matchers::isExplicitTemplateSpecialization())),
130 returns(IsStdString), hasDescendant(returnStmt()),
131 unless(hasDescendant(returnStmt(hasReturnValue(unless(
132 anyOf(stringLiteral(), hasType(IsStdStringView),
133 isStringLiteralOrTernary(),
134 cxxConstructExpr(anyOf(
135 allOf(hasType(IsStdString), argumentCountIs(0)),
136 allOf(isListInitialization(),
137 unless(cxxTemporaryObjectExpr()),
138 hasArgument(0, ignoringParenImpCasts(
139 stringLiteral()))))))))))))
140 .bind("func"),
141 this);
142}
143
144void UseStringViewCheck::check(const MatchFinder::MatchResult &Result) {
145 const auto *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>("func");
146 assert(MatchedDecl);
147 bool ShouldAKA = false;
148 const std::string DesugaredTypeStr =
149 desugarForDiagnostic(*Result.Context,
150 QualType(MatchedDecl->getReturnType()), ShouldAKA)
151 .getAsString();
152 const StringRef DestReturnTypeStr = toStringViewTypeStr(DesugaredTypeStr);
153
154 auto Diag =
155 diag(MatchedDecl->getTypeSpecStartLoc(),
156 "consider using '%0' to avoid unnecessary copying and allocations")
157 << DestReturnTypeStr;
158
159 fixReturns(MatchedDecl, Diag, *Result.Context);
160
161 for (const auto *FuncDecl : MatchedDecl->redecls())
162 if (const SourceRange ReturnTypeRange =
163 FuncDecl->getReturnTypeSourceRange();
164 ReturnTypeRange.isValid())
165 Diag << FixItHint::CreateReplacement(ReturnTypeRange, DestReturnTypeStr);
166}
167
168StringRef UseStringViewCheck::toStringViewTypeStr(StringRef Type) const {
169 if (Type.contains("wchar_t"))
170 return WStringViewClass;
171 if (Type.contains("char8_t"))
172 return U8StringViewClass;
173 if (Type.contains("char16_t"))
174 return U16StringViewClass;
175 if (Type.contains("char32_t"))
176 return U32StringViewClass;
177 return StringViewClass;
178}
179
180void UseStringViewCheck::parseReplacementStringViewClass(StringRef Options) {
181 if (Options.empty())
182 return;
183 const llvm::StringMap<StringRef *> StringClassesMap{
184 {StringViewClassKey, &StringViewClass},
185 {WStringViewClassKey, &WStringViewClass},
186 {U8StringViewClassKey, &U8StringViewClass},
187 {U16StringViewClassKey, &U16StringViewClass},
188 {U32StringViewClassKey, &U32StringViewClass}};
189 for (const auto &Option : utils::options::parseStringList(Options)) {
190 const auto Split = Option.split('=');
191 if (auto It = StringClassesMap.find(Split.first);
192 It != StringClassesMap.end())
193 *It->second = Split.second;
194 }
195}
196
197} // namespace clang::tidy::modernize
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
UseStringViewCheck(StringRef Name, ClangTidyContext *Context)
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
AST_MATCHER(BinaryOperator, isRelationalOperator)
inline ::clang::ast_matchers::internal::Matcher< NamedDecl > matchesAnyListedRegexName(llvm::ArrayRef< StringRef > NameList)
static auto getStringTypeMatcher(StringRef CharType)
static constexpr StringRef WStringViewClassKey
static constexpr StringRef U32StringViewClassKey
static constexpr StringRef U8StringViewClassKey
static constexpr StringRef StringViewClassKey
static void fixReturns(const FunctionDecl *FuncDecl, const DiagnosticBuilder &Diag, ASTContext &Context)
static constexpr StringRef U16StringViewClassKey
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
std::vector< StringRef > parseStringList(StringRef Option)
Parse a semicolon separated list of strings.
llvm::StringMap< ClangTidyValue > OptionMap
static constexpr const char FuncDecl[]