1//===--- QualifiedAutoCheck.cpp - clang-tidy ------------------------------===//
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 "QualifiedAutoCheck.h"
10#include "../utils/LexerUtils.h"
11#include "../utils/Matchers.h"
12#include "../utils/OptionsUtils.h"
13#include "clang/ASTMatchers/ASTMatchers.h"
14#include "llvm/ADT/SmallVector.h"
15#include <optional>
16
17using namespace clang::ast_matchers;
18
19namespace clang::tidy::readability {
20
21namespace {
22
23// FIXME move to ASTMatchers
24AST_MATCHER_P(QualType, hasUnqualifiedType,
25 ast_matchers::internal::Matcher<QualType>, InnerMatcher) {
26 return InnerMatcher.matches(Node: Node.getUnqualifiedType(), Finder, Builder);
27}
28
29enum class Qualifier { Const, Volatile, Restrict };
30
31std::optional<Token> findQualToken(const VarDecl *Decl, Qualifier Qual,
32 const MatchFinder::MatchResult &Result) {
33 // Since either of the locs can be in a macro, use `makeFileCharRange` to be
34 // sure that we have a consistent `CharSourceRange`, located entirely in the
35 // source file.
36
37 assert((Qual == Qualifier::Const || Qual == Qualifier::Volatile ||
38 Qual == Qualifier::Restrict) &&
39 "Invalid Qualifier");
40
41 SourceLocation BeginLoc = Decl->getQualifierLoc().getBeginLoc();
42 if (BeginLoc.isInvalid())
43 BeginLoc = Decl->getBeginLoc();
44 SourceLocation EndLoc = Decl->getLocation();
45
46 CharSourceRange FileRange = Lexer::makeFileCharRange(
47 Range: CharSourceRange::getCharRange(B: BeginLoc, E: EndLoc), SM: *Result.SourceManager,
48 LangOpts: Result.Context->getLangOpts());
49
50 if (FileRange.isInvalid())
51 return std::nullopt;
52
53 tok::TokenKind Tok = Qual == Qualifier::Const ? tok::kw_const
54 : Qual == Qualifier::Volatile ? tok::kw_volatile
55 : tok::kw_restrict;
56
57 return utils::lexer::getQualifyingToken(TK: Tok, Range: FileRange, Context: *Result.Context,
58 SM: *Result.SourceManager);
59}
60
61std::optional<SourceRange>
62getTypeSpecifierLocation(const VarDecl *Var,
63 const MatchFinder::MatchResult &Result) {
64 SourceRange TypeSpecifier(
65 Var->getTypeSpecStartLoc(),
66 Var->getTypeSpecEndLoc().getLocWithOffset(Lexer::MeasureTokenLength(
67 Loc: Var->getTypeSpecEndLoc(), SM: *Result.SourceManager,
68 LangOpts: Result.Context->getLangOpts())));
69
70 if (TypeSpecifier.getBegin().isMacroID() ||
71 TypeSpecifier.getEnd().isMacroID())
72 return std::nullopt;
73 return TypeSpecifier;
74}
75
76std::optional<SourceRange> mergeReplacementRange(SourceRange &TypeSpecifier,
77 const Token &ConstToken) {
78 if (TypeSpecifier.getBegin().getLocWithOffset(Offset: -1) == ConstToken.getEndLoc()) {
79 TypeSpecifier.setBegin(ConstToken.getLocation());
80 return std::nullopt;
81 }
82 if (TypeSpecifier.getEnd().getLocWithOffset(Offset: 1) == ConstToken.getLocation()) {
83 TypeSpecifier.setEnd(ConstToken.getEndLoc());
84 return std::nullopt;
85 }
86 return SourceRange(ConstToken.getLocation(), ConstToken.getEndLoc());
87}
88
89bool isPointerConst(QualType QType) {
90 QualType Pointee = QType->getPointeeType();
91 assert(!Pointee.isNull() && "can't have a null Pointee");
92 return Pointee.isConstQualified();
93}
94
95bool isAutoPointerConst(QualType QType) {
96 QualType Pointee =
97 cast<AutoType>(Val: QType->getPointeeType().getTypePtr())->desugar();
98 assert(!Pointee.isNull() && "can't have a null Pointee");
99 return Pointee.isConstQualified();
100}
101
102} // namespace
103
104QualifiedAutoCheck::QualifiedAutoCheck(StringRef Name,
105 ClangTidyContext *Context)
106 : ClangTidyCheck(Name, Context),
107 AddConstToQualified(Options.get(LocalName: "AddConstToQualified", Default: true)),
108 AllowedTypes(
109 utils::options::parseStringList(Option: Options.get(LocalName: "AllowedTypes", Default: ""))) {}
110
111void QualifiedAutoCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
112 Options.store(Options&: Opts, LocalName: "AddConstToQualified", Value: AddConstToQualified);
113 Options.store(Options&: Opts, LocalName: "AllowedTypes",
114 Value: utils::options::serializeStringList(Strings: AllowedTypes));
115}
116
117void QualifiedAutoCheck::registerMatchers(MatchFinder *Finder) {
118 auto ExplicitSingleVarDecl =
119 [](const ast_matchers::internal::Matcher<VarDecl> &InnerMatcher,
120 llvm::StringRef ID) {
121 return declStmt(
122 unless(isInTemplateInstantiation()),
123 hasSingleDecl(
124 InnerMatcher: varDecl(unless(isImplicit()), InnerMatcher).bind(ID)));
125 };
126 auto ExplicitSingleVarDeclInTemplate =
127 [](const ast_matchers::internal::Matcher<VarDecl> &InnerMatcher,
128 llvm::StringRef ID) {
129 return declStmt(
130 isInTemplateInstantiation(),
131 hasSingleDecl(
132 InnerMatcher: varDecl(unless(isImplicit()), InnerMatcher).bind(ID)));
133 };
134
135 auto IsBoundToType = refersToType(InnerMatcher: equalsBoundNode(ID: "type"));
136 auto UnlessFunctionType = unless(hasUnqualifiedDesugaredType(InnerMatcher: functionType()));
137 auto IsAutoDeducedToPointer = [](const std::vector<StringRef> &AllowedTypes,
138 const auto &...InnerMatchers) {
139 return autoType(hasDeducedType(
140 hasUnqualifiedDesugaredType(pointerType(pointee(InnerMatchers...))),
141 unless(hasUnqualifiedType(
142 InnerMatcher: matchers::matchesAnyListedTypeName(NameList: AllowedTypes, CanonicalTypes: false))),
143 unless(pointerType(pointee(hasUnqualifiedType(
144 InnerMatcher: matchers::matchesAnyListedTypeName(NameList: AllowedTypes, CanonicalTypes: false)))))));
145 };
146
147 Finder->addMatcher(
148 NodeMatch: ExplicitSingleVarDecl(
149 hasType(InnerMatcher: IsAutoDeducedToPointer(AllowedTypes, UnlessFunctionType)),
150 "auto"),
151 Action: this);
152
153 Finder->addMatcher(
154 NodeMatch: ExplicitSingleVarDeclInTemplate(
155 allOf(hasType(InnerMatcher: IsAutoDeducedToPointer(
156 AllowedTypes, hasUnqualifiedType(InnerMatcher: qualType().bind(ID: "type")),
157 UnlessFunctionType)),
158 anyOf(hasAncestor(
159 functionDecl(hasAnyTemplateArgument(InnerMatcher: IsBoundToType))),
160 hasAncestor(classTemplateSpecializationDecl(
161 hasAnyTemplateArgument(InnerMatcher: IsBoundToType))))),
162 "auto"),
163 Action: this);
164 if (!AddConstToQualified)
165 return;
166 Finder->addMatcher(NodeMatch: ExplicitSingleVarDecl(
167 hasType(InnerMatcher: pointerType(pointee(autoType()))), "auto_ptr"),
168 Action: this);
169 Finder->addMatcher(
170 NodeMatch: ExplicitSingleVarDecl(hasType(InnerMatcher: lValueReferenceType(pointee(autoType()))),
171 "auto_ref"),
172 Action: this);
173}
174
175void QualifiedAutoCheck::check(const MatchFinder::MatchResult &Result) {
176 if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>(ID: "auto")) {
177 SourceRange TypeSpecifier;
178 if (std::optional<SourceRange> TypeSpec =
179 getTypeSpecifierLocation(Var, Result)) {
180 TypeSpecifier = *TypeSpec;
181 } else
182 return;
183
184 llvm::SmallVector<SourceRange, 4> RemoveQualifiersRange;
185 auto CheckQualifier = [&](bool IsPresent, Qualifier Qual) {
186 if (IsPresent) {
187 std::optional<Token> Token = findQualToken(Decl: Var, Qual, Result);
188 if (!Token || Token->getLocation().isMacroID())
189 return true; // Disregard this VarDecl.
190 if (std::optional<SourceRange> Result =
191 mergeReplacementRange(TypeSpecifier, ConstToken: *Token))
192 RemoveQualifiersRange.push_back(Elt: *Result);
193 }
194 return false;
195 };
196
197 bool IsLocalConst = Var->getType().isLocalConstQualified();
198 bool IsLocalVolatile = Var->getType().isLocalVolatileQualified();
199 bool IsLocalRestrict = Var->getType().isLocalRestrictQualified();
200
201 if (CheckQualifier(IsLocalConst, Qualifier::Const) ||
202 CheckQualifier(IsLocalVolatile, Qualifier::Volatile) ||
203 CheckQualifier(IsLocalRestrict, Qualifier::Restrict))
204 return;
205
206 // Check for bridging the gap between the asterisk and name.
207 if (Var->getLocation() == TypeSpecifier.getEnd().getLocWithOffset(Offset: 1))
208 TypeSpecifier.setEnd(TypeSpecifier.getEnd().getLocWithOffset(Offset: 1));
209
210 CharSourceRange FixItRange = CharSourceRange::getCharRange(R: TypeSpecifier);
211 if (FixItRange.isInvalid())
212 return;
213
214 SourceLocation FixitLoc = FixItRange.getBegin();
215 for (SourceRange &Range : RemoveQualifiersRange) {
216 if (Range.getBegin() < FixitLoc)
217 FixitLoc = Range.getBegin();
218 }
219
220 std::string ReplStr = [&] {
221 llvm::StringRef PtrConst = isPointerConst(Var->getType()) ? "const " : "";
222 llvm::StringRef LocalConst = IsLocalConst ? "const " : "";
223 llvm::StringRef LocalVol = IsLocalVolatile ? "volatile " : "";
224 llvm::StringRef LocalRestrict = IsLocalRestrict ? "__restrict " : "";
225 return (PtrConst + "auto *" + LocalConst + LocalVol + LocalRestrict)
226 .str();
227 }();
228
229 DiagnosticBuilder Diag =
230 diag(Loc: FixitLoc,
231 Description: "'%select{|const }0%select{|volatile }1%select{|__restrict }2auto "
232 "%3' can be declared as '%4%3'")
233 << IsLocalConst << IsLocalVolatile << IsLocalRestrict << Var->getName()
234 << ReplStr;
235
236 for (SourceRange &Range : RemoveQualifiersRange) {
237 Diag << FixItHint::CreateRemoval(RemoveRange: CharSourceRange::getCharRange(R: Range));
238 }
239
240 Diag << FixItHint::CreateReplacement(RemoveRange: FixItRange, Code: ReplStr);
241 return;
242 }
243 if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>(ID: "auto_ptr")) {
244 if (!isPointerConst(Var->getType()))
245 return; // Pointer isn't const, no need to add const qualifier.
246 if (!isAutoPointerConst(Var->getType()))
247 return; // Const isn't wrapped in the auto type, so must be declared
248 // explicitly.
249
250 if (Var->getType().isLocalConstQualified()) {
251 std::optional<Token> Token = findQualToken(Decl: Var, Qual: Qualifier::Const, Result);
252 if (!Token || Token->getLocation().isMacroID())
253 return;
254 }
255 if (Var->getType().isLocalVolatileQualified()) {
256 std::optional<Token> Token =
257 findQualToken(Decl: Var, Qual: Qualifier::Volatile, Result);
258 if (!Token || Token->getLocation().isMacroID())
259 return;
260 }
261 if (Var->getType().isLocalRestrictQualified()) {
262 std::optional<Token> Token =
263 findQualToken(Decl: Var, Qual: Qualifier::Restrict, Result);
264 if (!Token || Token->getLocation().isMacroID())
265 return;
266 }
267
268 if (std::optional<SourceRange> TypeSpec =
269 getTypeSpecifierLocation(Var, Result)) {
270 if (TypeSpec->isInvalid() || TypeSpec->getBegin().isMacroID() ||
271 TypeSpec->getEnd().isMacroID())
272 return;
273 SourceLocation InsertPos = TypeSpec->getBegin();
274 diag(Loc: InsertPos,
275 Description: "'auto *%select{|const }0%select{|volatile }1%2' can be declared as "
276 "'const auto *%select{|const }0%select{|volatile }1%2'")
277 << Var->getType().isLocalConstQualified()
278 << Var->getType().isLocalVolatileQualified() << Var->getName()
279 << FixItHint::CreateInsertion(InsertionLoc: InsertPos, Code: "const ");
280 }
281 return;
282 }
283 if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>(ID: "auto_ref")) {
284 if (!isPointerConst(Var->getType()))
285 return; // Pointer isn't const, no need to add const qualifier.
286 if (!isAutoPointerConst(Var->getType()))
287 // Const isn't wrapped in the auto type, so must be declared explicitly.
288 return;
289
290 if (std::optional<SourceRange> TypeSpec =
291 getTypeSpecifierLocation(Var, Result)) {
292 if (TypeSpec->isInvalid() || TypeSpec->getBegin().isMacroID() ||
293 TypeSpec->getEnd().isMacroID())
294 return;
295 SourceLocation InsertPos = TypeSpec->getBegin();
296 diag(Loc: InsertPos, Description: "'auto &%0' can be declared as 'const auto &%0'")
297 << Var->getName() << FixItHint::CreateInsertion(InsertionLoc: InsertPos, Code: "const ");
298 }
299 return;
300 }
301}
302
303} // namespace clang::tidy::readability
304

source code of clang-tools-extra/clang-tidy/readability/QualifiedAutoCheck.cpp