1 //===--- UseAutoCheck.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 "UseAutoCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 #include "clang/ASTMatchers/ASTMatchers.h"
13 #include "clang/Basic/CharInfo.h"
14 #include "clang/Tooling/FixIt.h"
15
16 using namespace clang;
17 using namespace clang::ast_matchers;
18 using namespace clang::ast_matchers::internal;
19
20 namespace clang {
21 namespace tidy {
22 namespace modernize {
23 namespace {
24
25 const char IteratorDeclStmtId[] = "iterator_decl";
26 const char DeclWithNewId[] = "decl_new";
27 const char DeclWithCastId[] = "decl_cast";
28 const char DeclWithTemplateCastId[] = "decl_template";
29
GetTypeNameLength(bool RemoveStars,StringRef Text)30 size_t GetTypeNameLength(bool RemoveStars, StringRef Text) {
31 enum CharType { Space, Alpha, Punctuation };
32 CharType LastChar = Space, BeforeSpace = Punctuation;
33 size_t NumChars = 0;
34 int TemplateTypenameCntr = 0;
35 for (const unsigned char C : Text) {
36 if (C == '<')
37 ++TemplateTypenameCntr;
38 else if (C == '>')
39 --TemplateTypenameCntr;
40 const CharType NextChar =
41 isAlphanumeric(C)
42 ? Alpha
43 : (isWhitespace(C) ||
44 (!RemoveStars && TemplateTypenameCntr == 0 && C == '*'))
45 ? Space
46 : Punctuation;
47 if (NextChar != Space) {
48 ++NumChars; // Count the non-space character.
49 if (LastChar == Space && NextChar == Alpha && BeforeSpace == Alpha)
50 ++NumChars; // Count a single space character between two words.
51 BeforeSpace = NextChar;
52 }
53 LastChar = NextChar;
54 }
55 return NumChars;
56 }
57
58 /// Matches variable declarations that have explicit initializers that
59 /// are not initializer lists.
60 ///
61 /// Given
62 /// \code
63 /// iterator I = Container.begin();
64 /// MyType A(42);
65 /// MyType B{2};
66 /// MyType C;
67 /// \endcode
68 ///
69 /// varDecl(hasWrittenNonListInitializer()) maches \c I and \c A but not \c B
70 /// or \c C.
AST_MATCHER(VarDecl,hasWrittenNonListInitializer)71 AST_MATCHER(VarDecl, hasWrittenNonListInitializer) {
72 const Expr *Init = Node.getAnyInitializer();
73 if (!Init)
74 return false;
75
76 Init = Init->IgnoreImplicit();
77
78 // The following test is based on DeclPrinter::VisitVarDecl() to find if an
79 // initializer is implicit or not.
80 if (const auto *Construct = dyn_cast<CXXConstructExpr>(Init)) {
81 return !Construct->isListInitialization() && Construct->getNumArgs() > 0 &&
82 !Construct->getArg(0)->isDefaultArgument();
83 }
84 return Node.getInitStyle() != VarDecl::ListInit;
85 }
86
87 /// Matches QualTypes that are type sugar for QualTypes that match \c
88 /// SugarMatcher.
89 ///
90 /// Given
91 /// \code
92 /// class C {};
93 /// typedef C my_type;
94 /// typedef my_type my_other_type;
95 /// \endcode
96 ///
97 /// qualType(isSugarFor(recordType(hasDeclaration(namedDecl(hasName("C"))))))
98 /// matches \c my_type and \c my_other_type.
AST_MATCHER_P(QualType,isSugarFor,Matcher<QualType>,SugarMatcher)99 AST_MATCHER_P(QualType, isSugarFor, Matcher<QualType>, SugarMatcher) {
100 QualType QT = Node;
101 while (true) {
102 if (SugarMatcher.matches(QT, Finder, Builder))
103 return true;
104
105 QualType NewQT = QT.getSingleStepDesugaredType(Finder->getASTContext());
106 if (NewQT == QT)
107 return false;
108 QT = NewQT;
109 }
110 }
111
112 /// Matches named declarations that have one of the standard iterator
113 /// names: iterator, reverse_iterator, const_iterator, const_reverse_iterator.
114 ///
115 /// Given
116 /// \code
117 /// iterator I;
118 /// const_iterator CI;
119 /// \endcode
120 ///
121 /// namedDecl(hasStdIteratorName()) matches \c I and \c CI.
hasStdIteratorName()122 Matcher<NamedDecl> hasStdIteratorName() {
123 static const StringRef IteratorNames[] = {"iterator", "reverse_iterator",
124 "const_iterator",
125 "const_reverse_iterator"};
126 return hasAnyName(IteratorNames);
127 }
128
129 /// Matches named declarations that have one of the standard container
130 /// names.
131 ///
132 /// Given
133 /// \code
134 /// class vector {};
135 /// class forward_list {};
136 /// class my_ver{};
137 /// \endcode
138 ///
139 /// recordDecl(hasStdContainerName()) matches \c vector and \c forward_list
140 /// but not \c my_vec.
hasStdContainerName()141 Matcher<NamedDecl> hasStdContainerName() {
142 static StringRef ContainerNames[] = {"array", "deque",
143 "forward_list", "list",
144 "vector",
145
146 "map", "multimap",
147 "set", "multiset",
148
149 "unordered_map", "unordered_multimap",
150 "unordered_set", "unordered_multiset",
151
152 "queue", "priority_queue",
153 "stack"};
154
155 return hasAnyName(ContainerNames);
156 }
157
158 /// Matches declaration reference or member expressions with explicit template
159 /// arguments.
AST_POLYMORPHIC_MATCHER(hasExplicitTemplateArgs,AST_POLYMORPHIC_SUPPORTED_TYPES (DeclRefExpr,MemberExpr))160 AST_POLYMORPHIC_MATCHER(hasExplicitTemplateArgs,
161 AST_POLYMORPHIC_SUPPORTED_TYPES(DeclRefExpr,
162 MemberExpr)) {
163 return Node.hasExplicitTemplateArgs();
164 }
165
166 /// Returns a DeclarationMatcher that matches standard iterators nested
167 /// inside records with a standard container name.
standardIterator()168 DeclarationMatcher standardIterator() {
169 return decl(
170 namedDecl(hasStdIteratorName()),
171 hasDeclContext(recordDecl(hasStdContainerName(), isInStdNamespace())));
172 }
173
174 /// Returns a TypeMatcher that matches typedefs for standard iterators
175 /// inside records with a standard container name.
typedefIterator()176 TypeMatcher typedefIterator() {
177 return typedefType(hasDeclaration(standardIterator()));
178 }
179
180 /// Returns a TypeMatcher that matches records named for standard
181 /// iterators nested inside records named for standard containers.
nestedIterator()182 TypeMatcher nestedIterator() {
183 return recordType(hasDeclaration(standardIterator()));
184 }
185
186 /// Returns a TypeMatcher that matches types declared with using
187 /// declarations and which name standard iterators for standard containers.
iteratorFromUsingDeclaration()188 TypeMatcher iteratorFromUsingDeclaration() {
189 auto HasIteratorDecl = hasDeclaration(namedDecl(hasStdIteratorName()));
190 // Types resulting from using declarations are represented by elaboratedType.
191 return elaboratedType(
192 // Unwrap the nested name specifier to test for one of the standard
193 // containers.
194 hasQualifier(specifiesType(templateSpecializationType(hasDeclaration(
195 namedDecl(hasStdContainerName(), isInStdNamespace()))))),
196 // the named type is what comes after the final '::' in the type. It
197 // should name one of the standard iterator names.
198 namesType(
199 anyOf(typedefType(HasIteratorDecl), recordType(HasIteratorDecl))));
200 }
201
202 /// This matcher returns declaration statements that contain variable
203 /// declarations with written non-list initializer for standard iterators.
makeIteratorDeclMatcher()204 StatementMatcher makeIteratorDeclMatcher() {
205 return declStmt(unless(has(
206 varDecl(anyOf(unless(hasWrittenNonListInitializer()),
207 unless(hasType(isSugarFor(anyOf(
208 typedefIterator(), nestedIterator(),
209 iteratorFromUsingDeclaration())))))))))
210 .bind(IteratorDeclStmtId);
211 }
212
makeDeclWithNewMatcher()213 StatementMatcher makeDeclWithNewMatcher() {
214 return declStmt(
215 unless(has(varDecl(anyOf(
216 unless(hasInitializer(ignoringParenImpCasts(cxxNewExpr()))),
217 // FIXME: TypeLoc information is not reliable where CV
218 // qualifiers are concerned so these types can't be
219 // handled for now.
220 hasType(pointerType(
221 pointee(hasCanonicalType(hasLocalQualifiers())))),
222
223 // FIXME: Handle function pointers. For now we ignore them
224 // because the replacement replaces the entire type
225 // specifier source range which includes the identifier.
226 hasType(pointsTo(
227 pointsTo(parenType(innerType(functionType()))))))))))
228 .bind(DeclWithNewId);
229 }
230
makeDeclWithCastMatcher()231 StatementMatcher makeDeclWithCastMatcher() {
232 return declStmt(
233 unless(has(varDecl(unless(hasInitializer(explicitCastExpr()))))))
234 .bind(DeclWithCastId);
235 }
236
makeDeclWithTemplateCastMatcher()237 StatementMatcher makeDeclWithTemplateCastMatcher() {
238 auto ST =
239 substTemplateTypeParmType(hasReplacementType(equalsBoundNode("arg")));
240
241 auto ExplicitCall =
242 anyOf(has(memberExpr(hasExplicitTemplateArgs())),
243 has(ignoringImpCasts(declRefExpr(hasExplicitTemplateArgs()))));
244
245 auto TemplateArg =
246 hasTemplateArgument(0, refersToType(qualType().bind("arg")));
247
248 auto TemplateCall = callExpr(
249 ExplicitCall,
250 callee(functionDecl(TemplateArg,
251 returns(anyOf(ST, pointsTo(ST), references(ST))))));
252
253 return declStmt(unless(has(varDecl(
254 unless(hasInitializer(ignoringImplicit(TemplateCall)))))))
255 .bind(DeclWithTemplateCastId);
256 }
257
makeCombinedMatcher()258 StatementMatcher makeCombinedMatcher() {
259 return declStmt(
260 // At least one varDecl should be a child of the declStmt to ensure
261 // it's a declaration list and avoid matching other declarations,
262 // e.g. using directives.
263 has(varDecl(unless(isImplicit()))),
264 // Skip declarations that are already using auto.
265 unless(has(varDecl(anyOf(hasType(autoType()),
266 hasType(qualType(hasDescendant(autoType()))))))),
267 anyOf(makeIteratorDeclMatcher(), makeDeclWithNewMatcher(),
268 makeDeclWithCastMatcher(), makeDeclWithTemplateCastMatcher()));
269 }
270
271 } // namespace
272
UseAutoCheck(StringRef Name,ClangTidyContext * Context)273 UseAutoCheck::UseAutoCheck(StringRef Name, ClangTidyContext *Context)
274 : ClangTidyCheck(Name, Context),
275 MinTypeNameLength(Options.get("MinTypeNameLength", 5)),
276 RemoveStars(Options.get("RemoveStars", false)) {}
277
storeOptions(ClangTidyOptions::OptionMap & Opts)278 void UseAutoCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
279 Options.store(Opts, "MinTypeNameLength", MinTypeNameLength);
280 Options.store(Opts, "RemoveStars", RemoveStars);
281 }
282
registerMatchers(MatchFinder * Finder)283 void UseAutoCheck::registerMatchers(MatchFinder *Finder) {
284 Finder->addMatcher(traverse(ast_type_traits::TK_AsIs, makeCombinedMatcher()),
285 this);
286 }
287
replaceIterators(const DeclStmt * D,ASTContext * Context)288 void UseAutoCheck::replaceIterators(const DeclStmt *D, ASTContext *Context) {
289 for (const auto *Dec : D->decls()) {
290 const auto *V = cast<VarDecl>(Dec);
291 const Expr *ExprInit = V->getInit();
292
293 // Skip expressions with cleanups from the initializer expression.
294 if (const auto *E = dyn_cast<ExprWithCleanups>(ExprInit))
295 ExprInit = E->getSubExpr();
296
297 const auto *Construct = dyn_cast<CXXConstructExpr>(ExprInit);
298 if (!Construct)
299 continue;
300
301 // Ensure that the constructor receives a single argument.
302 if (Construct->getNumArgs() != 1)
303 return;
304
305 // Drill down to the as-written initializer.
306 const Expr *E = (*Construct->arg_begin())->IgnoreParenImpCasts();
307 if (E != E->IgnoreConversionOperatorSingleStep()) {
308 // We hit a conversion operator. Early-out now as they imply an implicit
309 // conversion from a different type. Could also mean an explicit
310 // conversion from the same type but that's pretty rare.
311 return;
312 }
313
314 if (const auto *NestedConstruct = dyn_cast<CXXConstructExpr>(E)) {
315 // If we ran into an implicit conversion constructor, can't convert.
316 //
317 // FIXME: The following only checks if the constructor can be used
318 // implicitly, not if it actually was. Cases where the converting
319 // constructor was used explicitly won't get converted.
320 if (NestedConstruct->getConstructor()->isConvertingConstructor(false))
321 return;
322 }
323 if (!Context->hasSameType(V->getType(), E->getType()))
324 return;
325 }
326
327 // Get the type location using the first declaration.
328 const auto *V = cast<VarDecl>(*D->decl_begin());
329
330 // WARNING: TypeLoc::getSourceRange() will include the identifier for things
331 // like function pointers. Not a concern since this action only works with
332 // iterators but something to keep in mind in the future.
333
334 SourceRange Range(V->getTypeSourceInfo()->getTypeLoc().getSourceRange());
335 diag(Range.getBegin(), "use auto when declaring iterators")
336 << FixItHint::CreateReplacement(Range, "auto");
337 }
338
replaceExpr(const DeclStmt * D,ASTContext * Context,llvm::function_ref<QualType (const Expr *)> GetType,StringRef Message)339 void UseAutoCheck::replaceExpr(
340 const DeclStmt *D, ASTContext *Context,
341 llvm::function_ref<QualType(const Expr *)> GetType, StringRef Message) {
342 const auto *FirstDecl = dyn_cast<VarDecl>(*D->decl_begin());
343 // Ensure that there is at least one VarDecl within the DeclStmt.
344 if (!FirstDecl)
345 return;
346
347 const QualType FirstDeclType = FirstDecl->getType().getCanonicalType();
348
349 std::vector<FixItHint> StarRemovals;
350 for (const auto *Dec : D->decls()) {
351 const auto *V = cast<VarDecl>(Dec);
352 // Ensure that every DeclStmt child is a VarDecl.
353 if (!V)
354 return;
355
356 const auto *Expr = V->getInit()->IgnoreParenImpCasts();
357 // Ensure that every VarDecl has an initializer.
358 if (!Expr)
359 return;
360
361 // If VarDecl and Initializer have mismatching unqualified types.
362 if (!Context->hasSameUnqualifiedType(V->getType(), GetType(Expr)))
363 return;
364
365 // All subsequent variables in this declaration should have the same
366 // canonical type. For example, we don't want to use `auto` in
367 // `T *p = new T, **pp = new T*;`.
368 if (FirstDeclType != V->getType().getCanonicalType())
369 return;
370
371 if (RemoveStars) {
372 // Remove explicitly written '*' from declarations where there's more than
373 // one declaration in the declaration list.
374 if (Dec == *D->decl_begin())
375 continue;
376
377 auto Q = V->getTypeSourceInfo()->getTypeLoc().getAs<PointerTypeLoc>();
378 while (!Q.isNull()) {
379 StarRemovals.push_back(FixItHint::CreateRemoval(Q.getStarLoc()));
380 Q = Q.getNextTypeLoc().getAs<PointerTypeLoc>();
381 }
382 }
383 }
384
385 // FIXME: There is, however, one case we can address: when the VarDecl pointee
386 // is the same as the initializer, just more CV-qualified. However, TypeLoc
387 // information is not reliable where CV qualifiers are concerned so we can't
388 // do anything about this case for now.
389 TypeLoc Loc = FirstDecl->getTypeSourceInfo()->getTypeLoc();
390 if (!RemoveStars) {
391 while (Loc.getTypeLocClass() == TypeLoc::Pointer ||
392 Loc.getTypeLocClass() == TypeLoc::Qualified)
393 Loc = Loc.getNextTypeLoc();
394 }
395 while (Loc.getTypeLocClass() == TypeLoc::LValueReference ||
396 Loc.getTypeLocClass() == TypeLoc::RValueReference ||
397 Loc.getTypeLocClass() == TypeLoc::Qualified) {
398 Loc = Loc.getNextTypeLoc();
399 }
400 SourceRange Range(Loc.getSourceRange());
401
402 if (MinTypeNameLength != 0 &&
403 GetTypeNameLength(RemoveStars,
404 tooling::fixit::getText(Loc.getSourceRange(),
405 FirstDecl->getASTContext())) <
406 MinTypeNameLength)
407 return;
408
409 auto Diag = diag(Range.getBegin(), Message);
410
411 // Space after 'auto' to handle cases where the '*' in the pointer type is
412 // next to the identifier. This avoids changing 'int *p' into 'autop'.
413 // FIXME: This doesn't work for function pointers because the variable name
414 // is inside the type.
415 Diag << FixItHint::CreateReplacement(Range, RemoveStars ? "auto " : "auto")
416 << StarRemovals;
417 }
418
check(const MatchFinder::MatchResult & Result)419 void UseAutoCheck::check(const MatchFinder::MatchResult &Result) {
420 if (const auto *Decl = Result.Nodes.getNodeAs<DeclStmt>(IteratorDeclStmtId)) {
421 replaceIterators(Decl, Result.Context);
422 } else if (const auto *Decl =
423 Result.Nodes.getNodeAs<DeclStmt>(DeclWithNewId)) {
424 replaceExpr(Decl, Result.Context,
425 [](const Expr *Expr) { return Expr->getType(); },
426 "use auto when initializing with new to avoid "
427 "duplicating the type name");
428 } else if (const auto *Decl =
429 Result.Nodes.getNodeAs<DeclStmt>(DeclWithCastId)) {
430 replaceExpr(
431 Decl, Result.Context,
432 [](const Expr *Expr) {
433 return cast<ExplicitCastExpr>(Expr)->getTypeAsWritten();
434 },
435 "use auto when initializing with a cast to avoid duplicating the type "
436 "name");
437 } else if (const auto *Decl =
438 Result.Nodes.getNodeAs<DeclStmt>(DeclWithTemplateCastId)) {
439 replaceExpr(
440 Decl, Result.Context,
441 [](const Expr *Expr) {
442 return cast<CallExpr>(Expr->IgnoreImplicit())
443 ->getDirectCallee()
444 ->getReturnType();
445 },
446 "use auto when initializing with a template cast to avoid duplicating "
447 "the type name");
448 } else {
449 llvm_unreachable("Bad Callback. No node provided.");
450 }
451 }
452
453 } // namespace modernize
454 } // namespace tidy
455 } // namespace clang
456