clang-tools 23.0.0git
UseUsingCheck.cpp
Go to the documentation of this file.
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-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "UseUsingCheck.h"
10#include "../utils/LexerUtils.h"
11#include "clang/AST/DeclGroup.h"
12#include "clang/Basic/LangOptions.h"
13#include "clang/Basic/SourceLocation.h"
14#include "clang/Basic/SourceManager.h"
15#include "clang/Basic/TokenKinds.h"
16#include "clang/Lex/Lexer.h"
17#include <string>
18
19using namespace clang::ast_matchers;
20
21namespace clang::tidy::modernize {
22
23namespace lexer = clang::tidy::utils::lexer;
24
25namespace {
26
27AST_MATCHER(LinkageSpecDecl, isExternCLinkage) {
28 return Node.getLanguage() == LinkageSpecLanguageIDs::C;
29}
30
31} // namespace
32
33namespace {
34
35struct TokenRangeInfo {
36 bool HasComment = false;
37 bool HasIdentifier = false;
38 bool HasPointerOrRef = false;
39};
40
41struct RangeTextInfo {
42 std::string Text;
43 TokenRangeInfo Tokens;
44};
45
46} // namespace
47
48static StringRef::size_type findFirstNonWhitespace(StringRef Text) {
49 return Text.find_first_not_of(" \t\n\r\f\v");
50}
51
52static std::optional<std::string> getSourceText(CharSourceRange Range,
53 const SourceManager &SM,
54 const LangOptions &LangOpts) {
55 if (Range.isInvalid())
56 return std::nullopt;
57
58 const CharSourceRange FileRange =
59 Lexer::makeFileCharRange(Range, SM, LangOpts);
60 if (FileRange.isInvalid())
61 return std::nullopt;
62
63 bool IsInvalid = false;
64 const StringRef Text =
65 Lexer::getSourceText(FileRange, SM, LangOpts, &IsInvalid);
66 if (IsInvalid)
67 return std::nullopt;
68 return Text.str();
69}
70
71static TokenRangeInfo getTokenRangeInfo(CharSourceRange Range,
72 const SourceManager &SM,
73 const LangOptions &LangOpts) {
74 TokenRangeInfo Info;
75 if (Range.isInvalid())
76 return Info;
77
78 const CharSourceRange FileRange =
79 Lexer::makeFileCharRange(Range, SM, LangOpts);
80 if (FileRange.isInvalid())
81 return Info;
82
83 const auto [BeginFID, BeginOffset] =
84 SM.getDecomposedLoc(FileRange.getBegin());
85 const auto [EndFID, EndOffset] = SM.getDecomposedLoc(FileRange.getEnd());
86 if (BeginFID != EndFID || BeginOffset > EndOffset)
87 return Info;
88
89 bool IsInvalid = false;
90 const StringRef Buffer = SM.getBufferData(BeginFID, &IsInvalid);
91 if (IsInvalid)
92 return Info;
93
94 const char *LexStart = Buffer.data() + BeginOffset;
95 Lexer TheLexer(SM.getLocForStartOfFile(BeginFID), LangOpts, Buffer.begin(),
96 LexStart, Buffer.end());
97 TheLexer.SetCommentRetentionState(true);
98
99 while (true) {
100 Token Tok;
101 if (TheLexer.LexFromRawLexer(Tok))
102 break;
103
104 if (Tok.is(tok::eof) || Tok.getLocation() == FileRange.getEnd() ||
105 SM.isBeforeInTranslationUnit(FileRange.getEnd(), Tok.getLocation()))
106 break;
107
108 if (Tok.is(tok::comment)) {
109 Info.HasComment = true;
110 continue;
111 }
112
113 if (Tok.isOneOf(tok::star, tok::amp))
114 Info.HasPointerOrRef = true;
115
116 if (tok::isAnyIdentifier(Tok.getKind()) ||
117 Tok.isOneOf(tok::kw_typedef, tok::kw_struct, tok::kw_class,
118 tok::kw_union, tok::kw_enum, tok::kw_typename,
119 tok::kw_template)) {
120 Info.HasIdentifier = true;
121 }
122 }
123
124 return Info;
125}
126
127static RangeTextInfo getRangeTextInfo(SourceLocation Begin, SourceLocation End,
128 const SourceManager &SM,
129 const LangOptions &LangOpts) {
130 if (!Begin.isValid() || !End.isValid() || Begin.isMacroID() ||
131 End.isMacroID())
132 return {};
133
134 const CharSourceRange Range = CharSourceRange::getCharRange(Begin, End);
135 if (std::optional<std::string> Text = getSourceText(Range, SM, LangOpts))
136 return {*Text, getTokenRangeInfo(Range, SM, LangOpts)};
137 return {};
138}
139
140static std::optional<std::string>
141getFunctionPointerTypeText(SourceRange TypeRange, SourceLocation NameLoc,
142 const SourceManager &SM, const LangOptions &LO) {
143 SourceLocation StartLoc = NameLoc;
144 SourceLocation EndLoc = NameLoc;
145
146 while (true) {
147 const std::optional<Token> Prev = lexer::getPreviousToken(StartLoc, SM, LO);
148 const std::optional<Token> Next =
149 lexer::findNextTokenSkippingComments(EndLoc, SM, LO);
150 if (!Prev || Prev->isNot(tok::l_paren) || !Next ||
151 Next->isNot(tok::r_paren))
152 break;
153
154 StartLoc = Prev->getLocation();
155 EndLoc = Next->getLocation();
156 }
157
158 const CharSourceRange RangeLeftOfIdentifier =
159 CharSourceRange::getCharRange(TypeRange.getBegin(), StartLoc);
160 const CharSourceRange RangeRightOfIdentifier = CharSourceRange::getCharRange(
161 Lexer::getLocForEndOfToken(EndLoc, 0, SM, LO),
162 Lexer::getLocForEndOfToken(TypeRange.getEnd(), 0, SM, LO));
163
164 const std::optional<std::string> LeftText =
165 getSourceText(RangeLeftOfIdentifier, SM, LO);
166 if (!LeftText)
167 return std::nullopt;
168
169 const std::optional<std::string> RightText =
170 getSourceText(RangeRightOfIdentifier, SM, LO);
171 if (!RightText)
172 return std::nullopt;
173
174 return *LeftText + *RightText;
175}
176
177static RangeTextInfo getLeadingTextInfo(bool IsFirstTypedefInGroup,
178 SourceRange ReplaceRange,
179 SourceRange TypeRange,
180 const SourceManager &SM,
181 const LangOptions &LO) {
182 if (!IsFirstTypedefInGroup)
183 return {};
184
185 const SourceLocation TypedefEnd =
186 Lexer::getLocForEndOfToken(ReplaceRange.getBegin(), 0, SM, LO);
187 RangeTextInfo Info =
188 getRangeTextInfo(TypedefEnd, TypeRange.getBegin(), SM, LO);
189 if (!Info.Tokens.HasComment)
190 Info.Text.clear();
191 return Info;
192}
193
194static RangeTextInfo
195getSuffixTextInfo(bool FunctionPointerCase, bool IsFirstTypedefInGroup,
196 SourceLocation PrevReplacementEnd, SourceRange TypeRange,
197 SourceLocation NameLoc, const SourceManager &SM,
198 const LangOptions &LO) {
199 if (FunctionPointerCase)
200 return {};
201
202 if (IsFirstTypedefInGroup) {
203 const SourceLocation AfterType =
204 Lexer::getLocForEndOfToken(TypeRange.getEnd(), 0, SM, LO);
205 return getRangeTextInfo(AfterType, NameLoc, SM, LO);
206 }
207
208 if (!PrevReplacementEnd.isValid() || PrevReplacementEnd.isMacroID())
209 return {};
210
211 SourceLocation AfterComma = PrevReplacementEnd;
212 if (const std::optional<Token> NextTok =
213 lexer::findNextTokenSkippingComments(AfterComma, SM, LO)) {
214 if (NextTok->is(tok::comma)) {
215 AfterComma =
216 Lexer::getLocForEndOfToken(NextTok->getLocation(), 0, SM, LO);
217 }
218 }
219
220 return getRangeTextInfo(AfterComma, NameLoc, SM, LO);
221}
222
223static void stripLeadingComma(RangeTextInfo &Info) {
224 const StringRef::size_type NonWs = findFirstNonWhitespace(Info.Text);
225 if (NonWs != StringRef::npos && Info.Text[NonWs] == ',')
226 Info.Text.erase(0, NonWs + 1);
227}
228
229static constexpr StringRef ExternCDeclName = "extern-c-decl";
230static constexpr StringRef ParentDeclName = "parent-decl";
231static constexpr StringRef TagDeclName = "tag-decl";
232static constexpr StringRef TypedefName = "typedef";
233static constexpr StringRef DeclStmtName = "decl-stmt";
234
236 : ClangTidyCheck(Name, Context),
237 IgnoreMacros(Options.get("IgnoreMacros", true)),
238 IgnoreExternC(Options.get("IgnoreExternC", false)) {}
239
241 Options.store(Opts, "IgnoreMacros", IgnoreMacros);
242 Options.store(Opts, "IgnoreExternC", IgnoreExternC);
243}
244
245void UseUsingCheck::registerMatchers(MatchFinder *Finder) {
246 Finder->addMatcher(
247 typedefDecl(
248 unless(isInstantiated()),
249 optionally(hasAncestor(
250 linkageSpecDecl(isExternCLinkage()).bind(ExternCDeclName))),
251 anyOf(hasParent(decl().bind(ParentDeclName)),
252 hasParent(declStmt().bind(DeclStmtName))))
253 .bind(TypedefName),
254 this);
255
256 // This matcher is used to find tag declarations in source code within
257 // typedefs. They appear in the AST just *prior* to the typedefs.
258 Finder->addMatcher(
259 tagDecl(
260 anyOf(allOf(unless(anyOf(isImplicit(),
261 classTemplateSpecializationDecl())),
262 anyOf(hasParent(decl().bind(ParentDeclName)),
263 hasParent(declStmt().bind(DeclStmtName)))),
264 // We want the parent of the ClassTemplateDecl, not the parent
265 // of the specialization.
266 classTemplateSpecializationDecl(hasAncestor(classTemplateDecl(
267 anyOf(hasParent(decl().bind(ParentDeclName)),
268 hasParent(declStmt().bind(DeclStmtName))))))))
269 .bind(TagDeclName),
270 this);
271}
272
273void UseUsingCheck::check(const MatchFinder::MatchResult &Result) {
274 const auto *ParentDecl = Result.Nodes.getNodeAs<Decl>(ParentDeclName);
275
276 if (!ParentDecl) {
277 const auto *ParentDeclStmt = Result.Nodes.getNodeAs<DeclStmt>(DeclStmtName);
278 if (ParentDeclStmt) {
279 if (ParentDeclStmt->isSingleDecl())
280 ParentDecl = ParentDeclStmt->getSingleDecl();
281 else
282 ParentDecl =
283 ParentDeclStmt->getDeclGroup().getDeclGroup()
284 [ParentDeclStmt->getDeclGroup().getDeclGroup().size() - 1];
285 }
286 }
287
288 if (!ParentDecl)
289 return;
290
291 const SourceManager &SM = *Result.SourceManager;
292 const LangOptions &LO = getLangOpts();
293
294 // Match CXXRecordDecl only to store the range of the last non-implicit full
295 // declaration, to later check whether it's within the typedef itself.
296 const auto *MatchedTagDecl = Result.Nodes.getNodeAs<TagDecl>(TagDeclName);
297 if (MatchedTagDecl) {
298 // It is not sufficient to just track the last TagDecl that we've seen,
299 // because if one struct or union is nested inside another, the last TagDecl
300 // before the typedef will be the nested one (PR#50990). Therefore, we also
301 // keep track of the parent declaration, so that we can look up the last
302 // TagDecl that is a sibling of the typedef in the AST.
303 if (MatchedTagDecl->isThisDeclarationADefinition())
304 LastTagDeclRanges[ParentDecl] = MatchedTagDecl->getSourceRange();
305 return;
306 }
307
308 const auto *MatchedDecl = Result.Nodes.getNodeAs<TypedefDecl>(TypedefName);
309 if (MatchedDecl->getLocation().isInvalid())
310 return;
311
312 const auto *ExternCDecl =
313 Result.Nodes.getNodeAs<LinkageSpecDecl>(ExternCDeclName);
314 if (ExternCDecl && IgnoreExternC)
315 return;
316
317 const SourceLocation StartLoc = MatchedDecl->getBeginLoc();
318
319 if (StartLoc.isMacroID() && IgnoreMacros)
320 return;
321
322 static constexpr StringRef UseUsingWarning =
323 "use 'using' instead of 'typedef'";
324
325 // Warn at StartLoc but do not fix if there is macro or array.
326 if (MatchedDecl->getUnderlyingType()->isArrayType() || StartLoc.isMacroID()) {
327 diag(StartLoc, UseUsingWarning);
328 return;
329 }
330
331 const TypeLoc TL = MatchedDecl->getTypeSourceInfo()->getTypeLoc();
332
333 struct TypeInfo {
334 SourceRange Range;
335 bool FunctionPointerCase = false;
336 bool Valid = false;
337 std::string Type;
338 std::string Qualifier;
339 };
340
341 const TypeInfo TI = [&] {
342 TypeInfo Info;
343 Info.Range = TL.getSourceRange();
344
345 // Function pointer case, get the left and right side of the identifier
346 // without the identifier.
347 if (Info.Range.fullyContains(MatchedDecl->getLocation())) {
348 Info.FunctionPointerCase = true;
349 if (std::optional<std::string> Type = getFunctionPointerTypeText(
350 Info.Range, MatchedDecl->getLocation(), SM, LO)) {
351 Info.Type = *Type;
352 Info.Valid = true;
353 }
354 return Info;
355 }
356
357 std::string ExtraReference;
358 if (MainTypeEndLoc.isValid() && Info.Range.fullyContains(MainTypeEndLoc)) {
359 // Each type introduced in a typedef can specify being a reference or
360 // pointer type separately, so we need to figure out if the new using-decl
361 // needs to be to a reference or pointer as well.
362 const SourceLocation Tok = lexer::findPreviousAnyTokenKind(
363 MatchedDecl->getLocation(), SM, LO, tok::TokenKind::star,
364 tok::TokenKind::amp, tok::TokenKind::comma,
365 tok::TokenKind::kw_typedef);
366
367 const std::optional<std::string> Reference = getSourceText(
368 CharSourceRange::getCharRange(Tok, Tok.getLocWithOffset(1)), SM, LO);
369 if (!Reference)
370 return Info;
371 ExtraReference = *Reference;
372
373 if (ExtraReference != "*" && ExtraReference != "&")
374 ExtraReference.clear();
375
376 Info.Range.setEnd(MainTypeEndLoc);
377 }
378
379 if (std::optional<std::string> Type =
380 getSourceText(CharSourceRange::getTokenRange(Info.Range), SM, LO)) {
381 Info.Type = *Type;
382 Info.Qualifier = ExtraReference;
383 Info.Valid = true;
384 }
385 return Info;
386 }();
387
388 if (!TI.Valid) {
389 diag(StartLoc, UseUsingWarning);
390 return;
391 }
392
393 const SourceRange TypeRange = TI.Range;
394 const bool FunctionPointerCase = TI.FunctionPointerCase;
395 std::string Type = TI.Type;
396 const std::string QualifierStr = TI.Qualifier;
397 const StringRef Name = MatchedDecl->getName();
398 const SourceLocation NameLoc = MatchedDecl->getLocation();
399 SourceRange ReplaceRange = MatchedDecl->getSourceRange();
400 const SourceLocation PrevReplacementEnd = LastReplacementEnd;
401
402 // typedefs with multiple comma-separated definitions produce multiple
403 // consecutive TypedefDecl nodes whose SourceRanges overlap. Each range starts
404 // at the "typedef" and then continues *across* previous definitions through
405 // the end of the current TypedefDecl definition.
406 // But also we need to check that the ranges belong to the same file because
407 // different files may contain overlapping ranges.
408 std::string Using = "using ";
409 const bool IsFirstTypedefInGroup =
410 ReplaceRange.getBegin().isMacroID() ||
411 (Result.SourceManager->getFileID(ReplaceRange.getBegin()) !=
412 Result.SourceManager->getFileID(LastReplacementEnd)) ||
413 (ReplaceRange.getBegin() >= LastReplacementEnd);
414
415 if (IsFirstTypedefInGroup) {
416 // This is the first (and possibly the only) TypedefDecl in a typedef. Save
417 // Type and Name in case we find subsequent TypedefDecl's in this typedef.
418 FirstTypedefType = Type;
419 FirstTypedefName = Name.str();
420 MainTypeEndLoc = TL.getEndLoc();
421 } else {
422 // This is additional TypedefDecl in a comma-separated typedef declaration.
423 // Start replacement *after* prior replacement and separate with semicolon.
424 ReplaceRange.setBegin(LastReplacementEnd);
425 Using = ";\nusing ";
426
427 // If this additional TypedefDecl's Type starts with the first TypedefDecl's
428 // type, make this using statement refer back to the first type, e.g. make
429 // "typedef int Foo, *Foo_p;" -> "using Foo = int;\nusing Foo_p = Foo*;"
430 if (Type == FirstTypedefType && !QualifierStr.empty())
431 Type = FirstTypedefName;
432 }
433
434 const RangeTextInfo LeadingTextInfo = getLeadingTextInfo(
435 IsFirstTypedefInGroup, ReplaceRange, TypeRange, SM, LO);
436 RangeTextInfo SuffixTextInfo =
437 getSuffixTextInfo(FunctionPointerCase, IsFirstTypedefInGroup,
438 PrevReplacementEnd, TypeRange, NameLoc, SM, LO);
439 if (!IsFirstTypedefInGroup)
440 stripLeadingComma(SuffixTextInfo);
441
442 const bool SuffixHasComment = SuffixTextInfo.Tokens.HasComment;
443 std::string SuffixText;
444 if (SuffixHasComment) {
445 SuffixText = SuffixTextInfo.Text;
446 } else if (QualifierStr.empty() &&
447 findFirstNonWhitespace(SuffixTextInfo.Text) != StringRef::npos &&
448 SuffixTextInfo.Tokens.HasPointerOrRef &&
449 !SuffixTextInfo.Tokens.HasIdentifier) {
450 SuffixText = SuffixTextInfo.Text;
451 }
452 const std::string QualifierText = SuffixHasComment ? "" : QualifierStr;
453
454 if (!ReplaceRange.getEnd().isMacroID()) {
455 const SourceLocation::IntTy Offset = FunctionPointerCase ? 0 : Name.size();
456 LastReplacementEnd = ReplaceRange.getEnd().getLocWithOffset(Offset);
457 }
458
459 auto Diag = diag(ReplaceRange.getBegin(), UseUsingWarning);
460
461 // If typedef contains a full tag declaration, extract its full text.
462 auto LastTagDeclRange = LastTagDeclRanges.find(ParentDecl);
463 if (LastTagDeclRange != LastTagDeclRanges.end() &&
464 LastTagDeclRange->second.isValid() &&
465 ReplaceRange.fullyContains(LastTagDeclRange->second)) {
466 const std::optional<std::string> TagType = getSourceText(
467 CharSourceRange::getTokenRange(LastTagDeclRange->second), SM, LO);
468 if (!TagType)
469 return;
470 Type = *TagType;
471 }
472
473 std::string TypeExpr =
474 LeadingTextInfo.Text + Type + QualifierText + SuffixText;
475 TypeExpr = StringRef(TypeExpr).rtrim(" \t").str();
476 StringRef Assign = " = ";
477 if (!TypeExpr.empty() &&
478 (TypeExpr.front() == ' ' || TypeExpr.front() == '\t'))
479 Assign = " =";
480
481 const std::string Replacement = (Using + Name + Assign + TypeExpr).str();
482 Diag << FixItHint::CreateReplacement(ReplaceRange, Replacement);
483}
484} // namespace clang::tidy::modernize
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
UseUsingCheck(StringRef Name, ClangTidyContext *Context)
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
AST_MATCHER(BinaryOperator, isRelationalOperator)
static void stripLeadingComma(RangeTextInfo &Info)
static std::optional< std::string > getFunctionPointerTypeText(SourceRange TypeRange, SourceLocation NameLoc, const SourceManager &SM, const LangOptions &LO)
static RangeTextInfo getSuffixTextInfo(bool FunctionPointerCase, bool IsFirstTypedefInGroup, SourceLocation PrevReplacementEnd, SourceRange TypeRange, SourceLocation NameLoc, const SourceManager &SM, const LangOptions &LO)
static constexpr StringRef ExternCDeclName
static StringRef::size_type findFirstNonWhitespace(StringRef Text)
static constexpr StringRef ParentDeclName
static RangeTextInfo getLeadingTextInfo(bool IsFirstTypedefInGroup, SourceRange ReplaceRange, SourceRange TypeRange, const SourceManager &SM, const LangOptions &LO)
static constexpr StringRef TagDeclName
static TokenRangeInfo getTokenRangeInfo(CharSourceRange Range, const SourceManager &SM, const LangOptions &LangOpts)
static std::optional< std::string > getSourceText(CharSourceRange Range, const SourceManager &SM, const LangOptions &LangOpts)
static constexpr StringRef TypedefName
static RangeTextInfo getRangeTextInfo(SourceLocation Begin, SourceLocation End, const SourceManager &SM, const LangOptions &LangOpts)
static constexpr StringRef DeclStmtName
llvm::StringMap< ClangTidyValue > OptionMap