| 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 | |