xref: /llvm-project/clang-tools-extra/clang-tidy/readability/QualifiedAutoCheck.cpp (revision 7d2ea6c422d3f5712b7253407005e1a465a76946)
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 
15 using namespace clang::ast_matchers;
16 
17 namespace clang::tidy::readability {
18 
19 namespace {
20 
21 // FIXME move to ASTMatchers
AST_MATCHER_P(QualType,hasUnqualifiedType,ast_matchers::internal::Matcher<QualType>,InnerMatcher)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 
findQualToken(const VarDecl * Decl,Qualifier Qual,const MatchFinder::MatchResult & Result)29 std::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       CharSourceRange::getCharRange(BeginLoc, EndLoc), *Result.SourceManager,
46       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(Tok, FileRange, *Result.Context,
57                                           *Result.SourceManager);
58 }
59 
60 std::optional<SourceRange>
getTypeSpecifierLocation(const VarDecl * Var,const MatchFinder::MatchResult & Result)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())
71     return std::nullopt;
72   return TypeSpecifier;
73 }
74 
mergeReplacementRange(SourceRange & TypeSpecifier,const Token & ConstToken)75 std::optional<SourceRange> mergeReplacementRange(SourceRange &TypeSpecifier,
76                                                  const Token &ConstToken) {
77   if (TypeSpecifier.getBegin().getLocWithOffset(-1) == ConstToken.getEndLoc()) {
78     TypeSpecifier.setBegin(ConstToken.getLocation());
79     return std::nullopt;
80   }
81   if (TypeSpecifier.getEnd().getLocWithOffset(1) == ConstToken.getLocation()) {
82     TypeSpecifier.setEnd(ConstToken.getEndLoc());
83     return std::nullopt;
84   }
85   return SourceRange(ConstToken.getLocation(), ConstToken.getEndLoc());
86 }
87 
isPointerConst(QualType QType)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 
isAutoPointerConst(QualType QType)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 
storeOptions(ClangTidyOptions::OptionMap & Opts)103 void QualifiedAutoCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
104   Options.store(Opts, "AddConstToQualified", AddConstToQualified);
105 }
106 
registerMatchers(MatchFinder * Finder)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"));
126   auto UnlessFunctionType = unless(hasUnqualifiedDesugaredType(functionType()));
127   auto IsAutoDeducedToPointer = [](const auto &...InnerMatchers) {
128     return autoType(hasDeducedType(
129         hasUnqualifiedDesugaredType(pointerType(pointee(InnerMatchers...)))));
130   };
131 
132   Finder->addMatcher(
133       ExplicitSingleVarDecl(hasType(IsAutoDeducedToPointer(UnlessFunctionType)),
134                             "auto"),
135       this);
136 
137   Finder->addMatcher(
138       ExplicitSingleVarDeclInTemplate(
139           allOf(hasType(IsAutoDeducedToPointer(
140                     hasUnqualifiedType(qualType().bind("type")),
141                     UnlessFunctionType)),
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 
check(const MatchFinder::MatchResult & Result)159 void QualifiedAutoCheck::check(const MatchFinder::MatchResult &Result) {
160   if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("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(Var, Qual, Result);
172         if (!Token || Token->getLocation().isMacroID())
173           return true; // Disregard this VarDecl.
174         if (std::optional<SourceRange> Result =
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 =
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;
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()))
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(Var, Qualifier::Const, Result);
236       if (!Token || Token->getLocation().isMacroID())
237         return;
238     }
239     if (Var->getType().isLocalVolatileQualified()) {
240       std::optional<Token> Token =
241           findQualToken(Var, Qualifier::Volatile, Result);
242       if (!Token || Token->getLocation().isMacroID())
243         return;
244     }
245     if (Var->getType().isLocalRestrictQualified()) {
246       std::optional<Token> Token =
247           findQualToken(Var, 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(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 ");
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()))
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(InsertPos, "'auto &%0' can be declared as 'const auto &%0'")
281           << Var->getName() << FixItHint::CreateInsertion(InsertPos, "const ");
282     }
283     return;
284   }
285 }
286 
287 } // namespace clang::tidy::readability
288