150
|
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"
|
252
|
13 #include <optional>
|
150
|
14
|
|
15 using namespace clang::ast_matchers;
|
|
16
|
252
|
17 namespace clang::tidy::readability {
|
150
|
18
|
|
19 namespace {
|
|
20
|
|
21 // FIXME move to ASTMatchers
|
|
22 AST_MATCHER_P(QualType, hasUnqualifiedType,
|
|
23 ast_matchers::internal::Matcher<QualType>, InnerMatcher) {
|
|
24 return InnerMatcher.matches(Node.getUnqualifiedType(), Finder, Builder);
|
|
25 }
|
|
26
|
|
27 enum class Qualifier { Const, Volatile, Restrict };
|
|
28
|
252
|
29 std::optional<Token> findQualToken(const VarDecl *Decl, Qualifier Qual,
|
|
30 const MatchFinder::MatchResult &Result) {
|
150
|
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 CharSourceRange::getCharRange(BeginLoc, EndLoc), *Result.SourceManager,
|
|
46 Result.Context->getLangOpts());
|
|
47
|
|
48 if (FileRange.isInvalid())
|
252
|
49 return std::nullopt;
|
150
|
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(Tok, FileRange, *Result.Context,
|
|
57 *Result.SourceManager);
|
|
58 }
|
|
59
|
252
|
60 std::optional<SourceRange>
|
150
|
61 getTypeSpecifierLocation(const VarDecl *Var,
|
|
62 const MatchFinder::MatchResult &Result) {
|
|
63 SourceRange TypeSpecifier(
|
|
64 Var->getTypeSpecStartLoc(),
|
|
65 Var->getTypeSpecEndLoc().getLocWithOffset(Lexer::MeasureTokenLength(
|
|
66 Var->getTypeSpecEndLoc(), *Result.SourceManager,
|
|
67 Result.Context->getLangOpts())));
|
|
68
|
|
69 if (TypeSpecifier.getBegin().isMacroID() ||
|
|
70 TypeSpecifier.getEnd().isMacroID())
|
252
|
71 return std::nullopt;
|
150
|
72 return TypeSpecifier;
|
|
73 }
|
|
74
|
252
|
75 std::optional<SourceRange> mergeReplacementRange(SourceRange &TypeSpecifier,
|
|
76 const Token &ConstToken) {
|
150
|
77 if (TypeSpecifier.getBegin().getLocWithOffset(-1) == ConstToken.getEndLoc()) {
|
|
78 TypeSpecifier.setBegin(ConstToken.getLocation());
|
252
|
79 return std::nullopt;
|
150
|
80 }
|
|
81 if (TypeSpecifier.getEnd().getLocWithOffset(1) == ConstToken.getLocation()) {
|
|
82 TypeSpecifier.setEnd(ConstToken.getEndLoc());
|
252
|
83 return std::nullopt;
|
150
|
84 }
|
|
85 return SourceRange(ConstToken.getLocation(), ConstToken.getEndLoc());
|
|
86 }
|
|
87
|
|
88 bool isPointerConst(QualType QType) {
|
|
89 QualType Pointee = QType->getPointeeType();
|
|
90 assert(!Pointee.isNull() && "can't have a null Pointee");
|
|
91 return Pointee.isConstQualified();
|
|
92 }
|
|
93
|
|
94 bool isAutoPointerConst(QualType QType) {
|
|
95 QualType Pointee =
|
|
96 cast<AutoType>(QType->getPointeeType().getTypePtr())->desugar();
|
|
97 assert(!Pointee.isNull() && "can't have a null Pointee");
|
|
98 return Pointee.isConstQualified();
|
|
99 }
|
|
100
|
|
101 } // namespace
|
|
102
|
|
103 void QualifiedAutoCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
|
|
104 Options.store(Opts, "AddConstToQualified", AddConstToQualified);
|
|
105 }
|
|
106
|
|
107 void 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 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 varDecl(unless(isImplicit()), InnerMatcher).bind(ID)));
|
|
123 };
|
|
124
|
|
125 auto IsBoundToType = refersToType(equalsBoundNode("type"));
|
236
|
126 auto UnlessFunctionType = unless(hasUnqualifiedDesugaredType(functionType()));
|
|
127 auto IsAutoDeducedToPointer = [](const auto &...InnerMatchers) {
|
|
128 return autoType(hasDeducedType(
|
|
129 hasUnqualifiedDesugaredType(pointerType(pointee(InnerMatchers...)))));
|
|
130 };
|
150
|
131
|
|
132 Finder->addMatcher(
|
236
|
133 ExplicitSingleVarDecl(hasType(IsAutoDeducedToPointer(UnlessFunctionType)),
|
150
|
134 "auto"),
|
|
135 this);
|
|
136
|
|
137 Finder->addMatcher(
|
|
138 ExplicitSingleVarDeclInTemplate(
|
236
|
139 allOf(hasType(IsAutoDeducedToPointer(
|
|
140 hasUnqualifiedType(qualType().bind("type")),
|
|
141 UnlessFunctionType)),
|
150
|
142 anyOf(hasAncestor(
|
|
143 functionDecl(hasAnyTemplateArgument(IsBoundToType))),
|
|
144 hasAncestor(classTemplateSpecializationDecl(
|
|
145 hasAnyTemplateArgument(IsBoundToType))))),
|
|
146 "auto"),
|
|
147 this);
|
|
148 if (!AddConstToQualified)
|
|
149 return;
|
|
150 Finder->addMatcher(ExplicitSingleVarDecl(
|
|
151 hasType(pointerType(pointee(autoType()))), "auto_ptr"),
|
|
152 this);
|
|
153 Finder->addMatcher(
|
|
154 ExplicitSingleVarDecl(hasType(lValueReferenceType(pointee(autoType()))),
|
|
155 "auto_ref"),
|
|
156 this);
|
|
157 }
|
|
158
|
|
159 void QualifiedAutoCheck::check(const MatchFinder::MatchResult &Result) {
|
|
160 if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("auto")) {
|
|
161 SourceRange TypeSpecifier;
|
252
|
162 if (std::optional<SourceRange> TypeSpec =
|
150
|
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) {
|
252
|
171 std::optional<Token> Token = findQualToken(Var, Qual, Result);
|
150
|
172 if (!Token || Token->getLocation().isMacroID())
|
|
173 return true; // Disregard this VarDecl.
|
252
|
174 if (std::optional<SourceRange> Result =
|
150
|
175 mergeReplacementRange(TypeSpecifier, *Token))
|
|
176 RemoveQualifiersRange.push_back(*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(1))
|
|
192 TypeSpecifier.setEnd(TypeSpecifier.getEnd().getLocWithOffset(1));
|
|
193
|
|
194 CharSourceRange FixItRange = CharSourceRange::getCharRange(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 =
|
221
|
214 diag(FixitLoc,
|
|
215 "'%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;
|
150
|
219
|
|
220 for (SourceRange &Range : RemoveQualifiersRange) {
|
|
221 Diag << FixItHint::CreateRemoval(CharSourceRange::getCharRange(Range));
|
|
222 }
|
|
223
|
|
224 Diag << FixItHint::CreateReplacement(FixItRange, ReplStr);
|
|
225 return;
|
|
226 }
|
|
227 if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("auto_ptr")) {
|
|
228 if (!isPointerConst(Var->getType()))
|
|
229 return; // Pointer isn't const, no need to add const qualifier.
|
|
230 if (!isAutoPointerConst(Var->getType()))
|
236
|
231 return; // Const isn't wrapped in the auto type, so must be declared
|
150
|
232 // explicitly.
|
|
233
|
|
234 if (Var->getType().isLocalConstQualified()) {
|
252
|
235 std::optional<Token> Token = findQualToken(Var, Qualifier::Const, Result);
|
150
|
236 if (!Token || Token->getLocation().isMacroID())
|
|
237 return;
|
|
238 }
|
|
239 if (Var->getType().isLocalVolatileQualified()) {
|
252
|
240 std::optional<Token> Token =
|
150
|
241 findQualToken(Var, Qualifier::Volatile, Result);
|
|
242 if (!Token || Token->getLocation().isMacroID())
|
|
243 return;
|
|
244 }
|
|
245 if (Var->getType().isLocalRestrictQualified()) {
|
252
|
246 std::optional<Token> Token =
|
150
|
247 findQualToken(Var, Qualifier::Restrict, Result);
|
|
248 if (!Token || Token->getLocation().isMacroID())
|
|
249 return;
|
|
250 }
|
|
251
|
252
|
252 if (std::optional<SourceRange> TypeSpec =
|
150
|
253 getTypeSpecifierLocation(Var, Result)) {
|
|
254 if (TypeSpec->isInvalid() || TypeSpec->getBegin().isMacroID() ||
|
|
255 TypeSpec->getEnd().isMacroID())
|
|
256 return;
|
|
257 SourceLocation InsertPos = TypeSpec->getBegin();
|
221
|
258 diag(InsertPos,
|
|
259 "'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(InsertPos, "const ");
|
150
|
264 }
|
|
265 return;
|
|
266 }
|
|
267 if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("auto_ref")) {
|
|
268 if (!isPointerConst(Var->getType()))
|
|
269 return; // Pointer isn't const, no need to add const qualifier.
|
|
270 if (!isAutoPointerConst(Var->getType()))
|
236
|
271 // Const isn't wrapped in the auto type, so must be declared explicitly.
|
150
|
272 return;
|
|
273
|
252
|
274 if (std::optional<SourceRange> TypeSpec =
|
150
|
275 getTypeSpecifierLocation(Var, Result)) {
|
|
276 if (TypeSpec->isInvalid() || TypeSpec->getBegin().isMacroID() ||
|
|
277 TypeSpec->getEnd().isMacroID())
|
|
278 return;
|
|
279 SourceLocation InsertPos = TypeSpec->getBegin();
|
|
280 diag(InsertPos, "'auto &%0' can be declared as 'const auto &%0'")
|
|
281 << Var->getName() << FixItHint::CreateInsertion(InsertPos, "const ");
|
|
282 }
|
|
283 return;
|
|
284 }
|
|
285 }
|
|
286
|
252
|
287 } // namespace clang::tidy::readability
|