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(FunctionDecl, isOverloaded) {
26 const DeclarationName Name = Node.getDeclName();
27 // Sanity check
28 if (Name.isEmpty())
29 return false;
30 const DeclContext *DC = Node.getDeclContext();
31 auto LookupResult = DC->lookup(Name);
32 size_t UniqueSignatures = 0;
33 llvm::SmallPtrSet<const FunctionDecl *, 2> SeenFunctions;
34 for (NamedDecl *ND : LookupResult) {
35 const FunctionDecl *FD = nullptr;
36 if (const auto *Func = dyn_cast<FunctionDecl>(ND)) {
37 // Regular functions
38 FD = Func;
39 } else if (const auto *USD = dyn_cast<UsingShadowDecl>(ND)) {
40 // Overloads via "using ns::func_name"
41 FD = dyn_cast<FunctionDecl>(USD->getTargetDecl());
42 } else if (const auto *FTD = dyn_cast<FunctionTemplateDecl>(ND)) {
43 // Templated functions
44 FD = FTD->getTemplatedDecl();
45 }
46 if (FD && SeenFunctions.insert(FD->getCanonicalDecl()).second) {
47 UniqueSignatures++;
48 if (UniqueSignatures > 1)
49 return true;
50 }
51 }
52 return false;
53}
54} // namespace
55
56static constexpr StringRef StringViewClassKey = "string";
57static constexpr StringRef WStringViewClassKey = "wstring";
58static constexpr StringRef U8StringViewClassKey = "u8string";
59static constexpr StringRef U16StringViewClassKey = "u16string";
60static constexpr StringRef U32StringViewClassKey = "u32string";
61
62static auto getStringTypeMatcher(StringRef CharType) {
63 return hasCanonicalType(hasDeclaration(cxxRecordDecl(hasName(CharType))));
64}
65
66static void fixReturns(const FunctionDecl *FuncDecl, DiagnosticBuilder &Diag,
67 ASTContext &Context) {
68 auto Matches = match(
69 findAll(returnStmt(hasReturnValue(ignoringParenImpCasts(
70 cxxTemporaryObjectExpr(argumentCountIs(0)).bind("temp_obj_expr"))))),
71 *FuncDecl->getBody(), Context);
72
73 for (const auto &Match : Matches)
74 if (const auto *TempObjExpr =
75 Match.getNodeAs<CXXTemporaryObjectExpr>("temp_obj_expr");
76 TempObjExpr && TempObjExpr->getSourceRange().isValid())
77 Diag << FixItHint::CreateReplacement(TempObjExpr->getSourceRange(), "{}");
78}
79
81 ClangTidyContext *Context)
82 : ClangTidyCheck(Name, Context),
83 CheckOverloadedFunctions(Options.get("CheckOverloadedFunctions", false)),
84 IgnoredFunctions(utils::options::parseStringList(
85 Options.get("IgnoredFunctions", "toString$;ToString$;to_string$"))) {
86 parseReplacementStringViewClass(
87 Options.get("ReplacementStringViewClass", ""));
88}
89
91 Options.store(Opts, "CheckOverloadedFunctions", CheckOverloadedFunctions);
92 Options.store(Opts, "IgnoredFunctions",
93 utils::options::serializeStringList(IgnoredFunctions));
94 Options.store(Opts, "ReplacementStringViewClass",
95 (Twine("") + StringViewClassKey + "=" + StringViewClass + ";" +
96 WStringViewClassKey + "=" + WStringViewClass + ";" +
97 U8StringViewClassKey + "=" + U8StringViewClass + ";" +
98 U16StringViewClassKey + "=" + U16StringViewClass + ";" +
99 U32StringViewClassKey + "=" + U32StringViewClass)
100 .str());
101}
102
103void UseStringViewCheck::registerMatchers(MatchFinder *Finder) {
104 const auto IsStdString = getStringTypeMatcher("::std::basic_string");
105 // TODO: also consider *StringViewClass types
106 const auto IsStdStringView = getStringTypeMatcher("::std::basic_string_view");
107 const auto IgnoredFunctionsMatcher =
108 matchers::matchesAnyListedRegexName(IgnoredFunctions);
109 const auto TernaryOperator = conditionalOperator(
110 hasTrueExpression(ignoringParenImpCasts(stringLiteral())),
111 hasFalseExpression(ignoringParenImpCasts(stringLiteral())));
112 const auto VirtualOrOperator =
113 cxxMethodDecl(anyOf(cxxConversionDecl(), isVirtual()));
114 const auto CheckOverloaded =
115 CheckOverloadedFunctions ? unless(anything()) : isOverloaded();
116 Finder->addMatcher(
117 functionDecl(
118 isDefinition(),
119 unless(anyOf(VirtualOrOperator, IgnoredFunctionsMatcher,
120 CheckOverloaded,
121 ast_matchers::isExplicitTemplateSpecialization())),
122 returns(IsStdString), hasDescendant(returnStmt()),
123 unless(hasDescendant(returnStmt(hasReturnValue(unless(
124 anyOf(stringLiteral(), hasType(IsStdStringView), TernaryOperator,
125 cxxConstructExpr(anyOf(
126 allOf(hasType(IsStdString), argumentCountIs(0)),
127 allOf(isListInitialization(),
128 unless(cxxTemporaryObjectExpr()),
129 hasArgument(0, ignoringParenImpCasts(
130 stringLiteral()))))))))))))
131 .bind("func"),
132 this);
133}
134
135void UseStringViewCheck::check(const MatchFinder::MatchResult &Result) {
136 const auto *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>("func");
137 assert(MatchedDecl);
138 bool ShouldAKA = false;
139 const std::string DesugaredTypeStr =
140 desugarForDiagnostic(*Result.Context,
141 QualType(MatchedDecl->getReturnType()), ShouldAKA)
142 .getAsString();
143 const StringRef DestReturnTypeStr = toStringViewTypeStr(DesugaredTypeStr);
144
145 auto Diag =
146 diag(MatchedDecl->getTypeSpecStartLoc(),
147 "consider using '%0' to avoid unnecessary copying and allocations")
148 << DestReturnTypeStr;
149
150 fixReturns(MatchedDecl, Diag, *Result.Context);
151
152 for (const auto *FuncDecl : MatchedDecl->redecls())
153 if (const SourceRange ReturnTypeRange =
154 FuncDecl->getReturnTypeSourceRange();
155 ReturnTypeRange.isValid())
156 Diag << FixItHint::CreateReplacement(ReturnTypeRange, DestReturnTypeStr);
157}
158
159StringRef UseStringViewCheck::toStringViewTypeStr(StringRef Type) const {
160 if (Type.contains("wchar_t"))
161 return WStringViewClass;
162 if (Type.contains("char8_t"))
163 return U8StringViewClass;
164 if (Type.contains("char16_t"))
165 return U16StringViewClass;
166 if (Type.contains("char32_t"))
167 return U32StringViewClass;
168 return StringViewClass;
169}
170
171void UseStringViewCheck::parseReplacementStringViewClass(StringRef Options) {
172 if (Options.empty())
173 return;
174 const llvm::StringMap<StringRef *> StringClassesMap{
175 {StringViewClassKey, &StringViewClass},
176 {WStringViewClassKey, &WStringViewClass},
177 {U8StringViewClassKey, &U8StringViewClass},
178 {U16StringViewClassKey, &U16StringViewClass},
179 {U32StringViewClassKey, &U32StringViewClass}};
180 for (const auto &Option : utils::options::parseStringList(Options)) {
181 const auto Split = Option.split('=');
182 if (auto It = StringClassesMap.find(Split.first);
183 It != StringClassesMap.end())
184 *It->second = Split.second;
185 }
186}
187
188} // 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 void fixReturns(const FunctionDecl *FuncDecl, DiagnosticBuilder &Diag, ASTContext &Context)
static constexpr StringRef StringViewClassKey
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[]