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

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