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, "'%0%1%2auto %3' can be declared as '%4%3'")
213 << (IsLocalConst ? "const " : "")
214 << (IsLocalVolatile ? "volatile " : "")
215 << (IsLocalRestrict ? "__restrict " : "") << Var->getName() << ReplStr;
216
217 for (SourceRange &Range : RemoveQualifiersRange) {
218 Diag << FixItHint::CreateRemoval(CharSourceRange::getCharRange(Range));
219 }
220
221 Diag << FixItHint::CreateReplacement(FixItRange, ReplStr);
222 return;
223 }
224 if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("auto_ptr")) {
225 if (!isPointerConst(Var->getType()))
226 return; // Pointer isn't const, no need to add const qualifier.
227 if (!isAutoPointerConst(Var->getType()))
228 return; // Const isnt wrapped in the auto type, so must be declared
229 // explicitly.
230
231 if (Var->getType().isLocalConstQualified()) {
232 llvm::Optional<Token> Token =
233 findQualToken(Var, Qualifier::Const, Result);
234 if (!Token || Token->getLocation().isMacroID())
235 return;
236 }
237 if (Var->getType().isLocalVolatileQualified()) {
238 llvm::Optional<Token> Token =
239 findQualToken(Var, Qualifier::Volatile, Result);
240 if (!Token || Token->getLocation().isMacroID())
241 return;
242 }
243 if (Var->getType().isLocalRestrictQualified()) {
244 llvm::Optional<Token> Token =
245 findQualToken(Var, Qualifier::Restrict, Result);
246 if (!Token || Token->getLocation().isMacroID())
247 return;
248 }
249
250 if (llvm::Optional<SourceRange> TypeSpec =
251 getTypeSpecifierLocation(Var, Result)) {
252 if (TypeSpec->isInvalid() || TypeSpec->getBegin().isMacroID() ||
253 TypeSpec->getEnd().isMacroID())
254 return;
255 SourceLocation InsertPos = TypeSpec->getBegin();
256 diag(InsertPos, "'auto *%0%1%2' can be declared as 'const auto *%0%1%2'")
257 << (Var->getType().isLocalConstQualified() ? "const " : "")
258 << (Var->getType().isLocalVolatileQualified() ? "volatile " : "")
259 << Var->getName() << FixItHint::CreateInsertion(InsertPos, "const ");
260 }
261 return;
262 }
263 if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("auto_ref")) {
264 if (!isPointerConst(Var->getType()))
265 return; // Pointer isn't const, no need to add const qualifier.
266 if (!isAutoPointerConst(Var->getType()))
267 // Const isnt wrapped in the auto type, so must be declared explicitly.
268 return;
269
270 if (llvm::Optional<SourceRange> TypeSpec =
271 getTypeSpecifierLocation(Var, Result)) {
272 if (TypeSpec->isInvalid() || TypeSpec->getBegin().isMacroID() ||
273 TypeSpec->getEnd().isMacroID())
274 return;
275 SourceLocation InsertPos = TypeSpec->getBegin();
276 diag(InsertPos, "'auto &%0' can be declared as 'const auto &%0'")
277 << Var->getName() << FixItHint::CreateInsertion(InsertPos, "const ");
278 }
279 return;
280 }
281 }
282
283 } // namespace readability
284 } // namespace tidy
285 } // namespace clang
286