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 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 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> 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 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 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 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 105 void QualifiedAutoCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { 106 Options.store(Opts, "AddConstToQualified", AddConstToQualified); 107 } 108 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 auto UnlessFunctionType = unless(hasUnqualifiedDesugaredType(functionType())); 129 auto IsAutoDeducedToPointer = [](const auto &...InnerMatchers) { 130 return autoType(hasDeducedType( 131 hasUnqualifiedDesugaredType(pointerType(pointee(InnerMatchers...))))); 132 }; 133 134 Finder->addMatcher( 135 ExplicitSingleVarDecl(hasType(IsAutoDeducedToPointer(UnlessFunctionType)), 136 "auto"), 137 this); 138 139 Finder->addMatcher( 140 ExplicitSingleVarDeclInTemplate( 141 allOf(hasType(IsAutoDeducedToPointer( 142 hasUnqualifiedType(qualType().bind("type")), 143 UnlessFunctionType)), 144 anyOf(hasAncestor( 145 functionDecl(hasAnyTemplateArgument(IsBoundToType))), 146 hasAncestor(classTemplateSpecializationDecl( 147 hasAnyTemplateArgument(IsBoundToType))))), 148 "auto"), 149 this); 150 if (!AddConstToQualified) 151 return; 152 Finder->addMatcher(ExplicitSingleVarDecl( 153 hasType(pointerType(pointee(autoType()))), "auto_ptr"), 154 this); 155 Finder->addMatcher( 156 ExplicitSingleVarDecl(hasType(lValueReferenceType(pointee(autoType()))), 157 "auto_ref"), 158 this); 159 } 160 161 void QualifiedAutoCheck::check(const MatchFinder::MatchResult &Result) { 162 if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("auto")) { 163 SourceRange TypeSpecifier; 164 if (llvm::Optional<SourceRange> TypeSpec = 165 getTypeSpecifierLocation(Var, Result)) { 166 TypeSpecifier = *TypeSpec; 167 } else 168 return; 169 170 llvm::SmallVector<SourceRange, 4> RemoveQualifiersRange; 171 auto CheckQualifier = [&](bool IsPresent, Qualifier Qual) { 172 if (IsPresent) { 173 llvm::Optional<Token> Token = findQualToken(Var, Qual, Result); 174 if (!Token || Token->getLocation().isMacroID()) 175 return true; // Disregard this VarDecl. 176 if (llvm::Optional<SourceRange> Result = 177 mergeReplacementRange(TypeSpecifier, *Token)) 178 RemoveQualifiersRange.push_back(*Result); 179 } 180 return false; 181 }; 182 183 bool IsLocalConst = Var->getType().isLocalConstQualified(); 184 bool IsLocalVolatile = Var->getType().isLocalVolatileQualified(); 185 bool IsLocalRestrict = Var->getType().isLocalRestrictQualified(); 186 187 if (CheckQualifier(IsLocalConst, Qualifier::Const) || 188 CheckQualifier(IsLocalVolatile, Qualifier::Volatile) || 189 CheckQualifier(IsLocalRestrict, Qualifier::Restrict)) 190 return; 191 192 // Check for bridging the gap between the asterisk and name. 193 if (Var->getLocation() == TypeSpecifier.getEnd().getLocWithOffset(1)) 194 TypeSpecifier.setEnd(TypeSpecifier.getEnd().getLocWithOffset(1)); 195 196 CharSourceRange FixItRange = CharSourceRange::getCharRange(TypeSpecifier); 197 if (FixItRange.isInvalid()) 198 return; 199 200 SourceLocation FixitLoc = FixItRange.getBegin(); 201 for (SourceRange &Range : RemoveQualifiersRange) { 202 if (Range.getBegin() < FixitLoc) 203 FixitLoc = Range.getBegin(); 204 } 205 206 std::string ReplStr = [&] { 207 llvm::StringRef PtrConst = isPointerConst(Var->getType()) ? "const " : ""; 208 llvm::StringRef LocalConst = IsLocalConst ? "const " : ""; 209 llvm::StringRef LocalVol = IsLocalVolatile ? "volatile " : ""; 210 llvm::StringRef LocalRestrict = IsLocalRestrict ? "__restrict " : ""; 211 return (PtrConst + "auto *" + LocalConst + LocalVol + LocalRestrict) 212 .str(); 213 }(); 214 215 DiagnosticBuilder Diag = 216 diag(FixitLoc, 217 "'%select{|const }0%select{|volatile }1%select{|__restrict }2auto " 218 "%3' can be declared as '%4%3'") 219 << IsLocalConst << IsLocalVolatile << IsLocalRestrict << Var->getName() 220 << ReplStr; 221 222 for (SourceRange &Range : RemoveQualifiersRange) { 223 Diag << FixItHint::CreateRemoval(CharSourceRange::getCharRange(Range)); 224 } 225 226 Diag << FixItHint::CreateReplacement(FixItRange, ReplStr); 227 return; 228 } 229 if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("auto_ptr")) { 230 if (!isPointerConst(Var->getType())) 231 return; // Pointer isn't const, no need to add const qualifier. 232 if (!isAutoPointerConst(Var->getType())) 233 return; // Const isn't wrapped in the auto type, so must be declared 234 // explicitly. 235 236 if (Var->getType().isLocalConstQualified()) { 237 llvm::Optional<Token> Token = 238 findQualToken(Var, Qualifier::Const, Result); 239 if (!Token || Token->getLocation().isMacroID()) 240 return; 241 } 242 if (Var->getType().isLocalVolatileQualified()) { 243 llvm::Optional<Token> Token = 244 findQualToken(Var, Qualifier::Volatile, Result); 245 if (!Token || Token->getLocation().isMacroID()) 246 return; 247 } 248 if (Var->getType().isLocalRestrictQualified()) { 249 llvm::Optional<Token> Token = 250 findQualToken(Var, Qualifier::Restrict, Result); 251 if (!Token || Token->getLocation().isMacroID()) 252 return; 253 } 254 255 if (llvm::Optional<SourceRange> TypeSpec = 256 getTypeSpecifierLocation(Var, Result)) { 257 if (TypeSpec->isInvalid() || TypeSpec->getBegin().isMacroID() || 258 TypeSpec->getEnd().isMacroID()) 259 return; 260 SourceLocation InsertPos = TypeSpec->getBegin(); 261 diag(InsertPos, 262 "'auto *%select{|const }0%select{|volatile }1%2' can be declared as " 263 "'const auto *%select{|const }0%select{|volatile }1%2'") 264 << Var->getType().isLocalConstQualified() 265 << Var->getType().isLocalVolatileQualified() << Var->getName() 266 << FixItHint::CreateInsertion(InsertPos, "const "); 267 } 268 return; 269 } 270 if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("auto_ref")) { 271 if (!isPointerConst(Var->getType())) 272 return; // Pointer isn't const, no need to add const qualifier. 273 if (!isAutoPointerConst(Var->getType())) 274 // Const isn't wrapped in the auto type, so must be declared explicitly. 275 return; 276 277 if (llvm::Optional<SourceRange> TypeSpec = 278 getTypeSpecifierLocation(Var, Result)) { 279 if (TypeSpec->isInvalid() || TypeSpec->getBegin().isMacroID() || 280 TypeSpec->getEnd().isMacroID()) 281 return; 282 SourceLocation InsertPos = TypeSpec->getBegin(); 283 diag(InsertPos, "'auto &%0' can be declared as 'const auto &%0'") 284 << Var->getName() << FixItHint::CreateInsertion(InsertPos, "const "); 285 } 286 return; 287 } 288 } 289 290 } // namespace readability 291 } // namespace tidy 292 } // namespace clang 293