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