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