1 | //===--- UseUsingCheck.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 "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 | |
19 | using namespace clang::ast_matchers; |
20 | namespace { |
21 | |
22 | AST_MATCHER(clang::LinkageSpecDecl, isExternCLinkage) { |
23 | return Node.getLanguage() == clang::LinkageSpecLanguageIDs::C; |
24 | } |
25 | } // namespace |
26 | |
27 | namespace clang::tidy::modernize { |
28 | |
29 | static constexpr llvm::StringLiteral ExternCDeclName = "extern-c-decl" ; |
30 | static constexpr llvm::StringLiteral ParentDeclName = "parent-decl" ; |
31 | static constexpr llvm::StringLiteral TagDeclName = "tag-decl" ; |
32 | static constexpr llvm::StringLiteral TypedefName = "typedef" ; |
33 | static constexpr llvm::StringLiteral DeclStmtName = "decl-stmt" ; |
34 | |
35 | UseUsingCheck::UseUsingCheck(StringRef Name, ClangTidyContext *Context) |
36 | : ClangTidyCheck(Name, Context), |
37 | IgnoreMacros(Options.getLocalOrGlobal(LocalName: "IgnoreMacros" , Default: true)), |
38 | IgnoreExternC(Options.get(LocalName: "IgnoreExternC" , Default: false)) {} |
39 | |
40 | void UseUsingCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
41 | Options.store(Options&: Opts, LocalName: "IgnoreMacros" , Value: IgnoreMacros); |
42 | Options.store(Options&: Opts, LocalName: "IgnoreExternC" , Value: IgnoreExternC); |
43 | } |
44 | |
45 | void UseUsingCheck::registerMatchers(MatchFinder *Finder) { |
46 | Finder->addMatcher( |
47 | NodeMatch: typedefDecl( |
48 | unless(isInstantiated()), |
49 | optionally(hasAncestor( |
50 | linkageSpecDecl(isExternCLinkage()).bind(ID: ExternCDeclName))), |
51 | anyOf(hasParent(decl().bind(ID: ParentDeclName)), |
52 | hasParent(declStmt().bind(ID: DeclStmtName)))) |
53 | .bind(ID: TypedefName), |
54 | Action: this); |
55 | |
56 | // This matcher is used to find tag declarations in source code within |
57 | // typedefs. They appear in the AST just *prior* to the typedefs. |
58 | Finder->addMatcher( |
59 | NodeMatch: tagDecl( |
60 | anyOf(allOf(unless(anyOf(isImplicit(), |
61 | classTemplateSpecializationDecl())), |
62 | anyOf(hasParent(decl().bind(ID: ParentDeclName)), |
63 | hasParent(declStmt().bind(ID: DeclStmtName)))), |
64 | // We want the parent of the ClassTemplateDecl, not the parent |
65 | // of the specialization. |
66 | classTemplateSpecializationDecl(hasAncestor(classTemplateDecl( |
67 | anyOf(hasParent(decl().bind(ID: ParentDeclName)), |
68 | hasParent(declStmt().bind(ID: DeclStmtName)))))))) |
69 | .bind(ID: TagDeclName), |
70 | Action: this); |
71 | } |
72 | |
73 | void UseUsingCheck::check(const MatchFinder::MatchResult &Result) { |
74 | const auto *ParentDecl = Result.Nodes.getNodeAs<Decl>(ID: ParentDeclName); |
75 | |
76 | if (!ParentDecl) { |
77 | const auto *ParentDeclStmt = Result.Nodes.getNodeAs<DeclStmt>(ID: DeclStmtName); |
78 | if (ParentDeclStmt) { |
79 | if (ParentDeclStmt->isSingleDecl()) |
80 | ParentDecl = ParentDeclStmt->getSingleDecl(); |
81 | else |
82 | ParentDecl = |
83 | ParentDeclStmt->getDeclGroup().getDeclGroup() |
84 | [ParentDeclStmt->getDeclGroup().getDeclGroup().size() - 1]; |
85 | } |
86 | } |
87 | |
88 | if (!ParentDecl) |
89 | return; |
90 | |
91 | const SourceManager &SM = *Result.SourceManager; |
92 | const LangOptions &LO = getLangOpts(); |
93 | |
94 | // Match CXXRecordDecl only to store the range of the last non-implicit full |
95 | // declaration, to later check whether it's within the typdef itself. |
96 | const auto *MatchedTagDecl = Result.Nodes.getNodeAs<TagDecl>(ID: TagDeclName); |
97 | if (MatchedTagDecl) { |
98 | // It is not sufficient to just track the last TagDecl that we've seen, |
99 | // because if one struct or union is nested inside another, the last TagDecl |
100 | // before the typedef will be the nested one (PR#50990). Therefore, we also |
101 | // keep track of the parent declaration, so that we can look up the last |
102 | // TagDecl that is a sibling of the typedef in the AST. |
103 | if (MatchedTagDecl->isThisDeclarationADefinition()) |
104 | LastTagDeclRanges[ParentDecl] = MatchedTagDecl->getSourceRange(); |
105 | return; |
106 | } |
107 | |
108 | const auto *MatchedDecl = Result.Nodes.getNodeAs<TypedefDecl>(ID: TypedefName); |
109 | if (MatchedDecl->getLocation().isInvalid()) |
110 | return; |
111 | |
112 | const auto *ExternCDecl = |
113 | Result.Nodes.getNodeAs<LinkageSpecDecl>(ID: ExternCDeclName); |
114 | if (ExternCDecl && IgnoreExternC) |
115 | return; |
116 | |
117 | SourceLocation StartLoc = MatchedDecl->getBeginLoc(); |
118 | |
119 | if (StartLoc.isMacroID() && IgnoreMacros) |
120 | return; |
121 | |
122 | static const char *UseUsingWarning = "use 'using' instead of 'typedef'" ; |
123 | |
124 | // Warn at StartLoc but do not fix if there is macro or array. |
125 | if (MatchedDecl->getUnderlyingType()->isArrayType() || StartLoc.isMacroID()) { |
126 | diag(Loc: StartLoc, Description: UseUsingWarning); |
127 | return; |
128 | } |
129 | |
130 | const TypeLoc TL = MatchedDecl->getTypeSourceInfo()->getTypeLoc(); |
131 | |
132 | auto [Type, QualifierStr] = [MatchedDecl, this, &TL, &SM, |
133 | &LO]() -> std::pair<std::string, std::string> { |
134 | SourceRange TypeRange = TL.getSourceRange(); |
135 | |
136 | // Function pointer case, get the left and right side of the identifier |
137 | // without the identifier. |
138 | if (TypeRange.fullyContains(other: MatchedDecl->getLocation())) { |
139 | const auto RangeLeftOfIdentifier = CharSourceRange::getCharRange( |
140 | TypeRange.getBegin(), MatchedDecl->getLocation()); |
141 | const auto RangeRightOfIdentifier = CharSourceRange::getCharRange( |
142 | Lexer::getLocForEndOfToken(Loc: MatchedDecl->getLocation(), Offset: 0, SM, LangOpts: LO), |
143 | Lexer::getLocForEndOfToken(Loc: TypeRange.getEnd(), Offset: 0, SM, LangOpts: LO)); |
144 | const std::string VerbatimType = |
145 | (Lexer::getSourceText(Range: RangeLeftOfIdentifier, SM, LangOpts: LO) + |
146 | Lexer::getSourceText(Range: RangeRightOfIdentifier, SM, LangOpts: LO)) |
147 | .str(); |
148 | return {VerbatimType, "" }; |
149 | } |
150 | |
151 | StringRef = "" ; |
152 | if (MainTypeEndLoc.isValid() && TypeRange.fullyContains(other: MainTypeEndLoc)) { |
153 | // Each type introduced in a typedef can specify being a reference or |
154 | // pointer type seperately, so we need to sigure out if the new using-decl |
155 | // needs to be to a reference or pointer as well. |
156 | const SourceLocation Tok = utils::lexer::findPreviousAnyTokenKind( |
157 | MatchedDecl->getLocation(), SM, LO, tok::TokenKind::star, |
158 | tok::TokenKind::amp, tok::TokenKind::comma, |
159 | tok::TokenKind::kw_typedef); |
160 | |
161 | ExtraReference = Lexer::getSourceText( |
162 | Range: CharSourceRange::getCharRange(B: Tok, E: Tok.getLocWithOffset(Offset: 1)), SM, LangOpts: LO); |
163 | |
164 | if (ExtraReference != "*" && ExtraReference != "&" ) |
165 | ExtraReference = "" ; |
166 | |
167 | TypeRange.setEnd(MainTypeEndLoc); |
168 | } |
169 | return { |
170 | Lexer::getSourceText(Range: CharSourceRange::getTokenRange(R: TypeRange), SM, LangOpts: LO) |
171 | .str(), |
172 | ExtraReference.str()}; |
173 | }(); |
174 | StringRef Name = MatchedDecl->getName(); |
175 | SourceRange ReplaceRange = MatchedDecl->getSourceRange(); |
176 | |
177 | // typedefs with multiple comma-separated definitions produce multiple |
178 | // consecutive TypedefDecl nodes whose SourceRanges overlap. Each range starts |
179 | // at the "typedef" and then continues *across* previous definitions through |
180 | // the end of the current TypedefDecl definition. |
181 | // But also we need to check that the ranges belong to the same file because |
182 | // different files may contain overlapping ranges. |
183 | std::string Using = "using " ; |
184 | if (ReplaceRange.getBegin().isMacroID() || |
185 | (Result.SourceManager->getFileID(SpellingLoc: ReplaceRange.getBegin()) != |
186 | Result.SourceManager->getFileID(SpellingLoc: LastReplacementEnd)) || |
187 | (ReplaceRange.getBegin() >= LastReplacementEnd)) { |
188 | // This is the first (and possibly the only) TypedefDecl in a typedef. Save |
189 | // Type and Name in case we find subsequent TypedefDecl's in this typedef. |
190 | FirstTypedefType = Type; |
191 | FirstTypedefName = Name.str(); |
192 | MainTypeEndLoc = TL.getEndLoc(); |
193 | } else { |
194 | // This is additional TypedefDecl in a comma-separated typedef declaration. |
195 | // Start replacement *after* prior replacement and separate with semicolon. |
196 | ReplaceRange.setBegin(LastReplacementEnd); |
197 | Using = ";\nusing " ; |
198 | |
199 | // If this additional TypedefDecl's Type starts with the first TypedefDecl's |
200 | // type, make this using statement refer back to the first type, e.g. make |
201 | // "typedef int Foo, *Foo_p;" -> "using Foo = int;\nusing Foo_p = Foo*;" |
202 | if (Type == FirstTypedefType && !QualifierStr.empty()) |
203 | Type = FirstTypedefName; |
204 | } |
205 | |
206 | if (!ReplaceRange.getEnd().isMacroID()) { |
207 | const SourceLocation::IntTy Offset = |
208 | MatchedDecl->getFunctionType() ? 0 : Name.size(); |
209 | LastReplacementEnd = ReplaceRange.getEnd().getLocWithOffset(Offset); |
210 | } |
211 | |
212 | auto Diag = diag(Loc: ReplaceRange.getBegin(), Description: UseUsingWarning); |
213 | |
214 | // If typedef contains a full tag declaration, extract its full text. |
215 | auto LastTagDeclRange = LastTagDeclRanges.find(Val: ParentDecl); |
216 | if (LastTagDeclRange != LastTagDeclRanges.end() && |
217 | LastTagDeclRange->second.isValid() && |
218 | ReplaceRange.fullyContains(other: LastTagDeclRange->second)) { |
219 | Type = std::string(Lexer::getSourceText( |
220 | Range: CharSourceRange::getTokenRange(R: LastTagDeclRange->second), SM, LangOpts: LO)); |
221 | if (Type.empty()) |
222 | return; |
223 | } |
224 | |
225 | std::string Replacement = (Using + Name + " = " + Type + QualifierStr).str(); |
226 | Diag << FixItHint::CreateReplacement(RemoveRange: ReplaceRange, Code: Replacement); |
227 | } |
228 | } // namespace clang::tidy::modernize |
229 | |