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/AST/TypeLoc.h" 12 #include "clang/ASTMatchers/ASTMatchFinder.h" 13 #include "clang/ASTMatchers/ASTMatchers.h" 14 #include "clang/Basic/CharInfo.h" 15 #include "clang/Tooling/FixIt.h" 16 #include "llvm/ADT/STLExtras.h" 17 18 using namespace clang; 19 using namespace clang::ast_matchers; 20 using namespace clang::ast_matchers::internal; 21 22 namespace clang::tidy::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 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. 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. 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. 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. 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. 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. 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. 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 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 231 StatementMatcher makeDeclWithCastMatcher() { 232 return declStmt( 233 unless(has(varDecl(unless(hasInitializer(explicitCastExpr())))))) 234 .bind(DeclWithCastId); 235 } 236 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 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 273 UseAutoCheck::UseAutoCheck(StringRef Name, ClangTidyContext *Context) 274 : ClangTidyCheck(Name, Context), 275 MinTypeNameLength(Options.get("MinTypeNameLength", 5)), 276 RemoveStars(Options.get("RemoveStars", false)) {} 277 278 void UseAutoCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { 279 Options.store(Opts, "MinTypeNameLength", MinTypeNameLength); 280 Options.store(Opts, "RemoveStars", RemoveStars); 281 } 282 283 void UseAutoCheck::registerMatchers(MatchFinder *Finder) { 284 Finder->addMatcher(traverse(TK_AsIs, makeCombinedMatcher()), this); 285 } 286 287 void UseAutoCheck::replaceIterators(const DeclStmt *D, ASTContext *Context) { 288 for (const auto *Dec : D->decls()) { 289 const auto *V = cast<VarDecl>(Dec); 290 const Expr *ExprInit = V->getInit(); 291 292 // Skip expressions with cleanups from the initializer expression. 293 if (const auto *E = dyn_cast<ExprWithCleanups>(ExprInit)) 294 ExprInit = E->getSubExpr(); 295 296 const auto *Construct = dyn_cast<CXXConstructExpr>(ExprInit); 297 if (!Construct) 298 continue; 299 300 // Ensure that the constructor receives a single argument. 301 if (Construct->getNumArgs() != 1) 302 return; 303 304 // Drill down to the as-written initializer. 305 const Expr *E = (*Construct->arg_begin())->IgnoreParenImpCasts(); 306 if (E != E->IgnoreConversionOperatorSingleStep()) { 307 // We hit a conversion operator. Early-out now as they imply an implicit 308 // conversion from a different type. Could also mean an explicit 309 // conversion from the same type but that's pretty rare. 310 return; 311 } 312 313 if (const auto *NestedConstruct = dyn_cast<CXXConstructExpr>(E)) { 314 // If we ran into an implicit conversion constructor, can't convert. 315 // 316 // FIXME: The following only checks if the constructor can be used 317 // implicitly, not if it actually was. Cases where the converting 318 // constructor was used explicitly won't get converted. 319 if (NestedConstruct->getConstructor()->isConvertingConstructor(false)) 320 return; 321 } 322 if (!Context->hasSameType(V->getType(), E->getType())) 323 return; 324 } 325 326 // Get the type location using the first declaration. 327 const auto *V = cast<VarDecl>(*D->decl_begin()); 328 329 // WARNING: TypeLoc::getSourceRange() will include the identifier for things 330 // like function pointers. Not a concern since this action only works with 331 // iterators but something to keep in mind in the future. 332 333 SourceRange Range(V->getTypeSourceInfo()->getTypeLoc().getSourceRange()); 334 diag(Range.getBegin(), "use auto when declaring iterators") 335 << FixItHint::CreateReplacement(Range, "auto"); 336 } 337 338 static void ignoreTypeLocClasses( 339 TypeLoc &Loc, 340 std::initializer_list<TypeLoc::TypeLocClass> const &LocClasses) { 341 while (llvm::is_contained(LocClasses, Loc.getTypeLocClass())) 342 Loc = Loc.getNextTypeLoc(); 343 } 344 345 static bool isMultiLevelPointerToTypeLocClasses( 346 TypeLoc Loc, 347 std::initializer_list<TypeLoc::TypeLocClass> const &LocClasses) { 348 ignoreTypeLocClasses(Loc, {TypeLoc::Paren, TypeLoc::Qualified}); 349 TypeLoc::TypeLocClass TLC = Loc.getTypeLocClass(); 350 if (TLC != TypeLoc::Pointer && TLC != TypeLoc::MemberPointer) 351 return false; 352 ignoreTypeLocClasses(Loc, {TypeLoc::Paren, TypeLoc::Qualified, 353 TypeLoc::Pointer, TypeLoc::MemberPointer}); 354 return llvm::is_contained(LocClasses, Loc.getTypeLocClass()); 355 } 356 357 void UseAutoCheck::replaceExpr( 358 const DeclStmt *D, ASTContext *Context, 359 llvm::function_ref<QualType(const Expr *)> GetType, StringRef Message) { 360 const auto *FirstDecl = dyn_cast<VarDecl>(*D->decl_begin()); 361 // Ensure that there is at least one VarDecl within the DeclStmt. 362 if (!FirstDecl) 363 return; 364 365 const QualType FirstDeclType = FirstDecl->getType().getCanonicalType(); 366 TypeSourceInfo *TSI = FirstDecl->getTypeSourceInfo(); 367 368 if (TSI == nullptr) 369 return; 370 371 std::vector<FixItHint> StarRemovals; 372 for (const auto *Dec : D->decls()) { 373 const auto *V = cast<VarDecl>(Dec); 374 // Ensure that every DeclStmt child is a VarDecl. 375 if (!V) 376 return; 377 378 const auto *Expr = V->getInit()->IgnoreParenImpCasts(); 379 // Ensure that every VarDecl has an initializer. 380 if (!Expr) 381 return; 382 383 // If VarDecl and Initializer have mismatching unqualified types. 384 if (!Context->hasSameUnqualifiedType(V->getType(), GetType(Expr))) 385 return; 386 387 // All subsequent variables in this declaration should have the same 388 // canonical type. For example, we don't want to use `auto` in 389 // `T *p = new T, **pp = new T*;`. 390 if (FirstDeclType != V->getType().getCanonicalType()) 391 return; 392 393 if (RemoveStars) { 394 // Remove explicitly written '*' from declarations where there's more than 395 // one declaration in the declaration list. 396 if (Dec == *D->decl_begin()) 397 continue; 398 399 auto Q = V->getTypeSourceInfo()->getTypeLoc().getAs<PointerTypeLoc>(); 400 while (!Q.isNull()) { 401 StarRemovals.push_back(FixItHint::CreateRemoval(Q.getStarLoc())); 402 Q = Q.getNextTypeLoc().getAs<PointerTypeLoc>(); 403 } 404 } 405 } 406 407 // FIXME: There is, however, one case we can address: when the VarDecl pointee 408 // is the same as the initializer, just more CV-qualified. However, TypeLoc 409 // information is not reliable where CV qualifiers are concerned so we can't 410 // do anything about this case for now. 411 TypeLoc Loc = TSI->getTypeLoc(); 412 if (!RemoveStars) 413 ignoreTypeLocClasses(Loc, {TypeLoc::Pointer, TypeLoc::Qualified}); 414 ignoreTypeLocClasses(Loc, {TypeLoc::LValueReference, TypeLoc::RValueReference, 415 TypeLoc::Qualified}); 416 SourceRange Range(Loc.getSourceRange()); 417 418 if (MinTypeNameLength != 0 && 419 getTypeNameLength(RemoveStars, 420 tooling::fixit::getText(Loc.getSourceRange(), 421 FirstDecl->getASTContext())) < 422 MinTypeNameLength) 423 return; 424 425 auto Diag = diag(Range.getBegin(), Message); 426 427 bool ShouldReplenishVariableName = isMultiLevelPointerToTypeLocClasses( 428 TSI->getTypeLoc(), {TypeLoc::FunctionProto, TypeLoc::ConstantArray}); 429 430 // Space after 'auto' to handle cases where the '*' in the pointer type is 431 // next to the identifier. This avoids changing 'int *p' into 'autop'. 432 llvm::StringRef Auto = ShouldReplenishVariableName 433 ? (RemoveStars ? "auto " : "auto *") 434 : (RemoveStars ? "auto " : "auto"); 435 std::string ReplenishedVariableName = 436 ShouldReplenishVariableName ? FirstDecl->getQualifiedNameAsString() : ""; 437 std::string Replacement = 438 (Auto + llvm::StringRef{ReplenishedVariableName}).str(); 439 Diag << FixItHint::CreateReplacement(Range, Replacement) << StarRemovals; 440 } 441 442 void UseAutoCheck::check(const MatchFinder::MatchResult &Result) { 443 if (const auto *Decl = Result.Nodes.getNodeAs<DeclStmt>(IteratorDeclStmtId)) { 444 replaceIterators(Decl, Result.Context); 445 } else if (const auto *Decl = 446 Result.Nodes.getNodeAs<DeclStmt>(DeclWithNewId)) { 447 replaceExpr(Decl, Result.Context, 448 [](const Expr *Expr) { return Expr->getType(); }, 449 "use auto when initializing with new to avoid " 450 "duplicating the type name"); 451 } else if (const auto *Decl = 452 Result.Nodes.getNodeAs<DeclStmt>(DeclWithCastId)) { 453 replaceExpr( 454 Decl, Result.Context, 455 [](const Expr *Expr) { 456 return cast<ExplicitCastExpr>(Expr)->getTypeAsWritten(); 457 }, 458 "use auto when initializing with a cast to avoid duplicating the type " 459 "name"); 460 } else if (const auto *Decl = 461 Result.Nodes.getNodeAs<DeclStmt>(DeclWithTemplateCastId)) { 462 replaceExpr( 463 Decl, Result.Context, 464 [](const Expr *Expr) { 465 return cast<CallExpr>(Expr->IgnoreImplicit()) 466 ->getDirectCallee() 467 ->getReturnType(); 468 }, 469 "use auto when initializing with a template cast to avoid duplicating " 470 "the type name"); 471 } else { 472 llvm_unreachable("Bad Callback. No node provided."); 473 } 474 } 475 476 } // namespace clang::tidy::modernize 477