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 } else {
146 return qualType(
147 anyOf(qualType(pointerType(pointee(InnerMatchers...))),
148 qualType(substTemplateTypeParmType(hasReplacementType(
149 pointerType(pointee(InnerMatchers...)))))));
150 }
151 };
152
153 auto IsAutoDeducedToPointer =
154 [IsPointerType](const std::vector<StringRef> &AllowedTypes,
155 const auto &...InnerMatchers) {
156 return autoType(hasDeducedType(
157 IsPointerType(InnerMatchers...),
158 unless(hasUnqualifiedType(
159 matchers::matchesAnyListedTypeName(AllowedTypes, false))),
160 unless(pointerType(pointee(hasUnqualifiedType(
161 matchers::matchesAnyListedTypeName(AllowedTypes, false)))))));
162 };
163
164 Finder->addMatcher(
165 ExplicitSingleVarDecl(
166 hasType(IsAutoDeducedToPointer(AllowedTypes, UnlessFunctionType)),
167 "auto"),
168 this);
169
170 Finder->addMatcher(
171 ExplicitSingleVarDeclInTemplate(
172 allOf(hasType(IsAutoDeducedToPointer(
173 AllowedTypes, hasUnqualifiedType(qualType().bind("type")),
174 UnlessFunctionType)),
175 anyOf(hasAncestor(
176 functionDecl(hasAnyTemplateArgument(IsBoundToType))),
177 hasAncestor(classTemplateSpecializationDecl(
178 hasAnyTemplateArgument(IsBoundToType))))),
179 "auto"),
180 this);
181 if (!AddConstToQualified)
182 return;
183 Finder->addMatcher(ExplicitSingleVarDecl(
184 hasType(pointerType(pointee(autoType()))), "auto_ptr"),
185 this);
186 Finder->addMatcher(
187 ExplicitSingleVarDecl(hasType(lValueReferenceType(pointee(autoType()))),
188 "auto_ref"),
189 this);
190}
191
192void QualifiedAutoCheck::check(const MatchFinder::MatchResult &Result) {
193 if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("auto")) {
194 SourceRange TypeSpecifier;
195 if (std::optional<SourceRange> TypeSpec =
196 getTypeSpecifierLocation(Var, Result)) {
197 TypeSpecifier = *TypeSpec;
198 } else
199 return;
200
201 llvm::SmallVector<SourceRange, 4> RemoveQualifiersRange;
202 auto CheckQualifier = [&](bool IsPresent, Qualifier Qual) {
203 if (IsPresent) {
204 std::optional<Token> Token = findQualToken(Var, Qual, Result);
205 if (!Token || Token->getLocation().isMacroID())
206 return true; // Disregard this VarDecl.
207 if (std::optional<SourceRange> Result =
208 mergeReplacementRange(TypeSpecifier, *Token))
209 RemoveQualifiersRange.push_back(*Result);
210 }
211 return false;
212 };
213
214 bool IsLocalConst = Var->getType().isLocalConstQualified();
215 bool IsLocalVolatile = Var->getType().isLocalVolatileQualified();
216 bool IsLocalRestrict = Var->getType().isLocalRestrictQualified();
217
218 if (CheckQualifier(IsLocalConst, Qualifier::Const) ||
219 CheckQualifier(IsLocalVolatile, Qualifier::Volatile) ||
220 CheckQualifier(IsLocalRestrict, Qualifier::Restrict))
221 return;
222
223 // Check for bridging the gap between the asterisk and name.
224 if (Var->getLocation() == TypeSpecifier.getEnd().getLocWithOffset(1))
225 TypeSpecifier.setEnd(TypeSpecifier.getEnd().getLocWithOffset(1));
226
227 CharSourceRange FixItRange = CharSourceRange::getCharRange(TypeSpecifier);
228 if (FixItRange.isInvalid())
229 return;
230
231 SourceLocation FixitLoc = FixItRange.getBegin();
232 for (SourceRange &Range : RemoveQualifiersRange) {
233 if (Range.getBegin() < FixitLoc)
234 FixitLoc = Range.getBegin();
235 }
236
237 std::string ReplStr = [&] {
238 llvm::StringRef PtrConst = isPointerConst(Var->getType()) ? "const " : "";
239 llvm::StringRef LocalConst = IsLocalConst ? "const " : "";
240 llvm::StringRef LocalVol = IsLocalVolatile ? "volatile " : "";
241 llvm::StringRef LocalRestrict = IsLocalRestrict ? "__restrict " : "";
242 return (PtrConst + "auto *" + LocalConst + LocalVol + LocalRestrict)
243 .str();
244 }();
245
246 DiagnosticBuilder Diag =
247 diag(FixitLoc,
248 "'%select{|const }0%select{|volatile }1%select{|__restrict }2auto "
249 "%3' can be declared as '%4%3'")
250 << IsLocalConst << IsLocalVolatile << IsLocalRestrict << Var->getName()
251 << ReplStr;
252
253 for (SourceRange &Range : RemoveQualifiersRange) {
254 Diag << FixItHint::CreateRemoval(CharSourceRange::getCharRange(Range));
255 }
256
257 Diag << FixItHint::CreateReplacement(FixItRange, ReplStr);
258 return;
259 }
260 if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("auto_ptr")) {
261 if (!isPointerConst(Var->getType()))
262 return; // Pointer isn't const, no need to add const qualifier.
263 if (!isAutoPointerConst(Var->getType()))
264 return; // Const isn't wrapped in the auto type, so must be declared
265 // explicitly.
266
267 if (Var->getType().isLocalConstQualified()) {
268 std::optional<Token> Token = findQualToken(Var, Qualifier::Const, Result);
269 if (!Token || Token->getLocation().isMacroID())
270 return;
271 }
272 if (Var->getType().isLocalVolatileQualified()) {
273 std::optional<Token> Token =
274 findQualToken(Var, Qualifier::Volatile, Result);
275 if (!Token || Token->getLocation().isMacroID())
276 return;
277 }
278 if (Var->getType().isLocalRestrictQualified()) {
279 std::optional<Token> Token =
280 findQualToken(Var, Qualifier::Restrict, Result);
281 if (!Token || Token->getLocation().isMacroID())
282 return;
283 }
284
285 if (std::optional<SourceRange> TypeSpec =
286 getTypeSpecifierLocation(Var, Result)) {
287 if (TypeSpec->isInvalid() || TypeSpec->getBegin().isMacroID() ||
288 TypeSpec->getEnd().isMacroID())
289 return;
290 SourceLocation InsertPos = TypeSpec->getBegin();
291 diag(InsertPos,
292 "'auto *%select{|const }0%select{|volatile }1%2' can be declared as "
293 "'const auto *%select{|const }0%select{|volatile }1%2'")
294 << Var->getType().isLocalConstQualified()
295 << Var->getType().isLocalVolatileQualified() << Var->getName()
296 << FixItHint::CreateInsertion(InsertPos, "const ");
297 }
298 return;
299 }
300 if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("auto_ref")) {
301 if (!isPointerConst(Var->getType()))
302 return; // Pointer isn't const, no need to add const qualifier.
303 if (!isAutoPointerConst(Var->getType()))
304 // Const isn't wrapped in the auto type, so must be declared explicitly.
305 return;
306
307 if (std::optional<SourceRange> TypeSpec =
308 getTypeSpecifierLocation(Var, Result)) {
309 if (TypeSpec->isInvalid() || TypeSpec->getBegin().isMacroID() ||
310 TypeSpec->getEnd().isMacroID())
311 return;
312 SourceLocation InsertPos = TypeSpec->getBegin();
313 diag(InsertPos, "'auto &%0' can be declared as 'const auto &%0'")
314 << Var->getName() << FixItHint::CreateInsertion(InsertPos, "const ");
315 }
316 return;
317 }
318}
319
320} // 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