476 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//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 18using namespace clang;19using namespace clang::ast_matchers;20using namespace clang::ast_matchers::internal;21 22namespace clang::tidy::modernize {23 24static const char IteratorDeclStmtId[] = "iterator_decl";25static const char DeclWithNewId[] = "decl_new";26static const char DeclWithCastId[] = "decl_cast";27static const char DeclWithTemplateCastId[] = "decl_template";28 29static size_t getTypeNameLength(bool RemoveStars, StringRef Text) {30 enum CharType { Space, Alpha, Punctuation };31 CharType LastChar = Space, BeforeSpace = Punctuation;32 size_t NumChars = 0;33 int TemplateTypenameCntr = 0;34 for (const unsigned char C : Text) {35 if (C == '<')36 ++TemplateTypenameCntr;37 else if (C == '>')38 --TemplateTypenameCntr;39 const CharType NextChar =40 isAlphanumeric(C) ? Alpha41 : (isWhitespace(C) ||42 (!RemoveStars && TemplateTypenameCntr == 0 && C == '*'))43 ? Space44 : Punctuation;45 if (NextChar != Space) {46 ++NumChars; // Count the non-space character.47 if (LastChar == Space && NextChar == Alpha && BeforeSpace == Alpha)48 ++NumChars; // Count a single space character between two words.49 BeforeSpace = NextChar;50 }51 LastChar = NextChar;52 }53 return NumChars;54}55 56namespace {57/// Matches variable declarations that have explicit initializers that58/// are not initializer lists.59///60/// Given61/// \code62/// iterator I = Container.begin();63/// MyType A(42);64/// MyType B{2};65/// MyType C;66/// \endcode67///68/// varDecl(hasWrittenNonListInitializer()) matches \c I and \c A but not \c B69/// or \c C.70AST_MATCHER(VarDecl, hasWrittenNonListInitializer) {71 const Expr *Init = Node.getAnyInitializer();72 if (!Init)73 return false;74 75 Init = Init->IgnoreImplicit();76 77 // The following test is based on DeclPrinter::VisitVarDecl() to find if an78 // initializer is implicit or not.79 if (const auto *Construct = dyn_cast<CXXConstructExpr>(Init)) {80 return !Construct->isListInitialization() && Construct->getNumArgs() > 0 &&81 !Construct->getArg(0)->isDefaultArgument();82 }83 return Node.getInitStyle() != VarDecl::ListInit;84}85 86/// Matches QualTypes that are type sugar for QualTypes that match \c87/// SugarMatcher.88///89/// Given90/// \code91/// class C {};92/// typedef C my_type;93/// typedef my_type my_other_type;94/// \endcode95///96/// qualType(isSugarFor(recordType(hasDeclaration(namedDecl(hasName("C"))))))97/// matches \c my_type and \c my_other_type.98AST_MATCHER_P(QualType, isSugarFor, Matcher<QualType>, SugarMatcher) {99 QualType QT = Node;100 while (true) {101 if (SugarMatcher.matches(QT, Finder, Builder))102 return true;103 104 const QualType NewQT =105 QT.getSingleStepDesugaredType(Finder->getASTContext());106 if (NewQT == QT)107 return false;108 QT = NewQT;109 }110}111 112/// Matches declaration reference or member expressions with explicit template113/// arguments.114AST_POLYMORPHIC_MATCHER(hasExplicitTemplateArgs,115 AST_POLYMORPHIC_SUPPORTED_TYPES(DeclRefExpr,116 MemberExpr)) {117 return Node.hasExplicitTemplateArgs();118}119} // namespace120 121/// Matches named declarations that have one of the standard iterator122/// names: iterator, reverse_iterator, const_iterator, const_reverse_iterator.123///124/// Given125/// \code126/// iterator I;127/// const_iterator CI;128/// \endcode129///130/// namedDecl(hasStdIteratorName()) matches \c I and \c CI.131static Matcher<NamedDecl> hasStdIteratorName() {132 static const StringRef IteratorNames[] = {"iterator", "reverse_iterator",133 "const_iterator",134 "const_reverse_iterator"};135 return hasAnyName(IteratorNames);136}137 138/// Matches named declarations that have one of the standard container139/// names.140///141/// Given142/// \code143/// class vector {};144/// class forward_list {};145/// class my_ver{};146/// \endcode147///148/// recordDecl(hasStdContainerName()) matches \c vector and \c forward_list149/// but not \c my_vec.150static Matcher<NamedDecl> hasStdContainerName() {151 static const StringRef ContainerNames[] = {152 "array", "deque",153 "forward_list", "list",154 "vector",155 156 "map", "multimap",157 "set", "multiset",158 159 "unordered_map", "unordered_multimap",160 "unordered_set", "unordered_multiset",161 162 "queue", "priority_queue",163 "stack"};164 165 return hasAnyName(ContainerNames);166}167 168/// Returns a DeclarationMatcher that matches standard iterators nested169/// inside records with a standard container name.170static DeclarationMatcher standardIterator() {171 return decl(172 namedDecl(hasStdIteratorName()),173 hasDeclContext(recordDecl(hasStdContainerName(), isInStdNamespace())));174}175 176/// Returns a TypeMatcher that matches typedefs for standard iterators177/// inside records with a standard container name.178static TypeMatcher typedefIterator() {179 return typedefType(hasDeclaration(standardIterator()));180}181 182/// Returns a TypeMatcher that matches records named for standard183/// iterators nested inside records named for standard containers.184static TypeMatcher nestedIterator() {185 return recordType(hasDeclaration(standardIterator()));186}187 188/// Returns a TypeMatcher that matches types declared with using189/// declarations and which name standard iterators for standard containers.190static TypeMatcher iteratorFromUsingDeclaration() {191 auto HasIteratorDecl = hasDeclaration(namedDecl(hasStdIteratorName()));192 // Unwrap the nested name specifier to test for one of the standard193 // containers.194 auto Qualifier = hasQualifier(specifiesType(templateSpecializationType(195 hasDeclaration(namedDecl(hasStdContainerName(), isInStdNamespace())))));196 // the named type is what comes after the final '::' in the type. It should197 // name one of the standard iterator names.198 return anyOf(typedefType(HasIteratorDecl, Qualifier),199 recordType(HasIteratorDecl, Qualifier));200}201 202/// This matcher returns declaration statements that contain variable203/// declarations with written non-list initializer for standard iterators.204static 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 213static StatementMatcher makeDeclWithNewMatcher() {214 return declStmt(215 unless(has(varDecl(anyOf(216 unless(hasInitializer(ignoringParenImpCasts(cxxNewExpr()))),217 // FIXME: TypeLoc information is not reliable where CV218 // qualifiers are concerned so these types can't be219 // handled for now.220 hasType(pointerType(221 pointee(hasCanonicalType(hasLocalQualifiers())))),222 223 // FIXME: Handle function pointers. For now we ignore them224 // because the replacement replaces the entire type225 // specifier source range which includes the identifier.226 hasType(pointsTo(227 pointsTo(parenType(innerType(functionType()))))))))))228 .bind(DeclWithNewId);229}230 231static StatementMatcher makeDeclWithCastMatcher() {232 return declStmt(233 unless(has(varDecl(unless(hasInitializer(explicitCastExpr()))))))234 .bind(DeclWithCastId);235}236 237static 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 258static StatementMatcher makeCombinedMatcher() {259 return declStmt(260 // At least one varDecl should be a child of the declStmt to ensure261 // 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 271UseAutoCheck::UseAutoCheck(StringRef Name, ClangTidyContext *Context)272 : ClangTidyCheck(Name, Context),273 MinTypeNameLength(Options.get("MinTypeNameLength", 5)),274 RemoveStars(Options.get("RemoveStars", false)) {}275 276void UseAutoCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {277 Options.store(Opts, "MinTypeNameLength", MinTypeNameLength);278 Options.store(Opts, "RemoveStars", RemoveStars);279}280 281void UseAutoCheck::registerMatchers(MatchFinder *Finder) {282 Finder->addMatcher(traverse(TK_AsIs, makeCombinedMatcher()), this);283}284 285void UseAutoCheck::replaceIterators(const DeclStmt *D, ASTContext *Context) {286 for (const auto *Dec : D->decls()) {287 const auto *V = cast<VarDecl>(Dec);288 const Expr *ExprInit = V->getInit();289 290 // Skip expressions with cleanups from the initializer expression.291 if (const auto *E = dyn_cast<ExprWithCleanups>(ExprInit))292 ExprInit = E->getSubExpr();293 294 const auto *Construct = dyn_cast<CXXConstructExpr>(ExprInit);295 if (!Construct)296 continue;297 298 // Ensure that the constructor receives a single argument.299 if (Construct->getNumArgs() != 1)300 return;301 302 // Drill down to the as-written initializer.303 const Expr *E = (*Construct->arg_begin())->IgnoreParenImpCasts();304 if (E != E->IgnoreConversionOperatorSingleStep()) {305 // We hit a conversion operator. Early-out now as they imply an implicit306 // conversion from a different type. Could also mean an explicit307 // conversion from the same type but that's pretty rare.308 return;309 }310 311 if (const auto *NestedConstruct = dyn_cast<CXXConstructExpr>(E)) {312 // If we ran into an implicit conversion constructor, can't convert.313 //314 // FIXME: The following only checks if the constructor can be used315 // implicitly, not if it actually was. Cases where the converting316 // constructor was used explicitly won't get converted.317 if (NestedConstruct->getConstructor()->isConvertingConstructor(false))318 return;319 }320 if (!ASTContext::hasSameType(V->getType(), E->getType()))321 return;322 }323 324 // Get the type location using the first declaration.325 const auto *V = cast<VarDecl>(*D->decl_begin());326 327 // WARNING: TypeLoc::getSourceRange() will include the identifier for things328 // like function pointers. Not a concern since this action only works with329 // iterators but something to keep in mind in the future.330 331 const SourceRange Range(332 V->getTypeSourceInfo()->getTypeLoc().getSourceRange());333 diag(Range.getBegin(), "use auto when declaring iterators")334 << FixItHint::CreateReplacement(Range, "auto");335}336 337static void ignoreTypeLocClasses(338 TypeLoc &Loc,339 const std::initializer_list<TypeLoc::TypeLocClass> &LocClasses) {340 while (llvm::is_contained(LocClasses, Loc.getTypeLocClass()))341 Loc = Loc.getNextTypeLoc();342}343 344static bool isMultiLevelPointerToTypeLocClasses(345 TypeLoc Loc,346 const std::initializer_list<TypeLoc::TypeLocClass> &LocClasses) {347 ignoreTypeLocClasses(Loc, {TypeLoc::Paren, TypeLoc::Qualified});348 const TypeLoc::TypeLocClass TLC = Loc.getTypeLocClass();349 if (TLC != TypeLoc::Pointer && TLC != TypeLoc::MemberPointer)350 return false;351 ignoreTypeLocClasses(Loc, {TypeLoc::Paren, TypeLoc::Qualified,352 TypeLoc::Pointer, TypeLoc::MemberPointer});353 return llvm::is_contained(LocClasses, Loc.getTypeLocClass());354}355 356void UseAutoCheck::replaceExpr(357 const DeclStmt *D, ASTContext *Context,358 llvm::function_ref<QualType(const Expr *)> GetType, StringRef Message) {359 const auto *FirstDecl = dyn_cast<VarDecl>(*D->decl_begin());360 // Ensure that there is at least one VarDecl within the DeclStmt.361 if (!FirstDecl)362 return;363 364 const QualType FirstDeclType = FirstDecl->getType().getCanonicalType();365 const TypeSourceInfo *TSI = FirstDecl->getTypeSourceInfo();366 367 if (TSI == nullptr)368 return;369 370 std::vector<FixItHint> StarRemovals;371 for (const auto *Dec : D->decls()) {372 const auto *V = cast<VarDecl>(Dec);373 // Ensure that every DeclStmt child is a VarDecl.374 if (!V)375 return;376 377 const auto *Expr = V->getInit()->IgnoreParenImpCasts();378 // Ensure that every VarDecl has an initializer.379 if (!Expr)380 return;381 382 // If VarDecl and Initializer have mismatching unqualified types.383 if (!ASTContext::hasSameUnqualifiedType(V->getType(), GetType(Expr)))384 return;385 386 // All subsequent variables in this declaration should have the same387 // canonical type. For example, we don't want to use `auto` in388 // `T *p = new T, **pp = new T*;`.389 if (FirstDeclType != V->getType().getCanonicalType())390 return;391 392 if (RemoveStars) {393 // Remove explicitly written '*' from declarations where there's more than394 // one declaration in the declaration list.395 if (Dec == *D->decl_begin())396 continue;397 398 auto Q = V->getTypeSourceInfo()->getTypeLoc().getAs<PointerTypeLoc>();399 while (!Q.isNull()) {400 StarRemovals.push_back(FixItHint::CreateRemoval(Q.getStarLoc()));401 Q = Q.getNextTypeLoc().getAs<PointerTypeLoc>();402 }403 }404 }405 406 // FIXME: There is, however, one case we can address: when the VarDecl pointee407 // is the same as the initializer, just more CV-qualified. However, TypeLoc408 // information is not reliable where CV qualifiers are concerned so we can't409 // do anything about this case for now.410 TypeLoc Loc = TSI->getTypeLoc();411 if (!RemoveStars)412 ignoreTypeLocClasses(Loc, {TypeLoc::Pointer, TypeLoc::Qualified});413 ignoreTypeLocClasses(Loc, {TypeLoc::LValueReference, TypeLoc::RValueReference,414 TypeLoc::Qualified});415 const SourceRange Range(Loc.getSourceRange());416 417 if (MinTypeNameLength != 0 &&418 getTypeNameLength(RemoveStars,419 tooling::fixit::getText(Loc.getSourceRange(),420 FirstDecl->getASTContext())) <421 MinTypeNameLength)422 return;423 424 auto Diag = diag(Range.getBegin(), Message);425 426 const bool ShouldReplenishVariableName = isMultiLevelPointerToTypeLocClasses(427 TSI->getTypeLoc(), {TypeLoc::FunctionProto, TypeLoc::ConstantArray});428 429 // Space after 'auto' to handle cases where the '*' in the pointer type is430 // next to the identifier. This avoids changing 'int *p' into 'autop'.431 const llvm::StringRef Auto = ShouldReplenishVariableName432 ? (RemoveStars ? "auto " : "auto *")433 : (RemoveStars ? "auto " : "auto");434 const std::string ReplenishedVariableName =435 ShouldReplenishVariableName ? FirstDecl->getQualifiedNameAsString() : "";436 const std::string Replacement =437 (Auto + llvm::StringRef{ReplenishedVariableName}).str();438 Diag << FixItHint::CreateReplacement(Range, Replacement) << StarRemovals;439}440 441void UseAutoCheck::check(const MatchFinder::MatchResult &Result) {442 if (const auto *Decl = Result.Nodes.getNodeAs<DeclStmt>(IteratorDeclStmtId)) {443 replaceIterators(Decl, Result.Context);444 } else if (const auto *Decl =445 Result.Nodes.getNodeAs<DeclStmt>(DeclWithNewId)) {446 replaceExpr(447 Decl, Result.Context, [](const Expr *Expr) { return Expr->getType(); },448 "use auto when initializing with new to avoid "449 "duplicating the type name");450 } else if (const auto *Decl =451 Result.Nodes.getNodeAs<DeclStmt>(DeclWithCastId)) {452 replaceExpr(453 Decl, Result.Context,454 [](const Expr *Expr) {455 return cast<ExplicitCastExpr>(Expr)->getTypeAsWritten();456 },457 "use auto when initializing with a cast to avoid duplicating the type "458 "name");459 } else if (const auto *Decl =460 Result.Nodes.getNodeAs<DeclStmt>(DeclWithTemplateCastId)) {461 replaceExpr(462 Decl, Result.Context,463 [](const Expr *Expr) {464 return cast<CallExpr>(Expr->IgnoreImplicit())465 ->getDirectCallee()466 ->getReturnType();467 },468 "use auto when initializing with a template cast to avoid duplicating "469 "the type name");470 } else {471 llvm_unreachable("Bad Callback. No node provided.");472 }473}474 475} // namespace clang::tidy::modernize476