clang-tools 10.0.0

UseAutoCheck.cpp
Go to the documentation of this file.
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
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.
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.
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.
122 AST_MATCHER(NamedDecl, hasStdIteratorName) {
123 static const char *const IteratorNames[] = {"iterator", "reverse_iterator",
124 "const_iterator",
125 "const_reverse_iterator"};
126
127 for (const char *Name : IteratorNames) {
128 if (hasName(Name).matches(Node, Finder, Builder))
129 return true;
130 }
131 return false;
132 }
133
134 /// Matches named declarations that have one of the standard container
135 /// names.
136 ///
137 /// Given
138 /// \code
139 /// class vector {};
140 /// class forward_list {};
141 /// class my_ver{};
142 /// \endcode
143 ///
144 /// recordDecl(hasStdContainerName()) matches \c vector and \c forward_list
145 /// but not \c my_vec.
146 AST_MATCHER(NamedDecl, hasStdContainerName) {
147 static const char *const ContainerNames[] = {
148 "array", "deque",
149 "forward_list", "list",
150 "vector",
151
152 "map", "multimap",
153 "set", "multiset",
154
155 "unordered_map", "unordered_multimap",
156 "unordered_set", "unordered_multiset",
157
158 "queue", "priority_queue",
159 "stack"};
160
161 for (const char *Name : ContainerNames) {
162 if (hasName(Name).matches(Node, Finder, Builder))
163 return true;
164 }
165 return false;
166 }
167
168 /// Matches declarations whose declaration context is the C++ standard library
169 /// namespace std.
170 ///
171 /// Note that inline namespaces are silently ignored during the lookup since
172 /// both libstdc++ and libc++ are known to use them for versioning purposes.
173 ///
174 /// Given:
175 /// \code
176 /// namespace ns {
177 /// struct my_type {};
178 /// using namespace std;
179 /// }
180 ///
181 /// using std::vector;
182 /// using ns:my_type;
183 /// using ns::list;
184 /// \code
185 ///
186 /// usingDecl(hasAnyUsingShadowDecl(hasTargetDecl(isFromStdNamespace())))
187 /// matches "using std::vector" and "using ns::list".
188 AST_MATCHER(Decl, isFromStdNamespace) {
189 const DeclContext *D = Node.getDeclContext();
190
191 while (D->isInlineNamespace())
192 D = D->getParent();
193
194 if (!D->isNamespace() || !D->getParent()->isTranslationUnit())
195 return false;
196
197 const IdentifierInfo *Info = cast<NamespaceDecl>(D)->getIdentifier();
198
199 return (Info && Info->isStr("std"));
200 }
201
202 /// Matches declaration reference or member expressions with explicit template
203 /// arguments.
204 AST_POLYMORPHIC_MATCHER(hasExplicitTemplateArgs,
205 AST_POLYMORPHIC_SUPPORTED_TYPES(DeclRefExpr,
206 MemberExpr)) {
207 return Node.hasExplicitTemplateArgs();
208 }
209
210 /// Returns a DeclarationMatcher that matches standard iterators nested
211 /// inside records with a standard container name.
212 DeclarationMatcher standardIterator() {
213 return decl(
214 namedDecl(hasStdIteratorName()),
215 hasDeclContext(recordDecl(hasStdContainerName(), isFromStdNamespace())));
216 }
217
218 /// Returns a TypeMatcher that matches typedefs for standard iterators
219 /// inside records with a standard container name.
220 TypeMatcher typedefIterator() {
221 return typedefType(hasDeclaration(standardIterator()));
222 }
223
224 /// Returns a TypeMatcher that matches records named for standard
225 /// iterators nested inside records named for standard containers.
226 TypeMatcher nestedIterator() {
227 return recordType(hasDeclaration(standardIterator()));
228 }
229
230 /// Returns a TypeMatcher that matches types declared with using
231 /// declarations and which name standard iterators for standard containers.
232 TypeMatcher iteratorFromUsingDeclaration() {
233 auto HasIteratorDecl = hasDeclaration(namedDecl(hasStdIteratorName()));
234 // Types resulting from using declarations are represented by elaboratedType.
235 return elaboratedType(
236 // Unwrap the nested name specifier to test for one of the standard
237 // containers.
238 hasQualifier(specifiesType(templateSpecializationType(hasDeclaration(
239 namedDecl(hasStdContainerName(), isFromStdNamespace()))))),
240 // the named type is what comes after the final '::' in the type. It
241 // should name one of the standard iterator names.
242 namesType(
243 anyOf(typedefType(HasIteratorDecl), recordType(HasIteratorDecl))));
244 }
245
246 /// This matcher returns declaration statements that contain variable
247 /// declarations with written non-list initializer for standard iterators.
248 StatementMatcher makeIteratorDeclMatcher() {
249 return declStmt(unless(has(
250 varDecl(anyOf(unless(hasWrittenNonListInitializer()),
251 unless(hasType(isSugarFor(anyOf(
252 typedefIterator(), nestedIterator(),
253 iteratorFromUsingDeclaration())))))))))
254 .bind(IteratorDeclStmtId);
255 }
256
257 StatementMatcher makeDeclWithNewMatcher() {
258 return declStmt(
259 unless(has(varDecl(anyOf(
260 unless(hasInitializer(ignoringParenImpCasts(cxxNewExpr()))),
261 // FIXME: TypeLoc information is not reliable where CV
262 // qualifiers are concerned so these types can't be
263 // handled for now.
264 hasType(pointerType(
265 pointee(hasCanonicalType(hasLocalQualifiers())))),
266
267 // FIXME: Handle function pointers. For now we ignore them
268 // because the replacement replaces the entire type
269 // specifier source range which includes the identifier.
270 hasType(pointsTo(
271 pointsTo(parenType(innerType(functionType()))))))))))
272 .bind(DeclWithNewId);
273 }
274
275 StatementMatcher makeDeclWithCastMatcher() {
276 return declStmt(
277 unless(has(varDecl(unless(hasInitializer(explicitCastExpr()))))))
278 .bind(DeclWithCastId);
279 }
280
281 StatementMatcher makeDeclWithTemplateCastMatcher() {
282 auto ST =
283 substTemplateTypeParmType(hasReplacementType(equalsBoundNode("arg")));
284
285 auto ExplicitCall =
286 anyOf(has(memberExpr(hasExplicitTemplateArgs())),
287 has(ignoringImpCasts(declRefExpr(hasExplicitTemplateArgs()))));
288
289 auto TemplateArg =
290 hasTemplateArgument(0, refersToType(qualType().bind("arg")));
291
292 auto TemplateCall = callExpr(
293 ExplicitCall,
294 callee(functionDecl(TemplateArg,
295 returns(anyOf(ST, pointsTo(ST), references(ST))))));
296
297 return declStmt(unless(has(varDecl(
298 unless(hasInitializer(ignoringImplicit(TemplateCall)))))))
299 .bind(DeclWithTemplateCastId);
300 }
301
302 StatementMatcher makeCombinedMatcher() {
303 return declStmt(
304 // At least one varDecl should be a child of the declStmt to ensure
305 // it's a declaration list and avoid matching other declarations,
306 // e.g. using directives.
307 has(varDecl(unless(isImplicit()))),
308 // Skip declarations that are already using auto.
309 unless(has(varDecl(anyOf(hasType(autoType()),
310 hasType(qualType(hasDescendant(autoType()))))))),
311 anyOf(makeIteratorDeclMatcher(), makeDeclWithNewMatcher(),
312 makeDeclWithCastMatcher(), makeDeclWithTemplateCastMatcher()));
313 }
314
315 } // namespace
316
317 UseAutoCheck::UseAutoCheck(StringRef Name, ClangTidyContext *Context)
318 : ClangTidyCheck(Name, Context),
319 MinTypeNameLength(Options.get("MinTypeNameLength", 5)),
320 RemoveStars(Options.get("RemoveStars", 0)) {}
321
322 void UseAutoCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
323 Options.store(Opts, "MinTypeNameLength", MinTypeNameLength);
324 Options.store(Opts, "RemoveStars", RemoveStars ? 1 : 0);
325 }
326
327 void UseAutoCheck::registerMatchers(MatchFinder *Finder) {
328 // Only register the matchers for C++; the functionality currently does not
329 // provide any benefit to other languages, despite being benign.
330 if (getLangOpts().CPlusPlus) {
331 Finder->addMatcher(makeCombinedMatcher(), this);
332 }
333 }
334
335 void UseAutoCheck::replaceIterators(const DeclStmt *D, ASTContext *Context) {
336 for (const auto *Dec : D->decls()) {
337 const auto *V = cast<VarDecl>(Dec);
338 const Expr *ExprInit = V->getInit();
339
340 // Skip expressions with cleanups from the intializer expression.
341 if (const auto *E = dyn_cast<ExprWithCleanups>(ExprInit))
342 ExprInit = E->getSubExpr();
343
344 const auto *Construct = dyn_cast<CXXConstructExpr>(ExprInit);
345 if (!Construct)
346 continue;
347
348 // Ensure that the constructor receives a single argument.
349 if (Construct->getNumArgs() != 1)
350 return;
351
352 // Drill down to the as-written initializer.
353 const Expr *E = (*Construct->arg_begin())->IgnoreParenImpCasts();
354 if (E != E->IgnoreConversionOperator()) {
355 // We hit a conversion operator. Early-out now as they imply an implicit
356 // conversion from a different type. Could also mean an explicit
357 // conversion from the same type but that's pretty rare.
358 return;
359 }
360
361 if (const auto *NestedConstruct = dyn_cast<CXXConstructExpr>(E)) {
362 // If we ran into an implicit conversion constructor, can't convert.
363 //
364 // FIXME: The following only checks if the constructor can be used
365 // implicitly, not if it actually was. Cases where the converting
366 // constructor was used explicitly won't get converted.
367 if (NestedConstruct->getConstructor()->isConvertingConstructor(false))
368 return;
369 }
370 if (!Context->hasSameType(V->getType(), E->getType()))
371 return;
372 }
373
374 // Get the type location using the first declaration.
375 const auto *V = cast<VarDecl>(*D->decl_begin());
376
377 // WARNING: TypeLoc::getSourceRange() will include the identifier for things
378 // like function pointers. Not a concern since this action only works with
379 // iterators but something to keep in mind in the future.
380
381 SourceRange Range(V->getTypeSourceInfo()->getTypeLoc().getSourceRange());
382 diag(Range.getBegin(), "use auto when declaring iterators")
383 << FixItHint::CreateReplacement(Range, "auto");
384 }
385
386 void UseAutoCheck::replaceExpr(
387 const DeclStmt *D, ASTContext *Context,
388 llvm::function_ref<QualType(const Expr *)> GetType, StringRef Message) {
389 const auto *FirstDecl = dyn_cast<VarDecl>(*D->decl_begin());
390 // Ensure that there is at least one VarDecl within the DeclStmt.
391 if (!FirstDecl)
392 return;
393
394 const QualType FirstDeclType = FirstDecl->getType().getCanonicalType();
395
396 std::vector<FixItHint> StarRemovals;
397 for (const auto *Dec : D->decls()) {
398 const auto *V = cast<VarDecl>(Dec);
399 // Ensure that every DeclStmt child is a VarDecl.
400 if (!V)
401 return;
402
403 const auto *Expr = V->getInit()->IgnoreParenImpCasts();
404 // Ensure that every VarDecl has an initializer.
405 if (!Expr)
406 return;
407
408 // If VarDecl and Initializer have mismatching unqualified types.
409 if (!Context->hasSameUnqualifiedType(V->getType(), GetType(Expr)))
410 return;
411
412 // All subsequent variables in this declaration should have the same
413 // canonical type. For example, we don't want to use `auto` in
414 // `T *p = new T, **pp = new T*;`.
415 if (FirstDeclType != V->getType().getCanonicalType())
416 return;
417
418 if (RemoveStars) {
419 // Remove explicitly written '*' from declarations where there's more than
420 // one declaration in the declaration list.
421 if (Dec == *D->decl_begin())
422 continue;
423
424 auto Q = V->getTypeSourceInfo()->getTypeLoc().getAs<PointerTypeLoc>();
425 while (!Q.isNull()) {
426 StarRemovals.push_back(FixItHint::CreateRemoval(Q.getStarLoc()));
427 Q = Q.getNextTypeLoc().getAs<PointerTypeLoc>();
428 }
429 }
430 }
431
432 // FIXME: There is, however, one case we can address: when the VarDecl pointee
433 // is the same as the initializer, just more CV-qualified. However, TypeLoc
434 // information is not reliable where CV qualifiers are concerned so we can't
435 // do anything about this case for now.
436 TypeLoc Loc = FirstDecl->getTypeSourceInfo()->getTypeLoc();
437 if (!RemoveStars) {
438 while (Loc.getTypeLocClass() == TypeLoc::Pointer ||
439 Loc.getTypeLocClass() == TypeLoc::Qualified)
440 Loc = Loc.getNextTypeLoc();
441 }
442 while (Loc.getTypeLocClass() == TypeLoc::LValueReference ||
443 Loc.getTypeLocClass() == TypeLoc::RValueReference ||
444 Loc.getTypeLocClass() == TypeLoc::Qualified) {
445 Loc = Loc.getNextTypeLoc();
446 }
447 SourceRange Range(Loc.getSourceRange());
448
449 if (MinTypeNameLength != 0 &&
450 GetTypeNameLength(RemoveStars,
451 tooling::fixit::getText(Loc.getSourceRange(),
452 FirstDecl->getASTContext())) <
453 MinTypeNameLength)
454 return;
455
456 auto Diag = diag(Range.getBegin(), Message);
457
458 // Space after 'auto' to handle cases where the '*' in the pointer type is
459 // next to the identifier. This avoids changing 'int *p' into 'autop'.
460 // FIXME: This doesn't work for function pointers because the variable name
461 // is inside the type.
462 Diag << FixItHint::CreateReplacement(Range, RemoveStars ? "auto " : "auto")
463 << StarRemovals;
464 }
465
466 void UseAutoCheck::check(const MatchFinder::MatchResult &Result) {
467 if (const auto *Decl = Result.Nodes.getNodeAs<DeclStmt>(IteratorDeclStmtId)) {
468 replaceIterators(Decl, Result.Context);
469 } else if (const auto *Decl =
470 Result.Nodes.getNodeAs<DeclStmt>(DeclWithNewId)) {
471 replaceExpr(Decl, Result.Context,
472 [](const Expr *Expr) { return Expr->getType(); },
473 "use auto when initializing with new to avoid "
474 "duplicating the type name");
475 } else if (const auto *Decl =
476 Result.Nodes.getNodeAs<DeclStmt>(DeclWithCastId)) {
477 replaceExpr(
478 Decl, Result.Context,
479 [](const Expr *Expr) {
480 return cast<ExplicitCastExpr>(Expr)->getTypeAsWritten();
481 },
482 "use auto when initializing with a cast to avoid duplicating the type "
483 "name");
484 } else if (const auto *Decl =
485 Result.Nodes.getNodeAs<DeclStmt>(DeclWithTemplateCastId)) {
486 replaceExpr(
487 Decl, Result.Context,
488 [](const Expr *Expr) {
489 return cast<CallExpr>(Expr->IgnoreImplicit())
490 ->getDirectCallee()
491 ->getReturnType();
492 },
493 "use auto when initializing with a template cast to avoid duplicating "
494 "the type name");
495 } else {
496 llvm_unreachable("Bad Callback. No node provided.");
497 }
498 }
499
500 } // namespace modernize
501 } // namespace tidy
502 } // namespace clang
Loc
SourceLocation Loc
&#39;#&#39; location in the include directive
Definition: IncludeOrderCheck.cpp:37
clang::ast_matchers
Definition: AbseilMatcher.h:14
UseAutoCheck.h
Decl
const FunctionDecl * Decl
Definition: AvoidBindCheck.cpp:93
clang::ast_matchers::AST_MATCHER
AST_MATCHER(Expr, isMacroID)
Definition: PreferIsaOrDynCastInConditionalsCheck.cpp:18
clang::ast_matchers::AST_POLYMORPHIC_MATCHER
AST_POLYMORPHIC_MATCHER(isInAbseilFile, AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt, TypeLoc, NestedNameSpecifierLoc))
Matches AST nodes that were found within Abseil files.
Definition: AbseilMatcher.h:31
clang::tidy::modernize::Message
constexpr llvm::StringLiteral Message
Definition: UseTrailingReturnTypeCheck.cpp:101
clang::tidy::ClangTidyCheck::getLangOpts
const LangOptions & getLangOpts() const
Returns the language options from the context.
Definition: ClangTidyCheck.h:188
clang::tidy::ClangTidyCheck::Options
OptionsView Options
Definition: ClangTidyCheck.h:184
clang::tidy::ClangTidyCheck::OptionsView::store
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Definition: ClangTidyCheck.cpp:58
Name
static constexpr llvm::StringLiteral Name
Definition: UppercaseLiteralSuffixCheck.cpp:27
clang::tidy::ClangTidyOptions::OptionMap
std::map< std::string, std::string > OptionMap
Definition: ClangTidyOptions.h:97
clang::clangd::CharType
CharType
Definition: FuzzyMatch.h:41
Info
FunctionInfo Info
Definition: FunctionSizeCheck.cpp:120
Builder
CodeCompletionBuilder Builder
Definition: CodeCompletionStringsTests.cpp:35
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
internal
Range
CharSourceRange Range
SourceRange for the file name.
Definition: IncludeOrderCheck.cpp:38
clang::clangd::Punctuation
Definition: FuzzyMatch.h:45
E
const Expr * E
Definition: AvoidBindCheck.cpp:82
clang::tidy::ClangTidyCheck::diag
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check&#39;s name.
Definition: ClangTidyCheck.cpp:21
clang::tidy::readability::AST_MATCHER_P
AST_MATCHER_P(CXXMethodDecl, hasCanonicalDecl, ast_matchers::internal::Matcher< CXXMethodDecl >, InnerMatcher)
Definition: ConvertMemberFunctionsToStatic.cpp:52