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("IgnoreMacros", true)), 38 IgnoreExternC(Options.get("IgnoreExternC", false)) {} 39 40 void UseUsingCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { 41 Options.store(Opts, "IgnoreMacros", IgnoreMacros); 42 Options.store(Opts, "IgnoreExternC", IgnoreExternC); 43 } 44 45 void UseUsingCheck::registerMatchers(MatchFinder *Finder) { 46 Finder->addMatcher( 47 typedefDecl( 48 unless(isInstantiated()), 49 optionally(hasAncestor( 50 linkageSpecDecl(isExternCLinkage()).bind(ExternCDeclName))), 51 anyOf(hasParent(decl().bind(ParentDeclName)), 52 hasParent(declStmt().bind(DeclStmtName)))) 53 .bind(TypedefName), 54 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 tagDecl( 60 anyOf(allOf(unless(anyOf(isImplicit(), 61 classTemplateSpecializationDecl())), 62 anyOf(hasParent(decl().bind(ParentDeclName)), 63 hasParent(declStmt().bind(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(ParentDeclName)), 68 hasParent(declStmt().bind(DeclStmtName)))))))) 69 .bind(TagDeclName), 70 this); 71 } 72 73 void UseUsingCheck::check(const MatchFinder::MatchResult &Result) { 74 const auto *ParentDecl = Result.Nodes.getNodeAs<Decl>(ParentDeclName); 75 76 if (!ParentDecl) { 77 const auto *ParentDeclStmt = Result.Nodes.getNodeAs<DeclStmt>(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>(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>(TypedefName); 109 if (MatchedDecl->getLocation().isInvalid()) 110 return; 111 112 const auto *ExternCDecl = 113 Result.Nodes.getNodeAs<LinkageSpecDecl>(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(StartLoc, 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(MatchedDecl->getLocation())) { 139 const auto RangeLeftOfIdentifier = CharSourceRange::getCharRange( 140 TypeRange.getBegin(), MatchedDecl->getLocation()); 141 const auto RangeRightOfIdentifier = CharSourceRange::getCharRange( 142 Lexer::getLocForEndOfToken(MatchedDecl->getLocation(), 0, SM, LO), 143 Lexer::getLocForEndOfToken(TypeRange.getEnd(), 0, SM, LO)); 144 const std::string VerbatimType = 145 (Lexer::getSourceText(RangeLeftOfIdentifier, SM, LO) + 146 Lexer::getSourceText(RangeRightOfIdentifier, SM, LO)) 147 .str(); 148 return {VerbatimType, ""}; 149 } 150 151 StringRef ExtraReference = ""; 152 if (MainTypeEndLoc.isValid() && TypeRange.fullyContains(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 CharSourceRange::getCharRange(Tok, Tok.getLocWithOffset(1)), SM, LO); 163 164 if (ExtraReference != "*" && ExtraReference != "&") 165 ExtraReference = ""; 166 167 TypeRange.setEnd(MainTypeEndLoc); 168 } 169 return { 170 Lexer::getSourceText(CharSourceRange::getTokenRange(TypeRange), SM, 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(ReplaceRange.getBegin()) != 186 Result.SourceManager->getFileID(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(ReplaceRange.getBegin(), UseUsingWarning); 213 214 // If typedef contains a full tag declaration, extract its full text. 215 auto LastTagDeclRange = LastTagDeclRanges.find(ParentDecl); 216 if (LastTagDeclRange != LastTagDeclRanges.end() && 217 LastTagDeclRange->second.isValid() && 218 ReplaceRange.fullyContains(LastTagDeclRange->second)) { 219 Type = std::string(Lexer::getSourceText( 220 CharSourceRange::getTokenRange(LastTagDeclRange->second), SM, LO)); 221 if (Type.empty()) 222 return; 223 } 224 225 std::string Replacement = (Using + Name + " = " + Type + QualifierStr).str(); 226 Diag << FixItHint::CreateReplacement(ReplaceRange, Replacement); 227 } 228 } // namespace clang::tidy::modernize 229