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