320 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 "QualifiedAutoCheck.h"10#include "../utils/LexerUtils.h"11#include "../utils/Matchers.h"12#include "../utils/OptionsUtils.h"13#include "clang/ASTMatchers/ASTMatchers.h"14#include "llvm/ADT/SmallVector.h"15#include <optional>16 17using namespace clang::ast_matchers;18 19namespace clang::tidy::readability {20 21namespace {22 23// FIXME move to ASTMatchers24AST_MATCHER_P(QualType, hasUnqualifiedType,25 ast_matchers::internal::Matcher<QualType>, InnerMatcher) {26 return InnerMatcher.matches(Node.getUnqualifiedType(), Finder, Builder);27}28 29enum class Qualifier { Const, Volatile, Restrict };30 31} // namespace32 33static std::optional<Token>34findQualToken(const VarDecl *Decl, Qualifier Qual,35 const MatchFinder::MatchResult &Result) {36 // Since either of the locs can be in a macro, use `makeFileCharRange` to be37 // sure that we have a consistent `CharSourceRange`, located entirely in the38 // source file.39 40 assert((Qual == Qualifier::Const || Qual == Qualifier::Volatile ||41 Qual == Qualifier::Restrict) &&42 "Invalid Qualifier");43 44 SourceLocation BeginLoc = Decl->getQualifierLoc().getBeginLoc();45 if (BeginLoc.isInvalid())46 BeginLoc = Decl->getBeginLoc();47 const SourceLocation EndLoc = Decl->getLocation();48 49 const CharSourceRange FileRange = Lexer::makeFileCharRange(50 CharSourceRange::getCharRange(BeginLoc, EndLoc), *Result.SourceManager,51 Result.Context->getLangOpts());52 53 if (FileRange.isInvalid())54 return std::nullopt;55 56 const tok::TokenKind Tok = Qual == Qualifier::Const ? tok::kw_const57 : Qual == Qualifier::Volatile ? tok::kw_volatile58 : tok::kw_restrict;59 60 return utils::lexer::getQualifyingToken(Tok, FileRange, *Result.Context,61 *Result.SourceManager);62}63 64static std::optional<SourceRange>65getTypeSpecifierLocation(const VarDecl *Var,66 const MatchFinder::MatchResult &Result) {67 SourceRange TypeSpecifier(68 Var->getTypeSpecStartLoc(),69 Var->getTypeSpecEndLoc().getLocWithOffset(Lexer::MeasureTokenLength(70 Var->getTypeSpecEndLoc(), *Result.SourceManager,71 Result.Context->getLangOpts())));72 73 if (TypeSpecifier.getBegin().isMacroID() ||74 TypeSpecifier.getEnd().isMacroID())75 return std::nullopt;76 return TypeSpecifier;77}78 79static std::optional<SourceRange>80mergeReplacementRange(SourceRange &TypeSpecifier, const Token &ConstToken) {81 if (TypeSpecifier.getBegin().getLocWithOffset(-1) == ConstToken.getEndLoc()) {82 TypeSpecifier.setBegin(ConstToken.getLocation());83 return std::nullopt;84 }85 if (TypeSpecifier.getEnd().getLocWithOffset(1) == ConstToken.getLocation()) {86 TypeSpecifier.setEnd(ConstToken.getEndLoc());87 return std::nullopt;88 }89 return SourceRange(ConstToken.getLocation(), ConstToken.getEndLoc());90}91 92static bool isPointerConst(QualType QType) {93 const QualType Pointee = QType->getPointeeType();94 assert(!Pointee.isNull() && "can't have a null Pointee");95 return Pointee.isConstQualified();96}97 98static bool isAutoPointerConst(QualType QType) {99 const QualType Pointee =100 cast<AutoType>(QType->getPointeeType().getTypePtr())->desugar();101 assert(!Pointee.isNull() && "can't have a null Pointee");102 return Pointee.isConstQualified();103}104 105QualifiedAutoCheck::QualifiedAutoCheck(StringRef Name,106 ClangTidyContext *Context)107 : ClangTidyCheck(Name, Context),108 AddConstToQualified(Options.get("AddConstToQualified", true)),109 AllowedTypes(110 utils::options::parseStringList(Options.get("AllowedTypes", ""))),111 IgnoreAliasing(Options.get("IgnoreAliasing", true)) {}112 113void QualifiedAutoCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {114 Options.store(Opts, "AddConstToQualified", AddConstToQualified);115 Options.store(Opts, "AllowedTypes",116 utils::options::serializeStringList(AllowedTypes));117 Options.store(Opts, "IgnoreAliasing", IgnoreAliasing);118}119 120void QualifiedAutoCheck::registerMatchers(MatchFinder *Finder) {121 auto ExplicitSingleVarDecl =122 [](const ast_matchers::internal::Matcher<VarDecl> &InnerMatcher,123 llvm::StringRef ID) {124 return declStmt(125 unless(isInTemplateInstantiation()),126 hasSingleDecl(127 varDecl(unless(isImplicit()), InnerMatcher).bind(ID)));128 };129 auto ExplicitSingleVarDeclInTemplate =130 [](const ast_matchers::internal::Matcher<VarDecl> &InnerMatcher,131 llvm::StringRef ID) {132 return declStmt(133 isInTemplateInstantiation(),134 hasSingleDecl(135 varDecl(unless(isImplicit()), InnerMatcher).bind(ID)));136 };137 138 auto IsBoundToType = refersToType(equalsBoundNode("type"));139 auto UnlessFunctionType = unless(hasUnqualifiedDesugaredType(functionType()));140 141 auto IsPointerType = [this](const auto &...InnerMatchers) {142 if (this->IgnoreAliasing) {143 return qualType(144 hasUnqualifiedDesugaredType(pointerType(pointee(InnerMatchers...))));145 }146 return qualType(anyOf(qualType(pointerType(pointee(InnerMatchers...))),147 qualType(substTemplateTypeParmType(hasReplacementType(148 pointerType(pointee(InnerMatchers...)))))));149 };150 151 auto IsAutoDeducedToPointer =152 [IsPointerType](const std::vector<StringRef> &AllowedTypes,153 const auto &...InnerMatchers) {154 return autoType(hasDeducedType(155 IsPointerType(InnerMatchers...),156 unless(hasUnqualifiedType(157 matchers::matchesAnyListedTypeName(AllowedTypes, false))),158 unless(pointerType(pointee(hasUnqualifiedType(159 matchers::matchesAnyListedTypeName(AllowedTypes, false)))))));160 };161 162 Finder->addMatcher(163 ExplicitSingleVarDecl(164 hasType(IsAutoDeducedToPointer(AllowedTypes, UnlessFunctionType)),165 "auto"),166 this);167 168 Finder->addMatcher(169 ExplicitSingleVarDeclInTemplate(170 allOf(hasType(IsAutoDeducedToPointer(171 AllowedTypes, hasUnqualifiedType(qualType().bind("type")),172 UnlessFunctionType)),173 anyOf(hasAncestor(174 functionDecl(hasAnyTemplateArgument(IsBoundToType))),175 hasAncestor(classTemplateSpecializationDecl(176 hasAnyTemplateArgument(IsBoundToType))))),177 "auto"),178 this);179 if (!AddConstToQualified)180 return;181 Finder->addMatcher(ExplicitSingleVarDecl(182 hasType(pointerType(pointee(autoType()))), "auto_ptr"),183 this);184 Finder->addMatcher(185 ExplicitSingleVarDecl(hasType(lValueReferenceType(pointee(autoType()))),186 "auto_ref"),187 this);188}189 190void QualifiedAutoCheck::check(const MatchFinder::MatchResult &Result) {191 if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("auto")) {192 SourceRange TypeSpecifier;193 if (std::optional<SourceRange> TypeSpec =194 getTypeSpecifierLocation(Var, Result)) {195 TypeSpecifier = *TypeSpec;196 } else197 return;198 199 llvm::SmallVector<SourceRange, 4> RemoveQualifiersRange;200 auto CheckQualifier = [&](bool IsPresent, Qualifier Qual) {201 if (IsPresent) {202 std::optional<Token> Token = findQualToken(Var, Qual, Result);203 if (!Token || Token->getLocation().isMacroID())204 return true; // Disregard this VarDecl.205 if (std::optional<SourceRange> Result =206 mergeReplacementRange(TypeSpecifier, *Token))207 RemoveQualifiersRange.push_back(*Result);208 }209 return false;210 };211 212 bool IsLocalConst = Var->getType().isLocalConstQualified();213 bool IsLocalVolatile = Var->getType().isLocalVolatileQualified();214 bool IsLocalRestrict = Var->getType().isLocalRestrictQualified();215 216 if (CheckQualifier(IsLocalConst, Qualifier::Const) ||217 CheckQualifier(IsLocalVolatile, Qualifier::Volatile) ||218 CheckQualifier(IsLocalRestrict, Qualifier::Restrict))219 return;220 221 // Check for bridging the gap between the asterisk and name.222 if (Var->getLocation() == TypeSpecifier.getEnd().getLocWithOffset(1))223 TypeSpecifier.setEnd(TypeSpecifier.getEnd().getLocWithOffset(1));224 225 const CharSourceRange FixItRange =226 CharSourceRange::getCharRange(TypeSpecifier);227 if (FixItRange.isInvalid())228 return;229 230 SourceLocation FixitLoc = FixItRange.getBegin();231 for (const SourceRange &Range : RemoveQualifiersRange) {232 if (Range.getBegin() < FixitLoc)233 FixitLoc = Range.getBegin();234 }235 236 const std::string ReplStr = [&] {237 const StringRef PtrConst = isPointerConst(Var->getType()) ? "const " : "";238 const StringRef LocalConst = IsLocalConst ? "const " : "";239 const StringRef LocalVol = IsLocalVolatile ? "volatile " : "";240 const StringRef LocalRestrict = IsLocalRestrict ? "__restrict " : "";241 return (PtrConst + "auto *" + LocalConst + LocalVol + LocalRestrict)242 .str();243 }();244 245 const DiagnosticBuilder Diag =246 diag(FixitLoc,247 "'%select{|const }0%select{|volatile }1%select{|__restrict }2auto "248 "%3' can be declared as '%4%3'")249 << IsLocalConst << IsLocalVolatile << IsLocalRestrict << Var->getName()250 << ReplStr;251 252 for (const SourceRange &Range : RemoveQualifiersRange) {253 Diag << FixItHint::CreateRemoval(CharSourceRange::getCharRange(Range));254 }255 256 Diag << FixItHint::CreateReplacement(FixItRange, ReplStr);257 return;258 }259 if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("auto_ptr")) {260 if (!isPointerConst(Var->getType()))261 return; // Pointer isn't const, no need to add const qualifier.262 if (!isAutoPointerConst(Var->getType()))263 return; // Const isn't wrapped in the auto type, so must be declared264 // explicitly.265 266 if (Var->getType().isLocalConstQualified()) {267 std::optional<Token> Token = findQualToken(Var, Qualifier::Const, Result);268 if (!Token || Token->getLocation().isMacroID())269 return;270 }271 if (Var->getType().isLocalVolatileQualified()) {272 std::optional<Token> Token =273 findQualToken(Var, Qualifier::Volatile, Result);274 if (!Token || Token->getLocation().isMacroID())275 return;276 }277 if (Var->getType().isLocalRestrictQualified()) {278 std::optional<Token> Token =279 findQualToken(Var, Qualifier::Restrict, Result);280 if (!Token || Token->getLocation().isMacroID())281 return;282 }283 284 if (std::optional<SourceRange> TypeSpec =285 getTypeSpecifierLocation(Var, Result)) {286 if (TypeSpec->isInvalid() || TypeSpec->getBegin().isMacroID() ||287 TypeSpec->getEnd().isMacroID())288 return;289 const SourceLocation InsertPos = TypeSpec->getBegin();290 diag(InsertPos,291 "'auto *%select{|const }0%select{|volatile }1%2' can be declared as "292 "'const auto *%select{|const }0%select{|volatile }1%2'")293 << Var->getType().isLocalConstQualified()294 << Var->getType().isLocalVolatileQualified() << Var->getName()295 << FixItHint::CreateInsertion(InsertPos, "const ");296 }297 return;298 }299 if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("auto_ref")) {300 if (!isPointerConst(Var->getType()))301 return; // Pointer isn't const, no need to add const qualifier.302 if (!isAutoPointerConst(Var->getType()))303 // Const isn't wrapped in the auto type, so must be declared explicitly.304 return;305 306 if (std::optional<SourceRange> TypeSpec =307 getTypeSpecifierLocation(Var, Result)) {308 if (TypeSpec->isInvalid() || TypeSpec->getBegin().isMacroID() ||309 TypeSpec->getEnd().isMacroID())310 return;311 const SourceLocation InsertPos = TypeSpec->getBegin();312 diag(InsertPos, "'auto &%0' can be declared as 'const auto &%0'")313 << Var->getName() << FixItHint::CreateInsertion(InsertPos, "const ");314 }315 return;316 }317}318 319} // namespace clang::tidy::readability320