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