blob: 8da04695542503fd099f77d21114fad7ef4df50c [file] [log] [blame]
//===--- ReservedIdentifierCheck.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 "ReservedIdentifierCheck.h"
#include "../utils/Matchers.h"
#include "../utils/OptionsUtils.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Lex/Token.h"
#include <algorithm>
#include <cctype>
// FixItHint
using namespace clang::ast_matchers;
namespace clang {
namespace tidy {
namespace bugprone {
static const char DoubleUnderscoreTag[] = "du";
static const char UnderscoreCapitalTag[] = "uc";
static const char GlobalUnderscoreTag[] = "global-under";
static const char NonReservedTag[] = "non-reserved";
static const char Message[] =
"declaration uses identifier '%0', which is %select{a reserved "
"identifier|not a reserved identifier|reserved in the global namespace}1";
static int getMessageSelectIndex(StringRef Tag) {
if (Tag == NonReservedTag)
return 1;
if (Tag == GlobalUnderscoreTag)
return 2;
return 0;
}
ReservedIdentifierCheck::ReservedIdentifierCheck(StringRef Name,
ClangTidyContext *Context)
: RenamerClangTidyCheck(Name, Context),
Invert(Options.get("Invert", false)),
AllowedIdentifiers(utils::options::parseStringList(
Options.get("AllowedIdentifiers", ""))) {}
void ReservedIdentifierCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
RenamerClangTidyCheck::storeOptions(Opts);
Options.store(Opts, "Invert", Invert);
Options.store(Opts, "AllowedIdentifiers",
utils::options::serializeStringList(AllowedIdentifiers));
}
static std::string collapseConsecutive(StringRef Str, char C) {
std::string Result;
std::unique_copy(Str.begin(), Str.end(), std::back_inserter(Result),
[C](char A, char B) { return A == C && B == C; });
return Result;
}
static bool hasReservedDoubleUnderscore(StringRef Name,
const LangOptions &LangOpts) {
if (LangOpts.CPlusPlus)
return Name.find("__") != StringRef::npos;
return Name.startswith("__");
}
static Optional<std::string>
getDoubleUnderscoreFixup(StringRef Name, const LangOptions &LangOpts) {
if (hasReservedDoubleUnderscore(Name, LangOpts))
return collapseConsecutive(Name, '_');
return None;
}
static bool startsWithUnderscoreCapital(StringRef Name) {
return Name.size() >= 2 && Name[0] == '_' && std::isupper(Name[1]);
}
static Optional<std::string> getUnderscoreCapitalFixup(StringRef Name) {
if (startsWithUnderscoreCapital(Name))
return std::string(Name.drop_front(1));
return None;
}
static bool startsWithUnderscoreInGlobalNamespace(StringRef Name,
bool IsInGlobalNamespace) {
return IsInGlobalNamespace && Name.size() >= 1 && Name[0] == '_';
}
static Optional<std::string>
getUnderscoreGlobalNamespaceFixup(StringRef Name, bool IsInGlobalNamespace) {
if (startsWithUnderscoreInGlobalNamespace(Name, IsInGlobalNamespace))
return std::string(Name.drop_front(1));
return None;
}
static std::string getNonReservedFixup(std::string Name) {
assert(!Name.empty());
if (Name[0] == '_' || std::isupper(Name[0]))
Name.insert(Name.begin(), '_');
else
Name.insert(Name.begin(), 2, '_');
return Name;
}
static Optional<RenamerClangTidyCheck::FailureInfo>
getFailureInfoImpl(StringRef Name, bool IsInGlobalNamespace,
const LangOptions &LangOpts, bool Invert,
ArrayRef<std::string> AllowedIdentifiers) {
assert(!Name.empty());
if (llvm::is_contained(AllowedIdentifiers, Name))
return None;
// TODO: Check for names identical to language keywords, and other names
// specifically reserved by language standards, e.g. C++ 'zombie names' and C
// future library directions
using FailureInfo = RenamerClangTidyCheck::FailureInfo;
if (!Invert) {
Optional<FailureInfo> Info;
auto AppendFailure = [&](StringRef Kind, std::string &&Fixup) {
if (!Info) {
Info = FailureInfo{std::string(Kind), std::move(Fixup)};
} else {
Info->KindName += Kind;
Info->Fixup = std::move(Fixup);
}
};
auto InProgressFixup = [&] {
return Info
.map([](const FailureInfo &Info) { return StringRef(Info.Fixup); })
.getValueOr(Name);
};
if (auto Fixup = getDoubleUnderscoreFixup(InProgressFixup(), LangOpts))
AppendFailure(DoubleUnderscoreTag, std::move(*Fixup));
if (auto Fixup = getUnderscoreCapitalFixup(InProgressFixup()))
AppendFailure(UnderscoreCapitalTag, std::move(*Fixup));
if (auto Fixup = getUnderscoreGlobalNamespaceFixup(InProgressFixup(),
IsInGlobalNamespace))
AppendFailure(GlobalUnderscoreTag, std::move(*Fixup));
return Info;
}
if (!(hasReservedDoubleUnderscore(Name, LangOpts) ||
startsWithUnderscoreCapital(Name) ||
startsWithUnderscoreInGlobalNamespace(Name, IsInGlobalNamespace)))
return FailureInfo{NonReservedTag, getNonReservedFixup(std::string(Name))};
return None;
}
Optional<RenamerClangTidyCheck::FailureInfo>
ReservedIdentifierCheck::getDeclFailureInfo(const NamedDecl *Decl,
const SourceManager &) const {
assert(Decl && Decl->getIdentifier() && !Decl->getName().empty() &&
!Decl->isImplicit() &&
"Decl must be an explicit identifier with a name.");
return getFailureInfoImpl(Decl->getName(),
isa<TranslationUnitDecl>(Decl->getDeclContext()),
getLangOpts(), Invert, AllowedIdentifiers);
}
Optional<RenamerClangTidyCheck::FailureInfo>
ReservedIdentifierCheck::getMacroFailureInfo(const Token &MacroNameTok,
const SourceManager &) const {
return getFailureInfoImpl(MacroNameTok.getIdentifierInfo()->getName(), true,
getLangOpts(), Invert, AllowedIdentifiers);
}
RenamerClangTidyCheck::DiagInfo
ReservedIdentifierCheck::getDiagInfo(const NamingCheckId &ID,
const NamingCheckFailure &Failure) const {
return DiagInfo{Message, [&](DiagnosticBuilder &Diag) {
Diag << ID.second
<< getMessageSelectIndex(Failure.Info.KindName);
}};
}
} // namespace bugprone
} // namespace tidy
} // namespace clang