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